diff options
Diffstat (limited to 'ext/opcache')
36 files changed, 2089 insertions, 1617 deletions
diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index 4f4dd8e11a..02fb8192a6 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -90,8 +90,9 @@ static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b) { zend_op *opcodes = op_array->opcodes; - while (opcodes[b->start].opcode == ZEND_NOP && b->start < b->end) { + while (b->len > 0 && opcodes[b->start].opcode == ZEND_NOP) { b->start++; + b->len--; } } @@ -100,10 +101,13 @@ static void strip_nops(zend_op_array *op_array, zend_basic_block *b) uint32_t i, j; strip_leading_nops(op_array, b); + if (b->len == 0) { + return; + } /* strip the inside NOPs */ i = j = b->start + 1; - while (i <= b->end) { + while (i < b->start + b->len) { if (op_array->opcodes[i].opcode != ZEND_NOP) { if (i != j) { op_array->opcodes[j] = op_array->opcodes[i]; @@ -112,7 +116,7 @@ static void strip_nops(zend_op_array *op_array, zend_basic_block *b) } i++; } - b->end = j - 1; + b->len = j - b->start; while (j < i) { MAKE_NOP(op_array->opcodes + j); j++; @@ -128,9 +132,8 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array strip_leading_nops(op_array, block); opline = op_array->opcodes + block->start; - end = op_array->opcodes + block->end + 1; + end = opline + block->len; while (opline < end) { - /* Constant Propagation: strip X = QM_ASSIGN(const) */ if ((opline->op1_type & (IS_TMP_VAR|IS_VAR)) && opline->opcode != ZEND_FREE) { @@ -685,6 +688,10 @@ optimize_constant_binary_op: SET_VAR_SOURCE(opline); opline++; continue; + } else if (zend_binary_op_produces_numeric_string_error(opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline))) { + SET_VAR_SOURCE(opline); + opline++; + continue; } er = EG(error_reporting); EG(error_reporting) = 0; @@ -765,34 +772,32 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) int n; for (b = blocks; b < end; b++) { + if (b->len == 0) { + continue; + } if (b->flags & ZEND_BB_REACHABLE) { - ZEND_ASSERT(b->start <= b->end); - opline = op_array->opcodes + b->end; + opline = op_array->opcodes + b->start + b->len - 1; if (opline->opcode == ZEND_JMP) { zend_basic_block *next = b + 1; - while (next < end && (!(next->flags & ZEND_BB_REACHABLE) || next->start > next->end)) { + while (next < end && !(next->flags & ZEND_BB_REACHABLE)) { next++; } if (next < end && next == blocks + b->successors[0]) { /* JMP to the next block - strip it */ MAKE_NOP(opline); - if (b->end == 0) { - b->start++; - } else { - b->end--; - } + b->len--; } - } else if (b->start == b->end && opline->opcode == ZEND_NOP) { + } else if (b->len == 1 && opline->opcode == ZEND_NOP) { /* skip empty block */ - b->start++; + b->len--; } - len += b->end - b->start + 1; - } else if (b->start <= b->end) { + len += b->len; + } else { /* this block will not be used, delete all constants there */ - zend_op *op; - zend_op *end = op_array->opcodes + b->end ; - for (op = op_array->opcodes + b->start; op <= end; op++) { + zend_op *op = op_array->opcodes + b->start; + zend_op *end = op + b->len; + for (; op < end; op++) { if (ZEND_OP1_TYPE(op) == IS_CONST) { literal_dtor(&ZEND_OP1_LITERAL(op)); } @@ -809,16 +814,9 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) /* Copy code of reachable blocks into a single buffer */ for (b = blocks; b < end; b++) { if (b->flags & ZEND_BB_REACHABLE) { - if (b->start <= b->end) { - uint32_t n = b->end - b->start + 1; - memcpy(opline, op_array->opcodes + b->start, n * sizeof(zend_op)); - b->start = opline - new_opcodes; - b->end = opline - new_opcodes + n - 1; - opline += n; - } else { - b->flags |= ZEND_BB_EMPTY; - b->start = b->end = opline - new_opcodes; - } + memcpy(opline, op_array->opcodes + b->start, b->len * sizeof(zend_op)); + b->start = opline - new_opcodes; + opline += b->len; } } @@ -828,10 +826,10 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) op_array->last = len; for (b = blocks; b < end; b++) { - if (!(b->flags & ZEND_BB_REACHABLE) || b->start > b->end) { + if (!(b->flags & ZEND_BB_REACHABLE) || b->len == 0) { continue; } - opline = op_array->opcodes + b->end; + opline = op_array->opcodes + b->start + b->len - 1; switch (opline->opcode) { case ZEND_FAST_CALL: case ZEND_JMP: @@ -846,7 +844,6 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - case ZEND_NEW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: @@ -901,9 +898,8 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) zend_op *opline = new_opcodes; zend_op *end = opline + len; while (opline < end) { - if ((opline->opcode == ZEND_FAST_CALL || - opline->opcode == ZEND_FAST_RET) && - opline->extended_value && + if (opline->opcode == ZEND_FAST_RET && + opline->op2.num != (uint32_t)-1 && opline->op2.num < (uint32_t)j) { opline->op2.num = map[opline->op2.num]; } @@ -999,8 +995,11 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr zend_basic_block *blocks = cfg->blocks; zend_op *last_op; - ZEND_ASSERT(block->start <= block->end); - last_op = op_array->opcodes + block->end; + if (block->len == 0) { + return; + } + + last_op = op_array->opcodes + block->start + block->len - 1; switch (last_op->opcode) { case ZEND_JMP: { @@ -1016,9 +1015,7 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr /* JMP(next) -> NOP */ if (block->successors[0] == next) { MAKE_NOP(last_op); - if (block->start != block->end) { - block->end--; - } + block->len--; break; } @@ -1162,7 +1159,7 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr next_target: target = op_array->opcodes + target_block->start; - target_end = op_array->opcodes + target_block->end + 1; + target_end = target + target_block->len; while (target < target_end && target->opcode == ZEND_NOP) { target++; } @@ -1186,9 +1183,9 @@ next_target: same_type == ZEND_OP1_TYPE(target) && same_var == VAR_NUM_EX(target->op1) && !(target_block->flags & ZEND_BB_PROTECTED)) { - /* JMPZ(X, L), L: X = JMPNZ_EX(X, L2) -> JMPZ(X, L+1) */ + /* JMPZ(X, L), L: T = JMPNZ_EX(X, L2) -> T = JMPZ_EX(X, L+1) */ last_op->opcode += 3; - last_op->result = target->result; + COPY_NODE(last_op->result, target->result); DEL_SOURCE(block, block->successors[0]); block->successors[0] = target_block->successors[1]; ADD_SOURCE(block, block->successors[0]); @@ -1298,7 +1295,7 @@ next_target: target_block = blocks + block->successors[0]; next_target_ex: target = op_array->opcodes + target_block->start; - target_end = op_array->opcodes + target_block->end + 1; + target_end = target + target_block->len; while (target < target_end && target->opcode == ZEND_NOP) { target++; } @@ -1421,7 +1418,7 @@ next_target_ex: next_target_znz: target = op_array->opcodes + target_block->start; - target_end = op_array->opcodes + target_block->end + 1; + target_end = target + target_block->len; while (target < target_end && target->opcode == ZEND_NOP) { target++; } @@ -1494,7 +1491,7 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use } opline = op_array->opcodes + block->start; - end = op_array->opcodes + block->end + 1; + end = opline + block->len; if (!(block->flags & ZEND_BB_FOLLOW) || (block->flags & ZEND_BB_TARGET)) { /* Skip continuation of "extended" BB */ @@ -1569,12 +1566,12 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use for (n = cfg->blocks_count; n > 0;) { block = cfg->blocks + (--n); - if (!(block->flags & ZEND_BB_REACHABLE)) { + if (!(block->flags & ZEND_BB_REACHABLE) || block->len == 0) { continue; } - opline = op_array->opcodes + block->end; end = op_array->opcodes + block->start; + opline = end + block->len - 1; if (!next_block || !(next_block->flags & ZEND_BB_FOLLOW) || (next_block->flags & ZEND_BB_TARGET)) { @@ -1629,8 +1626,11 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use case ZEND_BOOL_NOT: if (ZEND_OP1_TYPE(opline) == IS_CONST) { literal_dtor(&ZEND_OP1_LITERAL(opline)); + } else if (ZEND_OP1_TYPE(opline) == IS_TMP_VAR) { + opline->opcode = ZEND_FREE; + } else { + MAKE_NOP(opline); } - MAKE_NOP(opline); break; case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: @@ -1691,16 +1691,17 @@ static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg) if ((b->flags & ZEND_BB_FOLLOW) && !(b->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED)) && prev && - prev->successors[0] == i && prev->successors[1] == -1) { - - if (op_array->opcodes[prev->end].opcode == ZEND_JMP) { - MAKE_NOP(op_array->opcodes + prev->end); + prev->successors[0] == i && prev->successors[1] == -1) + { + zend_op *last_op = op_array->opcodes + prev->start + prev->len - 1; + if (prev->len != 0 && last_op->opcode == ZEND_JMP) { + MAKE_NOP(last_op); } for (bb = prev + 1; bb != b; bb++) { zend_op *op = op_array->opcodes + bb->start; - zend_op *end = op_array->opcodes + bb->end; - while (op <= end) { + zend_op *end = op + bb->len; + while (op < end) { if (ZEND_OP1_TYPE(op) == IS_CONST) { literal_dtor(&ZEND_OP1_LITERAL(op)); } @@ -1711,18 +1712,18 @@ static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg) op++; } /* make block empty */ - bb->start = bb->end + 1; + bb->len = 0; } /* re-link */ - prev->flags |= (b->flags & ZEND_BB_EXIT); - prev->end = b->end; + prev->flags |= (b->flags & ZEND_BB_EXIT); + prev->len = b->start + b->len - prev->start; prev->successors[0] = b->successors[0]; prev->successors[1] = b->successors[1]; /* unlink & make block empty and unreachable */ b->flags = 0; - b->start = b->end + 1; + b->len = 0; b->successors[0] = -1; b->successors[1] = -1; } else { diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c index 76c89fe828..68178f5873 100644 --- a/ext/opcache/Optimizer/compact_literals.c +++ b/ext/opcache/Optimizer/compact_literals.c @@ -72,9 +72,9 @@ typedef struct _literal_info { info[n].u.num = (_num); \ } while (0) -#define LITERAL_INFO_OBJ(n, kind, merge, slots, related, _num) do { \ +#define LITERAL_INFO_OBJ(n, kind, merge, slots, related) do { \ info[n].flags = (LITERAL_EX_OBJ | ((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \ - info[n].u.num = (_num); \ + info[n].u.num = (uint32_t)-1; \ } while (0) static void optimizer_literal_obj_info(literal_info *info, @@ -92,7 +92,7 @@ static void optimizer_literal_obj_info(literal_info *info, */ if (Z_TYPE(op_array->literals[constant]) == IS_STRING && op_type == IS_UNUSED) { - LITERAL_INFO_OBJ(constant, kind, 1, slots, related, op_array->this_var); + LITERAL_INFO_OBJ(constant, kind, 1, slots, related); } else { LITERAL_INFO(constant, kind, 0, slots, related); } @@ -421,9 +421,11 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case IS_CONSTANT: if (info[i].flags & LITERAL_MAY_MERGE) { if (info[i].flags & LITERAL_EX_OBJ) { - int key_len = MAX_LENGTH_OF_LONG + sizeof("->") - 1 + Z_STRLEN(op_array->literals[i]); + int key_len = sizeof("$this->") - 1 + Z_STRLEN(op_array->literals[i]); key = zend_string_alloc(key_len, 0); - ZSTR_LEN(key) = snprintf(ZSTR_VAL(key), ZSTR_LEN(key)-1, "%d->%s", info[i].u.num, Z_STRVAL(op_array->literals[i])); + memcpy(ZSTR_VAL(key), "$this->", sizeof("$this->") - 1); + memcpy(ZSTR_VAL(key) + sizeof("$this->") - 1, Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]) + 1); + ZSTR_LEN(key) = key_len; } else if (info[i].flags & LITERAL_EX_CLASS) { int key_len; zval *class_name = &op_array->literals[(info[i].u.num < i) ? map[info[i].u.num] : info[i].u.num]; diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index dac4ab2f45..a64619bfe8 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -27,6 +27,7 @@ #include "zend_cfg.h" #include "zend_ssa.h" #include "zend_func_info.h" +#include "zend_call_graph.h" #include "zend_inference.h" #include "zend_dump.h" @@ -42,7 +43,8 @@ int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, /* Build SSA */ memset(ssa, 0, sizeof(zend_ssa)); - if (zend_build_cfg(&ctx->arena, op_array, 0, &ssa->cfg, flags) != SUCCESS) { + if (zend_build_cfg(&ctx->arena, op_array, + ZEND_CFG_NO_ENTRY_PREDECESSORS, &ssa->cfg, flags) != SUCCESS) { return FAILURE; } @@ -80,7 +82,7 @@ int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, if (ctx->debug_level & ZEND_DUMP_DFA_PHI) { build_flags |= ZEND_SSA_DEBUG_PHI_PLACEMENT; } - if (zend_build_ssa(&ctx->arena, op_array, build_flags, ssa, flags) != SUCCESS) { + if (zend_build_ssa(&ctx->arena, ctx->script, op_array, build_flags, ssa, flags) != SUCCESS) { return FAILURE; } @@ -112,6 +114,253 @@ int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, return SUCCESS; } +static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa) +{ + zend_basic_block *blocks = ssa->cfg.blocks; + zend_basic_block *end = blocks + ssa->cfg.blocks_count; + zend_basic_block *b; + zend_func_info *func_info; + int j; + uint32_t i; + uint32_t target = 0; + uint32_t *shiftlist; + ALLOCA_FLAG(use_heap); + + shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); + memset(shiftlist, 0, sizeof(uint32_t) * op_array->last); + for (b = blocks; b < end; b++) { + if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { + uint32_t end; + if (b->flags & ZEND_BB_UNREACHABLE_FREE) { + /* Only keep the FREE for the loop var */ + ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE + || op_array->opcodes[b->start].opcode == ZEND_FE_FREE); + b->len = 1; + } + + end = b->start + b->len; + i = b->start; + b->start = target; + while (i < end) { + shiftlist[i] = i - target; + if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP) || + /*keep NOP to support ZEND_VM_SMART_BRANCH */ + (i > 0 && + i + 1 < op_array->last && + (op_array->opcodes[i+1].opcode == ZEND_JMPZ || + op_array->opcodes[i+1].opcode == ZEND_JMPNZ) && + (op_array->opcodes[i-1].opcode == ZEND_IS_IDENTICAL || + op_array->opcodes[i-1].opcode == ZEND_IS_NOT_IDENTICAL || + op_array->opcodes[i-1].opcode == ZEND_IS_EQUAL || + op_array->opcodes[i-1].opcode == ZEND_IS_NOT_EQUAL || + op_array->opcodes[i-1].opcode == ZEND_IS_SMALLER || + op_array->opcodes[i-1].opcode == ZEND_IS_SMALLER_OR_EQUAL || + op_array->opcodes[i-1].opcode == ZEND_CASE || + op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_VAR || + op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_STATIC_PROP || + op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ || + op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_PROP_OBJ || + op_array->opcodes[i-1].opcode == ZEND_INSTANCEOF || + op_array->opcodes[i-1].opcode == ZEND_TYPE_CHECK || + op_array->opcodes[i-1].opcode == ZEND_DEFINED))) { + if (i != target) { + op_array->opcodes[target] = op_array->opcodes[i]; + ssa->ops[target] = ssa->ops[i]; + } + target++; + } + i++; + } + if (target != end && b->len != 0) { + zend_op *opline; + zend_op *new_opline; + + opline = op_array->opcodes + end - 1; + b->len = target - b->start; + new_opline = op_array->opcodes + target - 1; + switch (new_opline->opcode) { + case ZEND_JMP: + case ZEND_FAST_CALL: + ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline)); + break; + case ZEND_JMPZNZ: + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + /* break missing intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); + break; + case ZEND_CATCH: + if (!opline->result.num) { + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + } + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + break; + } + } + } + } + + if (target != op_array->last) { + /* reset rest opcodes */ + for (i = target; i < op_array->last; i++) { + MAKE_NOP(op_array->opcodes + i); + } + + /* update SSA variables */ + for (j = 0; j < ssa->vars_count; j++) { + if (ssa->vars[j].definition >= 0) { + ssa->vars[j].definition -= shiftlist[ssa->vars[j].definition]; + } + if (ssa->vars[j].use_chain >= 0) { + ssa->vars[j].use_chain -= shiftlist[ssa->vars[j].use_chain]; + } + } + for (i = 0; i < op_array->last; i++) { + if (ssa->ops[i].op1_use_chain >= 0) { + ssa->ops[i].op1_use_chain -= shiftlist[ssa->ops[i].op1_use_chain]; + } + if (ssa->ops[i].op2_use_chain >= 0) { + ssa->ops[i].op2_use_chain -= shiftlist[ssa->ops[i].op2_use_chain]; + } + if (ssa->ops[i].res_use_chain >= 0) { + ssa->ops[i].res_use_chain -= shiftlist[ssa->ops[i].res_use_chain]; + } + } + + /* update branch targets */ + for (b = blocks; b < end; b++) { + if ((b->flags & ZEND_BB_REACHABLE) && b->len != 0) { + zend_op *opline = op_array->opcodes + b->start + b->len - 1; + + switch (opline->opcode) { + case ZEND_JMP: + case ZEND_FAST_CALL: + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]); + break; + case ZEND_JMPZNZ: + opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + /* break missing intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + case ZEND_CATCH: + opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + break; + } + } + } + + /* update brk/cont array */ + for (j = 0; j < op_array->last_live_range; j++) { + op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; + op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; + } + + /* update try/catch array */ + for (j = 0; j < op_array->last_try_catch; j++) { + op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; + op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; + if (op_array->try_catch_array[j].finally_op) { + op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; + op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; + } + } + + /* update early binding list */ + if (op_array->early_binding != (uint32_t)-1) { + uint32_t *opline_num = &op_array->early_binding; + + do { + *opline_num -= shiftlist[*opline_num]; + opline_num = &ZEND_RESULT(&op_array->opcodes[*opline_num]).opline_num; + } while (*opline_num != (uint32_t)-1); + } + + /* update call graph */ + func_info = ZEND_FUNC_INFO(op_array); + if (func_info) { + zend_call_info *call_info = func_info->callee_info; + while (call_info) { + call_info->caller_init_opline -= + shiftlist[call_info->caller_init_opline - op_array->opcodes]; + call_info->caller_call_opline -= + shiftlist[call_info->caller_call_opline - op_array->opcodes]; + call_info = call_info->next_callee; + } + } + + op_array->last = target; + } + free_alloca(shiftlist, use_heap); +} + +static inline zend_bool can_elide_return_type_check( + zend_op_array *op_array, zend_ssa *ssa, zend_ssa_op *ssa_op) { + zend_arg_info *info = &op_array->arg_info[-1]; + zend_ssa_var_info *use_info = &ssa->var_info[ssa_op->op1_use]; + zend_ssa_var_info *def_info = &ssa->var_info[ssa_op->op1_def]; + + /* A type is possible that is not in the allowed types */ + if ((use_info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~(def_info->type & MAY_BE_ANY)) { + return 0; + } + + if (info->type_hint == IS_CALLABLE) { + return 0; + } + + if (info->class_name) { + if (!use_info->ce || !def_info->ce || !instanceof_function(use_info->ce, def_info->ce)) { + return 0; + } + } + + return 1; +} + +static zend_bool opline_supports_assign_contraction(zend_ssa *ssa, zend_op *opline, int src_var) { + if (opline->opcode == ZEND_NEW) { + /* see Zend/tests/generators/aborted_yield_during_new.phpt */ + return 0; + } + + if (opline->opcode == ZEND_DO_ICALL || opline->opcode == ZEND_DO_UCALL + || opline->opcode == ZEND_DO_FCALL || opline->opcode == ZEND_DO_FCALL_BY_NAME) { + /* Function calls may dtor the return value after it has already been written -- allow + * direct assignment only for types where a double-dtor does not matter. */ + uint32_t type = ssa->var_info[src_var].type; + uint32_t simple = MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE; + return !((type & MAY_BE_ANY) & ~simple); + } + + return 1; +} + void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa) { if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) { @@ -119,95 +368,254 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx } if (ssa->var_info) { - int i; + int op_1; + int v; int remove_nops = 0; + zend_op *opline; + zval tmp; + + for (v = op_array->last_var; v < ssa->vars_count; v++) { + + op_1 = ssa->vars[v].definition; + + if (op_1 < 0) { + continue; + } + + opline = op_array->opcodes + op_1; + + /* Convert LONG constants to DOUBLE */ + if (ssa->var_info[v].use_as_double) { + if (opline->opcode == ZEND_ASSIGN + && opline->op2_type == IS_CONST + && ssa->ops[op_1].op1_def == v + && !RETURN_VALUE_USED(opline) + ) { + +// op_1: ASSIGN ? -> #v [use_as_double], long(?) => ASSIGN ? -> #v, double(?) + + zval *zv = CT_CONSTANT_EX(op_array, opline->op2.constant); + ZEND_ASSERT(Z_TYPE_INFO_P(zv) == IS_LONG); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp); + + } else if (opline->opcode == ZEND_QM_ASSIGN + && opline->op1_type == IS_CONST + ) { - // 1: #1.T = OP_Y | #3.CV = OP_Y - // 2: ASSIGN #2.CV [undef,scalar] -> #3.CV, #1.T | NOP - // -- - // 2: ASSIGN #2.CV [undef,scalar] -> #3.CV, X | 3.CV = QM_ASSIGN X +// op_1: QM_ASSIGN #v [use_as_double], long(?) => QM_ASSIGN #v, double(?) - for (i = 0; i < ssa->vars_count; i++) { - int op2 = ssa->vars[i].definition; + zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant); + ZEND_ASSERT(Z_TYPE_INFO_P(zv) == IS_LONG); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp); + } + + } else { + if (opline->opcode == ZEND_ADD + || opline->opcode == ZEND_SUB + || opline->opcode == ZEND_MUL + || opline->opcode == ZEND_IS_EQUAL + || opline->opcode == ZEND_IS_NOT_EQUAL + || opline->opcode == ZEND_IS_SMALLER + || opline->opcode == ZEND_IS_SMALLER_OR_EQUAL + ) { + + if (opline->op1_type == IS_CONST + && opline->op2_type != IS_CONST + && (OP2_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE + && Z_TYPE_INFO_P(CT_CONSTANT_EX(op_array, opline->op1.constant)) == IS_LONG + ) { + +// op_1: #v.? = ADD long(?), #?.? [double] => #v.? = ADD double(?), #?.? [double] + + zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp); + + } else if (opline->op1_type != IS_CONST + && opline->op2_type == IS_CONST + && (OP1_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE + && Z_TYPE_INFO_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG + ) { + +// op_1: #v.? = ADD #?.? [double], long(?) => #v.? = ADD #?.? [double], double(?) + + zval *zv = CT_CONSTANT_EX(op_array, opline->op2.constant); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp); + } + } + } - if (op2 >= 0 - && op_array->opcodes[op2].opcode == ZEND_ASSIGN - && op_array->opcodes[op2].op1_type == IS_CV - && !RETURN_VALUE_USED(&op_array->opcodes[op2]) + if (ssa->vars[v].var >= op_array->last_var) { + /* skip TMP and VAR */ + continue; + } + + if (opline->opcode == ZEND_ASSIGN + && ssa->ops[op_1].op1_def == v + && !RETURN_VALUE_USED(opline) ) { - int var2 = ssa->ops[op2].op1_use; + int orig_var = ssa->ops[op_1].op1_use; - if (var2 >= 0 - && !(ssa->var_info[var2].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) + if (orig_var >= 0 + && !(ssa->var_info[orig_var].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) ) { - if ((op_array->opcodes[op2].op2_type & (IS_TMP_VAR|IS_VAR)) - && ssa->ops[op2].op2_use >= 0 - && !(ssa->var_info[ssa->ops[op2].op2_use].type & MAY_BE_REF) - && ssa->vars[ssa->ops[op2].op2_use].definition >= 0 - && ssa->ops[ssa->vars[ssa->ops[op2].op2_use].definition].result_def == ssa->ops[op2].op2_use - && ssa->ops[ssa->vars[ssa->ops[op2].op2_use].definition].result_use < 0 - && ssa->vars[ssa->ops[op2].op2_use].use_chain == op2 - && ssa->ops[op2].op2_use_chain < 0 - && !ssa->vars[ssa->ops[op2].op2_use].phi_use_chain - && !ssa->vars[ssa->ops[op2].op2_use].sym_use_chain - /* see Zend/tests/generators/aborted_yield_during_new.phpt */ - && op_array->opcodes[ssa->vars[ssa->ops[op2].op2_use].definition].opcode != ZEND_NEW + int src_var = ssa->ops[op_1].op2_use; + + if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) + && src_var >= 0 + && !(ssa->var_info[src_var].type & MAY_BE_REF) + && ssa->vars[src_var].definition >= 0 + && ssa->ops[ssa->vars[src_var].definition].result_def == src_var + && ssa->ops[ssa->vars[src_var].definition].result_use < 0 + && ssa->vars[src_var].use_chain == op_1 + && ssa->ops[op_1].op2_use_chain < 0 + && !ssa->vars[src_var].phi_use_chain + && !ssa->vars[src_var].sym_use_chain + && opline_supports_assign_contraction( + ssa, &op_array->opcodes[ssa->vars[src_var].definition], src_var) ) { - int var1 = ssa->ops[op2].op2_use; - int op1 = ssa->vars[var1].definition; - int var3 = i; - if (zend_ssa_unlink_use_chain(ssa, op2, var2)) { + int op_2 = ssa->vars[src_var].definition; + +// op_2: #src_var.T = OP ... => #v.CV = OP ... +// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, #src_var.T NOP + + if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) { /* Reconstruct SSA */ - ssa->vars[var3].definition = op1; - ssa->ops[op1].result_def = var3; + ssa->vars[v].definition = op_2; + ssa->ops[op_2].result_def = v; - ssa->vars[var1].definition = -1; - ssa->vars[var1].use_chain = -1; + ssa->vars[src_var].definition = -1; + ssa->vars[src_var].use_chain = -1; - ssa->ops[op2].op1_use = -1; - ssa->ops[op2].op2_use = -1; - ssa->ops[op2].op1_def = -1; - ssa->ops[op2].op1_use_chain = -1; + ssa->ops[op_1].op1_use = -1; + ssa->ops[op_1].op2_use = -1; + ssa->ops[op_1].op1_def = -1; + ssa->ops[op_1].op1_use_chain = -1; /* Update opcodes */ - op_array->opcodes[op1].result_type = op_array->opcodes[op2].op1_type; - op_array->opcodes[op1].result.var = op_array->opcodes[op2].op1.var; - MAKE_NOP(&op_array->opcodes[op2]); + op_array->opcodes[op_2].result_type = opline->op1_type; + op_array->opcodes[op_2].result.var = opline->op1.var; + MAKE_NOP(opline); remove_nops = 1; } - } else if (op_array->opcodes[op2].op2_type == IS_CONST - || ((op_array->opcodes[op2].op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)) - && ssa->ops[op2].op2_use >= 0 - && ssa->ops[op2].op2_def < 0) + } else if (opline->op2_type == IS_CONST + || ((opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)) + && ssa->ops[op_1].op2_use >= 0 + && ssa->ops[op_1].op2_def < 0) ) { - int var3 = i; - if (zend_ssa_unlink_use_chain(ssa, op2, var2)) { +// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, CONST|TMPVAR => QM_ASSIGN v.CV, CONST|TMPVAR + + if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) { /* Reconstruct SSA */ - ssa->ops[op2].result_def = var3; - ssa->ops[op2].op1_def = -1; - ssa->ops[op2].op1_use = ssa->ops[op2].op2_use; - ssa->ops[op2].op1_use_chain = ssa->ops[op2].op2_use_chain; - ssa->ops[op2].op2_use = -1; - ssa->ops[op2].op2_use_chain = -1; + ssa->ops[op_1].result_def = v; + ssa->ops[op_1].op1_def = -1; + ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use; + ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain; + ssa->ops[op_1].op2_use = -1; + ssa->ops[op_1].op2_use_chain = -1; /* Update opcode */ - op_array->opcodes[op2].result_type = op_array->opcodes[op2].op1_type; - op_array->opcodes[op2].result.var = op_array->opcodes[op2].op1.var; - op_array->opcodes[op2].op1_type = op_array->opcodes[op2].op2_type; - op_array->opcodes[op2].op1.var = op_array->opcodes[op2].op2.var; - op_array->opcodes[op2].op2_type = IS_UNUSED; - op_array->opcodes[op2].op2.var = 0; - op_array->opcodes[op2].opcode = ZEND_QM_ASSIGN; + opline->result_type = opline->op1_type; + opline->result.var = opline->op1.var; + opline->op1_type = opline->op2_type; + opline->op1.var = opline->op2.var; + opline->op2_type = IS_UNUSED; + opline->op2.var = 0; + opline->opcode = ZEND_QM_ASSIGN; } } } + + } else if (opline->opcode == ZEND_ASSIGN_ADD + && opline->extended_value == 0 + && ssa->ops[op_1].op1_def == v + && opline->op2_type == IS_CONST + && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG + && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1 + && ssa->ops[op_1].op1_use >= 0 + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) { + +// op_1: ASSIGN_ADD #?.CV [undef,null,int,foat] ->#v.CV, int(1) => PRE_INC #?.CV ->#v.CV + + opline->opcode = ZEND_PRE_INC; + SET_UNUSED(opline->op2); + + } else if (opline->opcode == ZEND_ASSIGN_SUB + && opline->extended_value == 0 + && ssa->ops[op_1].op1_def == v + && opline->op2_type == IS_CONST + && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG + && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1 + && ssa->ops[op_1].op1_use >= 0 + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) { + +// op_1: ASSIGN_SUB #?.CV [undef,null,int,foat] -> #v.CV, int(1) => PRE_DEC #?.CV ->#v.CV + + opline->opcode = ZEND_PRE_DEC; + SET_UNUSED(opline->op2); + + } else if (opline->opcode == ZEND_VERIFY_RETURN_TYPE + && ssa->ops[op_1].op1_def == v + && ssa->ops[op_1].op1_use >= 0 + && ssa->ops[op_1].op1_use_chain == -1 + && ssa->vars[v].use_chain >= 0 + && can_elide_return_type_check(op_array, ssa, &ssa->ops[op_1])) { + +// op_1: VERIFY_RETURN_TYPE #orig_var.CV [T] -> #v.CV [T] => NOP + + int orig_var = ssa->ops[op_1].op1_use; + int ret = ssa->vars[v].use_chain; + + ssa->vars[orig_var].use_chain = ret; + ssa->ops[ret].op1_use = orig_var; + + ssa->vars[v].definition = -1; + ssa->vars[v].use_chain = -1; + + ssa->ops[op_1].op1_def = -1; + ssa->ops[op_1].op1_use = -1; + + MAKE_NOP(opline); + remove_nops = 1; + + } else if (ssa->ops[op_1].op1_def == v + && !RETURN_VALUE_USED(opline) + && ssa->ops[op_1].op1_use >= 0 + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) + && (opline->opcode == ZEND_ASSIGN_ADD + || opline->opcode == ZEND_ASSIGN_SUB + || opline->opcode == ZEND_ASSIGN_MUL + || opline->opcode == ZEND_ASSIGN_DIV + || opline->opcode == ZEND_ASSIGN_MOD + || opline->opcode == ZEND_ASSIGN_SL + || opline->opcode == ZEND_ASSIGN_SR + || opline->opcode == ZEND_ASSIGN_BW_OR + || opline->opcode == ZEND_ASSIGN_BW_AND + || opline->opcode == ZEND_ASSIGN_BW_XOR) + && opline->extended_value == 0) { + +// op_1: ASSIGN_ADD #orig_var.CV [undef,null,bool,int,double] -> #v.CV, ? => #v.CV = ADD #orig_var.CV, ? + + /* Reconstruct SSA */ + ssa->ops[op_1].result_def = ssa->ops[op_1].op1_def; + ssa->ops[op_1].op1_def = -1; + + /* Update opcode */ + opline->opcode -= (ZEND_ASSIGN_ADD - ZEND_ADD); + opline->result_type = opline->op1_type; + opline->result.var = opline->op1.var; + } } + if (remove_nops) { - // TODO: remove nop??? + zend_ssa_remove_nops(op_array, ssa); } } diff --git a/ext/opcache/Optimizer/nop_removal.c b/ext/opcache/Optimizer/nop_removal.c index aa04f6bc20..3ebbdad8cf 100644 --- a/ext/opcache/Optimizer/nop_removal.c +++ b/ext/opcache/Optimizer/nop_removal.c @@ -80,7 +80,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - case ZEND_NEW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: @@ -123,7 +122,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - case ZEND_NEW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: diff --git a/ext/opcache/Optimizer/optimize_func_calls.c b/ext/opcache/Optimizer/optimize_func_calls.c index a9fb259428..75120d2501 100644 --- a/ext/opcache/Optimizer/optimize_func_calls.c +++ b/ext/opcache/Optimizer/optimize_func_calls.c @@ -29,6 +29,9 @@ #include "zend_execute.h" #include "zend_vm.h" +#define ZEND_OP1_IS_CONST_STRING(opline) \ + (ZEND_OP1_TYPE(opline) == IS_CONST && \ + Z_TYPE(op_array->literals[(opline)->op1.constant]) == IS_STRING) #define ZEND_OP2_IS_CONST_STRING(opline) \ (ZEND_OP2_TYPE(opline) == IS_CONST && \ Z_TYPE(op_array->literals[(opline)->op2.constant]) == IS_STRING) @@ -56,19 +59,13 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) switch (opline->opcode) { case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: - if (ZEND_OP2_IS_CONST_STRING(opline)) { - zend_function *func; - zval *function_name = &op_array->literals[opline->op2.constant + 1]; - if ((func = zend_hash_find_ptr(&ctx->script->function_table, - Z_STR_P(function_name))) != NULL) { - call_stack[call].func = func; - } - } + case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_METHOD_CALL: + call_stack[call].func = zend_optimizer_get_called_func( + ctx->script, op_array, opline, 0); /* break missing intentionally */ case ZEND_NEW: case ZEND_INIT_DYNAMIC_CALL: - case ZEND_INIT_METHOD_CALL: - case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_FCALL: case ZEND_INIT_USER_CALL: call_stack[call].opline = opline; @@ -88,7 +85,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) Z_CACHE_SLOT(op_array->literals[fcall->op2.constant + 1]) = Z_CACHE_SLOT(op_array->literals[fcall->op2.constant]); literal_dtor(&ZEND_OP2_LITERAL(fcall)); fcall->op2.constant = fcall->op2.constant + 1; - opline->opcode = zend_get_call_op(ZEND_INIT_FCALL, call_stack[call].func); + opline->opcode = zend_get_call_op(fcall, call_stack[call].func); } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); @@ -96,7 +93,10 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) literal_dtor(&op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant + 2]); fcall->op2.constant = fcall->op2.constant + 1; - opline->opcode = zend_get_call_op(ZEND_INIT_FCALL, call_stack[call].func); + opline->opcode = zend_get_call_op(fcall, call_stack[call].func); + } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL + || fcall->opcode == ZEND_INIT_METHOD_CALL) { + /* We don't have specialized opcodes for this, do nothing */ } else { ZEND_ASSERT(0); } @@ -145,19 +145,20 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) } } break; - case ZEND_SEND_VAR_NO_REF: - if (!(opline->extended_value & ZEND_ARG_COMPILE_TIME_BOUND) && call_stack[call - 1].func) { - if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { - opline->extended_value |= ZEND_ARG_COMPILE_TIME_BOUND | ZEND_ARG_SEND_BY_REF; + case ZEND_SEND_VAR_NO_REF_EX: + if (call_stack[call - 1].func) { + if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { + opline->opcode = ZEND_SEND_VAR_NO_REF; + } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { + opline->opcode = ZEND_SEND_VAL; } else { opline->opcode = ZEND_SEND_VAR; - opline->extended_value = 0; } } break; #if 0 case ZEND_SEND_REF: - if (opline->extended_value != ZEND_ARG_COMPILE_TIME_BOUND && call_stack[call - 1].func) { + if (call_stack[call - 1].func) { /* We won't handle run-time pass by reference */ call_stack[call - 1].opline = NULL; } diff --git a/ext/opcache/Optimizer/pass1_5.c b/ext/opcache/Optimizer/pass1_5.c index 5809d247ec..71f879e958 100644 --- a/ext/opcache/Optimizer/pass1_5.c +++ b/ext/opcache/Optimizer/pass1_5.c @@ -84,6 +84,9 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) zval_get_long(&ZEND_OP2_LITERAL(opline)) < 0) { /* shift by negative number */ break; + } else if (zend_binary_op_produces_numeric_string_error(opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline))) { + /* produces numeric string E_NOTICE/E_WARNING */ + break; } er = EG(error_reporting); EG(error_reporting) = 0; @@ -643,7 +646,6 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_FE_RESET_RW: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: - case ZEND_NEW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: diff --git a/ext/opcache/Optimizer/pass2.c b/ext/opcache/Optimizer/pass2.c index d2a2064a78..c24d87a4ad 100644 --- a/ext/opcache/Optimizer/pass2.c +++ b/ext/opcache/Optimizer/pass2.c @@ -22,7 +22,6 @@ /* pass 2: * - convert non-numeric constants to numeric constants in numeric operators * - optimize constant conditional JMPs - * - optimize static BRKs and CONTs */ #include "php.h" @@ -48,7 +47,10 @@ void zend_optimizer_pass2(zend_op_array *op_array) case ZEND_POW: if (ZEND_OP1_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING) { - convert_scalar_to_number(&ZEND_OP1_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (is_numeric_string(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)), NULL, NULL, 0)) { + convert_scalar_to_number(&ZEND_OP1_LITERAL(opline)); + } } } /* break missing *intentionally* - the assign_op's may only optimize op2 */ @@ -63,7 +65,10 @@ void zend_optimizer_pass2(zend_op_array *op_array) } if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) { - convert_scalar_to_number(&ZEND_OP2_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (is_numeric_string(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), NULL, NULL, 0)) { + convert_scalar_to_number(&ZEND_OP2_LITERAL(opline)); + } } } break; @@ -73,7 +78,11 @@ void zend_optimizer_pass2(zend_op_array *op_array) case ZEND_SR: if (ZEND_OP1_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_LONG) { - convert_to_long(&ZEND_OP1_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (!(Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING + && !is_numeric_string(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)), NULL, NULL, 0))) { + convert_to_long(&ZEND_OP1_LITERAL(opline)); + } } } /* break missing *intentionally - the assign_op's may only optimize op2 */ @@ -86,7 +95,11 @@ void zend_optimizer_pass2(zend_op_array *op_array) } if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_LONG) { - convert_to_long(&ZEND_OP2_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (!(Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING + && !is_numeric_string(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), NULL, NULL, 0))) { + convert_to_long(&ZEND_OP2_LITERAL(opline)); + } } } break; diff --git a/ext/opcache/Optimizer/zend_call_graph.c b/ext/opcache/Optimizer/zend_call_graph.c index 6c1933cfac..53eb0a84ea 100644 --- a/ext/opcache/Optimizer/zend_call_graph.c +++ b/ext/opcache/Optimizer/zend_call_graph.c @@ -21,6 +21,8 @@ #include "php.h" #include "zend_compile.h" #include "zend_extensions.h" +#include "Optimizer/zend_optimizer.h" +#include "zend_optimizer_internal.h" #include "zend_inference.h" #include "zend_call_graph.h" #include "zend_func_info.h" @@ -102,6 +104,7 @@ static void zend_collect_args_info(zend_call_info *call_info) case ZEND_SEND_VAR_EX: case ZEND_SEND_REF: case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: num = opline->op2.num; if (num > 0) { num--; @@ -150,39 +153,32 @@ static int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t call_info = NULL; switch (opline->opcode) { case ZEND_INIT_FCALL: - if ((func = zend_hash_find_ptr(&script->function_table, Z_STR_P(CRT_CONSTANT(opline->op2)))) != NULL) { - zend_func_info *callee_func_info = ZEND_FUNC_INFO(&func->op_array); - if (callee_func_info) { - call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1))); - call_info->caller_op_array = op_array; - call_info->caller_init_opline = opline; - call_info->caller_call_opline = NULL; - call_info->callee_func = func; - call_info->num_args = opline->extended_value; - call_info->next_caller = callee_func_info->caller_info; - callee_func_info->caller_info = call_info; - call_info->next_callee = func_info->callee_info; - func_info->callee_info = call_info; - } - } else if ((func = zend_hash_find_ptr(EG(function_table), Z_STR_P(CRT_CONSTANT(opline->op2)))) != NULL && - func->type == ZEND_INTERNAL_FUNCTION) { + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_STATIC_METHOD_CALL: + func = zend_optimizer_get_called_func( + script, op_array, opline, (build_flags & ZEND_RT_CONSTANTS) != 0); + if (func) { call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1))); call_info->caller_op_array = op_array; call_info->caller_init_opline = opline; call_info->caller_call_opline = NULL; call_info->callee_func = func; call_info->num_args = opline->extended_value; - call_info->next_caller = NULL; call_info->next_callee = func_info->callee_info; func_info->callee_info = call_info; + + if (func->type == ZEND_INTERNAL_FUNCTION) { + call_info->next_caller = NULL; + } else { + zend_func_info *callee_func_info = ZEND_FUNC_INFO(&func->op_array); + call_info->next_caller = callee_func_info ? callee_func_info->caller_info : NULL; + } } /* break missing intentionally */ case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_DYNAMIC_CALL: case ZEND_NEW: - case ZEND_INIT_METHOD_CALL: - case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_USER_CALL: call_stack[call] = call_info; call++; @@ -260,6 +256,8 @@ static void zend_analyze_recursion(zend_call_graph *call_graph) call_info = call_info->next_caller; } } + + free_alloca(visited, use_heap); } static void zend_sort_op_arrays(zend_call_graph *call_graph) @@ -277,17 +275,12 @@ int zend_build_call_graph(zend_arena **arena, zend_script *script, uint32_t buil if (zend_foreach_op_array(call_graph, script, zend_op_array_calc) != SUCCESS) { return FAILURE; } - call_graph->op_arrays = (zend_op_array**)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_op_array*)); + call_graph->op_arrays = (zend_op_array**)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_op_array*)); call_graph->func_infos = (zend_func_info*)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_func_info)); - if (!call_graph->op_arrays || !call_graph->func_infos) { - return FAILURE; - } - call_graph->op_arrays_count = 0; if (zend_foreach_op_array(call_graph, script, zend_op_array_collect) != SUCCESS) { return FAILURE; } - for (i = 0; i < call_graph->op_arrays_count; i++) { zend_analyze_calls(arena, script, build_flags, call_graph->op_arrays[i], call_graph->func_infos + i); } diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index 63f14634cb..e986ac0c20 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -21,6 +21,8 @@ #include "zend_cfg.h" #include "zend_func_info.h" #include "zend_worklist.h" +#include "zend_optimizer.h" +#include "zend_optimizer_internal.h" static void zend_mark_reachable(zend_op *opcodes, zend_basic_block *blocks, zend_basic_block *b) /* {{{ */ { @@ -39,15 +41,17 @@ static void zend_mark_reachable(zend_op *opcodes, zend_basic_block *blocks, zend if (!(b0->flags & ZEND_BB_REACHABLE)) { zend_mark_reachable(opcodes, blocks, b0); } - opcode = opcodes[b->end].opcode; + + ZEND_ASSERT(b->len != 0); + opcode = opcodes[b->start + b->len - 1].opcode; b = blocks + successor_1; if (opcode == ZEND_JMPZNZ) { b->flags |= ZEND_BB_TARGET; } else { b->flags |= ZEND_BB_FOLLOW; } - } else { - opcode = opcodes[b->end].opcode; + } else if (b->len != 0) { + opcode = opcodes[b->start + b->len - 1].opcode; b = blocks + successor_0; if (opcode == ZEND_JMP) { b->flags |= ZEND_BB_TARGET; @@ -57,6 +61,7 @@ static void zend_mark_reachable(zend_op *opcodes, zend_basic_block *blocks, zend //TODO: support for stackless CFG??? if (0/*stackless*/) { if (opcode == ZEND_INCLUDE_OR_EVAL || + opcode == ZEND_GENERATOR_CREATE || opcode == ZEND_YIELD || opcode == ZEND_YIELD_FROM || opcode == ZEND_DO_FCALL || @@ -66,6 +71,9 @@ static void zend_mark_reachable(zend_op *opcodes, zend_basic_block *blocks, zend } } } + } else { + b = blocks + successor_0; + b->flags |= ZEND_BB_FOLLOW; } if (b->flags & ZEND_BB_REACHABLE) return; } else { @@ -91,35 +99,39 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg * do { changed = 0; - if (cfg->split_at_live_ranges) { - /* Add live range paths */ - for (j = 0; j < op_array->last_live_range; j++) { - if (op_array->live_range[j].var == (uint32_t)-1) { - /* this live range already removed */ + /* Add live range paths */ + for (j = 0; j < op_array->last_live_range; j++) { + zend_live_range *live_range = &op_array->live_range[j]; + if (live_range->var == (uint32_t)-1) { + /* this live range already removed */ + continue; + } + b = blocks + block_map[live_range->start]; + if (b->flags & ZEND_BB_REACHABLE) { + while (b->len > 0 && op_array->opcodes[b->start].opcode == ZEND_NOP) { + b->start++; + b->len--; + } + if (b->len == 0 && b->successors[0] == block_map[live_range->end]) { + /* mark as removed (empty live range) */ + live_range->var = (uint32_t)-1; continue; } - b = blocks + block_map[op_array->live_range[j].start]; - if (b->flags & ZEND_BB_REACHABLE) { - while (op_array->opcodes[b->start].opcode == ZEND_NOP && b->start != b->end) { - b->start++; - } - if (op_array->opcodes[b->start].opcode == ZEND_NOP && - b->start == b->end && - b->successors[0] == block_map[op_array->live_range[j].end]) { - /* mark as removed (empty live range) */ - op_array->live_range[j].var = (uint32_t)-1; - continue; - } - b->flags |= ZEND_BB_GEN_VAR; - b = blocks + block_map[op_array->live_range[j].end]; - b->flags |= ZEND_BB_KILL_VAR; - if (!(b->flags & ZEND_BB_REACHABLE)) { + b->flags |= ZEND_BB_GEN_VAR; + b = blocks + block_map[live_range->end]; + b->flags |= ZEND_BB_KILL_VAR; + if (!(b->flags & ZEND_BB_REACHABLE)) { + if (cfg->split_at_live_ranges) { changed = 1; zend_mark_reachable(op_array->opcodes, blocks, b); + } else { + ZEND_ASSERT(!(b->flags & ZEND_BB_UNREACHABLE_FREE)); + ZEND_ASSERT(b->start == live_range->end); + b->flags |= ZEND_BB_UNREACHABLE_FREE; } - } else { - ZEND_ASSERT(!(blocks[block_map[op_array->live_range[j].end]].flags & ZEND_BB_REACHABLE)); } + } else { + ZEND_ASSERT(!(blocks[block_map[live_range->end]].flags & ZEND_BB_REACHABLE)); } } @@ -230,9 +242,22 @@ static void record_successor(zend_basic_block *blocks, int pred, int n, int succ blocks[pred].successors[n] = succ; } +static void initialize_block(zend_basic_block *block) { + block->flags = 0; + block->successors[0] = -1; + block->successors[1] = -1; + block->predecessors_count = 0; + block->predecessor_offset = -1; + block->idom = -1; + block->loop_header = -1; + block->level = -1; + block->children = -1; + block->next_child = -1; +} + #define BB_START(i) do { \ if (!block_map[i]) { blocks_count++;} \ - block_map[i] = 1; \ + block_map[i]++; \ } while (0) int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg, uint32_t *func_flags) /* {{{ */ @@ -245,6 +270,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b int blocks_count = 0; zend_basic_block *blocks; zval *zv; + zend_bool extra_entry_block = 0; cfg->split_at_live_ranges = (build_flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES) != 0; cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t)); @@ -268,6 +294,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; case ZEND_INCLUDE_OR_EVAL: flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; + case ZEND_GENERATOR_CREATE: case ZEND_YIELD: case ZEND_YIELD_FROM: if (build_flags & ZEND_CFG_STACKLESS) { @@ -294,25 +321,8 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b } if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { if (fn->type == ZEND_INTERNAL_FUNCTION) { - if (zend_string_equals_literal(Z_STR_P(zv), "extract")) { - flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; - } else if (zend_string_equals_literal(Z_STR_P(zv), "compact")) { - flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; - } else if (zend_string_equals_literal(Z_STR_P(zv), "parse_str") && - opline->extended_value == 1) { - flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; - } else if (zend_string_equals_literal(Z_STR_P(zv), "mb_parse_str") && - opline->extended_value == 1) { - flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; - } else if (zend_string_equals_literal(Z_STR_P(zv), "get_defined_vars")) { - flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; - } else if (zend_string_equals_literal(Z_STR_P(zv), "func_num_args")) { - flags |= ZEND_FUNC_VARARG; - } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_arg")) { - flags |= ZEND_FUNC_VARARG; - } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_args")) { - flags |= ZEND_FUNC_VARARG; - } + flags |= zend_optimizer_classify_function( + Z_STR_P(zv), opline->extended_value); } } break; @@ -363,7 +373,6 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - case ZEND_NEW: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; @@ -395,6 +404,12 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b } } + /* If the entry block has predecessors, we may need to split it */ + if ((build_flags & ZEND_CFG_NO_ENTRY_PREDECESSORS) + && op_array->last > 0 && block_map[0] > 1) { + extra_entry_block = 1; + } + if (cfg->split_at_live_ranges) { for (j = 0; j < op_array->last_live_range; j++) { BB_START(op_array->live_range[j].start); @@ -417,6 +432,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b } } + blocks_count += extra_entry_block; cfg->blocks_count = blocks_count; /* Build CFG, Step 2: Build Array of Basic Blocks */ @@ -425,36 +441,40 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b return FAILURE; } - for (i = 0, blocks_count = -1; i < op_array->last; i++) { + blocks_count = -1; + + if (extra_entry_block) { + initialize_block(&blocks[0]); + blocks[0].start = 0; + blocks[0].len = 0; + blocks_count++; + } + + for (i = 0; i < op_array->last; i++) { if (block_map[i]) { if (blocks_count >= 0) { - blocks[blocks_count].end = i - 1; + blocks[blocks_count].len = i - blocks[blocks_count].start; } blocks_count++; - blocks[blocks_count].flags = 0; + initialize_block(&blocks[blocks_count]); blocks[blocks_count].start = i; - blocks[blocks_count].successors[0] = -1; - blocks[blocks_count].successors[1] = -1; - blocks[blocks_count].predecessors_count = 0; - blocks[blocks_count].predecessor_offset = -1; - blocks[blocks_count].idom = -1; - blocks[blocks_count].loop_header = -1; - blocks[blocks_count].level = -1; - blocks[blocks_count].children = -1; - blocks[blocks_count].next_child = -1; - block_map[i] = blocks_count; - } else { - block_map[i] = (uint32_t)-1; } + block_map[i] = blocks_count; } - blocks[blocks_count].end = i - 1; + blocks[blocks_count].len = i - blocks[blocks_count].start; blocks_count++; /* Build CFG, Step 3: Calculate successors */ for (j = 0; j < blocks_count; j++) { - zend_op *opline = op_array->opcodes + blocks[j].end; - switch(opline->opcode) { + zend_op *opline; + if (blocks[j].len == 0) { + record_successor(blocks, j, 0, j + 1); + continue; + } + + opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1; + switch (opline->opcode) { case ZEND_FAST_RET: case ZEND_RETURN: case ZEND_RETURN_BY_REF: @@ -496,7 +516,6 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - case ZEND_NEW: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; @@ -715,8 +734,8 @@ int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32 int *dj_spanning_tree; zend_worklist work; int flag = ZEND_FUNC_NO_LOOPS; - ALLOCA_FLAG(list_use_heap); - ALLOCA_FLAG(tree_use_heap); + ALLOCA_FLAG(list_use_heap) + ALLOCA_FLAG(tree_use_heap) ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap); dj_spanning_tree = do_alloca(sizeof(int) * cfg->blocks_count, tree_use_heap); diff --git a/ext/opcache/Optimizer/zend_cfg.h b/ext/opcache/Optimizer/zend_cfg.h index de94997dd5..da908fdbe3 100644 --- a/ext/opcache/Optimizer/zend_cfg.h +++ b/ext/opcache/Optimizer/zend_cfg.h @@ -31,7 +31,7 @@ #define ZEND_BB_FINALLY_END (1<<8) /* end of finally block */ #define ZEND_BB_GEN_VAR (1<<9) /* start of live range */ #define ZEND_BB_KILL_VAR (1<<10) /* end of live range */ -#define ZEND_BB_EMPTY (1<<11) +#define ZEND_BB_UNREACHABLE_FREE (1<<11) /* unreachable loop free */ #define ZEND_BB_LOOP_HEADER (1<<16) #define ZEND_BB_IRREDUCIBLE_LOOP (1<<17) @@ -43,7 +43,7 @@ typedef struct _zend_basic_block { uint32_t flags; uint32_t start; /* first opcode number */ - uint32_t end; /* last opcode number */ + uint32_t len; /* number of opcodes */ int successors[2]; /* up to 2 successor blocks */ int predecessors_count; /* number of predecessors */ int predecessor_offset; /* offset of 1-st predecessor */ @@ -96,6 +96,7 @@ typedef struct _zend_cfg { #define ZEND_SSA_DEBUG_PHI_PLACEMENT (1<<28) #define ZEND_SSA_RC_INFERENCE (1<<27) #define ZEND_CFG_SPLIT_AT_LIVE_RANGES (1<<26) +#define ZEND_CFG_NO_ENTRY_PREDECESSORS (1<<25) #define CRT_CONSTANT_EX(op_array, node, rt_constants) \ ((rt_constants) ? \ diff --git a/ext/opcache/Optimizer/zend_dfg.c b/ext/opcache/Optimizer/zend_dfg.c index e72afd1701..97fc846d3c 100644 --- a/ext/opcache/Optimizer/zend_dfg.c +++ b/ext/opcache/Optimizer/zend_dfg.c @@ -25,43 +25,45 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg int set_size; zend_basic_block *blocks = cfg->blocks; int blocks_count = cfg->blocks_count; - zend_bitset tmp, gen, def, use, in, out; - zend_op *opline; - uint32_t k; + zend_bitset tmp, def, use, in, out; + uint32_t k, var_num; int j; - /* FIXME: can we use "gen" instead of "def" for flow analyzing? */ set_size = dfg->size; tmp = dfg->tmp; - gen = dfg->gen; def = dfg->def; use = dfg->use; in = dfg->in; out = dfg->out; - /* Collect "gen", "def" and "use" sets */ + /* Collect "def" and "use" sets */ for (j = 0; j < blocks_count; j++) { + zend_op *opline, *end; if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } - for (k = blocks[j].start; k <= blocks[j].end; k++) { - opline = op_array->opcodes + k; + + opline = op_array->opcodes + blocks[j].start; + end = opline + blocks[j].len; + for (; opline < end; opline++) { if (opline->opcode != ZEND_OP_DATA) { zend_op *next = opline + 1; - if (k < blocks[j].end && - next->opcode == ZEND_OP_DATA) { + if (next < end && next->opcode == ZEND_OP_DATA) { if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { - if (!DFG_ISSET(def, set_size, j, EX_VAR_TO_NUM(next->op1.var))) { - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(next->op1.var)); + var_num = EX_VAR_TO_NUM(next->op1.var); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); } } if (next->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { - if (!DFG_ISSET(def, set_size, j,EX_VAR_TO_NUM(next->op2.var))) { - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(next->op2.var)); + var_num = EX_VAR_TO_NUM(next->op2.var); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); } } } if (opline->op1_type == IS_CV) { + var_num = EX_VAR_TO_NUM(opline->op1.var); switch (opline->opcode) { case ZEND_ADD_ARRAY_ELEMENT: case ZEND_INIT_ARRAY: @@ -81,6 +83,9 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg goto op1_def; } goto op1_use; + case ZEND_UNSET_VAR: + ZEND_ASSERT(opline->extended_value & ZEND_QUICK_SET); + /* break missing intentionally */ case ZEND_ASSIGN: case ZEND_ASSIGN_REF: case ZEND_BIND_GLOBAL: @@ -88,18 +93,8 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg case ZEND_SEND_VAR_EX: case ZEND_SEND_REF: case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: case ZEND_FE_RESET_RW: -op1_def: - if (!DFG_ISSET(use, set_size, j, EX_VAR_TO_NUM(opline->op1.var))) { - // FIXME: include into "use" to ...? - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op1.var)); - DFG_SET(def, set_size, j, EX_VAR_TO_NUM(opline->op1.var)); - } - DFG_SET(gen, set_size, j, EX_VAR_TO_NUM(opline->op1.var)); - break; - case ZEND_UNSET_VAR: - ZEND_ASSERT(opline->extended_value & ZEND_QUICK_SET); - /* break missing intentionally */ case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: @@ -128,19 +123,30 @@ op1_def: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_OBJ_UNSET: - DFG_SET(gen, set_size, j, EX_VAR_TO_NUM(opline->op1.var)); + case ZEND_VERIFY_RETURN_TYPE: +op1_def: + /* `def` always come along with dtor or separation, + * thus the origin var info might be also `use`d in the feature(CG) */ + DFG_SET(use, set_size, j, var_num); + DFG_SET(def, set_size, j, var_num); + break; default: op1_use: - if (!DFG_ISSET(def, set_size, j, EX_VAR_TO_NUM(opline->op1.var))) { - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op1.var)); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); } } } else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { - if (!DFG_ISSET(def, set_size, j, EX_VAR_TO_NUM(opline->op1.var))) { - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op1.var)); + var_num = EX_VAR_TO_NUM(opline->op1.var); + if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) { + DFG_SET(use, set_size, j, var_num); + DFG_SET(def, set_size, j, var_num); + } else if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); } } if (opline->op2_type == IS_CV) { + var_num = EX_VAR_TO_NUM(opline->op2.var); switch (opline->opcode) { case ZEND_ASSIGN: if (build_flags & ZEND_SSA_RC_INFERENCE) { @@ -156,42 +162,30 @@ op1_use: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: op2_def: - if (!DFG_ISSET(use, set_size, j, EX_VAR_TO_NUM(opline->op2.var))) { - // FIXME: include into "use" to ...? - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); - DFG_SET(def, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); - } - DFG_SET(gen, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); + // FIXME: include into "use" too ...? + DFG_SET(use, set_size, j, var_num); + DFG_SET(def, set_size, j, var_num); break; default: op2_use: - if (!DFG_ISSET(def, set_size, j, EX_VAR_TO_NUM(opline->op2.var))) { - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); } break; } } else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(opline->op2.var); if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) { - if (!DFG_ISSET(use, set_size, j, EX_VAR_TO_NUM(opline->op2.var))) { - DFG_SET(def, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); - } - DFG_SET(gen, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); + DFG_SET(def, set_size, j, var_num); } else { - if (!DFG_ISSET(def, set_size, j, EX_VAR_TO_NUM(opline->op2.var))) { - DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op2.var)); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); } } } - if (opline->result_type == IS_CV) { - if (!DFG_ISSET(use, set_size, j, EX_VAR_TO_NUM(opline->result.var))) { - DFG_SET(def, set_size, j, EX_VAR_TO_NUM(opline->result.var)); - } - DFG_SET(gen, set_size, j, EX_VAR_TO_NUM(opline->result.var)); - } else if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { - if (!DFG_ISSET(use, set_size, j, EX_VAR_TO_NUM(opline->result.var))) { - DFG_SET(def, set_size, j, EX_VAR_TO_NUM(opline->result.var)); - } - DFG_SET(gen, set_size, j, EX_VAR_TO_NUM(opline->result.var)); + if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(opline->result.var); + DFG_SET(def, set_size, j, var_num); } } } @@ -200,8 +194,9 @@ op2_use: /* Calculate "in" and "out" sets */ { uint32_t worklist_len = zend_bitset_len(blocks_count); + zend_bitset worklist; ALLOCA_FLAG(use_heap); - zend_bitset worklist = ZEND_BITSET_ALLOCA(worklist_len, use_heap); + worklist = ZEND_BITSET_ALLOCA(worklist_len, use_heap); memset(worklist, 0, worklist_len * ZEND_BITSET_ELM_SIZE); for (j = 0; j < blocks_count; j++) { zend_bitset_incl(worklist, j); diff --git a/ext/opcache/Optimizer/zend_dfg.h b/ext/opcache/Optimizer/zend_dfg.h index 9d864992ca..5ed8cfc5d0 100644 --- a/ext/opcache/Optimizer/zend_dfg.h +++ b/ext/opcache/Optimizer/zend_dfg.h @@ -26,7 +26,6 @@ typedef struct _zend_dfg { int vars; uint32_t size; zend_bitset tmp; - zend_bitset gen; zend_bitset def; zend_bitset use; zend_bitset in; diff --git a/ext/opcache/Optimizer/zend_dump.c b/ext/opcache/Optimizer/zend_dump.c index ba5fb8a0ce..a2eeb886ee 100644 --- a/ext/opcache/Optimizer/zend_dump.c +++ b/ext/opcache/Optimizer/zend_dump.c @@ -92,7 +92,7 @@ static void zend_dump_unused_op(const zend_op *opline, znode_op op, uint32_t fla if (ZEND_VM_OP_NUM == (flags & ZEND_VM_OP_MASK)) { fprintf(stderr, " %u", op.num); } else if (ZEND_VM_OP_TRY_CATCH == (flags & ZEND_VM_OP_MASK)) { - if (opline->opcode != ZEND_FAST_RET || opline->extended_value) { + if (op.num != (uint32_t)-1) { fprintf(stderr, " try-catch(%u)", op.num); } } else if (ZEND_VM_OP_LIVE_RANGE == (flags & ZEND_VM_OP_MASK)) { @@ -332,14 +332,14 @@ static void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa } } -static void zend_dump_pi_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_pi_constraint *r, uint32_t dump_flags) +static void zend_dump_type_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_type_constraint *constraint, uint32_t dump_flags) { - if (r->type_mask != (uint32_t) -1) { - fprintf(stderr, " TYPE"); - zend_dump_type_info(r->type_mask, NULL, 0, dump_flags); - return; - } + fprintf(stderr, " TYPE"); + zend_dump_type_info(constraint->type_mask, constraint->ce, 1, dump_flags); +} +static void zend_dump_range_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_range_constraint *r, uint32_t dump_flags) +{ if (r->range.underflow && r->range.overflow) { return; } @@ -399,9 +399,8 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block * if (!ssa || !ssa->ops || ssa->ops[opline - op_array->opcodes].result_use < 0) { if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { - if (ssa && ssa->ops) { + if (ssa && ssa->ops && ssa->ops[opline - op_array->opcodes].result_def >= 0) { int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_def; - ZEND_ASSERT(ssa_var_num >= 0); zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags); } else { zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var)); @@ -499,35 +498,12 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block * fprintf(stderr, " (\?\?\?)"); break; } - } else if (ZEND_VM_EXT_FAST_CALL == (flags & ZEND_VM_EXT_MASK)) { - if (opline->extended_value == ZEND_FAST_CALL_FROM_FINALLY) { - fprintf(stderr, " (from-finally)"); - } - } else if (ZEND_VM_EXT_FAST_RET == (flags & ZEND_VM_EXT_MASK)) { - if (opline->extended_value == ZEND_FAST_RET_TO_CATCH) { - fprintf(stderr, " (to-catch)"); - } else if (opline->extended_value == ZEND_FAST_RET_TO_FINALLY) { - fprintf(stderr, " (to-finally)"); - } } else if (ZEND_VM_EXT_SRC == (flags & ZEND_VM_EXT_MASK)) { if (opline->extended_value == ZEND_RETURNS_VALUE) { fprintf(stderr, " (value)"); } else if (opline->extended_value == ZEND_RETURNS_FUNCTION) { fprintf(stderr, " (function)"); } - } else if (ZEND_VM_EXT_SEND == (flags & ZEND_VM_EXT_MASK)) { - if (opline->extended_value & ZEND_ARG_SEND_BY_REF) { - fprintf(stderr, " (ref)"); - } - if (opline->extended_value & ZEND_ARG_COMPILE_TIME_BOUND) { - fprintf(stderr, " (compile-time)"); - } - if (opline->extended_value & ZEND_ARG_SEND_FUNCTION) { - fprintf(stderr, " (function)"); - } - if (opline->extended_value & ZEND_ARG_SEND_SILENT) { - fprintf(stderr, " (silent)"); - } } else { if (ZEND_VM_EXT_VAR_FETCH & flags) { switch (opline->extended_value & ZEND_FETCH_TYPE_MASK) { @@ -723,7 +699,11 @@ static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags if (b->flags & ZEND_BB_IRREDUCIBLE_LOOP) { fprintf(stderr, " irreducible"); } - fprintf(stderr, " lines=[%d-%d]", b->start, b->end); + if (b->len != 0) { + fprintf(stderr, " lines=[%d-%d]", b->start, b->start + b->len - 1); + } else { + fprintf(stderr, " empty"); + } fprintf(stderr, "\n"); if (b->predecessors_count) { @@ -793,7 +773,11 @@ static void zend_dump_block_header(const zend_cfg *cfg, const zend_op_array *op_ fprintf(stderr, " = Pi<BB%d>(", p->pi); zend_dump_ssa_var(op_array, ssa, p->sources[0], 0, p->var, dump_flags); fprintf(stderr, " &"); - zend_dump_pi_constraint(op_array, ssa, &p->constraint, dump_flags); + if (p->has_range_constraint) { + zend_dump_range_constraint(op_array, ssa, &p->constraint.range, dump_flags); + } else { + zend_dump_type_constraint(op_array, ssa, &p->constraint.type, dump_flags); + } fprintf(stderr, ")\n"); } p = p->next; @@ -937,13 +921,11 @@ void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, cons const zend_op *end; zend_dump_block_header(cfg, op_array, ssa, n, dump_flags); - if (!(b->flags & ZEND_BB_EMPTY)) { - opline = op_array->opcodes + b->start; - end = op_array->opcodes + b->end + 1; - while (opline < end) { - zend_dump_op(op_array, b, opline, dump_flags, data); - opline++; - } + opline = op_array->opcodes + b->start; + end = opline + b->len; + while (opline < end) { + zend_dump_op(op_array, b, opline, dump_flags, data); + opline++; } } } @@ -1143,7 +1125,6 @@ void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zen for (j = 0; j < cfg->blocks_count; j++) { fprintf(stderr, " BB%d:\n", j); - zend_dump_var_set(op_array, "gen", DFG_BITSET(dfg->gen, dfg->size, j)); zend_dump_var_set(op_array, "def", DFG_BITSET(dfg->def, dfg->size, j)); zend_dump_var_set(op_array, "use", DFG_BITSET(dfg->use, dfg->size, j)); zend_dump_var_set(op_array, "in ", DFG_BITSET(dfg->in, dfg->size, j)); diff --git a/ext/opcache/Optimizer/zend_func_info.c b/ext/opcache/Optimizer/zend_func_info.c index d1b6e760d0..9a1a9e25d4 100644 --- a/ext/opcache/Optimizer/zend_func_info.c +++ b/ext/opcache/Optimizer/zend_func_info.c @@ -229,7 +229,7 @@ static const func_info_t func_infos[] = { FC("strncmp", zend_lb_ssn_info), FC("strcasecmp", zend_l_ss_info), FC("strncasecmp", zend_lb_ssn_info), - F1("each", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("each", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_KEY_ANY), F1("error_reporting", MAY_BE_NULL | MAY_BE_LONG), F1("define", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_NULL), // TODO: inline FC("defined", zend_b_s_info), // TODO: inline @@ -251,8 +251,8 @@ static const func_info_t func_infos[] = { F1("get_included_files", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), F1("trigger_error", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("user_error", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), - F1("set_error_handler", MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT), - I1("restore_error_handler", MAY_BE_TRUE), + F1("set_error_handler", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT), + I1("restore_error_handler", MAY_BE_NULL | MAY_BE_TRUE), F1("get_declared_traits", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), F1("get_declared_classes", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), F1("get_declared_interfaces", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), @@ -330,7 +330,7 @@ static const func_info_t func_infos[] = { F1("stristr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("strrchr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("str_shuffle", MAY_BE_NULL | MAY_BE_STRING), - F1("str_word_count", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("str_word_count", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), F1("str_split", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), F1("strpbrk", MAY_BE_FALSE | MAY_BE_STRING), F1("substr_compare", MAY_BE_FALSE | MAY_BE_LONG), @@ -382,9 +382,9 @@ static const func_info_t func_infos[] = { F1("vsprintf", MAY_BE_FALSE | MAY_BE_STRING), F1("fprintf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), F1("vfprintf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), - F1("sscanf", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), - F1("fscanf", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), - F1("parse_url", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_LONG), + F1("sscanf", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), + F1("fscanf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), + F1("parse_url", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_LONG), F1("urlencode", MAY_BE_NULL | MAY_BE_STRING), F1("urldecode", MAY_BE_NULL | MAY_BE_STRING), F1("rawurlencode", MAY_BE_NULL | MAY_BE_STRING), @@ -491,11 +491,11 @@ static const func_info_t func_infos[] = { #endif F1("ip2long", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), F1("long2ip", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), - F1("getenv", MAY_BE_FALSE | MAY_BE_STRING), + F1("getenv", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), #ifdef HAVE_PUTENV F1("putenv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), #endif - F1("getopt", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("getopt", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), #ifdef HAVE_GETLOADAVG F1("sys_getloadavg", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_DOUBLE), #endif @@ -529,7 +529,7 @@ static const func_info_t func_infos[] = { F1("forward_static_call", UNKNOWN_INFO), F1("forward_static_call_array", UNKNOWN_INFO), F1("serialize", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), - FX("unserialize", UNKNOWN_INFO), + FN("unserialize", UNKNOWN_INFO), F1("var_dump", MAY_BE_NULL), F1("var_export", MAY_BE_NULL | MAY_BE_STRING), F1("debug_zval_dump", MAY_BE_NULL), @@ -539,12 +539,12 @@ static const func_info_t func_infos[] = { F1("register_shutdown_function", MAY_BE_NULL | MAY_BE_FALSE), F1("register_tick_function", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("unregister_tick_function", MAY_BE_NULL), - F1("highlight_file", MAY_BE_FALSE | MAY_BE_STRING), + F1("highlight_file", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), F1("show_source", MAY_BE_FALSE | MAY_BE_STRING), - F1("highlight_string", MAY_BE_FALSE | MAY_BE_STRING), + F1("highlight_string", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), F1("php_strip_whitespace", MAY_BE_FALSE | MAY_BE_STRING), F1("ini_get", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), - F1("ini_get_all", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("ini_get_all", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), F1("ini_set", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("ini_alter", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("ini_restore", MAY_BE_NULL), @@ -561,8 +561,8 @@ static const func_info_t func_infos[] = { F1("connection_aborted", MAY_BE_LONG), F1("connection_status", MAY_BE_LONG), F1("ignore_user_abort", MAY_BE_NULL | MAY_BE_LONG), - F1("parse_ini_file", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), - F1("parse_ini_string", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("parse_ini_file", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("parse_ini_string", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), #if ZEND_DEBUG F1("config_get_hash", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), #endif @@ -636,7 +636,7 @@ static const func_info_t func_infos[] = { F1("file_put_contents", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), F1("stream_select", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), F1("stream_context_create", MAY_BE_FALSE | MAY_BE_RESOURCE), - F1("stream_context_set_params", MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("stream_context_set_params", MAY_BE_FALSE | MAY_BE_TRUE), F1("stream_context_get_params", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), F1("stream_context_set_option", MAY_BE_FALSE | MAY_BE_TRUE), F1("stream_context_get_options", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), @@ -661,7 +661,7 @@ static const func_info_t func_infos[] = { F1("stream_copy_to_stream", MAY_BE_FALSE | MAY_BE_LONG), F1("stream_get_contents", MAY_BE_FALSE | MAY_BE_STRING), F1("stream_supports_lock", MAY_BE_FALSE | MAY_BE_TRUE), - F1("fgetcsv", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING), + F1("fgetcsv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING), F1("fputcsv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), F1("flock", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("get_meta_tags", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), @@ -672,7 +672,7 @@ static const func_info_t func_infos[] = { F1("set_socket_blocking", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("stream_set_blocking", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("socket_set_blocking", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), - F1("stream_get_meta_data", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F1("stream_get_meta_data", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), F1("stream_get_line", MAY_BE_FALSE | MAY_BE_STRING), F1("stream_wrapper_register", MAY_BE_FALSE | MAY_BE_TRUE), F1("stream_register_wrapper", MAY_BE_FALSE | MAY_BE_TRUE), @@ -699,9 +699,7 @@ static const func_info_t func_infos[] = { F1("pack", MAY_BE_FALSE | MAY_BE_STRING), F1("unpack", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), F1("get_browser", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), -#if HAVE_CRYPT F1("crypt", MAY_BE_NULL | MAY_BE_STRING), -#endif F1("opendir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), F1("closedir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("chdir", MAY_BE_FALSE | MAY_BE_TRUE), @@ -710,21 +708,21 @@ static const func_info_t func_infos[] = { #endif F1("getcwd", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("rewinddir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), - F1("readdir", MAY_BE_FALSE | MAY_BE_STRING), + F1("readdir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("dir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT), F1("scandir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), #ifdef HAVE_GLOB F1("glob", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), #endif - F1("fileatime", MAY_BE_NULL | MAY_BE_LONG), - F1("filectime", MAY_BE_NULL | MAY_BE_LONG), - F1("filegroup", MAY_BE_NULL | MAY_BE_LONG), - F1("fileinode", MAY_BE_NULL | MAY_BE_LONG), - F1("filemtime", MAY_BE_NULL | MAY_BE_LONG), - F1("fileowner", MAY_BE_NULL | MAY_BE_LONG), - F1("fileperms", MAY_BE_NULL | MAY_BE_LONG), - F1("filesize", MAY_BE_NULL | MAY_BE_LONG), - F1("filetype", MAY_BE_NULL | MAY_BE_STRING), + F1("fileatime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("filectime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("filegroup", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("fileinode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("filemtime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("fileowner", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("fileperms", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("filesize", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("filetype", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), F1("file_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("is_writable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("is_writeable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), @@ -733,8 +731,8 @@ static const func_info_t func_infos[] = { F1("is_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("is_dir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("is_link", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), - F1("stat", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), - F1("lstat", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("stat", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("lstat", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), #ifndef NETWARE F1("chown", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("chgrp", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), @@ -850,7 +848,7 @@ static const func_info_t func_infos[] = { F1("array_filter", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), F1("array_map", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), F1("array_chunk", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), - F1("array_combine", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_combine", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), F1("array_key_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), F1("pos", UNKNOWN_INFO), F1("sizeof", MAY_BE_NULL | MAY_BE_LONG), @@ -906,7 +904,7 @@ static const func_info_t func_infos[] = { F1("date_date_set", MAY_BE_FALSE | MAY_BE_OBJECT), F1("date_isodate_set", MAY_BE_FALSE | MAY_BE_OBJECT), F1("date_timestamp_set", MAY_BE_FALSE | MAY_BE_OBJECT), - F1("date_timestamp_get", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_timestamp_get", MAY_BE_FALSE | MAY_BE_LONG), F1("timezone_open", MAY_BE_FALSE | MAY_BE_OBJECT), F1("timezone_name_get", MAY_BE_FALSE | MAY_BE_STRING), F1("timezone_name_from_abbr", MAY_BE_FALSE | MAY_BE_STRING), diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index a1e2335fa9..ee14e49b5c 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -158,9 +158,9 @@ int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ int index = 0, *dfs, *root; zend_worklist_stack stack; int j; - ALLOCA_FLAG(dfs_use_heap); - ALLOCA_FLAG(root_use_heap); - ALLOCA_FLAG(stack_use_heap); + ALLOCA_FLAG(dfs_use_heap) + ALLOCA_FLAG(root_use_heap) + ALLOCA_FLAG(stack_use_heap) dfs = do_alloca(sizeof(int) * ssa->vars_count, dfs_use_heap); memset(dfs, -1, sizeof(int) * ssa->vars_count); @@ -199,6 +199,18 @@ int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ } /* }}} */ +static inline zend_bool is_no_val_use(const zend_op *opline, const zend_ssa_op *ssa_op, int var) +{ + if (opline->opcode == ZEND_ASSIGN || + (opline->opcode == ZEND_UNSET_VAR && (opline->extended_value & ZEND_QUICK_SET))) { + return ssa_op->op1_use == var && ssa_op->op2_use != var; + } + if (opline->opcode == ZEND_FE_FETCH_R) { + return ssa_op->op2_use == var && ssa_op->op1_use != var; + } + return 0; +} + int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ { zend_ssa_var *ssa_vars = ssa->vars; @@ -220,11 +232,10 @@ int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ss ssa_vars[i].no_val = 1; /* mark as unused */ use = ssa->vars[i].use_chain; while (use >= 0) { - if (op_array->opcodes[use].opcode != ZEND_ASSIGN || - ssa->ops[use].op1_use != i || - ssa->ops[use].op2_use == i) { + if (!is_no_val_use(&op_array->opcodes[use], &ssa->ops[use], i)) { ssa_vars[i].no_val = 0; /* used directly */ zend_bitset_incl(worklist, i); + break; } use = zend_ssa_next_use(ssa_ops, i, use); } @@ -475,8 +486,9 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int tmp->min = ZEND_LONG_MAX; tmp->max = ZEND_LONG_MIN; tmp->overflow = 0; - if (p->pi >= 0 && p->constraint.type_mask == (uint32_t) -1) { - if (p->constraint.negative) { + if (p->pi >= 0 && p->has_range_constraint) { + zend_ssa_range_constraint *constraint = &p->constraint.range; + if (constraint->negative) { if (ssa->var_info[p->sources[0]].has_range) { tmp->underflow = ssa->var_info[p->sources[0]].range.underflow; tmp->min = ssa->var_info[p->sources[0]].range.min; @@ -489,8 +501,8 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int tmp->overflow = 1; } #ifdef NEG_RANGE - if (p->constraint.min_ssa_var < 0 && - p->constraint.min_ssa_var < 0 && + if (constraint->min_ssa_var < 0 && + constraint->min_ssa_var < 0 && ssa->var_info[p->ssa_var].has_range) { #ifdef LOG_NEG_RANGE fprintf(stderr, "%s() #%d [%ld..%ld] -> [%ld..%ld]?\n", @@ -501,19 +513,19 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int tmp->min, tmp->max); #endif - if (p->constraint.negative == NEG_USE_LT && - tmp->max >= p->constraint.range.min) { + if (constraint->negative == NEG_USE_LT && + tmp->max >= constraint->range.min) { tmp->overflow = 0; - tmp->max = p->constraint.range.min - 1; + tmp->max = constraint->range.min - 1; #ifdef LOG_NEG_RANGE fprintf(stderr, " => [%ld..%ld]\n", tmp->min, tmp->max); #endif - } else if (p->constraint.negative == NEG_USE_GT && - tmp->min <= p->constraint.range.max) { + } else if (constraint->negative == NEG_USE_GT && + tmp->min <= constraint->range.max) { tmp->underflow = 0; - tmp->min = p->constraint.range.max + 1; + tmp->min = constraint->range.max + 1; #ifdef LOG_NEG_RANGE fprintf(stderr, " => [%ld..%ld]\n", tmp->min, @@ -528,44 +540,44 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int tmp->min = ssa->var_info[p->sources[0]].range.min; tmp->max = ssa->var_info[p->sources[0]].range.max; tmp->overflow = ssa->var_info[p->sources[0]].range.overflow; - if (p->constraint.min_ssa_var < 0) { - tmp->underflow = p->constraint.range.underflow && tmp->underflow; - tmp->min = MAX(p->constraint.range.min, tmp->min); + if (constraint->min_ssa_var < 0) { + tmp->underflow = constraint->range.underflow && tmp->underflow; + tmp->min = MAX(constraint->range.min, tmp->min); #ifdef SYM_RANGE - } else if (narrowing && ssa->var_info[p->constraint.min_ssa_var].has_range) { - tmp->underflow = ssa->var_info[p->constraint.min_ssa_var].range.underflow && tmp->underflow; - tmp->min = MAX(ssa->var_info[p->constraint.min_ssa_var].range.min + p->constraint.range.min, tmp->min); + } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) { + tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow && tmp->underflow; + tmp->min = MAX(ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min, tmp->min); #endif } - if (p->constraint.max_ssa_var < 0) { - tmp->max = MIN(p->constraint.range.max, tmp->max); - tmp->overflow = p->constraint.range.overflow && tmp->overflow; + if (constraint->max_ssa_var < 0) { + tmp->max = MIN(constraint->range.max, tmp->max); + tmp->overflow = constraint->range.overflow && tmp->overflow; #ifdef SYM_RANGE - } else if (narrowing && ssa->var_info[p->constraint.max_ssa_var].has_range) { - tmp->max = MIN(ssa->var_info[p->constraint.max_ssa_var].range.max + p->constraint.range.max, tmp->max); - tmp->overflow = ssa->var_info[p->constraint.max_ssa_var].range.overflow && tmp->overflow; + } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) { + tmp->max = MIN(ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max, tmp->max); + tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow && tmp->overflow; #endif } } else if (narrowing) { - if (p->constraint.min_ssa_var < 0) { - tmp->underflow = p->constraint.range.underflow; - tmp->min = p->constraint.range.min; + if (constraint->min_ssa_var < 0) { + tmp->underflow = constraint->range.underflow; + tmp->min = constraint->range.min; #ifdef SYM_RANGE - } else if (narrowing && ssa->var_info[p->constraint.min_ssa_var].has_range) { - tmp->underflow = ssa->var_info[p->constraint.min_ssa_var].range.underflow; - tmp->min = ssa->var_info[p->constraint.min_ssa_var].range.min + p->constraint.range.min; + } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) { + tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow; + tmp->min = ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min; #endif } else { tmp->underflow = 1; tmp->min = ZEND_LONG_MIN; } - if (p->constraint.max_ssa_var < 0) { - tmp->max = p->constraint.range.max; - tmp->overflow = p->constraint.range.overflow; + if (constraint->max_ssa_var < 0) { + tmp->max = constraint->range.max; + tmp->overflow = constraint->range.overflow; #ifdef SYM_RANGE - } else if (narrowing && ssa->var_info[p->constraint.max_ssa_var].has_range) { - tmp->max = ssa->var_info[p->constraint.max_ssa_var].range.max + p->constraint.range.max; - tmp->overflow = ssa->var_info[p->constraint.max_ssa_var].range.overflow; + } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) { + tmp->max = ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max; + tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow; #endif } else { tmp->max = ZEND_LONG_MAX; @@ -591,7 +603,6 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int return (tmp->min <= tmp->max); } else if (ssa->vars[var].definition < 0) { if (var < op_array->last_var && - var != EX_VAR_TO_NUM(op_array->this_var) && op_array->function_name) { tmp->min = 0; @@ -1814,55 +1825,57 @@ static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ss ssa->var_info[j].has_range && ssa->vars[j].definition_phi && ssa->vars[j].definition_phi->pi >= 0 && - ssa->vars[j].definition_phi->constraint.type_mask == (uint32_t) -1 && - ssa->vars[j].definition_phi->constraint.negative && - ssa->vars[j].definition_phi->constraint.min_ssa_var < 0 && - ssa->vars[j].definition_phi->constraint.min_ssa_var < 0) { + ssa->vars[j].definition_phi->has_range_constraint && + ssa->vars[j].definition_phi->constraint.range.negative && + ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0 && + ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0) { + zend_ssa_range_constraint *constraint = + &ssa->vars[j].definition_phi->constraint.range; if (tmp.min == ssa->var_info[j].range.min && tmp.max == ssa->var_info[j].range.max) { - if (ssa->vars[j].definition_phi->constraint.negative == NEG_INIT) { + if (constraint->negative == NEG_INIT) { #ifdef LOG_NEG_RANGE fprintf(stderr, "#%d INVARIANT\n", j); #endif - ssa->vars[j].definition_phi->constraint.negative = NEG_INVARIANT; + constraint->negative = NEG_INVARIANT; } } else if (tmp.min == ssa->var_info[j].range.min && tmp.max == ssa->var_info[j].range.max + 1 && - tmp.max < ssa->vars[j].definition_phi->constraint.range.min) { - if (ssa->vars[j].definition_phi->constraint.negative == NEG_INIT || - ssa->vars[j].definition_phi->constraint.negative == NEG_INVARIANT) { + tmp.max < constraint->range.min) { + if (constraint->negative == NEG_INIT || + constraint->negative == NEG_INVARIANT) { #ifdef LOG_NEG_RANGE fprintf(stderr, "#%d LT\n", j); #endif - ssa->vars[j].definition_phi->constraint.negative = NEG_USE_LT; + constraint->negative = NEG_USE_LT; //???NEG - } else if (ssa->vars[j].definition_phi->constraint.negative == NEG_USE_GT) { + } else if (constraint->negative == NEG_USE_GT) { #ifdef LOG_NEG_RANGE fprintf(stderr, "#%d UNKNOWN\n", j); #endif - ssa->vars[j].definition_phi->constraint.negative = NEG_UNKNOWN; + constraint->negative = NEG_UNKNOWN; } } else if (tmp.max == ssa->var_info[j].range.max && tmp.min == ssa->var_info[j].range.min - 1 && - tmp.min > ssa->vars[j].definition_phi->constraint.range.max) { - if (ssa->vars[j].definition_phi->constraint.negative == NEG_INIT || - ssa->vars[j].definition_phi->constraint.negative == NEG_INVARIANT) { + tmp.min > constraint->range.max) { + if (constraint->negative == NEG_INIT || + constraint->negative == NEG_INVARIANT) { #ifdef LOG_NEG_RANGE fprintf(stderr, "#%d GT\n", j); #endif - ssa->vars[j].definition_phi->constraint.negative = NEG_USE_GT; + constraint->negative = NEG_USE_GT; //???NEG - } else if (ssa->vars[j].definition_phi->constraint.negative == NEG_USE_LT) { + } else if (constraint->negative == NEG_USE_LT) { #ifdef LOG_NEG_RANGE fprintf(stderr, "#%d UNKNOWN\n", j); #endif - ssa->vars[j].definition_phi->constraint.negative = NEG_UNKNOWN; + constraint->negative = NEG_UNKNOWN; } } else { #ifdef LOG_NEG_RANGE fprintf(stderr, "#%d UNKNOWN\n", j); #endif - ssa->vars[j].definition_phi->constraint.negative = NEG_UNKNOWN; + constraint->negative = NEG_UNKNOWN; } } #endif @@ -2006,16 +2019,26 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{ #define UPDATE_SSA_OBJ_TYPE(_ce, _is_instanceof, var) \ do { \ if (var >= 0) { \ - if (ssa_var_info[var].ce != _ce || \ - ssa_var_info[var].is_instanceof != _is_instanceof) { \ - ssa_var_info[var].ce = _ce; \ - ssa_var_info[var].is_instanceof = _is_instanceof; \ - add_usages(op_array, ssa, worklist, var); \ + if (ssa_var_info[var].ce != (_ce) || \ + ssa_var_info[var].is_instanceof != (_is_instanceof)) { \ + ssa_var_info[var].ce = (_ce); \ + ssa_var_info[var].is_instanceof = (_is_instanceof); \ + add_usages(op_array, ssa, worklist, var); \ } \ /*zend_bitset_excl(worklist, var);*/ \ } \ } while (0) +#define COPY_SSA_OBJ_TYPE(from_var, to_var) do { \ + if ((from_var) >= 0 && (ssa_var_info[(from_var)].type & MAY_BE_OBJECT) \ + && ssa_var_info[(from_var)].ce) { \ + UPDATE_SSA_OBJ_TYPE(ssa_var_info[(from_var)].ce, \ + ssa_var_info[(from_var)].is_instanceof, (to_var)); \ + } else { \ + UPDATE_SSA_OBJ_TYPE(NULL, 0, (to_var)); \ + } \ +} while (0) + static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var) { if (ssa->vars[var].phi_use_chain) { @@ -2027,25 +2050,29 @@ static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset } if (ssa->vars[var].use_chain >= 0) { int use = ssa->vars[var].use_chain; + zend_ssa_op *op; + do { - if (ssa->ops[use].result_def >= 0) { - zend_bitset_incl(worklist, ssa->ops[use].result_def); + op = ssa->ops + use; + if (op->result_def >= 0) { + zend_bitset_incl(worklist, op->result_def); } - if (ssa->ops[use].op1_def >= 0) { - zend_bitset_incl(worklist, ssa->ops[use].op1_def); + if (op->op1_def >= 0) { + zend_bitset_incl(worklist, op->op1_def); } - if (ssa->ops[use].op2_def >= 0) { - zend_bitset_incl(worklist, ssa->ops[use].op2_def); + if (op->op2_def >= 0) { + zend_bitset_incl(worklist, op->op2_def); } if (op_array->opcodes[use].opcode == ZEND_OP_DATA) { - if (ssa->ops[use-1].result_def >= 0) { - zend_bitset_incl(worklist, ssa->ops[use-1].result_def); + op--; + if (op->result_def >= 0) { + zend_bitset_incl(worklist, op->result_def); } - if (ssa->ops[use-1].op1_def >= 0) { - zend_bitset_incl(worklist, ssa->ops[use-1].op1_def); + if (op->op1_def >= 0) { + zend_bitset_incl(worklist, op->op1_def); } - if (ssa->ops[use-1].op2_def >= 0) { - zend_bitset_incl(worklist, ssa->ops[use-1].op2_def); + if (op->op2_def >= 0) { + zend_bitset_incl(worklist, op->op2_def); } } use = zend_ssa_next_use(ssa->ops, var, use); @@ -2175,138 +2202,248 @@ uint32_t zend_array_element_type(uint32_t t1, int write, int insert) return tmp; } -static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) { - zend_class_entry *ce = zend_hash_find_ptr(&script->class_table, lcname); - if (ce) { - return ce; - } - - ce = zend_hash_find_ptr(CG(class_table), lcname); - if (ce && ce->type == ZEND_INTERNAL_CLASS) { - return ce; +static uint32_t assign_dim_result_type( + uint32_t arr_type, uint32_t dim_type, uint32_t value_type, zend_uchar dim_op_type) { + uint32_t tmp = arr_type + & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF) + & ~(MAY_BE_NULL|MAY_BE_FALSE); + if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_STRING)) { + tmp |= MAY_BE_ARRAY; + } + if (arr_type & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RC1; + } + if (tmp & MAY_BE_ARRAY) { + tmp |= (value_type & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT; + if (value_type & MAY_BE_UNDEF) { + tmp |= MAY_BE_ARRAY_OF_NULL; + } + if (dim_op_type == IS_UNUSED) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } else { + if (dim_type & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + if (dim_type & MAY_BE_STRING) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + if (dim_op_type != IS_CONST) { + // FIXME: numeric string + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + } + if (dim_type & (MAY_BE_UNDEF|MAY_BE_NULL)) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + } + } } - - return NULL; + return tmp; } -static void zend_update_type_info(const zend_op_array *op_array, - zend_ssa *ssa, - const zend_script *script, - zend_bitset worklist, - int i) -{ - uint32_t t1, t2; - uint32_t tmp, orig; - zend_op *opline = op_array->opcodes + i; - zend_ssa_op *ssa_ops = ssa->ops; - zend_ssa_var *ssa_vars = ssa->vars; - zend_ssa_var_info *ssa_var_info = ssa->var_info; - zend_class_entry *ce; - int j; - - if (opline->opcode == ZEND_OP_DATA) { - opline--; - i--; - } - - t1 = OP1_INFO(); - t2 = OP2_INFO(); - - switch (opline->opcode) { +/* For binary ops that have compound assignment operators */ +static uint32_t binary_op_result_type( + zend_ssa *ssa, zend_uchar opcode, uint32_t t1, uint32_t t2, uint32_t result_var) { + uint32_t tmp; + uint32_t t1_type = (t1 & MAY_BE_ANY) | (t1 & MAY_BE_UNDEF ? MAY_BE_NULL : 0); + uint32_t t2_type = (t2 & MAY_BE_ANY) | (t2 & MAY_BE_UNDEF ? MAY_BE_NULL : 0); + switch (opcode) { case ZEND_ADD: tmp = MAY_BE_RC1; - if ((t1 & MAY_BE_ANY) == MAY_BE_LONG && - (t2 & MAY_BE_ANY) == MAY_BE_LONG) { - - if (!ssa_var_info[ssa_ops[i].result_def].has_range || - ssa_var_info[ssa_ops[i].result_def].range.underflow || - ssa_var_info[ssa_ops[i].result_def].range.overflow) { + if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) { + if (!ssa->var_info[result_var].has_range || + ssa->var_info[result_var].range.underflow || + ssa->var_info[result_var].range.overflow) { /* may overflow */ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; } else { tmp |= MAY_BE_LONG; } - } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE || - (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) { + } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) { tmp |= MAY_BE_DOUBLE; - } else if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY && - (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) { + } else if (t1_type == MAY_BE_ARRAY && t2_type == MAY_BE_ARRAY) { tmp |= MAY_BE_ARRAY; tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); } else { tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; - if ((t1 & MAY_BE_ARRAY) && (t2 & MAY_BE_ARRAY)) { + if ((t1_type & MAY_BE_ARRAY) && (t2_type & MAY_BE_ARRAY)) { tmp |= MAY_BE_ARRAY; tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); } } - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; case ZEND_SUB: case ZEND_MUL: tmp = MAY_BE_RC1; - if ((t1 & MAY_BE_ANY) == MAY_BE_LONG && - (t2 & MAY_BE_ANY) == MAY_BE_LONG) { - if (!ssa_var_info[ssa_ops[i].result_def].has_range || - ssa_var_info[ssa_ops[i].result_def].range.underflow || - ssa_var_info[ssa_ops[i].result_def].range.overflow) { + if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) { + if (!ssa->var_info[result_var].has_range || + ssa->var_info[result_var].range.underflow || + ssa->var_info[result_var].range.overflow) { /* may overflow */ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; } else { tmp |= MAY_BE_LONG; } - } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE || - (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) { + } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) { tmp |= MAY_BE_DOUBLE; } else { tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; } - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; case ZEND_DIV: case ZEND_POW: tmp = MAY_BE_RC1; - if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE || - (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) { + if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) { tmp |= MAY_BE_DOUBLE; } else { tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; } /* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special * handling */ - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; case ZEND_MOD: tmp = MAY_BE_RC1 | MAY_BE_LONG; /* Division by zero results in an exception, so it doesn't need any special handling */ - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; - case ZEND_BW_NOT: + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: tmp = MAY_BE_RC1; - if (t1 & MAY_BE_STRING) { + if ((t1_type & MAY_BE_STRING) && (t2_type & MAY_BE_STRING)) { tmp |= MAY_BE_STRING; } - if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) { + if ((t1_type & ~MAY_BE_STRING) || (t2_type & ~MAY_BE_STRING)) { tmp |= MAY_BE_LONG; } - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; + case ZEND_SL: + case ZEND_SR: + tmp = MAY_BE_RC1|MAY_BE_LONG; + break; + case ZEND_CONCAT: + /* TODO: +MAY_BE_OBJECT ??? */ + tmp = MAY_BE_RC1|MAY_BE_STRING; + break; + EMPTY_SWITCH_DEFAULT_CASE() + } + return tmp; +} + +/* Get the normal op corresponding to a compound assignment op */ +static inline zend_uchar get_compound_assign_op(zend_uchar opcode) { + switch (opcode) { + case ZEND_ASSIGN_ADD: return ZEND_ADD; + case ZEND_ASSIGN_SUB: return ZEND_SUB; + case ZEND_ASSIGN_MUL: return ZEND_MUL; + case ZEND_ASSIGN_DIV: return ZEND_DIV; + case ZEND_ASSIGN_MOD: return ZEND_MOD; + case ZEND_ASSIGN_SL: return ZEND_SL; + case ZEND_ASSIGN_SR: return ZEND_SR; + case ZEND_ASSIGN_CONCAT: return ZEND_CONCAT; + case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR; + case ZEND_ASSIGN_BW_AND: return ZEND_BW_AND; + case ZEND_ASSIGN_BW_XOR: return ZEND_BW_XOR; + case ZEND_ASSIGN_POW: return ZEND_POW; + EMPTY_SWITCH_DEFAULT_CASE() + } +} + +static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) { + zend_class_entry *ce = zend_hash_find_ptr(&script->class_table, lcname); + if (ce) { + return ce; + } + + ce = zend_hash_find_ptr(CG(class_table), lcname); + if (ce && ce->type == ZEND_INTERNAL_CLASS) { + return ce; + } + + 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, + zend_bitset worklist, + int i) +{ + uint32_t t1, t2; + uint32_t tmp, orig; + zend_op *opline = op_array->opcodes + i; + zend_ssa_op *ssa_ops = ssa->ops; + zend_ssa_var *ssa_vars = ssa->vars; + zend_ssa_var_info *ssa_var_info = ssa->var_info; + zend_class_entry *ce; + int j; + + if (opline->opcode == ZEND_OP_DATA) { + opline--; + i--; + } + + t1 = OP1_INFO(); + t2 = OP2_INFO(); + + switch (opline->opcode) { + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + case ZEND_DIV: + case ZEND_POW: + case ZEND_MOD: case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: + case ZEND_SL: + case ZEND_SR: + case ZEND_CONCAT: + tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_ops[i].result_def); + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_BW_NOT: tmp = MAY_BE_RC1; - if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) { + if (t1 & MAY_BE_STRING) { tmp |= MAY_BE_STRING; } - if ((t1 & (MAY_BE_ANY-MAY_BE_STRING)) || (t1 & (MAY_BE_ANY-MAY_BE_STRING))) { + if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) { tmp |= MAY_BE_LONG; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; - case ZEND_SL: - case ZEND_SR: case ZEND_BEGIN_SILENCE: UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_LONG, ssa_ops[i].result_def); break; @@ -2344,7 +2481,7 @@ static void zend_update_type_info(const zend_op_array *op_array, if (t1 & MAY_BE_OBJECT) { tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; } else { - tmp |= (t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT; + tmp |= ((t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT) | MAY_BE_ARRAY_KEY_LONG; } } UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); @@ -2368,328 +2505,29 @@ static void zend_update_type_info(const zend_op_array *op_array, } } UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def); break; case ZEND_ASSIGN_ADD: - orig = 0; - if (opline->extended_value == ZEND_ASSIGN_OBJ) { - tmp = MAY_BE_RC1; - orig = t1; - t1 = MAY_BE_ANY; - t2 = OP1_DATA_INFO(); - } else if (opline->extended_value == ZEND_ASSIGN_DIM) { - tmp = MAY_BE_RC1; - orig = t1; - t1 = zend_array_element_type(t1, 1, 0); - t2 = OP1_DATA_INFO(); - } else { - tmp = 0; - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - if (ssa_ops[i].result_def >= 0) { - tmp |= MAY_BE_RCN; - } - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - } - if ((t1 & MAY_BE_ANY) == MAY_BE_LONG && - (t2 & MAY_BE_ANY) == MAY_BE_LONG) { - - if (!ssa_var_info[ssa_ops[i].op1_def].has_range || - ssa_var_info[ssa_ops[i].op1_def].range.underflow || - ssa_var_info[ssa_ops[i].op1_def].range.overflow) { - /* may overflow */ - tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; - } else { - tmp |= MAY_BE_LONG; - } - } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE || - (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) { - tmp |= MAY_BE_DOUBLE; - } else if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY && - (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) { - tmp |= MAY_BE_ARRAY; - tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); - tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); - } else { - tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; - if ((t1 & MAY_BE_ARRAY) && (t2 & MAY_BE_ARRAY)) { - tmp |= MAY_BE_ARRAY; - tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); - tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); - } - } - if (opline->extended_value == ZEND_ASSIGN_DIM) { - if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; - } - UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); - if ((orig & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } - } - } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { - if (opline->op1_type == IS_CV) { - if (orig & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING)) { - orig |= MAY_BE_OBJECT; - } - if (orig & MAY_BE_RCN) { - orig |= MAY_BE_RC1; - } - UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); - if ((orig & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } - } - } else { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - } - if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - } - break; case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: - if (opline->extended_value == ZEND_ASSIGN_OBJ) { - goto unknown_opcode; - } else if (opline->extended_value == ZEND_ASSIGN_DIM) { - tmp = MAY_BE_RC1; - orig = t1; - t1 = zend_array_element_type(t1, 1, 0); - t2 = OP1_DATA_INFO(); - } else { - tmp = 0; - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - if (ssa_ops[i].result_def >= 0) { - tmp |= MAY_BE_RCN; - } - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - } - if ((t1 & MAY_BE_ANY) == MAY_BE_LONG && - (t2 & MAY_BE_ANY) == MAY_BE_LONG) { - if (!ssa_var_info[ssa_ops[i].op1_def].has_range || - ssa_var_info[ssa_ops[i].op1_def].range.underflow || - ssa_var_info[ssa_ops[i].op1_def].range.overflow) { - /* may overflow */ - tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; - } else { - tmp |= MAY_BE_LONG; - } - } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE || - (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) { - tmp |= MAY_BE_DOUBLE; - } else { - tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; - } - if (opline->extended_value == ZEND_ASSIGN_DIM) { - if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; - } - UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); - } - } else { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - } - if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - } - break; case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_POW: - if (opline->extended_value == ZEND_ASSIGN_OBJ) { - goto unknown_opcode; - } else if (opline->extended_value == ZEND_ASSIGN_DIM) { - tmp = MAY_BE_RC1; - orig = t1; - t1 = zend_array_element_type(t1, 1, 0); - t2 = OP1_DATA_INFO(); - } else { - tmp = 0; - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - if (ssa_ops[i].result_def >= 0) { - tmp |= MAY_BE_RCN; - } - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - } - if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE || - (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) { - tmp |= MAY_BE_DOUBLE; - } else { - tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; - } - /* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special - * handling */ - if (opline->extended_value == ZEND_ASSIGN_DIM) { - if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; - } - UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); - } - } else { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - } - if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - } - break; case ZEND_ASSIGN_MOD: - if (opline->extended_value == ZEND_ASSIGN_OBJ) { - goto unknown_opcode; - } else if (opline->extended_value == ZEND_ASSIGN_DIM) { - tmp = MAY_BE_RC1; - orig = t1; - t1 = zend_array_element_type(t1, 1, 0); - t2 = OP1_DATA_INFO(); - } else { - tmp = 0; - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - if (ssa_ops[i].result_def >= 0) { - tmp |= MAY_BE_RCN; - } - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - } - tmp |= MAY_BE_LONG; - if (opline->extended_value == ZEND_ASSIGN_DIM) { - if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; - } - UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); - } - } else { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - } - if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - } - break; case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: + case ZEND_ASSIGN_BW_OR: + case ZEND_ASSIGN_BW_AND: + case ZEND_ASSIGN_BW_XOR: + case ZEND_ASSIGN_CONCAT: + orig = 0; if (opline->extended_value == ZEND_ASSIGN_OBJ) { - goto unknown_opcode; - } else if (opline->extended_value == ZEND_ASSIGN_DIM) { tmp = MAY_BE_RC1; - orig = t1; - t1 = zend_array_element_type(t1, 1, 0); + orig = t1 & ~MAY_BE_UNDEF; + t1 = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; t2 = OP1_DATA_INFO(); - } else { - tmp = 0; - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - if (ssa_ops[i].result_def >= 0) { - tmp |= MAY_BE_RCN; - } - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - } - tmp |= MAY_BE_LONG; - if (opline->extended_value == ZEND_ASSIGN_DIM) { - if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; - } - UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); - } - } else { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - } - if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - } - break; - case ZEND_ASSIGN_CONCAT: - if (opline->extended_value == ZEND_ASSIGN_OBJ) { - goto unknown_opcode; } else if (opline->extended_value == ZEND_ASSIGN_DIM) { tmp = MAY_BE_RC1; - orig = t1; + orig = t1 & ~MAY_BE_UNDEF; t1 = zend_array_element_type(t1, 1, 0); t2 = OP1_DATA_INFO(); } else { @@ -2704,79 +2542,26 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_REF; } } - tmp |= MAY_BE_STRING; + + tmp |= binary_op_result_type( + ssa, get_compound_assign_op(opline->opcode), t1, t2, ssa_ops[i].op1_def); + if (opline->extended_value == ZEND_ASSIGN_DIM) { if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; - } + orig = assign_dim_result_type(orig, OP2_INFO(), tmp, opline->op1_type); UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } - } else { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - } - if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - } - break; - case ZEND_ASSIGN_BW_OR: - case ZEND_ASSIGN_BW_AND: - case ZEND_ASSIGN_BW_XOR: - if (opline->extended_value == ZEND_ASSIGN_OBJ) { - goto unknown_opcode; - } else if (opline->extended_value == ZEND_ASSIGN_DIM) { - tmp = MAY_BE_RC1; - orig = t1; - t1 = zend_array_element_type(t1, 1, 0); - t2 = OP1_DATA_INFO(); - } else { - tmp = 0; - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - if (ssa_ops[i].result_def >= 0) { - tmp |= MAY_BE_RCN; - } - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - } - if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) { - tmp |= MAY_BE_STRING; - } - if ((t1 & (MAY_BE_ANY-MAY_BE_STRING)) || (t1 & (MAY_BE_ANY-MAY_BE_STRING))) { - tmp |= MAY_BE_LONG; - } - if (opline->extended_value == ZEND_ASSIGN_DIM) { + } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { if (opline->op1_type == IS_CV) { - orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT); - t2 = OP2_INFO(); - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; + if (orig & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING)) { + orig |= MAY_BE_OBJECT; } - if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) { - orig &= ~MAY_BE_RCN; + if (orig & MAY_BE_RCN) { + orig |= MAY_BE_RC1; } UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } } else { UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); @@ -2785,12 +2570,6 @@ static void zend_update_type_info(const zend_op_array *op_array, UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); } break; -// TODO: ??? -// UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].op1_def); -// if (ssa_ops[i].result_def >= 0) { -// UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].result_def); -// } -// break; case ZEND_PRE_INC: case ZEND_PRE_DEC: tmp = 0; @@ -2817,8 +2596,15 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_LONG; } } else { + if (t1 & MAY_BE_ERROR) { + tmp |= MAY_BE_NULL; + } if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_LONG; + if (opline->opcode == ZEND_PRE_INC) { + tmp |= MAY_BE_LONG; + } else { + tmp |= MAY_BE_NULL; + } } if (t1 & MAY_BE_LONG) { tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; @@ -2841,7 +2627,7 @@ static void zend_update_type_info(const zend_op_array *op_array, case ZEND_POST_INC: case ZEND_POST_DEC: if (ssa_ops[i].result_def >= 0) { - tmp = (MAY_BE_RC1 | t1) & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RCN); + tmp = (MAY_BE_RC1 | t1) & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_REF|MAY_BE_RCN); if (t1 & MAY_BE_UNDEF) { tmp |= MAY_BE_NULL; } @@ -2868,8 +2654,15 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_LONG; } } else { + if (t1 & MAY_BE_ERROR) { + tmp |= MAY_BE_NULL; + } if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_LONG; + if (opline->opcode == ZEND_POST_INC) { + tmp |= MAY_BE_LONG; + } else { + tmp |= MAY_BE_NULL; + } } if (t1 & MAY_BE_LONG) { tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; @@ -2888,36 +2681,9 @@ static void zend_update_type_info(const zend_op_array *op_array, break; case ZEND_ASSIGN_DIM: if (opline->op1_type == IS_CV) { - tmp = (t1 & (MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF)); - tmp &= ~MAY_BE_NULL; - if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING)) { - tmp |= MAY_BE_ARRAY; - } - if (t1 & MAY_BE_REF) { - tmp |= MAY_BE_REF; - } - if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { - tmp |= MAY_BE_RC1; - } - if (tmp & MAY_BE_ARRAY) { - tmp |= (OP1_DATA_INFO() & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT; - if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { - tmp |= MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_STRING)) { - // FIXME: numeric string - tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG; - } - if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { - tmp |= MAY_BE_ARRAY_KEY_STRING; - } - } + tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type); UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } if (ssa_ops[i].result_def >= 0) { tmp = 0; @@ -2959,11 +2725,7 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_RC1; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } if (ssa_ops[i].result_def >= 0) { // TODO: ??? @@ -3022,19 +2784,11 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_DOUBLE; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t2 & MAY_BE_OBJECT) && ssa_ops[i].op2_use >= 0 && ssa_var_info[ssa_ops[i].op2_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op2_use].ce, ssa_var_info[ssa_ops[i].op2_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op1_def); } if (ssa_ops[i].result_def >= 0) { - UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - if ((t2 & MAY_BE_OBJECT) && ssa_ops[i].op2_use >= 0 && ssa_var_info[ssa_ops[i].op2_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op2_use].ce, ssa_var_info[ssa_ops[i].op2_use].is_instanceof, ssa_ops[i].result_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); - } + UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_ops[i].result_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def); } break; case ZEND_ASSIGN_REF: @@ -3049,7 +2803,7 @@ static void zend_update_type_info(const zend_op_array *op_array, if (opline->op2_type == IS_VAR && opline->extended_value == ZEND_RETURNS_FUNCTION) { tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF; } else { - tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN); + tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_RC1|MAY_BE_RCN); } if (t2 & MAY_BE_UNDEF) { tmp |= MAY_BE_NULL; @@ -3060,20 +2814,18 @@ static void zend_update_type_info(const zend_op_array *op_array, } break; case ZEND_BIND_GLOBAL: - tmp = (MAY_BE_REF | MAY_BE_ANY); + tmp = MAY_BE_REF | MAY_BE_ANY + | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); break; case ZEND_BIND_STATIC: - tmp = MAY_BE_ANY | (opline->extended_value ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN)); + tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF + | (opline->extended_value ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN)); UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); break; case ZEND_SEND_VAR: UPDATE_SSA_TYPE(t1 | MAY_BE_RC1 | MAY_BE_RCN, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); break; case ZEND_BIND_LEXICAL: if (ssa_ops[i].op2_def >= 0) { @@ -3082,11 +2834,7 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_REF; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def); - if ((t2 & MAY_BE_OBJECT) && ssa_var_info[ssa_ops[i].op2_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op2_use].ce, ssa_var_info[ssa_ops[i].op2_use].is_instanceof, ssa_ops[i].op2_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op2_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op2_def); } break; case ZEND_YIELD: @@ -3096,11 +2844,7 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_REF; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } if (ssa_ops[i].result_def >= 0) { tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF @@ -3110,6 +2854,7 @@ static void zend_update_type_info(const zend_op_array *op_array, break; case ZEND_SEND_VAR_EX: case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: case ZEND_SEND_REF: // TODO: ??? if (ssa_ops[i].op1_def >= 0) { @@ -3117,16 +2862,22 @@ static void zend_update_type_info(const zend_op_array *op_array, UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); } break; + case ZEND_SEND_UNPACK: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1 | MAY_BE_RC1|MAY_BE_RCN; + if (t1 & MAY_BE_ARRAY) { + /* SEND_UNPACK may acquire references into the array */ + tmp |= MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; case ZEND_FAST_CONCAT: case ZEND_ROPE_INIT: case ZEND_ROPE_ADD: case ZEND_ROPE_END: UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_STRING, ssa_ops[i].result_def); break; - case ZEND_CONCAT: - /* TODO: +MAY_BE_OBJECT ??? */ - UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_STRING, ssa_ops[i].result_def); - break; case ZEND_RECV: case ZEND_RECV_INIT: { @@ -3139,30 +2890,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; @@ -3179,12 +2908,6 @@ static void zend_update_type_info(const zend_op_array *op_array, if (func_info && (int)opline->op1.num-1 < func_info->num_args) { tmp = (tmp & (MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF)) | (tmp & func_info->arg_info[opline->op1.num-1].info.type); - } else { - if (opline->opcode == ZEND_RECV && (!arg_info || arg_info->type_hint == IS_UNDEF)) { - /* If the argument has no default value and no typehint, it is possible - * to pass less arguments than the function expects */ - tmp |= MAY_BE_UNDEF|MAY_BE_RC1; - } } #if 0 /* We won't recieve unused arguments */ @@ -3214,6 +2937,8 @@ static void zend_update_type_info(const zend_op_array *op_array, } case ZEND_DECLARE_CLASS: case ZEND_DECLARE_INHERITED_CLASS: + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_ops[i].result_def); if ((ce = zend_hash_find_ptr(&script->class_table, Z_STR_P(CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants)))) != NULL) { UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def); @@ -3250,12 +2975,8 @@ static void zend_update_type_info(const zend_op_array *op_array, } else { UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); } - } else if (t2 & MAY_BE_OBJECT) { - if (ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def); - } } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def); } break; case ZEND_NEW: @@ -3272,11 +2993,7 @@ static void zend_update_type_info(const zend_op_array *op_array, break; case ZEND_CLONE: UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def); break; case ZEND_INIT_ARRAY: case ZEND_ADD_ARRAY_ELEMENT: @@ -3345,11 +3062,7 @@ static void zend_update_type_info(const zend_op_array *op_array, case ZEND_UNSET_OBJ: if (ssa_ops[i].op1_def >= 0) { UPDATE_SSA_TYPE(t1, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } break; // case ZEND_INCLUDE_OR_EVAL: @@ -3368,11 +3081,7 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp |= MAY_BE_REF; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } if (opline->opcode == ZEND_FE_RESET_RW) { //??? @@ -3383,11 +3092,7 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp = MAY_BE_RC1 | MAY_BE_RCN | (t1 & (MAY_BE_REF | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF)); } UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def); break; case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: @@ -3418,6 +3123,9 @@ static void zend_update_type_info(const zend_op_array *op_array, tmp = MAY_BE_RCN; } } + if (opline->opcode == ZEND_FE_FETCH_R && (t2 & MAY_BE_REF)) { + tmp |= MAY_BE_REF; + } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def); if (ssa_ops[i].result_def >= 0) { tmp = MAY_BE_RC1; @@ -3502,6 +3210,7 @@ static void zend_update_type_info(const zend_op_array *op_array, break; case ZEND_SEND_VAR_EX: case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: case ZEND_SEND_REF: case ZEND_ASSIGN_REF: tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; @@ -3523,11 +3232,7 @@ static void zend_update_type_info(const zend_op_array *op_array, j = zend_ssa_next_use(ssa_ops, ssa_ops[i].result_def, j); } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } /* FETCH_LIST on a string behaves like FETCH_R on null */ tmp = zend_array_element_type( @@ -3545,9 +3250,15 @@ static void zend_update_type_info(const zend_op_array *op_array, } else if (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) { tmp |= MAY_BE_ERROR; } + } else if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) { + tmp |= MAY_BE_NULL; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); break; + case ZEND_FETCH_THIS: + UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_ops[i].result_def); + UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def); + break; case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_IS: case ZEND_FETCH_OBJ_RW: @@ -3573,14 +3284,13 @@ static void zend_update_type_info(const zend_op_array *op_array, } } UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); - if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def); - } else { - UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); - } + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); } if (ssa_ops[i].result_def >= 0) { - tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ERROR; + tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + if (opline->opcode != ZEND_FETCH_OBJ_R && opline->opcode != ZEND_FETCH_OBJ_IS) { + tmp |= MAY_BE_ERROR; + } if (opline->result_type == IS_TMP_VAR) { tmp |= MAY_BE_RC1; } else { @@ -3626,7 +3336,7 @@ static void zend_update_type_info(const zend_op_array *op_array, break; case ZEND_STRLEN: tmp = MAY_BE_RC1|MAY_BE_LONG; - if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT))) { + if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) { tmp |= MAY_BE_NULL; } UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); @@ -3635,6 +3345,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_TMP_VAR|IS_VAR|IS_CV)) { + 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); + } + } else { + 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); + } + } + break; + } default: unknown_opcode: if (ssa_ops[i].op1_def >= 0) { @@ -3654,6 +3387,50 @@ unknown_opcode: } } +static uint32_t get_class_entry_rank(zend_class_entry *ce) { + uint32_t rank = 0; + while (ce->parent) { + rank++; + ce = ce->parent; + } + return rank; +} + +/* Compute least common ancestor on class inheritance tree only */ +static zend_class_entry *join_class_entries( + zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) { + uint32_t rank1, rank2; + if (ce1 == ce2) { + return ce1; + } + if (!ce1 || !ce2) { + return NULL; + } + + rank1 = get_class_entry_rank(ce1); + rank2 = get_class_entry_rank(ce2); + + while (rank1 != rank2) { + if (rank1 > rank2) { + ce1 = ce1->parent; + rank1--; + } else { + ce2 = ce2->parent; + rank2--; + } + } + + while (ce1 != ce2) { + ce1 = ce1->parent; + ce2 = ce2->parent; + } + + if (ce1) { + *is_instanceof = 1; + } + return ce1; +} + int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist) { zend_basic_block *blocks = ssa->cfg.blocks; @@ -3670,14 +3447,28 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script if (ssa_vars[j].definition_phi) { zend_ssa_phi *p = ssa_vars[j].definition_phi; if (p->pi >= 0) { + zend_class_entry *ce = ssa_var_info[p->sources[0]].ce; + int is_instanceof = ssa_var_info[p->sources[0]].is_instanceof; tmp = get_ssa_var_info(ssa, p->sources[0]); - if (p->constraint.type_mask != (uint32_t) -1) { - tmp &= p->constraint.type_mask; + + if (!p->has_range_constraint) { + zend_ssa_type_constraint *constraint = &p->constraint.type; + tmp &= constraint->type_mask; + if ((tmp & MAY_BE_OBJECT) && constraint->ce && ce != constraint->ce) { + if (!ce) { + ce = constraint->ce; + is_instanceof = 1; + } else if (is_instanceof && instanceof_function(constraint->ce, ce)) { + ce = constraint->ce; + } else { + /* Ignore the constraint (either ce instanceof constraint->ce or + * they are unrelated, as far as we can statically determine) */ + } + } } + UPDATE_SSA_TYPE(tmp, j); - if (ssa_var_info[p->sources[0]].ce) { - UPDATE_SSA_OBJ_TYPE(ssa_var_info[p->sources[0]].ce, ssa_var_info[p->sources[0]].is_instanceof, j); - } + UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j); } else { int first = 1; int is_instanceof = 0; @@ -3689,20 +3480,21 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script } UPDATE_SSA_TYPE(tmp, j); for (i = 0; i < blocks[p->block].predecessors_count; i++) { - if (get_ssa_var_info(ssa, p->sources[i])) { - if (first) { - ce = ssa_var_info[p->sources[i]].ce; - is_instanceof = ssa_var_info[p->sources[i]].is_instanceof; - first = 0; - } else if (ce != ssa_var_info[p->sources[i]].ce) { - ce = NULL; - is_instanceof = 0; - } else if (is_instanceof != ssa_var_info[p->sources[i]].is_instanceof) { - is_instanceof = 1; + if (p->sources[i] >= 0) { + zend_ssa_var_info *info = &ssa_var_info[p->sources[i]]; + if (info->type & MAY_BE_OBJECT) { + if (first) { + ce = info->ce; + is_instanceof = info->is_instanceof; + first = 0; + } else { + is_instanceof |= info->is_instanceof; + ce = join_class_entries(ce, info->ce, &is_instanceof); + } } } } - UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j); + UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j); } } else if (ssa_vars[j].definition >= 0) { i = ssa_vars[j].definition; @@ -3712,121 +3504,219 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script return SUCCESS; } -#define CHECK_MAY_BE_USED_AS_DOUBLE(var2) do { \ - if ((ssa->var_info[var2].type & MAY_BE_ANY) == (MAY_BE_LONG | MAY_BE_DOUBLE)) { \ - if (ssa->vars[var2].var < op_array->last_var) { \ - return 0; \ - } else if (ssa->vars[var2].definition >= 0 && \ - op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_ADD && \ - op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_SUB && \ - op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_MUL && \ - op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_DIV) { \ - return 0; \ - } else if (!zend_may_be_used_as_double(op_array, ssa, var2)) { \ - return 0; \ - } \ - } \ - } while (0) +static zend_bool is_narrowable_instr(zend_op *opline) { + return opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB + || opline->opcode == ZEND_MUL || opline->opcode == ZEND_DIV; +} -static int zend_may_be_used_as_double(const zend_op_array *op_array, zend_ssa *ssa, int var) -{ - FOR_EACH_VAR_USAGE(var, CHECK_MAY_BE_USED_AS_DOUBLE); - return 1; +static zend_bool is_effective_op1_double_cast(zend_op *opline, zval *op2) { + return (opline->opcode == ZEND_ADD && Z_LVAL_P(op2) == 0) + || (opline->opcode == ZEND_SUB && Z_LVAL_P(op2) == 0) + || (opline->opcode == ZEND_MUL && Z_LVAL_P(op2) == 1) + || (opline->opcode == ZEND_DIV && Z_LVAL_P(op2) == 1); +} +static zend_bool is_effective_op2_double_cast(zend_op *opline, zval *op1) { + /* In PHP it holds that (double)(0-$int) is bitwise identical to 0.0-(double)$int, + * so allowing SUB here is fine. */ + return (opline->opcode == ZEND_ADD && Z_LVAL_P(op1) == 0) + || (opline->opcode == ZEND_SUB && Z_LVAL_P(op1) == 0) + || (opline->opcode == ZEND_MUL && Z_LVAL_P(op1) == 1); } -static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa) -{ - zend_ssa_op *ssa_ops = ssa->ops; - zend_ssa_var *ssa_vars = ssa->vars; - zend_ssa_var_info *ssa_var_info = ssa->var_info; - int ssa_vars_count = ssa->vars_count; - int j; - zend_bitset worklist, types; - ALLOCA_FLAG(use_heap); +/* This function recursively checks whether it's possible to convert an integer variable + * initialization to a double initialization. The basic idea is that if the value is used + * only in add/sub/mul/div ("narrowable" instructions) with a double result value, then it + * will be cast to double at that point anyway, so we may as well do it earlier already. + * + * The tricky case are chains of operations, where it's not necessarily a given that converting + * an integer to double before the chain of operations is the same as converting it after the + * chain. What this function does is detect two cases where it is safe: + * * If the operations only involve constants, then we can simply verify that performing the + * calculation on integers and doubles yields the same value. + * * Even if one operand is not known, we may be able to determine that the operations with the + * integer replaced by a double only acts as an effective double cast on the unknown operand. + * E.g. 0+$i and 0.0+$i only differ by that cast. If then the consuming instruction of this + * result will perform a double cast anyway, the conversion is safe. + * + * The checks happens recursively, while keeping track of which variables are already visisted to + * avoid infinite loops. An iterative, worklist driven approach would be possible, but the state + * management more cumbersome to implement, so we don't bother for now. + */ +static zend_bool can_convert_to_double( + const zend_op_array *op_array, zend_ssa *ssa, int var_num, + zval *value, zend_bitset visited) { + zend_ssa_var *var = &ssa->vars[var_num]; + zend_ssa_phi *phi; + int use; + uint32_t type; - types = do_alloca(sizeof(zend_ulong) * (op_array->last_var + zend_bitset_len(ssa_vars_count)), use_heap); - memset(types, 0, sizeof(zend_ulong) * op_array->last_var); - worklist = types + op_array->last_var; - memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count)); + if (zend_bitset_in(visited, var_num)) { + return 1; + } + zend_bitset_incl(visited, var_num); - /* Find variables that may be only LONG or DOUBLE */ - for (j = op_array->last_var; j < ssa_vars_count; j++) { - if (ssa_vars[j].var < op_array->last_var) { - types[ssa_vars[j].var] |= ssa_var_info[j].type & (MAY_BE_ANY - MAY_BE_NULL); + for (use = var->use_chain; use >= 0; use = zend_ssa_next_use(ssa->ops, var_num, use)) { + zend_op *opline = &op_array->opcodes[use]; + zend_ssa_op *ssa_op = &ssa->ops[use]; + + if (is_no_val_use(opline, ssa_op, var_num)) { + continue; } - } - for (j = 0; j < op_array->last_var; j++) { - if (types[j] == (MAY_BE_LONG | MAY_BE_DOUBLE)) { - zend_bitset_incl(worklist, j); + + if (!is_narrowable_instr(opline)) { + return 0; } - } - if (zend_bitset_empty(worklist, zend_bitset_len(ssa_vars_count))) { - free_alloca(types, use_heap); - return SUCCESS; - } - /* Exclude variables that can't be narrowed */ - for (j = op_array->last_var; j < ssa_vars_count; j++) { - if (ssa_vars[j].var < op_array->last_var && - zend_bitset_in(worklist, ssa_vars[j].var)) { - if (ssa_vars[j].definition >= 0) { - if ((ssa_var_info[j].type & MAY_BE_ANY) == MAY_BE_LONG) { - if (ssa_vars[j].use_chain >= 0 || - op_array->opcodes[ssa_vars[j].definition].opcode != ZEND_ASSIGN || - op_array->opcodes[ssa_vars[j].definition].op2_type != IS_CONST) { - zend_bitset_excl(worklist, ssa_vars[j].var); - } - } else if ((ssa_var_info[j].type & MAY_BE_ANY) != MAY_BE_DOUBLE) { - zend_bitset_excl(worklist, ssa_vars[j].var); + /* Instruction always returns double, the conversion is certainly fine */ + type = ssa->var_info[ssa_op->result_def].type; + if ((type & MAY_BE_ANY) == MAY_BE_DOUBLE) { + continue; + } + + /* UNDEF signals that the previous result is an effective double cast, this is only allowed + * if this instruction would have done the cast anyway (previous check). */ + if (Z_ISUNDEF_P(value)) { + return 0; + } + + /* Check that narrowing can actually be useful */ + if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) { + return 0; + } + + { + /* For calculation on original values */ + zval orig_op1, orig_op2, orig_result; + /* For calculation with var_num cast to double */ + zval dval_op1, dval_op2, dval_result; + + ZVAL_UNDEF(&orig_op1); + ZVAL_UNDEF(&dval_op1); + if (ssa_op->op1_use == var_num) { + ZVAL_COPY_VALUE(&orig_op1, value); + ZVAL_DOUBLE(&dval_op1, (double) Z_LVAL_P(value)); + } else if (opline->op1_type == IS_CONST) { + zval *zv = CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants); + if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) { + ZVAL_COPY_VALUE(&orig_op1, zv); + ZVAL_COPY_VALUE(&dval_op1, zv); + } + } + + ZVAL_UNDEF(&orig_op2); + ZVAL_UNDEF(&dval_op2); + if (ssa_op->op2_use == var_num) { + ZVAL_COPY_VALUE(&orig_op2, value); + ZVAL_DOUBLE(&dval_op2, (double) Z_LVAL_P(value)); + } else if (opline->op2_type == IS_CONST) { + zval *zv = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants); + if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) { + ZVAL_COPY_VALUE(&orig_op2, zv); + ZVAL_COPY_VALUE(&dval_op2, zv); } } - } - } - if (zend_bitset_empty(worklist, zend_bitset_len(ssa_vars_count))) { - free_alloca(types, use_heap); - return SUCCESS; - } - for (j = op_array->last_var; j < ssa_vars_count; j++) { - if (ssa_vars[j].var < op_array->last_var && - zend_bitset_in(worklist, ssa_vars[j].var) && - (ssa_var_info[j].type & (MAY_BE_ANY-MAY_BE_NULL)) == (MAY_BE_LONG|MAY_BE_DOUBLE) && - ssa_vars[j].use_chain >= 0) { - if (!zend_may_be_used_as_double(op_array, ssa, j)) { - zend_bitset_excl(worklist, ssa_vars[j].var); + ZEND_ASSERT(!Z_ISUNDEF(orig_op1) || !Z_ISUNDEF(orig_op2)); + if (Z_ISUNDEF(orig_op1)) { + if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op2) == 0) { + ZVAL_LONG(&orig_result, 0); + } else if (is_effective_op1_double_cast(opline, &orig_op2)) { + ZVAL_UNDEF(&orig_result); + } else { + return 0; + } + } else if (Z_ISUNDEF(orig_op2)) { + if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op1) == 0) { + ZVAL_LONG(&orig_result, 0); + } else if (is_effective_op2_double_cast(opline, &orig_op1)) { + ZVAL_UNDEF(&orig_result); + } else { + return 0; + } + } else { + /* Avoid division by zero */ + if (opline->opcode == ZEND_DIV && zval_get_double(&orig_op2) == 0.0) { + return 0; + } + + get_binary_op(opline->opcode)(&orig_result, &orig_op1, &orig_op2); + get_binary_op(opline->opcode)(&dval_result, &dval_op1, &dval_op2); + ZEND_ASSERT(Z_TYPE(dval_result) == IS_DOUBLE); + if (zval_get_double(&orig_result) != Z_DVAL(dval_result)) { + return 0; + } + } + + if (!can_convert_to_double(op_array, ssa, ssa_op->result_def, &orig_result, visited)) { + return 0; } } } - if (zend_bitset_empty(worklist, zend_bitset_len(ssa_vars_count))) { - free_alloca(types, use_heap); - return SUCCESS; + + for (phi = var->phi_use_chain; phi; phi = zend_ssa_next_use_phi(ssa, var_num, phi)) { + /* Check that narrowing can actually be useful */ + type = ssa->var_info[phi->ssa_var].type; + if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) { + return 0; + } + + if (!can_convert_to_double(op_array, ssa, phi->ssa_var, value, visited)) { + return 0; + } } - for (j = op_array->last_var; j < ssa_vars_count; j++) { - if (ssa_vars[j].var < op_array->last_var && - zend_bitset_in(worklist, ssa_vars[j].var)) { - if ((ssa_var_info[j].type & MAY_BE_ANY) == MAY_BE_LONG && - ssa_vars[j].definition >= 0 && - ssa_ops[ssa_vars[j].definition].result_def < 0 && - op_array->opcodes[ssa_vars[j].definition].opcode == ZEND_ASSIGN && - op_array->opcodes[ssa_vars[j].definition].op2_type == IS_CONST && - Z_TYPE_P(CRT_CONSTANT_EX(op_array, op_array->opcodes[ssa_vars[j].definition].op2, ssa->rt_constants)) == IS_LONG) { - ssa_var_info[j].use_as_double = 1; - } - ssa_var_info[j].type &= ~MAY_BE_ANY; - zend_bitset_incl(worklist, j); + return 1; +} + +static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa) +{ + uint32_t bitset_len = zend_bitset_len(ssa->vars_count); + ALLOCA_FLAG(use_heap); + zend_bitset visited = ZEND_BITSET_ALLOCA(2 * bitset_len, use_heap); + zend_bitset worklist = visited + bitset_len; + int i; + uint32_t v; + zend_op *opline; + zend_bool narrowed = 0; + + zend_bitset_clear(worklist, bitset_len); + + for (v = op_array->last_var; v < ssa->vars_count; v++) { + if ((ssa->var_info[v].type & (MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF)) != MAY_BE_LONG) continue; + if (ssa->vars[v].definition < 0) continue; + if (ssa->vars[v].no_val) continue; + opline = op_array->opcodes + ssa->vars[v].definition; + /* Go through assignments of literal integers and check if they can be converted to + * doubles instead, in the hope that we'll narrow long|double to double. */ + if (opline->opcode == ZEND_ASSIGN && opline->result_type == IS_UNUSED && + opline->op1_type == IS_CV && opline->op2_type == IS_CONST) { + zval *value = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants); + + zend_bitset_clear(visited, bitset_len); + if (can_convert_to_double(op_array, ssa, v, value, visited)) { + narrowed = 1; + ssa->var_info[v].use_as_double = 1; + /* The "visited" vars are exactly those which may change their type due to + * narrowing. Reset their types and add them to the type inference worklist */ + ZEND_BITSET_FOREACH(visited, bitset_len, i) { + ssa->var_info[i].type &= ~MAY_BE_ANY; + } ZEND_BITSET_FOREACH_END(); + zend_bitset_union(worklist, visited, bitset_len); + } } } - for (j = 0; j < op_array->last_var; j++) { - zend_bitset_excl(worklist, j); + + if (!narrowed) { + free_alloca(visited, use_heap); + return SUCCESS; } if (zend_infer_types_ex(op_array, script, ssa, worklist) != SUCCESS) { - free_alloca(types, use_heap); + free_alloca(visited, use_heap); return FAILURE; } - free_alloca(types, use_heap); + free_alloca(visited, use_heap); return SUCCESS; } @@ -3855,7 +3745,26 @@ 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); + if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) { + ret->type |= MAY_BE_REF; + } + 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) @@ -3884,8 +3793,8 @@ void zend_func_return_info(const zend_op_array *op_array, } for (j = 0; j < blocks_count; j++) { - if (blocks[j].flags & ZEND_BB_REACHABLE) { - zend_op *opline = op_array->opcodes + blocks[j].end; + if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) { + zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1; if (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_RETURN_BY_REF) { if (!recursive && @@ -4033,16 +3942,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; } @@ -4072,8 +3984,21 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc /* Narrowing integer initialization to doubles */ zend_type_narrowing(op_array, script, ssa); + for (j = 0; j < op_array->last_var; j++) { + if (zend_string_equals_literal(op_array->vars[j], "php_errormsg")) { + /* Mark all SSA vars for $php_errormsg as references, + * to make sure we don't optimize it. */ + int i; + for (i = 0; i < ssa_vars_count; i++) { + if (ssa->vars[i].var == j) { + ssa_var_info[i].type |= MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + } + } + } + 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); @@ -4097,13 +4022,7 @@ int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const } } else { for (i = 0; i < op_array->last_var; i++) { - if (i == EX_VAR_TO_NUM(op_array->this_var)) { - ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_OBJECT; - ssa_var_info[i].ce = op_array->scope; - ssa_var_info[i].is_instanceof = 1; - } else { - ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RCN; - } + ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RCN; ssa_var_info[i].has_range = 0; } } diff --git a/ext/opcache/Optimizer/zend_inference.h b/ext/opcache/Optimizer/zend_inference.h index 1826b99434..6bf68090c5 100644 --- a/ext/opcache/Optimizer/zend_inference.h +++ b/ext/opcache/Optimizer/zend_inference.h @@ -24,39 +24,7 @@ #include "zend_bitset.h" /* Bitmask for type inference (zend_ssa_var_info.type) */ -#define MAY_BE_UNDEF (1 << IS_UNDEF) -#define MAY_BE_NULL (1 << IS_NULL) -#define MAY_BE_FALSE (1 << IS_FALSE) -#define MAY_BE_TRUE (1 << IS_TRUE) -#define MAY_BE_LONG (1 << IS_LONG) -#define MAY_BE_DOUBLE (1 << IS_DOUBLE) -#define MAY_BE_STRING (1 << IS_STRING) -#define MAY_BE_ARRAY (1 << IS_ARRAY) -#define MAY_BE_OBJECT (1 << IS_OBJECT) -#define MAY_BE_RESOURCE (1 << IS_RESOURCE) -#define MAY_BE_ANY (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE) -#define MAY_BE_REF (1 << IS_REFERENCE) /* may be reference */ - -#define MAY_BE_ARRAY_SHIFT (IS_REFERENCE) - -#define MAY_BE_ARRAY_OF_NULL (MAY_BE_NULL << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_FALSE (MAY_BE_FALSE << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_TRUE (MAY_BE_TRUE << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_LONG (MAY_BE_LONG << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_DOUBLE (MAY_BE_DOUBLE << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_STRING (MAY_BE_STRING << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_ARRAY (MAY_BE_ARRAY << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_OBJECT (MAY_BE_OBJECT << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_RESOURCE (MAY_BE_RESOURCE << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_ANY (MAY_BE_ANY << MAY_BE_ARRAY_SHIFT) -#define MAY_BE_ARRAY_OF_REF (MAY_BE_REF << MAY_BE_ARRAY_SHIFT) - -#define MAY_BE_ARRAY_KEY_LONG (1<<21) -#define MAY_BE_ARRAY_KEY_STRING (1<<22) -#define MAY_BE_ARRAY_KEY_ANY (MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_KEY_STRING) - -#define MAY_BE_ERROR (1<<23) -#define MAY_BE_CLASS (1<<24) +#include "zend_type_info.h" #define MAY_BE_IN_REG (1<<25) /* value allocated in CPU register */ @@ -273,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 f398a83927..35dff98362 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -29,10 +29,11 @@ #include "zend_cfg.h" #include "zend_func_info.h" #include "zend_call_graph.h" +#include "zend_inference.h" #include "zend_dump.h" #ifndef HAVE_DFA_PASS -# define HAVE_DFA_PASS 0 +# define HAVE_DFA_PASS 1 #endif static void zend_optimizer_zval_dtor_wrapper(zval *zvalue) @@ -253,6 +254,13 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array, return 0; } + if (zend_optimizer_classify_function(Z_STR_P(val), opline->extended_value)) { + /* Dynamic call to various special functions must stay dynamic, + * otherwise would drop a warning */ + zval_dtor(val); + return 0; + } + opline->opcode = ZEND_INIT_FCALL_BY_NAME; drop_leading_backslash(val); opline->op2.constant = zend_optimizer_add_literal(op_array, val); @@ -418,17 +426,10 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, opline->opcode = ZEND_SEND_VAL_EX; break; case ZEND_SEND_VAR_NO_REF: - if (opline->extended_value & ZEND_ARG_COMPILE_TIME_BOUND) { - if (opline->extended_value & ZEND_ARG_SEND_BY_REF) { - zval_dtor(val); - return 0; - } - opline->extended_value = 0; - opline->opcode = ZEND_SEND_VAL_EX; - } else { - opline->extended_value = 0; - opline->opcode = ZEND_SEND_VAL; - } + zval_dtor(val); + return 0; + case ZEND_SEND_VAR_NO_REF_EX: + opline->opcode = ZEND_SEND_VAL; break; case ZEND_SEND_USER: opline->opcode = ZEND_SEND_VAL_EX; @@ -526,6 +527,94 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, return 1; } +zend_function *zend_optimizer_get_called_func( + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants) +{ +#define GET_OP(op) CRT_CONSTANT_EX(op_array, opline->op, rt_constants) + switch (opline->opcode) { + case ZEND_INIT_FCALL: + { + zend_string *function_name = Z_STR_P(GET_OP(op2)); + zend_function *func; + if ((func = zend_hash_find_ptr(&script->function_table, function_name)) != NULL) { + return func; + } else if ((func = zend_hash_find_ptr(EG(function_table), function_name)) != NULL) { + ZEND_ASSERT(func->type == ZEND_INTERNAL_FUNCTION); + return func; + } + break; + } + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + if (opline->op2_type == IS_CONST && Z_TYPE_P(GET_OP(op2)) == IS_STRING) { + zval *function_name = GET_OP(op2) + 1; + return zend_hash_find_ptr(&script->function_table, Z_STR_P(function_name)); + } + break; + case ZEND_INIT_STATIC_METHOD_CALL: + if (opline->op2_type == IS_CONST && Z_TYPE_P(GET_OP(op2)) == IS_STRING) { + zend_class_entry *ce = NULL; + if (opline->op1_type == IS_CONST && Z_TYPE_P(GET_OP(op1)) == IS_STRING) { + zend_string *class_name = Z_STR_P(GET_OP(op1) + 1); + ce = zend_hash_find_ptr(&script->class_table, class_name); + } else if (opline->op1_type == IS_UNUSED && op_array->scope + && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) + && (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) { + ce = op_array->scope; + } + if (ce) { + zend_string *func_name = Z_STR_P(GET_OP(op2) + 1); + return zend_hash_find_ptr(&ce->function_table, func_name); + } + } + break; + case ZEND_INIT_METHOD_CALL: + if (opline->op1_type == IS_UNUSED + && opline->op2_type == IS_CONST && Z_TYPE_P(GET_OP(op2)) == IS_STRING + && op_array->scope && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)) { + zend_string *method_name = Z_STR_P(GET_OP(op2) + 1); + zend_function *fbc = zend_hash_find_ptr( + &op_array->scope->function_table, method_name); + if (fbc) { + zend_bool is_private = (fbc->common.fn_flags & ZEND_ACC_PRIVATE) != 0; + zend_bool is_final = (fbc->common.fn_flags & ZEND_ACC_FINAL) != 0; + zend_bool same_scope = fbc->common.scope == op_array->scope; + if ((is_private && same_scope) + || (is_final && (!is_private || same_scope))) { + return fbc; + } + } + } + break; + } + return NULL; +#undef GET_OP +} + +uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args) { + if (zend_string_equals_literal(name, "extract")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "compact")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "parse_str") && num_args <= 1) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "mb_parse_str") && num_args <= 1) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "get_defined_vars")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "assert")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "func_num_args")) { + return ZEND_FUNC_VARARG; + } else if (zend_string_equals_literal(name, "func_get_arg")) { + return ZEND_FUNC_VARARG; + } else if (zend_string_equals_literal(name, "func_get_args")) { + return ZEND_FUNC_VARARG; + } else { + return 0; + } +} + static void zend_optimize(zend_op_array *op_array, zend_optimizer_ctx *ctx) { @@ -542,6 +631,7 @@ static void zend_optimize(zend_op_array *op_array, * - perform compile-time evaluation of constant binary and unary operations * - optimize series of ADD_STRING and/or ADD_CHAR * - convert CAST(IS_BOOL,x) into BOOL(x) + * - pre-evaluate constant function calls */ if (ZEND_OPTIMIZER_PASS_1 & ctx->optimization_level) { zend_optimizer_pass1(op_array, ctx); @@ -553,8 +643,6 @@ static void zend_optimize(zend_op_array *op_array, /* pass 2: * - convert non-numeric constants to numeric constants in numeric operators * - optimize constant conditional JMPs - * - optimize static BRKs and CONTs - * - pre-evaluate constant function calls */ if (ZEND_OPTIMIZER_PASS_2 & ctx->optimization_level) { zend_optimizer_pass2(op_array); @@ -678,6 +766,34 @@ static void zend_redo_pass_two(zend_op_array *op_array) } } +#if HAVE_DFA_PASS +static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa) +{ + zend_op *opline, *end; + + opline = op_array->opcodes; + end = opline + op_array->last; + while (opline < end) { + zend_vm_set_opcode_handler_ex(opline, + opline->op1_type == IS_UNUSED ? 0 : (OP1_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)), + opline->op2_type == IS_UNUSED ? 0 : (OP2_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)), + (opline->opcode == ZEND_PRE_INC || + opline->opcode == ZEND_PRE_DEC || + opline->opcode == ZEND_POST_INC || + opline->opcode == ZEND_POST_DEC) ? + ((ssa->ops[opline - op_array->opcodes].op1_def >= 0) ? (OP1_DEF_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)) : MAY_BE_ANY) : + (opline->result_type == IS_UNUSED ? 0 : (RES_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)))); + if (opline->op1_type == IS_CONST) { + ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1); + } + if (opline->op2_type == IS_CONST) { + ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2); + } + opline++; + } +} +#endif + static void zend_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx) { @@ -711,6 +827,7 @@ static void zend_adjust_fcall_stack_size(zend_op_array *op_array, zend_optimizer } } +#if HAVE_DFA_PASS static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array) { zend_func_info *func_info = ZEND_FUNC_INFO(op_array); @@ -721,14 +838,14 @@ static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array) while (call_info) { zend_op *opline = call_info->caller_init_opline; - if (opline && call_info->callee_func) { - ZEND_ASSERT(opline->opcode == ZEND_INIT_FCALL); + if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) { opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func); } call_info = call_info->next_callee; } } } +#endif int zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level) { @@ -736,7 +853,9 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend zend_op_array *op_array; zend_string *name; zend_optimizer_ctx ctx; +#if HAVE_DFA_PASS zend_call_graph call_graph; +#endif ctx.arena = zend_arena_create(64 * 1024); ctx.script = script; @@ -779,6 +898,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); @@ -786,7 +914,6 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend } //TODO: perform inner-script inference??? - for (i = 0; i < call_graph.op_arrays_count; i++) { func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (func_info) { @@ -807,8 +934,13 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend } for (i = 0; i < call_graph.op_arrays_count; i++) { - zend_redo_pass_two(call_graph.op_arrays[i]); - ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); + func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); + if (func_info && func_info->ssa.var_info) { + zend_redo_pass_two_ex(call_graph.op_arrays[i], &func_info->ssa); + ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); + } else { + zend_redo_pass_two(call_graph.op_arrays[i]); + } } zend_arena_release(&ctx.arena, checkpoint); diff --git a/ext/opcache/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h index 220a00d6c4..e2e9823c78 100644 --- a/ext/opcache/Optimizer/zend_optimizer_internal.h +++ b/ext/opcache/Optimizer/zend_optimizer_internal.h @@ -106,5 +106,8 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c void zend_optimizer_nop_removal(zend_op_array *op_array); void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx); int zend_optimizer_is_disabled_func(const char *name, size_t len); +zend_function *zend_optimizer_get_called_func( + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants); +uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args); #endif diff --git a/ext/opcache/Optimizer/zend_ssa.c b/ext/opcache/Optimizer/zend_ssa.c index 0da793918c..6f1fff961c 100644 --- a/ext/opcache/Optimizer/zend_ssa.c +++ b/ext/opcache/Optimizer/zend_ssa.c @@ -23,19 +23,47 @@ #include "zend_dump.h" #include "zend_inference.h" -static int needs_pi(const zend_op_array *op_array, zend_dfg *dfg, zend_ssa *ssa, int from, int to, int var) /* {{{ */ -{ - if (from == to || ssa->cfg.blocks[to].predecessors_count != 1) { - zend_ssa_phi *p = ssa->blocks[to].phis; - while (p) { - if (p->pi < 0 && p->var == var) { - return 1; - } - p = p->next; +static zend_bool dominates(const zend_basic_block *blocks, int a, int b) { + while (blocks[b].level > blocks[a].level) { + b = blocks[b].idom; + } + return a == b; +} + +static zend_bool dominates_other_predecessors( + const zend_cfg *cfg, const zend_basic_block *block, int check, int exclude) { + int i; + for (i = 0; i < block->predecessors_count; i++) { + int predecessor = cfg->predecessors[block->predecessor_offset + i]; + if (predecessor != exclude && !dominates(cfg->blocks, check, predecessor)) { + return 0; } + } + return 1; +} + +static zend_bool needs_pi(const zend_op_array *op_array, zend_dfg *dfg, zend_ssa *ssa, int from, int to, int var) /* {{{ */ +{ + zend_basic_block *from_block, *to_block; + int other_successor; + + if (!DFG_ISSET(dfg->in, dfg->size, to, var)) { + /* Variable is not live, certainly won't benefit from pi */ return 0; } - return DFG_ISSET(dfg->in, dfg->size, to, var); + + to_block = &ssa->cfg.blocks[to]; + if (to_block->predecessors_count == 1) { + /* Always place pi if one predecessor (an if branch) */ + return 1; + } + + /* Check that the other successor of the from block does not dominate all other predecessors. + * If it does, we'd probably end up annihilating a positive+negative pi assertion. */ + from_block = &ssa->cfg.blocks[from]; + other_successor = from_block->successors[0] == to + ? from_block->successors[1] : from_block->successors[0]; + return !dominates_other_predecessors(&ssa->cfg, to_block, other_successor, from); } /* }}} */ @@ -62,6 +90,18 @@ static zend_ssa_phi *add_pi( phi->next = ssa->blocks[to].phis; ssa->blocks[to].phis = phi; + /* Block "to" now defines "var" via the pi statement, so add it to the "def" set. Note that + * this is not entirely accurate, because the pi is actually placed along the edge from->to. + * If there is a back-edge to "to" this may result in non-minimal SSA form. */ + DFG_SET(dfg->def, dfg->size, to, var); + + /* If there are multiple predecessors in the target block, we need to place a phi there. + * However this can (generally) not be expressed in terms of dominance frontiers, so place it + * explicitly. dfg->use here really is dfg->phi, we're reusing the set. */ + if (ssa->cfg.blocks[to].predecessors_count > 1) { + DFG_SET(dfg->use, dfg->size, to, var); + } + return phi; } /* }}} */ @@ -70,16 +110,17 @@ static void pi_range( zend_ssa_phi *phi, int min_var, int max_var, zend_long min, zend_long max, char underflow, char overflow, char negative) /* {{{ */ { - phi->constraint.min_var = min_var; - phi->constraint.max_var = max_var; - phi->constraint.min_ssa_var = -1; - phi->constraint.max_ssa_var = -1; - phi->constraint.range.min = min; - phi->constraint.range.max = max; - phi->constraint.range.underflow = underflow; - phi->constraint.range.overflow = overflow; - phi->constraint.negative = negative ? NEG_INIT : NEG_NONE; - phi->constraint.type_mask = (uint32_t) -1; + zend_ssa_range_constraint *constraint = &phi->constraint.range; + constraint->min_var = min_var; + constraint->max_var = max_var; + constraint->min_ssa_var = -1; + constraint->max_ssa_var = -1; + constraint->range.min = min; + constraint->range.max = max; + constraint->range.underflow = underflow; + constraint->range.overflow = overflow; + constraint->negative = negative ? NEG_INIT : NEG_NONE; + phi->has_range_constraint = 1; } /* }}} */ @@ -97,10 +138,12 @@ static inline void pi_range_max(zend_ssa_phi *phi, int var, zend_long val) { } static void pi_type_mask(zend_ssa_phi *phi, uint32_t type_mask) { - phi->constraint.type_mask = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN; - phi->constraint.type_mask |= type_mask; + phi->has_range_constraint = 0; + phi->constraint.type.ce = NULL; + phi->constraint.type.type_mask = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN; + phi->constraint.type.type_mask |= type_mask; if (type_mask & MAY_BE_NULL) { - phi->constraint.type_mask |= MAY_BE_UNDEF; + phi->constraint.type.type_mask |= MAY_BE_UNDEF; } } static inline void pi_not_type_mask(zend_ssa_phi *phi, uint32_t type_mask) { @@ -119,7 +162,7 @@ static inline uint32_t mask_for_type_check(uint32_t type) { /* We can interpret $a + 5 == 0 as $a = 0 - 5, i.e. shift the adjustment to the other operand. * This negated adjustment is what is written into the "adjustment" parameter. */ -static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_flags, zend_op *opline, uint32_t var_num, zend_long *adjustment) +static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_flags, zend_op *opline, uint32_t var_num, zend_long *adjustment) /* {{{ */ { zend_op *op = opline; while (op != op_array->opcodes) { @@ -164,6 +207,7 @@ static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_f } return -1; } +/* }}} */ static inline zend_bool add_will_overflow(zend_long a, zend_long b) { return (b > 0 && a > ZEND_LONG_MAX - b) @@ -179,17 +223,17 @@ static inline zend_bool sub_will_overflow(zend_long a, zend_long b) { * Order of Phis is importent, Pis must be placed before Phis */ static void place_essa_pis( - zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, - zend_dfg *dfg) { + zend_arena **arena, const zend_script *script, const zend_op_array *op_array, + uint32_t build_flags, zend_ssa *ssa, zend_dfg *dfg) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; int j, blocks_count = ssa->cfg.blocks_count; for (j = 0; j < blocks_count; j++) { zend_ssa_phi *pi; - zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].end; + zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1; int bt; /* successor block number if a condition is true */ int bf; /* successor block number if a condition is false */ - if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0 || blocks[j].len == 0) { continue; } /* the last instruction of basic block is conditional branch, @@ -198,12 +242,12 @@ static void place_essa_pis( switch (opline->opcode) { case ZEND_JMPZ: case ZEND_JMPZNZ: - bf = ssa->cfg.blocks[j].successors[0]; - bt = ssa->cfg.blocks[j].successors[1]; + bf = blocks[j].successors[0]; + bt = blocks[j].successors[1]; break; case ZEND_JMPNZ: - bt = ssa->cfg.blocks[j].successors[0]; - bf = ssa->cfg.blocks[j].successors[1]; + bt = blocks[j].successors[0]; + bf = blocks[j].successors[1]; break; default: continue; @@ -442,9 +486,27 @@ static void place_essa_pis( pi_not_type_mask(pi, type_mask); } } + } else if (opline->op1_type == IS_TMP_VAR && (opline-1)->opcode == ZEND_INSTANCEOF && + opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV && + (opline-1)->op2_type == IS_CONST) { + int var = EX_VAR_TO_NUM((opline-1)->op1.var); + zend_string *lcname = Z_STR_P(CRT_CONSTANT((opline-1)->op2) + 1); + zend_class_entry *ce = zend_hash_find_ptr(&script->class_table, lcname); + if (!ce) { + ce = zend_hash_find_ptr(CG(class_table), lcname); + if (!ce || ce->type != ZEND_INTERNAL_CLASS) { + continue; + } + } + + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_type_mask(pi, MAY_BE_OBJECT); + pi->constraint.type.ce = ce; + } } } } +/* }}} */ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ { @@ -453,8 +515,7 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa_op *ssa_ops = ssa->ops; int ssa_vars_count = ssa->vars_count; int i, j; - uint32_t k; - zend_op *opline; + zend_op *opline, *end; int *tmp = NULL; ALLOCA_FLAG(use_heap); @@ -479,12 +540,13 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, } while (phi); } - for (k = blocks[n].start; k <= blocks[n].end; k++) { - opline = op_array->opcodes + k; + opline = op_array->opcodes + blocks[n].start; + end = opline + blocks[n].len; + for (; opline < end; opline++) { + uint32_t k = opline - op_array->opcodes; if (opline->opcode != ZEND_OP_DATA) { zend_op *next = opline + 1; - if (k < blocks[n].end && - next->opcode == ZEND_OP_DATA) { + if (next < end && next->opcode == ZEND_OP_DATA) { if (next->op1_type == IS_CV) { ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)]; //USE_SSA_VAR(next->op1.var); @@ -583,8 +645,10 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, } break; case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: case ZEND_SEND_VAR_EX: case ZEND_SEND_REF: + case ZEND_SEND_UNPACK: case ZEND_FE_RESET_RW: //TODO: ??? if (opline->op1_type == IS_CV) { @@ -664,6 +728,15 @@ 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++; } + break; + case ZEND_VERIFY_RETURN_TYPE: + if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) { + 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; } @@ -688,11 +761,13 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, for (p = ssa_blocks[succ].phis; p; p = p->next) { if (p->pi == n) { /* e-SSA Pi */ - if (p->constraint.min_var >= 0) { - p->constraint.min_ssa_var = var[p->constraint.min_var]; - } - if (p->constraint.max_var >= 0) { - p->constraint.max_ssa_var = var[p->constraint.max_var]; + if (p->has_range_constraint) { + if (p->constraint.range.min_var >= 0) { + p->constraint.range.min_ssa_var = var[p->constraint.range.min_var]; + } + if (p->constraint.range.max_var >= 0) { + p->constraint.range.max_ssa_var = var[p->constraint.range.max_var]; + } } for (j = 0; j < blocks[succ].predecessors_count; j++) { p->sources[j] = var[p->var]; @@ -749,18 +824,18 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, } /* }}} */ -int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags) /* {{{ */ +int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; zend_ssa_block *ssa_blocks; int blocks_count = ssa->cfg.blocks_count; uint32_t set_size; - zend_bitset tmp, gen, in; + zend_bitset def, in, phi; int *var = NULL; int i, j, k, changed; zend_dfg dfg; - ALLOCA_FLAG(dfg_use_heap); - ALLOCA_FLAG(var_use_heap); + ALLOCA_FLAG(dfg_use_heap) + ALLOCA_FLAG(var_use_heap) ssa->rt_constants = (build_flags & ZEND_RT_CONSTANTS); ssa_blocks = zend_arena_calloc(arena, blocks_count, sizeof(zend_ssa_block)); @@ -772,10 +847,9 @@ int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t b /* Compute Variable Liveness */ dfg.vars = op_array->last_var + op_array->T; dfg.size = set_size = zend_bitset_len(dfg.vars); - dfg.tmp = do_alloca((set_size * sizeof(zend_ulong)) * (blocks_count * 5 + 1), dfg_use_heap); - memset(dfg.tmp, 0, (set_size * sizeof(zend_ulong)) * (blocks_count * 5 + 1)); - dfg.gen = dfg.tmp + set_size; - dfg.def = dfg.gen + set_size * blocks_count; + dfg.tmp = do_alloca((set_size * sizeof(zend_ulong)) * (blocks_count * 4 + 1), dfg_use_heap); + memset(dfg.tmp, 0, (set_size * sizeof(zend_ulong)) * (blocks_count * 4 + 1)); + dfg.def = dfg.tmp + set_size; dfg.use = dfg.def + set_size * blocks_count; dfg.in = dfg.use + set_size * blocks_count; dfg.out = dfg.in + set_size * blocks_count; @@ -789,28 +863,43 @@ int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t b zend_dump_dfg(op_array, &ssa->cfg, &dfg); } - tmp = dfg.tmp; - gen = dfg.gen; + def = dfg.def; in = dfg.in; - /* SSA construction, Step 1: Propagate "gen" sets in merge points */ + /* Reuse the "use" set, as we no longer need it */ + phi = dfg.use; + zend_bitset_clear(phi, set_size * blocks_count); + + /* Place e-SSA pis. This will add additional "def" points, so it must + * happen before def propagation. */ + place_essa_pis(arena, script, op_array, build_flags, ssa, &dfg); + + /* SSA construction, Step 1: Propagate "def" sets in merge points */ do { changed = 0; for (j = 0; j < blocks_count; j++) { + zend_bitset def_j = def + j * set_size, phi_j = phi + j * set_size; if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } - if (j >= 0 && (blocks[j].predecessors_count > 1 || j == 0)) { - zend_bitset_copy(tmp, gen + (j * set_size), set_size); - for (k = 0; k < blocks[j].predecessors_count; k++) { - i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; - while (i != -1 && i != blocks[j].idom) { - zend_bitset_union_with_intersection(tmp, tmp, gen + (i * set_size), in + (j * set_size), set_size); - i = blocks[i].idom; + if (blocks[j].predecessors_count > 1) { + if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) { + /* Prevent any values from flowing into irreducible loops by + replacing all incoming values with explicit phis. The + register allocator depends on this property. */ + zend_bitset_union(phi_j, in + (j * set_size), set_size); + } else { + for (k = 0; k < blocks[j].predecessors_count; k++) { + i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; + while (i != -1 && i != blocks[j].idom) { + zend_bitset_union_with_intersection( + phi_j, phi_j, def + (i * set_size), in + (j * set_size), set_size); + i = blocks[i].idom; + } } } - if (!zend_bitset_equal(gen + (j * set_size), tmp, set_size)) { - zend_bitset_copy(gen + (j * set_size), tmp, set_size); + if (!zend_bitset_subset(phi_j, def_j, set_size)) { + zend_bitset_union(def_j, phi_j, set_size); changed = 1; } } @@ -823,128 +912,39 @@ int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t b free_alloca(dfg.tmp, dfg_use_heap); return FAILURE; } - zend_bitset_clear(tmp, set_size); for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } - if (blocks[j].predecessors_count > 1) { - zend_bitset_clear(tmp, set_size); - if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) { - /* Prevent any values from flowing into irreducible loops by - replacing all incoming values with explicit phis. The - register allocator depends on this property. */ - zend_bitset_copy(tmp, in + (j * set_size), set_size); - } else { - for (k = 0; k < blocks[j].predecessors_count; k++) { - i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; - while (i != -1 && i != blocks[j].idom) { - zend_bitset_union_with_intersection(tmp, tmp, gen + (i * set_size), in + (j * set_size), set_size); - i = blocks[i].idom; - } - } - } + if (!zend_bitset_empty(phi + j * set_size, set_size)) { + ZEND_BITSET_REVERSE_FOREACH(phi + j * set_size, set_size, i) { + zend_ssa_phi *phi = zend_arena_calloc(arena, 1, + sizeof(zend_ssa_phi) + + sizeof(int) * blocks[j].predecessors_count + + sizeof(void*) * blocks[j].predecessors_count); - if (!zend_bitset_empty(tmp, set_size)) { - i = op_array->last_var + op_array->T; - while (i > 0) { - i--; - if (zend_bitset_in(tmp, i)) { - zend_ssa_phi *phi = zend_arena_calloc(arena, 1, - sizeof(zend_ssa_phi) + - sizeof(int) * blocks[j].predecessors_count + - sizeof(void*) * blocks[j].predecessors_count); - - if (!phi) { - goto failure; - } - phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); - memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); - phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); - - phi->pi = -1; - phi->var = i; - phi->ssa_var = -1; - phi->next = ssa_blocks[j].phis; - ssa_blocks[j].phis = phi; - } - } - } - } - } + phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); + memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); + phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); - place_essa_pis(arena, op_array, build_flags, ssa, &dfg); + phi->pi = -1; + phi->var = i; + phi->ssa_var = -1; - /* SSA construction, Step ?: Phi after Pi placement based on Dominance Frontiers */ - for (j = 0; j < blocks_count; j++) { - if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { - continue; - } - if (blocks[j].predecessors_count > 1) { - zend_bitset_clear(tmp, set_size); - if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) { - /* Prevent any values from flowing into irreducible loops by - replacing all incoming values with explicit phis. The - register allocator depends on this property. */ - zend_bitset_copy(tmp, in + (j * set_size), set_size); - } else { - for (k = 0; k < blocks[j].predecessors_count; k++) { - i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; - while (i != -1 && i != blocks[j].idom) { - zend_ssa_phi *p = ssa_blocks[i].phis; - while (p) { - if (p) { - if (p->pi >= 0) { - if (zend_bitset_in(in + (j * set_size), p->var) && - !zend_bitset_in(gen + (i * set_size), p->var)) { - zend_bitset_incl(tmp, p->var); - } - } else { - zend_bitset_excl(tmp, p->var); - } - } - p = p->next; - } - i = blocks[i].idom; - } - } - } - - if (!zend_bitset_empty(tmp, set_size)) { - i = op_array->last_var + op_array->T; - while (i > 0) { - i--; - if (zend_bitset_in(tmp, i)) { - zend_ssa_phi **pp = &ssa_blocks[j].phis; - while (*pp) { - if ((*pp)->pi <= 0 && (*pp)->var == i) { - break; - } - pp = &(*pp)->next; - } - if (*pp == NULL) { - zend_ssa_phi *phi = zend_arena_calloc(arena, 1, - sizeof(zend_ssa_phi) + - sizeof(int) * blocks[j].predecessors_count + - sizeof(void*) * blocks[j].predecessors_count); - - if (!phi) { - goto failure; - } - phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); - memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); - phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); - - phi->pi = -1; - phi->var = i; - phi->ssa_var = -1; - phi->next = NULL; - *pp = phi; + /* Place phis after pis */ + { + zend_ssa_phi **pp = &ssa_blocks[j].phis; + while (*pp) { + if ((*pp)->pi < 0) { + break; } + pp = &(*pp)->next; } + phi->next = *pp; + *pp = phi; } - } + } ZEND_BITSET_FOREACH_END(); } } @@ -955,14 +955,13 @@ int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t b /* SSA construction, Step 3: Renaming */ ssa->ops = zend_arena_calloc(arena, op_array->last, sizeof(zend_ssa_op)); memset(ssa->ops, 0xff, op_array->last * sizeof(zend_ssa_op)); - memset(var, 0xff, (op_array->last_var + op_array->T) * sizeof(int)); + memset(var + op_array->last_var, 0xff, op_array->T * sizeof(int)); /* Create uninitialized SSA variables for each CV */ for (j = 0; j < op_array->last_var; j++) { var[j] = j; } ssa->vars_count = op_array->last_var; if (zend_ssa_rename(op_array, build_flags, ssa, var, 0) != SUCCESS) { -failure: free_alloca(var, var_use_heap); free_alloca(dfg.tmp, dfg_use_heap); return FAILURE; @@ -1044,13 +1043,16 @@ int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_ ssa_vars[phi->sources[0]].phi_use_chain = phi; } } - /* min and max variables can't be used together */ - if (phi->constraint.min_ssa_var >= 0) { - phi->sym_use_chain = ssa_vars[phi->constraint.min_ssa_var].sym_use_chain; - ssa_vars[phi->constraint.min_ssa_var].sym_use_chain = phi; - } else if (phi->constraint.max_ssa_var >= 0) { - phi->sym_use_chain = ssa_vars[phi->constraint.max_ssa_var].sym_use_chain; - ssa_vars[phi->constraint.max_ssa_var].sym_use_chain = phi; + if (phi->has_range_constraint) { + /* min and max variables can't be used together */ + zend_ssa_range_constraint *constraint = &phi->constraint.range; + if (constraint->min_ssa_var >= 0) { + phi->sym_use_chain = ssa_vars[constraint->min_ssa_var].sym_use_chain; + ssa_vars[constraint->min_ssa_var].sym_use_chain = phi; + } else if (constraint->max_ssa_var >= 0) { + phi->sym_use_chain = ssa_vars[constraint->max_ssa_var].sym_use_chain; + ssa_vars[constraint->max_ssa_var].sym_use_chain = phi; + } } } else { int j; @@ -1076,7 +1078,7 @@ int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_ } /* }}} */ -int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) +int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) /* {{{ */ { if (ssa->vars[var].use_chain == op) { ssa->vars[var].use_chain = zend_ssa_next_use(ssa->ops, var, op); @@ -1115,6 +1117,7 @@ int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) return 0; } } +/* }}} */ /* * Local variables: diff --git a/ext/opcache/Optimizer/zend_ssa.h b/ext/opcache/Optimizer/zend_ssa.h index dbf260c4d8..653a61fc10 100644 --- a/ext/opcache/Optimizer/zend_ssa.h +++ b/ext/opcache/Optimizer/zend_ssa.h @@ -19,6 +19,7 @@ #ifndef ZEND_SSA_H #define ZEND_SSA_H +#include "zend_optimizer.h" #include "zend_cfg.h" typedef struct _zend_ssa_range { @@ -38,14 +39,23 @@ typedef enum _zend_ssa_negative_lat { } zend_ssa_negative_lat; /* Special kind of SSA Phi function used in eSSA */ -typedef struct _zend_ssa_pi_constraint { +typedef struct _zend_ssa_range_constraint { zend_ssa_range range; /* simple range constraint */ int min_var; int max_var; int min_ssa_var; /* ((min_var>0) ? MIN(ssa_var) : 0) + range.min */ - int max_ssa_var; /* ((man_var>0) ? MAX(ssa_var) : 0) + range.man */ + int max_ssa_var; /* ((max_var>0) ? MAX(ssa_var) : 0) + range.max */ zend_ssa_negative_lat negative; - uint32_t type_mask; /* If -1 this is a range constraint */ +} zend_ssa_range_constraint; + +typedef struct _zend_ssa_type_constraint { + uint32_t type_mask; /* Type mask to intersect with */ + zend_class_entry *ce; /* Class entry for instanceof constraints */ +} zend_ssa_type_constraint; + +typedef union _zend_ssa_pi_constraint { + zend_ssa_range_constraint range; + zend_ssa_type_constraint type; } zend_ssa_pi_constraint; /* SSA Phi - ssa_var = Phi(source0, source1, ...sourceN) */ @@ -57,7 +67,8 @@ struct _zend_ssa_phi { int var; /* Original CV, VAR or TMP variable index */ int ssa_var; /* SSA variable index */ int block; /* current BB index */ - int visited; /* flag to avoid recursive processing */ + int visited : 1; /* flag to avoid recursive processing */ + int has_range_constraint : 1; zend_ssa_phi **use_chains; zend_ssa_phi *sym_use_chain; int *sources; /* Array of SSA IDs that produce this var. @@ -116,7 +127,7 @@ typedef struct _zend_ssa { BEGIN_EXTERN_C() -int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags); +int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags); int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa); int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var); diff --git a/ext/opcache/README b/ext/opcache/README index c074440130..e4d36ba51a 100644 --- a/ext/opcache/README +++ b/ext/opcache/README @@ -213,3 +213,6 @@ opcache.mmap_base processes have to map shared memory into the same address space. This directive allows to manually fix the "Unable to reattach to base address" errors. + +opcache.lockfile_path (default "/tmp") + Absolute path used to store shared lockfiles (for *nix only) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index f1dc0e185c..222935bad0 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -97,7 +97,7 @@ zend_accel_globals accel_globals; #else int accel_globals_id; #if defined(COMPILE_DL_OPCACHE) -ZEND_TSRMLS_CACHE_DEFINE(); +ZEND_TSRMLS_CACHE_DEFINE() #endif #endif @@ -225,7 +225,11 @@ static inline void accel_restart_enter(void) #ifdef ZEND_WIN32 INCREMENT(restart_in); #else +# ifdef _AIX + static FLOCK_STRUCTURE(restart_in_progress, F_WRLCK, SEEK_SET, 2, 1); +# else static const FLOCK_STRUCTURE(restart_in_progress, F_WRLCK, SEEK_SET, 2, 1); +#endif if (fcntl(lock_file, F_SETLK, &restart_in_progress) == -1) { zend_accel_error(ACCEL_LOG_DEBUG, "RestartC(+1): %s (%d)", strerror(errno), errno); @@ -240,7 +244,11 @@ static inline void accel_restart_leave(void) ZCSG(restart_in_progress) = 0; DECREMENT(restart_in); #else +# ifdef _AIX + static FLOCK_STRUCTURE(restart_finished, F_UNLCK, SEEK_SET, 2, 1); +# else static const FLOCK_STRUCTURE(restart_finished, F_UNLCK, SEEK_SET, 2, 1); +# endif ZCSG(restart_in_progress) = 0; if (fcntl(lock_file, F_SETLK, &restart_finished) == -1) { @@ -278,7 +286,11 @@ static inline int accel_activate_add(void) #ifdef ZEND_WIN32 INCREMENT(mem_usage); #else +# ifdef _AIX + static FLOCK_STRUCTURE(mem_usage_lock, F_RDLCK, SEEK_SET, 1, 1); +# else static const FLOCK_STRUCTURE(mem_usage_lock, F_RDLCK, SEEK_SET, 1, 1); +# endif if (fcntl(lock_file, F_SETLK, &mem_usage_lock) == -1) { zend_accel_error(ACCEL_LOG_DEBUG, "UpdateC(+1): %s (%d)", strerror(errno), errno); @@ -297,7 +309,11 @@ static inline void accel_deactivate_sub(void) ZCG(counted) = 0; } #else +# ifdef _AIX + static FLOCK_STRUCTURE(mem_usage_unlock, F_UNLCK, SEEK_SET, 1, 1); +# else static const FLOCK_STRUCTURE(mem_usage_unlock, F_UNLCK, SEEK_SET, 1, 1); +# endif if (fcntl(lock_file, F_SETLK, &mem_usage_unlock) == -1) { zend_accel_error(ACCEL_LOG_DEBUG, "UpdateC(-1): %s (%d)", strerror(errno), errno); @@ -310,7 +326,11 @@ static inline void accel_unlock_all(void) #ifdef ZEND_WIN32 accel_deactivate_sub(); #else +# ifdef _AIX + static FLOCK_STRUCTURE(mem_usage_unlock_all, F_UNLCK, SEEK_SET, 0, 0); +# else static const FLOCK_STRUCTURE(mem_usage_unlock_all, F_UNLCK, SEEK_SET, 0, 0); +# endif if (fcntl(lock_file, F_SETLK, &mem_usage_unlock_all) == -1) { zend_accel_error(ACCEL_LOG_DEBUG, "UnlockAll: %s (%d)", strerror(errno), errno); @@ -503,6 +523,9 @@ static void accel_use_shm_interned_strings(void) s[1] = 0; CG(one_char_string)[j] = accel_new_interned_string(zend_string_init(s, 1, 0)); } + for (j = 0; j < CG(known_strings_count); j++) { + CG(known_strings)[j] = accel_new_interned_string(CG(known_strings)[j]); + } /* function table hash keys */ for (idx = 0; idx < CG(function_table)->nNumUsed; idx++) { @@ -2918,7 +2941,7 @@ void accelerator_shm_read_unlock(void) ZEND_EXT_API zend_extension zend_extension_entry = { ACCELERATOR_PRODUCT_NAME, /* name */ - ACCELERATOR_VERSION, /* version */ + PHP_VERSION, /* version */ "Zend Technologies", /* author */ "http://www.zend.com/", /* URL */ "Copyright (c) 1999-2016", /* copyright */ diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 7a6cf8b108..48e0c1b4cc 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -27,8 +27,6 @@ #endif #define ACCELERATOR_PRODUCT_NAME "Zend OPcache" -#define PHP_ZENDOPCACHE_VERSION "7.0.6-dev" -#define ACCELERATOR_VERSION PHP_ZENDOPCACHE_VERSION /* 2 - added Profiler support, on 20010712 */ /* 3 - added support for Optimizer's encoded-only-files mode */ /* 4 - works with the new Optimizer, that supports the file format with licenses */ @@ -195,6 +193,9 @@ typedef struct _zend_accel_directives { zend_long max_file_size; zend_long interned_strings_buffer; char *restrict_api; +#ifndef ZEND_WIN32 + char *lockfile_path; +#endif #ifdef HAVE_OPCACHE_FILE_CACHE char *file_cache; zend_bool file_cache_only; @@ -286,7 +287,7 @@ extern zend_accel_shared_globals *accel_shared_globals; # define ZCG(v) ZEND_TSRMG(accel_globals_id, zend_accel_globals *, v) extern int accel_globals_id; # ifdef COMPILE_DL_OPCACHE -ZEND_TSRMLS_CACHE_EXTERN(); +ZEND_TSRMLS_CACHE_EXTERN() # endif #else # define ZCG(v) (accel_globals.v) diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 15613285ff..ded7f3dab2 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -6,11 +6,11 @@ PHP_ARG_ENABLE(opcache, whether to enable Zend OPcache support, [ --disable-opcache Disable Zend OPcache support], yes) PHP_ARG_ENABLE(opcache-file, whether to enable file based caching, -[ --disable-opcache-file Disable file based caching], yes) +[ --disable-opcache-file Disable file based caching], yes, no) PHP_ARG_ENABLE(huge-code-pages, whether to enable copying PHP CODE pages into HUGE PAGES, [ --disable-huge-code-pages - Disable copying PHP CODE pages into HUGE PAGES], yes) + Disable copying PHP CODE pages into HUGE PAGES], yes, no) if test "$PHP_OPCACHE" != "no"; then @@ -376,7 +376,7 @@ AC_TRY_RUN([ AC_MSG_RESULT("yes") ], AC_MSG_RESULT("no") ) -if test "$flock_type" == "unknown"; then +if test "$flock_type" = "unknown"; then AC_MSG_ERROR([Don't know how to define struct flock on this system[,] set --enable-opcache=no]) fi diff --git a/ext/opcache/tests/bug71843.phpt b/ext/opcache/tests/bug71843.phpt new file mode 100644 index 0000000000..73301a6e96 --- /dev/null +++ b/ext/opcache/tests/bug71843.phpt @@ -0,0 +1,25 @@ +--TEST-- +Bug #71843 (null ptr deref ZEND_RETURN_SPEC_CONST_HANDLER (zend_vm_execute.h:3479)) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0xFFFFBFFF +--SKIPIF-- +<?php if (!extension_loaded('Zend OPcache')) die("skip"); ?> +--FILE-- +<?php +0 & ~E & ~R; +6 && ~See +?> +okey +--EXPECTF-- +Notice: Use of undefined constant E - assumed 'E' in %sbug71843.php on line %d + +Warning: A non-numeric value encountered in %s on line %d + +Notice: Use of undefined constant R - assumed 'R' in %sbug71843.php on line %d + +Warning: A non-numeric value encountered in %s on line %d + +Notice: Use of undefined constant See - assumed 'See' in %sbug71843.php on line %d +okey diff --git a/ext/opcache/tests/bug72014.phpt b/ext/opcache/tests/bug72014.phpt new file mode 100644 index 0000000000..d2ad96c0f1 --- /dev/null +++ b/ext/opcache/tests/bug72014.phpt @@ -0,0 +1,29 @@ +--TEST-- +Bug #72014 (Including a file with anonymous classes multiple times leads to fatal error) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +file_put_contents(__DIR__ . "/bug72014.annon.php", <<<PHP +<?php +\$a = new class() { public \$testvar = "Foo\n"; }; +echo \$a->testvar; +PHP +); + +include(__DIR__ . "/bug72014.annon.php"); +include(__DIR__ . "/bug72014.annon.php"); +include(__DIR__ . "/bug72014.annon.php"); +?> +--CLEAN-- +<?php +@unlink(__DIR__ . "/bug72014.annon.php") +?> +--EXPECT-- +Foo +Foo +Foo diff --git a/ext/opcache/tests/issue0140.phpt b/ext/opcache/tests/issue0140.phpt index 98e0e45cc2..97fc11b3c7 100644 --- a/ext/opcache/tests/issue0140.phpt +++ b/ext/opcache/tests/issue0140.phpt @@ -8,6 +8,7 @@ opcache.file_update_protection=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> <?php if (php_sapi_name() != "cli") die("skip CLI only"); ?> +<?php if (getenv("SKIP_SLOW_TESTS")) die("skip slow tests excluded by request") ?> --FILE-- <?php define("FILENAME", dirname(__FILE__) . "/issuer0140.inc.php"); diff --git a/ext/opcache/tests/ssa_bug_002.phpt b/ext/opcache/tests/ssa_bug_002.phpt new file mode 100644 index 0000000000..9ff6f799f4 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Incorrect NOP removal on jump to NOP +--FILE-- +<?php + +function test(int $i) : int { + if ($i == 1) { + $x = $i + 1; + } + return $i; +} +var_dump(test(42)); + +?> +--EXPECT-- +int(42) diff --git a/ext/opcache/tests/ssa_bug_003.phpt b/ext/opcache/tests/ssa_bug_003.phpt new file mode 100644 index 0000000000..2895551c08 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_003.phpt @@ -0,0 +1,38 @@ +--TEST-- +Incorrect elision of return type checks +--FILE-- +<?php + +function test1($x) : callable { + if ($x == 1) { + $c = 'foo'; + } elseif ($x == 2) { + $c = new stdClass; + } else { + $c = [$x => &$x]; + } + return $c; +} + +try { + test1(1); +} catch (Error $e) { + echo "Error: {$e->getMessage()}\n"; +} + +class Foo {} +function test2() : Foo { + $obj = new stdClass; + return $obj; +} + +try { + test2(); +} catch (Error $e) { + echo "Error: {$e->getMessage()}\n"; +} + +?> +--EXPECT-- +Error: Return value of test1() must be callable, string returned +Error: Return value of test2() must be an instance of Foo, instance of stdClass returned diff --git a/ext/opcache/tests/ssa_bug_004.phpt b/ext/opcache/tests/ssa_bug_004.phpt new file mode 100644 index 0000000000..20e313045a --- /dev/null +++ b/ext/opcache/tests/ssa_bug_004.phpt @@ -0,0 +1,19 @@ +--TEST-- +Assign elision exception safety: ICALL +--FILE-- +<?php + +set_error_handler(function() { throw new Exception; }); + +function test() { + $x = str_replace(['foo'], [[]], ['foo']); +} +try { + test(); +} catch (Exception $e) { + echo "caught\n"; +} + +?> +--EXPECT-- +caught diff --git a/ext/opcache/tests/ssa_bug_005.phpt b/ext/opcache/tests/ssa_bug_005.phpt new file mode 100644 index 0000000000..fb373e36b3 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_005.phpt @@ -0,0 +1,24 @@ +--TEST-- +Assign elision exception safety: UCALL +--FILE-- +<?php + +function test() { + $dtor = new class { function __destruct() { throw new Exception; } }; + $a = 1; + return [0, $a]; +} + +function test2() { + $x = test(); +} + +try { + test2(); +} catch (Exception $e) { + echo "caught\n"; +} + +?> +--EXPECT-- +caught diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 21d4e2a71e..61698b4a9f 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -300,7 +300,9 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.error_log" , "" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.error_log, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.restrict_api" , "" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.restrict_api, zend_accel_globals, accel_globals) -#ifdef ZEND_WIN32 +#ifndef ZEND_WIN32 + STD_PHP_INI_ENTRY("opcache.lockfile_path" , "/tmp" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.lockfile_path, zend_accel_globals, accel_globals) +#else STD_PHP_INI_ENTRY("opcache.mmap_base", NULL, PHP_INI_SYSTEM, OnUpdateString, accel_directives.mmap_base, zend_accel_globals, accel_globals) #endif @@ -516,7 +518,7 @@ static zend_module_entry accel_module_entry = { NULL, NULL, zend_accel_info, - ACCELERATOR_VERSION "FE", + PHP_VERSION, NO_MODULE_GLOBALS, accel_post_deactivate, STANDARD_MODULE_PROPERTIES_EX @@ -712,6 +714,10 @@ static ZEND_FUNCTION(opcache_get_configuration) add_assoc_bool(&directives, "opcache.enable_file_override", ZCG(accel_directives).file_override_enabled); add_assoc_long(&directives, "opcache.optimization_level", ZCG(accel_directives).optimization_level); +#ifndef ZEND_WIN32 + add_assoc_string(&directives, "opcache.lockfile_path", STRING_NOT_NULL(ZCG(accel_directives).lockfile_path)); +#endif + #ifdef HAVE_OPCACHE_FILE_CACHE add_assoc_string(&directives, "opcache.file_cache", ZCG(accel_directives).file_cache ? ZCG(accel_directives).file_cache : ""); add_assoc_bool(&directives, "opcache.file_cache_only", ZCG(accel_directives).file_cache_only); @@ -722,7 +728,7 @@ static ZEND_FUNCTION(opcache_get_configuration) /*version */ array_init(&version); - add_assoc_string(&version, "version", ACCELERATOR_VERSION); + add_assoc_string(&version, "version", PHP_VERSION); add_assoc_string(&version, "opcache_product_name", ACCELERATOR_PRODUCT_NAME); add_assoc_zval(return_value, "version", &version); diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 27731dd624..61c9c15d2b 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -40,8 +40,6 @@ typedef int (*id_function_t)(void *, void *); typedef void (*unique_copy_ctor_func_t)(void *pElement); -static zend_ast *zend_ast_clone(zend_ast *ast); - static void zend_accel_destroy_zend_function(zval *zv) { zend_function *function = Z_PTR_P(zv); @@ -169,61 +167,6 @@ static inline void zend_clone_zval(zval *src) src = Z_REFVAL_P(src); } } - if (Z_TYPE_P(src) == IS_CONSTANT_AST) { - if (Z_REFCOUNT_P(src) > 1 && (ptr = accel_xlat_get(Z_AST_P(src))) != NULL) { - Z_AST_P(src) = ptr; - } else { - zend_ast_ref *old = Z_AST_P(src); - - ZVAL_NEW_AST(src, old->ast); - Z_AST_P(src)->gc = old->gc; - if (Z_REFCOUNT_P(src) > 1) { - accel_xlat_set(old, Z_AST_P(src)); - } - Z_ASTVAL_P(src) = zend_ast_clone(Z_ASTVAL_P(src)); - } - } -} - -static zend_ast *zend_ast_clone(zend_ast *ast) -{ - uint32_t i; - - if (ast->kind == ZEND_AST_ZVAL) { - zend_ast_zval *copy = emalloc(sizeof(zend_ast_zval)); - copy->kind = ZEND_AST_ZVAL; - copy->attr = ast->attr; - ZVAL_COPY_VALUE(©->val, zend_ast_get_zval(ast)); - return (zend_ast *) copy; - } else if (zend_ast_is_list(ast)) { - zend_ast_list *list = zend_ast_get_list(ast); - zend_ast_list *copy = emalloc( - sizeof(zend_ast_list) - sizeof(zend_ast *) + sizeof(zend_ast *) * list->children); - copy->kind = list->kind; - copy->attr = list->attr; - copy->children = list->children; - for (i = 0; i < list->children; i++) { - if (list->child[i]) { - copy->child[i] = zend_ast_clone(list->child[i]); - } else { - copy->child[i] = NULL; - } - } - return (zend_ast *) copy; - } else { - uint32_t children = zend_ast_get_num_children(ast); - zend_ast *copy = emalloc(sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children); - copy->kind = ast->kind; - copy->attr = ast->attr; - for (i = 0; i < children; i++) { - if (ast->child[i]) { - copy->child[i] = zend_ast_clone(ast->child[i]); - } else { - copy->child[i] = NULL; - } - } - return copy; - } } static void zend_hash_clone_constants(HashTable *ht, HashTable *source) @@ -617,7 +560,6 @@ failure: static void zend_accel_class_hash_copy(HashTable *target, HashTable *source, unique_copy_ctor_func_t pCopyConstructor) { - zend_class_entry *ce1; Bucket *p, *end; zval *t; @@ -633,7 +575,17 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source, uni /* Mangled key - ignore and wait for runtime */ continue; } else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) { - goto failure; + zend_class_entry *ce1 = Z_PTR(p->val); + if (!(ce1->ce_flags & ZEND_ACC_ANON_CLASS)) { + CG(in_compilation) = 1; + zend_set_compiled_filename(ce1->info.user.filename); + CG(zend_lineno) = ce1->info.user.line_start; + zend_error(E_ERROR, + "Cannot declare %s %s, because the name is already in use", + zend_get_object_type(ce1), ZSTR_VAL(ce1->name)); + return; + } + continue; } } else { t = _zend_hash_append_ptr(target, p->key, Z_PTR(p->val)); @@ -644,13 +596,6 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source, uni } target->nInternalPointer = target->nNumOfElements ? 0 : HT_INVALID_IDX; return; - -failure: - ce1 = Z_PTR(p->val); - CG(in_compilation) = 1; - zend_set_compiled_filename(ce1->info.user.filename); - CG(zend_lineno) = ce1->info.user.line_start; - zend_error(E_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce1), ZSTR_VAL(ce1->name)); } #ifdef __SSE2__ diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 2184267ecd..6ac4f0e35f 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -373,7 +373,6 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra } if (!IS_SERIALIZED(op_array->opcodes)) { -#if ZEND_USE_ABS_CONST_ADDR || ZEND_USE_ABS_JMP_ADDR zend_op *opline, *end; SERIALIZE_PTR(op_array->opcodes); @@ -381,15 +380,15 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra UNSERIALIZE_PTR(opline); end = opline + op_array->last; while (opline < end) { -# if ZEND_USE_ABS_CONST_ADDR +#if ZEND_USE_ABS_CONST_ADDR if (opline->op1_type == IS_CONST) { SERIALIZE_PTR(opline->op1.zv); } if (opline->op2_type == IS_CONST) { SERIALIZE_PTR(opline->op2.zv); } -# endif -# if ZEND_USE_ABS_JMP_ADDR +#endif +#if ZEND_USE_ABS_JMP_ADDR switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: @@ -404,7 +403,6 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: - case ZEND_NEW: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: @@ -417,12 +415,10 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra /* relative extended_value don't have to be changed */ break; } -# endif +#endif + zend_serialize_opcode_handler(opline); opline++; } -#else - SERIALIZE_PTR(op_array->opcodes); -#endif if (op_array->arg_info) { zend_arg_info *p, *end; @@ -981,7 +977,6 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: - case ZEND_NEW: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: @@ -995,7 +990,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr break; } # endif - ZEND_VM_SET_OPCODE_HANDLER(opline); + zend_deserialize_opcode_handler(opline); opline++; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 48fefd8918..25a504575d 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -65,7 +65,6 @@ typedef void (*zend_persist_func_t)(zval*); static void zend_persist_zval(zval *z); -static void zend_persist_zval_const(zval *z); static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX}; @@ -204,7 +203,7 @@ static void zend_hash_persist_immutable(HashTable *ht) } /* persist the data itself */ - zend_persist_zval_const(&p->val); + zend_persist_zval(&p->val); nIndex = p->h | ht->nTableMask; Z_NEXT(p->val) = HT_HASH(ht, nIndex); @@ -229,7 +228,7 @@ static void zend_hash_persist_immutable(HashTable *ht) } /* persist the data itself */ - zend_persist_zval_const(&p->val); + zend_persist_zval(&p->val); } } @@ -278,63 +277,10 @@ static void zend_persist_zval(zval *z) zend_accel_store_interned_string(Z_STR_P(z)); Z_GC_FLAGS_P(z) |= flags; Z_TYPE_FLAGS_P(z) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE); - break; - case IS_ARRAY: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_ARR_P(z)); - if (new_ptr) { - Z_ARR_P(z) = new_ptr; - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - } else { - if (Z_IMMUTABLE_P(z)) { - Z_ARR_P(z) = zend_accel_memdup(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist_immutable(Z_ARRVAL_P(z)); - } else { - GC_REMOVE_FROM_BUFFER(Z_ARR_P(z)); - zend_accel_store(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist(Z_ARRVAL_P(z), zend_persist_zval); - /* make immutable array */ - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - GC_REFCOUNT(Z_COUNTED_P(z)) = 2; - GC_FLAGS(Z_COUNTED_P(z)) |= IS_ARRAY_IMMUTABLE; - Z_ARRVAL_P(z)->u.flags |= HASH_FLAG_STATIC_KEYS; - Z_ARRVAL_P(z)->u.flags &= ~HASH_FLAG_APPLY_PROTECTION; - } - } - break; - case IS_REFERENCE: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_REF_P(z)); - if (new_ptr) { - Z_REF_P(z) = new_ptr; - } else { - zend_accel_store(Z_REF_P(z), sizeof(zend_reference)); - zend_persist_zval(Z_REFVAL_P(z)); - } - break; - case IS_CONSTANT_AST: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_AST_P(z)); - if (new_ptr) { - Z_AST_P(z) = new_ptr; - } else { - zend_accel_store(Z_AST_P(z), sizeof(zend_ast_ref)); - Z_ASTVAL_P(z) = zend_persist_ast(Z_ASTVAL_P(z)); + if (Z_TYPE_P(z) == IS_CONSTANT) { + Z_TYPE_FLAGS_P(z) |= IS_TYPE_IMMUTABLE; } break; - } -} - -static void zend_persist_zval_static(zval *z) -{ - zend_uchar flags; - void *new_ptr; - - switch (Z_TYPE_P(z)) { - case IS_STRING: - case IS_CONSTANT: - flags = Z_GC_FLAGS_P(z) & ~ (IS_STR_PERSISTENT | IS_STR_INTERNED | IS_STR_PERMANENT); - zend_accel_store_interned_string(Z_STR_P(z)); - Z_GC_FLAGS_P(z) |= flags; - Z_TYPE_FLAGS_P(z) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE); - break; case IS_ARRAY: new_ptr = zend_shared_alloc_get_xlat_entry(Z_ARR_P(z)); if (new_ptr) { @@ -381,62 +327,6 @@ static void zend_persist_zval_static(zval *z) } } -static void zend_persist_zval_const(zval *z) -{ - zend_uchar flags; - void *new_ptr; - - switch (Z_TYPE_P(z)) { - case IS_STRING: - case IS_CONSTANT: - flags = Z_GC_FLAGS_P(z) & ~ (IS_STR_PERSISTENT | IS_STR_INTERNED | IS_STR_PERMANENT); - zend_accel_memdup_interned_string(Z_STR_P(z)); - Z_GC_FLAGS_P(z) |= flags; - Z_TYPE_FLAGS_P(z) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE); - break; - case IS_ARRAY: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_ARR_P(z)); - if (new_ptr) { - Z_ARR_P(z) = new_ptr; - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - } else { - if (Z_IMMUTABLE_P(z)) { - Z_ARR_P(z) = zend_accel_memdup(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist_immutable(Z_ARRVAL_P(z)); - } else { - GC_REMOVE_FROM_BUFFER(Z_ARR_P(z)); - zend_accel_store(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist(Z_ARRVAL_P(z), zend_persist_zval); - /* make immutable array */ - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - GC_REFCOUNT(Z_COUNTED_P(z)) = 2; - GC_FLAGS(Z_COUNTED_P(z)) |= IS_ARRAY_IMMUTABLE; - Z_ARRVAL_P(z)->u.flags |= HASH_FLAG_STATIC_KEYS; - Z_ARRVAL_P(z)->u.flags &= ~HASH_FLAG_APPLY_PROTECTION; - } - } - break; - case IS_REFERENCE: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_REF_P(z)); - if (new_ptr) { - Z_REF_P(z) = new_ptr; - } else { - zend_accel_store(Z_REF_P(z), sizeof(zend_reference)); - zend_persist_zval(Z_REFVAL_P(z)); - } - break; - case IS_CONSTANT_AST: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_AST_P(z)); - if (new_ptr) { - Z_AST_P(z) = new_ptr; - } else { - zend_accel_store(Z_AST_P(z), sizeof(zend_ast_ref)); - Z_ASTVAL_P(z) = zend_persist_ast(Z_ASTVAL_P(z)); - } - break; - } -} - static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_script* main_persistent_script) { int already_stored = 0; @@ -472,7 +362,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc if (stored) { op_array->static_variables = stored; } else { - zend_hash_persist(op_array->static_variables, zend_persist_zval_static); + zend_hash_persist(op_array->static_variables, zend_persist_zval); zend_accel_store(op_array->static_variables, sizeof(HashTable)); /* make immutable array */ GC_REFCOUNT(op_array->static_variables) = 2; @@ -541,7 +431,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: - case ZEND_NEW: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index deae886991..ff42c4cf0b 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -38,7 +38,6 @@ # include "sys/mman.h" #endif -#define TMP_DIR "/tmp" #define SEM_FILENAME_PREFIX ".ZendSem." #define S_H(s) g_shared_alloc_handler->s @@ -55,7 +54,7 @@ zend_smm_shared_globals *smm_shared_globals; static MUTEX_T zts_lock; #endif int lock_file; -static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8]; +static char lockfile_name[MAXPATHLEN]; #endif static const zend_shared_memory_handler_entry handler_table[] = { @@ -75,7 +74,7 @@ static const zend_shared_memory_handler_entry handler_table[] = { }; #ifndef ZEND_WIN32 -void zend_shared_alloc_create_lock(void) +void zend_shared_alloc_create_lock(char *lockfile_path) { int val; @@ -83,7 +82,7 @@ void zend_shared_alloc_create_lock(void) zts_lock = tsrm_mutex_alloc(); #endif - sprintf(lockfile_name, "%s/%sXXXXXX", TMP_DIR, SEM_FILENAME_PREFIX); + snprintf(lockfile_name, sizeof(lockfile_name), "%s/%sXXXXXX", lockfile_path, SEM_FILENAME_PREFIX); lock_file = mkstemp(lockfile_name); fchmod(lock_file, 0666); @@ -163,7 +162,11 @@ int zend_shared_alloc_startup(size_t requested_size) smm_shared_globals = &tmp_shared_globals; ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */ +#ifndef ZEND_WIN32 + zend_shared_alloc_create_lock(ZCG(accel_directives).lockfile_path); +#else zend_shared_alloc_create_lock(); +#endif if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) { char *model = ZCG(accel_directives).memory_model; @@ -496,6 +499,10 @@ void zend_accel_shared_protect(int mode) #ifdef HAVE_MPROTECT int i; + if (!smm_shared_globals) { + return; + } + if (mode) { mode = PROT_READ; } else { |
