From f93d6791f5ac2a93c78a344851a539a365892389 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 10 Nov 2026 20:28:20 +0100 Subject: [PATCH 1/3] JIT: Avoid run time cache reload in static property access --- ext/opcache/jit/zend_jit_ir.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 0f50d6915b39..376c6de699ac 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16437,11 +16437,13 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, default: ZEND_UNREACHABLE(); } + ir_ref run_time_cache_ptr = IR_UNUSED; if (is_static) { /* The cache slot is polymorphic on the called scope. */ // JIT: if (CACHED_PTR(cache_slot) == called_scope) + run_time_cache_ptr = ir_LOAD_A(jit_EX(run_time_cache)); ir_ref if_scope = ir_IF(ir_EQ( - ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot)), + ir_LOAD_A(ir_ADD_OFFSET(run_time_cache_ptr, cache_slot)), jit_called_scope(jit, op_array))); ir_IF_FALSE_cold(if_scope); ir_END_list(slow_inputs); @@ -16449,8 +16451,10 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, } // JIT: result = CACHED_PTR(cache_slot + sizeof(void *)); - ref = ir_LOAD_A( - ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*))); + if (run_time_cache_ptr == IR_UNUSED) { + run_time_cache_ptr = ir_LOAD_A(jit_EX(run_time_cache)); + } + ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache_ptr, cache_slot + sizeof(void*))); // JIT: if (result) if_cached = ir_IF(ref); @@ -16467,7 +16471,7 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, if (!known_prop_info) { // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) prop_info_ref = ir_LOAD_L( - ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)); + ir_ADD_OFFSET(run_time_cache_ptr, cache_slot + sizeof(void*) * 2)); if_typed = ir_IF(ir_AND_U32( ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), ir_CONST_U32(_ZEND_TYPE_MASK))); @@ -16496,7 +16500,7 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, if (!known_prop_info) { // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) prop_info_ref = ir_LOAD_L( - ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)); + ir_ADD_OFFSET(run_time_cache_ptr, cache_slot + sizeof(void*) * 2)); if_typed = ir_IF(ir_AND_U32( ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), ir_CONST_U32(_ZEND_TYPE_MASK))); From b13050abbf76de1c1f534b84af4eff3651e2c0c9 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:56:20 +0100 Subject: [PATCH 2/3] JIT: Optimize final class scope check for static property access --- ext/opcache/jit/zend_jit_ir.c | 11 ++++- .../fetch_static_prop_static_final_001.phpt | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 ext/opcache/tests/jit/fetch_static_prop_static_final_001.phpt diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 376c6de699ac..23a87e24db3a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16438,7 +16438,16 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, } ir_ref run_time_cache_ptr = IR_UNUSED; - if (is_static) { + if (is_static + && op_array->function_name + && op_array->scope + && (op_array->scope->ce_flags & ZEND_ACC_FINAL) + && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) + && !(op_array->fn_flags & (ZEND_ACC_CLOSURE|ZEND_ACC_TRAIT_CLONE))) { + /* A method in a final CE has a called scope equal to the scope. + * So the cache slot is always monomorphic. + * Closures are excluded because they can be bound to another scope, likewise traits are shared. */ + } else if (is_static) { /* The cache slot is polymorphic on the called scope. */ // JIT: if (CACHED_PTR(cache_slot) == called_scope) run_time_cache_ptr = ir_LOAD_A(jit_EX(run_time_cache)); diff --git a/ext/opcache/tests/jit/fetch_static_prop_static_final_001.phpt b/ext/opcache/tests/jit/fetch_static_prop_static_final_001.phpt new file mode 100644 index 000000000000..307204814806 --- /dev/null +++ b/ext/opcache/tests/jit/fetch_static_prop_static_final_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +JIT: FETCH_STATIC_PROP static:: in a final class +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit_hot_func=1 +--FILE-- +get()); +} +?> +--EXPECT-- +string(10) "FinalClass" +string(10) "FinalClass" +string(8) "NonFinal" +string(13) "NonFinalChild" +string(8) "NonFinal" From a4894b6658a31da8e32b102a06fb03ce1036c265 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Wed, 11 Nov 2026 11:05:19 +0100 Subject: [PATCH 3/3] remove redundant trait check --- ext/opcache/jit/zend_jit_ir.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 23a87e24db3a..ea7ed81d5f18 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16442,7 +16442,6 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, && op_array->function_name && op_array->scope && (op_array->scope->ce_flags & ZEND_ACC_FINAL) - && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) && !(op_array->fn_flags & (ZEND_ACC_CLOSURE|ZEND_ACC_TRAIT_CLONE))) { /* A method in a final CE has a called scope equal to the scope. * So the cache slot is always monomorphic.