diff options
Diffstat (limited to 'ext/opcache/Optimizer')
-rw-r--r-- | ext/opcache/Optimizer/block_pass.c | 2090 | ||||
-rw-r--r-- | ext/opcache/Optimizer/nop_removal.c | 126 | ||||
-rw-r--r-- | ext/opcache/Optimizer/optimize_temp_vars_5.c | 222 | ||||
-rw-r--r-- | ext/opcache/Optimizer/pass10.c | 3 | ||||
-rw-r--r-- | ext/opcache/Optimizer/pass1_5.c | 338 | ||||
-rw-r--r-- | ext/opcache/Optimizer/pass2.c | 211 | ||||
-rw-r--r-- | ext/opcache/Optimizer/pass3.c | 442 | ||||
-rw-r--r-- | ext/opcache/Optimizer/pass5.c | 3 | ||||
-rw-r--r-- | ext/opcache/Optimizer/pass9.c | 8 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_optimizer.c | 351 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_optimizer.h | 49 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_optimizer_internal.h | 86 |
12 files changed, 3929 insertions, 0 deletions
diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c new file mode 100644 index 0000000000..8c4c58e493 --- /dev/null +++ b/ext/opcache/Optimizer/block_pass.c @@ -0,0 +1,2090 @@ +#define DEBUG_BLOCKPASS 0 + +/* Checks if a constant (like "true") may be replaced by its value */ +static int zend_get_persistent_constant(char *name, uint name_len, zval *result, int copy TSRMLS_DC ELS_DC) +{ + zend_constant *c; + char *lookup_name; + int retval = 1; + ALLOCA_FLAG(use_heap); + + if (zend_hash_find(EG(zend_constants), name, name_len + 1, (void **) &c) == FAILURE) { + lookup_name = DO_ALLOCA(name_len + 1); + memcpy(lookup_name, name, name_len + 1); + zend_str_tolower(lookup_name, name_len); + + if (zend_hash_find(EG(zend_constants), lookup_name, name_len + 1, (void **) &c) == SUCCESS) { + if (!(c->flags & CONST_CT_SUBST) || (c->flags & CONST_CS)) { + retval = 0; + } + } else { + retval = 0; + } + FREE_ALLOCA(lookup_name); + } + + if (retval) { + if (c->flags & CONST_PERSISTENT) { + *result = c->value; + if (copy) { + zval_copy_ctor(result); + } + } else { + retval = 0; + } + } + + return retval; +} + +#if DEBUG_BLOCKPASS +# define BLOCK_REF(b) b?op_array->opcodes-b->start_opline:-1 + +static inline void print_block(zend_code_block *block, zend_op *opcodes, char *txt) +{ + fprintf(stderr, "%sBlock: %d-%d (%d)", txt, block->start_opline - opcodes, block->start_opline - opcodes + block->len - 1, block->len); + if (!block->access) { + fprintf(stderr, " unused"); + } + if (block->op1_to) { + fprintf(stderr, " 1: %d", block->op1_to->start_opline - opcodes); + } + if (block->op2_to) { + fprintf(stderr, " 2: %d", block->op2_to->start_opline - opcodes); + } + if (block->ext_to) { + fprintf(stderr, " e: %d", block->ext_to->start_opline - opcodes); + } + if (block->follow_to) { + fprintf(stderr, " f: %d", block->follow_to->start_opline - opcodes); + } + + if (block->sources) { + zend_block_source *bs = block->sources; + fprintf(stderr, " s:"); + while (bs) { + fprintf(stderr, " %d", bs->from->start_opline - opcodes); + bs = bs->next; + } + } + + fprintf(stderr, "\n"); + fflush(stderr); +} +#else +#define print_block(a,b,c) +#endif + +#define START_BLOCK_OP(opno) blocks[opno].start_opline = &op_array->opcodes[opno]; blocks[opno].start_opline_no = opno; blocks[opno].access = 1 + +/* find code blocks in op_array + code block is a set of opcodes with single flow of control, i.e. without jmps, + branches, etc. */ +static int find_code_blocks(zend_op_array *op_array, zend_cfg *cfg) +{ + zend_op *opline; + zend_op *end = op_array->opcodes + op_array->last; + zend_code_block *blocks, *cur_block; + zend_uint opno = 0; + + memset(cfg, 0, sizeof(zend_cfg)); + blocks = cfg->blocks = ecalloc(op_array->last + 2, sizeof(zend_code_block)); + opline = op_array->opcodes; + blocks[0].start_opline = opline; + blocks[0].start_opline_no = 0; + while (opline < end) { + switch((unsigned)opline->opcode) { + case ZEND_BRK: + case ZEND_CONT: +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + case ZEND_GOTO: +#endif + /* would not optimize non-optimized BRK/CONTs - we cannot + really know where it jumps, so these optimizations are + too dangerous */ + efree(blocks); + return 0; +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + case ZEND_FAST_CALL: + START_BLOCK_OP(ZEND_OP1(opline).opline_num); + if (opline->extended_value) { + START_BLOCK_OP(ZEND_OP2(opline).opline_num); + } + START_BLOCK_OP(opno + 1); + break; + case ZEND_FAST_RET: + if (opline->extended_value) { + START_BLOCK_OP(ZEND_OP2(opline).opline_num); + } + START_BLOCK_OP(opno + 1); + break; +#endif + case ZEND_JMP: + START_BLOCK_OP(ZEND_OP1(opline).opline_num); + /* break missing intentionally */ + case ZEND_RETURN: +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + case ZEND_RETURN_BY_REF: +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + case ZEND_GENERATOR_RETURN: +#endif + case ZEND_EXIT: + case ZEND_THROW: + /* start new block from this+1 */ + START_BLOCK_OP(opno + 1); + break; + /* TODO: if conditional jmp depends on constant, + don't start block that won't be executed */ + case ZEND_CATCH: + START_BLOCK_OP(opline->extended_value); + START_BLOCK_OP(opno + 1); + break; + case ZEND_JMPZNZ: + START_BLOCK_OP(opline->extended_value); + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET: + case ZEND_NEW: +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + case ZEND_JMP_SET: +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + case ZEND_JMP_SET_VAR: +#endif + START_BLOCK_OP(ZEND_OP2(opline).opline_num); + START_BLOCK_OP(opno + 1); + break; + case ZEND_FE_FETCH: + START_BLOCK_OP(ZEND_OP2(opline).opline_num); + START_BLOCK_OP(opno + 2); + break; + } + opno++; + opline++; + } + + /* first find block start points */ + if (op_array->last_try_catch) { + int i; + cfg->try = ecalloc(op_array->last_try_catch, sizeof(zend_code_block *)); + cfg->catch = ecalloc(op_array->last_try_catch, sizeof(zend_code_block *)); + for (i = 0; i< op_array->last_try_catch; i++) { + cfg->try[i] = &blocks[op_array->try_catch_array[i].try_op]; + cfg->catch[i] = &blocks[op_array->try_catch_array[i].catch_op]; + START_BLOCK_OP(op_array->try_catch_array[i].try_op); + START_BLOCK_OP(op_array->try_catch_array[i].catch_op); + blocks[op_array->try_catch_array[i].try_op].protected = 1; + } + } + /* Currently, we don't optimize op_arrays with BRK/CONT/GOTO opcodes, + * but, we have to keep brk_cont_array to avoid memory leaks during + * exception handling */ + if (op_array->last_brk_cont) { + int i, j; + + j = 0; + for (i = 0; i< op_array->last_brk_cont; i++) { + if (op_array->brk_cont_array[i].start >= 0 && + (op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_SWITCH_FREE)) { + int parent = op_array->brk_cont_array[i].parent; + + while (parent >= 0 && + op_array->brk_cont_array[parent].start < 0 && + op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_FREE && + op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_SWITCH_FREE) { + parent = op_array->brk_cont_array[parent].parent; + } + op_array->brk_cont_array[i].parent = parent; + j++; + } + } + if (j) { + cfg->loop_start = ecalloc(op_array->last_brk_cont, sizeof(zend_code_block *)); + cfg->loop_cont = ecalloc(op_array->last_brk_cont, sizeof(zend_code_block *)); + cfg->loop_brk = ecalloc(op_array->last_brk_cont, sizeof(zend_code_block *)); + j = 0; + for (i = 0; i< op_array->last_brk_cont; i++) { + if (op_array->brk_cont_array[i].start >= 0 && + (op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_SWITCH_FREE)) { + if (i != j) { + op_array->brk_cont_array[j] = op_array->brk_cont_array[i]; + } + cfg->loop_start[j] = &blocks[op_array->brk_cont_array[j].start]; + cfg->loop_cont[j] = &blocks[op_array->brk_cont_array[j].cont]; + cfg->loop_brk[j] = &blocks[op_array->brk_cont_array[j].brk]; + START_BLOCK_OP(op_array->brk_cont_array[j].start); + START_BLOCK_OP(op_array->brk_cont_array[j].cont); + START_BLOCK_OP(op_array->brk_cont_array[j].brk); + blocks[op_array->brk_cont_array[j].start].protected = 1; + blocks[op_array->brk_cont_array[j].brk].protected = 1; + j++; + } + } + op_array->last_brk_cont = j; + } else { + efree(op_array->brk_cont_array); + op_array->brk_cont_array = NULL; + op_array->last_brk_cont = 0; + } + } + + /* Build CFG (Control Flow Graph) */ + cur_block = blocks; + for (opno = 1; opno < op_array->last; opno++) { + if (blocks[opno].start_opline) { + /* found new block start */ + cur_block->len = blocks[opno].start_opline - cur_block->start_opline; + cur_block->next = &blocks[opno]; + /* what is the last OP of previous block? */ + opline = blocks[opno].start_opline - 1; + if (opline->opcode == ZEND_OP_DATA) { + opline--; + } + switch((unsigned)opline->opcode) { + case ZEND_RETURN: +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + case ZEND_RETURN_BY_REF: +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + case ZEND_GENERATOR_RETURN: +#endif + case ZEND_EXIT: + case ZEND_THROW: + break; +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + case ZEND_FAST_CALL: + if (opline->extended_value) { + cur_block->op2_to = &blocks[ZEND_OP2(opline).opline_num]; + } + cur_block->op1_to = &blocks[ZEND_OP1(opline).opline_num]; + break; + case ZEND_FAST_RET: + if (opline->extended_value) { + cur_block->op2_to = &blocks[ZEND_OP2(opline).opline_num]; + } + break; +#endif + case ZEND_JMP: + cur_block->op1_to = &blocks[ZEND_OP1(opline).opline_num]; + break; + case ZEND_JMPZNZ: + cur_block->op2_to = &blocks[ZEND_OP2(opline).opline_num]; + cur_block->ext_to = &blocks[opline->extended_value]; + break; + case ZEND_CATCH: + cur_block->ext_to = &blocks[opline->extended_value]; + cur_block->follow_to = &blocks[opno]; + break; + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET: + case ZEND_NEW: +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + case ZEND_JMP_SET: +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + case ZEND_JMP_SET_VAR: +#endif + case ZEND_FE_FETCH: + cur_block->op2_to = &blocks[ZEND_OP2(opline).opline_num]; + /* break missing intentionally */ + default: + /* next block follows this */ + cur_block->follow_to = &blocks[opno]; + break; + } + print_block(cur_block, op_array->opcodes, ""); + cur_block = cur_block->next; + } + } + cur_block->len = end - cur_block->start_opline; + cur_block->next = &blocks[op_array->last + 1]; + print_block(cur_block, op_array->opcodes, ""); + + return 1; +} + +/* CFG back references management */ + +#define ADD_SOURCE(fromb, tob) { \ + zend_block_source *__s = tob->sources; \ + while (__s && __s->from != fromb) __s = __s->next; \ + if (__s == NULL) { \ + zend_block_source *__t = emalloc(sizeof(zend_block_source)); \ + __t->next = tob->sources; \ + tob->sources = __t; \ + __t->from = fromb; \ + } \ +} + +#define DEL_SOURCE(cs) { \ + zend_block_source *__ns = (*cs)->next; \ + efree(*cs); \ + *cs = __ns; \ +} + + +static inline void replace_source(zend_block_source *list, zend_code_block *old, zend_code_block *new) +{ + /* replace all references to 'old' in 'list' with 'new' */ + zend_block_source **cs; + int found = 0; + + for (cs = &list; *cs; cs = &((*cs)->next)) { + if ((*cs)->from == new) { + if (found) { + DEL_SOURCE(cs); + } else { + found = 1; + } + } + + if ((*cs)->from == old) { + if (found) { + DEL_SOURCE(cs); + } else { + (*cs)->from = new; + found = 1; + } + } + } +} + +static inline void del_source(zend_code_block *from, zend_code_block *to) +{ + /* delete source 'from' from 'to'-s sources list */ + zend_block_source **cs = &to->sources; + + if (to->sources == NULL) { + to->access = 0; + return; + } + + while (*cs) { + if ((*cs)->from == from) { + DEL_SOURCE(cs); + break; + } + cs = &((*cs)->next); + } + + if (to->sources == NULL) { + /* 'to' has no more sources - it's unused, will be stripped */ + to->access = 0; + return; + } + + if (to->sources->next == NULL) { + /* source to only one block */ + zend_code_block *from_block = to->sources->from; + + if (from_block->access && from_block->follow_to == to && + from_block->op1_to == NULL && + from_block->op2_to == NULL && + from_block->ext_to == NULL) { + /* this block follows it's only predecessor - we can join them */ + zend_op *new_to = from_block->start_opline + from_block->len; + if (new_to != to->start_opline) { + /* move block to new location */ + memmove(new_to, to->start_opline, sizeof(zend_op)*to->len); + } + /* join blocks' lengths */ + from_block->len += to->len; + /* move 'to'`s references to 'from' */ + to->start_opline = NULL; + to->access = 0; + efree(to->sources); + to->sources = NULL; + from_block->follow_to = to->follow_to; + if (to->op1_to) { + from_block->op1_to = to->op1_to; + replace_source(to->op1_to->sources, to, from_block); + } + if (to->op2_to) { + from_block->op2_to = to->op2_to; + replace_source(to->op2_to->sources, to, from_block); + } + if (to->ext_to) { + from_block->ext_to = to->ext_to; + replace_source(to->ext_to->sources, to, from_block); + } + if (to->follow_to) { + replace_source(to->follow_to->sources, to, from_block); + } + /* remove "to" from list */ + } + } +} + +static void delete_code_block(zend_code_block *block) +{ + if (block->protected) { + return; + } + if (block->follow_to) { + zend_block_source *bs = block->sources; + while (bs) { + zend_code_block *from_block = bs->from; + zend_code_block *to = block->follow_to; + if (from_block->op1_to == block) { + from_block->op1_to = to; + ADD_SOURCE(from_block, to); + } + if (from_block->op2_to == block) { + from_block->op2_to = to; + ADD_SOURCE(from_block, to); + } + if (from_block->ext_to == block) { + from_block->ext_to = to; + ADD_SOURCE(from_block, to); + } + if (from_block->follow_to == block) { + from_block->follow_to = to; + ADD_SOURCE(from_block, to); + } + bs = bs->next; + } + } + block->access = 0; +} + +static void zend_access_path(zend_code_block *block) +{ + if (block->access) { + return; + } + + block->access = 1; + if (block->op1_to) { + zend_access_path(block->op1_to); + ADD_SOURCE(block, block->op1_to); + } + if (block->op2_to) { + zend_access_path(block->op2_to); + ADD_SOURCE(block, block->op2_to); + } + if (block->ext_to) { + zend_access_path(block->ext_to); + ADD_SOURCE(block, block->ext_to); + } + if (block->follow_to) { + zend_access_path(block->follow_to); + ADD_SOURCE(block, block->follow_to); + } +} + +/* Traverse CFG, mark reachable basic blocks and build back references */ +static void zend_rebuild_access_path(zend_cfg *cfg, zend_op_array *op_array, int find_start) +{ + zend_code_block *blocks = cfg->blocks; + zend_code_block *start = find_start? NULL : blocks; + zend_code_block *b; + + /* Mark all blocks as unaccessible and destroy back references */ + b = blocks; + while (b != NULL) { + zend_block_source *cs; + if (!start && b->access) { + start = b; + } + b->access = 0; + cs = b->sources; + while (cs) { + zend_block_source *n = cs->next; + efree(cs); + cs = n; + } + b->sources = NULL; + b = b->next; + } + + /* Walk thorough all paths */ + zend_access_path(start); + + /* Add brk/cont paths */ + if (op_array->last_brk_cont) { + int i; + for (i=0; i< op_array->last_brk_cont; i++) { + zend_access_path(cfg->loop_start[i]); + zend_access_path(cfg->loop_cont[i]); + zend_access_path(cfg->loop_brk[i]); + } + } + + /* Add exception paths */ + if (op_array->last_try_catch) { + int i; + for (i=0; i< op_array->last_try_catch; i++) { + if (!cfg->catch[i]->access) { + zend_access_path(cfg->catch[i]); + } + } + } +} + +/* Data dependencies macros */ + +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + +# define VAR_NUM_EX(op) ((op ## _type & (IS_TMP_VAR|IS_VAR))?VAR_NUM((op).var):(op).var) + +# define VAR_SOURCE(op) Tsource[VAR_NUM(op.var)] +# define SET_VAR_SOURCE(opline) Tsource[VAR_NUM(opline->result.var)] = opline + +# define VAR_UNSET(op) do { if (op ## _type & (IS_TMP_VAR|IS_VAR)) {VAR_SOURCE(op) = NULL;}} while (0) + +#else + +# define VAR_NUM_EX(op) ((op).op_type == IS_TMP_VAR || (op).op_type == IS_VAR? VAR_NUM((op).u.var) : (op).u.var) + +# define VAR_SOURCE(op) Tsource[VAR_NUM(op.u.var)] +# define SET_VAR_SOURCE(opline) Tsource[VAR_NUM(ZEND_RESULT(opline).var)] = opline + +# define VAR_UNSET(op) do { if ((op).op_type == IS_TMP_VAR || (op).op_type == IS_VAR) {VAR_SOURCE(op) = NULL;}} while (0) + +#endif + +#define convert_to_string_safe(v) \ + if (Z_TYPE_P((v)) == IS_NULL) { \ + ZVAL_STRINGL((v), "", 0, 1); \ + } else { \ + convert_to_string((v)); \ + } + +static void strip_nop(zend_code_block *block) +{ + zend_op *opline = block->start_opline; + zend_op *end, *new_end; + + /* remove leading NOPs */ + while (block->len > 0 && block->start_opline->opcode == ZEND_NOP) { + if (block->len == 1) { + /* this block is all NOPs, join with following block */ + if (block->follow_to) { + delete_code_block(block); + } + return; + } + block->start_opline++; + block->start_opline_no++; + block->len--; + } + + /* strip the inside NOPs */ + opline = new_end = block->start_opline; + end = opline + block->len; + + while (opline < end) { + zend_op *src; + int len = 0; + + while (opline < end && opline->opcode == ZEND_NOP) { + opline++; + } + src = opline; + + while (opline < end && opline->opcode != ZEND_NOP) { + opline++; + } + len = opline - src; + + /* move up non-NOP opcodes */ + memmove(new_end, src, len*sizeof(zend_op)); + + new_end += len; + } + block->len = new_end - block->start_opline; +} + +static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, char *used_ext TSRMLS_DC) +{ + zend_op *opline = block->start_opline; + zend_op *end, *last_op = NULL; + zend_op **Tsource = NULL; + + print_block(block, op_array->opcodes, "Opt "); + + /* remove leading NOPs */ + while (block->len > 0 && block->start_opline->opcode == ZEND_NOP) { + if (block->len == 1) { + /* this block is all NOPs, join with following block */ + if (block->follow_to) { + delete_code_block(block); + } + return; + } + block->start_opline++; + block->start_opline_no++; + block->len--; + } + + /* we track data dependencies only insight a single basic block */ + if (op_array->T) { + Tsource = ecalloc(op_array->T, sizeof(zend_op *)); + } + opline = block->start_opline; + end = opline + block->len; + while ((op_array->T) && (opline < end)) { + /* strip X = QM_ASSIGN(const) */ + if (ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_QM_ASSIGN && + ZEND_OP1_TYPE(VAR_SOURCE(opline->op1)) == IS_CONST && + opline->opcode != ZEND_CASE && /* CASE _always_ expects variable */ + opline->opcode != ZEND_FETCH_DIM_TMP_VAR && /* in 5.1, FETCH_DIM_TMP_VAR expects T */ + opline->opcode != ZEND_FE_RESET && + opline->opcode != ZEND_FREE + ) { + zend_op *src = VAR_SOURCE(opline->op1); + zval c = ZEND_OP1_LITERAL(src); + VAR_UNSET(opline->op1); + zval_copy_ctor(&c); + update_op1_const(op_array, opline, &c TSRMLS_CC); + literal_dtor(&ZEND_OP1_LITERAL(src)); + MAKE_NOP(src); + } + + /* T = QM_ASSIGN(C), F(T) => NOP, F(C) */ + if (ZEND_OP2_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op2) && + VAR_SOURCE(opline->op2)->opcode == ZEND_QM_ASSIGN && + ZEND_OP1_TYPE(VAR_SOURCE(opline->op2)) == IS_CONST) { + zend_op *src = VAR_SOURCE(opline->op2); + zval c = ZEND_OP1_LITERAL(src); + VAR_UNSET(opline->op2); + zval_copy_ctor(&c); + update_op2_const(op_array, opline, &c TSRMLS_CC); + literal_dtor(&ZEND_OP1_LITERAL(src)); + MAKE_NOP(src); + } + + /* T = PRINT(X), F(T) => ECHO(X), F(1) */ + if (ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_PRINT && + opline->opcode != ZEND_CASE && opline->opcode != ZEND_FREE) { + ZEND_OP1_TYPE(opline) = IS_CONST; + LITERAL_LONG(opline->op1, 1); + } + + if (ZEND_OP2_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op2) && + VAR_SOURCE(opline->op2)->opcode == ZEND_PRINT) { + ZEND_OP2_TYPE(opline) = IS_CONST; + LITERAL_LONG(opline->op2, 1); + } + + /* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */ + if ((opline->opcode == ZEND_ECHO || opline->opcode == ZEND_PRINT) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_CAST && + VAR_SOURCE(opline->op1)->extended_value == IS_STRING) { + zend_op *src = VAR_SOURCE(opline->op1); + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + } + + /* T = PRINT(X), FREE(T) => ECHO(X) */ + if (opline->opcode == ZEND_FREE && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1)) { + zend_op *src = VAR_SOURCE(opline->op1); + if (src->opcode == ZEND_PRINT) { + src->opcode = ZEND_ECHO; + ZEND_RESULT_TYPE(src) = IS_UNUSED; + MAKE_NOP(opline); + } + } + + /* T = BOOL(X), FREE(T) => NOP */ + if (opline->opcode == ZEND_FREE && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1)) { + zend_op *src = VAR_SOURCE(opline->op1); + if (src->opcode == ZEND_BOOL) { + if (ZEND_OP1_TYPE(src) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(src)); + } + MAKE_NOP(src); + MAKE_NOP(opline); + } + } + +#if 0 + /* pre-evaluate functions: + constant(x) + defined(x) + function_exists(x) + extension_loaded(x) + BAD: interacts badly with Accelerator + */ + if((ZEND_OP1_TYPE(opline) & 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 && + ZEND_OP1_TYPE(sv) == 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("defined")-1 && zend_binary_strcasecmp(fname, flen, "defined", sizeof("defined")-1) == 0) { + zval c; + if(zend_get_persistent_constant(Z_STRVAL_P(arg), Z_STRLEN_P(arg), &c, 0 TSRMLS_CC ELS_CC) != 0) { + literal_dtor(arg); + MAKE_NOP(sv); + MAKE_NOP(fcall); + LITERAL_BOOL(opline->op1, 1); + ZEND_OP1_TYPE(opline) = IS_CONST; + } + } else 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(zend_hash_find(EG(function_table), Z_STRVAL_P(arg), Z_STRLEN_P(arg)+1, (void **)&function) == SUCCESS) { + literal_dtor(arg); + MAKE_NOP(sv); + MAKE_NOP(fcall); + LITERAL_BOOL(opline->op1, 1); + ZEND_OP1_TYPE(opline) = IS_CONST; + } + } else if(flen == sizeof("constant")-1 && zend_binary_strcasecmp(fname, flen, "constant", sizeof("constant")-1) == 0) { + zval c; + if(zend_get_persistent_constant(Z_STRVAL_P(arg), Z_STRLEN_P(arg), &c, 1 TSRMLS_CC ELS_CC) != 0) { + literal_dtor(arg); + MAKE_NOP(sv); + MAKE_NOP(fcall); + ZEND_OP1_LITERAL(opline) = zend_optimizer_add_literal(op_array, &c TSRMLS_CC); + /* no copy ctor - get already copied it */ + ZEND_OP1_TYPE(opline) = 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_STRVAL_P(arg), Z_STRLEN_P(arg)+1)) { + literal_dtor(arg); + MAKE_NOP(sv); + MAKE_NOP(fcall); + LITERAL_BOOL(opline->op1, 1); + ZEND_OP1_TYPE(opline) = IS_CONST; + } + } + } + } +#endif + + /* 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) + */ + if (opline->opcode == ZEND_IS_EQUAL || + opline->opcode == ZEND_IS_NOT_EQUAL) { + if (ZEND_OP1_TYPE(opline) == IS_CONST && + Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_BOOL) { + opline->opcode = + ((opline->opcode == ZEND_IS_EQUAL) == Z_LVAL(ZEND_OP1_LITERAL(opline)))? + ZEND_BOOL : ZEND_BOOL_NOT; + COPY_NODE(opline->op1, opline->op2); + SET_UNUSED(opline->op2); + } else if (ZEND_OP2_TYPE(opline) == IS_CONST && + Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_BOOL) { + opline->opcode = + ((opline->opcode == ZEND_IS_EQUAL) == Z_LVAL(ZEND_OP2_LITERAL(opline)))? + ZEND_BOOL : ZEND_BOOL_NOT; + SET_UNUSED(opline->op2); + } + } + + if ((opline->opcode == ZEND_BOOL || + opline->opcode == ZEND_BOOL_NOT || + opline->opcode == ZEND_JMPZ || + opline->opcode == ZEND_JMPNZ || + opline->opcode == ZEND_JMPZNZ) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) != NULL && + !used_ext[VAR_NUM(ZEND_OP1(opline).var)] && + VAR_SOURCE(opline->op1)->opcode == ZEND_BOOL_NOT) { + /* T = BOOL_NOT(X) + JMPZ(T) -> NOP, JMPNZ(X) */ + zend_op *src = VAR_SOURCE(opline->op1); + + COPY_NODE(opline->op1, src->op1); + + switch (opline->opcode) { + case ZEND_BOOL: + /* T = BOOL_NOT(X) + BOOL(T) -> NOP, BOOL_NOT(X) */ + opline->opcode = ZEND_BOOL_NOT; + break; + case ZEND_BOOL_NOT: + /* T = BOOL_NOT(X) + BOOL_BOOL(T) -> NOP, BOOL(X) */ + opline->opcode = ZEND_BOOL; + break; + case ZEND_JMPZ: + /* T = BOOL_NOT(X) + JMPZ(T,L) -> NOP, JMPNZ(X,L) */ + opline->opcode = ZEND_JMPNZ; + break; + case ZEND_JMPNZ: + /* T = BOOL_NOT(X) + JMPNZ(T,L) -> NOP, JMPZ(X,L) */ + opline->opcode = ZEND_JMPZ; + break; + case ZEND_JMPZNZ: + { + /* T = BOOL_NOT(X) + JMPZNZ(T,L1,L2) -> NOP, JMPZNZ(X,L2,L1) */ + int op_t; + zend_code_block *op_b; + + op_t = opline->extended_value; + opline->extended_value = ZEND_OP2(opline).opline_num; + ZEND_OP2(opline).opline_num = op_t; + + op_b = block->ext_to; + block->ext_to = block->op2_to; + block->op2_to = op_b; + } + break; + } + + VAR_UNSET(opline->op1); + MAKE_NOP(src); + continue; + } else +#if 0 + /* T = BOOL_NOT(X) + T = JMPZ_EX(T, X) -> T = BOOL_NOT(X), JMPNZ(X) */ + if(0 && (opline->opcode == ZEND_JMPZ_EX || + opline->opcode == ZEND_JMPNZ_EX) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) != NULL && + VAR_SOURCE(opline->op1)->opcode == ZEND_BOOL_NOT && + ZEND_OP1(opline).var == ZEND_RESULT(opline).var + ) { + zend_op *src = VAR_SOURCE(opline->op1); + if(opline->opcode == ZEND_JMPZ_EX) { + opline->opcode = ZEND_JMPNZ; + } else { + opline->opcode = ZEND_JMPZ; + } + COPY_NODE(opline->op1, src->op1); + SET_UNUSED(opline->result); + continue; + } else +#endif + /* T = BOOL(X) + JMPZ(T) -> NOP, JMPZ(X) */ + if ((opline->opcode == ZEND_BOOL || + opline->opcode == ZEND_BOOL_NOT || + opline->opcode == ZEND_JMPZ || + opline->opcode == ZEND_JMPZ_EX || + opline->opcode == ZEND_JMPNZ_EX || + opline->opcode == ZEND_JMPNZ || + opline->opcode == ZEND_JMPZNZ) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) != NULL && + (!used_ext[VAR_NUM(ZEND_OP1(opline).var)] || + (ZEND_RESULT_TYPE(opline) == IS_TMP_VAR && + ZEND_RESULT(opline).var == ZEND_OP1(opline).var)) && + (VAR_SOURCE(opline->op1)->opcode == ZEND_BOOL || + VAR_SOURCE(opline->op1)->opcode == ZEND_QM_ASSIGN)) { + zend_op *src = VAR_SOURCE(opline->op1); + COPY_NODE(opline->op1, src->op1); + + VAR_UNSET(opline->op1); + MAKE_NOP(src); + continue; + } else if (last_op && opline->opcode == ZEND_ECHO && + last_op->opcode == ZEND_ECHO && + ZEND_OP1_TYPE(opline) == IS_CONST && + Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_DOUBLE && + ZEND_OP1_TYPE(last_op) == IS_CONST && + Z_TYPE(ZEND_OP1_LITERAL(last_op)) != IS_DOUBLE) { + /* compress consecutive ECHO's. + * Float to string conversion may be affected by current + * locale setting. + */ + int l; + + if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) { + convert_to_string_safe(&ZEND_OP1_LITERAL(opline)); + } + if (Z_TYPE(ZEND_OP1_LITERAL(last_op)) != IS_STRING) { + convert_to_string_safe(&ZEND_OP1_LITERAL(last_op)); + } + l = Z_STRLEN(ZEND_OP1_LITERAL(opline)) + Z_STRLEN(ZEND_OP1_LITERAL(last_op)); + if (IS_INTERNED(Z_STRVAL(ZEND_OP1_LITERAL(last_op)))) { + char *tmp = emalloc(l + 1); + memcpy(tmp, Z_STRVAL(ZEND_OP1_LITERAL(last_op)), l + 1); + Z_STRVAL(ZEND_OP1_LITERAL(last_op)) = tmp; + } else { + Z_STRVAL(ZEND_OP1_LITERAL(last_op)) = erealloc(Z_STRVAL(ZEND_OP1_LITERAL(last_op)), l + 1); + } + memcpy(Z_STRVAL(ZEND_OP1_LITERAL(last_op))+Z_STRLEN(ZEND_OP1_LITERAL(last_op)), Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline))); + Z_STRVAL(ZEND_OP1_LITERAL(last_op))[l] = '\0'; + zval_dtor(&ZEND_OP1_LITERAL(opline)); +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + Z_STRVAL(ZEND_OP1_LITERAL(opline)) = (char*)zend_new_interned_string(Z_STRVAL(ZEND_OP1_LITERAL(last_op)), l + 1, 1 TSRMLS_CC); + Z_TYPE(ZEND_OP1_LITERAL(last_op)) = IS_NULL; +#else + Z_STRVAL(ZEND_OP1_LITERAL(opline)) = Z_STRVAL(ZEND_OP1_LITERAL(last_op)); +#endif + Z_STRLEN(ZEND_OP1_LITERAL(opline)) = l; + MAKE_NOP(last_op); + } else if (opline->opcode == ZEND_CONCAT && + ZEND_OP2_TYPE(opline) == IS_CONST && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + (VAR_SOURCE(opline->op1)->opcode == ZEND_CONCAT || + VAR_SOURCE(opline->op1)->opcode == ZEND_ADD_STRING) && + ZEND_OP2_TYPE(VAR_SOURCE(opline->op1)) == IS_CONST && + ZEND_RESULT(VAR_SOURCE(opline->op1)).var == ZEND_OP1(opline).var) { + /* compress consecutive CONCATs */ + zend_op *src = VAR_SOURCE(opline->op1); + int l; + + if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) { + convert_to_string_safe(&ZEND_OP2_LITERAL(opline)); + } + if (Z_TYPE(ZEND_OP2_LITERAL(src)) != IS_STRING) { + convert_to_string_safe(&ZEND_OP2_LITERAL(src)); + } + + VAR_UNSET(opline->op1); + if (ZEND_OP1_TYPE(src) == IS_UNUSED) { + /* 5.3 may use IS_UNUSED as first argument to ZEND_ADD_... */ + opline->opcode = ZEND_ADD_STRING; + } + COPY_NODE(opline->op1, src->op1); + l = Z_STRLEN(ZEND_OP2_LITERAL(opline)) + Z_STRLEN(ZEND_OP2_LITERAL(src)); + if (IS_INTERNED(Z_STRVAL(ZEND_OP2_LITERAL(src)))) { + char *tmp = emalloc(l + 1); + memcpy(tmp, Z_STRVAL(ZEND_OP2_LITERAL(src)), l + 1); + Z_STRVAL(ZEND_OP2_LITERAL(src)) = tmp; + } else { + Z_STRVAL(ZEND_OP2_LITERAL(src)) = erealloc(Z_STRVAL(ZEND_OP2_LITERAL(src)), l + 1); + } + memcpy(Z_STRVAL(ZEND_OP2_LITERAL(src))+Z_STRLEN(ZEND_OP2_LITERAL(src)), Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline))); + Z_STRVAL(ZEND_OP2_LITERAL(src))[l] = '\0'; + if (!IS_INTERNED(Z_STRVAL(ZEND_OP2_LITERAL(opline)))) { + efree(Z_STRVAL(ZEND_OP2_LITERAL(opline))); + } +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + Z_STRVAL(ZEND_OP2_LITERAL(opline)) = (char*)zend_new_interned_string(Z_STRVAL(ZEND_OP2_LITERAL(src)), l + 1, 1 TSRMLS_CC); + Z_TYPE(ZEND_OP2_LITERAL(src)) = IS_NULL; +#else + Z_STRVAL(ZEND_OP2_LITERAL(opline)) = Z_STRVAL(ZEND_OP2_LITERAL(src)); +#endif + Z_STRLEN(ZEND_OP2_LITERAL(opline)) = l; + MAKE_NOP(src); + } else if ((opline->opcode == ZEND_ADD_STRING || opline->opcode == ZEND_ADD_VAR) && ZEND_OP1_TYPE(opline) == IS_CONST) { + /* convert ADD_STRING(C1, C2) to CONCAT(C1, C2) */ + opline->opcode = ZEND_CONCAT; + continue; + } else if (opline->opcode == ZEND_ADD_CHAR && ZEND_OP1_TYPE(opline) == IS_CONST && ZEND_OP2_TYPE(opline) == IS_CONST) { + /* convert ADD_CHAR(C1, C2) to CONCAT(C1, C2) */ + char c = (char)Z_LVAL(ZEND_OP2_LITERAL(opline)); + ZVAL_STRINGL(&ZEND_OP2_LITERAL(opline), &c, 1, 1); + opline->opcode = ZEND_CONCAT; + continue; + } else if ((opline->opcode == ZEND_ADD || + opline->opcode == ZEND_SUB || + opline->opcode == ZEND_MUL || + opline->opcode == ZEND_DIV || + opline->opcode == ZEND_MOD || + opline->opcode == ZEND_SL || + opline->opcode == ZEND_SR || + opline->opcode == ZEND_CONCAT || + opline->opcode == ZEND_IS_EQUAL || + opline->opcode == ZEND_IS_NOT_EQUAL || + opline->opcode == ZEND_IS_SMALLER || + opline->opcode == ZEND_IS_SMALLER_OR_EQUAL || + opline->opcode == ZEND_IS_IDENTICAL || + opline->opcode == ZEND_IS_NOT_IDENTICAL || + opline->opcode == ZEND_BOOL_XOR || + opline->opcode == ZEND_BW_OR || + opline->opcode == ZEND_BW_AND || + opline->opcode == ZEND_BW_XOR) && + ZEND_OP1_TYPE(opline)==IS_CONST && + ZEND_OP2_TYPE(opline)==IS_CONST) { + /* evaluate constant expressions */ + int (*binary_op)(zval *result, zval *op1, zval *op2 TSRMLS_DC) = get_binary_op(opline->opcode); + zval result; + int er; + + if ((opline->opcode == ZEND_DIV || opline->opcode == ZEND_MOD) && + ((Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_LONG && + Z_LVAL(ZEND_OP2_LITERAL(opline)) == 0) || + (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_DOUBLE && + Z_DVAL(ZEND_OP2_LITERAL(opline)) == 0.0))) { + if (RESULT_USED(opline)) { + SET_VAR_SOURCE(opline); + } + opline++; + continue; + } + er = EG(error_reporting); + EG(error_reporting) = 0; + if (binary_op(&result, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline) TSRMLS_CC) == SUCCESS) { + PZ_SET_REFCOUNT_P(&result, 1); + PZ_UNSET_ISREF_P(&result); + + literal_dtor(&ZEND_OP1_LITERAL(opline)); + literal_dtor(&ZEND_OP2_LITERAL(opline)); + opline->opcode = ZEND_QM_ASSIGN; + SET_UNUSED(opline->op2); + update_op1_const(op_array, opline, &result TSRMLS_CC); + } + EG(error_reporting) = er; + } else if ((opline->opcode == ZEND_BOOL || + opline->opcode == ZEND_BOOL_NOT || + opline->opcode == ZEND_BW_NOT) && ZEND_OP1_TYPE(opline) == IS_CONST) { + /* evaluate constant unary ops */ + unary_op_type unary_op = get_unary_op(opline->opcode); + zval result; + + if (unary_op) { +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + unary_op(&result, &ZEND_OP1_LITERAL(opline)); +#else + unary_op(&result, &ZEND_OP1_LITERAL(opline) TSRMLS_CC); +#endif + literal_dtor(&ZEND_OP1_LITERAL(opline)); + } else { + /* BOOL */ + result = ZEND_OP1_LITERAL(opline); + convert_to_boolean(&result); + } + PZ_SET_REFCOUNT_P(&result, 1); + PZ_UNSET_ISREF_P(&result); + opline->opcode = ZEND_QM_ASSIGN; + update_op1_const(op_array, opline, &result TSRMLS_CC); + } else if ((opline->opcode == ZEND_RETURN || opline->opcode == ZEND_EXIT) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_QM_ASSIGN) { + /* T = QM_ASSIGN(X), RETURN(T) to RETURN(X) */ + zend_op *src = VAR_SOURCE(opline->op1); + VAR_UNSET(opline->op1); + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + } else if ((opline->opcode == ZEND_ADD_STRING || + opline->opcode == ZEND_ADD_CHAR) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_INIT_STRING) { + /* convert T = INIT_STRING(), T = ADD_STRING(T, X) to T = QM_ASSIGN(X) */ + /* CHECKME: Remove ZEND_ADD_VAR optimization, since some conversions - + namely, BOOL(false)->string - don't allocate memory but use empty_string + and ADD_CHAR fails */ + zend_op *src = VAR_SOURCE(opline->op1); + VAR_UNSET(opline->op1); + COPY_NODE(opline->op1, opline->op2); + if (opline->opcode == ZEND_ADD_CHAR) { + char c = (char)Z_LVAL(ZEND_OP2_LITERAL(opline)); + ZVAL_STRINGL(&ZEND_OP1_LITERAL(opline), &c, 1, 1); + } + SET_UNUSED(opline->op2); + MAKE_NOP(src); + opline->opcode = ZEND_QM_ASSIGN; + } else if ((opline->opcode == ZEND_ADD_STRING || + opline->opcode == ZEND_ADD_CHAR || + opline->opcode == ZEND_ADD_VAR || + opline->opcode == ZEND_CONCAT) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_CONCAT && + ZEND_OP2_TYPE(VAR_SOURCE(opline->op1)) == IS_CONST && + Z_TYPE(ZEND_OP2_LITERAL(VAR_SOURCE(opline->op1))) == IS_STRING && + Z_STRLEN(ZEND_OP2_LITERAL(VAR_SOURCE(opline->op1))) == 0) { + /* convert T = CONCAT(X,''), T = ADD_STRING(T, Y) to T = CONCAT(X,Y) */ + zend_op *src = VAR_SOURCE(opline->op1); + VAR_UNSET(opline->op1); + COPY_NODE(opline->op1, src->op1); + if (opline->opcode == ZEND_ADD_CHAR) { + char c = (char)Z_LVAL(ZEND_OP2_LITERAL(opline)); + ZVAL_STRINGL(&ZEND_OP2_LITERAL(opline), &c, 1, 1); + } + opline->opcode = ZEND_CONCAT; + literal_dtor(&ZEND_OP2_LITERAL(src)); /* will take care of empty_string too */ + MAKE_NOP(src); + } else if (opline->opcode == ZEND_ADD_VAR && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_INIT_STRING) { + /* convert T = INIT_STRING(), T = ADD_VAR(T, X) to T = CAST(STRING, X) */ + zend_op *src = VAR_SOURCE(opline->op1); + VAR_UNSET(opline->op1); + COPY_NODE(opline->op1, opline->op2); + SET_UNUSED(opline->op2); + MAKE_NOP(src); + opline->opcode = ZEND_CAST; + opline->extended_value = IS_STRING; + } else if ((opline->opcode == ZEND_ADD_STRING || + opline->opcode == ZEND_ADD_CHAR || + opline->opcode == ZEND_ADD_VAR || + opline->opcode == ZEND_CONCAT) && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + VAR_SOURCE(opline->op1)->opcode == ZEND_CAST && + VAR_SOURCE(opline->op1)->extended_value == IS_STRING) { + /* convert T1 = CAST(STRING, X), T2 = CONCAT(T1, Y) to T2 = CONCAT(X,Y) */ + zend_op *src = VAR_SOURCE(opline->op1); + VAR_UNSET(opline->op1); + COPY_NODE(opline->op1, src->op1); + if (opline->opcode == ZEND_ADD_CHAR) { + char c = (char)Z_LVAL(ZEND_OP2_LITERAL(opline)); + ZVAL_STRINGL(&ZEND_OP2_LITERAL(opline), &c, 1, 1); + } + opline->opcode = ZEND_CONCAT; + MAKE_NOP(src); + } else if (opline->opcode == ZEND_QM_ASSIGN && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + ZEND_RESULT_TYPE(opline) == IS_TMP_VAR && + ZEND_OP1(opline).var == ZEND_RESULT(opline).var) { + /* strip T = QM_ASSIGN(T) */ + MAKE_NOP(opline); + } else if (opline->opcode == ZEND_BOOL && + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + (VAR_SOURCE(opline->op1)->opcode == ZEND_IS_EQUAL || + VAR_SOURCE(opline->op1)->opcode == ZEND_IS_NOT_EQUAL || + VAR_SOURCE(opline->op1)->opcode == ZEND_IS_SMALLER || + VAR_SOURCE(opline->op1)->opcode == ZEND_IS_SMALLER_OR_EQUAL || + VAR_SOURCE(opline->op1)->opcode == ZEND_BOOL || + VAR_SOURCE(opline->op1)->opcode == ZEND_IS_IDENTICAL || + VAR_SOURCE(opline->op1)->opcode == ZEND_IS_NOT_IDENTICAL || + VAR_SOURCE(opline->op1)->opcode == ZEND_ISSET_ISEMPTY_VAR || + VAR_SOURCE(opline->op1)->opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ) && + !used_ext[VAR_NUM(ZEND_OP1(opline).var)]) { + /* T = IS_SMALLER(X, Y), T1 = BOOL(T) => T = IS_SMALLER(X, Y), T1 = QM_ASSIGN(T) */ + zend_op *src = VAR_SOURCE(opline->op1); + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + } + /* get variable source */ + if (RESULT_USED(opline)) { + SET_VAR_SOURCE(opline); + } + if (opline->opcode != ZEND_NOP) { + last_op = opline; + } + opline++; + } + + strip_nop(block); + + if (op_array->T) { + efree(Tsource); + } +} + +/* Rebuild plain (optimized) op_array from CFG */ +static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) +{ + zend_code_block *blocks = cfg->blocks; + zend_op *new_opcodes = emalloc(op_array->last * sizeof(zend_op)); + zend_op *opline = new_opcodes; + zend_code_block *cur_block = blocks; + + /* Copy code of reachable blocks into a single buffer */ + while (cur_block) { + if (cur_block->access) { + memcpy(opline, cur_block->start_opline, cur_block->len * sizeof(zend_op)); + cur_block->start_opline = opline; + opline += cur_block->len; + if ((opline - 1)->opcode == ZEND_JMP) { + zend_code_block *next; + next = cur_block->next; + while (next && !next->access) { + next = next->next; + } + if (next && next == cur_block->op1_to) { + /* JMP to the next block - strip it */ + cur_block->follow_to = cur_block->op1_to; + cur_block->op1_to = NULL; + MAKE_NOP((opline - 1)); + opline--; + cur_block->len--; + } + } + } else { + /* this block will not be used, delete all constants there */ + zend_op *_opl; + zend_op *end = cur_block->start_opline + cur_block->len; + for (_opl = cur_block->start_opline; _opl && _opl < end; _opl++) { + if (ZEND_OP1_TYPE(_opl) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(_opl)); + } + if (ZEND_OP2_TYPE(_opl) == IS_CONST) { + literal_dtor(&ZEND_OP2_LITERAL(_opl)); + } + } + } + cur_block = cur_block->next; + } + + if ((opline-1)->opcode == ZEND_THROW) { + /* if we finished with THROW, we need to add space between THROW and HANDLE to not confuse + zend_throw_internal */ + MAKE_NOP(opline); + opline->lineno = opline[-1].lineno; + opline++; + } +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + MAKE_NOP(opline); + opline->opcode = ZEND_HANDLE_EXCEPTION; + opline->lineno = opline[-1].lineno; + opline++; +#endif + + op_array->last = opline-new_opcodes; + + /* adjust exception jump targets */ + if (op_array->last_try_catch) { + int i, j; + for (i = 0, j = 0; i< op_array->last_try_catch; i++) { + if (cfg->try[i]->access) { + op_array->try_catch_array[j].try_op = cfg->try[i]->start_opline - new_opcodes; + op_array->try_catch_array[j].catch_op = cfg->catch[i]->start_opline - new_opcodes; + j++; + } + } + op_array->last_try_catch = j; + efree(cfg->try); + efree(cfg->catch); + } + + /* adjust loop jump targets */ + if (op_array->last_brk_cont) { + int i; + for (i = 0; i< op_array->last_brk_cont; i++) { + op_array->brk_cont_array[i].start = cfg->loop_start[i]->start_opline - new_opcodes; + op_array->brk_cont_array[i].cont = cfg->loop_cont[i]->start_opline - new_opcodes; + op_array->brk_cont_array[i].brk = cfg->loop_brk[i]->start_opline - new_opcodes; + } + efree(cfg->loop_start); + efree(cfg->loop_cont); + efree(cfg->loop_brk); + } + + /* adjust jump targets */ + for (cur_block = blocks; cur_block; cur_block = cur_block->next) { + if (!cur_block->access) { + continue; + } + opline = cur_block->start_opline + cur_block->len - 1; + if (opline->opcode == ZEND_OP_DATA) { + opline--; + } + if (cur_block->op1_to) { + ZEND_OP1(opline).opline_num = cur_block->op1_to->start_opline - new_opcodes; + } + if (cur_block->op2_to) { + ZEND_OP2(opline).opline_num = cur_block->op2_to->start_opline - new_opcodes; + } + if (cur_block->ext_to) { + opline->extended_value = cur_block->ext_to->start_opline - new_opcodes; + } + print_block(cur_block, new_opcodes, "Out "); + } + efree(op_array->opcodes); + op_array->opcodes = erealloc(new_opcodes, op_array->last * sizeof(zend_op)); + +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + /* adjust early binding list */ + if (op_array->early_binding != (zend_uint)-1) { + zend_uint *opline_num = &op_array->early_binding; + zend_op *end; + + opline = op_array->opcodes; + end = opline + op_array->last; + while (opline < end) { + if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) { + *opline_num = opline - op_array->opcodes; + opline_num = &ZEND_RESULT(opline).opline_num; + } + ++opline; + } + *opline_num = -1; + } +#endif +} + +static void zend_jmp_optimization(zend_code_block *block, zend_op_array *op_array, zend_code_block *blocks TSRMLS_DC) +{ + /* last_op is the last opcode of the current block */ + zend_op *last_op = (block->start_opline + block->len - 1); + + if (!block->len) { + return; + } + switch (last_op->opcode) { + case ZEND_JMP: + { + zend_op *target = block->op1_to->start_opline; + zend_code_block *next = block->next; + + while (next && !next->access) { + /* find used one */ + next = next->next; + } + + /* JMP(next) -> NOP */ + if (block->op1_to == next) { + block->follow_to = block->op1_to; + block->op1_to = NULL; + MAKE_NOP(last_op); + block->len--; + if (block->len == 0) { + /* this block is nothing but NOP now */ + delete_code_block(block); + } + break; + } + + if (((target->opcode == ZEND_JMP && + block->op1_to != block->op1_to->op1_to) || + target->opcode == ZEND_JMPZNZ) && + !block->op1_to->protected) { + /* JMP L, L: JMP L1 -> JMP L1 */ + /* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */ + *last_op = *target; +#if ZEND_EXTENSION_API_NO < PHP_5_4_X_API_NO + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + zval_copy_ctor(&ZEND_OP1_LITERAL(last_op)); + } +#else + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + zval zv = ZEND_OP1_LITERAL(last_op); + zval_copy_ctor(&zv); + last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv TSRMLS_CC); + } +#endif + del_source(block, block->op1_to); + if (block->op1_to->op2_to) { + block->op2_to = block->op1_to->op2_to; + ADD_SOURCE(block, block->op2_to); + } + if (block->op1_to->ext_to) { + block->ext_to = block->op1_to->ext_to; + ADD_SOURCE(block, block->ext_to); + } + if (block->op1_to->op1_to) { + block->op1_to = block->op1_to->op1_to; + ADD_SOURCE(block, block->op1_to); + } else { + block->op1_to = NULL; + } + } else if (target->opcode == ZEND_RETURN || +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + target->opcode == ZEND_RETURN_BY_REF || +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + target->opcode == ZEND_FAST_RET || +#endif + target->opcode == ZEND_EXIT) { + /* JMP L, L: RETURN to immediate RETURN */ + *last_op = *target; +#if ZEND_EXTENSION_API_NO < PHP_5_4_X_API_NO + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + zval_copy_ctor(&ZEND_OP1_LITERAL(last_op)); + } +#else + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + zval zv = ZEND_OP1_LITERAL(last_op); + zval_copy_ctor(&zv); + last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv TSRMLS_CC); + } +#endif + del_source(block, block->op1_to); + block->op1_to = NULL; +#if 0 + /* Temporarily disabled - see bug #0025274 */ + } else if (0&& block->op1_to != block && + block->op1_to != blocks && + op_array->last_try_catch == 0 && + target->opcode != ZEND_FREE && + target->opcode != ZEND_SWITCH_FREE) { + /* Block Reordering (saves one JMP on each "for" loop iteration) + * It is disabled for some cases (ZEND_FREE/ZEND_SWITCH_FREE) + * which may break register allocation. + */ + zend_bool can_reorder = 0; + zend_block_source *cs = block->op1_to->sources; + + /* the "target" block doesn't had any followed block */ + while(cs) { + if (cs->from->follow_to == block->op1_to) { + can_reorder = 0; + break; + } + cs = cs->next; + } + if (can_reorder) { + next = block->op1_to; + /* the "target" block is not followed by current "block" */ + while (next->follow_to != NULL) { + if (next->follow_to == block) { + can_reorder = 0; + break; + } + next = next->follow_to; + } + if (can_reorder) { + zend_code_block *prev = blocks; + + while (prev->next != block->op1_to) { + prev = prev->next; + } + prev->next = next->next; + next->next = block->next; + block->next = block->op1_to; + + block->follow_to = block->op1_to; + block->op1_to = NULL; + MAKE_NOP(last_op); + block->len--; + if(block->len == 0) { + /* this block is nothing but NOP now */ + delete_code_block(block); + } + break; + } + } +#endif + } + } + break; + + case ZEND_JMPZ: + case ZEND_JMPNZ: + /* constant conditional JMPs */ + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(last_op)); + if (last_op->opcode == ZEND_JMPZ) { + should_jmp = !should_jmp; + } + literal_dtor(&ZEND_OP1_LITERAL(last_op)); + ZEND_OP1_TYPE(last_op) = IS_UNUSED; + if (should_jmp) { + /* JMPNZ(true) -> JMP */ + last_op->opcode = ZEND_JMP; + COPY_NODE(last_op->op1, last_op->op2); + block->op1_to = block->op2_to; + del_source(block, block->follow_to); + block->op2_to = NULL; + block->follow_to = NULL; + } else { + /* JMPNZ(false) -> NOP */ + MAKE_NOP(last_op); + del_source(block, block->op2_to); + block->op2_to = NULL; + } + break; + } + + if (block->op2_to) { + zend_uchar same_type = ZEND_OP1_TYPE(last_op); + zend_uint same_var = VAR_NUM_EX(last_op->op1); + zend_op *target; + zend_op *target_end; + zend_code_block *target_block = block->op2_to;; + +next_target: + target = target_block->start_opline; + target_end = target_block->start_opline + target_block->len; + while (target < target_end && target->opcode == ZEND_NOP) { + target++; + } + + /* next block is only NOP's */ + if (target == target_end) { + target_block = target_block->follow_to; + goto next_target; + } else if (target->opcode == INV_COND(last_op->opcode) && + /* JMPZ(X, L), L: JMPNZ(X, L2) -> JMPZ(X, L+1) */ + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + same_type == ZEND_OP1_TYPE(target) && + same_var == VAR_NUM_EX(target->op1) && + target_block->follow_to && + !target_block->protected + ) { + del_source(block, block->op2_to); + block->op2_to = target_block->follow_to; + ADD_SOURCE(block, block->op2_to); + } else if (target->opcode == INV_COND_EX(last_op->opcode) && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + same_type == ZEND_OP1_TYPE(target) && + same_var == VAR_NUM_EX(target->op1) && + target_block->follow_to && + !target_block->protected) { + /* JMPZ(X, L), L: X = JMPNZ_EX(X, L2) -> JMPZ(X, L+1) */ + last_op->opcode += 3; + last_op->result = target->result; + del_source(block, block->op2_to); + block->op2_to = target_block->follow_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op2_to && + target->opcode == last_op->opcode && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + same_type == ZEND_OP1_TYPE(target) && + same_var == VAR_NUM_EX(target->op1) && + !target_block->protected) { + /* JMPZ(X, L), L: JMPZ(X, L2) -> JMPZ(X, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op2_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op1_to && + target->opcode == ZEND_JMP && + !target_block->protected) { + /* JMPZ(X, L), L: JMP(L2) -> JMPZ(X, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op1_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op2_to && + target_block->ext_to && + target->opcode == ZEND_JMPZNZ && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + same_type == ZEND_OP1_TYPE(target) && + same_var == VAR_NUM_EX(target->op1) && + !target_block->protected) { + /* JMPZ(X, L), L: JMPZNZ(X, L2, L3) -> JMPZ(X, L2) */ + del_source(block, block->op2_to); + if (last_op->opcode == ZEND_JMPZ) { + block->op2_to = target_block->op2_to; + } else { + block->op2_to = target_block->ext_to; + } + ADD_SOURCE(block, block->op2_to); + } + } + + if (block->follow_to && + (last_op->opcode == ZEND_JMPZ || last_op->opcode == ZEND_JMPNZ)) { + zend_op *target; + zend_op *target_end; + + while (1) { + target = block->follow_to->start_opline; + target_end = block->follow_to->start_opline + block->follow_to->len; + while (target < target_end && target->opcode == ZEND_NOP) { + target++; + } + + /* next block is only NOP's */ + if (target == target_end) { + del_source(block, block->follow_to); + block->follow_to = block->follow_to->follow_to; + ADD_SOURCE(block, block->follow_to); + } else { + break; + } + } + /* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */ + if (target->opcode == ZEND_JMP && + block->follow_to->op1_to && + !block->follow_to->protected) { + del_source(block, block->follow_to); + if (last_op->opcode == ZEND_JMPZ) { + block->ext_to = block->follow_to->op1_to; + ADD_SOURCE(block, block->ext_to); + } else { + block->ext_to = block->op2_to; + block->op2_to = block->follow_to->op1_to; + ADD_SOURCE(block, block->op2_to); + } + block->follow_to = NULL; + last_op->opcode = ZEND_JMPZNZ; + } + } + break; + + case ZEND_JMPNZ_EX: + case ZEND_JMPZ_EX: + /* constant conditional JMPs */ + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(last_op)); + if (last_op->opcode == ZEND_JMPZ_EX) { + should_jmp = !should_jmp; + } + if (!should_jmp) { + /* T = JMPZ_EX(true,L) -> T = QM_ASSIGN(true) + * T = JMPNZ_EX(false,L) -> T = QM_ASSIGN(false) + */ + last_op->opcode = ZEND_QM_ASSIGN; + SET_UNUSED(last_op->op2); + del_source(block, block->op2_to); + block->op2_to = NULL; + } + break; + } + + if (block->op2_to) { + zend_op *target, *target_end; + char *same_t=NULL; + zend_code_block *target_block; + int var_num = 0; + if (op_array->T >= (zend_uint)op_array->last_var) { + var_num = op_array->T; + } else { + var_num = op_array->last_var; + } + if (var_num <= 0) { + return; + } + same_t = ecalloc(var_num, sizeof(char)); + if (same_t == NULL) { + return; + } + same_t[VAR_NUM_EX(last_op->op1)] |= ZEND_OP1_TYPE(last_op); + same_t[VAR_NUM_EX(last_op->result)] |= ZEND_RESULT_TYPE(last_op); + target_block = block->op2_to; +next_target_ex: + target = target_block->start_opline; + target_end = target_block->start_opline + target_block->len; + while (target < target_end && target->opcode == ZEND_NOP) { + target++; + } + /* next block is only NOP's */ + if (target == target_end) { + target_block = target_block->follow_to; + goto next_target_ex; + } else if (target_block->op2_to && + target->opcode == last_op->opcode-3 && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + (same_t[VAR_NUM_EX(target->op1)] & ZEND_OP1_TYPE(target)) != 0 && + !target_block->protected) { + /* T = JMPZ_EX(X, L1), L1: JMPZ({X|T}, L2) -> T = JMPZ_EX(X, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op2_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op2_to && + target->opcode == INV_EX_COND(last_op->opcode) && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + (same_t[VAR_NUM_EX(target->op1)] & ZEND_OP1_TYPE(target)) != 0 && + !target_block->protected) { + /* T = JMPZ_EX(X, L1), L1: JMPNZ({X|T1}, L2) -> T = JMPZ_EX(X, L1+1) */ + del_source(block, block->op2_to); + block->op2_to = target_block->follow_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op2_to && + target->opcode == INV_EX_COND_EX(last_op->opcode) && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + (same_t[VAR_NUM_EX(target->op1)] & ZEND_OP1_TYPE(target)) != 0 && + (same_t[VAR_NUM_EX(target->result)] & ZEND_RESULT_TYPE(target)) != 0 && + !target_block->protected) { + /* T = JMPZ_EX(X, L1), L1: T = JMPNZ_EX(T, L2) -> T = JMPZ_EX(X, L1+1) */ + del_source(block, block->op2_to); + block->op2_to = target_block->follow_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op2_to && + target->opcode == last_op->opcode && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + (same_t[VAR_NUM_EX(target->op1)] & ZEND_OP1_TYPE(target)) != 0 && + (same_t[VAR_NUM_EX(target->result)] & ZEND_RESULT_TYPE(target)) != 0 && + !target_block->protected) { + /* T = JMPZ_EX(X, L1), L1: T = JMPZ({X|T}, L2) -> T = JMPZ_EX(X, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op2_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op1_to && + target->opcode == ZEND_JMP && + !target_block->protected) { + /* T = JMPZ_EX(X, L), L: JMP(L2) -> T = JMPZ(X, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op1_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op2_to && + target_block->ext_to && + target->opcode == ZEND_JMPZNZ && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + (same_t[VAR_NUM_EX(target->op1)] & ZEND_OP1_TYPE(target)) != 0 && + !target_block->protected) { + /* T = JMPZ_EX(X, L), L: JMPZNZ({X|T}, L2, L3) -> T = JMPZ_EX(X, L2) */ + del_source(block, block->op2_to); + if (last_op->opcode == ZEND_JMPZ_EX) { + block->op2_to = target_block->op2_to; + } else { + block->op2_to = target_block->ext_to; + } + ADD_SOURCE(block, block->op2_to); + } + if (same_t != NULL) { + efree(same_t); + } + } + break; + + case ZEND_JMPZNZ: { + zend_code_block *next = block->next; + + while (next && !next->access) { + /* find first accessed one */ + next = next->next; + } + + if (ZEND_OP1_TYPE(last_op) == IS_CONST) { + if (!zend_is_true(&ZEND_OP1_LITERAL(last_op))) { + /* JMPZNZ(false,L1,L2) -> JMP(L1) */ + zend_code_block *todel; + + literal_dtor(&ZEND_OP1_LITERAL(last_op)); + last_op->opcode = ZEND_JMP; + SET_UNUSED(last_op->op1); + SET_UNUSED(last_op->op2); + block->op1_to = block->op2_to; + todel = block->ext_to; + block->op2_to = NULL; + block->ext_to = NULL; + del_source(block, todel); + } else { + /* JMPZNZ(true,L1,L2) -> JMP(L2) */ + zend_code_block *todel; + + literal_dtor(&ZEND_OP1_LITERAL(last_op)); + last_op->opcode = ZEND_JMP; + SET_UNUSED(last_op->op1); + SET_UNUSED(last_op->op2); + block->op1_to = block->ext_to; + todel = block->op2_to; + block->op2_to = NULL; + block->ext_to = NULL; + del_source(block, todel); + } + } else if (block->op2_to == block->ext_to) { + /* both goto the same one - it's JMP */ + /* JMPZNZ(?,L,L) -> JMP(L) */ + last_op->opcode = ZEND_JMP; + SET_UNUSED(last_op->op1); + SET_UNUSED(last_op->op2); + block->op1_to = block->op2_to; + block->op2_to = NULL; + block->ext_to = NULL; + } else if (block->op2_to == next) { + /* jumping to next on Z - can follow to it and jump only on NZ */ + /* JMPZNZ(X,L1,L2) L1: -> JMPNZ(X,L2) */ + last_op->opcode = ZEND_JMPNZ; + block->op2_to = block->ext_to; + block->follow_to = next; + block->ext_to = NULL; + /* no need to add source - it's block->op2_to */ + } else if (block->ext_to == next) { + /* 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; + block->follow_to = next; + block->ext_to = NULL; + /* no need to add source - it's block->ext_to */ + } + + if (last_op->opcode == ZEND_JMPZNZ && block->op2_to) { + zend_uchar same_type = ZEND_OP1_TYPE(last_op); + zend_uchar same_var = VAR_NUM_EX(last_op->op1); + zend_op *target; + zend_op *target_end; + zend_code_block *target_block = block->op2_to; + +next_target_znz: + target = target_block->start_opline; + target_end = target_block->start_opline + target_block->len; + while (target < target_end && target->opcode == ZEND_NOP) { + target++; + } + /* next block is only NOP's */ + if (target == target_end) { + target_block = target_block->follow_to; + goto next_target_znz; + } else if (target_block->op2_to && + (target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + same_type == ZEND_OP1_TYPE(target) && + same_var == VAR_NUM_EX(target->op1) && + !target_block->protected) { + /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op2_to; + ADD_SOURCE(block, block->op2_to); + } else if (target->opcode == ZEND_JMPNZ && + (ZEND_OP1_TYPE(target) & (IS_TMP_VAR|IS_CV)) && + same_type == ZEND_OP1_TYPE(target) && + same_var == VAR_NUM_EX(target->op1) && + target_block->follow_to && + !target_block->protected) { + /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->follow_to; + ADD_SOURCE(block, block->op2_to); + } else if (target_block->op1_to && + target->opcode == ZEND_JMP && + !target_block->protected) { + /* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */ + del_source(block, block->op2_to); + block->op2_to = target_block->op1_to; + ADD_SOURCE(block, block->op2_to); + } + } + break; + } + } +} + +/* Global data dependencies */ + +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + +# define T_USAGE(op) do { \ + if ((op ## _type & (IS_VAR | IS_TMP_VAR)) && \ + !defined_here[VAR_NUM(op.var)] && !used_ext[VAR_NUM(op.var)]) { \ + used_ext[VAR_NUM(op.var)] = 1; \ + } \ + } while (0) + +# define NEVER_USED(op) ((op ## _type & (IS_VAR | IS_TMP_VAR)) && !usage[VAR_NUM(op.var)]) /* !used_ext[op.var] && */ +# define RES_NEVER_USED(opline) (opline->result_type == IS_UNUSED || NEVER_USED(opline->result)) + +#else + +# define T_USAGE(op) do { \ + if ((op.op_type == IS_VAR || op.op_type == IS_TMP_VAR) && \ + !defined_here[VAR_NUM(op.u.var)] && !used_ext[VAR_NUM(op.u.var)]) { \ + used_ext[VAR_NUM(op.u.var)] = 1; \ + } \ + } while (0) + +# define NEVER_USED(op) ((op.op_type == IS_VAR || op.op_type == IS_TMP_VAR) && !usage[VAR_NUM(op.u.var)]) /* !used_ext[op.u.var] && */ +# define RES_NEVER_USED(opline) (ZEND_RESULT_TYPE(opline) == IS_UNUSED || NEVER_USED(opline->result)) + +#endif + +/* Find a set of variables which are used outside of the block where they are + * defined. We won't apply some optimization patterns for sush variables. */ +static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *used_ext) +{ + zend_code_block *next_block = block->next; + char *usage; + char *defined_here; + + if (op_array->T == 0) { + /* shortcut - if no Ts, nothing to do */ + return; + } + + usage = ecalloc(op_array->T, 1); + defined_here = emalloc(op_array->T); + + while (next_block) { + zend_op *opline = next_block->start_opline; + zend_op *end = opline + next_block->len; + + if (!next_block->access) { + next_block = next_block->next; + continue; + } + memset(defined_here, 0, op_array->T); + + while (opline<end) { + T_USAGE(opline->op1); + T_USAGE(opline->op2); + + if (RESULT_USED(opline)) { + if (!defined_here[VAR_NUM(ZEND_RESULT(opline).var)] && !used_ext[VAR_NUM(ZEND_RESULT(opline).var)] && + (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT || + (opline->opcode == ZEND_OP_DATA && ZEND_RESULT_TYPE(opline) == IS_TMP_VAR) || + opline->opcode == ZEND_ADD_ARRAY_ELEMENT)) { + /* these opcodes use the result as argument */ + used_ext[VAR_NUM(ZEND_RESULT(opline).var)] = 1; + } + defined_here[VAR_NUM(ZEND_RESULT(opline).var)] = 1; + } + opline++; + } + next_block = next_block->next; + } + +#if DEBUG_BLOCKPASS + { + int i; + for (i = 0; i< op_array->T; i++) { + fprintf(stderr, "T%d: %c\n", i, used_ext[i] + '0'); + } + } +#endif + + while (block) { + zend_op *opline = block->start_opline + block->len - 1; + + if (!block->access) { + block = block->next; + continue; + } + + memcpy(usage, used_ext, op_array->T); + + while (opline >= block->start_opline) { + /* usage checks */ + if (RES_NEVER_USED(opline)) { + switch (opline->opcode) { + case ZEND_ASSIGN_ADD: + case ZEND_ASSIGN_SUB: + case ZEND_ASSIGN_MUL: + case ZEND_ASSIGN_DIV: + case ZEND_ASSIGN_MOD: + case ZEND_ASSIGN_SL: + case ZEND_ASSIGN_SR: + case ZEND_ASSIGN_CONCAT: + case ZEND_ASSIGN_BW_OR: + case ZEND_ASSIGN_BW_AND: + case ZEND_ASSIGN_BW_XOR: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_POST_INC: + case ZEND_POST_DEC: + case ZEND_ASSIGN: + case ZEND_ASSIGN_REF: + case ZEND_DO_FCALL: + case ZEND_DO_FCALL_BY_NAME: + if (ZEND_RESULT_TYPE(opline) == IS_VAR) { +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + ZEND_RESULT_TYPE(opline) |= EXT_TYPE_UNUSED; +#else + ZEND_RESULT(opline).EA.type |= EXT_TYPE_UNUSED; +#endif + } + break; + case ZEND_QM_ASSIGN: + case ZEND_BOOL: + case ZEND_BOOL_NOT: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(opline)); + } + MAKE_NOP(opline); + break; + case ZEND_PRINT: + opline->opcode = ZEND_ECHO; + ZEND_RESULT_TYPE(opline) = IS_UNUSED; + break; + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + opline->opcode -= 3; + SET_UNUSED(opline->result); + break; + } + } + + if (opline->opcode == ZEND_RECV || + opline->opcode == ZEND_RECV_INIT || + opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { + if (ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_TMP_VAR) { + usage[VAR_NUM(ZEND_RESULT(opline).var)] = 1; + } + } else { + if (RESULT_USED(opline)) { + usage[VAR_NUM(ZEND_RESULT(opline).var)] = 0; + } + } + + if (ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_TMP_VAR) { + usage[VAR_NUM(ZEND_OP1(opline).var)] = 1; + } + if (ZEND_OP2_TYPE(opline) == IS_VAR || ZEND_OP2_TYPE(opline) == IS_TMP_VAR) { + usage[VAR_NUM(ZEND_OP2(opline).var)] = 1; + } + + +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + if ((ZEND_RESULT_TYPE(opline) & IS_VAR) && + (ZEND_RESULT_TYPE(opline) & EXT_TYPE_UNUSED) && + usage[VAR_NUM(ZEND_RESULT(opline).var)]) { + ZEND_RESULT_TYPE(opline) &= ~EXT_TYPE_UNUSED; + } +#else + if (ZEND_RESULT_TYPE(opline) == IS_VAR && + usage[VAR_NUM(ZEND_RESULT(opline).var)] && + (ZEND_RESULT(opline).EA.type & EXT_TYPE_UNUSED) != 0) { + ZEND_RESULT(opline).EA.type &= ~EXT_TYPE_UNUSED; + } +#endif + + opline--; + } + block = block->next; + } /* end blocks */ + + efree(defined_here); + efree(usage); +} + +#define PASSES 3 + +static void zend_block_optimization(zend_op_array *op_array TSRMLS_DC) +{ + zend_cfg cfg; + zend_code_block *cur_block; + int pass; + char *usage; + +#if DEBUG_BLOCKPASS + fprintf(stderr, "File %s func %s\n", op_array->filename, op_array->function_name? op_array->function_name : "main"); + fflush(stderr); +#endif + +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->has_finally_block) { + return; + } +#endif + + /* Build CFG */ + if (!find_code_blocks(op_array, &cfg)) { + return; + } + + zend_rebuild_access_path(&cfg, op_array, 0); + /* full rebuild here to produce correct sources! */ + usage = emalloc(op_array->T); + for (pass = 0; pass < PASSES; pass++) { + /* Compute data dependencies */ + memset(usage, 0, op_array->T); + zend_t_usage(cfg.blocks, op_array, usage); + + /* optimize each basic block separately */ + for (cur_block = cfg.blocks; cur_block; cur_block = cur_block->next) { + if (!cur_block->access) { + continue; + } + zend_optimize_block(cur_block, op_array, usage TSRMLS_CC); + } + + /* Jump optimization for each block */ + for (cur_block = cfg.blocks; cur_block; cur_block = cur_block->next) { + if (!cur_block->access) { + continue; + } + zend_jmp_optimization(cur_block, op_array, cfg.blocks TSRMLS_CC); + } + + /* Eliminate unreachable basic blocks */ + zend_rebuild_access_path(&cfg, op_array, 1); + } + + memset(usage, 0, op_array->T); + zend_t_usage(cfg.blocks, op_array, usage); + assemble_code_blocks(&cfg, op_array); + efree(usage); + + /* Destroy CFG */ + for (cur_block = cfg.blocks; cur_block; cur_block = cur_block->next) { + zend_block_source *cs = cur_block->sources; + while (cs) { + zend_block_source *n = cs->next; + efree(cs); + cs = n; + } + } + efree(cfg.blocks); +} diff --git a/ext/opcache/Optimizer/nop_removal.c b/ext/opcache/Optimizer/nop_removal.c new file mode 100644 index 0000000000..b2fb667ed5 --- /dev/null +++ b/ext/opcache/Optimizer/nop_removal.c @@ -0,0 +1,126 @@ +/* pass 10: + * - remove NOPs + */ + +static void nop_removal(zend_op_array *op_array) +{ + zend_op *end, *opline; + zend_uint new_count, i, shift; + int j; + zend_uint *shiftlist; + ALLOCA_FLAG(use_heap); + + shiftlist = (zend_uint *)DO_ALLOCA(sizeof(zend_uint) * op_array->last); + i = new_count = shift = 0; + end = op_array->opcodes + op_array->last; + for (opline = op_array->opcodes; opline < end; opline++) { + +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + /* GOTO target is unresolved yet. We can't optimize. */ + if (opline->opcode == ZEND_GOTO && + Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_LONG) { + /* TODO: in general we can avoid this restriction */ + FREE_ALLOCA(shiftlist); + return; + } +#endif + + /* Kill JMP-over-NOP-s */ + if (opline->opcode == ZEND_JMP && ZEND_OP1(opline).opline_num > i) { + /* check if there are only NOPs under the branch */ + zend_op *target = op_array->opcodes + ZEND_OP1(opline).opline_num - 1; + + while (target->opcode == ZEND_NOP) { + target--; + } + if (target == opline) { + /* only NOPs */ + opline->opcode = ZEND_NOP; + } + } + + shiftlist[i++] = shift; + if (opline->opcode == ZEND_NOP) { + shift++; + } else { + if (shift) { + op_array->opcodes[new_count] = *opline; + } + new_count++; + } + } + + if (shift) { + op_array->last = new_count; + end = op_array->opcodes + op_array->last; + + /* update JMPs */ + for (opline = op_array->opcodes; opline<end; opline++) { + switch (opline->opcode) { + case ZEND_JMP: +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + case ZEND_GOTO: +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + case ZEND_FAST_CALL: +#endif + ZEND_OP1(opline).opline_num -= shiftlist[ZEND_OP1(opline).opline_num]; + break; + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_FETCH: + case ZEND_FE_RESET: + case ZEND_NEW: +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + case ZEND_JMP_SET: +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + case ZEND_JMP_SET_VAR: +#endif + ZEND_OP2(opline).opline_num -= shiftlist[ZEND_OP2(opline).opline_num]; + break; + case ZEND_JMPZNZ: + ZEND_OP2(opline).opline_num -= shiftlist[ZEND_OP2(opline).opline_num]; + opline->extended_value -= shiftlist[opline->extended_value]; + break; + case ZEND_CATCH: + opline->extended_value -= shiftlist[opline->extended_value]; + break; + } + } + + /* update brk/cont array */ + for (j = 0; j < op_array->last_brk_cont; j++) { + op_array->brk_cont_array[j].brk -= shiftlist[op_array->brk_cont_array[j].brk]; + op_array->brk_cont_array[j].cont -= shiftlist[op_array->brk_cont_array[j].cont]; + op_array->brk_cont_array[j].start -= shiftlist[op_array->brk_cont_array[j].start]; + } + + /* update try/catch array */ + for (j = 0; j < op_array->last_try_catch; j++) { + op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; + op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->try_catch_array[j].finally_op) { + op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; + op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; + } +#endif + } + +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + /* update early binding list */ + if (op_array->early_binding != (zend_uint)-1) { + zend_uint *opline_num = &op_array->early_binding; + + do { + *opline_num -= shiftlist[*opline_num]; + opline_num = &ZEND_RESULT(&op_array->opcodes[*opline_num]).opline_num; + } while (*opline_num != (zend_uint)-1); + } +#endif + } + FREE_ALLOCA(shiftlist); +} diff --git a/ext/opcache/Optimizer/optimize_temp_vars_5.c b/ext/opcache/Optimizer/optimize_temp_vars_5.c new file mode 100644 index 0000000000..dc630733fa --- /dev/null +++ b/ext/opcache/Optimizer/optimize_temp_vars_5.c @@ -0,0 +1,222 @@ +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + +/* ops that use CLs: +op1: +ZEND_FETCH_CONSTANT: +ZEND_INIT_CTOR_CALL: +ZEND_INIT_STATIC_METHOD_CALL: +ZEND_INIT_METHOD_CALL: +ZEND_IMPORT_CLASS: +ZEND_IMPORT_FUNCTION: +ZEND_IMPORT_CONST: +ZEND_ADD_INTERFACE: +ZEND_VERIFY_ABSTRACT_CLASS: +ZEND_NEW: +ZEND_CATCH: +ZEND_INIT_FCALL_BY_NAME: + +op2: +ZEND_UNSET_VAR: +ZEND_ISSET_ISEMPTY_VAR: +ZEND_FETCH_UNSET: +ZEND_FETCH_IS: +ZEND_FETCH_R: +ZEND_FETCH_W: +ZEND_FETCH_RW: +ZEND_FETCH_FUNC_ARG: +ZEND_ADD_INTERFACE: +ZEND_INSTANCEOF: + +extended_value: +ZEND_DECLARE_INHERITED_CLASS: + +ignore result +INIT_METHOD_CALL: +*/ + +#define OP1_CONST_IS_CLASS 1 +#define OP2_CONST_IS_CLASS 2 +#define EXT_CONST_IS_CLASS 4 +#define RESULT_IS_UNUSED 8 + +static const char op_const_means_class[256] = { + /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 32 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + /* 64 */ + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, + /* 96 */ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 9, 1, 2, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 128 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 160 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 192 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 224 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +#endif + +#define GET_AVAILABLE_T() \ + for (i = 0; i < T; i++) { \ + if (!taken_T[i]) { \ + break; \ + } \ + } \ + taken_T[i] = 1; \ + if (i > max) { \ + max = i; \ + } + +static void optimize_temporary_variables(zend_op_array *op_array) +{ + int T = op_array->T; + char *taken_T; /* T index in use */ + zend_op **start_of_T; /* opline where T is first used */ + char *valid_T; /* Is the map_T valid */ + int *map_T; /* Map's the T to its new index */ + zend_op *opline, *end; + int currT; + int i; + int max = -1; + int var_to_free = -1; + + taken_T = (char *) emalloc(T); + start_of_T = (zend_op **) emalloc(T * sizeof(zend_op *)); + valid_T = (char *) emalloc(T); + map_T = (int *) emalloc(T * sizeof(int)); + + end = op_array->opcodes; + opline = &op_array->opcodes[op_array->last - 1]; + + /* Find T definition points */ + while (opline >= end) { +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR | IS_CONST)) { + if (!(op_const_means_class[opline->opcode] & RESULT_IS_UNUSED)) { + start_of_T[VAR_NUM(ZEND_RESULT(opline).var)] = opline; + } + } +#else + if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { + start_of_T[VAR_NUM(ZEND_RESULT(opline).var)] = opline; + } +#endif + opline--; + } + + memset(valid_T, 0, T); + memset(taken_T, 0, T); + + end = op_array->opcodes; + opline = &op_array->opcodes[op_array->last - 1]; + + while (opline >= end) { + if ((ZEND_OP1_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + || ((op_const_means_class[opline->opcode] & OP1_CONST_IS_CLASS) && ZEND_OP1_TYPE(opline) == IS_CONST) +#endif + ) { + currT = VAR_NUM(ZEND_OP1(opline).var); + if (!valid_T[currT]) { + GET_AVAILABLE_T(); + map_T[currT] = i; + valid_T[currT] = 1; + } + ZEND_OP1(opline).var = NUM_VAR(map_T[currT]); + } + + /* Skip OP_DATA */ + if (opline->opcode == ZEND_OP_DATA && + (opline-1)->opcode == ZEND_ASSIGN_DIM) { + opline--; + continue; + } + + if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + || ((op_const_means_class[opline->opcode] & OP2_CONST_IS_CLASS) && ZEND_OP2_TYPE(opline) == IS_CONST) +#endif + ) { + currT = VAR_NUM(ZEND_OP2(opline).var); + if (!valid_T[currT]) { + GET_AVAILABLE_T(); + map_T[currT] = i; + valid_T[currT] = 1; + } + ZEND_OP2(opline).var = NUM_VAR(map_T[currT]); + } + +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + if ((op_const_means_class[opline->opcode] & EXT_CONST_IS_CLASS)) { +#else + if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS || + opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) { +#endif + currT = VAR_NUM(opline->extended_value); + if (!valid_T[currT]) { + GET_AVAILABLE_T(); + map_T[currT] = i; + valid_T[currT] = 1; + } + opline->extended_value = NUM_VAR(map_T[currT]); + } + + /* Allocate OP_DATA->op2 after "operands", but before "result" */ + if (opline->opcode == ZEND_ASSIGN_DIM && + (opline + 1)->opcode == ZEND_OP_DATA && + ZEND_OP2_TYPE(opline + 1) & (IS_VAR | IS_TMP_VAR)) { + currT = VAR_NUM(ZEND_OP2(opline + 1).var); + GET_AVAILABLE_T(); + map_T[currT] = i; + valid_T[currT] = 1; + taken_T[i] = 0; + ZEND_OP2(opline + 1).var = NUM_VAR(i); + var_to_free = i; + } + +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR | IS_CONST)) { + if (!(op_const_means_class[opline->opcode] & RESULT_IS_UNUSED)) { +#else + if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { +#endif + currT = VAR_NUM(ZEND_RESULT(opline).var); + if (valid_T[currT]) { + if (start_of_T[currT] == opline) { + taken_T[map_T[currT]] = 0; + } + ZEND_RESULT(opline).var = NUM_VAR(map_T[currT]); + } else { /* Au still needs to be assigned a T which is a bit dumb. Should consider changing Zend */ + GET_AVAILABLE_T(); + + if (RESULT_UNUSED(opline)) { + taken_T[i] = 0; + } else { + /* Code which gets here is using a wrongly built opcode such as RECV() */ + map_T[currT] = i; + valid_T[currT] = 1; + } + ZEND_RESULT(opline).var = NUM_VAR(i); + } +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + } +#endif + } + + if (var_to_free >= 0) { + taken_T[var_to_free] = 0; + var_to_free = -1; + } + + opline--; + } + + efree(taken_T); + efree(start_of_T); + efree(valid_T); + efree(map_T); + op_array->T = max + 1; +} diff --git a/ext/opcache/Optimizer/pass10.c b/ext/opcache/Optimizer/pass10.c new file mode 100644 index 0000000000..3bfcec643a --- /dev/null +++ b/ext/opcache/Optimizer/pass10.c @@ -0,0 +1,3 @@ +if (((ZEND_OPTIMIZER_PASS_10|ZEND_OPTIMIZER_PASS_5) & OPTIMIZATION_LEVEL) == ZEND_OPTIMIZER_PASS_10) { + nop_removal(op_array); +} diff --git a/ext/opcache/Optimizer/pass1_5.c b/ext/opcache/Optimizer/pass1_5.c new file mode 100644 index 0000000000..ca5b882901 --- /dev/null +++ b/ext/opcache/Optimizer/pass1_5.c @@ -0,0 +1,338 @@ +/* pass 1 + * - substitute persistent constants (true, false, null, etc) + * - perform compile-time evaluation of constant binary and unary operations + * - optimize series of ADD_STRING and/or ADD_CHAR + * - convert CAST(IS_BOOL,x) into BOOL(x) + * - convert INTI_FCALL_BY_NAME, DO_FCALL_BY_NAME into DO_FCALL + */ + +if (ZEND_OPTIMIZER_PASS_1 & OPTIMIZATION_LEVEL) { + int i = 0; + zend_op *opline = op_array->opcodes; + zend_op *end = opline + op_array->last; + + while (opline < end) { + switch (opline->opcode) { + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + case ZEND_DIV: + case ZEND_MOD: + case ZEND_SL: + case ZEND_SR: + case ZEND_CONCAT: + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + case ZEND_IS_SMALLER: + case ZEND_IS_SMALLER_OR_EQUAL: + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + case ZEND_BOOL_XOR: + if (ZEND_OP1_TYPE(opline) == IS_CONST && + ZEND_OP2_TYPE(opline) == IS_CONST) { + /* binary operation with constant operands */ + int (*binary_op)(zval *result, zval *op1, zval *op2 TSRMLS_DC) = get_binary_op(opline->opcode); + zend_uint tv = ZEND_RESULT(opline).var; /* temporary variable */ + zval result; + int er; + + if (opline->opcode == ZEND_DIV && + Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_LONG && + Z_LVAL(ZEND_OP2_LITERAL(opline)) == 0) { + /* div by 0 */ + break; + } + er = EG(error_reporting); + EG(error_reporting) = 0; + /* evaluate constant expression */ + if (binary_op(&result, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline) TSRMLS_CC) != SUCCESS) { + EG(error_reporting) = er; + break; + } + EG(error_reporting) = er; + PZ_SET_REFCOUNT_P(&result, 1); + PZ_UNSET_ISREF_P(&result); + + literal_dtor(&ZEND_OP1_LITERAL(opline)); + literal_dtor(&ZEND_OP2_LITERAL(opline)); + MAKE_NOP(opline); + + replace_tmp_by_const(op_array, opline + 1, tv, &result TSRMLS_CC); + } + break; + + case ZEND_CAST: + if (ZEND_OP1_TYPE(opline) == IS_CONST && + opline->extended_value != IS_ARRAY && + opline->extended_value != IS_OBJECT && + opline->extended_value != IS_RESOURCE) { + /* cast of constant operand */ + zend_uint tv = ZEND_RESULT(opline).var; /* temporary variable */ + zval res; + res = ZEND_OP1_LITERAL(opline); + zval_copy_ctor(&res); + switch (opline->extended_value) { + case IS_NULL: + convert_to_null(&res); + break; + case IS_BOOL: + convert_to_boolean(&res); + break; + case IS_LONG: + convert_to_long(&res); + break; + case IS_DOUBLE: + convert_to_double(&res); + break; + case IS_STRING: + convert_to_string(&res); + break; + } + + literal_dtor(&ZEND_OP1_LITERAL(opline)); + MAKE_NOP(opline); + + replace_tmp_by_const(op_array, opline + 1, tv, &res TSRMLS_CC); + } else if (opline->extended_value == IS_BOOL) { + /* T = CAST(X, IS_BOOL) => T = BOOL(X) */ + opline->opcode = ZEND_BOOL; + opline->extended_value = 0; + } + break; + + case ZEND_BW_NOT: + case ZEND_BOOL_NOT: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + /* unary operation on constant operand */ + unary_op_type unary_op = get_unary_op(opline->opcode); + zval result; + zend_uint tv = ZEND_RESULT(opline).var; /* temporary variable */ + int er; + + er = EG(error_reporting); + EG(error_reporting) = 0; +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + if (unary_op(&result, &ZEND_OP1_LITERAL(opline)) != SUCCESS) { +#else + if (unary_op(&result, &ZEND_OP1_LITERAL(opline) TSRMLS_CC) != SUCCESS) { +#endif + EG(error_reporting) = er; + break; + } + EG(error_reporting) = er; + PZ_SET_REFCOUNT_P(&result, 1); + PZ_UNSET_ISREF_P(&result); + + literal_dtor(&ZEND_OP1_LITERAL(opline)); + MAKE_NOP(opline); + + replace_tmp_by_const(op_array, opline + 1, tv, &result TSRMLS_CC); + } + break; + + case ZEND_ADD_STRING: + case ZEND_ADD_CHAR: + { + zend_op *next_op = opline + 1; + int requires_conversion = (opline->opcode == ZEND_ADD_CHAR? 1 : 0); + size_t final_length = 0; + char *ptr; + zend_op *last_op; + + /* There is always a ZEND_RETURN at the end + if (next_op>=end) { + break; + } + */ + while (next_op->opcode == ZEND_ADD_STRING || next_op->opcode == ZEND_ADD_CHAR) { + if (ZEND_RESULT(opline).var != ZEND_RESULT(next_op).var) { + break; + } + if (next_op->opcode == ZEND_ADD_CHAR) { + final_length += 1; + } else { /* ZEND_ADD_STRING */ + final_length += ZEND_OP2_LITERAL(next_op).value.str.len; + } + next_op++; + } + if (final_length == 0) { + break; + } + last_op = next_op; + final_length += (requires_conversion? 1 : ZEND_OP2_LITERAL(opline).value.str.len); + ptr = (char *)emalloc(final_length + 1); + ptr[final_length] = '\0'; + if (requires_conversion) { /* ZEND_ADD_CHAR */ + char chval = (char)ZEND_OP2_LITERAL(opline).value.lval; + + ZEND_OP2_LITERAL(opline).value.str.val = ptr; + ptr[0] = chval; + ZEND_OP2_LITERAL(opline).type = IS_STRING; + opline->opcode = ZEND_ADD_STRING; + ptr++; + } else { /* ZEND_ADD_STRING */ + memcpy(ptr, Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline))); + if (!IS_INTERNED(Z_STRVAL(ZEND_OP2_LITERAL(opline)))) { + efree(Z_STRVAL(ZEND_OP2_LITERAL(opline))); + } + Z_STRVAL(ZEND_OP2_LITERAL(opline)) = ptr; + ptr += Z_STRLEN(ZEND_OP2_LITERAL(opline)); + } + ZEND_OP2_LITERAL(opline).value.str.len = final_length; + next_op = opline + 1; + while (next_op < last_op) { + if (next_op->opcode == ZEND_ADD_STRING) { + memcpy(ptr, ZEND_OP2_LITERAL(next_op).value.str.val, ZEND_OP2_LITERAL(next_op).value.str.len); + ptr += ZEND_OP2_LITERAL(next_op).value.str.len; + literal_dtor(&ZEND_OP2_LITERAL(next_op)); + } else { /* ZEND_ADD_CHAR */ + *ptr = (char)ZEND_OP2_LITERAL(next_op).value.lval; + ptr++; + } + MAKE_NOP(next_op); + next_op++; + } + if (!((ZEND_OPTIMIZER_PASS_5|ZEND_OPTIMIZER_PASS_10) & OPTIMIZATION_LEVEL)) { + /* NOP removal is disabled => insert JMP over NOPs */ + if (last_op-opline >= 3) { /* If we have more than 2 NOPS then JMP over them */ + (opline + 1)->opcode = ZEND_JMP; + ZEND_OP1(opline + 1).opline_num = last_op - op_array->opcodes; /* that's OK even for ZE2, since opline_num's are resolved in pass 2 later */ + } + } + } + break; + + case ZEND_FETCH_CONSTANT: + if (ZEND_OP1_TYPE(opline) == IS_UNUSED && + ZEND_OP2_TYPE(opline) == IS_CONST && + Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING && + Z_STRLEN(ZEND_OP2_LITERAL(opline)) == sizeof("__COMPILER_HALT_OFFSET__") - 1 && + memcmp(Z_STRVAL(ZEND_OP2_LITERAL(opline)), "__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__") - 1) == 0) { + /* substitute __COMPILER_HALT_OFFSET__ constant */ + zend_bool orig_in_execution = EG(in_execution); + zend_op_array *orig_op_array = EG(active_op_array); + zval offset; + + EG(in_execution) = 1; + EG(active_op_array) = op_array; + if (zend_get_constant("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__") - 1, &offset TSRMLS_CC)) { + zend_uint tv = ZEND_RESULT(opline).var; + + literal_dtor(&ZEND_OP2_LITERAL(opline)); + MAKE_NOP(opline); + replace_tmp_by_const(op_array, opline, tv, &offset TSRMLS_CC); + } + EG(active_op_array) = orig_op_array; + EG(in_execution) = orig_in_execution; + break; + } + + if (ZEND_OP1_TYPE(opline) == IS_UNUSED && + ZEND_OP2_TYPE(opline) == IS_CONST && + ZEND_OP2_LITERAL(opline).type == IS_STRING) { + /* substitute persistent constants */ + zend_uint tv = ZEND_RESULT(opline).var; + zval c; + + if (!zend_get_persistent_constant(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), &c, 1 TSRMLS_CC)) { + break; + } + literal_dtor(&ZEND_OP2_LITERAL(opline)); + MAKE_NOP(opline); + replace_tmp_by_const(op_array, opline, tv, &c TSRMLS_CC); + } + break; + + case ZEND_INIT_FCALL_BY_NAME: + if (opline->extended_value == 0 /* not method */ && + ZEND_OP1_TYPE(opline) == IS_UNUSED && + ZEND_OP2_TYPE(opline) == IS_CONST) { + if ((opline + 1)->opcode == ZEND_DO_FCALL_BY_NAME && + (opline + 1)->extended_value == 0) { + (opline + 1)->opcode = ZEND_DO_FCALL; + COPY_NODE((opline + 1)->op1, opline->op2); + zend_str_tolower(Z_STRVAL(ZEND_OP1_LITERAL(opline + 1)), Z_STRLEN(ZEND_OP1_LITERAL(opline + 1))); +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + Z_HASH_P(&ZEND_OP1_LITERAL(opline + 1)) = zend_hash_func(Z_STRVAL(ZEND_OP1_LITERAL(opline + 1)), Z_STRLEN(ZEND_OP1_LITERAL(opline + 1)) + 1); + op_array->literals[(opline + 1)->op1.constant].cache_slot = op_array->last_cache_slot++; +#endif + MAKE_NOP(opline); + } + } + break; + +#if ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO + case ZEND_FETCH_R: + case ZEND_FETCH_W: + case ZEND_FETCH_RW: + case ZEND_FETCH_FUNC_ARG: + case ZEND_FETCH_IS: + case ZEND_FETCH_UNSET: + if (opline != op_array->opcodes && + (opline-1)->opcode == ZEND_BEGIN_SILENCE && + (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL && + opline->op1_type == IS_CONST && + opline->op2_type == IS_UNUSED && + Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING && + (Z_STRLEN(ZEND_OP1_LITERAL(opline)) != sizeof("this")-1 || + memcmp(Z_STRVAL(ZEND_OP1_LITERAL(opline)), "this", sizeof("this")) != 0)) { + + int var = opline->result.var; + int level = 0; + zend_op *op = opline + 1; + zend_op *use = NULL; + + while (op < end) { + if (op->opcode == ZEND_BEGIN_SILENCE) { + level++; + } else if (op->opcode == ZEND_END_SILENCE) { + if (level == 0) { + break; + } else { + level--; + } + } + if (op->op1_type == IS_VAR && op->op1.var == var) { + if (use) { + /* used more than once */ + use = NULL; + break; + } + use = op; + } else if (op->op2_type == IS_VAR && op->op2.var == var) { + if (use) { + /* used more than once */ + use = NULL; + break; + } + use = op; + } + op++; + } + if (use) { + if (use->op1_type == IS_VAR && use->op1.var == var) { + use->op1_type = IS_CV; + use->op1.var = zend_optimizer_lookup_cv(op_array, + Z_STRVAL(ZEND_OP1_LITERAL(opline)), + Z_STRLEN(ZEND_OP1_LITERAL(opline))); + MAKE_NOP(opline); + } else if (use->op2_type == IS_VAR && use->op2.var == var) { + use->op2_type = IS_CV; + use->op2.var = zend_optimizer_lookup_cv(op_array, + Z_STRVAL(ZEND_OP1_LITERAL(opline)), + Z_STRLEN(ZEND_OP1_LITERAL(opline))); + MAKE_NOP(opline); + } + } + } + break; +#endif + + } + opline++; + i++; + } +} diff --git a/ext/opcache/Optimizer/pass2.c b/ext/opcache/Optimizer/pass2.c new file mode 100644 index 0000000000..30708a0935 --- /dev/null +++ b/ext/opcache/Optimizer/pass2.c @@ -0,0 +1,211 @@ +/* pass 2: + * - convert non-numeric constants to numeric constants in numeric operators + * - optimize constant conditional JMPs + * - optimize static BRKs and CONTs + */ + +if (ZEND_OPTIMIZER_PASS_2 & OPTIMIZATION_LEVEL) { + zend_op *opline; + zend_op *end = op_array->opcodes + op_array->last; + + opline = op_array->opcodes; + while (opline < end) { + switch (opline->opcode) { + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + case ZEND_DIV: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + if (ZEND_OP1_LITERAL(opline).type == IS_STRING) { + convert_scalar_to_number(&ZEND_OP1_LITERAL(opline) TSRMLS_CC); + } + } + /* break missing *intentionally* - the assign_op's may only optimize op2 */ + case ZEND_ASSIGN_ADD: + case ZEND_ASSIGN_SUB: + case ZEND_ASSIGN_MUL: + case ZEND_ASSIGN_DIV: + if (opline->extended_value != 0) { + /* object tristate op - don't attempt to optimize it! */ + break; + } + if (ZEND_OP2_TYPE(opline) == IS_CONST) { + if (ZEND_OP2_LITERAL(opline).type == IS_STRING) { + convert_scalar_to_number(&ZEND_OP2_LITERAL(opline) TSRMLS_CC); + } + } + break; + + case ZEND_MOD: + case ZEND_SL: + case ZEND_SR: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + if (ZEND_OP1_LITERAL(opline).type != IS_LONG) { + convert_to_long(&ZEND_OP1_LITERAL(opline)); + } + } + /* break missing *intentionally - the assign_op's may only optimize op2 */ + case ZEND_ASSIGN_MOD: + case ZEND_ASSIGN_SL: + case ZEND_ASSIGN_SR: + if (opline->extended_value != 0) { + /* object tristate op - don't attempt to optimize it! */ + break; + } + if (ZEND_OP2_TYPE(opline) == IS_CONST) { + if (ZEND_OP2_LITERAL(opline).type != IS_LONG) { + convert_to_long(&ZEND_OP2_LITERAL(opline)); + } + } + break; + + case ZEND_CONCAT: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + if (ZEND_OP1_LITERAL(opline).type != IS_STRING) { + convert_to_string(&ZEND_OP1_LITERAL(opline)); + } + } + /* break missing *intentionally - the assign_op's may only optimize op2 */ + case ZEND_ASSIGN_CONCAT: + if (opline->extended_value != 0) { + /* object tristate op - don't attempt to optimize it! */ + break; + } + if (ZEND_OP2_TYPE(opline) == IS_CONST) { + if (ZEND_OP2_LITERAL(opline).type != IS_STRING) { + convert_to_string(&ZEND_OP2_LITERAL(opline)); + } + } + break; + + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + /* convert Ti = JMPZ_EX(Ti, L) to JMPZ(Ti, L) */ + if (0 && /* FIXME: temporary disable unsafe pattern */ + ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + ZEND_RESULT_TYPE(opline) == IS_TMP_VAR && + ZEND_OP1(opline).var == ZEND_RESULT(opline).var) { + opline->opcode -= 3; + /* convert Ti = JMPZ_EX(C, L) => Ti = QM_ASSIGN(C) + in case we know it wouldn't jump */ + } else if (ZEND_OP1_TYPE(opline) == IS_CONST) { + int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline)); + if (opline->opcode == ZEND_JMPZ_EX) { + should_jmp = !should_jmp; + } + if (!should_jmp) { + opline->opcode = ZEND_QM_ASSIGN; + SET_UNUSED(opline->op2); + } + } + break; + + case ZEND_JMPZ: + case ZEND_JMPNZ: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline)); + + if (opline->opcode == ZEND_JMPZ) { + should_jmp = !should_jmp; + } + literal_dtor(&ZEND_OP1_LITERAL(opline)); + ZEND_OP1_TYPE(opline) = IS_UNUSED; + if (should_jmp) { + opline->opcode = ZEND_JMP; + COPY_NODE(opline->op1, opline->op2); + } else { + MAKE_NOP(opline); + } + break; + } + if ((opline + 1)->opcode == ZEND_JMP) { + /* JMPZ(X, L1), JMP(L2) => JMPZNZ(X, L1, L2) */ + /* JMPNZ(X, L1), JMP(L2) => JMPZNZ(X, L2, L1) */ + if (ZEND_OP2(opline).opline_num == ZEND_OP1(opline + 1).opline_num) { + /* JMPZ(X, L1), JMP(L1) => NOP, JMP(L1) */ + MAKE_NOP(opline); + } else { + if (opline->opcode == ZEND_JMPZ) { + opline->extended_value = ZEND_OP1(opline + 1).opline_num; + } else { + opline->extended_value = ZEND_OP2(opline).opline_num; + COPY_NODE(opline->op2, (opline + 1)->op1); + } + opline->opcode = ZEND_JMPZNZ; + } + } + break; + + case ZEND_JMPZNZ: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + int opline_num; + + if (zend_is_true(&ZEND_OP1_LITERAL(opline))) { + opline_num = opline->extended_value; /* JMPNZ */ + } else { + opline_num = ZEND_OP2(opline).opline_num; /* JMPZ */ + } + literal_dtor(&ZEND_OP1_LITERAL(opline)); + ZEND_OP1(opline).opline_num = opline_num; + ZEND_OP1_TYPE(opline) = IS_UNUSED; + opline->opcode = ZEND_JMP; + } + break; + + case ZEND_BRK: + case ZEND_CONT: + { + zend_brk_cont_element *jmp_to; + int array_offset; + int nest_levels; + int dont_optimize = 0; + + if (ZEND_OP2_TYPE(opline) != IS_CONST) { + break; + } + convert_to_long(&ZEND_OP2_LITERAL(opline)); + nest_levels = ZEND_OP2_LITERAL(opline).value.lval; + + array_offset = ZEND_OP1(opline).opline_num; + while (1) { + if (array_offset == -1) { + dont_optimize = 1; /* don't optimize this bogus break/continue, let the executor shout */ + break; + } + jmp_to = &op_array->brk_cont_array[array_offset]; + array_offset = jmp_to->parent; + if (--nest_levels > 0) { + if (opline->opcode == ZEND_BRK && + (op_array->opcodes[jmp_to->brk].opcode == ZEND_FREE || + op_array->opcodes[jmp_to->brk].opcode == ZEND_SWITCH_FREE)) { + dont_optimize = 1; + break; + } + } else { + break; + } + } + + if (dont_optimize) { + break; + } + + /* optimize - convert to a JMP */ + switch (opline->opcode) { + case ZEND_BRK: + MAKE_NOP(opline); + ZEND_OP1(opline).opline_num = jmp_to->brk; + break; + case ZEND_CONT: + MAKE_NOP(opline); + ZEND_OP1(opline).opline_num = jmp_to->cont; + break; + } + opline->opcode = ZEND_JMP; + /* MAKE_NOP() already set op1 and op2 to IS_UNUSED */ + } + break; + } + opline++; + } +} diff --git a/ext/opcache/Optimizer/pass3.c b/ext/opcache/Optimizer/pass3.c new file mode 100644 index 0000000000..fd2a190009 --- /dev/null +++ b/ext/opcache/Optimizer/pass3.c @@ -0,0 +1,442 @@ +/* pass 3: + * - optimize $i = $i+expr to $i+=expr + * - optimize series of JMPs + * - change $i++ to ++$i where possible + */ + +/* compares opcodes with allowing oc1 be _EX of oc2 */ +#define SAME_OPCODE_EX(oc1, oc2) ((oc1 == oc2) || (oc1 == ZEND_JMPZ_EX && oc2 == ZEND_JMPZ) || (oc1 == ZEND_JMPNZ_EX && oc2 == ZEND_JMPNZ)) + +/* we use "jmp_hitlist" to avoid infinity loops during jmp optimization */ +#define CHECK_JMP(target, label) \ + for (i=0; i<jmp_hitlist_count; i++) { \ + if (jmp_hitlist[i] == ZEND_OP1(&op_array->opcodes[target]).opline_num) { \ + goto label; \ + } \ + } \ + jmp_hitlist[jmp_hitlist_count++] = ZEND_OP1(&op_array->opcodes[target]).opline_num; + +#define CHECK_JMP2(target, label) \ + for (i=0; i<jmp_hitlist_count; i++) { \ + if (jmp_hitlist[i] == ZEND_OP2(&op_array->opcodes[target]).opline_num) { \ + goto label; \ + } \ + } \ + jmp_hitlist[jmp_hitlist_count++] = ZEND_OP2(&op_array->opcodes[target]).opline_num; + +if (ZEND_OPTIMIZER_PASS_3 & OPTIMIZATION_LEVEL) { + zend_op *opline; + zend_op *end = op_array->opcodes + op_array->last; + zend_uint *jmp_hitlist; + int jmp_hitlist_count; + int i; + zend_uint opline_num = 0; + ALLOCA_FLAG(use_heap); + + jmp_hitlist = (zend_uint *)DO_ALLOCA(sizeof(zend_uint)*op_array->last); + opline = op_array->opcodes; + + while (opline < end) { + jmp_hitlist_count = 0; + + switch (opline->opcode) { + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + case ZEND_DIV: + case ZEND_MOD: + case ZEND_CONCAT: + case ZEND_SL: + case ZEND_SR: + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + { + zend_op *next_opline = opline + 1; + + while (next_opline < end && next_opline->opcode == ZEND_NOP) { + ++next_opline; + } + + if (next_opline >= end || next_opline->opcode != ZEND_ASSIGN) { + break; + } + + if ((ZEND_OP2_TYPE(opline) == IS_VAR || ZEND_OP2_TYPE(opline) == IS_CV) + && ZEND_OP2(opline).var == ZEND_OP1(next_opline).var && + (opline->opcode == ZEND_ADD || + opline->opcode == ZEND_MUL || + opline->opcode == ZEND_BW_OR || + opline->opcode == ZEND_BW_AND || + opline->opcode == ZEND_BW_XOR)) { + /* change $i=expr+$i to $i=$i+expr so that the next + * optimization works on it + */ +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + zend_uchar tmp_type = opline->op1_type; + znode_op tmp = opline->op1; +#else + znode tmp = opline->op1; +#endif + + if (opline->opcode != ZEND_ADD || ZEND_OP1_TYPE(opline) == IS_CONST) { + /* protection from array add: $a = array + $a is not commutative! */ + COPY_NODE(opline->op1, opline->op2); + COPY_NODE(opline->op2, tmp); + } + } + if ((ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_CV) + && ZEND_OP1(opline).var == ZEND_OP1(next_opline).var + && ZEND_OP1_TYPE(opline) == ZEND_OP1_TYPE(next_opline)) { + switch (opline->opcode) { + case ZEND_ADD: + opline->opcode = ZEND_ASSIGN_ADD; + break; + case ZEND_SUB: + opline->opcode = ZEND_ASSIGN_SUB; + break; + case ZEND_MUL: + opline->opcode = ZEND_ASSIGN_MUL; + break; + case ZEND_DIV: + opline->opcode = ZEND_ASSIGN_DIV; + break; + case ZEND_MOD: + opline->opcode = ZEND_ASSIGN_MOD; + break; + case ZEND_CONCAT: + opline->opcode = ZEND_ASSIGN_CONCAT; + break; + case ZEND_SL: + opline->opcode = ZEND_ASSIGN_SL; + break; + case ZEND_SR: + opline->opcode = ZEND_ASSIGN_SR; + break; + case ZEND_BW_OR: + opline->opcode = ZEND_ASSIGN_BW_OR; + break; + case ZEND_BW_AND: + opline->opcode = ZEND_ASSIGN_BW_AND; + break; + case ZEND_BW_XOR: + opline->opcode = ZEND_ASSIGN_BW_XOR; + break; + } + COPY_NODE(opline->result, next_opline->result); + MAKE_NOP(next_opline); + opline++; + opline_num++; + } + } + break; + + case ZEND_JMP: +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->has_finally_block) { + break; + } +#endif + + /* convert L: JMP L+1 to NOP */ + if (ZEND_OP1(opline).opline_num == opline_num + 1) { + MAKE_NOP(opline); + goto done_jmp_optimization; + } + + /* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */ + while (ZEND_OP1(opline).opline_num < op_array->last + && op_array->opcodes[ZEND_OP1(opline).opline_num].opcode == ZEND_JMP) { + int target = ZEND_OP1(opline).opline_num; + CHECK_JMP(target, done_jmp_optimization); + ZEND_OP1(opline).opline_num = ZEND_OP1(&op_array->opcodes[target]).opline_num; + } + break; + +#if ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + case ZEND_JMP_SET: +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + case ZEND_JMP_SET_VAR: +#endif + +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->has_finally_block) { + break; + } +#endif + + while (ZEND_OP2(opline).opline_num < op_array->last) { + int target = ZEND_OP2(opline).opline_num; + if (op_array->opcodes[target].opcode == ZEND_JMP) { + ZEND_OP2(opline).opline_num = ZEND_OP1(&op_array->opcodes[target]).opline_num; + } else { + break; + } + } + break; +#endif + + case ZEND_JMPZ: + case ZEND_JMPNZ: +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->has_finally_block) { + break; + } +#endif + + /* convert L: JMPZ L+1 to NOP */ + if (ZEND_OP2(opline).opline_num == opline_num + 1) { + MAKE_NOP(opline); + goto done_jmp_optimization; + } + + while (ZEND_OP2(opline).opline_num < op_array->last) { + int target = ZEND_OP2(opline).opline_num; + + if (op_array->opcodes[target].opcode == ZEND_JMP) { + /* plain JMP */ + /* JMPZ(X,L1), L1: JMP(L2) => JMPZ(X,L2), L1: JMP(L2) */ + CHECK_JMP(target, done_jmp_optimization); + ZEND_OP2(opline).opline_num = ZEND_OP1(&op_array->opcodes[target]).opline_num; + } else if (op_array->opcodes[target].opcode == opline->opcode && + SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + /* same opcode and same var as this opcode */ + /* JMPZ(X,L1), L1: JMPZ(X,L2) => JMPZ(X,L2), L1: JMPZ(X,L2) */ + CHECK_JMP2(target, done_jmp_optimization); + ZEND_OP2(opline).opline_num = ZEND_OP2(&op_array->opcodes[target]).opline_num; + } else if (op_array->opcodes[target].opcode == opline->opcode + 3 && + SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + /* convert JMPZ(X,L1), L1: T JMPZ_EX(X,L2) to + T = JMPZ_EX(X, L2) */ + ZEND_OP2(opline).opline_num = ZEND_OP2(&op_array->opcodes[target]).opline_num;opline->opcode += 3; + COPY_NODE(opline->result, op_array->opcodes[target].result); + break; + } else if (op_array->opcodes[target].opcode == INV_COND(opline->opcode) && + SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + /* convert JMPZ(X,L1), L1: JMPNZ(X,L2) to + JMPZ(X,L1+1) */ + ZEND_OP2(opline).opline_num = target + 1; + break; + } else if (op_array->opcodes[target].opcode == INV_COND_EX(opline->opcode) && + SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + /* convert JMPZ(X,L1), L1: T = JMPNZ_EX(X,L2) to + T = JMPZ_EX(X,L1+1) */ + ZEND_OP2(opline).opline_num = target + 1; + opline->opcode += 3; + COPY_NODE(opline->result, op_array->opcodes[target].result); + break; + } else { + break; + } + } + break; + + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: { +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + zend_uchar T_type = opline->result_type; + znode_op T = opline->result; +#else + znode T = opline->result; +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->has_finally_block) { + break; + } +#endif + /* convert L: T = JMPZ_EX X,L+1 to T = BOOL(X) */ + /* convert L: T = JMPZ_EX T,L+1 to NOP */ + if (ZEND_OP2(opline).opline_num == opline_num + 1) { + if (ZEND_OP1(opline).var == ZEND_RESULT(opline).var) { + MAKE_NOP(opline); + } else { + opline->opcode = ZEND_BOOL; + SET_UNUSED(opline->op2); + } + goto done_jmp_optimization; + } + + while (ZEND_OP2(opline).opline_num < op_array->last) { + int target = ZEND_OP2(opline).opline_num; + if (SAME_OPCODE_EX(opline->opcode, op_array->opcodes[target].opcode) && + SAME_VAR(op_array->opcodes[target].op1, T)) { + /* Check for JMPZ_EX to JMPZ[_EX] with the same condition, either with _EX or not */ + if (op_array->opcodes[target].opcode == opline->opcode) { + /* change T only if we have _EX opcode there */ + COPY_NODE(T, op_array->opcodes[target].result); + } + CHECK_JMP2(target, continue_jmp_ex_optimization); + ZEND_OP2(opline).opline_num = ZEND_OP2(&op_array->opcodes[target]).opline_num; + } else if (op_array->opcodes[target].opcode == ZEND_JMPZNZ && + SAME_VAR(op_array->opcodes[target].op1, T)) { + /* Check for JMPZNZ with same cond variable */ + int new_target; + CHECK_JMP2(target, continue_jmp_ex_optimization); + if (opline->opcode == ZEND_JMPZ_EX) { + new_target = ZEND_OP2(&op_array->opcodes[target]).opline_num; + } else { + /* JMPNZ_EX */ + new_target = op_array->opcodes[target].extended_value; + } + ZEND_OP2(opline).opline_num = new_target; + } else if ((op_array->opcodes[target].opcode == INV_EX_COND_EX(opline->opcode) || + op_array->opcodes[target].opcode == INV_EX_COND(opline->opcode)) && + SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + /* convert JMPZ_EX(X,L1), L1: JMPNZ_EX(X,L2) to + JMPZ_EX(X,L1+1) */ + ZEND_OP2(opline).opline_num = target + 1; + break; + } else { + break; + } + } /* while */ +continue_jmp_ex_optimization: + break; +#if 0 + /* If Ti = JMPZ_EX(X, L) and Ti is not used, convert to JMPZ(X, L) */ + { + zend_op *op; + for(op = opline+1; op<end; op++) { + if(ZEND_RESULT_TYPE(op) == IS_TMP_VAR && + ZEND_RESULT(op).var == ZEND_RESULT(opline).var) { + break; /* can pass to part 2 */ + } + + if(op->opcode == ZEND_JMP || + op->opcode == ZEND_JMPZ || + op->opcode == ZEND_JMPZ_EX || + op->opcode == ZEND_JMPNZ || + op->opcode == ZEND_JMPNZ_EX || + op->opcode == ZEND_JMPZNZ || + op->opcode == ZEND_BRK || + op->opcode == ZEND_CONT || + op->opcode == ZEND_CASE || + op->opcode == ZEND_RETURN || +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + op->opcode == ZEND_RETURN_BY_REF || +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + op->opcode == ZEND_FAST_RET || +#endif + op->opcode == ZEND_FE_FETCH || + op->opcode == ZEND_EXIT) { + break; + } + + if(ZEND_OP1_TYPE(op) == IS_TMP_VAR && + ZEND_OP1(op).var == ZEND_RESULT(opline).var) { + goto done_jmp_optimization; + } + + if(ZEND_OP2_TYPE(op) == IS_TMP_VAR && + ZEND_OP2(op).var == ZEND_RESULT(opline).var) { + goto done_jmp_optimization; + } + } /* for */ + + for(op = &op_array->opcodes[ZEND_OP2(opline).opline_num]; op<end; op++) { + + if(ZEND_RESULT_TYPE(op) == IS_TMP_VAR && + ZEND_RESULT(op).var == ZEND_RESULT(opline).var) { + break; /* can pass to optimization */ + } + + if(op->opcode == ZEND_JMP || + op->opcode == ZEND_JMPZ || + op->opcode == ZEND_JMPZ_EX || + op->opcode == ZEND_JMPNZ || + op->opcode == ZEND_JMPNZ_EX || + op->opcode == ZEND_JMPZNZ || + op->opcode == ZEND_BRK || + op->opcode == ZEND_CONT || + op->opcode == ZEND_CASE || + op->opcode == ZEND_RETURN || +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + op->opcode == ZEND_RETURN_BY_REF || +#endif +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + op->opcode == ZEND_FAST_RET || +#endif + op->opcode == ZEND_FE_FETCH || + op->opcode == ZEND_EXIT) { + break; + } + + if(ZEND_OP1_TYPE(op) == IS_TMP_VAR && + ZEND_OP1(op).var == ZEND_RESULT(opline).var) { + goto done_jmp_optimization; + } + + if(ZEND_OP2_TYPE(op) == IS_TMP_VAR && + ZEND_OP2(op).var == ZEND_RESULT(opline).var) { + goto done_jmp_optimization; + } + } + + opline->opcode = opline->opcode-3; /* JMP_EX -> JMP */ + SET_UNUSED(opline->result); + break; + } +#endif + } + break; + + case ZEND_JMPZNZ: +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO + if (op_array->has_finally_block) { + break; + } +#endif + /* JMPZNZ(X,L1,L2), L1: JMP(L3) => JMPZNZ(X,L3,L2), L1: JMP(L3) */ + while (ZEND_OP2(opline).opline_num < op_array->last + && op_array->opcodes[ZEND_OP2(opline).opline_num].opcode == ZEND_JMP) { + int target = ZEND_OP2(opline).opline_num; + CHECK_JMP(target, continue_jmpznz_optimization); + ZEND_OP2(opline).opline_num = ZEND_OP1(&op_array->opcodes[target]).opline_num; + } +continue_jmpznz_optimization: + /* JMPZNZ(X,L1,L2), L2: JMP(L3) => JMPZNZ(X,L1,L3), L2: JMP(L3) */ + while (opline->extended_value < op_array->last + && op_array->opcodes[opline->extended_value].opcode == ZEND_JMP) { + int target = opline->extended_value; + CHECK_JMP(target, done_jmp_optimization); + opline->extended_value = ZEND_OP1(&op_array->opcodes[target]).opline_num; + } + break; + + case ZEND_POST_INC: + case ZEND_POST_DEC: { + /* POST_INC, FREE => PRE_INC */ + zend_op *next_op = opline + 1; + + if (next_op >= end) { + break; + } + if (next_op->opcode == ZEND_FREE && + ZEND_OP1(next_op).var == ZEND_RESULT(opline).var) { + MAKE_NOP(next_op); + switch (opline->opcode) { + case ZEND_POST_INC: + opline->opcode = ZEND_PRE_INC; + break; + case ZEND_POST_DEC: + opline->opcode = ZEND_PRE_DEC; + break; + } +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + ZEND_RESULT_TYPE(opline) = IS_VAR | EXT_TYPE_UNUSED; +#else + ZEND_RESULT_TYPE(opline) = IS_VAR; + ZEND_RESULT(opline).EA.type = 0; + ZEND_RESULT(opline).EA.type |= EXT_TYPE_UNUSED; +#endif + } + } + break; + } +done_jmp_optimization: + opline++; + opline_num++; + } + FREE_ALLOCA(jmp_hitlist); +} diff --git a/ext/opcache/Optimizer/pass5.c b/ext/opcache/Optimizer/pass5.c new file mode 100644 index 0000000000..b0d651a5fc --- /dev/null +++ b/ext/opcache/Optimizer/pass5.c @@ -0,0 +1,3 @@ +if (ZEND_OPTIMIZER_PASS_5 & OPTIMIZATION_LEVEL) { + zend_block_optimization(op_array TSRMLS_CC); +} diff --git a/ext/opcache/Optimizer/pass9.c b/ext/opcache/Optimizer/pass9.c new file mode 100644 index 0000000000..586160c14d --- /dev/null +++ b/ext/opcache/Optimizer/pass9.c @@ -0,0 +1,8 @@ +/* pass 9 + * + * - optimize usage of temporary variables + */ + +if (ZEND_OPTIMIZER_PASS_9 & OPTIMIZATION_LEVEL) { + optimize_temporary_variables(op_array); +} diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c new file mode 100644 index 0000000000..2c96c3b0ce --- /dev/null +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -0,0 +1,351 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2013 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 <andi@zend.com> | + | Zeev Suraski <zeev@zend.com> | + | Stanislav Malyshev <stas@zend.com> | + | Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#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" + +#define OPTIMIZATION_LEVEL \ + ZCG(accel_directives).optimization_level + +#if ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO +static int zend_optimizer_lookup_cv(zend_op_array *op_array, char* name, int name_len) +{ + int i = 0; + ulong hash_value = zend_inline_hash_func(name, name_len+1); + + while (i < op_array->last_var) { + if (op_array->vars[i].name == name || + (op_array->vars[i].hash_value == hash_value && + op_array->vars[i].name_len == name_len && + memcmp(op_array->vars[i].name, name, name_len) == 0)) { + return i; + } + i++; + } + i = op_array->last_var; + op_array->last_var++; + op_array->vars = erealloc(op_array->vars, op_array->last_var * sizeof(zend_compiled_variable)); + if (IS_INTERNED(name)) { + op_array->vars[i].name = name; + } else { + op_array->vars[i].name = estrndup(name, name_len); + } + op_array->vars[i].name_len = name_len; + op_array->vars[i].hash_value = hash_value; + return i; +} +#endif + +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO +int zend_optimizer_add_literal(zend_op_array *op_array, const zval *zv TSRMLS_DC) +{ + int i = op_array->last_literal; + op_array->last_literal++; + if (i >= CG(context).literals_size) { + CG(context).literals_size += 16; /* FIXME */ + op_array->literals = (zend_literal*)erealloc(op_array->literals, CG(context).literals_size * sizeof(zend_literal)); + } + op_array->literals[i].constant = *zv; + op_array->literals[i].hash_value = 0; + op_array->literals[i].cache_slot = -1; + Z_SET_REFCOUNT(op_array->literals[i].constant, 2); + Z_SET_ISREF(op_array->literals[i].constant); + return i; +} + +# define LITERAL_LONG(op, val) do { \ + zval _c; \ + ZVAL_LONG(&_c, val); \ + op.constant = zend_optimizer_add_literal(op_array, &_c TSRMLS_CC); \ + } while (0) + +# define LITERAL_BOOL(op, val) do { \ + zval _c; \ + ZVAL_BOOL(&_c, val); \ + op.constant = zend_optimizer_add_literal(op_array, &_c TSRMLS_CC); \ + } while (0) + +# define literal_dtor(zv) do { \ + zval_dtor(zv); \ + Z_TYPE_P(zv) = IS_NULL; \ + } while (0) + +#define COPY_NODE(target, src) do { \ + target ## _type = src ## _type; \ + target = src; \ + } while (0) + +#else + +# define LITERAL_LONG(op, val) ZVAL_LONG(&op.u.constant, val) + +# define LITERAL_BOOL(op, val) ZVAL_BOOL(&op.u.constant, val) + +# define literal_dtor(zv) zval_dtor(zv) + +#define COPY_NODE(target, src) do { \ + target = src; \ + } while (0) + +#endif + +static void update_op1_const(zend_op_array *op_array, + zend_op *opline, + zval *val TSRMLS_DC) +{ + if (opline->opcode == ZEND_FREE) { + MAKE_NOP(opline); + zval_dtor(val); + } else { + ZEND_OP1_TYPE(opline) = IS_CONST; +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + if (Z_TYPE_P(val) == IS_STRING) { + switch (opline->opcode) { + case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_CATCH: + case ZEND_FETCH_CONSTANT: + opline->op1.constant = zend_optimizer_add_literal(op_array, val TSRMLS_CC); + Z_HASH_P(&ZEND_OP1_LITERAL(opline)) = zend_hash_func(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)) + 1); + op_array->literals[opline->op1.constant].cache_slot = op_array->last_cache_slot++; + zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val)); + zend_optimizer_add_literal(op_array, val TSRMLS_CC); + op_array->literals[opline->op1.constant+1].hash_value = zend_hash_func(Z_STRVAL(op_array->literals[opline->op1.constant+1].constant), Z_STRLEN(op_array->literals[opline->op1.constant+1].constant) + 1); + break; + case ZEND_DO_FCALL: + zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val)); + opline->op1.constant = zend_optimizer_add_literal(op_array, val TSRMLS_CC); + Z_HASH_P(&ZEND_OP1_LITERAL(opline)) = zend_hash_func(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)) + 1); + op_array->literals[opline->op1.constant].cache_slot = op_array->last_cache_slot++; + break; + default: + opline->op1.constant = zend_optimizer_add_literal(op_array, val TSRMLS_CC); + Z_HASH_P(&ZEND_OP1_LITERAL(opline)) = zend_hash_func(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)) + 1); + break; + } + } else { + opline->op1.constant = zend_optimizer_add_literal(op_array, val TSRMLS_CC); + } +#else + ZEND_OP1_LITERAL(opline) = *val; +#endif + } +} + +static void update_op2_const(zend_op_array *op_array, + zend_op *opline, + zval *val TSRMLS_DC) +{ + ZEND_OP2_TYPE(opline) = IS_CONST; +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO + opline->op2.constant = zend_optimizer_add_literal(op_array, val TSRMLS_CC); + if (Z_TYPE_P(val) == IS_STRING) { + Z_HASH_P(&ZEND_OP2_LITERAL(opline)) = zend_hash_func(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)) + 1); + switch (opline->opcode) { + case ZEND_FETCH_R: + case ZEND_FETCH_W: + case ZEND_FETCH_RW: + case ZEND_FETCH_IS: + case ZEND_FETCH_UNSET: + case ZEND_FETCH_FUNC_ARG: + case ZEND_FETCH_CLASS: + case ZEND_INIT_FCALL_BY_NAME: + /*case ZEND_INIT_NS_FCALL_BY_NAME:*/ + case ZEND_UNSET_VAR: + case ZEND_ISSET_ISEMPTY_VAR: + case ZEND_ADD_INTERFACE: + case ZEND_ADD_TRAIT: + op_array->literals[opline->op2.constant].cache_slot = op_array->last_cache_slot++; + zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val)); + zend_optimizer_add_literal(op_array, val TSRMLS_CC); + op_array->literals[opline->op2.constant+1].hash_value = zend_hash_func(Z_STRVAL(op_array->literals[opline->op2.constant+1].constant), Z_STRLEN(op_array->literals[opline->op2.constant+1].constant) + 1); + break; + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_STATIC_METHOD_CALL: + zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val)); + zend_optimizer_add_literal(op_array, val TSRMLS_CC); + op_array->literals[opline->op2.constant+1].hash_value = zend_hash_func(Z_STRVAL(op_array->literals[opline->op2.constant+1].constant), Z_STRLEN(op_array->literals[opline->op2.constant+1].constant) + 1); + /* break missing intentionally */ + /*case ZEND_FETCH_CONSTANT:*/ + case ZEND_ASSIGN_OBJ: + case ZEND_FETCH_OBJ_R: + case ZEND_FETCH_OBJ_W: + case ZEND_FETCH_OBJ_RW: + case ZEND_FETCH_OBJ_IS: + case ZEND_FETCH_OBJ_UNSET: + case ZEND_FETCH_OBJ_FUNC_ARG: + case ZEND_UNSET_OBJ: + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + op_array->literals[opline->op2.constant].cache_slot = op_array->last_cache_slot; + op_array->last_cache_slot += 2; + break; + case ZEND_ASSIGN_ADD: + case ZEND_ASSIGN_SUB: + case ZEND_ASSIGN_MUL: + case ZEND_ASSIGN_DIV: + case ZEND_ASSIGN_MOD: + case ZEND_ASSIGN_SL: + case ZEND_ASSIGN_SR: + case ZEND_ASSIGN_CONCAT: + case ZEND_ASSIGN_BW_OR: + case ZEND_ASSIGN_BW_AND: + case ZEND_ASSIGN_BW_XOR: + if (opline->extended_value == ZEND_ASSIGN_OBJ) { + op_array->literals[opline->op2.constant].cache_slot = op_array->last_cache_slot; + op_array->last_cache_slot += 2; + } + break; +#if ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO + case ZEND_OP_DATA: + if ((opline-1)->opcode == ZEND_ASSIGN_DIM || + ((opline-1)->extended_value == ZEND_ASSIGN_DIM && + ((opline-1)->opcode == ZEND_ASSIGN_ADD || + (opline-1)->opcode == ZEND_ASSIGN_SUB || + (opline-1)->opcode == ZEND_ASSIGN_MUL || + (opline-1)->opcode == ZEND_ASSIGN_DIV || + (opline-1)->opcode == ZEND_ASSIGN_MOD || + (opline-1)->opcode == ZEND_ASSIGN_SL || + (opline-1)->opcode == ZEND_ASSIGN_SR || + (opline-1)->opcode == ZEND_ASSIGN_CONCAT || + (opline-1)->opcode == ZEND_ASSIGN_BW_OR || + (opline-1)->opcode == ZEND_ASSIGN_BW_AND || + (opline-1)->opcode == ZEND_ASSIGN_BW_XOR))) { + goto check_numeric; + } + break; + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + case ZEND_ADD_ARRAY_ELEMENT: + case ZEND_INIT_ARRAY: + case ZEND_UNSET_DIM: + case ZEND_FETCH_DIM_R: + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: + case ZEND_FETCH_DIM_IS: + case ZEND_FETCH_DIM_FUNC_ARG: + case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_DIM_TMP_VAR: +check_numeric: + { + ulong index; + int numeric = 0; + + ZEND_HANDLE_NUMERIC_EX(Z_STRVAL_P(val), Z_STRLEN_P(val)+1, index, numeric = 1); + if (numeric) { + zval_dtor(val); + ZVAL_LONG(val, index); + op_array->literals[opline->op2.constant].constant = *val; + } + } + break; +#endif + default: + break; + } + } +#else + ZEND_OP2_LITERAL(opline) = *val; +#endif +} + +static void replace_tmp_by_const(zend_op_array *op_array, + zend_op *opline, + zend_uint var, + zval *val + TSRMLS_DC) +{ + zend_op *end = op_array->opcodes + op_array->last; + + while (opline < end) { + if (ZEND_OP1_TYPE(opline) == IS_TMP_VAR && + ZEND_OP1(opline).var == var) { + + update_op1_const(op_array, opline, val TSRMLS_CC); + /* TMP_VAR my be used only once */ + break; + } + + if (ZEND_OP2_TYPE(opline) == IS_TMP_VAR && + ZEND_OP2(opline).var == var) { + + update_op2_const(op_array, opline, val TSRMLS_CC); + /* TMP_VAR my be used only once */ + break; + } + opline++; + } +} + +#include "Optimizer/nop_removal.c" +#include "Optimizer/block_pass.c" +#include "Optimizer/optimize_temp_vars_5.c" + +void zend_optimizer(zend_op_array *op_array TSRMLS_DC) +{ + if (op_array->type == ZEND_EVAL_CODE || + (op_array->fn_flags & ZEND_ACC_INTERACTIVE)) { + return; + } + + /* pass 1 + * - substitute persistent constants (true, false, null, etc) + * - perform compile-time evaluation of constant binary and unary operations + * - optimize series of ADD_STRING and/or ADD_CHAR + * - convert CAST(IS_BOOL,x) into BOOL(x) + * - convert INTI_FCALL_BY_NAME + DO_FCALL_BY_NAME into DO_FCALL + */ +#include "Optimizer/pass1_5.c" + + /* pass 2: + * - convert non-numeric constants to numeric constants in numeric operators + * - optimize constant conditional JMPs + * - optimize static BRKs and CONTs + */ +#include "Optimizer/pass2.c" + + /* pass 3: + * - optimize $i = $i+expr to $i+=expr + * - optimize series of JMPs + * - change $i++ to ++$i where possible + */ +#include "Optimizer/pass3.c" + + /* pass 5: + * - CFG optimization + */ +#include "Optimizer/pass5.c" + + /* pass 9: + * - Optimize temp variables usage + */ +#include "Optimizer/pass9.c" + + /* pass 10: + * - remove NOPs + */ +#include "Optimizer/pass10.c" +} diff --git a/ext/opcache/Optimizer/zend_optimizer.h b/ext/opcache/Optimizer/zend_optimizer.h new file mode 100644 index 0000000000..7f2b4f21fd --- /dev/null +++ b/ext/opcache/Optimizer/zend_optimizer.h @@ -0,0 +1,49 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2013 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 <andi@zend.com> | + | Zeev Suraski <zeev@zend.com> | + | Stanislav Malyshev <stas@zend.com> | + | Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_OPTIMIZER_H +#define ZEND_OPTIMIZER_H + +#include "zend.h" +#include "zend_compile.h" + +#define ZEND_OPTIMIZER_PASS_1 (1<<0) /* CSE, STRING construction */ +#define ZEND_OPTIMIZER_PASS_2 (1<<1) /* Constant conversion and jumps */ +#define ZEND_OPTIMIZER_PASS_3 (1<<2) /* ++, +=, series of jumps */ +#define ZEND_OPTIMIZER_PASS_4 (1<<3) +#define ZEND_OPTIMIZER_PASS_5 (1<<4) /* CFG based optimization */ +#define ZEND_OPTIMIZER_PASS_6 (1<<5) +#define ZEND_OPTIMIZER_PASS_7 (1<<6) +#define ZEND_OPTIMIZER_PASS_8 (1<<7) +#define ZEND_OPTIMIZER_PASS_9 (1<<8) /* TMP VAR usage */ +#define ZEND_OPTIMIZER_PASS_10 (1<<9) /* NOP removal */ +#define ZEND_OPTIMIZER_PASS_11 (1<<10) +#define ZEND_OPTIMIZER_PASS_12 (1<<11) +#define ZEND_OPTIMIZER_PASS_13 (1<<12) +#define ZEND_OPTIMIZER_PASS_14 (1<<13) + +#define ZEND_OPTIMIZER_ALL_PASSES 0xFFFFFFFF + +#define DEFAULT_OPTIMIZATION_LEVEL "0xFFFFFFFF" + +void zend_optimizer(zend_op_array *op_array TSRMLS_DC); + +#endif diff --git a/ext/opcache/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h new file mode 100644 index 0000000000..657b480316 --- /dev/null +++ b/ext/opcache/Optimizer/zend_optimizer_internal.h @@ -0,0 +1,86 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2013 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 <andi@zend.com> | + | Zeev Suraski <zeev@zend.com> | + | Stanislav Malyshev <stas@zend.com> | + | Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_OPTIMIZER_INTERNAL_H +#define ZEND_OPTIMIZER_INTERNAL_H + +#include "ZendAccelerator.h" + +#if ZEND_EXTENSION_API_NO > PHP_5_4_X_API_NO +# define VAR_NUM(v) ((zend_uint)(EX_TMP_VAR_NUM(0, 0) - EX_TMP_VAR(0, v))) +# define NUM_VAR(v) ((zend_uint)(zend_uintptr_t)EX_TMP_VAR_NUM(0, v)) +#elif ZEND_EXTENSION_API_NO > PHP_5_2_X_API_NO +# define VAR_NUM(v) ((v)/ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable))) +# define NUM_VAR(v) ((v)*ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable))) +#else +# define VAR_NUM(v) ((v)/(sizeof(temp_variable))) +# define NUM_VAR(v) ((v)*(sizeof(temp_variable))) +#endif + +#define INV_COND(op) ((op) == ZEND_JMPZ ? ZEND_JMPNZ : ZEND_JMPZ) +#define INV_EX_COND(op) ((op) == ZEND_JMPZ_EX ? ZEND_JMPNZ : ZEND_JMPZ) +#define INV_COND_EX(op) ((op) == ZEND_JMPZ ? ZEND_JMPNZ_EX : ZEND_JMPZ_EX) +#define INV_EX_COND_EX(op) ((op) == ZEND_JMPZ_EX ? ZEND_JMPNZ_EX : ZEND_JMPZ_EX) + +#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO +# define MAKE_NOP(opline) { opline->opcode = ZEND_NOP; memset(&opline->result,0,sizeof(opline->result)); memset(&opline->op1,0,sizeof(opline->op1)); memset(&opline->op2,0,sizeof(opline->op2)); opline->result_type=opline->op1_type=opline->op2_type=IS_UNUSED; opline->handler = zend_opcode_handlers[ZEND_NOP]; } +# define RESULT_USED(op) (((op->result_type & IS_VAR) && !(op->result_type & EXT_TYPE_UNUSED)) || op->result_type == IS_TMP_VAR) +# define RESULT_UNUSED(op) ((op->result_type & EXT_TYPE_UNUSED) != 0) +# define SAME_VAR(op1, op2) ((((op1 ## _type & IS_VAR) && (op2 ## _type & IS_VAR)) || (op1 ## _type == IS_TMP_VAR && op2 ## _type == IS_TMP_VAR)) && op1.var == op2.var) +#else +# define MAKE_NOP(opline) { opline->opcode = ZEND_NOP; memset(&opline->result,0,sizeof(znode)); memset(&opline->op1,0,sizeof(znode)); memset(&opline->op2,0,sizeof(znode)); opline->result.op_type=opline->op1.op_type=opline->op2.op_type=IS_UNUSED; opline->handler = zend_opcode_handlers[ZEND_NOP]; } +# define RESULT_USED(op) ((op->result.op_type == IS_VAR && (op->result.u.EA.type & EXT_TYPE_UNUSED) == 0) || (op->result.op_type == IS_TMP_VAR)) +# define RESULT_UNUSED(op) ((op->result.op_type == IS_VAR) && (op->result.u.EA.type == EXT_TYPE_UNUSED)) +# define SAME_VAR(op1, op2) (((op1.op_type == IS_VAR && op2.op_type == IS_VAR) || (op1.op_type == IS_TMP_VAR && op2.op_type == IS_TMP_VAR)) && op1.u.var == op2.u.var) +#endif + +typedef struct _zend_code_block zend_code_block; +typedef struct _zend_block_source zend_block_source; + +struct _zend_code_block { + int access; + zend_op *start_opline; + int start_opline_no; + int len; + zend_code_block *op1_to; + zend_code_block *op2_to; + zend_code_block *ext_to; + zend_code_block *follow_to; + zend_code_block *next; + zend_block_source *sources; + zend_bool protected; /* don't merge this block with others */ +}; + +typedef struct _zend_cfg { + zend_code_block *blocks; + zend_code_block **try; + zend_code_block **catch; + zend_code_block **loop_start; + zend_code_block **loop_cont; + zend_code_block **loop_brk; +} zend_cfg; + +struct _zend_block_source { + zend_code_block *from; + zend_block_source *next; +}; + +#endif |