]> granicus.if.org Git - php/commitdiff
JMP optimization cleanup.
authorDmitry Stogov <dmitry@zend.com>
Fri, 11 Oct 2019 08:23:02 +0000 (11:23 +0300)
committerDmitry Stogov <dmitry@zend.com>
Fri, 11 Oct 2019 08:23:02 +0000 (11:23 +0300)
Make JMP optimisation in pass3 and JMP optimization from CFG pass to be almost identical.

ext/opcache/Optimizer/block_pass.c
ext/opcache/Optimizer/pass3.c

index 983d5cf1a9c581a5a8089d7a053ed65740f8be4d..181ba725acda858682726b5d3c551a39825f2d2e 100644 (file)
@@ -58,11 +58,6 @@ int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int
        return 0;
 }
 
-/* CFG back references management */
-
-#define DEL_SOURCE(from, to)
-#define ADD_SOURCE(from, to)
-
 /* Data dependencies macros */
 
 #define VAR_SOURCE(op) Tsource[VAR_NUM(op.var)]
@@ -540,14 +535,12 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
                                                    (opline->opcode == ZEND_JMPZ)) {
 
                                                        MAKE_NOP(opline);
-                                                       DEL_SOURCE(block, block->successors[0]);
                                                        block->successors[0] = block->successors[1];
                                                        block->len--;
                                                        break;
                                                } else {
                                                        opline->opcode = ZEND_JMP;
                                                        COPY_NODE(opline->op1, opline->op2);
-                                                       DEL_SOURCE(block, block->successors[1]);
                                                        break;
                                                }
                                        } else if (opline->op1_type == IS_TMP_VAR &&
@@ -583,12 +576,10 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
                                                if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
                                                        zend_op *target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
                                                        ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
-                                                       DEL_SOURCE(block, block->successors[0]);
                                                        block->successors[0] = block->successors[1];
                                                } else {
                                                        zend_op *target_opline = ZEND_OP2_JMP_ADDR(opline);
                                                        ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
-                                                       DEL_SOURCE(block, block->successors[0]);
                                                }
                                                block->successors_count = 1;
                                                opline->op1_type = IS_UNUSED;
@@ -637,7 +628,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
                                                        zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
                                                        ZVAL_BOOL(&ZEND_OP1_LITERAL(opline), opline->opcode == ZEND_JMPZ_EX);
                                                        opline->op2.num = 0;
-                                                       DEL_SOURCE(block, block->successors[0]);
                                                        block->successors_count = 1;
                                                        block->successors[0] = block->successors[1];
                                                        break;
@@ -1099,11 +1089,84 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
        }
 }
 
-static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_array, zend_cfg *cfg, uint32_t *opt_count)
+static zend_always_inline zend_basic_block *get_target_block(const zend_cfg *cfg, zend_basic_block *block, int n, uint32_t *opt_count)
+{
+       int b;
+       zend_basic_block *target_block = cfg->blocks + block->successors[n];
+
+       if (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED)) {
+               do {
+                       b = target_block->successors[0];
+                       target_block = cfg->blocks + b;
+               } while (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED));
+               block->successors[n] = b;
+               ++(*opt_count);
+       }
+       return target_block;
+}
+
+static zend_always_inline zend_basic_block *get_follow_block(const zend_cfg *cfg, zend_basic_block *block, int n, uint32_t *opt_count)
+{
+       int b;
+       zend_basic_block *target_block = cfg->blocks + block->successors[n];
+
+       if (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED)) {
+               do {
+                       b = target_block->successors[0];
+                       target_block = cfg->blocks + b;
+               } while (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED));
+               block->successors[n] = b;
+               ++(*opt_count);
+       }
+       return target_block;
+}
+
+static zend_always_inline zend_basic_block *get_next_block(const zend_cfg *cfg, zend_basic_block *block)
+{
+       zend_basic_block *next_block = block + 1;
+       zend_basic_block *end = cfg->blocks + cfg->blocks_count;
+
+       while (1) {
+               if (next_block == end) {
+                       return NULL;
+               } else if (next_block->flags & ZEND_BB_REACHABLE) {
+                       break;
+               }
+               next_block++;
+       }
+       while (next_block->len == 0 && !(next_block->flags & ZEND_BB_PROTECTED)) {
+               next_block = cfg->blocks + next_block->successors[0];
+       }
+       return next_block;
+}
+
+
+/* we use "jmp_hitlist" to avoid infinity loops during jmp optimization */
+static zend_always_inline int in_hitlist(int target, int *jmp_hitlist, int jmp_hitlist_count)
+{
+       int i;
+
+       for (i = 0; i < jmp_hitlist_count; i++) {
+               if (jmp_hitlist[i] == target) {
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+#define CHECK_LOOP(target) \
+       if (EXPECTED(!in_hitlist(target, jmp_hitlist, jmp_hitlist_count))) { \
+               jmp_hitlist[jmp_hitlist_count++] = target;      \
+       } else { \
+               break; \
+       }
+
+static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_array, const zend_cfg *cfg, int *jmp_hitlist, uint32_t *opt_count)
 {
        /* last_op is the last opcode of the current block */
-       zend_basic_block *blocks = cfg->blocks;
-       zend_op *last_op;
+       zend_basic_block *target_block, *follow_block, *next_block;
+       zend_op *last_op, *target;
+       int next, jmp_hitlist_count;
 
        if (block->len == 0) {
                return;
@@ -1112,35 +1175,32 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr
        last_op = op_array->opcodes + block->start + block->len - 1;
        switch (last_op->opcode) {
                case ZEND_JMP:
-                       {
-                               zend_basic_block *target_block = blocks + block->successors[0];
-                               zend_op *target = op_array->opcodes + target_block->start;
-                               int next = (block - blocks) + 1;
+                       jmp_hitlist_count = 0;
 
-                               while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) {
-                                       /* find used one */
-                                       next++;
-                               }
-
-                               /* JMP(next) -> NOP */
-                               if (block->successors[0] == next) {
-                                       MAKE_NOP(last_op);
-                                       ++(*opt_count);
-                                       block->len--;
+                       target_block = get_target_block(cfg, block, 0, opt_count);
+                       while (target_block->len == 1) {
+                               target = op_array->opcodes + target_block->start;
+                               if (target->opcode == ZEND_JMP) {
+                                       /* JMP L, L: JMP L1 -> JMP L1 */
+                                       next = target_block->successors[0];
+                               } else {
                                        break;
                                }
+                               CHECK_LOOP(next);
+                               block->successors[0] = next;
+                               ++(*opt_count);
+                               target_block = get_target_block(cfg, block, 0, opt_count);
+                       }
 
-                               if (target->opcode == ZEND_JMP &&
-                                       block->successors[0] != target_block->successors[0] &&
-                                       !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* JMP L, L: JMP L1 -> JMP L1 */
-                                       *last_op = *target;
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == ZEND_JMPZNZ &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
+                       next_block = get_next_block(cfg, block);
+                       if (target_block == next_block) {
+                               /* JMP(next) -> NOP */
+                               MAKE_NOP(last_op);
+                               ++(*opt_count);
+                               block->len--;
+                       } else if (target_block->len == 1) {
+                               target = op_array->opcodes + target_block->start;
+                               if (target->opcode == ZEND_JMPZNZ) {
                                        /* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */
                                        *last_op = *target;
                                        if (last_op->op1_type == IS_CONST) {
@@ -1148,15 +1208,14 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr
                                                ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op));
                                                last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv);
                                        }
-                                       DEL_SOURCE(block, block->successors[0]);
                                        block->successors_count = 2;
                                        block->successors[0] = target_block->successors[0];
                                        block->successors[1] = target_block->successors[1];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ADD_SOURCE(block, block->successors[1]);
                                        ++(*opt_count);
+                                       goto optimize_jmpznz;
                                } else if ((target->opcode == ZEND_RETURN ||
                                            target->opcode == ZEND_RETURN_BY_REF ||
+                                           target->opcode == ZEND_GENERATOR_RETURN ||
                                            target->opcode == ZEND_EXIT) &&
                                           !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
                                        /* JMP L, L: RETURN to immediate RETURN */
@@ -1166,166 +1225,125 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr
                                                ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op));
                                                last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv);
                                        }
-                                       DEL_SOURCE(block, block->successors[0]);
                                        block->successors_count = 0;
                                        ++(*opt_count);
                                }
                        }
                        break;
 
-               case ZEND_JMPZ:
-               case ZEND_JMPNZ:
-                       if (block->successors[0] == block->successors[1]) {
-                               /* L: JMP[N]Z(X, L+1) -> NOP or FREE(X) */
+               case ZEND_JMP_SET:
+               case ZEND_COALESCE:
+                       jmp_hitlist_count = 0;
 
-                               if (last_op->op1_type == IS_CV) {
-                                       last_op->opcode = ZEND_CHECK_VAR;
-                                       last_op->op2.num = 0;
-                               } else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) {
-                                       last_op->opcode = ZEND_FREE;
-                                       last_op->op2.num = 0;
+                       target_block = get_target_block(cfg, block, 0, opt_count);
+                       while (target_block->len == 1) {
+                               target = op_array->opcodes + target_block->start;
+
+                               if (target->opcode == ZEND_JMP) {
+                                       /* JMP_SET(X, L), L: JMP(L2) -> JMP_SET(X, L2) */
+                                       next = target_block->successors[0];
+                                       CHECK_LOOP(next);
+                                       block->successors[0] = next;
+                                       ++(*opt_count);
                                } else {
-                                       MAKE_NOP(last_op);
+                                       break;
                                }
-                               block->successors_count = 1;
-                               ++(*opt_count);
-                               break;
+                               target_block = get_target_block(cfg, block, 0, opt_count);
                        }
+                       break;
 
-                       if (1) {
-                               zend_uchar same_type = last_op->op1_type;
-                               uint32_t same_var = last_op->op1.var;
-                               zend_op *target;
-                               zend_op *target_end;
-                               zend_basic_block *target_block = blocks + block->successors[0];
+               case ZEND_JMPZ:
+               case ZEND_JMPNZ:
+                       jmp_hitlist_count = 0;
 
-next_target:
+                       target_block = get_target_block(cfg, block, 0, opt_count);
+                       while (target_block->len == 1) {
                                target = op_array->opcodes + target_block->start;
-                               target_end = target + target_block->len;
-                               while (target < target_end && target->opcode == ZEND_NOP) {
-                                       target++;
-                               }
 
-                               /* next block is only NOP's */
-                               if (target == target_end) {
-                                       target_block = blocks + target_block->successors[0];
-                                       ++(*opt_count);
-                                       goto next_target;
-                               } else if (target->opcode == INV_COND(last_op->opcode) &&
-                                          same_var == target->op1.var &&
-                                          same_type == target->op1_type &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* JMPZ(X, L), L: JMPNZ(X, L2) -> JMPZ(X, L+1) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[1];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == INV_COND_EX(last_op->opcode) &&
-                                          same_var == target->op1.var &&
-                                          same_type == target->op1_type &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* JMPZ(X, L), L: T = JMPNZ_EX(X, L2) -> T = JMPZ_EX(X, L+1) */
-                                       last_op->opcode += 3;
-                                       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]);
-                                       ++(*opt_count);
+                               if (target->opcode == ZEND_JMP) {
+                                       /* JMPZ(X, L), L: JMP(L2) -> JMPZ(X, L2) */
+                                       next = target_block->successors[0];
                                } else if (target->opcode == last_op->opcode &&
-                                          same_var == target->op1.var &&
-                                          same_type == target->op1_type &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
+                                          SAME_VAR(target->op1, last_op->op1)) {
                                        /* JMPZ(X, L), L: JMPZ(X, L2) -> JMPZ(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == ZEND_JMP &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* JMPZ(X, L), L: JMP(L2) -> JMPZ(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
+                                       next = target_block->successors[0];
+                               } else if (target->opcode == INV_COND(last_op->opcode) &&
+                                          SAME_VAR(target->op1, last_op->op1)) {
+                                       /* JMPZ(X, L), L: JMPNZ(X, L2) -> JMPZ(X, L+1) */
+                                       next = target_block->successors[1];
                                } else if (target->opcode == ZEND_JMPZNZ &&
-                                          same_var == target->op1.var &&
-                                          same_type == target->op1_type &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
+                                          SAME_VAR(target->op1, last_op->op1)) {
                                        /* JMPZ(X, L), L: JMPZNZ(X, L2, L3) -> JMPZ(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       if (last_op->opcode == ZEND_JMPZ) {
-                                               block->successors[0] = target_block->successors[0];
-                                       } else {
-                                               block->successors[0] = target_block->successors[1];
-                                       }
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
+                                       next = target_block->successors[last_op->opcode == ZEND_JMPNZ];
+                               } else {
+                                       break;
                                }
+                               CHECK_LOOP(next);
+                               block->successors[0] = next;
+                               ++(*opt_count);
+                               target_block = get_target_block(cfg, block, 0, opt_count);
                        }
 
-                       if (last_op->opcode == ZEND_JMPZ || last_op->opcode == ZEND_JMPNZ) {
-                               zend_op *target;
-                               zend_op *target_end;
-                               zend_basic_block *target_block;
-
-                               while (1) {
-                                       target_block = blocks + block->successors[1];
-                                       target = op_array->opcodes + target_block->start;
-                                       target_end = op_array->opcodes + target_block->start + 1;
-                                       while (target < target_end && target->opcode == ZEND_NOP) {
-                                               target++;
-                                       }
-
-                                       /* next block is only NOP's */
-                                       if (target == target_end && !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                               DEL_SOURCE(block, block->successors[1]);
-                                               block->successors[1] = target_block->successors[0];
-                                               ADD_SOURCE(block, block->successors[1]);
+                       follow_block = get_follow_block(cfg, block, 1, opt_count);
+                       if (target_block == follow_block) {
+                               /* L: JMP[N]Z(X, L+1) -> NOP or FREE(X) */
+                               if (last_op->op1_type == IS_CV) {
+                                       last_op->opcode = ZEND_CHECK_VAR;
+                                       last_op->op2.num = 0;
+                               } else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) {
+                                       last_op->opcode = ZEND_FREE;
+                                       last_op->op2.num = 0;
+                               } else {
+                                       MAKE_NOP(last_op);
+                                       block->len--;
+                               }
+                               block->successors_count = 1;
+                               ++(*opt_count);
+                       } else if (follow_block->len == 1) {
+                               target = op_array->opcodes + follow_block->start;
+                               if (target->opcode == ZEND_JMP) {
+                                   if (block->successors[0] == follow_block->successors[0]) {
+                                               /* JMPZ(X,L1), JMP(L1) -> NOP, JMP(L1) */
+                                               if (last_op->op1_type == IS_CV) {
+                                                       last_op->opcode = ZEND_CHECK_VAR;
+                                                       last_op->op2.num = 0;
+                                               } else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) {
+                                                       last_op->opcode = ZEND_FREE;
+                                                       last_op->op2.num = 0;
+                                               } else {
+                                                       MAKE_NOP(last_op);
+                                                       block->len--;
+                                               }
+                                               block->successors_count = 1;
                                                ++(*opt_count);
-                                       } else {
                                                break;
-                                       }
-                               }
-
-                               if (target->opcode == ZEND_JMP &&
-                                       !(target_block->flags & ZEND_BB_PROTECTED)) {
+                                       } else if (!(follow_block->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED))) {
+                                               next_block = get_next_block(cfg, follow_block);
 
-                                       if (!(target_block->flags & ZEND_BB_TARGET)) {
-                                               int next = (target_block - blocks) + 1;
-
-                                               while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) {
-                                                       /* find used one */
-                                                       next++;
-                                               }
-                                               if (next < cfg->blocks_count &&
-                                                   block->successors[0] == next) {
+                                               if (target_block == next_block) {
                                                        /* JMPZ(X,L1) JMP(L2) L1: -> JMPNZ(X,L2) NOP*/
 
                                                        last_op->opcode = INV_COND(last_op->opcode);
 
-                                                       DEL_SOURCE(block, block->successors[1]);
-                                                       block->successors[0] = target_block->successors[0];
-                                                       block->successors[1] = next;
-                                                       ADD_SOURCE(block, block->successors[1]);
+                                                       block->successors[0] = follow_block->successors[0];
+                                                       block->successors[1] = next_block - cfg->blocks;
 
-                                                       target_block->flags &= ~ZEND_BB_REACHABLE;
+                                                       follow_block->flags &= ~ZEND_BB_REACHABLE;
                                                        MAKE_NOP(target);
+                                                       follow_block->len = 0;
 
-                                                       blocks[next].flags |= ZEND_BB_FOLLOW;
+                                                       next_block->flags |= ZEND_BB_FOLLOW;
 
                                                        break;
                                                }
                                        }
 
                                        /* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */
-                                       DEL_SOURCE(block, block->successors[1]);
                                        if (last_op->opcode == ZEND_JMPZ) {
-                                               block->successors[1] = target_block->successors[0];
-                                               ADD_SOURCE(block, block->successors[1]);
+                                               block->successors[1] = follow_block->successors[0];
                                        } else {
                                                block->successors[1] = block->successors[0];
-                                               block->successors[0] = target_block->successors[0];
-                                               ADD_SOURCE(block, block->successors[0]);
+                                               block->successors[0] = follow_block->successors[0];
                                        }
                                        last_op->opcode = ZEND_JMPZNZ;
                                        ++(*opt_count);
@@ -1335,185 +1353,153 @@ next_target:
 
                case ZEND_JMPNZ_EX:
                case ZEND_JMPZ_EX:
-                       if (block->successors[0] == block->successors[1]) {
-                               /* L: T = JMP[N]Z_EX(X, L+1) -> T = BOOL(X) */
-
-                               last_op->opcode = ZEND_BOOL;
-                               last_op->op2.num = 0;
-                               block->successors_count = 1;
-                               ++(*opt_count);
-                               break;
-                       }
-
-                       if (1) {
-                               zend_op *target, *target_end;
-                               zend_basic_block *target_block;
-                               zend_uchar same_type = last_op->op1_type;
-                               uint32_t same_var = last_op->op1.var;
-                               uint32_t same_res_var = last_op->result.var;
+                       jmp_hitlist_count = 0;
 
-                               target_block = blocks + block->successors[0];
-next_target_ex:
+                       target_block = get_target_block(cfg, block, 0, opt_count);
+                       while (target_block->len == 1) {
                                target = op_array->opcodes + target_block->start;
-                               target_end = target + target_block->len;
-                               while (target < target_end && target->opcode == ZEND_NOP) {
-                                       target++;
-                               }
-                               /* next block is only NOP's */
-                               if (target == target_end) {
-                                       target_block = blocks + target_block->successors[0];
-                                       ++(*opt_count);
-                                       goto next_target_ex;
+
+                               if (target->opcode == ZEND_JMP) {
+                                       /* T = JMPZ_EX(X, L), L: JMP(L2) -> T = JMPZ(X, L2) */
+                                       next = target_block->successors[0];
                                } else if (target->opcode == last_op->opcode-3 &&
-                                          ((same_var == target->op1.var &&
-                                            same_type == target->op1_type) ||
-                                           (same_res_var  == target->op1.var &&
-                                            target->op1_type == IS_TMP_VAR)) &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
+                                          (SAME_VAR(target->op1, last_op->result) ||
+                                           SAME_VAR(target->op1, last_op->op1))) {
                                        /* T = JMPZ_EX(X, L1), L1: JMPZ({X|T}, L2) -> T = JMPZ_EX(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == INV_EX_COND(last_op->opcode) &&
-                                          ((same_var == target->op1.var &&
-                                            same_type == target->op1_type) ||
-                                           (same_res_var == target->op1.var &&
-                                            target->op1_type == IS_TMP_VAR)) &&
-                                           !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* T = JMPZ_EX(X, L1), L1: JMPNZ({X|T1}, L2) -> T = JMPZ_EX(X, L1+1) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[1];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == INV_EX_COND_EX(last_op->opcode) &&
-                                          same_res_var == target->result.var &&
-                                          ((same_var == target->op1.var &&
-                                            same_type == target->op1_type) ||
-                                           (same_res_var == target->op1.var &&
-                                            target->op1_type == IS_TMP_VAR)) &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* T = JMPZ_EX(X, L1), L1: T = JMPNZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L1+1) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[1];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
+                                       next = target_block->successors[0];
                                } else if (target->opcode == last_op->opcode &&
-                                          same_res_var == target->result.var &&
-                                          ((same_var == target->op1.var &&
-                                            same_type == target->op1_type) ||
-                                           (same_res_var == target->op1.var &&
-                                            target->op1_type == IS_TMP_VAR)) &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
+                                          target->result.var == last_op->result.var &&
+                                          (SAME_VAR(target->op1, last_op->result) ||
+                                           SAME_VAR(target->op1, last_op->op1))) {
                                        /* T = JMPZ_EX(X, L1), L1: T = JMPZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == ZEND_JMP &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* T = JMPZ_EX(X, L), L: JMP(L2) -> T = JMPZ(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
+                                       next = target_block->successors[0];
                                } else if (target->opcode == ZEND_JMPZNZ &&
-                                          ((same_var == target->op1.var &&
-                                            same_type == target->op1_type) ||
-                                           (same_res_var == target->op1.var &&
-                                            target->op1_type == IS_TMP_VAR)) &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
+                                          (SAME_VAR(target->op1, last_op->result) ||
+                                           SAME_VAR(target->op1, last_op->op1))) {
                                        /* T = JMPZ_EX(X, L), L: JMPZNZ({X|T}, L2, L3) -> T = JMPZ_EX(X, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       if (last_op->opcode == ZEND_JMPZ_EX) {
-                                               block->successors[0] = target_block->successors[0];
-                                       } else {
-                                               block->successors[0] = target_block->successors[1];
-                                       }
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
+                                       next = target_block->successors[last_op->opcode == ZEND_JMPNZ_EX];
+                               } else if (target->opcode == INV_EX_COND(last_op->opcode) &&
+                                          (SAME_VAR(target->op1, last_op->result) ||
+                                           SAME_VAR(target->op1, last_op->op1))) {
+                                       /* T = JMPZ_EX(X, L1), L1: JMPNZ({X|T1}, L2) -> T = JMPZ_EX(X, L1+1) */
+                                       next = target_block->successors[1];
+                               } else if (target->opcode == INV_EX_COND_EX(last_op->opcode) &&
+                                          target->result.var == last_op->result.var &&
+                                          (SAME_VAR(target->op1, last_op->result) ||
+                                           SAME_VAR(target->op1, last_op->op1))) {
+                                       /* T = JMPZ_EX(X, L1), L1: T = JMPNZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L1+1) */
+                                       next = target_block->successors[1];
+                               } else if (target->opcode == ZEND_BOOL &&
+                                          (SAME_VAR(target->op1, last_op->result) ||
+                                           SAME_VAR(target->op1, last_op->op1))) {
+                                       /* convert Y = JMPZ_EX(X,L1), L1: Z = BOOL(Y) to
+                                          Z = JMPZ_EX(X,L1+1) */
+
+                                       /* NOTE: This optimization pattern is not safe, but works, */
+                                       /*       because result of JMPZ_EX instruction             */
+                                       /*       is not used on the following path and             */
+                                       /*       should be used once on the branch path.           */
+                                       /*                                                         */
+                                       /*       The pattern works well only if jums processed in  */
+                                       /*       direct order, otherwise it breakes JMPZ_EX        */
+                                       /*       sequences too early.                              */
+                                       last_op->result.var = target->result.var;
+                                       next = target_block->successors[0];
+                               } else {
+                                       break;
                                }
+                               CHECK_LOOP(next);
+                               block->successors[0] = next;
+                               ++(*opt_count);
+                               target_block = get_target_block(cfg, block, 0, opt_count);
+                       }
+
+                       follow_block = get_follow_block(cfg, block, 1, opt_count);
+                       if (target_block == follow_block) {
+                               /* L: T = JMP[N]Z_EX(X, L+1) -> T = BOOL(X) */
+                               last_op->opcode = ZEND_BOOL;
+                               last_op->op2.num = 0;
+                               block->successors_count = 1;
+                               ++(*opt_count);
+                               break;
                        }
                        break;
 
                case ZEND_JMPZNZ: {
-                       int next = (block - blocks) + 1;
+optimize_jmpznz:
+                       jmp_hitlist_count = 0;
+                       target_block = get_target_block(cfg, block, 0, opt_count);
+                       while (target_block->len == 1) {
+                               target = op_array->opcodes + target_block->start;
 
-                       while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) {
-                               /* find first accessed one */
-                               next++;
+                               if (target->opcode == ZEND_JMP) {
+                                       /* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */
+                                       next = target_block->successors[0];
+                               } else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
+                                          SAME_VAR(target->op1, last_op->op1)) {
+                                       /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
+                                       next = target_block->successors[0];
+                               } else if (target->opcode == ZEND_JMPNZ &&
+                                          SAME_VAR(target->op1, last_op->op1)) {
+                                       /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
+                                       next = target_block->successors[1];
+                               } else {
+                                       break;
+                               }
+                               CHECK_LOOP(next);
+                               block->successors[0] = next;
+                               ++(*opt_count);
+                               target_block = get_target_block(cfg, block, 0, opt_count);
                        }
 
-                       if (block->successors[0] == block->successors[1]) {
-                               /* both goto the same one - it's JMP */
-                               if (!(last_op->op1_type & (IS_VAR|IS_TMP_VAR))) {
-                                       /* JMPZNZ(?,L,L) -> JMP(L) */
-                                       last_op->opcode = ZEND_JMP;
-                                       SET_UNUSED(last_op->op1);
-                                       SET_UNUSED(last_op->op2);
-                                       block->successors_count = 1;
-                                       ++(*opt_count);
+                       jmp_hitlist_count = 0;
+                       follow_block = get_target_block(cfg, block, 1, opt_count);
+                       while (follow_block->len == 1) {
+                               target = op_array->opcodes + follow_block->start;
+
+                               if (target->opcode == ZEND_JMP) {
+                                       /* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */
+                                       next = follow_block->successors[0];
+                               } else if (target->opcode == ZEND_JMPNZ &&
+                                          SAME_VAR(target->op1, last_op->op1)) {
+                                       /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
+                                       next = follow_block->successors[0];
+                               } else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
+                                          SAME_VAR(target->op1, last_op->op1)) {
+                                       /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
+                                       next = target_block->successors[1];
+                               } else {
+                                       break;
                                }
-                       } else if (block->successors[0] == next) {
+                               CHECK_LOOP(next);
+                               block->successors[1] = next;
+                               ++(*opt_count);
+                               follow_block = get_target_block(cfg, block, 1, opt_count);
+                       }
+
+                       next_block = get_next_block(cfg, block);
+                       if (target_block == follow_block &&
+                           !(last_op->op1_type & (IS_VAR|IS_TMP_VAR))) {
+                               /* JMPZNZ(?,L,L) -> JMP(L) */
+                               last_op->opcode = ZEND_JMP;
+                               SET_UNUSED(last_op->op1);
+                               SET_UNUSED(last_op->op2);
+                               last_op->extended_value = 0;
+                               block->successors_count = 1;
+                               ++(*opt_count);
+                       } else if (target_block == next_block) {
                                /* jumping to next on Z - can follow to it and jump only on NZ */
                                /* JMPZNZ(X,L1,L2) L1: -> JMPNZ(X,L2) */
+                               int tmp = block->successors[0];
                                last_op->opcode = ZEND_JMPNZ;
                                block->successors[0] = block->successors[1];
-                               block->successors[1] = next;
+                               block->successors[1] = tmp;
                                ++(*opt_count);
-                               /* no need to add source */
-                       } else if (block->successors[1] == next) {
+                       } else if (follow_block == next_block) {
                                /* jumping to next on NZ - can follow to it and jump only on Z */
                                /* JMPZNZ(X,L1,L2) L2: -> JMPZ(X,L1) */
                                last_op->opcode = ZEND_JMPZ;
                                ++(*opt_count);
-                               /* no need to add source */
-                       }
-
-                       if (last_op->opcode == ZEND_JMPZNZ) {
-                               zend_uchar same_type = last_op->op1_type;
-                               zend_uchar same_var = last_op->op1.var;
-                               zend_op *target;
-                               zend_op *target_end;
-                               zend_basic_block *target_block = blocks + block->successors[0];
-
-next_target_znz:
-                               target = op_array->opcodes + target_block->start;
-                               target_end = target + target_block->len;
-                               while (target < target_end && target->opcode == ZEND_NOP) {
-                                       target++;
-                               }
-                               /* next block is only NOP's */
-                               if (target == target_end) {
-                                       target_block = blocks + target_block->successors[0];
-                                       ++(*opt_count);
-                                       goto next_target_znz;
-                               } else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
-                                          same_var == target->op1.var &&
-                                          same_type == target->op1_type &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                   /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == ZEND_JMPNZ &&
-                                          same_var == target->op1.var &&
-                                          same_type == target->op1_type &&
-                                          !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[1];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               } else if (target->opcode == ZEND_JMP &&
-                                              !(target_block->flags & ZEND_BB_PROTECTED)) {
-                                       /* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */
-                                       DEL_SOURCE(block, block->successors[0]);
-                                       block->successors[0] = target_block->successors[0];
-                                       ADD_SOURCE(block, block->successors[0]);
-                                       ++(*opt_count);
-                               }
                        }
                        break;
                }
@@ -1816,6 +1802,7 @@ void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
        void *checkpoint;
        zend_op **Tsource;
        uint32_t opt_count;
+       int *jmp_hitlist;
 
     /* Build CFG */
        checkpoint = zend_arena_checkpoint(ctx->arena);
@@ -1836,6 +1823,7 @@ void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
        bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
        Tsource = zend_arena_calloc(&ctx->arena, op_array->last_var + op_array->T, sizeof(zend_op *));
        usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
+       jmp_hitlist = zend_arena_alloc(&ctx->arena, cfg.blocks_count * sizeof(int));
 
        blocks = cfg.blocks;
        end = blocks + cfg.blocks_count;
@@ -1870,7 +1858,7 @@ void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                /* Jump optimization for each block */
                for (b = blocks; b < end; b++) {
                        if (b->flags & ZEND_BB_REACHABLE) {
-                               zend_jmp_optimization(b, op_array, &cfg, &opt_count);
+                               zend_jmp_optimization(b, op_array, &cfg, jmp_hitlist, &opt_count);
                        }
                }
 
index 97a7cd6316e46cd7c119c21fca87c03d339a1889..a4abe2f8d345542d305a14b03a440fba27b63a03 100644 (file)
@@ -70,10 +70,10 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                        case ZEND_JMP:
                                jmp_hitlist_count = 0;
 
-                               /* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */
                                target = ZEND_OP1_JMP_ADDR(opline);
                                while (1) {
                                        if (target->opcode == ZEND_JMP) {
+                                               /* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */
                                                target = ZEND_OP1_JMP_ADDR(target);
                                                CHECK_LOOP(target);
                                        } else if (target->opcode == ZEND_NOP) {
@@ -84,16 +84,35 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                        ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target);
                                }
 
-                               /* convert L: JMP L+1 to NOP */
                                if (target == opline + 1) {
+                                       /* convert L: JMP L+1 to NOP */
                                        MAKE_NOP(opline);
-                                       break;
-                               }
-
-                               if (opline > op_array->opcodes &&
-                                   ((opline-1)->opcode == ZEND_JMPZ ||
-                                    (opline-1)->opcode == ZEND_JMPNZ)) {
+                               } else if (target->opcode == ZEND_JMPZNZ) {
+                                       /* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */
+                                       *opline = *target;
+                                       if (opline->op1_type == IS_CONST) {
+                                               zval zv;
+                                               ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(opline));
+                                               opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+                                       }
+                                       goto optimize_jmpznz;
+                               } else if ((target->opcode == ZEND_RETURN ||
+                                           target->opcode == ZEND_RETURN_BY_REF ||
+                                           target->opcode == ZEND_GENERATOR_RETURN ||
+                                           target->opcode == ZEND_EXIT) &&
+                                          !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
+                                       /* JMP L, L: RETURN to immediate RETURN */
+                                       *opline = *target;
+                                       if (opline->op1_type == IS_CONST) {
+                                               zval zv;
+                                               ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(opline));
+                                               opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+                                       }
+                               } else if (opline > op_array->opcodes &&
+                                          ((opline-1)->opcode == ZEND_JMPZ ||
+                                           (opline-1)->opcode == ZEND_JMPNZ)) {
                                    if (ZEND_OP2_JMP_ADDR(opline-1) == target) {
+                                               /* JMPZ(X,L1), JMP(L1) -> NOP, JMP(L1) */
                                                if ((opline-1)->op1_type == IS_CV) {
                                                        (opline-1)->opcode = ZEND_CHECK_VAR;
                                                        (opline-1)->op2.num = 0;
@@ -104,6 +123,7 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                                        MAKE_NOP(opline-1);
                                                }
                                    } else {
+                                               /* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */
                                                if ((opline-1)->opcode == ZEND_JMPZ) {
                                                        (opline-1)->extended_value = ZEND_OPLINE_TO_OFFSET((opline-1), target);
                                                } else {
@@ -202,15 +222,16 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                                target = ZEND_OP2_JMP_ADDR(target);
                                                CHECK_LOOP(target);
                                        } else if (target->opcode == opline->opcode &&
+                                                  target->result.var == opline->result.var &&
                                                   (SAME_VAR(target->op1, opline->result) ||
-                                                   SAME_VAR(target->op1, opline->op1)) &&
-                                                  SAME_VAR(target->result, opline->result)) {
+                                                   SAME_VAR(target->op1, opline->op1))) {
                                                /* convert T=JMPZ_EX(X,L1), L1: T=JMPZ_EX(T,L2) to
                                                   JMPZ_EX(X,L2) */
                                                target = ZEND_OP2_JMP_ADDR(target);
                                                CHECK_LOOP(target);
                                        } else if (target->opcode == ZEND_JMPZNZ &&
-                                                          SAME_VAR(target->op1, opline->result)) {
+                                                  (SAME_VAR(target->op1, opline->result) ||
+                                                   SAME_VAR(target->op1, opline->op1))) {
                                                /* Check for JMPZNZ with same cond variable */
                                                target = (opline->opcode == ZEND_JMPZ_EX) ?
                                                        ZEND_OP2_JMP_ADDR(target) :
@@ -223,10 +244,9 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                                  JMPZ_EX(X,L1+1) */
                                                target = target + 1;
                                        } else if (target->opcode == INV_EX_COND_EX(opline->opcode) &&
+                                                  target->result.var == opline->result.var &&
                                                   (SAME_VAR(target->op1, opline->result) ||
-                                                   SAME_VAR(target->op1, opline->op1)) &&
-                                                  SAME_VAR(target->op1, opline->result) &&
-                                                  SAME_VAR(target->result, opline->result)) {
+                                                   SAME_VAR(target->op1, opline->op1))) {
                                           /* convert T=JMPZ_EX(X,L1), L1: T=JMPNZ_EX(T,L2) to
                                                  JMPZ_EX(X,L1+1) */
                                                target = target + 1;
@@ -259,11 +279,11 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                if (target == opline + 1) {
                                        opline->opcode = ZEND_BOOL;
                                        opline->op2.num = 0;
-                                       break;
                                }
                                break;
 
                        case ZEND_JMPZNZ:
+optimize_jmpznz:
                                jmp_hitlist_count = 0;
                                target = ZEND_OP2_JMP_ADDR(opline);
                                while (1) {
@@ -316,6 +336,18 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                        }
                                        opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, target);
                                }
+
+                               if (ZEND_OP2_JMP_ADDR(opline) == target &&
+                                   !(opline->op1_type & (IS_VAR|IS_TMP_VAR))) {
+                                       /* JMPZNZ(?,L,L) -> JMP(L) */
+                                       opline->opcode = ZEND_JMP;
+                                       ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target);
+                                       SET_UNUSED(opline->op1);
+                                       SET_UNUSED(opline->op2);
+                                       opline->extended_value = 0;
+                               }
+                               /* Don't convert JMPZNZ back to JMPZ/JMPNZ, because the
+                                  following JMP is not removed yet. */
                                break;
                }
                opline++;