diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 01a5f50a32f6..6c0df98b63e3 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2527,7 +2527,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op && (opline->op2_type == IS_CONST || (opline->op2_type == IS_UNUSED && ((opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF - || (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT))))) { + || (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT + || (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_STATIC))))) { break; } if (!zend_jit_fetch_static_prop(&ctx, opline, op_array)) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 66b1b6a0c7b6..eef0a7cabbf1 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -9286,6 +9286,31 @@ static int zend_jit_init_method_call(zend_jit_ctx *jit, return 1; } +/* Called scope of the current frame, as used by static:: and inherited by self::/parent:: calls. */ +static ir_ref jit_called_scope(zend_jit_ctx *jit, const zend_op_array *op_array) +{ + if (op_array->fn_flags & ZEND_ACC_STATIC) { + // JIT: Z_CE(EX(This)) + return ir_LOAD_A(jit_EX(This.value.ref)); + } else if (op_array->scope + && op_array->function_name + && !(op_array->fn_flags & ZEND_ACC_CLOSURE)) { + // JIT: Z_OBJCE(EX(This)) + return ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce))); + } else { + /* Closures can be unbound, file/eval inherits This from the existing frame's scope. */ + ir_ref if_object, values = IR_UNUSED; + + if_object = ir_IF(ir_EQ(jit_Z_TYPE_ref(jit, jit_EX(This)), ir_CONST_U8(IS_OBJECT))); + ir_IF_TRUE(if_object); + ir_END_PHI_list(values, + ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce)))); + ir_IF_FALSE(if_object); + ir_END_PHI_list(values, ir_LOAD_A(jit_EX(This.value.ref))); + return ir_PHI_list(values); + } +} + static int zend_jit_init_static_method_call(zend_jit_ctx *jit, const zend_op *opline, uint32_t b, @@ -9417,22 +9442,7 @@ static int zend_jit_init_static_method_call(zend_jit_ctx *jit, if (opline->op1_type == IS_UNUSED && ((opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT || (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF)) { - if (op_array->fn_flags & ZEND_ACC_STATIC) { - scope_ref = ir_LOAD_A(jit_EX(This.value.ref)); - } else if ((op_array->fn_flags & ZEND_ACC_CLOSURE) || !op_array->function_name) { - /* Closures can be unbound, file/eval inherits This from the existing frame's scope. */ - ir_ref if_object, values = IR_UNUSED; - - if_object = ir_IF(ir_EQ(jit_Z_TYPE_ref(jit, jit_EX(This)), ir_CONST_U8(IS_OBJECT))); - ir_IF_TRUE(if_object); - ir_END_PHI_list(values, - ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce)))); - ir_IF_FALSE(if_object); - ir_END_PHI_list(values, ir_LOAD_A(jit_EX(This.value.ref))); - scope_ref = ir_PHI_list(values); - } else { - scope_ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce))); - } + scope_ref = jit_called_scope(jit, op_array); } if (!zend_jit_push_call_frame(jit, opline, op_array, func, false, false, checked_stack, func_ref, scope_ref)) { return 0; @@ -16334,11 +16344,14 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, uint32_t cache_slot = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS; uint32_t flags; ir_ref ref, ref2, if_cached, fast_path, cold_path, prop_info_ref, if_typed, if_def; + ir_ref slow_inputs = IR_UNUSED; int fetch_type; zend_property_info *known_prop_info = NULL; zend_class_entry *ce; + bool is_static = opline->op2_type == IS_UNUSED + && (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_STATIC; - ce = zend_get_known_class(op_array, opline, opline->op2_type, opline->op2); + ce = is_static ? NULL : zend_get_known_class(op_array, opline, opline->op2_type, opline->op2); if (ce) { zval *zv = RT_CONSTANT(opline, opline->op1); zend_string *prop_name; @@ -16381,6 +16394,17 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, default: ZEND_UNREACHABLE(); } + if (is_static) { + /* The cache slot is polymorphic on the called scope. */ + // JIT: if (CACHED_PTR(cache_slot) == called_scope) + ir_ref if_scope = ir_IF(ir_EQ( + ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot)), + jit_called_scope(jit, op_array))); + ir_IF_FALSE_cold(if_scope); + ir_END_list(slow_inputs); + ir_IF_TRUE(if_scope); + } + // 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*))); @@ -16456,6 +16480,10 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, fast_path = ir_END(); ir_IF_FALSE_cold(if_cached); + if (slow_inputs) { + ir_END_list(slow_inputs); + ir_MERGE_list(slow_inputs); + } jit_SET_EX_OPLINE(jit, opline); ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_fetch_static_property), jit_FP(jit), ir_CONST_I32(fetch_type)); zend_jit_check_exception_undef_result(jit, opline); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 9bdbf3a8a887..98c0e2829652 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -6172,7 +6172,8 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer, && (opline->op2_type == IS_CONST || (opline->op2_type == IS_UNUSED && ((opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF - || (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT))))) { + || (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT + || (opline->op2.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_STATIC))))) { break; } if (!zend_jit_fetch_static_prop(&ctx, opline, op_array)) { diff --git a/ext/opcache/tests/jit/fetch_static_prop_002.phpt b/ext/opcache/tests/jit/fetch_static_prop_002.phpt new file mode 100644 index 000000000000..090bb9d0d588 --- /dev/null +++ b/ext/opcache/tests/jit/fetch_static_prop_002.phpt @@ -0,0 +1,113 @@ +--TEST-- +FETCH_STATIC_PROP_* with static:: must respect the called scope +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=1 +opcache.jit_hot_return=1 +opcache.jit_hot_side_exit=1 +--EXTENSIONS-- +opcache +--FILE-- + static::$v; + } + + public static function push($x) { + static::$arr[] = $x; + } + + public static function has() { + return isset(static::$v); + } + + public static function getTyped() { + return static::$typed; + } +} + +// Used for the shared cache slot testing +class B extends A { + public static $v = 'B'; + public static array $arr = []; + public static $t = 'traitB'; +} + +$out = []; +for ($i = 0; $i < 8; $i++) { + $o = $i % 2 ? new B : new A; + $c = $i % 2 ? 'B' : 'A'; + $out[] = $c::getStatic() . $o->getInstance() . ($o->getClosure())() . ($c::getStaticClosure())() . $o->getT() . (int) $c::has(); + $c::push($i); +} +echo implode(',', $out), "\n"; +print_r(A::$arr); +echo "\n"; +print_r(B::$arr); +echo "\n"; + +for ($i = 0; $i < 2; $i++) { + try { + A::getTyped(); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} +A::$typed = 42; +var_dump(A::getTyped(), B::getTyped()); + +?> +--EXPECT-- +AAAAtrait1,BBBAtraitB1,AAAAtrait1,BBBAtraitB1,AAAAtrait1,BBBAtraitB1,AAAAtrait1,BBBAtraitB1 +Array +( + [0] => 0 + [1] => 2 + [2] => 4 + [3] => 6 +) + +Array +( + [0] => 1 + [1] => 3 + [2] => 5 + [3] => 7 +) + +Typed static property A::$typed must not be accessed before initialization +Typed static property A::$typed must not be accessed before initialization +int(42) +int(42)