summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Stogov <dmitry@zend.com>2016-04-07 17:34:53 +0300
committerDmitry Stogov <dmitry@zend.com>2016-04-07 17:34:53 +0300
commit3444c1ae2400b279b936c4ca04cb84dfdfb57bbf (patch)
treea63676497e4c163bb917a183780180e743a1acd2
parent708d030b694f89a001c5f5ed6e3af25e7097f483 (diff)
downloadphp-git-3444c1ae2400b279b936c4ca04cb84dfdfb57bbf.tar.gz
Use return type hints for type inference and eliminate useless VERIFY_RETRUN_TYPE opcodes.
-rw-r--r--Zend/zend_compile.c24
-rw-r--r--ext/opcache/Optimizer/dfa_pass.c22
-rw-r--r--ext/opcache/Optimizer/zend_dfg.c4
-rw-r--r--ext/opcache/Optimizer/zend_inference.c121
-rw-r--r--ext/opcache/Optimizer/zend_inference.h4
-rw-r--r--ext/opcache/Optimizer/zend_optimizer.c9
-rw-r--r--ext/opcache/Optimizer/zend_ssa.c8
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;
}