Avoid need of insertion NOP opcoes between unrelated SMART BRANCH instruction and following JMPZ/JMPNZ.
Now instead of checking the opcode of following instruction, the same information is encoded into SMART BRANH result_type.
static inline uint32_t zend_emit_cond_jump(zend_uchar opcode, znode *cond, uint32_t opnum_target) /* {{{ */
{
uint32_t opnum = get_next_op_number();
- zend_op *opline;
-
- if ((cond->op_type & (IS_CV|IS_CONST))
- && opnum > 0
- && zend_is_smart_branch(CG(active_op_array)->opcodes + opnum - 1)) {
- /* emit extra NOP to avoid incorrect SMART_BRANCH in very rare cases */
- zend_emit_op(NULL, ZEND_NOP, NULL, NULL);
- opnum = get_next_op_number();
+ zend_op *opline = CG(active_op_array)->opcodes + opnum - 1;
+
+ if (cond->op_type == IS_TMP_VAR && opnum > 0) {
+ opline = CG(active_op_array)->opcodes + opnum - 1;
+ if (opline->result_type == IS_TMP_VAR
+ && opline->result.var == cond->u.op.var
+ && zend_is_smart_branch(opline)) {
+ if (opcode == ZEND_JMPZ) {
+ opline->result_type = IS_TMP_VAR | IS_SMART_BRANCH_JMPZ;
+ } else {
+ ZEND_ASSERT(opcode == ZEND_JMPNZ);
+ opline->result_type = IS_TMP_VAR | IS_SMART_BRANCH_JMPNZ;
+ }
+ }
}
opline = zend_emit_op(NULL, opcode, cond, NULL);
opline->op2.opline_num = opnum_target;
#define IS_VAR (1<<2)
#define IS_CV (1<<3) /* Compiled variable */
+/* Used for result.type of smart branch instructions */
+#define IS_SMART_BRANCH_JMPZ (1<<4)
+#define IS_SMART_BRANCH_JMPNZ (1<<5)
+
#define ZEND_EXTRA_VALUE 1
#include "zend_globals.h"
#define ZEND_VM_SMART_BRANCH(_result, _check) do { \
if ((_check) && UNEXPECTED(EG(exception))) { \
OPLINE = EX(opline); \
- } else if (EXPECTED((opline+1)->opcode == ZEND_JMPZ)) { \
+ } else if (EXPECTED(opline->result_type == (IS_SMART_BRANCH_JMPZ|IS_TMP_VAR))) { \
if (_result) { \
ZEND_VM_SET_NEXT_OPCODE(opline + 2); \
} else { \
ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline + 1, (opline+1)->op2)); \
} \
- } else if (EXPECTED((opline+1)->opcode == ZEND_JMPNZ)) { \
+ } else if (EXPECTED(opline->result_type == (IS_SMART_BRANCH_JMPNZ|IS_TMP_VAR))) { \
if (!(_result)) { \
ZEND_VM_SET_NEXT_OPCODE(opline + 2); \
} else { \
ZEND_VM_CONTINUE(); \
} while (0)
#define ZEND_VM_SMART_BRANCH_TRUE() do { \
- if (EXPECTED((opline+1)->opcode == ZEND_JMPNZ)) { \
+ if (EXPECTED(opline->result_type == (IS_SMART_BRANCH_JMPNZ|IS_TMP_VAR))) { \
ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline + 1, (opline+1)->op2)); \
- } else if (EXPECTED((opline+1)->opcode == ZEND_JMPZ)) { \
+ } else if (EXPECTED(opline->result_type == (IS_SMART_BRANCH_JMPZ|IS_TMP_VAR))) { \
ZEND_VM_SET_NEXT_OPCODE(opline + 2); \
} else { \
ZVAL_TRUE(EX_VAR(opline->result.var)); \
ZEND_VM_NEXT_OPCODE(); \
} while (0)
#define ZEND_VM_SMART_BRANCH_FALSE() do { \
- if (EXPECTED((opline+1)->opcode == ZEND_JMPNZ)) { \
+ if (EXPECTED(opline->result_type == (IS_SMART_BRANCH_JMPNZ|IS_TMP_VAR))) { \
ZEND_VM_SET_NEXT_OPCODE(opline + 2); \
- } else if (EXPECTED((opline+1)->opcode == ZEND_JMPZ)) { \
+ } else if (EXPECTED(opline->result_type == (IS_SMART_BRANCH_JMPZ|IS_TMP_VAR))) { \
ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline + 1, (opline+1)->op2)); \
} else { \
ZVAL_FALSE(EX_VAR(opline->result.var)); \
offset = offset * 2 + (op->extended_value & ZEND_ISEMPTY);
} else if (spec & SPEC_RULE_SMART_BRANCH) {
offset = offset * 3;
- if ((op+1)->opcode == ZEND_JMPZ) {
+ if (op->result_type == (IS_SMART_BRANCH_JMPZ|IS_TMP_VAR)) {
offset += 1;
- } else if ((op+1)->opcode == ZEND_JMPNZ) {
+ } else if (op->result_type == (IS_SMART_BRANCH_JMPNZ|IS_TMP_VAR)) {
offset += 2;
}
}
if (isset($used_extra_spec["SMART_BRANCH"])) {
out($f, "\t\t{$else}if (spec & SPEC_RULE_SMART_BRANCH) {\n");
out($f, "\t\t\toffset = offset * 3;\n");
- out($f, "\t\t\tif ((op+1)->opcode == ZEND_JMPZ) {\n");
+ out($f, "\t\t\tif (op->result_type == (IS_SMART_BRANCH_JMPZ|IS_TMP_VAR)) {\n");
out($f, "\t\t\t\toffset += 1;\n");
- out($f, "\t\t\t} else if ((op+1)->opcode == ZEND_JMPNZ) {\n");
+ out($f, "\t\t\t} else if (op->result_type == (IS_SMART_BRANCH_JMPNZ|IS_TMP_VAR)) {\n");
out($f, "\t\t\t\toffset += 2;\n");
out($f, "\t\t\t}\n");
$else = "} else ";
zend_op *opcodes = op_array->opcodes;
do {
- /* check if NOP breaks incorrect smart branch */
- if (b->len == 2
- && (opcodes[b->start + 1].opcode == ZEND_JMPZ
- || opcodes[b->start + 1].opcode == ZEND_JMPNZ)
- && (opcodes[b->start + 1].op1_type & (IS_CV|IS_CONST))
- && b->start > 0
- && zend_is_smart_branch(opcodes + b->start - 1)) {
- break;
- }
b->start++;
b->len--;
} while (b->len > 0 && opcodes[b->start].opcode == ZEND_NOP);
}
j++;
}
- if (i + 1 < b->start + b->len
- && (op_array->opcodes[i+1].opcode == ZEND_JMPZ
- || op_array->opcodes[i+1].opcode == ZEND_JMPNZ)
- && op_array->opcodes[i+1].op1_type & (IS_CV|IS_CONST)
- && zend_is_smart_branch(op_array->opcodes + j - 1)) {
- /* don't remove NOP, that splits incorrect smart branch */
- j++;
- }
i++;
}
b->len = j - b->start;
return SUCCESS;
}
-static zend_bool is_smart_branch_inhibiting_nop(
- zend_op_array *op_array, uint32_t target, uint32_t current,
- zend_basic_block *b, zend_basic_block *blocks_end)
-{
- uint32_t next;
- /* Target points one past the last non-nop instruction. Make sure there is one. */
- if (target == 0) {
- return 0;
- }
-
- /* Find the next instruction, skipping unreachable or empty blocks. */
- next = current + 1;
- if (next >= b->start + b->len) {
- do {
- b++;
- if (b == blocks_end) {
- return 0;
- }
- } while (!(b->flags & ZEND_BB_REACHABLE) || b->len == 0);
- next = b->start;
- }
-
- return (op_array->opcodes[next].opcode == ZEND_JMPZ ||
- op_array->opcodes[next].opcode == ZEND_JMPNZ) &&
- zend_is_smart_branch(op_array->opcodes + target - 1);
-}
-
static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_optimizer_ctx *ctx)
{
zend_basic_block *blocks = ssa->cfg.blocks;
old_end = b->start + b->len;
while (i < old_end) {
shiftlist[i] = i - target;
- if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP) ||
- is_smart_branch_inhibiting_nop(op_array, target, i, b, blocks_end)) {
+ if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP)) {
if (i != target) {
op_array->opcodes[target] = op_array->opcodes[i];
ssa->ops[target] = ssa->ops[i];
MAKE_NOP(send_array);
removed_ops++;
+ op_num = call_info->caller_call_opline - op_array->opcodes;
+ ssa_op = ssa->ops + op_num;
+ if (ssa_op->result_def >= 0) {
+ int var = ssa_op->result_def;
+ int use = ssa->vars[var].use_chain;
+
+ if (ssa->vars[var].phi_use_chain == NULL) {
+ if (ssa->ops[use].op1_use == var
+ && ssa->ops[use].op1_use_chain == -1) {
+ call_info->caller_call_opline->result_type = IS_TMP_VAR;
+ op_array->opcodes[use].op1_type = IS_TMP_VAR;
+ } else if (ssa->ops[use].op2_use == var
+ && ssa->ops[use].op2_use_chain == -1) {
+ call_info->caller_call_opline->result_type = IS_TMP_VAR;
+ op_array->opcodes[use].op2_type = IS_TMP_VAR;
+ }
+ }
+ }
}
}
}
while (block->len > 0) {
zend_op *opline = &op_array->opcodes[block->start + block->len - 1];
- if (opline->opcode == ZEND_NOP
- && (block->len == 1 || !zend_is_smart_branch(opline - 1))) {
+ if (opline->opcode == ZEND_NOP) {
block->len--;
} else {
break;
}
static inline zend_bool is_var_type(zend_uchar type) {
- return type == IS_CV || type == IS_VAR || type == IS_TMP_VAR;
+ return (type & (IS_CV|IS_VAR|IS_TMP_VAR)) != 0;
}
#define FAIL(...) do { \
fprintf(stderr, "CV%d($%s)", var_num, op_array->vars[var_num]->val);
} else if (var_type == IS_VAR) {
fprintf(stderr, "V%d", var_num);
- } else if (var_type == IS_TMP_VAR) {
+ } else if (var_type == IS_TMP_VAR || !(var_type & (IS_VAR|IS_CV))) {
fprintf(stderr, "T%d", var_num);
} else {
fprintf(stderr, "X%d", var_num);
}
if (opline->result_type == IS_CONST) {
zend_dump_const(CRT_CONSTANT_EX(op_array, opline, opline->result, (dump_flags & ZEND_DUMP_RT_CONSTANTS)));
+#if 0
+ } else if (opline->result_type & IS_SMART_BRANCH_JMPZ) {
+ fprintf(stderr, " jmpz");
+ } else if (opline->result_type & IS_SMART_BRANCH_JMPNZ) {
+ fprintf(stderr, " jmpnz");
+#endif
} else 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 (opline->op2_type == IS_CONST) {
ZEND_PASS_TWO_UNDO_CONSTANT(op_array, opline, opline->op2);
}
+ /* reset smart branch flags IS_SMART_BRANCH_JMP[N]Z */
+ opline->result_type &= (IS_TMP_VAR|IS_VAR|IS_CV|IS_CONST);
opline++;
}
#if !ZEND_USE_ABS_CONST_ADDR
if (opline->op2_type == IS_CONST) {
ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op2);
}
-#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
if (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) {
/* fix jumps to point to new array */
switch (opline->opcode) {
+#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
case ZEND_JMP:
case ZEND_FAST_CALL:
opline->op1.jmp_addr = &op_array->opcodes[opline->op1.jmp_addr - old_opcodes];
case ZEND_SWITCH_STRING:
/* relative extended_value don't have to be changed */
break;
+#endif
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_CASE:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_INSTANCEOF:
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ if (opline->result_type & IS_TMP_VAR) {
+ /* reinitialize result_type od smart branch instructions */
+ if (opline + 1 < end) {
+ if ((opline+1)->opcode == ZEND_JMPZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPZ | IS_TMP_VAR;
+ } else if ((opline+1)->opcode == ZEND_JMPNZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPNZ | IS_TMP_VAR;
+ }
+ }
+ }
+ break;
}
}
-#endif
ZEND_VM_SET_OPCODE_HANDLER(opline);
opline++;
}
ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op2);
}
- zend_vm_set_opcode_handler_ex(opline, op1_info, op2_info, res_info);
-#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
if (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) {
/* fix jumps to point to new array */
switch (opline->opcode) {
+#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
case ZEND_JMP:
case ZEND_FAST_CALL:
opline->op1.jmp_addr = &op_array->opcodes[opline->op1.jmp_addr - old_opcodes];
case ZEND_SWITCH_STRING:
/* relative extended_value don't have to be changed */
break;
+#endif
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_CASE:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_INSTANCEOF:
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ if (opline->result_type & IS_TMP_VAR) {
+ /* reinitialize result_type od smart branch instructions */
+ if (opline + 1 < end) {
+ if ((opline+1)->opcode == ZEND_JMPZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPZ | IS_TMP_VAR;
+ } else if ((opline+1)->opcode == ZEND_JMPNZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPNZ | IS_TMP_VAR;
+ }
+ }
+ }
+ break;
}
}
-#endif
+ zend_vm_set_opcode_handler_ex(opline, op1_info, op2_info, res_info);
opline++;
}
}
goto done;
case ZEND_JMPZ:
case ZEND_JMPNZ:
- case ZEND_JMPZNZ:
- case ZEND_JMPZ_EX:
- case ZEND_JMPNZ_EX:
- if (i != ssa->cfg.blocks[b].start &&
- ((opline-1)->opcode == ZEND_IS_EQUAL ||
- (opline-1)->opcode == ZEND_IS_NOT_EQUAL ||
- (opline-1)->opcode == ZEND_IS_SMALLER ||
- (opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL ||
- (opline-1)->opcode == ZEND_CASE)) {
- /* skip */
- } else if (i != ssa->cfg.blocks[b].start &&
- (opline->opcode == ZEND_JMPZ ||
- (opline->opcode == ZEND_JMPNZ)) &&
- zend_is_smart_branch(opline-1)) {
- /* smart branch */
+ if (opline > op_array->opcodes &&
+ ((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+ /* smart branch */
if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) {
goto jit_failure;
}
- } else {
- if (!zend_jit_bool_jmpznz(&dasm_state, opline, b, op_array, ssa, ra)) {
- goto jit_failure;
- }
+ goto done;
+ }
+ /* break missing intentionally */
+ case ZEND_JMPZNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ if (!zend_jit_bool_jmpznz(&dasm_state, opline, b, op_array, ssa, ra)) {
+ goto jit_failure;
}
goto done;
case ZEND_FETCH_DIM_R:
break;
case ZEND_JMPZ:
case ZEND_JMPNZ:
- if (i != ssa->cfg.blocks[b].start) {
- if (zend_is_smart_branch(opline-1)) {
- /* smart branch */
- if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) {
- goto jit_failure;
- }
- break;
+ if (opline > op_array->opcodes &&
+ ((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+ /* smart branch */
+ if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) {
+ goto jit_failure;
}
+ goto done;
}
/* break missing intentionally */
case ZEND_JMPZ_EX: