]> granicus.if.org Git - php/commitdiff
JIT for FETCH_OBJ_W
authorDmitry Stogov <dmitry@zend.com>
Fri, 19 Jun 2020 11:36:38 +0000 (14:36 +0300)
committerDmitry Stogov <dmitry@zend.com>
Fri, 19 Jun 2020 11:36:38 +0000 (14:36 +0300)
ext/opcache/jit/zend_jit.c
ext/opcache/jit/zend_jit_disasm_x86.c
ext/opcache/jit/zend_jit_helpers.c
ext/opcache/jit/zend_jit_trace.c
ext/opcache/jit/zend_jit_x86.dasc

index 223dd53ba6db2b93af9eb25073e80bc06295bf1d..53f47b7fac38d2c9d0a94a6ae725f191f97752dc 100644 (file)
@@ -1969,6 +1969,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
        zend_bool send_result;
        zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr;
        zend_class_entry *ce;
+       zend_bool ce_is_instanceof;
 
        if (JIT_G(bisect_limit)) {
                jit_bisect_pos++;
@@ -2759,16 +2760,19 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                goto done;
                                        case ZEND_FETCH_OBJ_R:
                                        case ZEND_FETCH_OBJ_IS:
+                                       case ZEND_FETCH_OBJ_W:
                                                if (opline->op2_type != IS_CONST
                                                 || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING
                                                 || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') {
                                                        break;
                                                }
                                                ce = NULL;
+                                               ce_is_instanceof = 0;
                                                if (opline->op1_type == IS_UNUSED) {
                                                        op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
                                                        op1_addr = 0;
                                                        ce = op_array->scope;
+                                                       ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
                                                } else {
                                                        op1_info = OP1_INFO();
                                                        if (!(op1_info & MAY_BE_OBJECT)) {
@@ -2779,14 +2783,15 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                                zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
                                                                if (ssa_op->op1_use >= 0) {
                                                                        zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
-                                                                       if (op1_ssa->ce && !op1_ssa->is_instanceof && !op1_ssa->ce->create_object) {
+                                                                       if (op1_ssa->ce && !op1_ssa->ce->create_object) {
                                                                                ce = op1_ssa->ce;
+                                                                               ce_is_instanceof = op1_ssa->is_instanceof;
                                                                        }
                                                                }
                                                        }
                                                }
-                                               if (!zend_jit_fetch_obj_read(&dasm_state, opline, op_array,
-                                                               op1_info, op1_addr, ce,
+                                               if (!zend_jit_fetch_obj(&dasm_state, opline, op_array,
+                                                               op1_info, op1_addr, 0, ce, ce_is_instanceof,
                                                                zend_may_throw(opline, ssa_op, op_array, ssa))) {
                                                        goto jit_failure;
                                                }
index ccabb564c9feaa9e09f33abc96b3089d4c6038d7..039efa5f8c8288209b867301474612c61d59d63c 100644 (file)
@@ -432,6 +432,10 @@ static int zend_jit_disasm_init(void)
        REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic);
        REGISTER_HELPER(zend_jit_fetch_obj_is_slow);
        REGISTER_HELPER(zend_jit_fetch_obj_is_dynamic);
+       REGISTER_HELPER(zend_jit_fetch_obj_w_slow);
+       REGISTER_HELPER(zend_jit_check_array_promotion);
+       REGISTER_HELPER(zend_jit_create_typed_ref);
+       REGISTER_HELPER(zend_jit_extract_helper);
        REGISTER_HELPER(zend_jit_vm_stack_free_args_helper);
        REGISTER_HELPER(zend_jit_copy_extra_args_helper);
        REGISTER_HELPER(zend_jit_deprecated_helper);
@@ -447,6 +451,7 @@ static int zend_jit_disasm_init(void)
        REGISTER_HELPER(zend_jit_only_vars_by_reference);
        REGISTER_HELPER(zend_jit_invalid_array_access);
        REGISTER_HELPER(zend_jit_invalid_property_read);
+       REGISTER_HELPER(zend_jit_invalid_property_write);
        REGISTER_HELPER(zend_jit_prepare_assign_dim_ref);
        REGISTER_HELPER(zend_jit_pre_inc);
        REGISTER_HELPER(zend_jit_pre_dec);
index 99b3f8817d567626fa142f7f7f14f76474c047f4..adbcc9004d9c3dd676d0c5cfb67f36f99376538a 100644 (file)
@@ -1345,6 +1345,193 @@ static void ZEND_FASTCALL zend_jit_fetch_obj_is_dynamic(zend_object *zobj, intpt
        zend_jit_fetch_obj_is_slow(zobj, offset, result, cache_slot);
 }
 
+static zend_always_inline zend_bool promotes_to_array(zval *val) {
+       return Z_TYPE_P(val) <= IS_FALSE
+               || (Z_ISREF_P(val) && Z_TYPE_P(Z_REFVAL_P(val)) <= IS_FALSE);
+}
+
+static zend_always_inline zend_bool check_type_array_assignable(zend_type type) {
+       if (!ZEND_TYPE_IS_SET(type)) {
+               return 1;
+       }
+       return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0;
+}
+
+static zend_property_info *zend_object_fetch_property_type_info(
+               zend_object *obj, zval *slot)
+{
+       if (EXPECTED(!ZEND_CLASS_HAS_TYPE_HINTS(obj->ce))) {
+               return NULL;
+       }
+
+       /* Not a declared property */
+       if (UNEXPECTED(slot < obj->properties_table ||
+                       slot >= obj->properties_table + obj->ce->default_properties_count)) {
+               return NULL;
+       }
+
+       return zend_get_typed_property_info_for_slot(obj, slot);
+}
+
+static zend_never_inline ZEND_COLD void zend_throw_auto_init_in_prop_error(zend_property_info *prop, const char *type) {
+       zend_string *type_str = zend_type_to_string(prop->type);
+       zend_type_error(
+               "Cannot auto-initialize an %s inside property %s::$%s of type %s",
+               type,
+               ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name),
+               ZSTR_VAL(type_str)
+       );
+       zend_string_release(type_str);
+}
+
+static zend_never_inline ZEND_COLD void zend_throw_access_uninit_prop_by_ref_error(
+               zend_property_info *prop) {
+       zend_throw_error(NULL,
+               "Cannot access uninitialized non-nullable property %s::$%s by reference",
+               ZSTR_VAL(prop->ce->name),
+               zend_get_unmangled_property_name(prop->name));
+}
+
+static zend_never_inline zend_bool zend_handle_fetch_obj_flags(
+               zval *result, zval *ptr, zend_object *obj, zend_property_info *prop_info, uint32_t flags)
+{
+       switch (flags) {
+               case ZEND_FETCH_DIM_WRITE:
+                       if (promotes_to_array(ptr)) {
+                               if (!prop_info) {
+                                       prop_info = zend_object_fetch_property_type_info(obj, ptr);
+                                       if (!prop_info) {
+                                               break;
+                                       }
+                               }
+                               if (!check_type_array_assignable(prop_info->type)) {
+                                       zend_throw_auto_init_in_prop_error(prop_info, "array");
+                                       if (result) ZVAL_ERROR(result);
+                                       return 0;
+                               }
+                       }
+                       break;
+               case ZEND_FETCH_REF:
+                       if (Z_TYPE_P(ptr) != IS_REFERENCE) {
+                               if (!prop_info) {
+                                       prop_info = zend_object_fetch_property_type_info(obj, ptr);
+                                       if (!prop_info) {
+                                               break;
+                                       }
+                               }
+                               if (Z_TYPE_P(ptr) == IS_UNDEF) {
+                                       if (!ZEND_TYPE_ALLOW_NULL(prop_info->type)) {
+                                               zend_throw_access_uninit_prop_by_ref_error(prop_info);
+                                               if (result) ZVAL_ERROR(result);
+                                               return 0;
+                                       }
+                                       ZVAL_NULL(ptr);
+                               }
+
+                               ZVAL_NEW_REF(ptr, ptr);
+                               ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(ptr), prop_info);
+                       }
+                       break;
+               EMPTY_SWITCH_DEFAULT_CASE()
+       }
+       return 1;
+}
+
+static void ZEND_FASTCALL zend_jit_fetch_obj_w_slow(zend_object *zobj, zval *offset, zval *result, uint32_t cache_slot)
+{
+       zval *retval;
+       zend_execute_data *execute_data = EG(current_execute_data);
+       const zend_op *opline = execute_data->opline;
+       zend_string *name, *tmp_name;
+
+       name = zval_get_tmp_string(offset, &tmp_name);
+       retval = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_W, CACHE_ADDR(cache_slot));
+       if (NULL == retval) {
+               retval = zobj->handlers->read_property(zobj, name, BP_VAR_W, CACHE_ADDR(cache_slot), result);
+               if (retval == result) {
+                       if (UNEXPECTED(Z_ISREF_P(retval) && Z_REFCOUNT_P(retval) == 1)) {
+                               ZVAL_UNREF(retval);
+                       }
+                       goto end;
+               }
+       } else if (UNEXPECTED(Z_ISERROR_P(retval))) {
+               ZVAL_ERROR(result);
+               goto end;
+       }
+
+       ZVAL_INDIRECT(result, retval);
+
+       /* Support for typed properties */
+       do {
+               uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
+
+               if (flags) {
+                       zend_property_info *prop_info = NULL;
+
+                       if (opline->op2_type == IS_CONST) {
+                               prop_info = CACHED_PTR_EX(CACHE_ADDR(cache_slot) + 2);
+                               if (!prop_info) {
+                                       break;
+                               }
+                       }
+                       if (UNEXPECTED(!zend_handle_fetch_obj_flags(result, retval, zobj, prop_info, flags))) {
+                               goto end;
+                       }
+               }
+       } while (0);
+
+       if (UNEXPECTED(Z_TYPE_P(retval) == IS_UNDEF)) {
+               ZVAL_NULL(retval);
+       }
+
+end:
+       zend_tmp_string_release(tmp_name);
+}
+
+static void ZEND_FASTCALL zend_jit_check_array_promotion(zval *val, zend_property_info *prop)
+{
+       zend_execute_data *execute_data = EG(current_execute_data);
+       const zend_op *opline = execute_data->opline;
+       zval *result = EX_VAR(opline->result.var);
+
+       if (((Z_TYPE_P(val) <= IS_FALSE
+         || (Z_ISREF_P(val) && Z_TYPE_P(Z_REFVAL_P(val)) <= IS_FALSE))
+        &&     ZEND_TYPE_IS_SET(prop->type)
+        && ZEND_TYPE_FULL_MASK(prop->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) {
+               zend_string *type_str = zend_type_to_string(prop->type);
+               zend_type_error(
+                       "Cannot auto-initialize an array inside property %s::$%s of type %s",
+                       ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name),
+                       ZSTR_VAL(type_str)
+               );
+               zend_string_release(type_str);
+               ZVAL_ERROR(result);
+       } else {
+               ZVAL_INDIRECT(result, val);
+       }
+}
+
+static void ZEND_FASTCALL zend_jit_create_typed_ref(zval *val, zend_property_info *prop, zval *result)
+{
+       if (!Z_ISREF_P(val)) {
+               ZVAL_NEW_REF(val, val);
+               ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(val), prop);
+       }
+       ZVAL_INDIRECT(result, val);
+}
+
+static void ZEND_FASTCALL zend_jit_extract_helper(zend_refcounted *garbage)
+{
+       zend_execute_data *execute_data = EG(current_execute_data);
+       const zend_op *opline = execute_data->opline;
+       zval *zv = EX_VAR(opline->result.var);
+
+       if (EXPECTED(Z_TYPE_P(zv) == IS_INDIRECT)) {
+               ZVAL_COPY(zv, Z_INDIRECT_P(zv));
+       }
+       rc_dtor_func(garbage);
+}
+
 static void ZEND_FASTCALL zend_jit_vm_stack_free_args_helper(zend_execute_data *call)
 {
        zend_vm_stack_free_args(call);
@@ -1508,6 +1695,13 @@ static void ZEND_FASTCALL zend_jit_invalid_property_read(zval *container, const
        zend_error(E_WARNING, "Attempt to read property '%s' on %s", property_name, zend_zval_type_name(container));
 }
 
+static void ZEND_FASTCALL zend_jit_invalid_property_write(zval *container, const char *property_name)
+{
+       zend_throw_error(NULL,
+               "Attempt to modify property '%s' on %s",
+               property_name, zend_zval_type_name(container));
+}
+
 static zval * ZEND_FASTCALL zend_jit_prepare_assign_dim_ref(zval *ref) {
        zval *val = Z_REFVAL_P(ref);
        if (Z_TYPE_P(val) <= IS_FALSE) {
index 3bcb5a7697d74f591f8d85ae24ee0b033be0c570..a2f0784ade0f65f7d7e8b47b05efa725337f31f7 100644 (file)
@@ -1557,6 +1557,7 @@ propagate_arg:
                                        /* break missing intentionally */
                                case ZEND_FETCH_OBJ_R:
                                case ZEND_FETCH_OBJ_IS:
+                               case ZEND_FETCH_OBJ_W:
                                        if (opline->op2_type != IS_CONST
                                         || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING
                                         || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') {
@@ -2632,6 +2633,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
        zend_bool send_result = 0;
        zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr;
        zend_class_entry *ce;
+       zend_bool ce_is_instanceof;
        uint32_t i;
        zend_jit_trace_stack_frame *frame, *top, *call;
        zend_jit_trace_stack *stack;
@@ -2865,6 +2867,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
                        uint8_t op2_type = p->op2_type;
                        uint8_t op3_type = p->op3_type;
                        uint8_t orig_op1_type = op1_type;
+                       zend_bool op1_indirect;
 
                        opline = p->opline;
                        if (op1_type & (IS_TRACE_REFERENCE|IS_TRACE_INDIRECT)) {
@@ -3728,15 +3731,19 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
                                                /* break missing intentionally */
                                        case ZEND_FETCH_OBJ_R:
                                        case ZEND_FETCH_OBJ_IS:
+                                       case ZEND_FETCH_OBJ_W:
                                                if (opline->op2_type != IS_CONST
                                                 || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING
                                                 || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') {
                                                        break;
                                                }
                                                ce = NULL;
+                                               ce_is_instanceof = 0;
+                                               op1_indirect = 0;
                                                if (opline->op1_type == IS_UNUSED) {
                                                        op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
                                                        ce = op_array->scope;
+                                                       ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
                                                        op1_addr = 0;
                                                } else {
                                                        op1_info = OP1_INFO();
@@ -3744,6 +3751,16 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
                                                                break;
                                                        }
                                                        op1_addr = OP1_REG_ADDR();
+                                                       if (opline->op1_type == IS_VAR
+                                                        && opline->opcode == ZEND_FETCH_OBJ_W) {
+                                                               if (orig_op1_type != IS_UNKNOWN
+                                                                && (orig_op1_type & IS_TRACE_INDIRECT)) {
+                                                                       op1_indirect = 1;
+                                                                       if (!zend_jit_fetch_indirect_var(&dasm_state, opline, orig_op1_type, &op1_info, &op1_addr)) {
+                                                                               goto jit_failure;
+                                                                       }
+                                                               }
+                                                       }
                                                        if (orig_op1_type != IS_UNKNOWN
                                                         && (orig_op1_type & IS_TRACE_REFERENCE)) {
                                                                if (!zend_jit_fetch_reference(&dasm_state, opline, orig_op1_type, &op1_info, &op1_addr, 1)) {
@@ -3755,14 +3772,15 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
                                                        if (ssa->var_info && ssa->ops) {
                                                                if (ssa_op->op1_use >= 0) {
                                                                        zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
-                                                                       if (op1_ssa->ce && !op1_ssa->is_instanceof && !op1_ssa->ce->create_object) {
+                                                                       if (op1_ssa->ce && !op1_ssa->ce->create_object) {
                                                                                ce = op1_ssa->ce;
+                                                                               ce_is_instanceof = op1_ssa->is_instanceof;
                                                                        }
                                                                }
                                                        }
                                                }
-                                               if (!zend_jit_fetch_obj_read(&dasm_state, opline, op_array,
-                                                               op1_info, op1_addr, ce,
+                                               if (!zend_jit_fetch_obj(&dasm_state, opline, op_array,
+                                                               op1_info, op1_addr, op1_indirect, ce, ce_is_instanceof,
                                                                zend_may_throw(opline, ssa_op, op_array, ssa))) {
                                                        goto jit_failure;
                                                }
index b4306f5bdba9717ee72f7beabab7c10f68942825..29d462b4e3a637986875ad96bf4e598a088d3725 100644 (file)
@@ -10712,10 +10712,12 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zen
 
 #define ZEND_WRONG_PROPERTY_OFFSET 0
 
-static uint32_t zend_get_known_property_offset(zend_class_entry *ce, zend_string *member, zend_bool on_this, zend_string *filename)
+static uint32_t zend_get_known_property_offset(zend_class_entry *ce, zend_string *member, zend_bool on_this, zend_string *filename, zend_property_info **prop_info)
 {
        zend_property_info *info;
 
+       *prop_info = NULL;
+
        if (!ce || !(ce->ce_flags & ZEND_ACC_LINKED) || (ce->ce_flags & ZEND_ACC_TRAIT)) {
                return ZEND_WRONG_PROPERTY_OFFSET;
        }
@@ -10756,6 +10758,7 @@ static uint32_t zend_get_known_property_offset(zend_class_entry *ce, zend_string
                return ZEND_WRONG_PROPERTY_OFFSET;
        }
 
+       *prop_info = info;
        return info->offset;
 }
 
@@ -10787,10 +10790,11 @@ static zend_bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string
        return 0;
 }
 
-static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, zend_class_entry *ce, int may_throw)
+static int zend_jit_fetch_obj(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, zend_bool op1_indirect, zend_class_entry *ce, zend_bool ce_is_instanceof, int may_throw)
 {
        zval *member;
        uint32_t offset;
+       zend_property_info *prop_info;
        zend_bool may_be_dynamic = 1;
        zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
        zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
@@ -10801,13 +10805,25 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
 
        member = RT_CONSTANT(opline, opline->op2);
        ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0');
-       offset = zend_get_known_property_offset(ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename);
+       offset = zend_get_known_property_offset(ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename, &prop_info);
 
        if (opline->op1_type == IS_UNUSED) {
                |       GET_ZVAL_PTR FCARG1a, this_addr
        } else {
-               if (op1_info & MAY_BE_REF) {
+               if (opline->op1_type == IS_VAR
+                && opline->opcode == ZEND_FETCH_OBJ_W
+                && (op1_info & MAY_BE_INDIRECT)
+                && Z_REG(op1_addr) == ZREG_FP) {
                        |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
+                       |       IF_NOT_Z_TYPE FCARG1a, IS_INDIRECT, >1
+                       |       GET_Z_PTR FCARG1a, FCARG1a
+                       |1:
+                       op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
+               }
+               if (op1_info & MAY_BE_REF) {
+                       if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
+                               |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
+                       }
                        |       ZVAL_DEREF FCARG1a, op1_info
                        op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
                }
@@ -10836,12 +10852,48 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
                may_be_dynamic = zend_may_be_dynamic_property(ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename);
                if (may_be_dynamic) {
                        |       test r0, r0
-                       |       jl >8 // dynamic property
+                       if (opline->opcode == ZEND_FETCH_OBJ_W) {
+                               |       jl >5
+                       } else {
+                               |       jl >8 // dynamic property
+                       }
                }
                |       mov edx, dword [FCARG1a + r0 + 8]
                |       IF_UNDEF dl, >5
                |       add FCARG1a, r0
                prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
+               if (opline->opcode == ZEND_FETCH_OBJ_W
+                && (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
+                && (!ce ||     ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS))) {
+                       uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
+
+                       |       mov r0, EX->run_time_cache
+                       |       mov FCARG2a, aword [r0 + (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2]
+                       |       test FCARG2a, FCARG2a
+                       |       jnz >1
+                       |.cold_code
+                       |1:
+                       if (flags == ZEND_FETCH_DIM_WRITE) {
+                               |       SAVE_VALID_OPLINE opline, r0
+                               |       EXT_CALL zend_jit_check_array_promotion, r0
+                               |       jmp >9
+                       } else if (flags == ZEND_FETCH_REF) {
+                               |.if X64
+                                       |       LOAD_ZVAL_ADDR CARG3, res_addr
+                               |.else
+                                       |       sub r4, 12
+                                       |       PUSH_ZVAL_ADDR res_addr, r0
+                               |.endif
+                               |       EXT_CALL zend_jit_create_typed_ref, r0
+                               |.if not(X64)
+                               |       add r4, 12
+                               |.endif
+                               |       jmp >9
+                       } else {
+                               ZEND_UNREACHABLE();
+                       }
+                       |.code
+               }
        } else {
                prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, offset);
                |       mov edx, dword [FCARG1a + offset + 8]
@@ -10856,9 +10908,59 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
                } else {
                        |       IF_UNDEF dl, >5
                }
+               if (opline->opcode == ZEND_FETCH_OBJ_W
+                && (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
+                && ZEND_TYPE_IS_SET(prop_info->type)) {
+                       uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
+
+                       if (flags == ZEND_FETCH_DIM_WRITE) {
+                               if ((ZEND_TYPE_FULL_MASK(prop_info->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) {
+                                       |       cmp dl, IS_FALSE
+                                       |       jle >1
+                                       |.cold_code
+                                       |1:
+                                       if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
+                                               |       LOAD_ZVAL_ADDR FCARG1a, prop_addr
+                                       }
+                                       |       LOAD_ADDR FCARG2a, prop_info
+                                       |       SAVE_VALID_OPLINE opline, r0
+                                       |       EXT_CALL zend_jit_check_array_promotion, r0
+                                       |       jmp >9
+                                       |.code
+                               }
+                       } else if (flags == ZEND_FETCH_REF) {
+                               |       IF_TYPE dl, IS_REFERENCE, >1
+                               if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
+                                       |       LOAD_ZVAL_ADDR FCARG1a, prop_addr
+                               }
+                               |       LOAD_ADDR FCARG2a, prop_info
+                               |.if X64
+                                       |       LOAD_ZVAL_ADDR CARG3, res_addr
+                               |.else
+                                       |       sub r4, 12
+                                       |       PUSH_ZVAL_ADDR res_addr, r0
+                               |.endif
+                               |       EXT_CALL zend_jit_create_typed_ref, r0
+                               |.if not(X64)
+                               |       add r4, 12
+                               |.endif
+                               |       jmp >9
+                               |1:
+                       } else {
+                               ZEND_UNREACHABLE();
+                       }
+               }
        }
-       if (!zend_jit_zval_copy_deref(Dst, res_addr, prop_addr, ZREG_R2)) {
-               return 0;
+       if (opline->opcode == ZEND_FETCH_OBJ_W) {
+               if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
+                       |       LOAD_ZVAL_ADDR FCARG1a, prop_addr
+               }
+               |       SET_ZVAL_PTR res_addr, FCARG1a
+               |       SET_ZVAL_TYPE_INFO res_addr, IS_INDIRECT
+       } else {
+               if (!zend_jit_zval_copy_deref(Dst, res_addr, prop_addr, ZREG_R2)) {
+                       return 0;
+               }
        }
 
        |.cold_code
@@ -10875,7 +10977,9 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
                        |       PUSH_ZVAL_ADDR res_addr, r0
                |.endif
                |       SAVE_VALID_OPLINE opline, r0
-               if (opline->opcode != ZEND_FETCH_OBJ_IS) {
+               if (opline->opcode == ZEND_FETCH_OBJ_W) {
+                       |       EXT_CALL zend_jit_fetch_obj_w_slow, r0
+               } else if (opline->opcode != ZEND_FETCH_OBJ_IS) {
                        |       EXT_CALL zend_jit_fetch_obj_r_slow, r0
                } else {
                        |       EXT_CALL zend_jit_fetch_obj_is_slow, r0
@@ -10890,7 +10994,8 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
                |7:
                if (opline->opcode != ZEND_FETCH_OBJ_IS) {
                        |       SAVE_VALID_OPLINE opline, r1
-                       if (op1_info & MAY_BE_UNDEF) {
+                       if (opline->opcode != ZEND_FETCH_OBJ_W
+                        && (op1_info & MAY_BE_UNDEF)) {
                                zend_jit_addr orig_op1_addr = OP1_ADDR();
 
                                if (op1_info & MAY_BE_ANY) {
@@ -10900,17 +11005,27 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
                                |       EXT_CALL zend_jit_undefined_op_helper, r0
                                |1:
                                |       LOAD_ZVAL_ADDR FCARG1a, orig_op1_addr
-                       } else {
+                       } else if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
                                |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
                        }
                        |       LOAD_ADDR FCARG2a, Z_STRVAL_P(member)
-                       |       EXT_CALL zend_jit_invalid_property_read, r0
+                       if (opline->opcode == ZEND_FETCH_OBJ_W) {
+                               |       EXT_CALL zend_jit_invalid_property_write, r0
+                               |       SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR
+                       } else {
+                               |       EXT_CALL zend_jit_invalid_property_read, r0
+                               |       SET_ZVAL_TYPE_INFO res_addr, IS_NULL
+                       }
+                       |       jmp >9
+               } else {
+                       |       SET_ZVAL_TYPE_INFO res_addr, IS_NULL
+                       |       jmp >9
                }
-               |       SET_ZVAL_TYPE_INFO res_addr, IS_NULL
-               |       jmp >9
        }
 
-       if (offset == ZEND_WRONG_PROPERTY_OFFSET && may_be_dynamic) {
+       if (offset == ZEND_WRONG_PROPERTY_OFFSET
+        && may_be_dynamic
+        && opline->opcode != ZEND_FETCH_OBJ_W) {
                |8:
                |       mov FCARG2a, r0
                |.if X64WIN
@@ -10941,7 +11056,22 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, cons
 
        |.code;
        |9: // END
-       |       FREE_OP opline->op1_type, opline->op1, op1_info, 1, op_array, opline
+       if (!op1_indirect) {
+               if (opline->op1_type == IS_VAR
+                && opline->opcode == ZEND_FETCH_OBJ_W) {
+                       zend_jit_addr orig_op1_addr = OP1_ADDR();
+
+                       |       IF_NOT_ZVAL_REFCOUNTED orig_op1_addr, >1
+                       |       GET_ZVAL_PTR FCARG1a, orig_op1_addr
+                       |       GC_DELREF FCARG1a
+                       |       jnz >1
+                       |       SAVE_VALID_OPLINE opline, r0
+                       |       EXT_CALL zend_jit_extract_helper, r0
+                       |1:
+               } else {
+                       |       FREE_OP opline->op1_type, opline->op1, op1_info, 1, op_array, opline
+               }
+       }
 
        if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
         && offset != ZEND_WRONG_PROPERTY_OFFSET
@@ -11408,6 +11538,51 @@ static zend_bool zend_jit_fetch_reference(dasm_State **Dst, const zend_op *oplin
        return 1;
 }
 
+static zend_bool zend_jit_fetch_indirect_var(dasm_State **Dst, const zend_op *opline, uint8_t var_type, uint32_t *var_info_ptr, zend_jit_addr *var_addr_ptr)
+{
+       zend_jit_addr var_addr = *var_addr_ptr;
+       uint32_t var_info = *var_info_ptr;
+       int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL, 0);
+       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+       if (!exit_addr) {
+               return 0;
+       }
+
+       |       IF_NOT_ZVAL_TYPE var_addr, IS_INDIRECT, &exit_addr
+       |       GET_ZVAL_PTR FCARG1a, var_addr
+       var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
+       *var_addr_ptr = var_addr;
+
+       var_type &= ~IS_TRACE_INDIRECT;
+       if (!(var_type & IS_TRACE_REFERENCE)
+        && var_type != IS_UNKNOWN
+        && (var_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << var_type)) {
+               exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL, 0);
+               exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+               if (!exit_addr) {
+                       return 0;
+               }
+
+               |       IF_NOT_Z_TYPE FCARG1a, var_type, &exit_addr
+
+               //var_info = zend_jit_trace_type_to_info_ex(var_type, var_info);
+               ZEND_ASSERT(var_info & (1 << var_type));
+               if (var_type < IS_STRING) {
+                       var_info = (1 << var_type);
+               } else if (var_type != IS_ARRAY) {
+                       var_info = (1 << var_type) | (var_info & (MAY_BE_RC1|MAY_BE_RCN));
+               } else {
+                       var_info = MAY_BE_ARRAY | (var_info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_RC1|MAY_BE_RCN));
+               }
+
+               *var_info_ptr = var_info;
+       }
+
+       return 1;
+}
+
 static zend_bool zend_jit_may_reuse_reg(const zend_op *opline, const zend_ssa_op *ssa_op, zend_ssa *ssa, int def_var, int use_var)
 {
        if ((ssa->var_info[def_var].type & ~MAY_BE_GUARD) != (ssa->var_info[use_var].type & ~MAY_BE_GUARD)) {