Skip to content

Commit f6e49bb

Browse files
committed
Implement acyclic object tracking
Acyclic objects don't need to be added to the GC. We know an object is acyclic if all properties are typed as acyclic, and the object does not have any dynamic properties. This is possible to track at runtime with minimal overhead. Fixes GH-17127
1 parent 07cd468 commit f6e49bb

31 files changed

+123
-3
lines changed

Zend/tests/gc/gc_045.phpt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class GlobalData
1111

1212
class Value
1313
{
14+
/* Force object to be added to GC, even though it is acyclic. */
15+
public $dummy;
16+
1417
public function __destruct()
1518
{
1619
new Bar();
@@ -19,6 +22,9 @@ class Value
1922

2023
class Bar
2124
{
25+
/* Force object to be added to GC, even though it is acyclic. */
26+
public $dummy;
27+
2228
public function __construct()
2329
{
2430
GlobalData::$bar = $this;

Zend/tests/weakrefs/gh10043-008.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Self-referencing map entry GC - 008
55

66
class Canary extends stdClass
77
{
8+
/* Force object to be added to GC, even though it is acyclic. */
9+
public $dummy;
10+
811
public function __construct(public string $name)
912
{
1013
}

Zend/zend_API.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4494,6 +4494,27 @@ static zend_always_inline bool is_persistent_class(zend_class_entry *ce) {
44944494
&& ce->info.internal.module->type == MODULE_PERSISTENT;
44954495
}
44964496

4497+
static bool zend_type_may_be_cyclic(zend_type type)
4498+
{
4499+
if (!ZEND_TYPE_IS_SET(type)) {
4500+
return true;
4501+
}
4502+
4503+
if (!ZEND_TYPE_IS_COMPLEX(type)) {
4504+
return ZEND_TYPE_PURE_MASK(type) & (MAY_BE_OBJECT|MAY_BE_ARRAY);
4505+
} else if (ZEND_TYPE_IS_UNION(type)) {
4506+
zend_type *list_type;
4507+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
4508+
if (zend_type_may_be_cyclic(*list_type)) {
4509+
return true;
4510+
}
4511+
} ZEND_TYPE_LIST_FOREACH_END();
4512+
return false;
4513+
}
4514+
4515+
return true;
4516+
}
4517+
44974518
ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */
44984519
{
44994520
zend_property_info *property_info, *property_info_ptr;
@@ -4506,6 +4527,12 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
45064527
}
45074528
}
45084529

4530+
if (!(access_type & ZEND_ACC_STATIC)
4531+
&& !(ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC)
4532+
&& zend_type_may_be_cyclic(type)) {
4533+
ce->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
4534+
}
4535+
45094536
if (ce->type == ZEND_INTERNAL_CLASS) {
45104537
property_info = pemalloc(sizeof(zend_property_info), 1);
45114538
} else {

Zend/zend_closures.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,9 @@ void zend_register_closure_ce(void) /* {{{ */
705705
zend_ce_closure = register_class_Closure();
706706
zend_ce_closure->create_object = zend_closure_new;
707707
zend_ce_closure->default_object_handlers = &closure_handlers;
708+
/* FIXME: Potentially improve during construction of closure? static closures
709+
* not binding by references can't be cyclic. */
710+
zend_ce_closure->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
708711

709712
memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers));
710713
closure_handlers.free_obj = zend_closure_free_storage;

Zend/zend_compile.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ typedef struct _zend_oparray_context {
267267
#define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */
268268
#define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */
269269
/* | | | */
270-
/* Class Flags (unused: 30,31) | | | */
270+
/* Class Flags (unused: 31) | | | */
271271
/* =========== | | | */
272272
/* | | | */
273273
/* Special class types | | | */
@@ -333,6 +333,9 @@ typedef struct _zend_oparray_context {
333333
/* Class cannot be serialized or unserialized | | | */
334334
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
335335
/* | | | */
336+
/* Object may be the root of a cycle | | | */
337+
#define ZEND_ACC_MAY_BE_CYCLIC (1 << 30) /* X | | | */
338+
/* | | | */
336339
/* Function Flags (unused: 29-30) | | | */
337340
/* ============== | | | */
338341
/* | | | */

Zend/zend_fibers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,7 @@ void zend_register_fiber_ce(void)
11071107
zend_ce_fiber = register_class_Fiber();
11081108
zend_ce_fiber->create_object = zend_fiber_object_create;
11091109
zend_ce_fiber->default_object_handlers = &zend_fiber_handlers;
1110+
zend_ce_fiber->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
11101111

11111112
zend_fiber_handlers = std_object_handlers;
11121113
zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy;

Zend/zend_generators.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,7 @@ void zend_register_generator_ce(void) /* {{{ */
12151215
/* get_iterator has to be assigned *after* implementing the interface */
12161216
zend_ce_generator->get_iterator = zend_generator_get_iterator;
12171217
zend_ce_generator->default_object_handlers = &zend_generator_handlers;
1218+
zend_ce_generator->ce_flags |= ZEND_ACC_MAY_BE_CYCLIC;
12181219

12191220
memcpy(&zend_generator_handlers, &std_object_handlers, sizeof(zend_object_handlers));
12201221
zend_generator_handlers.free_obj = zend_generator_free_storage;

Zend/zend_inheritance.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
18101810
ce->parent = parent_ce;
18111811
ce->default_object_handlers = parent_ce->default_object_handlers;
18121812
ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT;
1813+
ce->ce_flags |= (parent_ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC);
18131814

18141815
/* Inherit properties */
18151816
if (parent_ce->default_properties_count) {
@@ -2832,6 +2833,9 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
28322833
if (!traits[i]) {
28332834
continue;
28342835
}
2836+
2837+
ce->ce_flags |= (traits[i]->ce_flags & ZEND_ACC_MAY_BE_CYCLIC);
2838+
28352839
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->properties_info, prop_name, property_info) {
28362840
uint32_t flags = property_info->flags;
28372841

Zend/zend_object_handlers.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj) /* {{{
7070
zend_class_entry *ce = zobj->ce;
7171
int i;
7272

73+
GC_TYPE_INFO(zobj) &= ~(GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT);
74+
7375
zobj->properties = zend_new_array(ce->default_properties_count);
7476
if (ce->default_properties_count) {
7577
zend_hash_real_init_mixed(zobj->properties);

Zend/zend_objects.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c
3131
{
3232
GC_SET_REFCOUNT(object, 1);
3333
GC_TYPE_INFO(object) = GC_OBJECT;
34+
if (!(ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC)) {
35+
GC_TYPE_INFO(object) |= (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT);
36+
}
3437
object->ce = ce;
3538
object->extra_flags = 0;
3639
object->handlers = ce->default_object_handlers;

0 commit comments

Comments
 (0)