diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 77dc322fbdec..4bd7421ff9a7 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -1087,6 +1087,48 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx #endif } + /* Elide the run-time type check on typed property writes (ASSIGN_OBJ) + * when the assigned value is statically proven to already satisfy the + * property type, so no verification or coercion is required. The flag is + * read by the ASSIGN_OBJ handler from the following OP_DATA opline. */ + for (int i = 0; i < (int) op_array->last; i++) { + zend_op *op = op_array->opcodes + i; + if (op->opcode != ZEND_ASSIGN_OBJ) { + continue; + } + + const zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, op, &ssa->ops[i]); + if (!prop_info + || !ZEND_TYPE_IS_SET(prop_info->type) + || !ZEND_TYPE_IS_ONLY_MASK(prop_info->type) + || prop_info->hooks + || (prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_VIRTUAL))) { + continue; + } + + /* The assigned value lives in the following OP_DATA opline. */ + zend_op *data = op + 1; + uint32_t val_type; + if (data->op1_type == IS_CONST) { + val_type = _const_op_type(CRT_CONSTANT_EX(op_array, data, data->op1)); + } else if (ssa->ops[i + 1].op1_use >= 0) { + val_type = ssa->var_info[ssa->ops[i + 1].op1_use].type; + } else { + continue; + } + + if (val_type & (MAY_BE_REF | MAY_BE_UNDEF)) { + continue; + } + + uint32_t pure = val_type & MAY_BE_ANY; + if (!pure || (pure & ~ZEND_TYPE_PURE_MASK(prop_info->type))) { + continue; + } + + op_array->opcodes[i + 1].extended_value |= ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK; + } + for (v = op_array->last_var; v < ssa->vars_count; v++) { op_1 = ssa->vars[v].definition; diff --git a/Zend/Optimizer/zend_dump.c b/Zend/Optimizer/zend_dump.c index c6cc5193b2dc..2afd9b43f1a0 100644 --- a/Zend/Optimizer/zend_dump.c +++ b/Zend/Optimizer/zend_dump.c @@ -770,6 +770,10 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block } } } + /* ASSIGN_OBJ carries the elision flag on its following OP_DATA opline. */ + if (opline->opcode == ZEND_ASSIGN_OBJ && ((opline + 1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { + fprintf(stderr, " (skip type check)"); + } } ZEND_API void zend_dump_op_line(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const void *data) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 05d33d3d75fb..9a9a5bbce67e 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2432,7 +2432,7 @@ static const zend_property_info *lookup_prop_info(const zend_class_entry *ce, ze return NULL; } -static const zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) +ZEND_API const zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) { const zend_property_info *prop_info = NULL; if (opline->op2_type == IS_CONST) { diff --git a/Zend/Optimizer/zend_inference.h b/Zend/Optimizer/zend_inference.h index e6b4207c7494..5c4ee27c9903 100644 --- a/Zend/Optimizer/zend_inference.h +++ b/Zend/Optimizer/zend_inference.h @@ -223,6 +223,8 @@ ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool wri ZEND_API bool zend_inference_propagate_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp); +ZEND_API const zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op); + ZEND_API uint32_t zend_fetch_arg_info_type( const zend_script *script, const zend_arg_info *arg_info, zend_class_entry **pce); ZEND_API void zend_init_func_return_info( diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 2351882a560d..448013dfe46f 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1206,6 +1206,11 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf, #define ZEND_RETURNS_FUNCTION (1<<0) #define ZEND_RETURNS_VALUE (1<<1) +/* Stored in the OP_DATA opline's extended_value for ZEND_ASSIGN_OBJ. Set by the + * optimizer when the assigned value is statically proven to already satisfy the + * (typed) property, so the run-time type verification can be skipped. */ +#define ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK (1<<0) + #define ZEND_ARRAY_ELEMENT_REF (1<<0) #define ZEND_ARRAY_NOT_PACKED (1<<1) #define ZEND_ARRAY_SIZE_SHIFT 2 diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1de7a7cd4195..7dd7c14f06bd 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2523,7 +2523,7 @@ ZEND_VM_C_LABEL(assign_object): ZEND_VM_C_LABEL(assign_obj_simple): property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); ZEND_VM_C_GOTO(free_and_exit_assign_obj); } else { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5b52f1941845..6741903d06b1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -24472,7 +24472,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -24629,7 +24629,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -24784,7 +24784,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -27180,7 +27180,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -27336,7 +27336,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -27490,7 +27490,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -31004,7 +31004,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -31161,7 +31161,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -31316,7 +31316,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -33538,7 +33538,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -33696,7 +33696,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -33852,7 +33852,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -35636,7 +35636,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -35793,7 +35793,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -35948,7 +35948,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -38205,7 +38205,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -38363,7 +38363,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -38519,7 +38519,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -42574,7 +42574,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -42732,7 +42732,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -42888,7 +42888,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -46397,7 +46397,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -46554,7 +46554,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -46709,7 +46709,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -51513,7 +51513,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -51671,7 +51671,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -51827,7 +51827,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -76944,7 +76944,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -77101,7 +77101,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -77256,7 +77256,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -79652,7 +79652,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -79808,7 +79808,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -79962,7 +79962,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -83476,7 +83476,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -83633,7 +83633,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -83788,7 +83788,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -86010,7 +86010,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -86168,7 +86168,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -86324,7 +86324,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -88108,7 +88108,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -88265,7 +88265,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -88420,7 +88420,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -90677,7 +90677,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -90835,7 +90835,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -90991,7 +90991,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -95046,7 +95046,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -95204,7 +95204,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -95360,7 +95360,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -98869,7 +98869,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -99026,7 +99026,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -99181,7 +99181,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -103883,7 +103883,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -104041,7 +104041,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { @@ -104197,7 +104197,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_obj_simple: property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { - if (prop_info != NULL) { + if (prop_info != NULL && !((opline+1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK)) { value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index cf43d3ad840f..7fc4fcd1a69a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14897,6 +14897,11 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } } + /* The optimizer proved the assigned value already satisfies the (typed) + * property, so the run-time type check can be skipped (see dfa_pass.c). */ + bool skip_type_check = prop_info + && ((opline + 1)->extended_value & ZEND_ASSIGN_OBJ_SKIP_TYPE_CHECK); + if (!prop_info) { ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache)); ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS)); @@ -14989,7 +14994,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, ir_END_list(slow_inputs); ir_IF_TRUE(if_def); } - if (ZEND_TYPE_IS_SET(prop_info->type)) { + if (ZEND_TYPE_IS_SET(prop_info->type) && !skip_type_check) { ir_ref ref, arg3, arg4; // JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); @@ -15031,7 +15036,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } } - if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) { + if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type) || skip_type_check) { if (Z_MODE(val_addr) != IS_REG && (res_addr == 0 || Z_MODE(res_addr) != IS_REG) && opline->result_type == IS_UNUSED) { diff --git a/ext/opcache/tests/named_parameter_new.phpt b/ext/opcache/tests/named_parameter_new.phpt index f8979766421c..4efd3ff64168 100644 --- a/ext/opcache/tests/named_parameter_new.phpt +++ b/ext/opcache/tests/named_parameter_new.phpt @@ -49,7 +49,7 @@ MyClass::__construct: 0001 CV1($bar) = RECV_INIT 2 int(0) 0002 ASSIGN_OBJ THIS string("foo") 0003 OP_DATA CV0($foo) -0004 ASSIGN_OBJ THIS string("bar") +0004 ASSIGN_OBJ THIS string("bar") (skip type check) 0005 OP_DATA CV1($bar) 0006 RETURN null diff --git a/ext/opcache/tests/opt/elide_typed_prop_write_check.phpt b/ext/opcache/tests/opt/elide_typed_prop_write_check.phpt new file mode 100644 index 000000000000..9ff018764f1c --- /dev/null +++ b/ext/opcache/tests/opt/elide_typed_prop_write_check.phpt @@ -0,0 +1,45 @@ +--TEST-- +Elide the type check on a typed property write when the value provably satisfies the property type +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--EXTENSIONS-- +opcache +--FILE-- +i = $i; } + public function coerce(int $x): void { $this->f = $x; } +} +echo "done\n"; +?> +--EXPECTF-- +$_main: + ; (lines=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 ECHO string("done\n") +0001 RETURN int(1) + +C::exact: + ; (lines=4, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:5-5 +0000 CV0($i) = RECV 1 +0001 ASSIGN_OBJ THIS string("i") (skip type check) +0002 OP_DATA CV0($i) +0003 RETURN null + +C::coerce: + ; (lines=4, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:6-6 +0000 CV0($x) = RECV 1 +0001 ASSIGN_OBJ THIS string("f") +0002 OP_DATA CV0($x) +0003 RETURN null +done diff --git a/ext/opcache/tests/opt/elide_typed_prop_write_check_behavior.phpt b/ext/opcache/tests/opt/elide_typed_prop_write_check_behavior.phpt new file mode 100644 index 000000000000..44fe92aaa0bf --- /dev/null +++ b/ext/opcache/tests/opt/elide_typed_prop_write_check_behavior.phpt @@ -0,0 +1,48 @@ +--TEST-- +Typed property write check elision preserves runtime behavior (exact match, coercion, both modes) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--EXTENSIONS-- +opcache +--FILE-- +i = $i; $this->s = $s; } + public function setBool(bool $b): void { $this->b = $b; } + public function setNullable(?int $ni): void { $this->ni = $ni; } + public function coerceToFloat(int $x): void { $this->f = $x; } +} + +$c = new C(5, "hello"); +var_dump($c->i, $c->s); +$c->setBool(true); +var_dump($c->b); +$c->setNullable(null); +var_dump($c->ni); +$c->setNullable(7); +var_dump($c->ni); +$c->coerceToFloat(3); +var_dump($c->f); + +class Promoted { + public function __construct(public int $x, public readonly string $y) {} +} +$p = new Promoted(1, "ok"); +var_dump($p->x, $p->y); +?> +--EXPECT-- +int(5) +string(5) "hello" +bool(true) +NULL +int(7) +float(3) +int(1) +string(2) "ok"