diff options
author | Dmitry Stogov <dmitry@zend.com> | 2016-04-07 17:34:53 +0300 |
---|---|---|
committer | Dmitry Stogov <dmitry@zend.com> | 2016-04-07 17:34:53 +0300 |
commit | 3444c1ae2400b279b936c4ca04cb84dfdfb57bbf (patch) | |
tree | a63676497e4c163bb917a183780180e743a1acd2 | |
parent | 708d030b694f89a001c5f5ed6e3af25e7097f483 (diff) | |
download | php-git-3444c1ae2400b279b936c4ca04cb84dfdfb57bbf.tar.gz |
Use return type hints for type inference and eliminate useless VERIFY_RETRUN_TYPE opcodes.
-rw-r--r-- | Zend/zend_compile.c | 24 | ||||
-rw-r--r-- | ext/opcache/Optimizer/dfa_pass.c | 22 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_dfg.c | 4 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_inference.c | 121 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_inference.h | 4 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_optimizer.c | 9 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_ssa.c | 8 |
7 files changed, 155 insertions, 37 deletions
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5f3114d10e..da2c83c55e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2265,12 +2265,30 @@ static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */ static void zend_emit_return_type_check(znode *expr, zend_arg_info *return_info) /* {{{ */ { /* `return ...;` is illegal in a void function (but `return;` isn't) */ - if (expr && return_info->type_hint == IS_VOID) { - zend_error_noreturn(E_COMPILE_ERROR, "A void function must not return a value"); + if (return_info->type_hint == IS_VOID) { + if (expr) { + zend_error_noreturn(E_COMPILE_ERROR, "A void function must not return a value"); + } + /* we don't need run-time check */ + return; } if (return_info->type_hint != IS_UNDEF) { - zend_op *opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL); + zend_op *opline; + + if (expr && expr->op_type == IS_CONST) { + if ((return_info->type_hint == Z_TYPE(expr->u.constant)) + ||((return_info->type_hint == _IS_BOOL) + && (Z_TYPE(expr->u.constant) == IS_FALSE + || Z_TYPE(expr->u.constant) == IS_TRUE)) + || (return_info->allow_null + && Z_TYPE(expr->u.constant) == IS_NULL)) { + /* we don't need run-time check */ + return; + } + } + + opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL); if (expr && expr->op_type == IS_CONST) { opline->result_type = expr->op_type = IS_TMP_VAR; opline->result.var = expr->u.op.var = get_temporary_variable(CG(active_op_array)); diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index 74edad6361..16dfd16932 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -429,6 +429,28 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx op_array->opcodes[op2].opcode = ZEND_PRE_DEC; SET_UNUSED(op_array->opcodes[op2].op2); + } else if (op_array->opcodes[op2].opcode == ZEND_VERIFY_RETURN_TYPE + && ssa->ops[op2].op1_def == i + && ssa->ops[op2].op1_use >= 0 + && ssa->ops[op2].op1_use_chain == -1 + && ssa->vars[i].use_chain >= 0 + && (ssa->var_info[ssa->ops[op2].op1_use].type & (MAY_BE_ANY|MAY_BE_UNDEF)) == (ssa->var_info[ssa->ops[op2].op1_def].type & MAY_BE_ANY)) { + /* remove useless type check */ + int var1 = ssa->ops[op2].op1_use; + int ret = ssa->vars[i].use_chain; + + ssa->vars[var1].use_chain = ret; + ssa->ops[ret].op1_use = var1; + + ssa->vars[i].definition = -1; + ssa->vars[i].use_chain = -1; + + ssa->ops[op2].op1_def = -1; + ssa->ops[op2].op1_use = -1; + + MAKE_NOP(&op_array->opcodes[op2]); + remove_nops = 1; + } else if (ssa->ops[op2].op1_def == i && !RETURN_VALUE_USED(&op_array->opcodes[op2]) && ssa->ops[op2].op1_use >= 0 diff --git a/ext/opcache/Optimizer/zend_dfg.c b/ext/opcache/Optimizer/zend_dfg.c index 91c447799e..b45aac41fb 100644 --- a/ext/opcache/Optimizer/zend_dfg.c +++ b/ext/opcache/Optimizer/zend_dfg.c @@ -131,6 +131,7 @@ op1_def: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_OBJ_UNSET: + case ZEND_VERIFY_RETURN_TYPE: DFG_SET(gen, set_size, j, var_num); default: op1_use: @@ -140,6 +141,9 @@ op1_use: } } else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { var_num = EX_VAR_TO_NUM(opline->op1.var); + if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) { + DFG_SET(gen, set_size, j, var_num); + } if (!DFG_ISSET(def, set_size, j, var_num)) { DFG_SET(use, set_size, j, var_num); } diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index d7f2f26dad..51817ab5a9 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -2203,6 +2203,39 @@ static inline zend_class_entry *get_class_entry(const zend_script *script, zend_ return NULL; } +static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce) +{ + uint32_t tmp = 0; + + *pce = NULL; + if (arg_info->class_name) { + // class type hinting... + zend_string *lcname = zend_string_tolower(arg_info->class_name); + tmp |= MAY_BE_OBJECT; + *pce = get_class_entry(script, lcname); + zend_string_release(lcname); + } else if (arg_info->type_hint != IS_UNDEF) { + if (arg_info->type_hint == IS_VOID) { + tmp |= MAY_BE_NULL; + } else if (arg_info->type_hint == IS_CALLABLE) { + tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else if (arg_info->type_hint == IS_ARRAY) { + tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else if (arg_info->type_hint == _IS_BOOL) { + tmp |= MAY_BE_TRUE|MAY_BE_FALSE; + } else { + ZEND_ASSERT(arg_info->type_hint < IS_REFERENCE); + tmp |= 1 << arg_info->type_hint; + } + } else { + tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } + if (arg_info->allow_null) { + tmp |= MAY_BE_NULL; + } + return tmp; +} + static void zend_update_type_info(const zend_op_array *op_array, zend_ssa *ssa, const zend_script *script, @@ -3153,30 +3186,8 @@ static void zend_update_type_info(const zend_op_array *op_array, ce = NULL; if (arg_info) { - tmp = 0; - if (arg_info->class_name) { - // class type hinting... - zend_string *lcname = zend_string_tolower(arg_info->class_name); - tmp |= MAY_BE_OBJECT; - ce = get_class_entry(script, lcname); - zend_string_release(lcname); - } else if (arg_info->type_hint != IS_UNDEF) { - if (arg_info->type_hint == IS_CALLABLE) { - tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; - } else if (arg_info->type_hint == IS_ARRAY) { - tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; - } else if (arg_info->type_hint == _IS_BOOL) { - tmp |= MAY_BE_TRUE|MAY_BE_FALSE; - } else { - ZEND_ASSERT(arg_info->type_hint < IS_REFERENCE); - tmp |= 1 << arg_info->type_hint; - } - } else { - tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; - } - if (arg_info->allow_null) { - tmp |= MAY_BE_NULL; - } else if (opline->opcode == ZEND_RECV_INIT && + tmp = zend_fetch_arg_info(script, arg_info, &ce); + if (opline->opcode == ZEND_RECV_INIT && Z_CONSTANT_P(CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants))) { /* The constant may resolve to NULL */ tmp |= MAY_BE_NULL; @@ -3649,6 +3660,29 @@ static void zend_update_type_info(const zend_op_array *op_array, case ZEND_DEFINED: UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_FALSE|MAY_BE_TRUE, ssa_ops[i].result_def); break; + case ZEND_VERIFY_RETURN_TYPE: + { + zend_arg_info *ret_info = op_array->arg_info - 1; + + tmp = zend_fetch_arg_info(script, ret_info, &ce); + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + if (opline->op1_type == IS_CONST) { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + if (ce) { + UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + } else { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + if (ce) { + UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].op1_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); + } + } + break; + } default: unknown_opcode: if (ssa_ops[i].op1_def >= 0) { @@ -3869,7 +3903,23 @@ static int is_recursive_tail_call(const zend_op_array *op_array, return 0; } +void zend_init_func_return_info(const zend_op_array *op_array, + const zend_script *script, + zend_ssa_var_info *ret) +{ + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_arg_info *ret_info = op_array->arg_info - 1; + zend_ssa_range tmp_range = {0, 0, 0, 0}; + + ret->type = zend_fetch_arg_info(script, ret_info, &ret->ce); + ret->is_instanceof = (ret->ce) ? 1 : 0; + ret->range = tmp_range; + ret->has_range = 0; + } +} + void zend_func_return_info(const zend_op_array *op_array, + const zend_script *script, int recursive, int widening, zend_ssa_var_info *ret) @@ -4047,16 +4097,19 @@ void zend_func_return_info(const zend_op_array *op_array, } } } - if (tmp_is_instanceof < 0) { - tmp_is_instanceof = 0; - tmp_ce = NULL; - } - if (tmp_has_range < 0) { - tmp_has_range = 0; + + if (!(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + if (tmp_is_instanceof < 0) { + tmp_is_instanceof = 0; + tmp_ce = NULL; + } + if (tmp_has_range < 0) { + tmp_has_range = 0; + } + ret->type = tmp; + ret->ce = tmp_ce; + ret->is_instanceof = tmp_is_instanceof; } - ret->type = tmp; - ret->ce = tmp_ce; - ret->is_instanceof = tmp_is_instanceof; ret->range = tmp_range; ret->has_range = tmp_has_range; } @@ -4087,7 +4140,7 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc zend_type_narrowing(op_array, script, ssa); if (ZEND_FUNC_INFO(op_array)) { - zend_func_return_info(op_array, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info); + zend_func_return_info(op_array, script, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info); } free_alloca(worklist, use_heap); diff --git a/ext/opcache/Optimizer/zend_inference.h b/ext/opcache/Optimizer/zend_inference.h index b82d7b1c39..6bf68090c5 100644 --- a/ext/opcache/Optimizer/zend_inference.h +++ b/ext/opcache/Optimizer/zend_inference.h @@ -241,7 +241,11 @@ void zend_inference_check_recursive_dependencies(zend_op_array *op_array); int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist); +void zend_init_func_return_info(const zend_op_array *op_array, + const zend_script *script, + zend_ssa_var_info *ret); void zend_func_return_info(const zend_op_array *op_array, + const zend_script *script, int recursive, int widening, zend_ssa_var_info *ret); diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 42c608416f..a18f79a843 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -812,6 +812,15 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend } for (i = 0; i < call_graph.op_arrays_count; i++) { + if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); + if (func_info) { + zend_init_func_return_info(call_graph.op_arrays[i], script, &func_info->return_info); + } + } + } + + for (i = 0; i < call_graph.op_arrays_count; i++) { func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (func_info) { zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, &func_info->flags); diff --git a/ext/opcache/Optimizer/zend_ssa.c b/ext/opcache/Optimizer/zend_ssa.c index 1144d89fb3..1df5ec032e 100644 --- a/ext/opcache/Optimizer/zend_ssa.c +++ b/ext/opcache/Optimizer/zend_ssa.c @@ -664,6 +664,14 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; } + case ZEND_VERIFY_RETURN_TYPE: + if (opline->op1_type != IS_CONST) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; default: break; } |