Skip to content

Commit 48a34bc

Browse files
committed
Add helper APIs for get_gc implementations
get_gc() implementations that need to explore heterogeneous data currently work by computing how many GC entries they need, allocating a buffer for that and storing it on the object. This is inefficient and wastes memory, because the buffer is retained after the GC run. This commit adds an API for a single global GC buffer, which can be reused by get_gc implementations (as only one get_gc call is ever active at the same time). The GC buffer will automatically grow during the GC run and be discarded at the end.
1 parent 41c7d28 commit 48a34bc

8 files changed

+89
-113
lines changed

Zend/zend_execute_API.c

+2
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ void init_executor(void) /* {{{ */
184184
EG(persistent_functions_count) = EG(function_table)->nNumUsed;
185185
EG(persistent_classes_count) = EG(class_table)->nNumUsed;
186186

187+
EG(get_gc_buffer).start = EG(get_gc_buffer).end = EG(get_gc_buffer).cur = NULL;
188+
187189
zend_weakrefs_init();
188190

189191
EG(active) = 1;

Zend/zend_gc.c

+27-1
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,8 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
14161416
} while (0);
14171417
}
14181418

1419+
static void zend_get_gc_buffer_release();
1420+
14191421
ZEND_API int zend_gc_collect_cycles(void)
14201422
{
14211423
int count = 0;
@@ -1451,6 +1453,7 @@ ZEND_API int zend_gc_collect_cycles(void)
14511453
if (!GC_G(num_roots)) {
14521454
/* nothing to free */
14531455
GC_TRACE("Nothing to free");
1456+
zend_get_gc_buffer_release();
14541457
GC_G(gc_active) = 0;
14551458
return 0;
14561459
}
@@ -1533,6 +1536,7 @@ ZEND_API int zend_gc_collect_cycles(void)
15331536

15341537
if (GC_G(gc_protected)) {
15351538
/* something went wrong */
1539+
zend_get_gc_buffer_release();
15361540
return 0;
15371541
}
15381542
}
@@ -1595,7 +1599,7 @@ ZEND_API int zend_gc_collect_cycles(void)
15951599
}
15961600

15971601
gc_compact();
1598-
1602+
zend_get_gc_buffer_release();
15991603
return count;
16001604
}
16011605

@@ -1607,6 +1611,28 @@ ZEND_API void zend_gc_get_status(zend_gc_status *status)
16071611
status->num_roots = GC_G(num_roots);
16081612
}
16091613

1614+
ZEND_API zend_get_gc_buffer *zend_get_gc_buffer_create() {
1615+
/* There can only be one get_gc() call active at a time,
1616+
* so there only needs to be one buffer. */
1617+
zend_get_gc_buffer *gc_buffer = &EG(get_gc_buffer);
1618+
gc_buffer->cur = gc_buffer->start;
1619+
return gc_buffer;
1620+
}
1621+
1622+
ZEND_API void zend_get_gc_buffer_grow(zend_get_gc_buffer *gc_buffer) {
1623+
size_t old_capacity = gc_buffer->end - gc_buffer->start;
1624+
size_t new_capacity = old_capacity == 0 ? 64 : old_capacity * 2;
1625+
gc_buffer->start = erealloc(gc_buffer->start, new_capacity * sizeof(zval));
1626+
gc_buffer->end = gc_buffer->start + new_capacity;
1627+
gc_buffer->cur = gc_buffer->start + old_capacity;
1628+
}
1629+
1630+
static void zend_get_gc_buffer_release() {
1631+
zend_get_gc_buffer *gc_buffer = &EG(get_gc_buffer);
1632+
efree(gc_buffer->start);
1633+
gc_buffer->start = gc_buffer->end = gc_buffer->cur = NULL;
1634+
}
1635+
16101636
#ifdef ZTS
16111637
size_t zend_gc_globals_size(void)
16121638
{

Zend/zend_gc.h

+39
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,43 @@ static zend_always_inline void gc_check_possible_root(zend_refcounted *ref)
8484
}
8585
}
8686

87+
/* These APIs can be used to simplify object get_gc implementations
88+
* over heterogenous structures. See zend_generator_get_gc() for
89+
* a usage example. */
90+
91+
typedef struct {
92+
zval *cur;
93+
zval *end;
94+
zval *start;
95+
} zend_get_gc_buffer;
96+
97+
ZEND_API zend_get_gc_buffer *zend_get_gc_buffer_create();
98+
ZEND_API void zend_get_gc_buffer_grow(zend_get_gc_buffer *gc_buffer);
99+
100+
static zend_always_inline void zend_get_gc_buffer_add_zval(
101+
zend_get_gc_buffer *gc_buffer, zval *zv) {
102+
if (Z_REFCOUNTED_P(zv)) {
103+
if (UNEXPECTED(gc_buffer->cur == gc_buffer->end)) {
104+
zend_get_gc_buffer_grow(gc_buffer);
105+
}
106+
ZVAL_COPY_VALUE(gc_buffer->cur, zv);
107+
gc_buffer->cur++;
108+
}
109+
}
110+
111+
static zend_always_inline void zend_get_gc_buffer_add_obj(
112+
zend_get_gc_buffer *gc_buffer, zend_object *obj) {
113+
if (UNEXPECTED(gc_buffer->cur == gc_buffer->end)) {
114+
zend_get_gc_buffer_grow(gc_buffer);
115+
}
116+
ZVAL_OBJ(gc_buffer->cur, obj);
117+
gc_buffer->cur++;
118+
}
119+
120+
static zend_always_inline void zend_get_gc_buffer_use(
121+
zend_get_gc_buffer *gc_buffer, zval **table, int *n) {
122+
*table = gc_buffer->start;
123+
*n = gc_buffer->cur - gc_buffer->start;
124+
}
125+
87126
#endif /* ZEND_GC_H */

Zend/zend_generators.c

+12-76
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,6 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished
152152
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
153153
}
154154

155-
/* Free GC buffer. GC for closed generators doesn't need an allocated buffer */
156-
if (generator->gc_buffer) {
157-
efree(generator->gc_buffer);
158-
generator->gc_buffer = NULL;
159-
}
160-
161155
efree(execute_data);
162156
}
163157
}
@@ -279,63 +273,11 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */
279273
}
280274
/* }}} */
281275

282-
static uint32_t calc_gc_buffer_size(zend_generator *generator) /* {{{ */
283-
{
284-
uint32_t size = 4; /* value, key, retval, values */
285-
if (generator->execute_data) {
286-
zend_execute_data *execute_data = generator->execute_data;
287-
zend_op_array *op_array = &EX(func)->op_array;
288-
289-
/* Compiled variables */
290-
if (!(EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE)) {
291-
size += op_array->last_var;
292-
}
293-
/* Extra args */
294-
if (EX_CALL_INFO() & ZEND_CALL_FREE_EXTRA_ARGS) {
295-
size += EX_NUM_ARGS() - op_array->num_args;
296-
}
297-
size += (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) != 0; /* $this */
298-
size += (EX_CALL_INFO() & ZEND_CALL_CLOSURE) != 0; /* Closure object */
299-
300-
/* Live vars */
301-
if (execute_data->opline != op_array->opcodes) {
302-
/* -1 required because we want the last run opcode, not the next to-be-run one. */
303-
uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1;
304-
for (i = 0; i < op_array->last_live_range; i++) {
305-
const zend_live_range *range = &op_array->live_range[i];
306-
if (range->start > op_num) {
307-
/* Further ranges will not be relevant... */
308-
break;
309-
} else if (op_num < range->end) {
310-
/* LIVE_ROPE and LIVE_SILENCE not relevant for GC */
311-
uint32_t kind = range->var & ZEND_LIVE_MASK;
312-
if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
313-
size++;
314-
}
315-
}
316-
}
317-
}
318-
319-
/* Yield from root references */
320-
if (generator->node.children == 0) {
321-
zend_generator *root = generator->node.ptr.root;
322-
while (root != generator) {
323-
root = zend_generator_get_child(&root->node, generator);
324-
size++;
325-
}
326-
}
327-
}
328-
return size;
329-
}
330-
/* }}} */
331-
332276
static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *n) /* {{{ */
333277
{
334278
zend_generator *generator = (zend_generator*)object;
335279
zend_execute_data *execute_data = generator->execute_data;
336280
zend_op_array *op_array;
337-
zval *gc_buffer;
338-
uint32_t gc_buffer_size;
339281

340282
if (!execute_data) {
341283
/* If the generator has been closed, it can only hold on to three values: The value, key
@@ -346,40 +288,33 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
346288
}
347289

348290
op_array = &EX(func)->op_array;
349-
gc_buffer_size = calc_gc_buffer_size(generator);
350-
if (generator->gc_buffer_size < gc_buffer_size) {
351-
generator->gc_buffer = safe_erealloc(generator->gc_buffer, sizeof(zval), gc_buffer_size, 0);
352-
generator->gc_buffer_size = gc_buffer_size;
353-
}
354-
355-
*n = gc_buffer_size;
356-
*table = gc_buffer = generator->gc_buffer;
357291

358-
ZVAL_COPY_VALUE(gc_buffer++, &generator->value);
359-
ZVAL_COPY_VALUE(gc_buffer++, &generator->key);
360-
ZVAL_COPY_VALUE(gc_buffer++, &generator->retval);
361-
ZVAL_COPY_VALUE(gc_buffer++, &generator->values);
292+
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
293+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
294+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
295+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
296+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->values);
362297

363298
if (!(EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE)) {
364299
uint32_t i, num_cvs = EX(func)->op_array.last_var;
365300
for (i = 0; i < num_cvs; i++) {
366-
ZVAL_COPY_VALUE(gc_buffer++, EX_VAR_NUM(i));
301+
zend_get_gc_buffer_add_zval(gc_buffer, EX_VAR_NUM(i));
367302
}
368303
}
369304

370305
if (EX_CALL_INFO() & ZEND_CALL_FREE_EXTRA_ARGS) {
371306
zval *zv = EX_VAR_NUM(op_array->last_var + op_array->T);
372307
zval *end = zv + (EX_NUM_ARGS() - op_array->num_args);
373308
while (zv != end) {
374-
ZVAL_COPY_VALUE(gc_buffer++, zv++);
309+
zend_get_gc_buffer_add_zval(gc_buffer, zv++);
375310
}
376311
}
377312

378313
if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) {
379-
ZVAL_OBJ(gc_buffer++, Z_OBJ(execute_data->This));
314+
zend_get_gc_buffer_add_obj(gc_buffer, Z_OBJ(execute_data->This));
380315
}
381316
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
382-
ZVAL_OBJ(gc_buffer++, ZEND_CLOSURE_OBJECT(EX(func)));
317+
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func)));
383318
}
384319

385320
if (execute_data->opline != op_array->opcodes) {
@@ -393,7 +328,7 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
393328
uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
394329
zval *var = EX_VAR(var_num);
395330
if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
396-
ZVAL_COPY_VALUE(gc_buffer++, var);
331+
zend_get_gc_buffer_add_zval(gc_buffer, var);
397332
}
398333
}
399334
}
@@ -402,11 +337,12 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
402337
if (generator->node.children == 0) {
403338
zend_generator *root = generator->node.ptr.root;
404339
while (root != generator) {
405-
ZVAL_OBJ(gc_buffer++, &root->std);
340+
zend_get_gc_buffer_add_obj(gc_buffer, &root->std);
406341
root = zend_generator_get_child(&root->node, generator);
407342
}
408343
}
409344

345+
zend_get_gc_buffer_use(gc_buffer, table, n);
410346
if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) {
411347
return execute_data->symbol_table;
412348
} else {

Zend/zend_generators.h

-3
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ struct _zend_generator {
8989

9090
/* ZEND_GENERATOR_* flags */
9191
zend_uchar flags;
92-
93-
zval *gc_buffer;
94-
uint32_t gc_buffer_size;
9592
};
9693

9794
static const zend_uchar ZEND_GENERATOR_CURRENTLY_RUNNING = 0x1;

Zend/zend_globals.h

+2
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ struct _zend_executor_globals {
238238

239239
zend_bool exception_ignore_args;
240240

241+
zend_get_gc_buffer get_gc_buffer;
242+
241243
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
242244
};
243245

ext/spl/spl_dllist.c

+3-15
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ struct _spl_dllist_object {
9191
zend_function *fptr_offset_del;
9292
zend_function *fptr_count;
9393
zend_class_entry *ce_get_iterator;
94-
zval *gc_data;
95-
int gc_data_count;
9694
zend_object std;
9795
};
9896

@@ -356,10 +354,6 @@ static void spl_dllist_object_free_storage(zend_object *object) /* {{{ */
356354
zval_ptr_dtor(&tmp);
357355
}
358356

359-
if (intern->gc_data != NULL) {
360-
efree(intern->gc_data);
361-
};
362-
363357
spl_ptr_llist_destroy(intern->llist);
364358
SPL_LLIST_CHECK_DELREF(intern->traverse_pointer);
365359
}
@@ -534,21 +528,15 @@ static inline HashTable* spl_dllist_object_get_debug_info(zend_object *obj) /* {
534528
static HashTable *spl_dllist_object_get_gc(zend_object *obj, zval **gc_data, int *gc_data_count) /* {{{ */
535529
{
536530
spl_dllist_object *intern = spl_dllist_from_obj(obj);
531+
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
537532
spl_ptr_llist_element *current = intern->llist->head;
538-
int i = 0;
539-
540-
if (intern->gc_data_count < intern->llist->count) {
541-
intern->gc_data_count = intern->llist->count;
542-
intern->gc_data = safe_erealloc(intern->gc_data, intern->gc_data_count, sizeof(zval), 0);
543-
}
544533

545534
while (current) {
546-
ZVAL_COPY_VALUE(&intern->gc_data[i++], &current->data);
535+
zend_get_gc_buffer_add_zval(gc_buffer, &current->data);
547536
current = current->next;
548537
}
549538

550-
*gc_data = intern->gc_data;
551-
*gc_data_count = i;
539+
zend_get_gc_buffer_use(gc_buffer, gc_data, gc_data_count);
552540
return zend_std_get_properties(obj);
553541
}
554542
/* }}} */

ext/spl/spl_observer.c

+4-18
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ typedef struct _spl_SplObjectStorage { /* {{{ */
5050
HashPosition pos;
5151
zend_long flags;
5252
zend_function *fptr_get_hash;
53-
zval *gcdata;
54-
size_t gcdata_num;
5553
zend_object std;
5654
} spl_SplObjectStorage; /* }}} */
5755

@@ -75,11 +73,6 @@ void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */
7573
zend_object_std_dtor(&intern->std);
7674

7775
zend_hash_destroy(&intern->storage);
78-
79-
if (intern->gcdata != NULL) {
80-
efree(intern->gcdata);
81-
}
82-
8376
} /* }}} */
8477

8578
static int spl_object_storage_get_hash(zend_hash_key *key, spl_SplObjectStorage *intern, zval *obj) {
@@ -285,23 +278,16 @@ static inline HashTable* spl_object_storage_debug_info(zend_object *obj) /* {{{
285278
/* overridden for garbage collection */
286279
static HashTable *spl_object_storage_get_gc(zend_object *obj, zval **table, int *n) /* {{{ */
287280
{
288-
int i = 0;
289281
spl_SplObjectStorage *intern = spl_object_storage_from_obj(obj);
290282
spl_SplObjectStorageElement *element;
291-
292-
if (intern->storage.nNumOfElements * 2 > intern->gcdata_num) {
293-
intern->gcdata_num = intern->storage.nNumOfElements * 2;
294-
intern->gcdata = (zval*)erealloc(intern->gcdata, sizeof(zval) * intern->gcdata_num);
295-
}
283+
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
296284

297285
ZEND_HASH_FOREACH_PTR(&intern->storage, element) {
298-
ZVAL_COPY_VALUE(&intern->gcdata[i++], &element->obj);
299-
ZVAL_COPY_VALUE(&intern->gcdata[i++], &element->inf);
286+
zend_get_gc_buffer_add_zval(gc_buffer, &element->obj);
287+
zend_get_gc_buffer_add_zval(gc_buffer, &element->inf);
300288
} ZEND_HASH_FOREACH_END();
301289

302-
*table = intern->gcdata;
303-
*n = i;
304-
290+
zend_get_gc_buffer_use(gc_buffer, table, n);
305291
return zend_std_get_properties(obj);
306292
}
307293
/* }}} */

0 commit comments

Comments
 (0)