From 0510a05c41ddf1c40125e57a8214bd49faaf1d07 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 10 Jul 2026 12:47:34 +0200 Subject: [PATCH] Share the method's runtime cache with fake closures of the same scope Creating a fake closure (Closure::fromCallable(), first-class callable syntax, ReflectionMethod::getClosure()) over a method whose runtime cache was not yet initialized allocated a cold per-closure heap cache on every creation. Initialize the method's own shared cache instead - exactly what the first real call would do - so repeated closure creations reuse one warm cache. Trampolines and heap-cache functions keep the per-closure path. --- Zend/zend_closures.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 4aa467315907..aa1592c04fea 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -864,6 +864,16 @@ static void zend_create_closure_ex( ptr = zend_arena_alloc(&CG(arena), func->op_array.cache_size); ZEND_MAP_PTR_SET(func->op_array.run_time_cache, ptr); closure->func.op_array.fn_flags &= ~ZEND_ACC_HEAP_RT_CACHE; + } else if (!ptr + && func->common.scope == scope + && !(func->common.fn_flags & (ZEND_ACC_HEAP_RT_CACHE|ZEND_ACC_CALL_VIA_TRAMPOLINE))) { + /* Fake closure over a method that has not run yet: initialize + * the method's own shared runtime cache and use it, instead + * of allocating a cold per-closure heap cache on every + * Closure::fromCallable()/first-class callable creation. */ + ptr = zend_arena_alloc(&CG(arena), func->op_array.cache_size); + ZEND_MAP_PTR_SET(func->op_array.run_time_cache, ptr); + closure->func.op_array.fn_flags &= ~ZEND_ACC_HEAP_RT_CACHE; } else { /* Otherwise, we use a non-shared runtime cache */ ptr = emalloc(func->op_array.cache_size);