diff options
author | Dmitry Stogov <dmitry@zend.com> | 2020-09-14 19:36:15 +0300 |
---|---|---|
committer | Dmitry Stogov <dmitry@zend.com> | 2020-09-14 19:36:15 +0300 |
commit | 8f342ad4d2be23e898984900a8093be0f78e830a (patch) | |
tree | 06375b0d9be6d6b3136ff52a827a46ec600c3355 | |
parent | d0111d785df10de3eacf41716aff27e579ab67e8 (diff) | |
download | php-git-8f342ad4d2be23e898984900a8093be0f78e830a.tar.gz |
JIT for PRE/POST_INC/DEC_OBJ
-rw-r--r-- | ext/opcache/jit/zend_jit.c | 43 | ||||
-rw-r--r-- | ext/opcache/jit/zend_jit_disasm_x86.c | 11 | ||||
-rw-r--r-- | ext/opcache/jit/zend_jit_helpers.c | 429 | ||||
-rw-r--r-- | ext/opcache/jit/zend_jit_trace.c | 71 | ||||
-rw-r--r-- | ext/opcache/jit/zend_jit_x86.dasc | 387 |
5 files changed, 941 insertions, 0 deletions
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 939a0a1cb4..04bb52f352 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2457,6 +2457,49 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op goto jit_failure; } goto done; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + 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; + } + if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { + break; + } + ce = NULL; + ce_is_instanceof = 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(); + if (!(op1_info & MAY_BE_OBJECT)) { + break; + } + op1_addr = OP1_REG_ADDR(); + if (ssa->var_info && ssa->ops) { + 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->ce->create_object) { + ce = op1_ssa->ce; + ce_is_instanceof = op1_ssa->is_instanceof; + } + } + } + } + if (!zend_jit_incdec_obj(&dasm_state, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, + 0, ce, ce_is_instanceof, 0, NULL, + zend_may_throw(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; case ZEND_ASSIGN_OBJ_OP: if (opline->extended_value == ZEND_POW || opline->extended_value == ZEND_DIV) { diff --git a/ext/opcache/jit/zend_jit_disasm_x86.c b/ext/opcache/jit/zend_jit_disasm_x86.c index 8bae06d219..0146d2a1ec 100644 --- a/ext/opcache/jit/zend_jit_disasm_x86.c +++ b/ext/opcache/jit/zend_jit_disasm_x86.c @@ -456,6 +456,7 @@ static int zend_jit_disasm_init(void) 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_invalid_property_incdec); REGISTER_HELPER(zend_jit_invalid_property_assign); REGISTER_HELPER(zend_jit_invalid_property_assign_op); REGISTER_HELPER(zend_jit_prepare_assign_dim_ref); @@ -472,6 +473,16 @@ static int zend_jit_disasm_init(void) REGISTER_HELPER(zend_jit_assign_obj_op_helper); REGISTER_HELPER(zend_jit_assign_to_typed_prop); REGISTER_HELPER(zend_jit_assign_op_to_typed_prop); + REGISTER_HELPER(zend_jit_inc_typed_prop); + REGISTER_HELPER(zend_jit_dec_typed_prop); + REGISTER_HELPER(zend_jit_pre_inc_typed_prop); + REGISTER_HELPER(zend_jit_pre_dec_typed_prop); + REGISTER_HELPER(zend_jit_post_inc_typed_prop); + REGISTER_HELPER(zend_jit_post_dec_typed_prop); + REGISTER_HELPER(zend_jit_pre_inc_obj_helper); + REGISTER_HELPER(zend_jit_pre_dec_obj_helper); + REGISTER_HELPER(zend_jit_post_inc_obj_helper); + REGISTER_HELPER(zend_jit_post_dec_obj_helper); #undef REGISTER_HELPER #ifndef _WIN32 diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index c02ac48779..c1886b0266 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1858,6 +1858,13 @@ static void ZEND_FASTCALL zend_jit_invalid_property_write(zval *container, const property_name, zend_zval_type_name(container)); } +static void ZEND_FASTCALL zend_jit_invalid_property_incdec(zval *container, const char *property_name) +{ + zend_throw_error(NULL, + "Attempt to increment/decrement property \"%s\" on %s", + property_name, zend_zval_type_name(container)); +} + static void ZEND_FASTCALL zend_jit_invalid_property_assign(zval *container, const char *property_name) { zend_throw_error(NULL, @@ -2060,3 +2067,425 @@ static void ZEND_FASTCALL zend_jit_assign_obj_op_helper(zend_object *zobj, zend_ _zend_jit_assign_op_overloaded_property(zobj, name, cache_slot, value, binary_op); } } + +static ZEND_COLD zend_long _zend_jit_throw_inc_prop_error(zend_property_info *prop) +{ + zend_string *type_str = zend_type_to_string(prop->type); + zend_type_error("Cannot increment property %s::$%s of type %s past its maximal value", + ZSTR_VAL(prop->ce->name), + zend_get_unmangled_property_name(prop->name), + ZSTR_VAL(type_str)); + zend_string_release(type_str); + return ZEND_LONG_MAX; +} + +static ZEND_COLD zend_long _zend_jit_throw_dec_prop_error(zend_property_info *prop) +{ + zend_string *type_str = zend_type_to_string(prop->type); + zend_type_error("Cannot decrement property %s::$%s of type %s past its minimal value", + ZSTR_VAL(prop->ce->name), + zend_get_unmangled_property_name(prop->name), + ZSTR_VAL(type_str)); + zend_string_release(type_str); + return ZEND_LONG_MIN; +} + +static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info) +{ + zend_execute_data *execute_data = EG(current_execute_data); + zval tmp; + + ZVAL_COPY(&tmp, var_ptr); + + increment_function(var_ptr); + + if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE(tmp) == IS_LONG) { + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_inc_prop_error(prop_info); + ZVAL_LONG(var_ptr, val); + } + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(var_ptr); + ZVAL_COPY_VALUE(var_ptr, &tmp); + } else { + zval_ptr_dtor(&tmp); + } +} + +static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info) +{ + zend_execute_data *execute_data = EG(current_execute_data); + zval tmp; + + ZVAL_COPY(&tmp, var_ptr); + + decrement_function(var_ptr); + + if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE(tmp) == IS_LONG) { + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_dec_prop_error(prop_info); + ZVAL_LONG(var_ptr, val); + } + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(var_ptr); + ZVAL_COPY_VALUE(var_ptr, &tmp); + } else { + zval_ptr_dtor(&tmp); + } +} + +static void ZEND_FASTCALL zend_jit_pre_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +{ + zend_execute_data *execute_data = EG(current_execute_data); + zval tmp; + + if (!result) { + result = &tmp; + } + + ZVAL_COPY(result, var_ptr); + + increment_function(var_ptr); + + if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(result) == IS_LONG) { + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_inc_prop_error(prop_info); + ZVAL_LONG(var_ptr, val); + } + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(var_ptr); + ZVAL_COPY_VALUE(var_ptr, result); + ZVAL_UNDEF(result); + } else if (result == &tmp) { + zval_ptr_dtor(&tmp); + } + if (UNEXPECTED(result)) { + ZVAL_COPY(result, var_ptr); + } +} + +static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +{ + zend_execute_data *execute_data = EG(current_execute_data); + zval tmp; + + if (!result) { + result = &tmp; + } + + ZVAL_COPY(result, var_ptr); + + decrement_function(var_ptr); + + if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(result) == IS_LONG) { + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_dec_prop_error(prop_info); + ZVAL_LONG(var_ptr, val); + } + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(var_ptr); + ZVAL_COPY_VALUE(var_ptr, result); + ZVAL_UNDEF(result); + } else if (result == &tmp) { + zval_ptr_dtor(&tmp); + } + if (UNEXPECTED(result)) { + ZVAL_COPY(result, var_ptr); + } +} + +static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +{ + zend_execute_data *execute_data = EG(current_execute_data); + + ZVAL_COPY(result, var_ptr); + + increment_function(var_ptr); + + if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(result) == IS_LONG) { + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_inc_prop_error(prop_info); + ZVAL_LONG(var_ptr, val); + } + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(var_ptr); + ZVAL_COPY_VALUE(var_ptr, result); + ZVAL_UNDEF(result); + } +} + +static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +{ + zend_execute_data *execute_data = EG(current_execute_data); + + ZVAL_COPY(result, var_ptr); + + decrement_function(var_ptr); + + if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(result) == IS_LONG) { + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_dec_prop_error(prop_info); + ZVAL_LONG(var_ptr, val); + } + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(var_ptr); + ZVAL_COPY_VALUE(var_ptr, result); + ZVAL_UNDEF(result); + } +} + +static void ZEND_FASTCALL zend_jit_pre_inc_obj_helper(zend_object *zobj, zend_string *name, void **cache_slot, zval *result) +{ + zval *prop; + + if (EXPECTED((prop = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) { + if (UNEXPECTED(Z_ISERROR_P(prop))) { + if (UNEXPECTED(result)) { + ZVAL_NULL(result); + } + } else { + zend_property_info *prop_info = (zend_property_info *) CACHED_PTR_EX(cache_slot + 2); + + if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) { + fast_long_increment_function(prop); + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_inc_prop_error(prop_info); + ZVAL_LONG(prop, val); + } + } else { + do { + if (Z_ISREF_P(prop)) { + zend_reference *ref = Z_REF_P(prop); + prop = Z_REFVAL_P(prop); + if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(ref))) { + zend_jit_pre_inc_typed_ref(&ref->val, ref, result); + break; + } + } + + if (UNEXPECTED(prop_info)) { + zend_jit_pre_inc_typed_prop(prop, prop_info, result); + return; + } else { + increment_function(prop); + } + } while (0); + } + if (UNEXPECTED(result)) { + ZVAL_COPY(result, prop); + } + } + } else { + zval rv; + zval *z; + zval z_copy; + + GC_ADDREF(zobj); + z = zobj->handlers->read_property(zobj, name, BP_VAR_R, cache_slot, &rv); + if (UNEXPECTED(EG(exception))) { + OBJ_RELEASE(zobj); + if (UNEXPECTED(result)) { + ZVAL_NULL(result); + } + return; + } + + ZVAL_COPY_DEREF(&z_copy, z); + increment_function(&z_copy); + if (UNEXPECTED(result)) { + ZVAL_COPY(result, &z_copy); + } + zobj->handlers->write_property(zobj, name, &z_copy, cache_slot); + OBJ_RELEASE(zobj); + zval_ptr_dtor(&z_copy); + zval_ptr_dtor(z); + } +} + +static void ZEND_FASTCALL zend_jit_pre_dec_obj_helper(zend_object *zobj, zend_string *name, void **cache_slot, zval *result) +{ + zval *prop; + + if (EXPECTED((prop = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) { + if (UNEXPECTED(Z_ISERROR_P(prop))) { + if (UNEXPECTED(result)) { + ZVAL_NULL(result); + } + } else { + zend_property_info *prop_info = (zend_property_info *) CACHED_PTR_EX(cache_slot + 2); + + if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) { + fast_long_decrement_function(prop); + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_dec_prop_error(prop_info); + ZVAL_LONG(prop, val); + } + } else { + do { + if (Z_ISREF_P(prop)) { + zend_reference *ref = Z_REF_P(prop); + prop = Z_REFVAL_P(prop); + if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(ref))) { + zend_jit_pre_dec_typed_ref(&ref->val, ref, result); + break; + } + } + + if (UNEXPECTED(prop_info)) { + zend_jit_pre_dec_typed_prop(prop, prop_info, result); + return; + } else { + decrement_function(prop); + } + } while (0); + } + if (UNEXPECTED(result)) { + ZVAL_COPY(result, prop); + } + } + } else { + zval rv; + zval *z; + zval z_copy; + + GC_ADDREF(zobj); + z = zobj->handlers->read_property(zobj, name, BP_VAR_R, cache_slot, &rv); + if (UNEXPECTED(EG(exception))) { + OBJ_RELEASE(zobj); + if (UNEXPECTED(result)) { + ZVAL_NULL(result); + } + return; + } + + ZVAL_COPY_DEREF(&z_copy, z); + decrement_function(&z_copy); + if (UNEXPECTED(result)) { + ZVAL_COPY(result, &z_copy); + } + zobj->handlers->write_property(zobj, name, &z_copy, cache_slot); + OBJ_RELEASE(zobj); + zval_ptr_dtor(&z_copy); + zval_ptr_dtor(z); + } +} + +static void ZEND_FASTCALL zend_jit_post_inc_obj_helper(zend_object *zobj, zend_string *name, void **cache_slot, zval *result) +{ + zval *prop; + + if (EXPECTED((prop = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) { + if (UNEXPECTED(Z_ISERROR_P(prop))) { + ZVAL_NULL(result); + } else { + zend_property_info *prop_info = (zend_property_info*)CACHED_PTR_EX(cache_slot + 2); + + if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) { + ZVAL_LONG(result, Z_LVAL_P(prop)); + fast_long_increment_function(prop); + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_inc_prop_error(prop_info); + ZVAL_LONG(prop, val); + } + } else { + if (Z_ISREF_P(prop)) { + zend_reference *ref = Z_REF_P(prop); + prop = Z_REFVAL_P(prop); + if (ZEND_REF_HAS_TYPE_SOURCES(ref)) { + zend_jit_post_inc_typed_ref(&ref->val, ref, result); + return; + } + } + + if (UNEXPECTED(prop_info)) { + zend_jit_post_inc_typed_prop(prop, prop_info, result); + } else { + ZVAL_COPY(result, prop); + increment_function(prop); + } + } + } + } else { + zval rv; + zval *z; + zval z_copy; + + GC_ADDREF(zobj); + z = zobj->handlers->read_property(zobj, name, BP_VAR_R, cache_slot, &rv); + if (UNEXPECTED(EG(exception))) { + OBJ_RELEASE(zobj); + ZVAL_UNDEF(result); + return; + } + + ZVAL_COPY_DEREF(&z_copy, z); + ZVAL_COPY(result, &z_copy); + increment_function(&z_copy); + zobj->handlers->write_property(zobj, name, &z_copy, cache_slot); + OBJ_RELEASE(zobj); + zval_ptr_dtor(&z_copy); + zval_ptr_dtor(z); + } +} + +static void ZEND_FASTCALL zend_jit_post_dec_obj_helper(zend_object *zobj, zend_string *name, void **cache_slot, zval *result) +{ + zval *prop; + + if (EXPECTED((prop = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) { + if (UNEXPECTED(Z_ISERROR_P(prop))) { + ZVAL_NULL(result); + } else { + zend_property_info *prop_info = (zend_property_info*)CACHED_PTR_EX(cache_slot + 2); + + if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) { + ZVAL_LONG(result, Z_LVAL_P(prop)); + fast_long_decrement_function(prop); + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = _zend_jit_throw_dec_prop_error(prop_info); + ZVAL_LONG(prop, val); + } + } else { + if (Z_ISREF_P(prop)) { + zend_reference *ref = Z_REF_P(prop); + prop = Z_REFVAL_P(prop); + if (ZEND_REF_HAS_TYPE_SOURCES(ref)) { + zend_jit_post_dec_typed_ref(&ref->val, ref, result); + return; + } + } + + if (UNEXPECTED(prop_info)) { + zend_jit_post_dec_typed_prop(prop, prop_info, result); + } else { + ZVAL_COPY(result, prop); + decrement_function(prop); + } + } + } + } else { + zval rv; + zval *z; + zval z_copy; + + GC_ADDREF(zobj); + z = zobj->handlers->read_property(zobj, name, BP_VAR_R, cache_slot, &rv); + if (UNEXPECTED(EG(exception))) { + OBJ_RELEASE(zobj); + ZVAL_UNDEF(result); + return; + } + + ZVAL_COPY_DEREF(&z_copy, z); + ZVAL_COPY(result, &z_copy); + decrement_function(&z_copy); + zobj->handlers->write_property(zobj, name, &z_copy, cache_slot); + OBJ_RELEASE(zobj); + zval_ptr_dtor(&z_copy); + zval_ptr_dtor(z); + } +} diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 3612a60b3a..ad1ea8d978 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1439,6 +1439,10 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin } /* break missing intentionally */ case ZEND_ASSIGN_OBJ: + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: 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') { @@ -3732,6 +3736,73 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par goto jit_failure; } goto done; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + 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; + delayed_fetch_this = 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 { + if (ssa_op->op1_use >= 0) { + delayed_fetch_this = ssa->var_info[ssa_op->op1_use].delayed_fetch_this; + } + op1_info = OP1_INFO(); + if (!(op1_info & MAY_BE_OBJECT)) { + break; + } + op1_addr = OP1_REG_ADDR(); + if (opline->op1_type == IS_VAR) { + 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, !ssa->var_info[ssa_op->op1_use].indirect_reference)) { + 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, + !ssa->var_info[ssa_op->op1_use].guarded_reference, 1)) { + goto jit_failure; + } + if (opline->op1_type == IS_CV + && zend_jit_var_may_alias(op_array, op_array_ssa, EX_VAR_TO_NUM(opline->op1.var)) == NO_ALIAS) { + ssa->var_info[ssa_op->op1_use].guarded_reference = 1; + } + } else { + CHECK_OP1_TRACE_TYPE(); + } + 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->ce->create_object) { + ce = op1_ssa->ce; + ce_is_instanceof = op1_ssa->is_instanceof; + } + } + } + } + if (!zend_jit_incdec_obj(&dasm_state, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, + op1_indirect, ce, ce_is_instanceof, delayed_fetch_this, op1_ce, + zend_may_throw(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; case ZEND_ASSIGN_OBJ_OP: if (opline->extended_value == ZEND_POW || opline->extended_value == ZEND_DIV) { diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index ee325d5973..cfbbec04e2 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -12324,6 +12324,393 @@ static int zend_jit_fetch_obj(dasm_State **Dst, return 1; } +static int zend_jit_incdec_obj(dasm_State **Dst, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + zend_bool op1_indirect, + zend_class_entry *ce, + zend_bool ce_is_instanceof, + zend_bool use_this, + zend_class_entry *trace_ce, + int may_throw) +{ + zval *member; + zend_string *name; + zend_property_info *prop_info; + zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This)); + zend_jit_addr res_addr = 0; + zend_jit_addr prop_addr; + zend_bool needs_slow_path = 0; + + ZEND_ASSERT(opline->op2_type == IS_CONST); + ZEND_ASSERT(op1_info & MAY_BE_OBJECT); + + if (opline->result_type != IS_UNUSED) { + res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var); + } + + member = RT_CONSTANT(opline, opline->op2); + ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0'); + name = Z_STR_P(member); + prop_info = zend_get_known_property_info(ce, name, opline->op1_type == IS_UNUSED, op_array->filename); + + if (opline->op1_type == IS_UNUSED || use_this) { + | GET_ZVAL_PTR FCARG1a, this_addr + } else { + if (opline->op1_type == IS_VAR + && (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); + } + if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) { + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { + int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); + const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); + + if (!exit_addr) { + return 0; + } + | IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr + } else { + | IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >1 + |.cold_code + |1: + | SET_EX_OPLINE opline, r0 + if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) { + | LOAD_ZVAL_ADDR FCARG1a, op1_addr + } + | LOAD_ADDR FCARG2a, ZSTR_VAL(name) + | EXT_CALL zend_jit_invalid_property_incdec, r0 + | jmp ->exception_handler + |.code + } + } + | GET_ZVAL_PTR FCARG1a, op1_addr + } + + if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + prop_info = zend_get_known_property_info(trace_ce, name, opline->op1_type == IS_UNUSED, op_array->filename); + if (prop_info) { + ce = trace_ce; + ce_is_instanceof = 0; + if (!(op1_info & MAY_BE_CLASS_GUARD)) { + if (!zend_jit_class_guard(Dst, opline, trace_ce)) { + return 0; + } + if (ssa->var_info && ssa_op->op1_use >= 0) { + ssa->var_info[ssa_op->op1_use].type |= MAY_BE_CLASS_GUARD; + ssa->var_info[ssa_op->op1_use].ce = ce; + ssa->var_info[ssa_op->op1_use].is_instanceof = ce_is_instanceof; + } + if (ssa->var_info && ssa_op->op1_def >= 0) { + ssa->var_info[ssa_op->op1_def].type |= MAY_BE_CLASS_GUARD; + ssa->var_info[ssa_op->op1_def].ce = ce; + ssa->var_info[ssa_op->op1_def].is_instanceof = ce_is_instanceof; + } + } + } + } + + if (!prop_info) { + needs_slow_path = 1; + + | mov r0, EX->run_time_cache + | mov r2, aword [r0 + opline->extended_value] + | cmp r2, aword [FCARG1a + offsetof(zend_object, ce)] + | jne >7 + if (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) { + | cmp aword [r0 + opline->extended_value + sizeof(void*) * 2], 0 + | jnz >7 + } + | mov r0, aword [r0 + opline->extended_value + sizeof(void*)] + | test r0, r0 + | jl >7 + | IF_TYPE byte [FCARG1a + r0 + 8], IS_UNDEF, >7 + | add FCARG1a, r0 + prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0); + } else { + prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, prop_info->offset); + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { + int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); + const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); + + if (!exit_addr) { + return 0; + } + | IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, &exit_addr + } else { + | IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, >7 + needs_slow_path = 1; + } + if (ZEND_TYPE_IS_SET(prop_info->type)) { + | SET_EX_OPLINE opline, r0 + if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) { + | LOAD_ADDR FCARG2a, prop_info + } else { + int prop_info_offset = + (((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*)); + + | mov r0, aword [FCARG1a + offsetof(zend_object, ce)] + | mov r0, aword [r0 + offsetof(zend_class_entry, properties_info_table)] + | mov FCARG2a, aword[r0 + prop_info_offset] + } + | LOAD_ZVAL_ADDR FCARG1a, prop_addr + if (opline->result_type == IS_UNUSED) { + switch (opline->opcode) { + case ZEND_PRE_INC_OBJ: + case ZEND_POST_INC_OBJ: + | EXT_CALL zend_jit_inc_typed_prop, r0 + break; + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_DEC_OBJ: + | EXT_CALL zend_jit_dec_typed_prop, r0 + break; + default: + ZEND_UNREACHABLE(); + } + } else { + |.if X64 + | LOAD_ZVAL_ADDR CARG3, res_addr + |.else + | sub r4, 12 + | PUSH_ZVAL_ADDR res_addr, r0 + |.endif + switch (opline->opcode) { + case ZEND_PRE_INC_OBJ: + | EXT_CALL zend_jit_pre_inc_typed_prop, r0 + break; + case ZEND_PRE_DEC_OBJ: + | EXT_CALL zend_jit_pre_dec_typed_prop, r0 + break; + case ZEND_POST_INC_OBJ: + | EXT_CALL zend_jit_post_inc_typed_prop, r0 + break; + case ZEND_POST_DEC_OBJ: + | EXT_CALL zend_jit_post_dec_typed_prop, r0 + break; + default: + ZEND_UNREACHABLE(); + } + |.if not(X64) + | add r4, 12 + |.endif + } + } + } + + if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) { + zend_jit_addr var_addr = prop_addr; + + var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0); + | LOAD_ZVAL_ADDR r0, prop_addr + + | IF_NOT_ZVAL_TYPE var_addr, IS_REFERENCE, >2 + | GET_ZVAL_PTR FCARG2a, var_addr + | cmp aword [FCARG2a + offsetof(zend_reference, sources.ptr)], 0 + | jnz >1 + | lea r0, aword [FCARG2a + offsetof(zend_reference, val)] + |.cold_code + |1: + | lea FCARG1a, [FCARG2a + offsetof(zend_reference, val)] + if (opline) { + | SET_EX_OPLINE opline, r0 + } + |.if X64 + if (opline->result_type == IS_UNUSED) { + | xor CARG3, CARG3 + } else { + | LOAD_ZVAL_ADDR CARG3, res_addr + } + |.else + if (opline->result_type == IS_UNUSED) { + | push 0 + } else { + | PUSH_ZVAL_ADDR res_addr, r0 + } + |.endif + switch (opline->opcode) { + case ZEND_PRE_INC_OBJ: + | EXT_CALL zend_jit_pre_inc_typed_ref, r0 + break; + case ZEND_PRE_DEC_OBJ: + | EXT_CALL zend_jit_pre_dec_typed_ref, r0 + break; + case ZEND_POST_INC_OBJ: + | EXT_CALL zend_jit_post_inc_typed_ref, r0 + break; + case ZEND_POST_DEC_OBJ: + | EXT_CALL zend_jit_post_dec_typed_ref, r0 + break; + default: + ZEND_UNREACHABLE(); + } + |.if not(X64) + | add r4, 12 + |.endif + | jmp >9 + |.code + + |2: + | IF_NOT_ZVAL_TYPE var_addr, IS_LONG, >2 + if (opline->opcode == ZEND_POST_INC_OBJ || opline->opcode == ZEND_POST_DEC_OBJ) { + if (opline->result_type != IS_UNUSED) { + | ZVAL_COPY_VALUE res_addr, -1, var_addr, MAY_BE_LONG, ZREG_R1, ZREG_R2 + } + } + if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) { + | LONG_OP_WITH_32BIT_CONST add, var_addr, Z_L(1) + } else { + | LONG_OP_WITH_32BIT_CONST sub, var_addr, Z_L(1) + } + | jo >3 + if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ) { + if (opline->result_type != IS_UNUSED) { + | ZVAL_COPY_VALUE res_addr, -1, var_addr, MAY_BE_LONG, ZREG_R1, ZREG_R2 + } + } + |.cold_code + |2: + | LOAD_ZVAL_ADDR FCARG1a, var_addr + if (opline->opcode == ZEND_POST_INC_OBJ || opline->opcode == ZEND_POST_DEC_OBJ) { + zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0); + + | ZVAL_COPY_VALUE res_addr, -1, val_addr, MAY_BE_ANY, ZREG_R0, ZREG_R2 + | TRY_ADDREF MAY_BE_ANY, ah, r2 + } + if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) { + if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) { + | LOAD_ZVAL_ADDR FCARG2a, res_addr + | EXT_CALL zend_jit_pre_inc, r0 + } else { + | EXT_CALL increment_function, r0 + } + } else { + if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) { + | LOAD_ZVAL_ADDR FCARG2a, res_addr + | EXT_CALL zend_jit_pre_dec, r0 + } else { + | EXT_CALL decrement_function, r0 + } + } + | jmp >4 + + |3: + if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) { + |.if X64 + | mov64 rax, 0x43e0000000000000 + | SET_ZVAL_LVAL var_addr, rax + if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) { + | SET_ZVAL_LVAL res_addr, rax + } + |.else + | SET_ZVAL_LVAL var_addr, 0 + | SET_ZVAL_W2 var_addr, 0x41e00000 + if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) { + | SET_ZVAL_LVAL res_addr, 0 + | SET_ZVAL_W2 res_addr, 0x41e00000 + } + |.endif + } else { + |.if X64 + | mov64 rax, 0xc3e0000000000000 + | SET_ZVAL_LVAL var_addr, rax + if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) { + | SET_ZVAL_LVAL res_addr, rax + } + |.else + | SET_ZVAL_LVAL var_addr, 0x00200000 + | SET_ZVAL_W2 var_addr, 0xc1e00000 + if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) { + | SET_ZVAL_LVAL res_addr, 0x00200000 + | SET_ZVAL_W2 res_addr, 0xc1e00000 + } + |.endif + } + | jmp >4 + |.code + |4: + } + + if (needs_slow_path) { + |.cold_code + |7: + | SET_EX_OPLINE opline, r0 + | // value = zobj->handlers->write_property(zobj, name, value, CACHE_ADDR(opline->extended_value)); + | LOAD_ADDR FCARG2a, name + |.if X64 + | mov CARG3, EX->run_time_cache + | add CARG3, opline->extended_value + if (opline->result_type == IS_UNUSED) { + | xor CARG4, CARG4 + } else { + | LOAD_ZVAL_ADDR CARG4, res_addr + } + |.else + | sub r4, 8 + if (opline->result_type == IS_UNUSED) { + | push 0 + } else { + | PUSH_ZVAL_ADDR res_addr, r0 + } + | mov r0, EX->run_time_cache + | add r0, opline->extended_value + | push r0 + |.endif + + switch (opline->opcode) { + case ZEND_PRE_INC_OBJ: + | EXT_CALL zend_jit_pre_inc_obj_helper, r0 + break; + case ZEND_PRE_DEC_OBJ: + | EXT_CALL zend_jit_pre_dec_obj_helper, r0 + break; + case ZEND_POST_INC_OBJ: + | EXT_CALL zend_jit_post_inc_obj_helper, r0 + break; + case ZEND_POST_DEC_OBJ: + | EXT_CALL zend_jit_post_dec_obj_helper, r0 + break; + default: + ZEND_UNREACHABLE(); + } + + |.if not(X64) + | add r4, 8 + |.endif + + | jmp >9 + |.code + } + + |9: + if (opline->op1_type != IS_UNUSED && !use_this && !op1_indirect) { + | FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline + } + + if (may_throw) { + if (!zend_jit_check_exception(Dst)) { + return 0; + } + } + + return 1; +} + static int zend_jit_assign_obj_op(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, |