Skip to content

Commit ef6d9cf

Browse files
committed
Store the Closure run_time_cache scope in the cache itself
1 parent 0ec7095 commit ef6d9cf

File tree

3 files changed

+47
-20
lines changed

3 files changed

+47
-20
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Closure: Binding + RT cache edge cases
3+
--FILE--
4+
<?php
5+
6+
// cache_size may be zero
7+
$f = function () {};
8+
$f();
9+
$g = $f->bindTo(new class {});
10+
11+
?>
12+
==DONE==
13+
--EXPECT--
14+
==DONE==

Zend/zend_closures.c

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,29 @@ static ZEND_NAMED_FUNCTION(zend_closure_internal_handler) /* {{{ */
733733
}
734734
/* }}} */
735735

736+
static zend_class_entry *zend_closure_rt_cache_scope(zend_function *func, void **run_time_cache, bool is_fake, zend_class_entry *default_scope)
737+
{
738+
ZEND_ASSERT(!(func->op_array.fn_flags & ZEND_ACC_HEAP_RT_CACHE));
739+
740+
if (is_fake) {
741+
return func->op_array.scope;
742+
}
743+
744+
/* Zero RT cache is valid for any scope */
745+
if (func->op_array.cache_size == 0) {
746+
return default_scope;
747+
}
748+
749+
ZEND_ASSERT(func->common.fn_flags & ZEND_ACC_CLOSURE);
750+
ZEND_ASSERT(!(func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE));
751+
752+
return CACHED_PTR_EX(run_time_cache - 1);
753+
}
754+
736755
static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool is_fake) /* {{{ */
737756
{
738757
zend_closure *closure;
739-
void *ptr;
758+
void **ptr;
740759

741760
object_init_ex(res, zend_ce_closure);
742761

@@ -777,19 +796,22 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en
777796
/* Runtime cache is scope-dependent, so we cannot reuse it if the scope changed */
778797
ptr = ZEND_MAP_PTR_GET(func->op_array.run_time_cache);
779798
if (!ptr
780-
|| func->common.scope != scope
781799
|| (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE)
800+
|| zend_closure_rt_cache_scope(func, ptr, is_fake, scope) != scope
782801
) {
783-
if (!ptr
784-
&& (func->common.fn_flags & ZEND_ACC_CLOSURE)
785-
&& (func->common.scope == scope ||
786-
!(func->common.fn_flags & ZEND_ACC_IMMUTABLE))) {
802+
if (func->op_array.cache_size == 0) {
803+
ptr = zend_arena_alloc(&CG(arena), 0);
804+
ZEND_MAP_PTR_SET(func->op_array.run_time_cache, ptr);
805+
closure->func.op_array.fn_flags &= ~ZEND_ACC_HEAP_RT_CACHE;
806+
} else if (!ptr
807+
&& func->common.fn_flags & ZEND_ACC_CLOSURE) {
808+
ZEND_ASSERT(!(func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE));
787809
/* If a real closure is used for the first time, we create a shared runtime cache
788-
* and remember which scope it is for. */
789-
if (func->common.scope != scope) {
790-
func->common.scope = scope;
791-
}
792-
ptr = zend_arena_alloc(&CG(arena), func->op_array.cache_size);
810+
* and remember which scope it is for. The scope is stored in
811+
* an extra slot before the run_time_cache. */
812+
size_t extra_slot_size = sizeof(void*);
813+
ptr = zend_arena_alloc(&CG(arena), func->op_array.cache_size + extra_slot_size) + extra_slot_size;
814+
CACHE_PTR_EX(ptr - 1, scope);
793815
ZEND_MAP_PTR_SET(func->op_array.run_time_cache, ptr);
794816
closure->func.op_array.fn_flags &= ~ZEND_ACC_HEAP_RT_CACHE;
795817
} else {

Zend/zend_compile.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8462,15 +8462,6 @@ static zend_op_array *zend_compile_func_decl_ex(
84628462
/* Pop the loop variable stack separator */
84638463
zend_stack_del_top(&CG(loop_var_stack));
84648464

8465-
if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
8466-
/* Set the closure scope at compile time as an optimization to
8467-
* prevent creating a separate runtime cache for every initialization
8468-
* of this closure. Most closures are expected not to change their
8469-
* scope in practice.
8470-
*/
8471-
op_array->scope = CG(active_class_entry);
8472-
}
8473-
84748465
if (level == FUNC_DECL_LEVEL_TOPLEVEL) {
84758466
zend_observer_function_declared_notify(op_array, lcname);
84768467
}

0 commit comments

Comments
 (0)