php-src/Zend/zend_gc.c
Nikita Popov 589542f50c Remove properties HT from nested GC data
The properties HT may be a GC root itself, so we need to remove it.
I'm not sure this issue actually applies to PHP 7.2, but committing
it there to be safe. As seen from the test case, the handling here
is rather buggy on 7.2.
2019-08-26 17:49:37 +02:00

1243 lines
30 KiB
C

/*
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
| Copyright (c) 1998-2018 Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: David Wang <planetbeing@gmail.com> |
| Dmitry Stogov <dmitry@zend.com> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
/**
* zend_gc_collect_cycles
* ======================
*
* Colors and its meaning
* ----------------------
*
* BLACK (GC_BLACK) - In use or free.
* GREY (GC_GREY) - Possible member of cycle.
* WHITE (GC_WHITE) - Member of garbage cycle.
* PURPLE (GC_PURPLE) - Possible root of cycle.
*
* Colors described in the paper but not used
* ------------------------------------------
*
* GREEN - Acyclic
* RED - Candidate cycle underogin
* ORANGE - Candidate cycle awaiting epoch boundary.
*
*
* Flow
* =====
*
* The garbage collect cycle starts from 'gc_mark_roots', which traverses the
* possible roots, and calls mark_grey for roots are marked purple with
* depth-first traverse.
*
* After all possible roots are traversed and marked,
* gc_scan_roots will be called, and each root will be called with
* gc_scan(root->ref)
*
* gc_scan checkes the colors of possible members.
*
* If the node is marked as grey and the refcount > 0
* gc_scan_black will be called on that node to scan it's subgraph.
* otherwise (refcount == 0), it marks the node white.
*
* A node MAY be added to possbile roots when ZEND_UNSET_VAR happens or
* zend_assign_to_variable is called only when possible garbage node is
* produced.
* gc_possible_root() will be called to add the nodes to possible roots.
*
*
* For objects, we call their get_gc handler (by default 'zend_std_get_gc') to
* get the object properties to scan.
*
*
* @see http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
*/
#include "zend.h"
#include "zend_API.h"
/* one (0) is reserved */
#define GC_ROOT_BUFFER_MAX_ENTRIES 10001
#define GC_HAS_DESTRUCTORS (1<<0)
#ifndef ZEND_GC_DEBUG
# define ZEND_GC_DEBUG 0
#endif
#ifdef ZTS
ZEND_API int gc_globals_id;
#else
ZEND_API zend_gc_globals gc_globals;
#endif
ZEND_API int (*gc_collect_cycles)(void);
#if ZEND_GC_DEBUG > 1
# define GC_TRACE(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__);
# define GC_TRACE_REF(ref, format, ...) \
do { \
gc_trace_ref((zend_refcounted *) ref); \
fprintf(stderr, format "\n", ##__VA_ARGS__); \
} while (0)
# define GC_TRACE_SET_COLOR(ref, color) \
GC_TRACE_REF(ref, "->%s", gc_color_name(color))
#else
# define GC_TRACE_REF(ref, format, ...)
# define GC_TRACE_SET_COLOR(ref, new_color)
# define GC_TRACE(str)
#endif
#define GC_REF_SET_ADDRESS(ref, a) \
GC_INFO_SET_ADDRESS(GC_INFO(ref), a)
#define GC_REF_GET_COLOR(ref) \
GC_INFO_GET_COLOR(GC_INFO(ref))
#define GC_REF_SET_COLOR(ref, c) \
do { GC_TRACE_SET_COLOR(ref, c); GC_INFO_SET_COLOR(GC_INFO(ref), c); } while (0)
#define GC_REF_SET_BLACK(ref) \
do { GC_TRACE_SET_COLOR(ref, GC_BLACK); GC_INFO_SET_BLACK(GC_INFO(ref)); } while (0)
#define GC_REF_SET_PURPLE(ref) \
do { GC_TRACE_SET_COLOR(ref, GC_PURPLE); GC_INFO_SET_PURPLE(GC_INFO(ref)); } while (0)
#if ZEND_GC_DEBUG > 1
static const char *gc_color_name(uint32_t color) {
switch (color) {
case GC_BLACK: return "black";
case GC_WHITE: return "white";
case GC_GREY: return "grey";
case GC_PURPLE: return "purple";
default: return "unknown";
}
}
static void gc_trace_ref(zend_refcounted *ref) {
if (GC_TYPE(ref) == IS_OBJECT) {
zend_object *obj = (zend_object *) ref;
fprintf(stderr, "[%p] rc=%d addr=%d %s object(%s)#%d ",
ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
gc_color_name(GC_REF_GET_COLOR(ref)),
obj->ce->name->val, obj->handle);
} else if (GC_TYPE(ref) == IS_ARRAY) {
zend_array *arr = (zend_array *) ref;
fprintf(stderr, "[%p] rc=%d addr=%d %s array(%d) ",
ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
gc_color_name(GC_REF_GET_COLOR(ref)),
zend_hash_num_elements(arr));
} else {
fprintf(stderr, "[%p] rc=%d addr=%d %s %s ",
ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
gc_color_name(GC_REF_GET_COLOR(ref)),
zend_get_type_by_const(GC_TYPE(ref)));
}
}
#endif
static zend_always_inline void gc_remove_from_roots(gc_root_buffer *root)
{
root->next->prev = root->prev;
root->prev->next = root->next;
root->prev = GC_G(unused);
GC_G(unused) = root;
GC_BENCH_DEC(root_buf_length);
}
static zend_always_inline void gc_remove_from_additional_roots(gc_root_buffer *root)
{
root->next->prev = root->prev;
root->prev->next = root->next;
}
static void root_buffer_dtor(zend_gc_globals *gc_globals)
{
if (gc_globals->buf) {
free(gc_globals->buf);
gc_globals->buf = NULL;
}
}
static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
{
gc_globals->gc_enabled = 0;
gc_globals->gc_active = 0;
gc_globals->buf = NULL;
gc_globals->roots.next = &gc_globals->roots;
gc_globals->roots.prev = &gc_globals->roots;
gc_globals->unused = NULL;
gc_globals->next_to_free = NULL;
gc_globals->to_free.next = &gc_globals->to_free;
gc_globals->to_free.prev = &gc_globals->to_free;
gc_globals->gc_runs = 0;
gc_globals->collected = 0;
gc_globals->additional_buffer = NULL;
#if GC_BENCH
gc_globals->root_buf_length = 0;
gc_globals->root_buf_peak = 0;
gc_globals->zval_possible_root = 0;
gc_globals->zval_buffered = 0;
gc_globals->zval_remove_from_buffer = 0;
gc_globals->zval_marked_grey = 0;
#endif
}
ZEND_API void gc_globals_ctor(void)
{
#ifdef ZTS
ts_allocate_id(&gc_globals_id, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
#else
gc_globals_ctor_ex(&gc_globals);
#endif
}
ZEND_API void gc_globals_dtor(void)
{
#ifndef ZTS
root_buffer_dtor(&gc_globals);
#endif
}
ZEND_API void gc_reset(void)
{
GC_G(gc_runs) = 0;
GC_G(collected) = 0;
GC_G(gc_full) = 0;
#if GC_BENCH
GC_G(root_buf_length) = 0;
GC_G(root_buf_peak) = 0;
GC_G(zval_possible_root) = 0;
GC_G(zval_buffered) = 0;
GC_G(zval_remove_from_buffer) = 0;
GC_G(zval_marked_grey) = 0;
#endif
GC_G(roots).next = &GC_G(roots);
GC_G(roots).prev = &GC_G(roots);
GC_G(to_free).next = &GC_G(to_free);
GC_G(to_free).prev = &GC_G(to_free);
if (GC_G(buf)) {
GC_G(unused) = NULL;
GC_G(first_unused) = GC_G(buf) + 1;
} else {
GC_G(unused) = NULL;
GC_G(first_unused) = NULL;
GC_G(last_unused) = NULL;
}
GC_G(additional_buffer) = NULL;
}
ZEND_API void gc_init(void)
{
if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
gc_reset();
}
}
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
{
gc_root_buffer *newRoot;
if (UNEXPECTED(CG(unclean_shutdown)) || UNEXPECTED(GC_G(gc_active))) {
return;
}
ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
ZEND_ASSERT(EXPECTED(GC_REF_GET_COLOR(ref) == GC_BLACK));
ZEND_ASSERT(!GC_ADDRESS(GC_INFO(ref)));
GC_BENCH_INC(zval_possible_root);
newRoot = GC_G(unused);
if (newRoot) {
GC_G(unused) = newRoot->prev;
} else if (GC_G(first_unused) != GC_G(last_unused)) {
newRoot = GC_G(first_unused);
GC_G(first_unused)++;
} else {
if (!GC_G(gc_enabled)) {
return;
}
GC_REFCOUNT(ref)++;
gc_collect_cycles();
GC_REFCOUNT(ref)--;
if (UNEXPECTED(GC_REFCOUNT(ref)) == 0) {
zval_dtor_func(ref);
return;
}
if (UNEXPECTED(GC_INFO(ref))) {
return;
}
newRoot = GC_G(unused);
if (!newRoot) {
#if ZEND_GC_DEBUG
if (!GC_G(gc_full)) {
fprintf(stderr, "GC: no space to record new root candidate\n");
GC_G(gc_full) = 1;
}
#endif
return;
}
GC_G(unused) = newRoot->prev;
}
GC_TRACE_SET_COLOR(ref, GC_PURPLE);
GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE;
newRoot->ref = ref;
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;
GC_BENCH_INC(zval_buffered);
GC_BENCH_INC(root_buf_length);
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
}
static zend_always_inline gc_root_buffer* gc_find_additional_buffer(zend_refcounted *ref)
{
gc_additional_buffer *additional_buffer = GC_G(additional_buffer);
/* We have to check each additional_buffer to find which one holds the ref */
while (additional_buffer) {
uint32_t idx = GC_ADDRESS(GC_INFO(ref)) - GC_ROOT_BUFFER_MAX_ENTRIES;
if (idx < additional_buffer->used) {
gc_root_buffer *root = additional_buffer->buf + idx;
if (root->ref == ref) {
return root;
}
}
additional_buffer = additional_buffer->next;
}
ZEND_ASSERT(0);
return NULL;
}
ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
{
gc_root_buffer *root;
ZEND_ASSERT(GC_ADDRESS(GC_INFO(ref)));
GC_BENCH_INC(zval_remove_from_buffer);
if (EXPECTED(GC_ADDRESS(GC_INFO(ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
root = GC_G(buf) + GC_ADDRESS(GC_INFO(ref));
gc_remove_from_roots(root);
} else {
root = gc_find_additional_buffer(ref);
gc_remove_from_additional_roots(root);
}
if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
GC_TRACE_SET_COLOR(ref, GC_PURPLE);
}
GC_INFO(ref) = 0;
/* updete next root that is going to be freed */
if (GC_G(next_to_free) == root) {
GC_G(next_to_free) = root->next;
}
}
static void gc_scan_black(zend_refcounted *ref)
{
HashTable *ht;
Bucket *p, *end;
zval *zv;
tail_call:
ht = NULL;
GC_REF_SET_BLACK(ref);
if (GC_TYPE(ref) == IS_OBJECT) {
zend_object_get_gc_t get_gc;
zend_object *obj = (zend_object*)ref;
if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) &&
(get_gc = obj->handlers->get_gc) != NULL)) {
int n;
zval *zv, *end;
zval tmp;
ZVAL_OBJ(&tmp, obj);
ht = get_gc(&tmp, &zv, &n);
end = zv + n;
if (EXPECTED(!ht) || UNEXPECTED(GC_REF_GET_COLOR(ht) == GC_BLACK)) {
ht = NULL;
if (!n) return;
while (!Z_REFCOUNTED_P(--end)) {
if (zv == end) return;
}
} else {
GC_REF_SET_BLACK(ht);
}
while (zv != end) {
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
gc_scan_black(ref);
}
}
zv++;
}
if (EXPECTED(!ht)) {
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
goto tail_call;
}
return;
}
} else {
return;
}
} else if (GC_TYPE(ref) == IS_ARRAY) {
if ((zend_array*)ref != &EG(symbol_table)) {
ht = (zend_array*)ref;
} else {
return;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
GC_REFCOUNT(ref)++;
if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
goto tail_call;
}
}
return;
} else {
return;
}
if (!ht->nNumUsed) return;
p = ht->arData;
end = p + ht->nNumUsed;
while (1) {
end--;
zv = &end->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
break;
}
if (p == end) return;
}
while (p != end) {
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
gc_scan_black(ref);
}
}
p++;
}
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
goto tail_call;
}
}
static void gc_mark_grey(zend_refcounted *ref)
{
HashTable *ht;
Bucket *p, *end;
zval *zv;
tail_call:
if (GC_REF_GET_COLOR(ref) != GC_GREY) {
ht = NULL;
GC_BENCH_INC(zval_marked_grey);
GC_REF_SET_COLOR(ref, GC_GREY);
if (GC_TYPE(ref) == IS_OBJECT) {
zend_object_get_gc_t get_gc;
zend_object *obj = (zend_object*)ref;
if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) &&
(get_gc = obj->handlers->get_gc) != NULL)) {
int n;
zval *zv, *end;
zval tmp;
ZVAL_OBJ(&tmp, obj);
ht = get_gc(&tmp, &zv, &n);
end = zv + n;
if (EXPECTED(!ht) || UNEXPECTED(GC_REF_GET_COLOR(ht) == GC_GREY)) {
ht = NULL;
if (!n) return;
while (!Z_REFCOUNTED_P(--end)) {
if (zv == end) return;
}
} else {
GC_REF_SET_COLOR(ht, GC_GREY);
}
while (zv != end) {
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
ZEND_ASSERT(GC_REFCOUNT(ref) > 0);
GC_REFCOUNT(ref)--;
gc_mark_grey(ref);
}
zv++;
}
if (EXPECTED(!ht)) {
ref = Z_COUNTED_P(zv);
ZEND_ASSERT(GC_REFCOUNT(ref) > 0);
GC_REFCOUNT(ref)--;
goto tail_call;
}
} else {
return;
}
} else if (GC_TYPE(ref) == IS_ARRAY) {
if (((zend_array*)ref) == &EG(symbol_table)) {
GC_REF_SET_BLACK(ref);
return;
} else {
ht = (zend_array*)ref;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
ZEND_ASSERT(GC_REFCOUNT(ref) > 0);
GC_REFCOUNT(ref)--;
goto tail_call;
}
return;
} else {
return;
}
if (!ht->nNumUsed) return;
p = ht->arData;
end = p + ht->nNumUsed;
while (1) {
end--;
zv = &end->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
break;
}
if (p == end) return;
}
while (p != end) {
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
ZEND_ASSERT(GC_REFCOUNT(ref) > 0);
GC_REFCOUNT(ref)--;
gc_mark_grey(ref);
}
p++;
}
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
ref = Z_COUNTED_P(zv);
ZEND_ASSERT(GC_REFCOUNT(ref) > 0);
GC_REFCOUNT(ref)--;
goto tail_call;
}
}
static void gc_mark_roots(void)
{
gc_root_buffer *current = GC_G(roots).next;
while (current != &GC_G(roots)) {
if (GC_REF_GET_COLOR(current->ref) == GC_PURPLE) {
gc_mark_grey(current->ref);
}
current = current->next;
}
}
static void gc_scan(zend_refcounted *ref)
{
HashTable *ht;
Bucket *p, *end;
zval *zv;
tail_call:
if (GC_REF_GET_COLOR(ref) == GC_GREY) {
if (GC_REFCOUNT(ref) > 0) {
gc_scan_black(ref);
} else {
GC_REF_SET_COLOR(ref, GC_WHITE);
if (GC_TYPE(ref) == IS_OBJECT) {
zend_object_get_gc_t get_gc;
zend_object *obj = (zend_object*)ref;
if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) &&
(get_gc = obj->handlers->get_gc) != NULL)) {
int n;
zval *zv, *end;
zval tmp;
ZVAL_OBJ(&tmp, obj);
ht = get_gc(&tmp, &zv, &n);
end = zv + n;
if (EXPECTED(!ht) || UNEXPECTED(GC_REF_GET_COLOR(ht) != GC_GREY)) {
ht = NULL;
if (!n) return;
while (!Z_REFCOUNTED_P(--end)) {
if (zv == end) return;
}
} else {
GC_REF_SET_COLOR(ht, GC_WHITE);
}
while (zv != end) {
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
gc_scan(ref);
}
zv++;
}
if (EXPECTED(!ht)) {
ref = Z_COUNTED_P(zv);
goto tail_call;
}
} else {
return;
}
} else if (GC_TYPE(ref) == IS_ARRAY) {
if ((zend_array*)ref == &EG(symbol_table)) {
GC_REF_SET_BLACK(ref);
return;
} else {
ht = (zend_array*)ref;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
goto tail_call;
}
return;
} else {
return;
}
if (!ht->nNumUsed) return;
p = ht->arData;
end = p + ht->nNumUsed;
while (1) {
end--;
zv = &end->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
break;
}
if (p == end) return;
}
while (p != end) {
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
gc_scan(ref);
}
p++;
}
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
ref = Z_COUNTED_P(zv);
goto tail_call;
}
}
}
static void gc_scan_roots(void)
{
gc_root_buffer *current = GC_G(roots).next;
while (current != &GC_G(roots)) {
gc_scan(current->ref);
current = current->next;
}
}
static void gc_add_garbage(zend_refcounted *ref)
{
gc_root_buffer *buf = GC_G(unused);
if (buf) {
GC_G(unused) = buf->prev;
#if 1
/* optimization: color is already GC_BLACK (0) */
GC_INFO(ref) = buf - GC_G(buf);
#else
GC_REF_SET_ADDRESS(ref, buf - GC_G(buf));
#endif
} else if (GC_G(first_unused) != GC_G(last_unused)) {
buf = GC_G(first_unused);
GC_G(first_unused)++;
#if 1
/* optimization: color is already GC_BLACK (0) */
GC_INFO(ref) = buf - GC_G(buf);
#else
GC_REF_SET_ADDRESS(ref, buf - GC_G(buf));
#endif
} else {
/* If we don't have free slots in the buffer, allocate a new one and
* set it's address above GC_ROOT_BUFFER_MAX_ENTRIES that have special
* meaning.
*/
if (!GC_G(additional_buffer) || GC_G(additional_buffer)->used == GC_NUM_ADDITIONAL_ENTRIES) {
gc_additional_buffer *new_buffer = emalloc(sizeof(gc_additional_buffer));
new_buffer->used = 0;
new_buffer->next = GC_G(additional_buffer);
GC_G(additional_buffer) = new_buffer;
}
buf = GC_G(additional_buffer)->buf + GC_G(additional_buffer)->used;
#if 1
/* optimization: color is already GC_BLACK (0) */
GC_INFO(ref) = GC_ROOT_BUFFER_MAX_ENTRIES + GC_G(additional_buffer)->used;
#else
GC_REF_SET_ADDRESS(ref, GC_ROOT_BUFFER_MAX_ENTRIES) + GC_G(additional_buffer)->used;
#endif
GC_G(additional_buffer)->used++;
}
if (buf) {
buf->ref = ref;
buf->next = GC_G(roots).next;
buf->prev = &GC_G(roots);
GC_G(roots).next->prev = buf;
GC_G(roots).next = buf;
}
}
static int gc_collect_white(zend_refcounted *ref, uint32_t *flags)
{
int count = 0;
HashTable *ht;
Bucket *p, *end;
zval *zv;
tail_call:
if (GC_REF_GET_COLOR(ref) == GC_WHITE) {
ht = NULL;
GC_REF_SET_BLACK(ref);
/* don't count references for compatibility ??? */
if (GC_TYPE(ref) != IS_REFERENCE) {
count++;
}
if (GC_TYPE(ref) == IS_OBJECT) {
zend_object_get_gc_t get_gc;
zend_object *obj = (zend_object*)ref;
if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) &&
(get_gc = obj->handlers->get_gc) != NULL)) {
int n;
zval *zv, *end;
zval tmp;
#if 1
/* optimization: color is GC_BLACK (0) */
if (!GC_INFO(ref)) {
#else
if (!GC_ADDRESS(GC_INFO(ref))) {
#endif
gc_add_garbage(ref);
}
if (obj->handlers->dtor_obj &&
((obj->handlers->dtor_obj != zend_objects_destroy_object) ||
(obj->ce->destructor != NULL))) {
*flags |= GC_HAS_DESTRUCTORS;
}
ZVAL_OBJ(&tmp, obj);
ht = get_gc(&tmp, &zv, &n);
end = zv + n;
if (EXPECTED(!ht) || UNEXPECTED(GC_REF_GET_COLOR(ht) == GC_BLACK)) {
ht = NULL;
if (!n) return count;
while (!Z_REFCOUNTED_P(--end)) {
/* count non-refcounted for compatibility ??? */
if (Z_TYPE_P(zv) != IS_UNDEF) {
count++;
}
if (zv == end) return count;
}
} else {
GC_REF_SET_BLACK(ht);
}
while (zv != end) {
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
count += gc_collect_white(ref, flags);
/* count non-refcounted for compatibility ??? */
} else if (Z_TYPE_P(zv) != IS_UNDEF) {
count++;
}
zv++;
}
if (EXPECTED(!ht)) {
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
goto tail_call;
}
} else {
return count;
}
} else if (GC_TYPE(ref) == IS_ARRAY) {
#if 1
/* optimization: color is GC_BLACK (0) */
if (!GC_INFO(ref)) {
#else
if (!GC_ADDRESS(GC_INFO(ref))) {
#endif
gc_add_garbage(ref);
}
ht = (zend_array*)ref;
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
GC_REFCOUNT(ref)++;
goto tail_call;
}
return count;
} else {
return count;
}
if (!ht->nNumUsed) return count;
p = ht->arData;
end = p + ht->nNumUsed;
while (1) {
end--;
zv = &end->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
break;
}
/* count non-refcounted for compatibility ??? */
if (Z_TYPE_P(zv) != IS_UNDEF) {
count++;
}
if (p == end) return count;
}
while (p != end) {
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
count += gc_collect_white(ref, flags);
/* count non-refcounted for compatibility ??? */
} else if (Z_TYPE_P(zv) != IS_UNDEF) {
count++;
}
p++;
}
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++;
goto tail_call;
}
return count;
}
static int gc_collect_roots(uint32_t *flags)
{
int count = 0;
gc_root_buffer *current = GC_G(roots).next;
/* remove non-garbage from the list */
while (current != &GC_G(roots)) {
gc_root_buffer *next = current->next;
if (GC_REF_GET_COLOR(current->ref) == GC_BLACK) {
if (EXPECTED(GC_ADDRESS(GC_INFO(current->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
gc_remove_from_roots(current);
} else {
gc_remove_from_additional_roots(current);
}
GC_INFO(current->ref) = 0; /* reset GC_ADDRESS() and keep GC_BLACK */
}
current = next;
}
current = GC_G(roots).next;
while (current != &GC_G(roots)) {
if (GC_REF_GET_COLOR(current->ref) == GC_WHITE) {
count += gc_collect_white(current->ref, flags);
}
current = current->next;
}
/* relink remaining roots into list to free */
if (GC_G(roots).next != &GC_G(roots)) {
if (GC_G(to_free).next == &GC_G(to_free)) {
/* move roots into list to free */
GC_G(to_free).next = GC_G(roots).next;
GC_G(to_free).prev = GC_G(roots).prev;
GC_G(to_free).next->prev = &GC_G(to_free);
GC_G(to_free).prev->next = &GC_G(to_free);
} else {
/* add roots into list to free */
GC_G(to_free).prev->next = GC_G(roots).next;
GC_G(roots).next->prev = GC_G(to_free).prev;
GC_G(roots).prev->next = &GC_G(to_free);
GC_G(to_free).prev = GC_G(roots).prev;
}
GC_G(roots).next = &GC_G(roots);
GC_G(roots).prev = &GC_G(roots);
}
return count;
}
static void gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root)
{
HashTable *ht = NULL;
Bucket *p, *end;
zval *zv;
tail_call:
do {
if (root) {
GC_TRACE_REF(ref, "removing from buffer");
if (EXPECTED(GC_ADDRESS(GC_INFO(root->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
gc_remove_from_roots(root);
} else {
gc_remove_from_additional_roots(root);
}
GC_INFO(ref) = 0;
root = NULL;
} else if (GC_ADDRESS(GC_INFO(ref)) != 0
&& GC_REF_GET_COLOR(ref) == GC_BLACK) {
GC_TRACE_REF(ref, "removing from buffer");
GC_REMOVE_FROM_BUFFER(ref);
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
goto tail_call;
}
return;
} else {
return;
}
if (GC_TYPE(ref) == IS_OBJECT) {
zend_object_get_gc_t get_gc;
zend_object *obj = (zend_object*)ref;
if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) &&
(get_gc = obj->handlers->get_gc) != NULL)) {
int n;
zval *zv, *end;
zval tmp;
ZVAL_OBJ(&tmp, obj);
ht = get_gc(&tmp, &zv, &n);
end = zv + n;
if (EXPECTED(!ht)) {
if (!n) return;
while (!Z_REFCOUNTED_P(--end)) {
if (zv == end) return;
}
}
while (zv != end) {
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
gc_remove_nested_data_from_buffer(ref, NULL);
}
zv++;
}
if (EXPECTED(!ht)) {
ref = Z_COUNTED_P(zv);
goto tail_call;
}
if (GC_ADDRESS(GC_INFO(ht)) != 0 && GC_REF_GET_COLOR(ht) == GC_BLACK) {
GC_TRACE_REF(ht, "removing from buffer");
GC_REMOVE_FROM_BUFFER(ht);
}
} else {
return;
}
} else if (GC_TYPE(ref) == IS_ARRAY) {
ht = (zend_array*)ref;
} else {
return;
}
if (!ht->nNumUsed) return;
p = ht->arData;
end = p + ht->nNumUsed;
while (1) {
end--;
zv = &end->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
break;
}
if (p == end) return;
}
while (p != end) {
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv);
gc_remove_nested_data_from_buffer(ref, NULL);
}
p++;
}
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
ref = Z_COUNTED_P(zv);
goto tail_call;
} while (0);
}
ZEND_API int zend_gc_collect_cycles(void)
{
int count = 0;
if (GC_G(roots).next != &GC_G(roots)) {
gc_root_buffer *current, *next, *orig_next_to_free;
zend_refcounted *p;
gc_root_buffer to_free;
uint32_t gc_flags = 0;
gc_additional_buffer *additional_buffer_snapshot;
#if ZEND_GC_DEBUG
zend_bool orig_gc_full;
#endif
if (GC_G(gc_active)) {
return 0;
}
GC_TRACE("Collecting cycles");
GC_G(gc_runs)++;
GC_G(gc_active) = 1;
GC_TRACE("Marking roots");
gc_mark_roots();
GC_TRACE("Scanning roots");
gc_scan_roots();
#if ZEND_GC_DEBUG
orig_gc_full = GC_G(gc_full);
GC_G(gc_full) = 0;
#endif
GC_TRACE("Collecting roots");
additional_buffer_snapshot = GC_G(additional_buffer);
count = gc_collect_roots(&gc_flags);
#if ZEND_GC_DEBUG
GC_G(gc_full) = orig_gc_full;
#endif
GC_G(gc_active) = 0;
if (GC_G(to_free).next == &GC_G(to_free)) {
/* nothing to free */
GC_TRACE("Nothing to free");
return 0;
}
/* Copy global to_free list into local list */
to_free.next = GC_G(to_free).next;
to_free.prev = GC_G(to_free).prev;
to_free.next->prev = &to_free;
to_free.prev->next = &to_free;
/* Free global list */
GC_G(to_free).next = &GC_G(to_free);
GC_G(to_free).prev = &GC_G(to_free);
orig_next_to_free = GC_G(next_to_free);
#if ZEND_GC_DEBUG
orig_gc_full = GC_G(gc_full);
GC_G(gc_full) = 0;
#endif
if (gc_flags & GC_HAS_DESTRUCTORS) {
GC_TRACE("Calling destructors");
/* Remember reference counters before calling destructors */
current = to_free.next;
while (current != &to_free) {
current->refcount = GC_REFCOUNT(current->ref);
current = current->next;
}
/* Call destructors */
current = to_free.next;
while (current != &to_free) {
p = current->ref;
GC_G(next_to_free) = current->next;
if (GC_TYPE(p) == IS_OBJECT) {
zend_object *obj = (zend_object*)p;
if (!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) {
GC_TRACE_REF(obj, "calling destructor");
GC_FLAGS(obj) |= IS_OBJ_DESTRUCTOR_CALLED;
if (obj->handlers->dtor_obj
&& (obj->handlers->dtor_obj != zend_objects_destroy_object
|| obj->ce->destructor)) {
GC_REFCOUNT(obj)++;
obj->handlers->dtor_obj(obj);
GC_REFCOUNT(obj)--;
}
}
}
current = GC_G(next_to_free);
}
/* Remove values captured in destructors */
current = to_free.next;
while (current != &to_free) {
GC_G(next_to_free) = current->next;
if (GC_REFCOUNT(current->ref) > current->refcount) {
gc_remove_nested_data_from_buffer(current->ref, current);
}
current = GC_G(next_to_free);
}
}
/* Destroy zvals */
GC_TRACE("Destroying zvals");
GC_G(gc_active) = 1;
current = to_free.next;
while (current != &to_free) {
p = current->ref;
GC_G(next_to_free) = current->next;
GC_TRACE_REF(p, "destroying");
if (GC_TYPE(p) == IS_OBJECT) {
zend_object *obj = (zend_object*)p;
EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj);
GC_TYPE(obj) = IS_NULL;
if (!(GC_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
GC_FLAGS(obj) |= IS_OBJ_FREE_CALLED;
if (obj->handlers->free_obj) {
GC_REFCOUNT(obj)++;
obj->handlers->free_obj(obj);
GC_REFCOUNT(obj)--;
}
}
SET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[obj->handle], EG(objects_store).free_list_head);
EG(objects_store).free_list_head = obj->handle;
p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset);
} else if (GC_TYPE(p) == IS_ARRAY) {
zend_array *arr = (zend_array*)p;
GC_TYPE(arr) = IS_NULL;
/* GC may destroy arrays with rc>1. This is valid and safe. */
HT_ALLOW_COW_VIOLATION(arr);
zend_hash_destroy(arr);
}
current = GC_G(next_to_free);
}
/* Free objects */
current = to_free.next;
while (current != &to_free) {
next = current->next;
p = current->ref;
if (EXPECTED(current >= GC_G(buf) && current < GC_G(buf) + GC_ROOT_BUFFER_MAX_ENTRIES)) {
current->prev = GC_G(unused);
GC_G(unused) = current;
}
efree(p);
current = next;
}
while (GC_G(additional_buffer) != additional_buffer_snapshot) {
gc_additional_buffer *next = GC_G(additional_buffer)->next;
efree(GC_G(additional_buffer));
GC_G(additional_buffer) = next;
}
GC_TRACE("Collection finished");
GC_G(collected) += count;
GC_G(next_to_free) = orig_next_to_free;
#if ZEND_GC_DEBUG
GC_G(gc_full) = orig_gc_full;
#endif
GC_G(gc_active) = 0;
}
return count;
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode: t
* End:
* vim600: sw=4 ts=4 fdm=marker
* vim<600: sw=4 ts=4
*
* vim:noexpandtab:
*/