/* +----------------------------------------------------------------------+ | Zend OPcache | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Andi Gutmans | | Zeev Suraski | | Stanislav Malyshev | | Dmitry Stogov | +----------------------------------------------------------------------+ */ #include "php.h" #include "Optimizer/zend_optimizer.h" #include "Optimizer/zend_optimizer_internal.h" #include "zend_API.h" #include "zend_constants.h" #include "zend_execute.h" #include "zend_vm.h" #include "zend_bitset.h" #include "zend_cfg.h" #include "zend_dump.h" /* Checks if a constant (like "true") may be replaced by its value */ int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int copy) { zend_constant *c = zend_hash_find_ptr(EG(zend_constants), name); if (c) { if ((ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT) && !(ZEND_CONSTANT_FLAGS(c) & CONST_DEPRECATED) && (!(ZEND_CONSTANT_FLAGS(c) & CONST_NO_FILE_CACHE) || !(CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE))) { ZVAL_COPY_VALUE(result, &c->value); if (copy) { Z_TRY_ADDREF_P(result); } return 1; } else { return 0; } } /* Special constants null/true/false can always be substituted. */ c = zend_get_special_const(ZSTR_VAL(name), ZSTR_LEN(name)); if (c) { ZVAL_COPY_VALUE(result, &c->value); return 1; } return 0; } /* Data dependencies macros */ #define VAR_SOURCE(op) Tsource[VAR_NUM(op.var)] #define SET_VAR_SOURCE(opline) Tsource[VAR_NUM(opline->result.var)] = opline static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b) { zend_op *opcodes = op_array->opcodes; do { b->start++; b->len--; } while (b->len > 0 && opcodes[b->start].opcode == ZEND_NOP); } static void strip_nops(zend_op_array *op_array, zend_basic_block *b) { uint32_t i, j; if (b->len == 0) { return; } if (op_array->opcodes[b->start].opcode == ZEND_NOP) { strip_leading_nops(op_array, b); } if (b->len == 0) { return; } /* strip the inside NOPs */ i = j = b->start + 1; 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]; } j++; } i++; } b->len = j - b->start; while (j < i) { MAKE_NOP(op_array->opcodes + j); j++; } } static int get_const_switch_target(zend_cfg *cfg, zend_op_array *op_array, zend_basic_block *block, zend_op *opline, zval *val) { HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline)); zval *zv; if ((opline->opcode == ZEND_SWITCH_LONG && Z_TYPE_P(val) != IS_LONG) || (opline->opcode == ZEND_SWITCH_STRING && Z_TYPE_P(val) != IS_STRING)) { /* fallback to next block */ return block->successors[block->successors_count - 1]; } if (opline->opcode == ZEND_MATCH && Z_TYPE_P(val) != IS_LONG && Z_TYPE_P(val) != IS_STRING) { /* always jump to the default arm */ return block->successors[block->successors_count - 1]; } if (Z_TYPE_P(val) == IS_LONG) { zv = zend_hash_index_find(jumptable, Z_LVAL_P(val)); } else { ZEND_ASSERT(Z_TYPE_P(val) == IS_STRING); zv = zend_hash_find(jumptable, Z_STR_P(val)); } if (!zv) { /* default */ return block->successors[block->successors_count - (opline->opcode == ZEND_MATCH ? 1 : 2)]; } return cfg->map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))]; } static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_op **Tsource, uint32_t *opt_count) { zend_op *opline, *src; zend_op *end, *last_op = NULL; if (block->len == 0) { return; } if (op_array->opcodes[block->start].opcode == ZEND_NOP) { /* remove leading NOPs */ strip_leading_nops(op_array, block); } opline = op_array->opcodes + block->start; end = opline + block->len; while (opline < end) { /* Constant Propagation: strip X = QM_ASSIGN(const) */ if (opline->op1_type == IS_TMP_VAR && opline->opcode != ZEND_FREE) { src = VAR_SOURCE(opline->op1); if (src && src->opcode == ZEND_QM_ASSIGN && src->op1_type == IS_CONST ) { znode_op op1 = opline->op1; if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) { COPY_NODE(opline->result, opline->op1); COPY_NODE(opline->op1, src->op1); VAR_SOURCE(op1) = NULL; MAKE_NOP(src); ++(*opt_count); } else { zval c; ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src)); if (zend_optimizer_update_op1_const(op_array, opline, &c)) { VAR_SOURCE(op1) = NULL; literal_dtor(&ZEND_OP1_LITERAL(src)); MAKE_NOP(src); ++(*opt_count); } else { zval_ptr_dtor_nogc(&c); } } } } /* Constant Propagation: strip X = QM_ASSIGN(const) */ if (opline->op2_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op2); if (src && src->opcode == ZEND_QM_ASSIGN && src->op1_type == IS_CONST) { znode_op op2 = opline->op2; zval c; ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src)); if (zend_optimizer_update_op2_const(op_array, opline, &c)) { VAR_SOURCE(op2) = NULL; literal_dtor(&ZEND_OP1_LITERAL(src)); MAKE_NOP(src); ++(*opt_count); } else { zval_ptr_dtor_nogc(&c); } } } switch (opline->opcode) { case ZEND_ECHO: if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { src = VAR_SOURCE(opline->op1); if (src && src->opcode == ZEND_CAST && src->extended_value == IS_STRING) { /* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */ VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); } } else if (opline->op1_type == IS_CONST && Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_DOUBLE) { if (last_op == opline - 1) { /* compress consecutive ECHO's. * Float to string conversion may be affected by current * locale setting. */ int l, old_len; if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) { convert_to_string(&ZEND_OP1_LITERAL(opline)); } if (Z_TYPE(ZEND_OP1_LITERAL(last_op)) != IS_STRING) { convert_to_string(&ZEND_OP1_LITERAL(last_op)); } old_len = Z_STRLEN(ZEND_OP1_LITERAL(last_op)); l = old_len + Z_STRLEN(ZEND_OP1_LITERAL(opline)); if (!Z_REFCOUNTED(ZEND_OP1_LITERAL(last_op))) { zend_string *tmp = zend_string_alloc(l, 0); memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP1_LITERAL(last_op)), old_len); Z_STR(ZEND_OP1_LITERAL(last_op)) = tmp; } else { Z_STR(ZEND_OP1_LITERAL(last_op)) = zend_string_extend(Z_STR(ZEND_OP1_LITERAL(last_op)), l, 0); } Z_TYPE_INFO(ZEND_OP1_LITERAL(last_op)) = IS_STRING_EX; memcpy(Z_STRVAL(ZEND_OP1_LITERAL(last_op)) + old_len, Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline))); Z_STRVAL(ZEND_OP1_LITERAL(last_op))[l] = '\0'; zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline)); ZVAL_STR(&ZEND_OP1_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP1_LITERAL(last_op)))); ZVAL_NULL(&ZEND_OP1_LITERAL(last_op)); MAKE_NOP(last_op); ++(*opt_count); } last_op = opline; } break; case ZEND_FREE: if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src) { switch (src->opcode) { case ZEND_BOOL: case ZEND_BOOL_NOT: /* T = BOOL(X), FREE(T) => T = BOOL(X) */ /* The remaining BOOL is removed by a separate optimization */ VAR_SOURCE(opline->op1) = NULL; MAKE_NOP(opline); ++(*opt_count); break; case ZEND_ASSIGN: case ZEND_ASSIGN_DIM: case ZEND_ASSIGN_OBJ: case ZEND_ASSIGN_STATIC_PROP: case ZEND_ASSIGN_OP: case ZEND_ASSIGN_DIM_OP: case ZEND_ASSIGN_OBJ_OP: case ZEND_ASSIGN_STATIC_PROP_OP: case ZEND_PRE_INC: case ZEND_PRE_DEC: case ZEND_PRE_INC_OBJ: case ZEND_PRE_DEC_OBJ: case ZEND_PRE_INC_STATIC_PROP: case ZEND_PRE_DEC_STATIC_PROP: src->result_type = IS_UNUSED; VAR_SOURCE(opline->op1) = NULL; MAKE_NOP(opline); ++(*opt_count); break; default: break; } } } else if (opline->op1_type == IS_VAR) { src = VAR_SOURCE(opline->op1); /* V = OP, FREE(V) => OP. NOP */ if (src && src->opcode != ZEND_FETCH_R && src->opcode != ZEND_FETCH_STATIC_PROP_R && src->opcode != ZEND_FETCH_DIM_R && src->opcode != ZEND_FETCH_OBJ_R && src->opcode != ZEND_NEW) { src->result_type = IS_UNUSED; MAKE_NOP(opline); ++(*opt_count); if (src->opcode == ZEND_QM_ASSIGN) { if (src->op1_type & (IS_VAR|IS_TMP_VAR)) { src->opcode = ZEND_FREE; } else { MAKE_NOP(src); } } } } break; #if 0 /* pre-evaluate functions: constant(x) function_exists(x) extension_loaded(x) BAD: interacts badly with Accelerator */ if((opline->op1_type & IS_VAR) && VAR_SOURCE(opline->op1) && VAR_SOURCE(opline->op1)->opcode == ZEND_DO_CF_FCALL && VAR_SOURCE(opline->op1)->extended_value == 1) { zend_op *fcall = VAR_SOURCE(opline->op1); zend_op *sv = fcall-1; if(sv >= block->start_opline && sv->opcode == ZEND_SEND_VAL && sv->op1_type == IS_CONST && Z_TYPE(OPLINE_OP1_LITERAL(sv)) == IS_STRING && Z_LVAL(OPLINE_OP2_LITERAL(sv)) == 1 ) { zval *arg = &OPLINE_OP1_LITERAL(sv); char *fname = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].function_name; int flen = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].name_len; if((flen == sizeof("function_exists")-1 && zend_binary_strcasecmp(fname, flen, "function_exists", sizeof("function_exists")-1) == 0) || (flen == sizeof("is_callable")-1 && zend_binary_strcasecmp(fname, flen, "is_callable", sizeof("is_callable")-1) == 0) ) { zend_function *function; if((function = zend_hash_find_ptr(EG(function_table), Z_STR_P(arg))) != NULL) { literal_dtor(arg); MAKE_NOP(sv); MAKE_NOP(fcall); LITERAL_BOOL(opline->op1, 1); opline->op1_type = IS_CONST; } } else if(flen == sizeof("constant")-1 && zend_binary_strcasecmp(fname, flen, "constant", sizeof("constant")-1) == 0) { zval c; if(zend_optimizer_get_persistent_constant(Z_STR_P(arg), &c, 1 ELS_CC) != 0) { literal_dtor(arg); MAKE_NOP(sv); MAKE_NOP(fcall); ZEND_OP1_LITERAL(opline) = zend_optimizer_add_literal(op_array, &c); /* no copy ctor - get already copied it */ opline->op1_type = IS_CONST; } } else if(flen == sizeof("extension_loaded")-1 && zend_binary_strcasecmp(fname, flen, "extension_loaded", sizeof("extension_loaded")-1) == 0) { if(zend_hash_exists(&module_registry, Z_STR_P(arg))) { literal_dtor(arg); MAKE_NOP(sv); MAKE_NOP(fcall); LITERAL_BOOL(opline->op1, 1); opline->op1_type = IS_CONST; } } } } #endif case ZEND_FETCH_LIST_R: case ZEND_FETCH_LIST_W: if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { /* LIST variable will be deleted later by FREE */ Tsource[VAR_NUM(opline->op1.var)] = NULL; } break; case ZEND_SWITCH_LONG: case ZEND_SWITCH_STRING: case ZEND_MATCH: if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { /* SWITCH variable will be deleted later by FREE, so we can't optimize it */ Tsource[VAR_NUM(opline->op1.var)] = NULL; break; } if (opline->op1_type == IS_CONST) { int target = get_const_switch_target(cfg, op_array, block, opline, &ZEND_OP1_LITERAL(opline)); literal_dtor(&ZEND_OP1_LITERAL(opline)); literal_dtor(&ZEND_OP2_LITERAL(opline)); opline->opcode = ZEND_JMP; opline->op1_type = IS_UNUSED; opline->op2_type = IS_UNUSED; block->successors_count = 1; block->successors[0] = target; } break; case ZEND_CASE: case ZEND_CASE_STRICT: case ZEND_COPY_TMP: if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { /* Variable will be deleted later by FREE, so we can't optimize it */ Tsource[VAR_NUM(opline->op1.var)] = NULL; break; } /* break missing intentionally */ case ZEND_IS_EQUAL: case ZEND_IS_NOT_EQUAL: if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST) { goto optimize_constant_binary_op; } /* IS_EQ(TRUE, X) => BOOL(X) * IS_EQ(FALSE, X) => BOOL_NOT(X) * IS_NOT_EQ(TRUE, X) => BOOL_NOT(X) * IS_NOT_EQ(FALSE, X) => BOOL(X) * CASE(TRUE, X) => BOOL(X) * CASE(FALSE, X) => BOOL_NOT(X) */ if (opline->op1_type == IS_CONST && (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE || Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_TRUE)) { /* Optimization of comparison with "null" is not safe, * because ("0" == null) is not equal to !("0") */ opline->opcode = ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP1_LITERAL(opline))) == IS_TRUE)) ? ZEND_BOOL : ZEND_BOOL_NOT; COPY_NODE(opline->op1, opline->op2); SET_UNUSED(opline->op2); ++(*opt_count); goto optimize_bool; } else if (opline->op2_type == IS_CONST && (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE || Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) { /* Optimization of comparison with "null" is not safe, * because ("0" == null) is not equal to !("0") */ opline->opcode = ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ? ZEND_BOOL : ZEND_BOOL_NOT; SET_UNUSED(opline->op2); ++(*opt_count); goto optimize_bool; } break; case ZEND_BOOL: case ZEND_BOOL_NOT: optimize_bool: if (opline->op1_type == IS_CONST) { goto optimize_const_unary_op; } if (opline->op1_type == IS_TMP_VAR && !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) { src = VAR_SOURCE(opline->op1); if (src) { switch (src->opcode) { case ZEND_BOOL_NOT: /* T = BOOL_NOT(X) + BOOL(T) -> NOP, BOOL_NOT(X) */ VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); opline->opcode = (opline->opcode == ZEND_BOOL) ? ZEND_BOOL_NOT : ZEND_BOOL; MAKE_NOP(src); ++(*opt_count); goto optimize_bool; case ZEND_BOOL: /* T = BOOL(X) + BOOL(T) -> NOP, BOOL(X) */ VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); goto optimize_bool; case ZEND_IS_EQUAL: if (opline->opcode == ZEND_BOOL_NOT) { src->opcode = ZEND_IS_NOT_EQUAL; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; case ZEND_IS_NOT_EQUAL: if (opline->opcode == ZEND_BOOL_NOT) { src->opcode = ZEND_IS_EQUAL; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; case ZEND_IS_IDENTICAL: if (opline->opcode == ZEND_BOOL_NOT) { src->opcode = ZEND_IS_NOT_IDENTICAL; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; case ZEND_IS_NOT_IDENTICAL: if (opline->opcode == ZEND_BOOL_NOT) { src->opcode = ZEND_IS_IDENTICAL; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; case ZEND_IS_SMALLER: if (opline->opcode == ZEND_BOOL_NOT) { zend_uchar tmp_type; uint32_t tmp; src->opcode = ZEND_IS_SMALLER_OR_EQUAL; tmp_type = src->op1_type; src->op1_type = src->op2_type; src->op2_type = tmp_type; tmp = src->op1.num; src->op1.num = src->op2.num; src->op2.num = tmp; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; case ZEND_IS_SMALLER_OR_EQUAL: if (opline->opcode == ZEND_BOOL_NOT) { zend_uchar tmp_type; uint32_t tmp; src->opcode = ZEND_IS_SMALLER; tmp_type = src->op1_type; src->op1_type = src->op2_type; src->op2_type = tmp_type; tmp = src->op1.num; src->op1.num = src->op2.num; src->op2.num = tmp; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; 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->opcode == ZEND_BOOL_NOT) { break; } COPY_NODE(src->result, opline->result); SET_VAR_SOURCE(src); MAKE_NOP(opline); ++(*opt_count); break; } } } break; case ZEND_JMPZ: case ZEND_JMPNZ: while (1) { if (opline->op1_type == IS_CONST) { ++(*opt_count); block->successors_count = 1; if (zend_is_true(&ZEND_OP1_LITERAL(opline)) == (opline->opcode == ZEND_JMPZ)) { MAKE_NOP(opline); block->successors[0] = block->successors[1]; block->len--; cfg->blocks[block->successors[0]].flags |= ZEND_BB_FOLLOW; break; } else { zend_basic_block *next = cfg->blocks + block->successors[1]; next->flags &= ~ZEND_BB_FOLLOW; if (!(next->flags & (ZEND_BB_TARGET|ZEND_BB_PROTECTED))) { next->flags &= ~ZEND_BB_REACHABLE; } opline->opcode = ZEND_JMP; COPY_NODE(opline->op1, opline->op2); break; } } else if (opline->op1_type == IS_TMP_VAR && !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) { src = VAR_SOURCE(opline->op1); if (src) { if (src->opcode == ZEND_BOOL_NOT) { VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); /* T = BOOL_NOT(X) + JMPZ(T) -> NOP, JMPNZ(X) */ opline->opcode = INV_COND(opline->opcode); MAKE_NOP(src); ++(*opt_count); continue; } else if (src->opcode == ZEND_BOOL || src->opcode == ZEND_QM_ASSIGN) { VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); continue; } } } break; } break; case ZEND_JMPZNZ: while (1) { if (opline->op1_type == IS_CONST) { ++(*opt_count); 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); 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); } block->successors_count = 1; opline->op1_type = IS_UNUSED; opline->extended_value = 0; opline->opcode = ZEND_JMP; break; } else if (opline->op1_type == IS_TMP_VAR && !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) { src = VAR_SOURCE(opline->op1); if (src) { if (src->opcode == ZEND_BOOL_NOT) { /* T = BOOL_NOT(X) + JMPZNZ(T,L1,L2) -> NOP, JMPZNZ(X,L2,L1) */ uint32_t tmp; VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); tmp = block->successors[0]; block->successors[0] = block->successors[1]; block->successors[1] = tmp; MAKE_NOP(src); ++(*opt_count); continue; } else if (src->opcode == ZEND_BOOL || src->opcode == ZEND_QM_ASSIGN) { VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); continue; } } } break; } break; case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: while (1) { if (opline->op1_type == IS_CONST) { if (zend_is_true(&ZEND_OP1_LITERAL(opline)) == (opline->opcode == ZEND_JMPZ_EX)) { ++(*opt_count); opline->opcode = ZEND_QM_ASSIGN; zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline)); ZVAL_BOOL(&ZEND_OP1_LITERAL(opline), opline->opcode == ZEND_JMPZ_EX); opline->op2.num = 0; block->successors_count = 1; block->successors[0] = block->successors[1]; cfg->blocks[block->successors[0]].flags |= ZEND_BB_FOLLOW; break; } } else if (opline->op1_type == IS_TMP_VAR && (!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var)) || opline->result.var == opline->op1.var)) { src = VAR_SOURCE(opline->op1); if (src) { if (src->opcode == ZEND_BOOL || src->opcode == ZEND_QM_ASSIGN) { VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); continue; } } } break; } break; case ZEND_CONCAT: case ZEND_FAST_CONCAT: if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST) { goto optimize_constant_binary_op; } if (opline->op2_type == IS_CONST && opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src && (src->opcode == ZEND_CONCAT || src->opcode == ZEND_FAST_CONCAT) && src->op2_type == IS_CONST) { /* compress consecutive CONCATs */ int l, old_len; if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) { convert_to_string(&ZEND_OP2_LITERAL(opline)); } if (Z_TYPE(ZEND_OP2_LITERAL(src)) != IS_STRING) { convert_to_string(&ZEND_OP2_LITERAL(src)); } VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); old_len = Z_STRLEN(ZEND_OP2_LITERAL(src)); l = old_len + Z_STRLEN(ZEND_OP2_LITERAL(opline)); if (!Z_REFCOUNTED(ZEND_OP2_LITERAL(src))) { zend_string *tmp = zend_string_alloc(l, 0); memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP2_LITERAL(src)), old_len); Z_STR(ZEND_OP2_LITERAL(src)) = tmp; } else { Z_STR(ZEND_OP2_LITERAL(src)) = zend_string_extend(Z_STR(ZEND_OP2_LITERAL(src)), l, 0); } Z_TYPE_INFO(ZEND_OP2_LITERAL(src)) = IS_STRING_EX; memcpy(Z_STRVAL(ZEND_OP2_LITERAL(src)) + old_len, Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline))); Z_STRVAL(ZEND_OP2_LITERAL(src))[l] = '\0'; zval_ptr_dtor_str(&ZEND_OP2_LITERAL(opline)); ZVAL_STR(&ZEND_OP2_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP2_LITERAL(src)))); ZVAL_NULL(&ZEND_OP2_LITERAL(src)); MAKE_NOP(src); ++(*opt_count); } } if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { src = VAR_SOURCE(opline->op1); if (src && src->opcode == ZEND_CAST && src->extended_value == IS_STRING && src->op1_type != IS_CONST) { /* convert T1 = CAST(STRING, X), T2 = CONCAT(T1, Y) to T2 = CONCAT(X,Y) */ VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); } } if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) { src = VAR_SOURCE(opline->op2); if (src && src->opcode == ZEND_CAST && src->extended_value == IS_STRING && src->op1_type != IS_CONST) { /* convert T1 = CAST(STRING, X), T2 = CONCAT(Y, T1) to T2 = CONCAT(Y,X) */ zend_op *src = VAR_SOURCE(opline->op2); VAR_SOURCE(opline->op2) = NULL; COPY_NODE(opline->op2, src->op1); MAKE_NOP(src); ++(*opt_count); } } if (opline->op1_type == IS_CONST && Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING && Z_STRLEN(ZEND_OP1_LITERAL(opline)) == 0) { /* convert CONCAT('', X) => CAST(STRING, X) */ literal_dtor(&ZEND_OP1_LITERAL(opline)); opline->opcode = ZEND_CAST; opline->extended_value = IS_STRING; COPY_NODE(opline->op1, opline->op2); opline->op2_type = IS_UNUSED; opline->op2.var = 0; ++(*opt_count); } else if (opline->op2_type == IS_CONST && Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING && Z_STRLEN(ZEND_OP2_LITERAL(opline)) == 0) { /* convert CONCAT(X, '') => CAST(STRING, X) */ literal_dtor(&ZEND_OP2_LITERAL(opline)); opline->opcode = ZEND_CAST; opline->extended_value = IS_STRING; opline->op2_type = IS_UNUSED; opline->op2.var = 0; ++(*opt_count); } else if (opline->opcode == ZEND_CONCAT && (opline->op1_type == IS_CONST || (opline->op1_type == IS_TMP_VAR && VAR_SOURCE(opline->op1) && (VAR_SOURCE(opline->op1)->opcode == ZEND_FAST_CONCAT || VAR_SOURCE(opline->op1)->opcode == ZEND_ROPE_END || VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CONSTANT || VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CLASS_CONSTANT))) && (opline->op2_type == IS_CONST || (opline->op2_type == IS_TMP_VAR && VAR_SOURCE(opline->op2) && (VAR_SOURCE(opline->op2)->opcode == ZEND_FAST_CONCAT || VAR_SOURCE(opline->op2)->opcode == ZEND_ROPE_END || VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CONSTANT || VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CLASS_CONSTANT)))) { opline->opcode = ZEND_FAST_CONCAT; ++(*opt_count); } break; case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: case ZEND_DIV: case ZEND_MOD: case ZEND_SL: case ZEND_SR: case ZEND_IS_SMALLER: case ZEND_IS_SMALLER_OR_EQUAL: case ZEND_IS_IDENTICAL: case ZEND_IS_NOT_IDENTICAL: case ZEND_BOOL_XOR: case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST) { /* evaluate constant expressions */ zval result; optimize_constant_binary_op: if (zend_optimizer_eval_binary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) { literal_dtor(&ZEND_OP1_LITERAL(opline)); literal_dtor(&ZEND_OP2_LITERAL(opline)); opline->opcode = ZEND_QM_ASSIGN; SET_UNUSED(opline->op2); zend_optimizer_update_op1_const(op_array, opline, &result); ++(*opt_count); } } break; case ZEND_BW_NOT: if (opline->op1_type == IS_CONST) { /* evaluate constant unary ops */ zval result; optimize_const_unary_op: if (zend_optimizer_eval_unary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline)) == SUCCESS) { literal_dtor(&ZEND_OP1_LITERAL(opline)); opline->opcode = ZEND_QM_ASSIGN; zend_optimizer_update_op1_const(op_array, opline, &result); ++(*opt_count); } } break; case ZEND_CAST: if (opline->op1_type == IS_CONST) { /* cast of constant operand */ zval result; if (zend_optimizer_eval_cast(&result, opline->extended_value, &ZEND_OP1_LITERAL(opline)) == SUCCESS) { literal_dtor(&ZEND_OP1_LITERAL(opline)); opline->opcode = ZEND_QM_ASSIGN; opline->extended_value = 0; zend_optimizer_update_op1_const(op_array, opline, &result); ++(*opt_count); } } break; case ZEND_STRLEN: if (opline->op1_type == IS_CONST) { zval result; if (zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) { literal_dtor(&ZEND_OP1_LITERAL(opline)); opline->opcode = ZEND_QM_ASSIGN; zend_optimizer_update_op1_const(op_array, opline, &result); ++(*opt_count); } } break; case ZEND_RETURN: case ZEND_EXIT: if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src && src->opcode == ZEND_QM_ASSIGN) { zend_op *op = src + 1; bool optimize = 1; while (op < opline) { if ((op->op1_type == opline->op1_type && op->op1.var == opline->op1.var) || (op->op2_type == opline->op1_type && op->op2.var == opline->op1.var)) { optimize = 0; break; } op++; } if (optimize) { /* T = QM_ASSIGN(X), RETURN(T) to NOP, RETURN(X) */ VAR_SOURCE(opline->op1) = NULL; COPY_NODE(opline->op1, src->op1); MAKE_NOP(src); ++(*opt_count); } } } break; case ZEND_QM_ASSIGN: if (opline->op1_type == opline->result_type && opline->op1.var == opline->result.var) { /* strip T = QM_ASSIGN(T) */ MAKE_NOP(opline); ++(*opt_count); } else if (opline->op1_type == IS_TMP_VAR && opline->result_type == IS_TMP_VAR && !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) { /* T1 = ..., T2 = QM_ASSIGN(T1) to T2 = ..., NOP */ src = VAR_SOURCE(opline->op1); if (src && src->opcode != ZEND_COPY_TMP && src->opcode != ZEND_ADD_ARRAY_ELEMENT && src->opcode != ZEND_ADD_ARRAY_UNPACK && (src->opcode != ZEND_DECLARE_LAMBDA_FUNCTION || src == opline -1)) { src->result.var = opline->result.var; VAR_SOURCE(opline->op1) = NULL; VAR_SOURCE(opline->result) = src; MAKE_NOP(opline); ++(*opt_count); } } break; } /* get variable source */ if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { SET_VAR_SOURCE(opline); } opline++; } } /* Rebuild plain (optimized) op_array from CFG */ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_basic_block *blocks = cfg->blocks; zend_basic_block *end = blocks + cfg->blocks_count; zend_basic_block *b; zend_op *new_opcodes; zend_op *opline; uint32_t len = 0; int n; for (b = blocks; b < end; b++) { if (b->len == 0) { continue; } if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { 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); len += b->len = 1; continue; } 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++; } if (next < end && next == blocks + b->successors[0]) { /* JMP to the next block - strip it */ MAKE_NOP(opline); b->len--; } } else if (b->len == 1 && opline->opcode == ZEND_NOP) { /* skip empty block */ b->len--; } len += b->len; } else { /* this block will not be used, delete all constants there */ zend_op *op = op_array->opcodes + b->start; zend_op *end = op + b->len; for (; op < end; op++) { if (op->op1_type == IS_CONST) { literal_dtor(&ZEND_OP1_LITERAL(op)); } if (op->op2_type == IS_CONST) { literal_dtor(&ZEND_OP2_LITERAL(op)); } } } } new_opcodes = emalloc(len * sizeof(zend_op)); opline = new_opcodes; /* Copy code of reachable blocks into a single buffer */ for (b = blocks; b < end; b++) { if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { memcpy(opline, op_array->opcodes + b->start, b->len * sizeof(zend_op)); b->start = opline - new_opcodes; opline += b->len; } } /* adjust jump targets */ efree(op_array->opcodes); op_array->opcodes = new_opcodes; op_array->last = len; for (b = blocks; b < end; b++) { if (!(b->flags & ZEND_BB_REACHABLE) || b->len == 0) { continue; } opline = op_array->opcodes + b->start + b->len - 1; switch (opline->opcode) { case ZEND_FAST_CALL: case ZEND_JMP: ZEND_SET_OP_JMP_ADDR(opline, opline->op1, new_opcodes + blocks[b->successors[0]].start); break; case ZEND_JMPZNZ: opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[1]].start); /* 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: case ZEND_JMP_NULL: ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start); break; case ZEND_CATCH: if (!(opline->extended_value & ZEND_LAST_CATCH)) { ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start); } break; case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[0]].start); break; case ZEND_SWITCH_LONG: case ZEND_SWITCH_STRING: case ZEND_MATCH: { HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline)); zval *zv; uint32_t s = 0; ZEND_ASSERT(b->successors_count == (opline->opcode == ZEND_MATCH ? 1 : 2) + zend_hash_num_elements(jumptable)); ZEND_HASH_FOREACH_VAL(jumptable, zv) { Z_LVAL_P(zv) = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start); } ZEND_HASH_FOREACH_END(); opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start); break; } } } /* adjust exception jump targets & remove unused try_catch_array entries */ if (op_array->last_try_catch) { int i, j; uint32_t *map; ALLOCA_FLAG(use_heap); map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_try_catch, use_heap); for (i = 0, j = 0; i< op_array->last_try_catch; i++) { if (blocks[cfg->map[op_array->try_catch_array[i].try_op]].flags & ZEND_BB_REACHABLE) { map[i] = j; op_array->try_catch_array[j].try_op = blocks[cfg->map[op_array->try_catch_array[i].try_op]].start; if (op_array->try_catch_array[i].catch_op) { op_array->try_catch_array[j].catch_op = blocks[cfg->map[op_array->try_catch_array[i].catch_op]].start; } else { op_array->try_catch_array[j].catch_op = 0; } if (op_array->try_catch_array[i].finally_op) { op_array->try_catch_array[j].finally_op = blocks[cfg->map[op_array->try_catch_array[i].finally_op]].start; } else { op_array->try_catch_array[j].finally_op = 0; } if (!op_array->try_catch_array[i].finally_end) { op_array->try_catch_array[j].finally_end = 0; } else { op_array->try_catch_array[j].finally_end = blocks[cfg->map[op_array->try_catch_array[i].finally_end]].start; } j++; } } if (i != j) { op_array->last_try_catch = j; if (j == 0) { efree(op_array->try_catch_array); op_array->try_catch_array = NULL; } if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { zend_op *opline = new_opcodes; zend_op *end = opline + len; while (opline < end) { 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]; } opline++; } } } free_alloca(map, use_heap); } /* adjust early binding list */ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) { ZEND_ASSERT(op_array == &ctx->script->main_op_array); ctx->script->first_early_binding_opline = zend_build_delayed_early_binding_list(op_array); } /* rebuild map (just for printing) */ memset(cfg->map, -1, sizeof(int) * op_array->last); for (n = 0; n < cfg->blocks_count; n++) { if (cfg->blocks[n].flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { cfg->map[cfg->blocks[n].start] = n; } } } 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 *target_block, *follow_block, *next_block; zend_op *last_op, *target; int next, jmp_hitlist_count; if (block->len == 0) { return; } last_op = op_array->opcodes + block->start + block->len - 1; switch (last_op->opcode) { case ZEND_JMP: 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; 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); } 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) { zval zv; ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op)); last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv); } block->successors_count = 2; block->successors[0] = target_block->successors[0]; block->successors[1] = target_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 */ *last_op = *target; if (last_op->op1_type == IS_CONST) { zval zv; ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op)); last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv); } block->successors_count = 0; ++(*opt_count); } } break; case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_JMP_NULL: 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; 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 { break; } target_block = get_target_block(cfg, block, 0, opt_count); } break; case ZEND_JMPZ: case ZEND_JMPNZ: 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; 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, last_op->op1)) { /* JMPZ(X, L), L: JMPZ(X, L2) -> JMPZ(X, L2) */ 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, last_op->op1)) { /* JMPZ(X, L), L: JMPZNZ(X, L2, L3) -> JMPZ(X, L2) */ 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); } 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[0] = follow_block - cfg->blocks; block->successors_count = 1; ++(*opt_count); break; } else if (!(follow_block->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED))) { next_block = get_next_block(cfg, follow_block); if (target_block == next_block) { /* JMPZ(X,L1) JMP(L2) L1: -> JMPNZ(X,L2) NOP*/ last_op->opcode = INV_COND(last_op->opcode); block->successors[0] = follow_block->successors[0]; block->successors[1] = next_block - cfg->blocks; follow_block->flags &= ~ZEND_BB_REACHABLE; MAKE_NOP(target); follow_block->len = 0; next_block->flags |= ZEND_BB_FOLLOW; break; } } /* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */ if (last_op->opcode == ZEND_JMPZ) { block->successors[1] = follow_block->successors[0]; } else { block->successors[1] = block->successors[0]; block->successors[0] = follow_block->successors[0]; } last_op->opcode = ZEND_JMPZNZ; ++(*opt_count); } } break; case ZEND_JMPNZ_EX: case ZEND_JMPZ_EX: 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; 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, 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) */ next = target_block->successors[0]; } else if (target->opcode == 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 = JMPZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L2) */ next = target_block->successors[0]; } else if (target->opcode == ZEND_JMPZNZ && (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) */ 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 breaks 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: { 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; 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); } 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), L2: JMP(L3) -> JMPZNZ(X, L1, L3) */ next = follow_block->successors[0]; } else if (target->opcode == ZEND_JMPNZ && SAME_VAR(target->op1, last_op->op1)) { /* JMPZNZ(X, L1, L2), L2: X = JMPNZ(X, L3) -> JMPZNZ(X, L1, L3) */ 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), L2: JMPZ(X, L3) -> JMPZNZ(X, L1, L2+1) */ next = follow_block->successors[1]; } else { break; } 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] = tmp; ++(*opt_count); } 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); } break; } } } /* Global data dependencies */ /* Find a set of variables which are used outside of the block where they are * defined. We won't apply some optimization patterns for such variables. */ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx) { int n; zend_basic_block *block, *next_block; uint32_t var_num; uint32_t bitset_len; zend_bitset usage; zend_bitset defined_here; void *checkpoint; zend_op *opline, *end; if (op_array->T == 0) { /* shortcut - if no Ts, nothing to do */ return; } checkpoint = zend_arena_checkpoint(ctx->arena); bitset_len = zend_bitset_len(op_array->last_var + op_array->T); defined_here = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE); zend_bitset_clear(defined_here, bitset_len); for (n = 1; n < cfg->blocks_count; n++) { block = cfg->blocks + n; if (!(block->flags & ZEND_BB_REACHABLE)) { continue; } opline = op_array->opcodes + block->start; end = opline + block->len; if (!(block->flags & ZEND_BB_FOLLOW) || (block->flags & ZEND_BB_TARGET)) { /* Skip continuation of "extended" BB */ zend_bitset_clear(defined_here, bitset_len); } while (oplineop1_type & (IS_VAR|IS_TMP_VAR)) { var_num = VAR_NUM(opline->op1.var); if (!zend_bitset_in(defined_here, var_num)) { zend_bitset_incl(used_ext, var_num); } } if (opline->op2_type == IS_VAR) { var_num = VAR_NUM(opline->op2.var); if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) { /* these opcode use the op2 as result */ zend_bitset_incl(defined_here, var_num); } else if (!zend_bitset_in(defined_here, var_num)) { zend_bitset_incl(used_ext, var_num); } } else if (opline->op2_type == IS_TMP_VAR) { var_num = VAR_NUM(opline->op2.var); if (!zend_bitset_in(defined_here, var_num)) { zend_bitset_incl(used_ext, var_num); } } if (opline->result_type == IS_VAR) { var_num = VAR_NUM(opline->result.var); zend_bitset_incl(defined_here, var_num); } else if (opline->result_type == IS_TMP_VAR) { var_num = VAR_NUM(opline->result.var); switch (opline->opcode) { case ZEND_ADD_ARRAY_ELEMENT: case ZEND_ADD_ARRAY_UNPACK: case ZEND_ROPE_ADD: /* these opcodes use the result as argument */ if (!zend_bitset_in(defined_here, var_num)) { zend_bitset_incl(used_ext, var_num); } break; default : zend_bitset_incl(defined_here, var_num); } } opline++; } } if (ctx->debug_level & ZEND_DUMP_BLOCK_PASS_VARS) { int printed = 0; uint32_t i; for (i = op_array->last_var; i< op_array->T; i++) { if (zend_bitset_in(used_ext, i)) { if (!printed) { fprintf(stderr, "NON-LOCAL-VARS: %d", i); printed = 1; } else { fprintf(stderr, ", %d", i); } } } if (printed) { fprintf(stderr, "\n"); } } usage = defined_here; next_block = NULL; for (n = cfg->blocks_count; n > 0;) { block = cfg->blocks + (--n); if (!(block->flags & ZEND_BB_REACHABLE) || block->len == 0) { continue; } 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)) { /* Skip continuation of "extended" BB */ zend_bitset_copy(usage, used_ext, bitset_len); } else if (block->successors_count > 1) { zend_bitset_union(usage, used_ext, bitset_len); } next_block = block; while (opline >= end) { /* usage checks */ if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) { switch (opline->opcode) { case ZEND_ASSIGN_OP: case ZEND_ASSIGN_DIM_OP: case ZEND_ASSIGN_OBJ_OP: case ZEND_ASSIGN_STATIC_PROP_OP: case ZEND_PRE_INC: case ZEND_PRE_DEC: case ZEND_ASSIGN: case ZEND_ASSIGN_REF: case ZEND_DO_FCALL: case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: opline->result_type = IS_UNUSED; break; case ZEND_POST_INC: case ZEND_POST_DEC: case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: case ZEND_POST_INC_STATIC_PROP: case ZEND_POST_DEC_STATIC_PROP: opline->opcode -= 2; opline->result_type = IS_UNUSED; break; case ZEND_QM_ASSIGN: case ZEND_BOOL: case ZEND_BOOL_NOT: if (opline->op1_type == IS_CV) { opline->opcode = ZEND_CHECK_VAR; SET_UNUSED(opline->result); } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { opline->opcode = ZEND_FREE; SET_UNUSED(opline->result); } else { if (opline->op1_type == IS_CONST) { literal_dtor(&ZEND_OP1_LITERAL(opline)); } MAKE_NOP(opline); } break; case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: opline->opcode -= 3; SET_UNUSED(opline->result); break; case ZEND_ADD_ARRAY_ELEMENT: case ZEND_ADD_ARRAY_UNPACK: case ZEND_ROPE_ADD: zend_bitset_incl(usage, VAR_NUM(opline->result.var)); break; } } else { switch (opline->opcode) { case ZEND_ADD_ARRAY_ELEMENT: case ZEND_ADD_ARRAY_UNPACK: case ZEND_ROPE_ADD: break; default: zend_bitset_excl(usage, VAR_NUM(opline->result.var)); break; } } } if (opline->op2_type == IS_VAR) { switch (opline->opcode) { case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: zend_bitset_excl(usage, VAR_NUM(opline->op2.var)); break; default: zend_bitset_incl(usage, VAR_NUM(opline->op2.var)); break; } } else if (opline->op2_type == IS_TMP_VAR) { zend_bitset_incl(usage, VAR_NUM(opline->op2.var)); } if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { zend_bitset_incl(usage, VAR_NUM(opline->op1.var)); } opline--; } } zend_arena_release(&ctx->arena, checkpoint); } static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg, uint32_t *opt_count) { int i; zend_basic_block *b, *bb; zend_basic_block *prev = NULL; for (i = 0; i < cfg->blocks_count; i++) { b = cfg->blocks + i; if (b->flags & ZEND_BB_REACHABLE) { if ((b->flags & ZEND_BB_FOLLOW) && !(b->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED)) && prev && prev->successors_count == 1 && prev->successors[0] == i) { 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 + bb->len; while (op < end) { if (op->op1_type == IS_CONST) { literal_dtor(&ZEND_OP1_LITERAL(op)); } if (op->op2_type == IS_CONST) { literal_dtor(&ZEND_OP2_LITERAL(op)); } MAKE_NOP(op); op++; } /* make block empty */ bb->len = 0; } /* re-link */ prev->flags |= (b->flags & ZEND_BB_EXIT); prev->len = b->start + b->len - prev->start; prev->successors_count = b->successors_count; if (b->successors != b->successors_storage) { prev->successors = b->successors; b->successors = b->successors_storage; } else { memcpy(prev->successors, b->successors, b->successors_count * sizeof(int)); } /* unlink & make block empty and unreachable */ b->flags = 0; b->len = 0; b->successors_count = 0; ++(*opt_count); } else { prev = b; } } } } #define PASSES 3 void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_cfg cfg; zend_basic_block *blocks, *end, *b; int pass; uint32_t bitset_len; zend_bitset usage; void *checkpoint; zend_op **Tsource; uint32_t opt_count; int *jmp_hitlist; /* Build CFG */ checkpoint = zend_arena_checkpoint(ctx->arena); if (zend_build_cfg(&ctx->arena, op_array, 0, &cfg) != SUCCESS) { zend_arena_release(&ctx->arena, checkpoint); return; } if (cfg.blocks_count * (op_array->last_var + op_array->T) > 64 * 1024 * 1024) { zend_arena_release(&ctx->arena, checkpoint); return; } if (ctx->debug_level & ZEND_DUMP_BEFORE_BLOCK_PASS) { zend_dump_op_array(op_array, ZEND_DUMP_CFG, "before block pass", &cfg); } 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; for (pass = 0; pass < PASSES; pass++) { opt_count = 0; /* Compute data dependencies */ zend_bitset_clear(usage, bitset_len); zend_t_usage(&cfg, op_array, usage, ctx); /* optimize each basic block separately */ for (b = blocks; b < end; b++) { if (!(b->flags & ZEND_BB_REACHABLE)) { continue; } /* we track data dependencies only inside a single basic block */ if (!(b->flags & ZEND_BB_FOLLOW) || (b->flags & ZEND_BB_TARGET)) { /* Skip continuation of "extended" BB */ memset(Tsource, 0, (op_array->last_var + op_array->T) * sizeof(zend_op *)); } zend_optimize_block(b, op_array, usage, &cfg, Tsource, &opt_count); } /* Eliminate NOPs */ for (b = blocks; b < end; b++) { if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { strip_nops(op_array, b); } } opt_count = 0; /* Jump optimization for each block */ for (b = blocks; b < end; b++) { if (b->flags & ZEND_BB_REACHABLE) { zend_jmp_optimization(b, op_array, &cfg, jmp_hitlist, &opt_count); } } /* Eliminate unreachable basic blocks */ zend_cfg_remark_reachable_blocks(op_array, &cfg); /* Merge Blocks */ zend_merge_blocks(op_array, &cfg, &opt_count); if (opt_count == 0) { break; } } assemble_code_blocks(&cfg, op_array, ctx); if (ctx->debug_level & ZEND_DUMP_AFTER_BLOCK_PASS) { zend_dump_op_array(op_array, ZEND_DUMP_CFG | ZEND_DUMP_HIDE_UNREACHABLE, "after block pass", &cfg); } /* Destroy CFG */ zend_arena_release(&ctx->arena, checkpoint); }