Commit 90fb1e6799e for php

commit 90fb1e6799ee97307a34a05e27d6055d32a4bd1c
Author: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Date:   Wed Nov 11 12:02:21 2026 +0100

    JIT: Minor optimizations to static property access (#25381)

    * JIT: Avoid run time cache reload in static property access

    * JIT: Optimize final class scope check for static property access

    * remove redundant trait check

diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 0f50d6915b3..ea7ed81d5f1 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -16437,11 +16437,21 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline,
 		default: ZEND_UNREACHABLE();
 	}

-	if (is_static) {
+	ir_ref run_time_cache_ptr = IR_UNUSED;
+	if (is_static
+	 && op_array->function_name
+	 && op_array->scope
+	 && (op_array->scope->ce_flags & ZEND_ACC_FINAL)
+	 && !(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));
 		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 +16459,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 +16479,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 +16508,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)));
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 00000000000..30720481480
--- /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--
+<?php
+final class FinalClass {
+    public static $prop = 'FinalClass';
+
+    function get() {
+        return static::$prop;
+    }
+}
+
+class NonFinal {
+    public static $prop = 'NonFinal';
+
+    function get() {
+        return static::$prop;
+    }
+}
+
+class NonFinalChild extends NonFinal {
+    public static $prop = 'NonFinalChild';
+}
+
+foreach ([new FinalClass, new FinalClass, new NonFinal, new NonFinalChild, new NonFinal] as $obj) {
+    var_dump($obj->get());
+}
+?>
+--EXPECT--
+string(10) "FinalClass"
+string(10) "FinalClass"
+string(8) "NonFinal"
+string(13) "NonFinalChild"
+string(8) "NonFinal"