Skip to content

Commit

Permalink
Reduce gc stack usage for strings (and resources)
Browse files Browse the repository at this point in the history
Adding strings to the worklist is useless, because they never contribute to
cycles. The assembly size on x86_64 does not change. This significantly improves
performance in this synthetic benchmark by 33%.

    function test($a) {}

    $a = new stdClass();
    $a->self = $a;
    $a->prop1 = str_repeat('a', 10);
    $a->prop2 = str_repeat('a', 10);
    $a->prop3 = str_repeat('a', 10);
    $a->prop4 = str_repeat('a', 10);
    $a->prop5 = str_repeat('a', 10);
    $a->prop6 = str_repeat('a', 10);
    $a->prop7 = str_repeat('a', 10);
    $a->prop8 = str_repeat('a', 10);
    $a->prop9 = str_repeat('a', 10);
    $a->prop10 = str_repeat('a', 10);

    for ($i = 0; $i < 10_000_000; $i++) {
        test($a);
        gc_collect_cycles();
    }

This requires adding IS_TYPE_COLLECTABLE to IS_REFERENCE_EX to ensure these
values continue to be pushed onto the stack. Luckily, IS_TYPE_COLLECTABLE is
currently only used in gc_check_possible_root(), where the checked value cannot
be a reference.

Note that this changes the output of gc_collect_cycles(). Non-cyclic, refcounted
values no longer count towards the total reported values collected.

Also, there is some obvious overlap with GH-17130. This change should be good
nonetheless, especially if we can remove the GC_COLLECTABLE(Z_COUNTED_P(zv))
condition in PHP 9 and rely on Z_COLLECTABLE_P() exclusively, given we can
assume an object doesn't become cyclic at runtime anymore.

Closes GH-17194
  • Loading branch information
iluuu1994 committed Dec 18, 2024
1 parent 287aeeb commit e69317b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 41 deletions.
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ PHP 8.5 UPGRADE NOTES
$object. If compared to a statically unknown value $object == $true, it
would always return false. This behavior was consolidated to always follow
the behavior of (bool) $object.
. The return value of gc_collect_cycles() no longer includes strings and
resources that were indirectly collected through cycles.

- Intl:
. The extension now requires at least ICU 57.1.
Expand Down
80 changes: 40 additions & 40 deletions Zend/zend_gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
zval *entry = (zval*) Z_PTR_P(zv);
zval *weakmap = zv+1;
ZEND_ASSERT(Z_REFCOUNTED_P(weakmap));
if (Z_OPT_REFCOUNTED_P(entry)) {
if (Z_OPT_COLLECTABLE_P(entry)) {
GC_UNSET_FROM_WEAKMAP_KEY(entry);
if (GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_GREY)) {
/* Weakmap was scanned in gc_mark_roots, we must
Expand Down Expand Up @@ -855,7 +855,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
ZEND_ASSERT(Z_TYPE_P(zv+1) == IS_PTR);
zval *key = zv;
zval *entry = (zval*) Z_PTR_P(zv+1);
if (Z_OPT_REFCOUNTED_P(entry)) {
if (Z_OPT_COLLECTABLE_P(entry)) {
GC_UNSET_FROM_WEAKMAP(entry);
if (GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_GREY)) {
/* Key was scanned in gc_mark_roots, we must
Expand Down Expand Up @@ -893,7 +893,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
if (!GC_REF_CHECK_COLOR(ht, GC_BLACK)) {
GC_REF_SET_BLACK(ht);
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
Expand All @@ -909,14 +909,14 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)

handle_zvals:
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
GC_REF_SET_BLACK(ref);
zv++;
while (--n) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
Expand Down Expand Up @@ -948,7 +948,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
Expand All @@ -959,7 +959,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
Expand All @@ -975,7 +975,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack)
p++;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
if (Z_COLLECTABLE(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
GC_ADDREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) {
Expand Down Expand Up @@ -1019,7 +1019,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
zval *entry = (zval*) Z_PTR_P(zv);
zval *weakmap = zv+1;
ZEND_ASSERT(Z_REFCOUNTED_P(weakmap));
if (Z_REFCOUNTED_P(entry)) {
if (Z_COLLECTABLE_P(entry)) {
GC_SET_FROM_WEAKMAP_KEY(entry);
ref = Z_COUNTED_P(entry);
/* Only DELREF if the contribution from the weakmap has
Expand All @@ -1043,7 +1043,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
for (; n != 0; n--) {
ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
zval *entry = (zval*) Z_PTR_P(zv);
if (Z_REFCOUNTED_P(entry)) {
if (Z_COLLECTABLE_P(entry)) {
GC_SET_FROM_WEAKMAP(entry);
ref = Z_COUNTED_P(entry);
/* Only DELREF if the contribution from the weakmap key
Expand All @@ -1069,7 +1069,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
if (!GC_REF_CHECK_COLOR(ht, GC_GREY)) {
GC_REF_SET_COLOR(ht, GC_GREY);
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_DELREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
Expand All @@ -1084,14 +1084,14 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
}
handle_zvals:
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_DELREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_GREY);
zv++;
while (--n) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_DELREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
Expand Down Expand Up @@ -1123,7 +1123,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_DELREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
Expand All @@ -1134,7 +1134,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_DELREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
Expand All @@ -1150,7 +1150,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack)
p++;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
if (Z_COLLECTABLE(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
GC_DELREF(ref);
if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) {
Expand Down Expand Up @@ -1263,7 +1263,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
for (; n != 0; n--) {
ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
zval *entry = (zval*) Z_PTR_P(zv);
if (Z_OPT_REFCOUNTED_P(entry)) {
if (Z_OPT_COLLECTABLE_P(entry)) {
ref = Z_COUNTED_P(entry);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
Expand All @@ -1282,7 +1282,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
GC_REF_SET_COLOR(ht, GC_WHITE);
GC_STACK_PUSH((zend_refcounted *) ht);
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
Expand All @@ -1297,13 +1297,13 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)

handle_zvals:
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
zv++;
while (--n) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
Expand Down Expand Up @@ -1335,7 +1335,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
Expand All @@ -1345,7 +1345,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
Expand All @@ -1360,7 +1360,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack)
p++;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
if (Z_COLLECTABLE(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
if (GC_REF_CHECK_COLOR(ref, GC_GREY)) {
GC_REF_SET_COLOR(ref, GC_WHITE);
Expand Down Expand Up @@ -1473,7 +1473,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
for (; n != 0; n--) {
ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
zval *entry = (zval*) Z_PTR_P(zv);
if (Z_REFCOUNTED_P(entry) && GC_FROM_WEAKMAP_KEY(entry)) {
if (Z_COLLECTABLE_P(entry) && GC_FROM_WEAKMAP_KEY(entry)) {
GC_UNSET_FROM_WEAKMAP_KEY(entry);
GC_UNSET_FROM_WEAKMAP(entry);
ref = Z_COUNTED_P(entry);
Expand All @@ -1494,7 +1494,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
for (; n != 0; n--) {
ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
zval *entry = (zval*) Z_PTR_P(zv);
if (Z_REFCOUNTED_P(entry) && GC_FROM_WEAKMAP(entry)) {
if (Z_COLLECTABLE_P(entry) && GC_FROM_WEAKMAP(entry)) {
GC_UNSET_FROM_WEAKMAP_KEY(entry);
GC_UNSET_FROM_WEAKMAP(entry);
ref = Z_COUNTED_P(entry);
Expand All @@ -1517,7 +1517,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
if (GC_REF_CHECK_COLOR(ht, GC_WHITE)) {
GC_REF_SET_BLACK(ht);
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
Expand All @@ -1533,14 +1533,14 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta

handle_zvals:
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
GC_REF_SET_BLACK(ref);
zv++;
while (--n) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
Expand Down Expand Up @@ -1576,7 +1576,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
Expand All @@ -1587,7 +1587,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_ADDREF(ref);
if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
Expand All @@ -1603,7 +1603,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta
p++;
}
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
if (Z_COLLECTABLE(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
GC_ADDREF(ref);
if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) {
Expand Down Expand Up @@ -1681,7 +1681,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
GC_REMOVE_FROM_BUFFER(ref);
count++;
} else if (GC_TYPE(ref) == IS_REFERENCE) {
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
if (Z_COLLECTABLE(((zend_reference*)ref)->val)) {
ref = Z_COUNTED(((zend_reference*)ref)->val);
goto tail_call;
}
Expand All @@ -1704,7 +1704,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
for (; n != 0; n--) {
ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR);
zval *entry = (zval*) Z_PTR_P(zv);
if (Z_OPT_REFCOUNTED_P(entry)) {
if (Z_OPT_COLLECTABLE_P(entry)) {
ref = Z_COUNTED_P(entry);
GC_STACK_PUSH(ref);
}
Expand All @@ -1717,7 +1717,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
zv = table;
if (UNEXPECTED(ht)) {
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
GC_STACK_PUSH(ref);
}
Expand All @@ -1732,11 +1732,11 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe

handle_zvals:
for (; n != 0; n--) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
zv++;
while (--n) {
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_STACK_PUSH(ref);
}
Expand All @@ -1763,15 +1763,15 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
ref = Z_COUNTED_P(zv);
p++;
while (--n) {
zv = &p->val;
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zv = Z_INDIRECT_P(zv);
}
if (Z_REFCOUNTED_P(zv)) {
if (Z_COLLECTABLE_P(zv)) {
zend_refcounted *ref = Z_COUNTED_P(zv);
GC_STACK_PUSH(ref);
}
Expand Down Expand Up @@ -2175,7 +2175,7 @@ static void zend_gc_check_root_tmpvars(void) {
if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
zval *var = ZEND_CALL_VAR(ex, var_num);
if (Z_REFCOUNTED_P(var)) {
if (Z_COLLECTABLE_P(var)) {
gc_check_possible_root(Z_COUNTED_P(var));
}
}
Expand Down Expand Up @@ -2205,7 +2205,7 @@ static void zend_gc_remove_root_tmpvars(void) {
if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
zval *var = ZEND_CALL_VAR(ex, var_num);
if (Z_REFCOUNTED_P(var)) {
if (Z_COLLECTABLE_P(var)) {
GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var));
}
}
Expand Down
5 changes: 4 additions & 1 deletion Zend/zend_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
#define IS_ARRAY_EX (IS_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT) | (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT))
#define IS_OBJECT_EX (IS_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT) | (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT))
#define IS_RESOURCE_EX (IS_RESOURCE | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
#define IS_REFERENCE_EX (IS_REFERENCE | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
#define IS_REFERENCE_EX (IS_REFERENCE | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT) | (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT))

#define IS_CONSTANT_AST_EX (IS_CONSTANT_AST | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))

Expand Down Expand Up @@ -943,6 +943,9 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
#define Z_OPT_REFCOUNTED(zval) Z_TYPE_INFO_REFCOUNTED(Z_TYPE_INFO(zval))
#define Z_OPT_REFCOUNTED_P(zval_p) Z_OPT_REFCOUNTED(*(zval_p))

#define Z_OPT_COLLECTABLE(zval) ((Z_TYPE_INFO(zval) & (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT)) != 0)
#define Z_OPT_COLLECTABLE_P(zval_p) Z_OPT_COLLECTABLE(*(zval_p))

/* deprecated: (COPYABLE is the same as IS_ARRAY) */
#define Z_OPT_COPYABLE(zval) (Z_OPT_TYPE(zval) == IS_ARRAY)
#define Z_OPT_COPYABLE_P(zval_p) Z_OPT_COPYABLE(*(zval_p))
Expand Down

0 comments on commit e69317b

Please sign in to comment.