summaryrefslogtreecommitdiff
path: root/ext/opcache
diff options
context:
space:
mode:
Diffstat (limited to 'ext/opcache')
-rw-r--r--ext/opcache/Optimizer/block_pass.c121
-rw-r--r--ext/opcache/Optimizer/compact_literals.c12
-rw-r--r--ext/opcache/Optimizer/dfa_pass.c534
-rw-r--r--ext/opcache/Optimizer/nop_removal.c2
-rw-r--r--ext/opcache/Optimizer/optimize_func_calls.c37
-rw-r--r--ext/opcache/Optimizer/pass1_5.c4
-rw-r--r--ext/opcache/Optimizer/pass2.c23
-rw-r--r--ext/opcache/Optimizer/zend_call_graph.c43
-rw-r--r--ext/opcache/Optimizer/zend_cfg.c155
-rw-r--r--ext/opcache/Optimizer/zend_cfg.h5
-rw-r--r--ext/opcache/Optimizer/zend_dfg.c105
-rw-r--r--ext/opcache/Optimizer/zend_dfg.h1
-rw-r--r--ext/opcache/Optimizer/zend_dump.c65
-rw-r--r--ext/opcache/Optimizer/zend_func_info.c66
-rw-r--r--ext/opcache/Optimizer/zend_inference.c1493
-rw-r--r--ext/opcache/Optimizer/zend_inference.h38
-rw-r--r--ext/opcache/Optimizer/zend_optimizer.c170
-rw-r--r--ext/opcache/Optimizer/zend_optimizer_internal.h3
-rw-r--r--ext/opcache/Optimizer/zend_ssa.c371
-rw-r--r--ext/opcache/Optimizer/zend_ssa.h21
-rw-r--r--ext/opcache/README3
-rw-r--r--ext/opcache/ZendAccelerator.c27
-rw-r--r--ext/opcache/ZendAccelerator.h7
-rw-r--r--ext/opcache/config.m46
-rw-r--r--ext/opcache/tests/bug71843.phpt25
-rw-r--r--ext/opcache/tests/bug72014.phpt29
-rw-r--r--ext/opcache/tests/issue0140.phpt1
-rw-r--r--ext/opcache/tests/ssa_bug_002.phpt16
-rw-r--r--ext/opcache/tests/ssa_bug_003.phpt38
-rw-r--r--ext/opcache/tests/ssa_bug_004.phpt19
-rw-r--r--ext/opcache/tests/ssa_bug_005.phpt24
-rw-r--r--ext/opcache/zend_accelerator_module.c12
-rw-r--r--ext/opcache/zend_accelerator_util_funcs.c77
-rw-r--r--ext/opcache/zend_file_cache.c17
-rw-r--r--ext/opcache/zend_persist.c121
-rw-r--r--ext/opcache/zend_shared_alloc.c15
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(&copy->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 {