com php-src: Extend SCCP to support properties of non-escaping objects: ext/opcache/Optimizer/dce.c ext/opcache/Optimizer/escape_anal ysis.c
ext/opcache/Optimizer/sccp.c ext/opcache/tests/o pt/sccp_010.phpt ext/opcache/tests/opt/sccp_0 11.phpt
| From: | Dmitry Stogov | Date: | Thu, 31 Aug 2017 10:17:46 +0000 |
| Subject: | com php-src: Extend SCCP to support properties of non-escaping objects: ext/opcache/Optimizer/dce.c ext/opcache/Optimizer/escape_anal ysis.c ext/opcache/Optimizer/sccp.c ext/opcache/tests/o pt/sccp_010.phpt ext/opcache/tests/opt/sccp_0 11.phpt |
||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-99207@lists.php.net to get a copy of this message | ||
Commit: 1eb6fd8bc47b8aab2239f1b1f317f3362639070f
Author: Dmitry Stogov <dmitry@zend.com> Thu, 31 Aug 2017 13:17:46 +0300
Parents: 2e462e12db7a35fd75ec0d9f1b7b7a0531f1af10
Branches: master
Link: http://git.php.net/?p=php-src.git;a=commitdiff;h=1eb6fd8bc47b8aab2239f1b1f317f3362639070f
Log:
Extend SCCP to support properties of non-escaping objects
Changed paths:
M ext/opcache/Optimizer/dce.c
M ext/opcache/Optimizer/escape_analysis.c
M ext/opcache/Optimizer/sccp.c
A ext/opcache/tests/opt/sccp_010.phpt
A ext/opcache/tests/opt/sccp_011.phpt
diff --git a/ext/opcache/Optimizer/dce.c b/ext/opcache/Optimizer/dce.c index 998993d..c43b4a8 100644 --- a/ext/opcache/Optimizer/dce.c +++ b/ext/opcache/Optimizer/dce.c @@ -226,6 +226,15 @@ static inline zend_bool may_have_side_effects( } } return 0; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def) + || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) { + return 1; + } + return 0; default: /* For everything we didn't handle, assume a side-effect */ return 1; diff --git a/ext/opcache/Optimizer/escape_analysis.c b/ext/opcache/Optimizer/escape_analysis.c index 29da73c..d0fe52b 100644 --- a/ext/opcache/Optimizer/escape_analysis.c +++ b/ext/opcache/Optimizer/escape_analysis.c @@ -257,6 +257,11 @@ static int is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int va case ZEND_ASSIGN_DIM: case ZEND_ASSIGN_OBJ: break; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + break; default: return 1; } diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index ca0fbde..4d9eb57 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -83,13 +83,41 @@ typedef struct _sccp_ctx { #define TOP ((zend_uchar)-1) #define BOT ((zend_uchar)-2) #define PARTIAL_ARRAY ((zend_uchar)-3) +#define PARTIAL_OBJECT ((zend_uchar)-4) #define IS_TOP(zv) (Z_TYPE_P(zv) == TOP) #define IS_BOT(zv) (Z_TYPE_P(zv) == BOT) #define IS_PARTIAL_ARRAY(zv) (Z_TYPE_P(zv) == PARTIAL_ARRAY) +#define IS_PARTIAL_OBJECT(zv) (Z_TYPE_P(zv) == PARTIAL_OBJECT) #define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP) #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT) +static void empty_partial_array(zval *zv) +{ + Z_TYPE_INFO_P(zv) = PARTIAL_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(zv) = (zend_array *) emalloc(sizeof(zend_array)); + zend_hash_init(Z_ARR_P(zv), 8, NULL, ZVAL_PTR_DTOR, 0); +} + +static void dup_partial_array(zval *dst, zval *src) +{ + Z_TYPE_INFO_P(dst) = PARTIAL_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src)); +} + +static void empty_partial_object(zval *zv) +{ + Z_TYPE_INFO_P(zv) = PARTIAL_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(zv) = (zend_array *) emalloc(sizeof(zend_array)); + zend_hash_init(Z_ARR_P(zv), 8, NULL, ZVAL_PTR_DTOR, 0); +} + +static void dup_partial_object(zval *dst, zval *src) +{ + Z_TYPE_INFO_P(dst) = PARTIAL_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src)); +} + static inline zend_bool value_known(zval *zv) { return !IS_TOP(zv) && !IS_BOT(zv); } @@ -116,7 +144,7 @@ static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) { } #if ZEND_DEBUG - ZEND_ASSERT(IS_PARTIAL_ARRAY(new) || zend_is_identical(value, new)); + ZEND_ASSERT(IS_PARTIAL_ARRAY(new) || IS_PARTIAL_OBJECT(new) || zend_is_identical(value, new)); #endif } @@ -462,9 +490,95 @@ static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) { } } +static inline int fetch_obj_prop(zval **result, zval *op1, zval *op2) { + switch (Z_TYPE_P(op2)) { + case IS_STRING: + *result = zend_symtable_find(Z_ARR_P(op1), Z_STR_P(op2)); + return SUCCESS; + default: + return FAILURE; + } +} + +static inline int ct_eval_fetch_obj(zval *result, zval *op1, zval *op2) { + if (IS_PARTIAL_OBJECT(op1)) { + zval *value; + if (fetch_obj_prop(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) { + ZVAL_COPY(result, value); + return SUCCESS; + } + } + return FAILURE; +} + +static inline int ct_eval_isset_obj(zval *result, uint32_t extended_value, zval *op1, zval *op2) { + if (IS_PARTIAL_OBJECT(op1)) { + zval *value; + if (fetch_obj_prop(&value, op1, op2) == FAILURE) { + return FAILURE; + } + if (!value || IS_BOT(value)) { + return FAILURE; + } + if (extended_value & ZEND_ISSET) { + ZVAL_BOOL(result, value && Z_TYPE_P(value) != IS_NULL); + } else { + ZEND_ASSERT(extended_value & ZEND_ISEMPTY); + ZVAL_BOOL(result, !value || !zend_is_true(value)); + } + return SUCCESS; + } else { + ZVAL_BOOL(result, extended_value != ZEND_ISSET); + return SUCCESS; + } +} + +static inline int ct_eval_del_obj_prop(zval *result, zval *key) { + ZEND_ASSERT(IS_PARTIAL_OBJECT(result)); + + switch (Z_TYPE_P(key)) { + case IS_STRING: + zend_symtable_del(Z_ARR_P(result), Z_STR_P(key)); + break; + default: + return FAILURE; + } + + return SUCCESS; +} + +static inline int ct_eval_add_obj_prop(zval *result, zval *value, zval *key) { + switch (Z_TYPE_P(key)) { + case IS_STRING: + value = zend_symtable_update(Z_ARR_P(result), Z_STR_P(key), value); + break; + default: + return FAILURE; + } + + Z_TRY_ADDREF_P(value); + return SUCCESS; +} + +static inline int ct_eval_assign_obj(zval *result, zval *value, zval *key) { + switch (Z_TYPE_P(result)) { + case IS_NULL: + case IS_FALSE: + empty_partial_object(result); + /* break missing intentionally */ + case PARTIAL_OBJECT: + return ct_eval_add_obj_prop(result, value, key); + default: + return FAILURE; + } +} + static inline int ct_eval_incdec(zval *result, zend_uchar opcode, zval *op1) { ZVAL_COPY(result, op1); - if (opcode == ZEND_PRE_INC || opcode == ZEND_POST_INC) { + if (opcode == ZEND_PRE_INC + || opcode == ZEND_POST_INC + || opcode == ZEND_PRE_INC_OBJ + || opcode == ZEND_POST_INC_OBJ) { increment_function(result); } else { decrement_function(result); @@ -806,13 +920,6 @@ static inline int ct_eval_func_call( #define SKIP_IF_TOP(op) if (IS_TOP(op)) return; -static void empty_partial_array(zval *zv) -{ - Z_TYPE_INFO_P(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR_P(zv) = (zend_array *) emalloc(sizeof(zend_array)); - zend_hash_init(Z_ARR_P(zv), 8, NULL, ZVAL_PTR_DTOR, 0); -} - static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op) { sccp_ctx *ctx = (sccp_ctx *) scdf; zval *op1, *op2, zv; /* zv is a temporary to hold result values */ @@ -899,8 +1006,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { empty_partial_array(&zv); } else { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); } if (!op2) { @@ -920,8 +1026,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } else { if (IS_PARTIAL_ARRAY(op1)) { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); } else { ZVAL_DUP(&zv, op1); } @@ -943,6 +1048,81 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o return; } + case ZEND_ASSIGN_OBJ: + if (ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + + /* If $a in $a->foo=$c is UNDEF, treat it like NULL. There is no warning. */ + if ((ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_ANY) == 0) { + op1 = &EG(uninitialized_zval); + } + + if (IS_BOT(op1)) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + return; + } + + SKIP_IF_TOP(op1); + SKIP_IF_TOP(data); + SKIP_IF_TOP(op2); + + if (IS_BOT(op2)) { + /* Update of unknown property */ + SET_RESULT_BOT(result); + empty_partial_object(&zv); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + return; + } + + if (IS_BOT(data)) { + SET_RESULT_BOT(result); + if (IS_PARTIAL_OBJECT(op1) + || Z_TYPE_P(op1) == IS_NULL + || Z_TYPE_P(op1) == IS_FALSE) { + + if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { + empty_partial_object(&zv); + } else { + dup_partial_object(&zv, op1); + } + + if (ct_eval_del_obj_prop(&zv, op2) == SUCCESS) { + SET_RESULT(op1, &zv); + } else { + SET_RESULT_BOT(op1); + } + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(op1); + } + + } else { + + if (IS_PARTIAL_OBJECT(op1)) { + dup_partial_object(&zv, op1); + } else { + ZVAL_COPY(&zv, op1); + } + + if (ct_eval_assign_obj(&zv, data, op2) == SUCCESS) { + SET_RESULT(result, data); + SET_RESULT(op1, &zv); + } else { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + } + + zval_ptr_dtor_nogc(&zv); + } + } else { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + } + return; + case ZEND_SEND_VAL: case ZEND_SEND_VAR: { @@ -1059,6 +1239,16 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } return; } + case ZEND_NEW: + if (ssa_op->result_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + empty_partial_object(&zv); + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(result); + } + return; } if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) { @@ -1140,8 +1330,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o if (ct_eval_fetch_dim(&tmp, op1, op2, 0) == SUCCESS) { if (IS_BOT(data)) { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); ct_eval_del_array_elem(&zv, op2); } else { if (zend_optimizer_eval_binary_op(&tmp, zend_compound_assign_to_binary_op(opline->opcode), &tmp, data) != SUCCESS) { @@ -1151,8 +1340,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } if (IS_PARTIAL_ARRAY(op1)) { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); } else { ZVAL_DUP(&zv, op1); } @@ -1167,10 +1355,75 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o zval_ptr_dtor_nogc(&zv); } } + } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { + if (op1 && IS_PARTIAL_OBJECT(op1) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + zval tmp; + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + + SKIP_IF_TOP(data); + + if (ct_eval_fetch_obj(&tmp, op1, op2) == SUCCESS) { + + if (IS_BOT(data)) { + dup_partial_object(&zv, op1); + ct_eval_del_obj_prop(&zv, op2); + } else { + if (zend_optimizer_eval_binary_op(&tmp, zend_compound_assign_to_binary_op(opline->opcode), &tmp, data) != SUCCESS) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + break; + } + + dup_partial_object(&zv, op1); + } + + if (ct_eval_assign_obj(&zv, &tmp, op2) == SUCCESS) { + SET_RESULT(result, &tmp); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + zval_ptr_dtor_nogc(&zv); + } + } } SET_RESULT_BOT(result); SET_RESULT_BOT(op1); break; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + if (op1) { + SKIP_IF_TOP(op1); + SKIP_IF_TOP(op2); + if (IS_PARTIAL_OBJECT(op1) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + zval tmp1, tmp2; + + if (ct_eval_fetch_obj(&tmp1, op1, op2) == SUCCESS + && ct_eval_incdec(&tmp2, opline->opcode, &tmp1) == SUCCESS) { + + dup_partial_object(&zv, op1); + ct_eval_assign_obj(&zv, &tmp2, op2); + if (opline->opcode == ZEND_PRE_INC_OBJ + || ZEND_PRE_DEC_OBJ) { + SET_RESULT(result, &tmp2); + } else { + SET_RESULT(result, &tmp1); + } + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } + } + SET_RESULT_BOT(op1); + SET_RESULT_BOT(result); + break; case ZEND_PRE_INC: case ZEND_PRE_DEC: SKIP_IF_TOP(op1); @@ -1273,6 +1526,33 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } SET_RESULT_BOT(result); break; + case ZEND_FETCH_OBJ_R: + case ZEND_FETCH_OBJ_IS: + if (op1) { + SKIP_IF_TOP(op1); + SKIP_IF_TOP(op2); + + if (ct_eval_fetch_obj(&zv, op1, op2) == SUCCESS) { + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } + SET_RESULT_BOT(result); + break; + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + if (op1) { + SKIP_IF_TOP(op1); + SKIP_IF_TOP(op2); + + if (ct_eval_isset_obj(&zv, opline->extended_value, op1, op2) == SUCCESS) { + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } + SET_RESULT_BOT(result); + break; case ZEND_QM_ASSIGN: case ZEND_JMP_SET: case ZEND_COALESCE: @@ -1476,21 +1756,11 @@ static void sccp_mark_feasible_successors( scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } -static int join_partial_arrays(zval *a, zval *b) +static void join_hash_tables(HashTable *ret, HashTable *ht1, HashTable *ht2) { - HashTable *ht1, *ht2; zend_ulong index; zend_string *key; - zval *val1, *val2, ret; - - if ((Z_TYPE_P(a) != IS_ARRAY && !IS_PARTIAL_ARRAY(a)) - || (Z_TYPE_P(b) != IS_ARRAY && !IS_PARTIAL_ARRAY(b))) { - return FAILURE; - } - - ht1 = Z_ARRVAL_P(a); - ht2 = Z_ARRVAL_P(b); - empty_partial_array(&ret); + zval *val1, *val2; ZEND_HASH_FOREACH_KEY_VAL(ht1, index, key, val1) { if (key) { @@ -1500,14 +1770,42 @@ static int join_partial_arrays(zval *a, zval *b) } if (val2 && zend_is_identical(val1, val2)) { if (key) { - val1 = zend_hash_add_new(Z_ARRVAL(ret), key, val1); + val1 = zend_hash_add_new(ret, key, val1); } else { - val1 = zend_hash_index_add_new(Z_ARRVAL(ret), index, val1); + val1 = zend_hash_index_add_new(ret, index, val1); } Z_TRY_ADDREF_P(val1); } } ZEND_HASH_FOREACH_END(); +} + +static int join_partial_arrays(zval *a, zval *b) +{ + zval ret; + + if ((Z_TYPE_P(a) != IS_ARRAY && !IS_PARTIAL_ARRAY(a)) + || (Z_TYPE_P(b) != IS_ARRAY && !IS_PARTIAL_ARRAY(b))) { + return FAILURE; + } + empty_partial_array(&ret); + join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b)); + zval_ptr_dtor_nogc(a); + ZVAL_COPY_VALUE(a, &ret); + + return SUCCESS; +} + +static int join_partial_objects(zval *a, zval *b) +{ + zval ret; + + if (!IS_PARTIAL_OBJECT(a) || !IS_PARTIAL_OBJECT(b)) { + return FAILURE; + } + + empty_partial_object(&ret); + join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b)); zval_ptr_dtor_nogc(a); ZVAL_COPY_VALUE(a, &ret); @@ -1533,6 +1831,11 @@ static void join_phi_values(zval *a, zval *b, zend_bool escape) { zval_ptr_dtor_nogc(a); MAKE_BOT(a); } + } else if (IS_PARTIAL_OBJECT(a) || IS_PARTIAL_OBJECT(b)) { + if (escape || join_partial_objects(a, b) != SUCCESS) { + zval_ptr_dtor_nogc(a); + MAKE_BOT(a); + } } else if (!zend_is_identical(a, b)) { if (escape || join_partial_arrays(a, b) != SUCCESS) { zval_ptr_dtor_nogc(a); @@ -1630,7 +1933,8 @@ static int replace_constant_operands(sccp_ctx *ctx) { zval *value; int use; - if (IS_PARTIAL_ARRAY(&ctx->values[i])) { + if (IS_PARTIAL_ARRAY(&ctx->values[i]) + || IS_PARTIAL_OBJECT(&ctx->values[i])) { if (!Z_DELREF(ctx->values[i])) { zend_array_destroy(Z_ARR(ctx->values[i])); } diff --git a/ext/opcache/tests/opt/sccp_010.phpt b/ext/opcache/tests/opt/sccp_010.phpt new file mode 100644 index 0000000..33cb883 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_010.phpt @@ -0,0 +1,39 @@ +--TEST-- +SCCP 010: Conditional Constant Propagation of non-escaping object properties +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function foo() { + $o = new stdClass(); + $o->foo = 0; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $o->foo = $i; + echo $o->foo; + } + echo $o->foo; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_010.php:1-15 +L0: RETURN int(1) + +foo: ; (lines=6, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %ssccp_010.php:2-13 +L0: V0 = NEW 0 string("stdClass") +L1: DO_FCALL +L2: FREE V0 +L3: ECHO int(1) +L4: ECHO int(1) +L5: RETURN null diff --git a/ext/opcache/tests/opt/sccp_011.phpt b/ext/opcache/tests/opt/sccp_011.phpt new file mode 100644 index 0000000..249bb8b --- /dev/null +++ b/ext/opcache/tests/opt/sccp_011.phpt @@ -0,0 +1,40 @@ +--TEST-- +SCCP 011: Conditional Constant Propagation of non-escaping object properties +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function foo(int $x) { + $o = new stdClass; + if ($x) { + $o->foo = 0; + $o->bar = 1; + } else { + $o->foo = 0; + $o->bar = 2; + } + echo $o->foo; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_011.php:1-14 +L0: RETURN int(1) + +foo: ; (lines=8, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_011.php:2-12 +L0: CV0($x) = RECV 1 +L1: V1 = NEW 0 string("stdClass") +L2: DO_FCALL +L3: FREE V1 +L4: JMPZ CV0($x) L6 +L5: JMP L6 +L6: ECHO int(0) +L7: RETURN null
diff --git a/ext/opcache/Optimizer/dce.c b/ext/opcache/Optimizer/dce.c index 998993d..c43b4a8 100644 --- a/ext/opcache/Optimizer/dce.c +++ b/ext/opcache/Optimizer/dce.c @@ -226,6 +226,15 @@ static inline zend_bool may_have_side_effects( } } return 0; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def) + || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) { + return 1; + } + return 0; default: /* For everything we didn't handle, assume a side-effect */ return 1; diff --git a/ext/opcache/Optimizer/escape_analysis.c b/ext/opcache/Optimizer/escape_analysis.c index 29da73c..d0fe52b 100644 --- a/ext/opcache/Optimizer/escape_analysis.c +++ b/ext/opcache/Optimizer/escape_analysis.c @@ -257,6 +257,11 @@ static int is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int va case ZEND_ASSIGN_DIM: case ZEND_ASSIGN_OBJ: break; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + break; default: return 1; } diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index ca0fbde..4d9eb57 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -83,13 +83,41 @@ typedef struct _sccp_ctx { #define TOP ((zend_uchar)-1) #define BOT ((zend_uchar)-2) #define PARTIAL_ARRAY ((zend_uchar)-3) +#define PARTIAL_OBJECT ((zend_uchar)-4) #define IS_TOP(zv) (Z_TYPE_P(zv) == TOP) #define IS_BOT(zv) (Z_TYPE_P(zv) == BOT) #define IS_PARTIAL_ARRAY(zv) (Z_TYPE_P(zv) == PARTIAL_ARRAY) +#define IS_PARTIAL_OBJECT(zv) (Z_TYPE_P(zv) == PARTIAL_OBJECT) #define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP) #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT) +static void empty_partial_array(zval *zv) +{ + Z_TYPE_INFO_P(zv) = PARTIAL_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(zv) = (zend_array *) emalloc(sizeof(zend_array)); + zend_hash_init(Z_ARR_P(zv), 8, NULL, ZVAL_PTR_DTOR, 0); +} + +static void dup_partial_array(zval *dst, zval *src) +{ + Z_TYPE_INFO_P(dst) = PARTIAL_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src)); +} + +static void empty_partial_object(zval *zv) +{ + Z_TYPE_INFO_P(zv) = PARTIAL_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(zv) = (zend_array *) emalloc(sizeof(zend_array)); + zend_hash_init(Z_ARR_P(zv), 8, NULL, ZVAL_PTR_DTOR, 0); +} + +static void dup_partial_object(zval *dst, zval *src) +{ + Z_TYPE_INFO_P(dst) = PARTIAL_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src)); +} + static inline zend_bool value_known(zval *zv) { return !IS_TOP(zv) && !IS_BOT(zv); } @@ -116,7 +144,7 @@ static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) { } #if ZEND_DEBUG - ZEND_ASSERT(IS_PARTIAL_ARRAY(new) || zend_is_identical(value, new)); + ZEND_ASSERT(IS_PARTIAL_ARRAY(new) || IS_PARTIAL_OBJECT(new) || zend_is_identical(value, new)); #endif } @@ -462,9 +490,95 @@ static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) { } } +static inline int fetch_obj_prop(zval **result, zval *op1, zval *op2) { + switch (Z_TYPE_P(op2)) { + case IS_STRING: + *result = zend_symtable_find(Z_ARR_P(op1), Z_STR_P(op2)); + return SUCCESS; + default: + return FAILURE; + } +} + +static inline int ct_eval_fetch_obj(zval *result, zval *op1, zval *op2) { + if (IS_PARTIAL_OBJECT(op1)) { + zval *value; + if (fetch_obj_prop(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) { + ZVAL_COPY(result, value); + return SUCCESS; + } + } + return FAILURE; +} + +static inline int ct_eval_isset_obj(zval *result, uint32_t extended_value, zval *op1, zval *op2) { + if (IS_PARTIAL_OBJECT(op1)) { + zval *value; + if (fetch_obj_prop(&value, op1, op2) == FAILURE) { + return FAILURE; + } + if (!value || IS_BOT(value)) { + return FAILURE; + } + if (extended_value & ZEND_ISSET) { + ZVAL_BOOL(result, value && Z_TYPE_P(value) != IS_NULL); + } else { + ZEND_ASSERT(extended_value & ZEND_ISEMPTY); + ZVAL_BOOL(result, !value || !zend_is_true(value)); + } + return SUCCESS; + } else { + ZVAL_BOOL(result, extended_value != ZEND_ISSET); + return SUCCESS; + } +} + +static inline int ct_eval_del_obj_prop(zval *result, zval *key) { + ZEND_ASSERT(IS_PARTIAL_OBJECT(result)); + + switch (Z_TYPE_P(key)) { + case IS_STRING: + zend_symtable_del(Z_ARR_P(result), Z_STR_P(key)); + break; + default: + return FAILURE; + } + + return SUCCESS; +} + +static inline int ct_eval_add_obj_prop(zval *result, zval *value, zval *key) { + switch (Z_TYPE_P(key)) { + case IS_STRING: + value = zend_symtable_update(Z_ARR_P(result), Z_STR_P(key), value); + break; + default: + return FAILURE; + } + + Z_TRY_ADDREF_P(value); + return SUCCESS; +} + +static inline int ct_eval_assign_obj(zval *result, zval *value, zval *key) { + switch (Z_TYPE_P(result)) { + case IS_NULL: + case IS_FALSE: + empty_partial_object(result); + /* break missing intentionally */ + case PARTIAL_OBJECT: + return ct_eval_add_obj_prop(result, value, key); + default: + return FAILURE; + } +} + static inline int ct_eval_incdec(zval *result, zend_uchar opcode, zval *op1) { ZVAL_COPY(result, op1); - if (opcode == ZEND_PRE_INC || opcode == ZEND_POST_INC) { + if (opcode == ZEND_PRE_INC + || opcode == ZEND_POST_INC + || opcode == ZEND_PRE_INC_OBJ + || opcode == ZEND_POST_INC_OBJ) { increment_function(result); } else { decrement_function(result); @@ -806,13 +920,6 @@ static inline int ct_eval_func_call( #define SKIP_IF_TOP(op) if (IS_TOP(op)) return; -static void empty_partial_array(zval *zv) -{ - Z_TYPE_INFO_P(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR_P(zv) = (zend_array *) emalloc(sizeof(zend_array)); - zend_hash_init(Z_ARR_P(zv), 8, NULL, ZVAL_PTR_DTOR, 0); -} - static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op) { sccp_ctx *ctx = (sccp_ctx *) scdf; zval *op1, *op2, zv; /* zv is a temporary to hold result values */ @@ -899,8 +1006,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { empty_partial_array(&zv); } else { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); } if (!op2) { @@ -920,8 +1026,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } else { if (IS_PARTIAL_ARRAY(op1)) { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); } else { ZVAL_DUP(&zv, op1); } @@ -943,6 +1048,81 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o return; } + case ZEND_ASSIGN_OBJ: + if (ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + + /* If $a in $a->foo=$c is UNDEF, treat it like NULL. There is no warning. */ + if ((ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_ANY) == 0) { + op1 = &EG(uninitialized_zval); + } + + if (IS_BOT(op1)) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + return; + } + + SKIP_IF_TOP(op1); + SKIP_IF_TOP(data); + SKIP_IF_TOP(op2); + + if (IS_BOT(op2)) { + /* Update of unknown property */ + SET_RESULT_BOT(result); + empty_partial_object(&zv); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + return; + } + + if (IS_BOT(data)) { + SET_RESULT_BOT(result); + if (IS_PARTIAL_OBJECT(op1) + || Z_TYPE_P(op1) == IS_NULL + || Z_TYPE_P(op1) == IS_FALSE) { + + if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { + empty_partial_object(&zv); + } else { + dup_partial_object(&zv, op1); + } + + if (ct_eval_del_obj_prop(&zv, op2) == SUCCESS) { + SET_RESULT(op1, &zv); + } else { + SET_RESULT_BOT(op1); + } + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(op1); + } + + } else { + + if (IS_PARTIAL_OBJECT(op1)) { + dup_partial_object(&zv, op1); + } else { + ZVAL_COPY(&zv, op1); + } + + if (ct_eval_assign_obj(&zv, data, op2) == SUCCESS) { + SET_RESULT(result, data); + SET_RESULT(op1, &zv); + } else { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + } + + zval_ptr_dtor_nogc(&zv); + } + } else { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + } + return; + case ZEND_SEND_VAL: case ZEND_SEND_VAR: { @@ -1059,6 +1239,16 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } return; } + case ZEND_NEW: + if (ssa_op->result_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + empty_partial_object(&zv); + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(result); + } + return; } if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) { @@ -1140,8 +1330,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o if (ct_eval_fetch_dim(&tmp, op1, op2, 0) == SUCCESS) { if (IS_BOT(data)) { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); ct_eval_del_array_elem(&zv, op2); } else { if (zend_optimizer_eval_binary_op(&tmp, zend_compound_assign_to_binary_op(opline->opcode), &tmp, data) != SUCCESS) { @@ -1151,8 +1340,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } if (IS_PARTIAL_ARRAY(op1)) { - Z_TYPE_INFO(zv) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); - Z_ARR(zv) = zend_array_dup(Z_ARR_P(op1)); + dup_partial_array(&zv, op1); } else { ZVAL_DUP(&zv, op1); } @@ -1167,10 +1355,75 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o zval_ptr_dtor_nogc(&zv); } } + } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { + if (op1 && IS_PARTIAL_OBJECT(op1) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + zval tmp; + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + + SKIP_IF_TOP(data); + + if (ct_eval_fetch_obj(&tmp, op1, op2) == SUCCESS) { + + if (IS_BOT(data)) { + dup_partial_object(&zv, op1); + ct_eval_del_obj_prop(&zv, op2); + } else { + if (zend_optimizer_eval_binary_op(&tmp, zend_compound_assign_to_binary_op(opline->opcode), &tmp, data) != SUCCESS) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + break; + } + + dup_partial_object(&zv, op1); + } + + if (ct_eval_assign_obj(&zv, &tmp, op2) == SUCCESS) { + SET_RESULT(result, &tmp); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + zval_ptr_dtor_nogc(&zv); + } + } } SET_RESULT_BOT(result); SET_RESULT_BOT(op1); break; + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + if (op1) { + SKIP_IF_TOP(op1); + SKIP_IF_TOP(op2); + if (IS_PARTIAL_OBJECT(op1) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + zval tmp1, tmp2; + + if (ct_eval_fetch_obj(&tmp1, op1, op2) == SUCCESS + && ct_eval_incdec(&tmp2, opline->opcode, &tmp1) == SUCCESS) { + + dup_partial_object(&zv, op1); + ct_eval_assign_obj(&zv, &tmp2, op2); + if (opline->opcode == ZEND_PRE_INC_OBJ + || ZEND_PRE_DEC_OBJ) { + SET_RESULT(result, &tmp2); + } else { + SET_RESULT(result, &tmp1); + } + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } + } + SET_RESULT_BOT(op1); + SET_RESULT_BOT(result); + break; case ZEND_PRE_INC: case ZEND_PRE_DEC: SKIP_IF_TOP(op1); @@ -1273,6 +1526,33 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } SET_RESULT_BOT(result); break; + case ZEND_FETCH_OBJ_R: + case ZEND_FETCH_OBJ_IS: + if (op1) { + SKIP_IF_TOP(op1); + SKIP_IF_TOP(op2); + + if (ct_eval_fetch_obj(&zv, op1, op2) == SUCCESS) { + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } + SET_RESULT_BOT(result); + break; + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + if (op1) { + SKIP_IF_TOP(op1); + SKIP_IF_TOP(op2); + + if (ct_eval_isset_obj(&zv, opline->extended_value, op1, op2) == SUCCESS) { + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } + SET_RESULT_BOT(result); + break; case ZEND_QM_ASSIGN: case ZEND_JMP_SET: case ZEND_COALESCE: @@ -1476,21 +1756,11 @@ static void sccp_mark_feasible_successors( scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } -static int join_partial_arrays(zval *a, zval *b) +static void join_hash_tables(HashTable *ret, HashTable *ht1, HashTable *ht2) { - HashTable *ht1, *ht2; zend_ulong index; zend_string *key; - zval *val1, *val2, ret; - - if ((Z_TYPE_P(a) != IS_ARRAY && !IS_PARTIAL_ARRAY(a)) - || (Z_TYPE_P(b) != IS_ARRAY && !IS_PARTIAL_ARRAY(b))) { - return FAILURE; - } - - ht1 = Z_ARRVAL_P(a); - ht2 = Z_ARRVAL_P(b); - empty_partial_array(&ret); + zval *val1, *val2; ZEND_HASH_FOREACH_KEY_VAL(ht1, index, key, val1) { if (key) { @@ -1500,14 +1770,42 @@ static int join_partial_arrays(zval *a, zval *b) } if (val2 && zend_is_identical(val1, val2)) { if (key) { - val1 = zend_hash_add_new(Z_ARRVAL(ret), key, val1); + val1 = zend_hash_add_new(ret, key, val1); } else { - val1 = zend_hash_index_add_new(Z_ARRVAL(ret), index, val1); + val1 = zend_hash_index_add_new(ret, index, val1); } Z_TRY_ADDREF_P(val1); } } ZEND_HASH_FOREACH_END(); +} + +static int join_partial_arrays(zval *a, zval *b) +{ + zval ret; + + if ((Z_TYPE_P(a) != IS_ARRAY && !IS_PARTIAL_ARRAY(a)) + || (Z_TYPE_P(b) != IS_ARRAY && !IS_PARTIAL_ARRAY(b))) { + return FAILURE; + } + empty_partial_array(&ret); + join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b)); + zval_ptr_dtor_nogc(a); + ZVAL_COPY_VALUE(a, &ret); + + return SUCCESS; +} + +static int join_partial_objects(zval *a, zval *b) +{ + zval ret; + + if (!IS_PARTIAL_OBJECT(a) || !IS_PARTIAL_OBJECT(b)) { + return FAILURE; + } + + empty_partial_object(&ret); + join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b)); zval_ptr_dtor_nogc(a); ZVAL_COPY_VALUE(a, &ret); @@ -1533,6 +1831,11 @@ static void join_phi_values(zval *a, zval *b, zend_bool escape) { zval_ptr_dtor_nogc(a); MAKE_BOT(a); } + } else if (IS_PARTIAL_OBJECT(a) || IS_PARTIAL_OBJECT(b)) { + if (escape || join_partial_objects(a, b) != SUCCESS) { + zval_ptr_dtor_nogc(a); + MAKE_BOT(a); + } } else if (!zend_is_identical(a, b)) { if (escape || join_partial_arrays(a, b) != SUCCESS) { zval_ptr_dtor_nogc(a); @@ -1630,7 +1933,8 @@ static int replace_constant_operands(sccp_ctx *ctx) { zval *value; int use; - if (IS_PARTIAL_ARRAY(&ctx->values[i])) { + if (IS_PARTIAL_ARRAY(&ctx->values[i]) + || IS_PARTIAL_OBJECT(&ctx->values[i])) { if (!Z_DELREF(ctx->values[i])) { zend_array_destroy(Z_ARR(ctx->values[i])); } diff --git a/ext/opcache/tests/opt/sccp_010.phpt b/ext/opcache/tests/opt/sccp_010.phpt new file mode 100644 index 0000000..33cb883 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_010.phpt @@ -0,0 +1,39 @@ +--TEST-- +SCCP 010: Conditional Constant Propagation of non-escaping object properties +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function foo() { + $o = new stdClass(); + $o->foo = 0; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $o->foo = $i; + echo $o->foo; + } + echo $o->foo; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_010.php:1-15 +L0: RETURN int(1) + +foo: ; (lines=6, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %ssccp_010.php:2-13 +L0: V0 = NEW 0 string("stdClass") +L1: DO_FCALL +L2: FREE V0 +L3: ECHO int(1) +L4: ECHO int(1) +L5: RETURN null diff --git a/ext/opcache/tests/opt/sccp_011.phpt b/ext/opcache/tests/opt/sccp_011.phpt new file mode 100644 index 0000000..249bb8b --- /dev/null +++ b/ext/opcache/tests/opt/sccp_011.phpt @@ -0,0 +1,40 @@ +--TEST-- +SCCP 011: Conditional Constant Propagation of non-escaping object properties +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function foo(int $x) { + $o = new stdClass; + if ($x) { + $o->foo = 0; + $o->bar = 1; + } else { + $o->foo = 0; + $o->bar = 2; + } + echo $o->foo; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_011.php:1-14 +L0: RETURN int(1) + +foo: ; (lines=8, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_011.php:2-12 +L0: CV0($x) = RECV 1 +L1: V1 = NEW 0 string("stdClass") +L2: DO_FCALL +L3: FREE V1 +L4: JMPZ CV0($x) L6 +L5: JMP L6 +L6: ECHO int(0) +L7: RETURN null