From: Dmitry Stogov Date: Wed, 9 Oct 2019 10:48:39 +0000 (+0300) Subject: SAMRT BRANCH improvement. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f6f32f2cf0648cb4777408074e6293a97ed6a062;p=php SAMRT BRANCH improvement. 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. --- diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 57ec932310..edc0888c2d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2000,14 +2000,20 @@ ZEND_API int zend_is_smart_branch(const zend_op *opline) /* {{{ */ 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; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 03e007fe33..b0ed47ddff 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -702,6 +702,10 @@ struct _zend_execute_data { #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" diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 53c88f007c..2c54474119 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4261,13 +4261,13 @@ static zend_never_inline int ZEND_FASTCALL zend_quick_check_constant( #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 { \ @@ -4305,9 +4305,9 @@ static zend_never_inline int ZEND_FASTCALL zend_quick_check_constant( 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)); \ @@ -4328,9 +4328,9 @@ static zend_never_inline int ZEND_FASTCALL zend_quick_check_constant( 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)); \ diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index ffd3ede581..accf802b17 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -61000,9 +61000,9 @@ static const uint32_t ZEND_FASTCALL zend_vm_get_opcode_handler_idx(uint32_t spec 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; } } diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index a63d0d03cc..d263f542c5 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -2746,9 +2746,9 @@ function gen_vm($def, $skel) { 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 "; diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index 4e4d270b62..d1f79d1ab8 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -73,15 +73,6 @@ static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b) 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); @@ -112,14 +103,6 @@ static void strip_nops(zend_op_array *op_array, zend_basic_block *b) } 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; diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index f3156e1fa5..d753c54a00 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -125,33 +125,6 @@ int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, 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; @@ -199,8 +172,7 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_op 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]; @@ -484,6 +456,24 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) 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; + } + } + } } } } @@ -533,8 +523,7 @@ static void compress_block(zend_op_array *op_array, zend_basic_block *block) 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; diff --git a/ext/opcache/Optimizer/ssa_integrity.c b/ext/opcache/Optimizer/ssa_integrity.c index ede40be59a..4f042cae74 100644 --- a/ext/opcache/Optimizer/ssa_integrity.c +++ b/ext/opcache/Optimizer/ssa_integrity.c @@ -87,7 +87,7 @@ static inline zend_bool is_in_successors(zend_basic_block *block, int check) { } 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 { \ diff --git a/ext/opcache/Optimizer/zend_dump.c b/ext/opcache/Optimizer/zend_dump.c index d10e7f989e..cc4602192a 100644 --- a/ext/opcache/Optimizer/zend_dump.c +++ b/ext/opcache/Optimizer/zend_dump.c @@ -139,7 +139,7 @@ void zend_dump_var(const zend_op_array *op_array, zend_uchar var_type, int var_n 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); @@ -688,6 +688,12 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block * } 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) { diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 1307c5f9c8..d7f24e47ce 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -1055,6 +1055,8 @@ static void zend_revert_pass_two(zend_op_array *op_array) 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 @@ -1099,10 +1101,10 @@ static void zend_redo_pass_two(zend_op_array *op_array) 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]; @@ -1132,9 +1134,41 @@ static void zend_redo_pass_two(zend_op_array *op_array) 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++; } @@ -1185,11 +1219,10 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa) 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]; @@ -1219,9 +1252,42 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa) 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++; } } diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index dccff807aa..8c7f1abcc6 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2321,28 +2321,20 @@ static int zend_jit(zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_op 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: @@ -2468,14 +2460,13 @@ static int zend_jit(zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_op 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: