com php-src: Extend SCCP pass with support for elements of non-escaping arrays: ext/opcache/Optimizer/sccp.c ext/opcache/tests/opt/sccp _001.phpt
ext/opcache/tests/opt/sccp_002. phpt ext/opcache/tests/opt/sccp_003.phpt ext/opca che/tests/opt/sccp_004.phpt ext/opcache/tests/opt/s ccp_005.phpt
ext/opcache/tests/opt/sccp_006 .phpt ext/opcache/tests/opt/sccp_007.phpt ext/o pcache/tests/opt/sccp_008.phpt
| From: | Dmitry Stogov | Date: | Wed, 30 Aug 2017 12:31:23 +0000 |
| Subject: | com php-src: Extend SCCP pass with support for elements of non-escaping arrays: ext/opcache/Optimizer/sccp.c ext/opcache/tests/opt/sccp _001.phpt ext/opcache/tests/opt/sccp_002. phpt ext/opcache/tests/opt/sccp_003.phpt ext/opca che/tests/opt/sccp_004.phpt ext/opcache/tests/opt/s ccp_005.phpt ext/opcache/tests/opt/sccp_006 .phpt ext/opcache/tests/opt/sccp_007.phpt ext/o pcache/tests/opt/sccp_008.phpt |
||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-99198@lists.php.net to get a copy of this message | ||
Commit: ce3dbb53a7dcef314bba1d355f049ddb2252fad1
Author: Dmitry Stogov <dmitry@zend.com> Wed, 30 Aug 2017 15:31:23 +0300
Parents: 0cdeb60757aea13f7d50b0c18e0c9c9234564c0f
Branches: master
Link: http://git.php.net/?p=php-src.git;a=commitdiff;h=ce3dbb53a7dcef314bba1d355f049ddb2252fad1
Log:
Extend SCCP pass with support for elements of non-escaping arrays
Changed paths:
M ext/opcache/Optimizer/sccp.c
A ext/opcache/tests/opt/sccp_001.phpt
A ext/opcache/tests/opt/sccp_002.phpt
A ext/opcache/tests/opt/sccp_003.phpt
A ext/opcache/tests/opt/sccp_004.phpt
A ext/opcache/tests/opt/sccp_005.phpt
A ext/opcache/tests/opt/sccp_006.phpt
A ext/opcache/tests/opt/sccp_007.phpt
A ext/opcache/tests/opt/sccp_008.phpt
diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index 24af78e..ca0fbde 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -80,10 +80,12 @@ typedef struct _sccp_ctx { zval bot; } sccp_ctx; -#define TOP ((zend_uchar)-1) +#define TOP ((zend_uchar)-1) #define BOT ((zend_uchar)-2) +#define PARTIAL_ARRAY ((zend_uchar)-3) #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 MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP) #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT) @@ -114,7 +116,7 @@ static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) { } #if ZEND_DEBUG - ZEND_ASSERT(zend_is_identical(value, new)); + ZEND_ASSERT(IS_PARTIAL_ARRAY(new) || zend_is_identical(value, new)); #endif } @@ -311,9 +313,9 @@ static inline int fetch_array_elem(zval **result, zval *op1, zval *op2) { } static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int support_strings) { - if (Z_TYPE_P(op1) == IS_ARRAY) { + if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) { zval *value; - if (fetch_array_elem(&value, op1, op2) == SUCCESS && value) { + if (fetch_array_elem(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) { ZVAL_COPY(result, value); return SUCCESS; } @@ -331,11 +333,14 @@ static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int supp } static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval *op1, zval *op2) { - if (Z_TYPE_P(op1) == IS_ARRAY) { + if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) { zval *value; if (fetch_array_elem(&value, op1, op2) == FAILURE) { return FAILURE; } + if (IS_PARTIAL_ARRAY(op1) && (!value || IS_BOT(value))) { + return FAILURE; + } if (extended_value & ZEND_ISSET) { ZVAL_BOOL(result, value && Z_TYPE_P(value) != IS_NULL); } else { @@ -352,6 +357,35 @@ static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval } } +static inline int ct_eval_del_array_elem(zval *result, zval *key) { + ZEND_ASSERT(IS_PARTIAL_ARRAY(result)); + + switch (Z_TYPE_P(key)) { + case IS_NULL: + zend_hash_del(Z_ARR_P(result), ZSTR_EMPTY_ALLOC()); + break; + case IS_FALSE: + zend_hash_index_del(Z_ARR_P(result), 0); + break; + case IS_TRUE: + zend_hash_index_del(Z_ARR_P(result), 1); + break; + case IS_LONG: + zend_hash_index_del(Z_ARR_P(result), Z_LVAL_P(key)); + break; + case IS_DOUBLE: + zend_hash_index_del(Z_ARR_P(result), zend_dval_to_lval(Z_DVAL_P(key))); + break; + 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_array_elem(zval *result, zval *value, zval *key) { if (!key) { if ((value = zend_hash_next_index_insert(Z_ARR_P(result), value))) { @@ -396,6 +430,7 @@ static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) { array_init(result); /* break missing intentionally */ case IS_ARRAY: + case PARTIAL_ARRAY: return ct_eval_add_array_elem(result, value, key); case IS_STRING: // TODO Before enabling this case, make sure ARRAY_DIM result op is correct @@ -769,7 +804,14 @@ static inline int ct_eval_func_call( #define SET_RESULT_BOT(op) SET_RESULT(op, &ctx->bot) #define SET_RESULT_TOP(op) SET_RESULT(op, &ctx->top) -#define SKIP_IF_TOP(op) if (IS_TOP(op)) break; +#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; @@ -810,11 +852,97 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } break; case ZEND_ASSIGN_DIM: + { + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + /* If $a in $a[$b]=$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); } - break; + + if (IS_BOT(op1)) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + return; + } + + SKIP_IF_TOP(op1); + SKIP_IF_TOP(data); + if (op2) { + SKIP_IF_TOP(op2); + } + + if (op2 && IS_BOT(op2)) { + /* Update of unknown index */ + SET_RESULT_BOT(result); + if (ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + empty_partial_array(&zv); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(op1); + } + return; + } + + if (IS_BOT(data)) { + + SET_RESULT_BOT(result); + if ((IS_PARTIAL_ARRAY(op1) + || Z_TYPE_P(op1) == IS_NULL + || Z_TYPE_P(op1) == IS_FALSE + || Z_TYPE_P(op1) == IS_ARRAY) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + + 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)); + } + + if (!op2) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(op1, &zv); + } else if (ct_eval_del_array_elem(&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_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)); + } else { + ZVAL_DUP(&zv, op1); + } + + if (!op2 && IS_PARTIAL_ARRAY(&zv)) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(result, data); + SET_RESULT(result, &zv); + } else if (ct_eval_assign_dim(&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); + } + return; + } + case ZEND_SEND_VAL: case ZEND_SEND_VAR: { @@ -834,6 +962,103 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o ssa_op = &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]; break; } + case ZEND_INIT_ARRAY: + case ZEND_ADD_ARRAY_ELEMENT: + { + zval *result = NULL; + + if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { + result = &ctx->values[ssa_op->result_use]; + if (IS_BOT(result)) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + return; + } + SKIP_IF_TOP(result); + } + + if (op1) { + SKIP_IF_TOP(op1); + } + + if (op2) { + SKIP_IF_TOP(op2); + } + + /* We want to avoid keeping around intermediate arrays for each SSA variable in the + * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode + * and use a NULL value everywhere else. */ + if (Z_TYPE(ctx->values[ssa_op->result_def]) == IS_NULL) { + return; + } + + if (op2 && IS_BOT(op2)) { + /* Update of unknown index */ + SET_RESULT_BOT(op1); + if (ssa_op->result_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + empty_partial_array(&zv); + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(result); + } + return; + } + + if ((op1 && IS_BOT(op1)) + || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) { + + SET_RESULT_BOT(op1); + if (ssa_op->result_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + if (!result) { + empty_partial_array(&zv); + } else { + Z_TYPE_INFO_P(result) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); + ZVAL_COPY_VALUE(&zv, result); + ZVAL_NULL(result); + } + if (!op2) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(result, &zv); + } else if (ct_eval_del_array_elem(&zv, op2) == SUCCESS) { + SET_RESULT(result, &zv); + } else { + SET_RESULT_BOT(result); + } + zval_ptr_dtor_nogc(&zv); + } else { + /* If any operand is BOT, mark the result as BOT right away. + * Exceptions to this rule are handled above. */ + SET_RESULT_BOT(result); + } + + } else { + if (result) { + ZVAL_COPY_VALUE(&zv, result); + ZVAL_NULL(result); + } else { + array_init(&zv); + } + + if (op1) { + if (!op2 && IS_PARTIAL_ARRAY(&zv)) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(result, &zv); + } else if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) { + SET_RESULT(result, &zv); + } else { + SET_RESULT_BOT(result); + } + } else { + SET_RESULT(result, &zv); + } + + zval_ptr_dtor_nogc(&zv); + } + return; + } } if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) { @@ -889,24 +1114,62 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: case ZEND_ASSIGN_POW: - /* Obj/dim compound assign */ - if (opline->extended_value) { - SET_RESULT_BOT(op1); - SET_RESULT_BOT(result); - break; + if (op1) { + SKIP_IF_TOP(op1); } - - SKIP_IF_TOP(op1); - SKIP_IF_TOP(op2); - - if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) { - SET_RESULT(op1, &zv); - SET_RESULT(result, &zv); - zval_ptr_dtor_nogc(&zv); - break; + if (op2) { + SKIP_IF_TOP(op2); + } + if (!opline->extended_value) { + if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) { + SET_RESULT(op1, &zv); + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } else if (opline->extended_value == ZEND_ASSIGN_DIM) { + if ((IS_PARTIAL_ARRAY(op1) || Z_TYPE_P(op1) == IS_ARRAY) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE + && op2) { + zval tmp; + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + + SKIP_IF_TOP(data); + + 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)); + 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) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + break; + } + + 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)); + } else { + ZVAL_DUP(&zv, op1); + } + } + + if (ct_eval_assign_dim(&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(op1); SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); break; case ZEND_PRE_INC: case ZEND_PRE_DEC: @@ -1065,80 +1328,6 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } SET_RESULT_BOT(result); break; - case ZEND_INIT_ARRAY: - case ZEND_ADD_ARRAY_ELEMENT: - { - zval *result = NULL; - if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) { - SET_RESULT_BOT(result); - SET_RESULT_BOT(op1); - break; - } - - if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { - result = &ctx->values[ssa_op->result_use]; - if (IS_BOT(result)) { - SET_RESULT_BOT(result); - break; - } - SKIP_IF_TOP(result); - } - - SKIP_IF_TOP(op1); - if (op2) { - SKIP_IF_TOP(op2); - } - - /* We want to avoid keeping around intermediate arrays for each SSA variable in the - * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode - * and use a NULL value everywhere else. */ - if (Z_TYPE(ctx->values[ssa_op->result_def]) == IS_NULL) { - break; - } - - if (result) { - ZVAL_COPY_VALUE(&zv, result); - ZVAL_NULL(result); - } else { - array_init(&zv); - } - - if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) { - SET_RESULT(result, &zv); - zval_ptr_dtor_nogc(&zv); - break; - } - SET_RESULT_BOT(result); - zval_ptr_dtor_nogc(&zv); - break; - } - case ZEND_ASSIGN_DIM: - { - zval *data = get_op1_value(ctx, opline+1, ssa_op+1); - if (IS_BOT(data)) { - SET_RESULT_BOT(op1); - SET_RESULT_BOT(result); - break; - } - - SKIP_IF_TOP(data); - SKIP_IF_TOP(op1); - if (op2) { - SKIP_IF_TOP(op2); - } - - ZVAL_DUP(&zv, op1); - if (ct_eval_assign_dim(&zv, data, op2) == SUCCESS) { - SET_RESULT(result, data); - SET_RESULT(op1, &zv); - zval_ptr_dtor_nogc(&zv); - break; - } - SET_RESULT_BOT(result); - SET_RESULT_BOT(op1); - zval_ptr_dtor_nogc(&zv); - break; - } case ZEND_DO_ICALL: { zend_call_info *call; @@ -1271,7 +1460,7 @@ static void sccp_mark_feasible_successors( break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - if (Z_TYPE_P(op1) != IS_ARRAY) { + if (Z_TYPE_P(op1) != IS_ARRAY && !IS_PARTIAL_ARRAY(op1)) { scdf_mark_edge_feasible(scdf, block_num, block->successors[0]); scdf_mark_edge_feasible(scdf, block_num, block->successors[1]); return; @@ -1287,7 +1476,45 @@ static void sccp_mark_feasible_successors( scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } -static void join_phi_values(zval *a, zval *b) { +static int join_partial_arrays(zval *a, zval *b) +{ + 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); + + ZEND_HASH_FOREACH_KEY_VAL(ht1, index, key, val1) { + if (key) { + val2 = zend_hash_find(ht2, key); + } else { + val2 = zend_hash_index_find(ht2, index); + } + if (val2 && zend_is_identical(val1, val2)) { + if (key) { + val1 = zend_hash_add_new(Z_ARRVAL(ret), key, val1); + } else { + val1 = zend_hash_index_add_new(Z_ARRVAL(ret), index, val1); + } + Z_TRY_ADDREF_P(val1); + } + } ZEND_HASH_FOREACH_END(); + + zval_ptr_dtor_nogc(a); + ZVAL_COPY_VALUE(a, &ret); + + return SUCCESS; +} + +static void join_phi_values(zval *a, zval *b, zend_bool escape) { if (IS_BOT(a) || IS_TOP(b)) { return; } @@ -1296,9 +1523,21 @@ static void join_phi_values(zval *a, zval *b) { ZVAL_COPY(a, b); return; } - if (IS_BOT(b) || !zend_is_identical(a, b)) { + if (IS_BOT(b)) { zval_ptr_dtor_nogc(a); MAKE_BOT(a); + return; + } + if (IS_PARTIAL_ARRAY(a) || IS_PARTIAL_ARRAY(b)) { + if (escape || join_partial_arrays(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); + MAKE_BOT(a); + } } } @@ -1317,14 +1556,14 @@ static void sccp_visit_phi(scdf_ctx *scdf, zend_ssa_phi *phi) { if (phi->pi >= 0) { ZEND_ASSERT(phi->sources[0] >= 0); if (scdf_is_edge_feasible(scdf, phi->pi, phi->block)) { - join_phi_values(&result, &ctx->values[phi->sources[0]]); + join_phi_values(&result, &ctx->values[phi->sources[0]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE); } } else { for (i = 0; i < block->predecessors_count; i++) { ZEND_ASSERT(phi->sources[i] >= 0); if (scdf_is_edge_feasible(scdf, predecessors[i], phi->block)) { SCP_DEBUG("val, "); - join_phi_values(&result, &ctx->values[phi->sources[i]]); + join_phi_values(&result, &ctx->values[phi->sources[i]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE); } else { SCP_DEBUG("--, "); } @@ -1391,7 +1630,13 @@ static int replace_constant_operands(sccp_ctx *ctx) { zval *value; int use; - if (value_known(&ctx->values[i])) { + if (IS_PARTIAL_ARRAY(&ctx->values[i])) { + if (!Z_DELREF(ctx->values[i])) { + zend_array_destroy(Z_ARR(ctx->values[i])); + } + Z_TYPE_INFO(ctx->values[i]) = BOT; + continue; + } else if (value_known(&ctx->values[i])) { value = &ctx->values[i]; } else { value = value_from_type_and_range(ctx, i, &tmp); diff --git a/ext/opcache/tests/opt/sccp_001.phpt b/ext/opcache/tests/opt/sccp_001.phpt new file mode 100644 index 0000000..70d2ddc --- /dev/null +++ b/ext/opcache/tests/opt/sccp_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +SCCP 001: Constant propagation +--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() { + $a = 1; + $b = $a + 2; + $a += $b; + return $a; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_001.php:1-9 +L0: RETURN int(1) + +foo: ; (lines=1, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %ssccp_001.php:2-7 +L0: RETURN int(4) diff --git a/ext/opcache/tests/opt/sccp_002.phpt b/ext/opcache/tests/opt/sccp_002.phpt new file mode 100644 index 0000000..3b9aae4 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_002.phpt @@ -0,0 +1,36 @@ +--TEST-- +SCCP 002: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = [$x]; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $a[$k] = $i; + echo $a[$k]; + } + echo $a[2]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_002.php:1-14 +L0: RETURN int(1) + +foo: ; (lines=4, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_002.php:2-12 +L0: CV0($x) = RECV 1 +L1: ECHO int(1) +L2: ECHO int(1) +L3: RETURN null diff --git a/ext/opcache/tests/opt/sccp_003.phpt b/ext/opcache/tests/opt/sccp_003.phpt new file mode 100644 index 0000000..84c1e5a --- /dev/null +++ b/ext/opcache/tests/opt/sccp_003.phpt @@ -0,0 +1,35 @@ +--TEST-- +SCCP 003: Conditional Constant Propagation of non-escaping array elements +--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() { + $a = [1,2,3]; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $a[$k] = $i; + echo $a[$k]; + } + echo $a[2]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_003.php:1-14 +L0: RETURN int(1) + +foo: ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %ssccp_003.php:2-12 +L0: ECHO int(1) +L1: ECHO int(1) +L2: RETURN null diff --git a/ext/opcache/tests/opt/sccp_004.phpt b/ext/opcache/tests/opt/sccp_004.phpt new file mode 100644 index 0000000..7a59bfa --- /dev/null +++ b/ext/opcache/tests/opt/sccp_004.phpt @@ -0,0 +1,39 @@ +--TEST-- +SCCP 004: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = [1,2,3]; + $a[2] = $x; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $a[$k] = $i; +// $a[$k]++; + echo isset($a[$k]); +// $a[$k] += 5; + } + echo $a[2]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_004.php:1-17 +L0: RETURN int(1) + +foo: ; (lines=4, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_004.php:2-15 +L0: CV0($x) = RECV 1 +L1: ECHO bool(true) +L2: ECHO int(1) +L3: RETURN null diff --git a/ext/opcache/tests/opt/sccp_005.phpt b/ext/opcache/tests/opt/sccp_005.phpt new file mode 100644 index 0000000..bb639f7 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_005.phpt @@ -0,0 +1,28 @@ +--TEST-- +SCCP 005: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = [1,2,$x]; + echo $a[1]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_005.php:1-7 +L0: RETURN int(1) + +foo: ; (lines=3, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_005.php:2-5 +L0: CV0($x) = RECV 1 +L1: ECHO int(2) +L2: RETURN null diff --git a/ext/opcache/tests/opt/sccp_006.phpt b/ext/opcache/tests/opt/sccp_006.phpt new file mode 100644 index 0000000..bc3bac0 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_006.phpt @@ -0,0 +1,35 @@ +--TEST-- +SCCP 006: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = ["a"=>1,"a"=>2,"a"=>$x]; + echo $a["a"]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_006.php:1-7 +L0: RETURN int(1) + +foo: ; (lines=8, args=1, vars=2, tmps=1) + ; (after optimizer) + ; %ssccp_006.php:2-5 +L0: CV0($x) = RECV 1 +L1: T2 = INIT_ARRAY 3 int(1) string("a") +L2: T2 = ADD_ARRAY_ELEMENT int(2) string("a") +L3: T2 = ADD_ARRAY_ELEMENT CV0($x) string("a") +L4: CV1($a) = QM_ASSIGN T2 +L5: V2 = FETCH_DIM_R CV1($a) string("a") +L6: ECHO V2 +L7: RETURN null +LIVE RANGES: + 2: L2 - L4 (tmp/var) diff --git a/ext/opcache/tests/opt/sccp_007.phpt b/ext/opcache/tests/opt/sccp_007.phpt new file mode 100644 index 0000000..b3f6e2f --- /dev/null +++ b/ext/opcache/tests/opt/sccp_007.phpt @@ -0,0 +1,34 @@ +--TEST-- +SCCP 007: Conditional Constant Propagation of non-escaping array elements +--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) { + if ($x) { + $a = [0,1]; + } else { + $a = [0,2]; + } + echo $a[0]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_007.php:1-11 +L0: RETURN int(1) + +foo: ; (lines=5, args=1, vars=1, tmps=1) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_007.php:2-9 +L0: CV0($x) = RECV 1 +L1: JMPZ CV0($x) L3 +L2: JMP L3 +L3: ECHO int(0) +L4: RETURN null diff --git a/ext/opcache/tests/opt/sccp_008.phpt b/ext/opcache/tests/opt/sccp_008.phpt new file mode 100644 index 0000000..4b9db3f --- /dev/null +++ b/ext/opcache/tests/opt/sccp_008.phpt @@ -0,0 +1,37 @@ +--TEST-- +SCCP 008: Conditional Constant Propagation of non-escaping array elements +--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) { + if ($x) { + $a = [0,1]; + } else { + $a = [0,2]; + } + echo $a[1]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_008.php:1-11 +L0: RETURN int(1) + +foo: ; (lines=8, args=1, vars=2, tmps=1) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_008.php:2-9 +L0: CV0($x) = RECV 1 +L1: JMPZ CV0($x) L4 +L2: CV1($a) = QM_ASSIGN array(...) +L3: JMP L5 +L4: CV1($a) = QM_ASSIGN array(...) +L5: V2 = FETCH_DIM_R CV1($a) int(1) +L6: ECHO V2 +L7: RETURN null
diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index 24af78e..ca0fbde 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -80,10 +80,12 @@ typedef struct _sccp_ctx { zval bot; } sccp_ctx; -#define TOP ((zend_uchar)-1) +#define TOP ((zend_uchar)-1) #define BOT ((zend_uchar)-2) +#define PARTIAL_ARRAY ((zend_uchar)-3) #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 MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP) #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT) @@ -114,7 +116,7 @@ static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) { } #if ZEND_DEBUG - ZEND_ASSERT(zend_is_identical(value, new)); + ZEND_ASSERT(IS_PARTIAL_ARRAY(new) || zend_is_identical(value, new)); #endif } @@ -311,9 +313,9 @@ static inline int fetch_array_elem(zval **result, zval *op1, zval *op2) { } static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int support_strings) { - if (Z_TYPE_P(op1) == IS_ARRAY) { + if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) { zval *value; - if (fetch_array_elem(&value, op1, op2) == SUCCESS && value) { + if (fetch_array_elem(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) { ZVAL_COPY(result, value); return SUCCESS; } @@ -331,11 +333,14 @@ static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int supp } static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval *op1, zval *op2) { - if (Z_TYPE_P(op1) == IS_ARRAY) { + if (Z_TYPE_P(op1) == IS_ARRAY || IS_PARTIAL_ARRAY(op1)) { zval *value; if (fetch_array_elem(&value, op1, op2) == FAILURE) { return FAILURE; } + if (IS_PARTIAL_ARRAY(op1) && (!value || IS_BOT(value))) { + return FAILURE; + } if (extended_value & ZEND_ISSET) { ZVAL_BOOL(result, value && Z_TYPE_P(value) != IS_NULL); } else { @@ -352,6 +357,35 @@ static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval } } +static inline int ct_eval_del_array_elem(zval *result, zval *key) { + ZEND_ASSERT(IS_PARTIAL_ARRAY(result)); + + switch (Z_TYPE_P(key)) { + case IS_NULL: + zend_hash_del(Z_ARR_P(result), ZSTR_EMPTY_ALLOC()); + break; + case IS_FALSE: + zend_hash_index_del(Z_ARR_P(result), 0); + break; + case IS_TRUE: + zend_hash_index_del(Z_ARR_P(result), 1); + break; + case IS_LONG: + zend_hash_index_del(Z_ARR_P(result), Z_LVAL_P(key)); + break; + case IS_DOUBLE: + zend_hash_index_del(Z_ARR_P(result), zend_dval_to_lval(Z_DVAL_P(key))); + break; + 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_array_elem(zval *result, zval *value, zval *key) { if (!key) { if ((value = zend_hash_next_index_insert(Z_ARR_P(result), value))) { @@ -396,6 +430,7 @@ static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) { array_init(result); /* break missing intentionally */ case IS_ARRAY: + case PARTIAL_ARRAY: return ct_eval_add_array_elem(result, value, key); case IS_STRING: // TODO Before enabling this case, make sure ARRAY_DIM result op is correct @@ -769,7 +804,14 @@ static inline int ct_eval_func_call( #define SET_RESULT_BOT(op) SET_RESULT(op, &ctx->bot) #define SET_RESULT_TOP(op) SET_RESULT(op, &ctx->top) -#define SKIP_IF_TOP(op) if (IS_TOP(op)) break; +#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; @@ -810,11 +852,97 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } break; case ZEND_ASSIGN_DIM: + { + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + /* If $a in $a[$b]=$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); } - break; + + if (IS_BOT(op1)) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + return; + } + + SKIP_IF_TOP(op1); + SKIP_IF_TOP(data); + if (op2) { + SKIP_IF_TOP(op2); + } + + if (op2 && IS_BOT(op2)) { + /* Update of unknown index */ + SET_RESULT_BOT(result); + if (ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + empty_partial_array(&zv); + SET_RESULT(op1, &zv); + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(op1); + } + return; + } + + if (IS_BOT(data)) { + + SET_RESULT_BOT(result); + if ((IS_PARTIAL_ARRAY(op1) + || Z_TYPE_P(op1) == IS_NULL + || Z_TYPE_P(op1) == IS_FALSE + || Z_TYPE_P(op1) == IS_ARRAY) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + + 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)); + } + + if (!op2) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(op1, &zv); + } else if (ct_eval_del_array_elem(&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_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)); + } else { + ZVAL_DUP(&zv, op1); + } + + if (!op2 && IS_PARTIAL_ARRAY(&zv)) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(result, data); + SET_RESULT(result, &zv); + } else if (ct_eval_assign_dim(&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); + } + return; + } + case ZEND_SEND_VAL: case ZEND_SEND_VAR: { @@ -834,6 +962,103 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o ssa_op = &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]; break; } + case ZEND_INIT_ARRAY: + case ZEND_ADD_ARRAY_ELEMENT: + { + zval *result = NULL; + + if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { + result = &ctx->values[ssa_op->result_use]; + if (IS_BOT(result)) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + return; + } + SKIP_IF_TOP(result); + } + + if (op1) { + SKIP_IF_TOP(op1); + } + + if (op2) { + SKIP_IF_TOP(op2); + } + + /* We want to avoid keeping around intermediate arrays for each SSA variable in the + * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode + * and use a NULL value everywhere else. */ + if (Z_TYPE(ctx->values[ssa_op->result_def]) == IS_NULL) { + return; + } + + if (op2 && IS_BOT(op2)) { + /* Update of unknown index */ + SET_RESULT_BOT(op1); + if (ssa_op->result_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + empty_partial_array(&zv); + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + } else { + SET_RESULT_BOT(result); + } + return; + } + + if ((op1 && IS_BOT(op1)) + || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) { + + SET_RESULT_BOT(op1); + if (ssa_op->result_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { + if (!result) { + empty_partial_array(&zv); + } else { + Z_TYPE_INFO_P(result) = PARTIAL_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT); + ZVAL_COPY_VALUE(&zv, result); + ZVAL_NULL(result); + } + if (!op2) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(result, &zv); + } else if (ct_eval_del_array_elem(&zv, op2) == SUCCESS) { + SET_RESULT(result, &zv); + } else { + SET_RESULT_BOT(result); + } + zval_ptr_dtor_nogc(&zv); + } else { + /* If any operand is BOT, mark the result as BOT right away. + * Exceptions to this rule are handled above. */ + SET_RESULT_BOT(result); + } + + } else { + if (result) { + ZVAL_COPY_VALUE(&zv, result); + ZVAL_NULL(result); + } else { + array_init(&zv); + } + + if (op1) { + if (!op2 && IS_PARTIAL_ARRAY(&zv)) { + /* We can't add NEXT element into partial array (skip it) */ + SET_RESULT(result, &zv); + } else if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) { + SET_RESULT(result, &zv); + } else { + SET_RESULT_BOT(result); + } + } else { + SET_RESULT(result, &zv); + } + + zval_ptr_dtor_nogc(&zv); + } + return; + } } if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) { @@ -889,24 +1114,62 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: case ZEND_ASSIGN_POW: - /* Obj/dim compound assign */ - if (opline->extended_value) { - SET_RESULT_BOT(op1); - SET_RESULT_BOT(result); - break; + if (op1) { + SKIP_IF_TOP(op1); } - - SKIP_IF_TOP(op1); - SKIP_IF_TOP(op2); - - if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) { - SET_RESULT(op1, &zv); - SET_RESULT(result, &zv); - zval_ptr_dtor_nogc(&zv); - break; + if (op2) { + SKIP_IF_TOP(op2); + } + if (!opline->extended_value) { + if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) { + SET_RESULT(op1, &zv); + SET_RESULT(result, &zv); + zval_ptr_dtor_nogc(&zv); + break; + } + } else if (opline->extended_value == ZEND_ASSIGN_DIM) { + if ((IS_PARTIAL_ARRAY(op1) || Z_TYPE_P(op1) == IS_ARRAY) + && ssa_op->op1_def >= 0 + && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE + && op2) { + zval tmp; + zval *data = get_op1_value(ctx, opline+1, ssa_op+1); + + SKIP_IF_TOP(data); + + 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)); + 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) { + SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); + break; + } + + 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)); + } else { + ZVAL_DUP(&zv, op1); + } + } + + if (ct_eval_assign_dim(&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(op1); SET_RESULT_BOT(result); + SET_RESULT_BOT(op1); break; case ZEND_PRE_INC: case ZEND_PRE_DEC: @@ -1065,80 +1328,6 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } SET_RESULT_BOT(result); break; - case ZEND_INIT_ARRAY: - case ZEND_ADD_ARRAY_ELEMENT: - { - zval *result = NULL; - if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) { - SET_RESULT_BOT(result); - SET_RESULT_BOT(op1); - break; - } - - if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { - result = &ctx->values[ssa_op->result_use]; - if (IS_BOT(result)) { - SET_RESULT_BOT(result); - break; - } - SKIP_IF_TOP(result); - } - - SKIP_IF_TOP(op1); - if (op2) { - SKIP_IF_TOP(op2); - } - - /* We want to avoid keeping around intermediate arrays for each SSA variable in the - * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode - * and use a NULL value everywhere else. */ - if (Z_TYPE(ctx->values[ssa_op->result_def]) == IS_NULL) { - break; - } - - if (result) { - ZVAL_COPY_VALUE(&zv, result); - ZVAL_NULL(result); - } else { - array_init(&zv); - } - - if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) { - SET_RESULT(result, &zv); - zval_ptr_dtor_nogc(&zv); - break; - } - SET_RESULT_BOT(result); - zval_ptr_dtor_nogc(&zv); - break; - } - case ZEND_ASSIGN_DIM: - { - zval *data = get_op1_value(ctx, opline+1, ssa_op+1); - if (IS_BOT(data)) { - SET_RESULT_BOT(op1); - SET_RESULT_BOT(result); - break; - } - - SKIP_IF_TOP(data); - SKIP_IF_TOP(op1); - if (op2) { - SKIP_IF_TOP(op2); - } - - ZVAL_DUP(&zv, op1); - if (ct_eval_assign_dim(&zv, data, op2) == SUCCESS) { - SET_RESULT(result, data); - SET_RESULT(op1, &zv); - zval_ptr_dtor_nogc(&zv); - break; - } - SET_RESULT_BOT(result); - SET_RESULT_BOT(op1); - zval_ptr_dtor_nogc(&zv); - break; - } case ZEND_DO_ICALL: { zend_call_info *call; @@ -1271,7 +1460,7 @@ static void sccp_mark_feasible_successors( break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - if (Z_TYPE_P(op1) != IS_ARRAY) { + if (Z_TYPE_P(op1) != IS_ARRAY && !IS_PARTIAL_ARRAY(op1)) { scdf_mark_edge_feasible(scdf, block_num, block->successors[0]); scdf_mark_edge_feasible(scdf, block_num, block->successors[1]); return; @@ -1287,7 +1476,45 @@ static void sccp_mark_feasible_successors( scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } -static void join_phi_values(zval *a, zval *b) { +static int join_partial_arrays(zval *a, zval *b) +{ + 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); + + ZEND_HASH_FOREACH_KEY_VAL(ht1, index, key, val1) { + if (key) { + val2 = zend_hash_find(ht2, key); + } else { + val2 = zend_hash_index_find(ht2, index); + } + if (val2 && zend_is_identical(val1, val2)) { + if (key) { + val1 = zend_hash_add_new(Z_ARRVAL(ret), key, val1); + } else { + val1 = zend_hash_index_add_new(Z_ARRVAL(ret), index, val1); + } + Z_TRY_ADDREF_P(val1); + } + } ZEND_HASH_FOREACH_END(); + + zval_ptr_dtor_nogc(a); + ZVAL_COPY_VALUE(a, &ret); + + return SUCCESS; +} + +static void join_phi_values(zval *a, zval *b, zend_bool escape) { if (IS_BOT(a) || IS_TOP(b)) { return; } @@ -1296,9 +1523,21 @@ static void join_phi_values(zval *a, zval *b) { ZVAL_COPY(a, b); return; } - if (IS_BOT(b) || !zend_is_identical(a, b)) { + if (IS_BOT(b)) { zval_ptr_dtor_nogc(a); MAKE_BOT(a); + return; + } + if (IS_PARTIAL_ARRAY(a) || IS_PARTIAL_ARRAY(b)) { + if (escape || join_partial_arrays(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); + MAKE_BOT(a); + } } } @@ -1317,14 +1556,14 @@ static void sccp_visit_phi(scdf_ctx *scdf, zend_ssa_phi *phi) { if (phi->pi >= 0) { ZEND_ASSERT(phi->sources[0] >= 0); if (scdf_is_edge_feasible(scdf, phi->pi, phi->block)) { - join_phi_values(&result, &ctx->values[phi->sources[0]]); + join_phi_values(&result, &ctx->values[phi->sources[0]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE); } } else { for (i = 0; i < block->predecessors_count; i++) { ZEND_ASSERT(phi->sources[i] >= 0); if (scdf_is_edge_feasible(scdf, predecessors[i], phi->block)) { SCP_DEBUG("val, "); - join_phi_values(&result, &ctx->values[phi->sources[i]]); + join_phi_values(&result, &ctx->values[phi->sources[i]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE); } else { SCP_DEBUG("--, "); } @@ -1391,7 +1630,13 @@ static int replace_constant_operands(sccp_ctx *ctx) { zval *value; int use; - if (value_known(&ctx->values[i])) { + if (IS_PARTIAL_ARRAY(&ctx->values[i])) { + if (!Z_DELREF(ctx->values[i])) { + zend_array_destroy(Z_ARR(ctx->values[i])); + } + Z_TYPE_INFO(ctx->values[i]) = BOT; + continue; + } else if (value_known(&ctx->values[i])) { value = &ctx->values[i]; } else { value = value_from_type_and_range(ctx, i, &tmp); diff --git a/ext/opcache/tests/opt/sccp_001.phpt b/ext/opcache/tests/opt/sccp_001.phpt new file mode 100644 index 0000000..70d2ddc --- /dev/null +++ b/ext/opcache/tests/opt/sccp_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +SCCP 001: Constant propagation +--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() { + $a = 1; + $b = $a + 2; + $a += $b; + return $a; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_001.php:1-9 +L0: RETURN int(1) + +foo: ; (lines=1, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %ssccp_001.php:2-7 +L0: RETURN int(4) diff --git a/ext/opcache/tests/opt/sccp_002.phpt b/ext/opcache/tests/opt/sccp_002.phpt new file mode 100644 index 0000000..3b9aae4 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_002.phpt @@ -0,0 +1,36 @@ +--TEST-- +SCCP 002: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = [$x]; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $a[$k] = $i; + echo $a[$k]; + } + echo $a[2]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_002.php:1-14 +L0: RETURN int(1) + +foo: ; (lines=4, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_002.php:2-12 +L0: CV0($x) = RECV 1 +L1: ECHO int(1) +L2: ECHO int(1) +L3: RETURN null diff --git a/ext/opcache/tests/opt/sccp_003.phpt b/ext/opcache/tests/opt/sccp_003.phpt new file mode 100644 index 0000000..84c1e5a --- /dev/null +++ b/ext/opcache/tests/opt/sccp_003.phpt @@ -0,0 +1,35 @@ +--TEST-- +SCCP 003: Conditional Constant Propagation of non-escaping array elements +--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() { + $a = [1,2,3]; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $a[$k] = $i; + echo $a[$k]; + } + echo $a[2]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_003.php:1-14 +L0: RETURN int(1) + +foo: ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %ssccp_003.php:2-12 +L0: ECHO int(1) +L1: ECHO int(1) +L2: RETURN null diff --git a/ext/opcache/tests/opt/sccp_004.phpt b/ext/opcache/tests/opt/sccp_004.phpt new file mode 100644 index 0000000..7a59bfa --- /dev/null +++ b/ext/opcache/tests/opt/sccp_004.phpt @@ -0,0 +1,39 @@ +--TEST-- +SCCP 004: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = [1,2,3]; + $a[2] = $x; + $i = 1; + $c = $i < 2; + if ($c) { + $k = 2 * $i; + $a[$k] = $i; +// $a[$k]++; + echo isset($a[$k]); +// $a[$k] += 5; + } + echo $a[2]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_004.php:1-17 +L0: RETURN int(1) + +foo: ; (lines=4, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_004.php:2-15 +L0: CV0($x) = RECV 1 +L1: ECHO bool(true) +L2: ECHO int(1) +L3: RETURN null diff --git a/ext/opcache/tests/opt/sccp_005.phpt b/ext/opcache/tests/opt/sccp_005.phpt new file mode 100644 index 0000000..bb639f7 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_005.phpt @@ -0,0 +1,28 @@ +--TEST-- +SCCP 005: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = [1,2,$x]; + echo $a[1]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_005.php:1-7 +L0: RETURN int(1) + +foo: ; (lines=3, args=1, vars=1, tmps=1) + ; (after optimizer) + ; %ssccp_005.php:2-5 +L0: CV0($x) = RECV 1 +L1: ECHO int(2) +L2: RETURN null diff --git a/ext/opcache/tests/opt/sccp_006.phpt b/ext/opcache/tests/opt/sccp_006.phpt new file mode 100644 index 0000000..bc3bac0 --- /dev/null +++ b/ext/opcache/tests/opt/sccp_006.phpt @@ -0,0 +1,35 @@ +--TEST-- +SCCP 006: Conditional Constant Propagation of non-escaping array elements +--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) { + $a = ["a"=>1,"a"=>2,"a"=>$x]; + echo $a["a"]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %ssccp_006.php:1-7 +L0: RETURN int(1) + +foo: ; (lines=8, args=1, vars=2, tmps=1) + ; (after optimizer) + ; %ssccp_006.php:2-5 +L0: CV0($x) = RECV 1 +L1: T2 = INIT_ARRAY 3 int(1) string("a") +L2: T2 = ADD_ARRAY_ELEMENT int(2) string("a") +L3: T2 = ADD_ARRAY_ELEMENT CV0($x) string("a") +L4: CV1($a) = QM_ASSIGN T2 +L5: V2 = FETCH_DIM_R CV1($a) string("a") +L6: ECHO V2 +L7: RETURN null +LIVE RANGES: + 2: L2 - L4 (tmp/var) diff --git a/ext/opcache/tests/opt/sccp_007.phpt b/ext/opcache/tests/opt/sccp_007.phpt new file mode 100644 index 0000000..b3f6e2f --- /dev/null +++ b/ext/opcache/tests/opt/sccp_007.phpt @@ -0,0 +1,34 @@ +--TEST-- +SCCP 007: Conditional Constant Propagation of non-escaping array elements +--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) { + if ($x) { + $a = [0,1]; + } else { + $a = [0,2]; + } + echo $a[0]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_007.php:1-11 +L0: RETURN int(1) + +foo: ; (lines=5, args=1, vars=1, tmps=1) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_007.php:2-9 +L0: CV0($x) = RECV 1 +L1: JMPZ CV0($x) L3 +L2: JMP L3 +L3: ECHO int(0) +L4: RETURN null diff --git a/ext/opcache/tests/opt/sccp_008.phpt b/ext/opcache/tests/opt/sccp_008.phpt new file mode 100644 index 0000000..4b9db3f --- /dev/null +++ b/ext/opcache/tests/opt/sccp_008.phpt @@ -0,0 +1,37 @@ +--TEST-- +SCCP 008: Conditional Constant Propagation of non-escaping array elements +--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) { + if ($x) { + $a = [0,1]; + } else { + $a = [0,2]; + } + echo $a[1]; +} +?> +--EXPECTF-- +$_main: ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_008.php:1-11 +L0: RETURN int(1) + +foo: ; (lines=8, args=1, vars=2, tmps=1) + ; (after optimizer) + ; /home/dmitry/php/php-master/ext/opcache/tests/opt/sccp_008.php:2-9 +L0: CV0($x) = RECV 1 +L1: JMPZ CV0($x) L4 +L2: CV1($a) = QM_ASSIGN array(...) +L3: JMP L5 +L4: CV1($a) = QM_ASSIGN array(...) +L5: V2 = FETCH_DIM_R CV1($a) int(1) +L6: ECHO V2 +L7: RETURN null