diff options
Diffstat (limited to 'ext/opcache')
66 files changed, 13714 insertions, 2376 deletions
diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index acce8b70d0..ee4b5516a9 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -27,8 +27,8 @@ #include "zend_execute.h" #include "zend_vm.h" #include "zend_bitset.h" - -#define DEBUG_BLOCKPASS 0 +#include "zend_cfg.h" +#include "zend_dump.h" /* Checks if a constant (like "true") may be replaced by its value */ int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int copy) @@ -39,7 +39,7 @@ int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int ALLOCA_FLAG(use_heap); if ((c = zend_hash_find_ptr(EG(zend_constants), name)) == NULL) { - lookup_name = DO_ALLOCA(ZSTR_LEN(name) + 1); + lookup_name = do_alloca(ZSTR_LEN(name) + 1, use_heap); memcpy(lookup_name, ZSTR_VAL(name), ZSTR_LEN(name) + 1); zend_str_tolower(lookup_name, ZSTR_LEN(name)); @@ -50,7 +50,7 @@ int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int } else { retval = 0; } - FREE_ALLOCA(lookup_name); + free_alloca(lookup_name, use_heap); } if (retval) { @@ -67,483 +67,10 @@ int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int 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_optimizer_ctx *ctx) -{ - zend_op *opline; - zend_op *end = op_array->opcodes + op_array->last; - zend_code_block *blocks, *cur_block; - uint32_t opno = 0; - - memset(cfg, 0, sizeof(zend_cfg)); - blocks = cfg->blocks = zend_arena_calloc(&ctx->arena, 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_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; - case ZEND_JMP: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: - START_BLOCK_OP(ZEND_OP1(opline).opline_num); - /* break missing intentionally */ - case ZEND_RETURN: - case ZEND_RETURN_BY_REF: - case ZEND_GENERATOR_RETURN: - 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_R: - case ZEND_FE_RESET_RW: - case ZEND_NEW: - case ZEND_JMP_SET: - case ZEND_COALESCE: - case ZEND_ASSERT_CHECK: - START_BLOCK_OP(ZEND_OP2(opline).opline_num); - START_BLOCK_OP(opno + 1); - break; - case ZEND_FE_FETCH_R: - case ZEND_FE_FETCH_RW: - START_BLOCK_OP(opline->extended_value); - START_BLOCK_OP(opno + 1); - break; - } - opno++; - opline++; - } - - /* first find block start points */ - if (op_array->last_try_catch) { - int i; - cfg->try = zend_arena_calloc(&ctx->arena, op_array->last_try_catch, sizeof(zend_code_block *)); - cfg->catch = zend_arena_calloc(&ctx->arena, 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_FE_FREE || - op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_ROPE_END || - op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_END_SILENCE)) { - 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_FE_FREE || - op_array->opcodes[op_array->brk_cont_array[i].brk].opcode != ZEND_ROPE_END || - op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_END_SILENCE)) { - parent = op_array->brk_cont_array[parent].parent; - } - op_array->brk_cont_array[i].parent = parent; - j++; - } - } - if (j) { - cfg->loop_start = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *)); - cfg->loop_cont = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *)); - cfg->loop_brk = zend_arena_calloc(&ctx->arena, 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_FE_FREE || - op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_ROPE_END || - op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_END_SILENCE)) { - 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: - case ZEND_RETURN_BY_REF: - case ZEND_GENERATOR_RETURN: - case ZEND_EXIT: - case ZEND_THROW: - break; - 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; - case ZEND_JMP: - cur_block->op1_to = &blocks[ZEND_OP1(opline).opline_num]; - break; - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: - cur_block->op1_to = &blocks[ZEND_OP1(opline).opline_num]; - cur_block->follow_to = &blocks[opno]; - 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_FE_FETCH_R: - case ZEND_FE_FETCH_RW: - 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_R: - case ZEND_FE_RESET_RW: - case ZEND_NEW: - case ZEND_JMP_SET: - case ZEND_COALESCE: - case ZEND_ASSERT_CHECK: - 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 = zend_arena_alloc(&ctx->arena, sizeof(zend_block_source)); \ - __t->next = tob->sources; \ - tob->sources = __t; \ - __t->from = fromb; \ - } \ -} - -#define DEL_SOURCE(cs) do { \ - *(cs) = (*(cs))->next; \ - } while (0) - - -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; - } - - if (from == to) { - 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->protected && 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; - 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, zend_optimizer_ctx *ctx) -{ - 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, zend_optimizer_ctx *ctx) -{ - if (block->access) { - return; - } - - block->access = 1; - if (block->op1_to) { - zend_access_path(block->op1_to, ctx); - ADD_SOURCE(block, block->op1_to); - } - if (block->op2_to) { - zend_access_path(block->op2_to, ctx); - ADD_SOURCE(block, block->op2_to); - } - if (block->ext_to) { - zend_access_path(block->ext_to, ctx); - ADD_SOURCE(block, block->ext_to); - } - if (block->follow_to) { - zend_access_path(block->follow_to, ctx); - 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_optimizer_ctx *ctx) -{ - 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) { - if (!start && b->access) { - start = b; - } - b->access = 0; - b->sources = NULL; - b = b->next; - } - - /* Walk thorough all paths */ - zend_access_path(start, ctx); - - /* 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], ctx); - zend_access_path(cfg->loop_cont[i], ctx); - zend_access_path(cfg->loop_brk[i], ctx); - } - } - - /* 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], ctx); - } - } - } -} +#define DEL_SOURCE(from, to) +#define ADD_SOURCE(from, to) /* Data dependencies macros */ @@ -552,8 +79,6 @@ static void zend_rebuild_access_path(zend_cfg *cfg, zend_op_array *op_array, int #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) - #define convert_to_string_safe(v) \ if (Z_TYPE_P((v)) == IS_NULL) { \ ZVAL_STRINGL((v), "", 0); \ @@ -561,183 +86,205 @@ static void zend_rebuild_access_path(zend_cfg *cfg, zend_op_array *op_array, int convert_to_string((v)); \ } -static int is_predecessor_smart_branch(zend_op *start, zend_op *predecessor) { - do { - if (predecessor == start) { - return 0; +static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b) +{ + zend_op *opcodes = op_array->opcodes; + + while (b->len > 0 && opcodes[b->start].opcode == ZEND_NOP) { + /* check if NOP breaks incorrect smart branch */ + if (b->len == 2 + && (op_array->opcodes[b->start + 1].opcode == ZEND_JMPZ + || op_array->opcodes[b->start + 1].opcode == ZEND_JMPNZ) + && (op_array->opcodes[b->start + 1].op1_type & (IS_CV|IS_CONST)) + && b->start > 0 + && zend_is_smart_branch(op_array->opcodes + b->start - 1)) { + break; } - predecessor--; - } while (predecessor->opcode == ZEND_NOP); - - return zend_is_smart_branch(predecessor); + b->start++; + b->len--; + } } -static void strip_nop(zend_code_block *block, zend_op_array *op_array, zend_optimizer_ctx *ctx) +static void strip_nops(zend_op_array *op_array, zend_basic_block *b) { - zend_op *opline = block->start_opline; - zend_op *end, *new_end; + uint32_t i, j; - /* 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, ctx); - } - return; - } - if (block->len == 2 - && ((block->start_opline + 1)->opcode == ZEND_JMPZ - || (block->start_opline + 1)->opcode == ZEND_JMPNZ) - && (block->start_opline + 1)->op1_type & (IS_CV|IS_CONST) - && block->start_opline > op_array->opcodes - && zend_is_smart_branch(block->start_opline - 1)) { - break; - } - block->start_opline++; - block->start_opline_no++; - block->len--; + strip_leading_nops(op_array, b); + if (b->len == 0) { + return; } /* strip the inside NOPs */ - opline = new_end = block->start_opline; - end = opline + block->len; - - while (opline < end) { - zend_op *src; - int len = 0; - - src = opline; - while (opline < end && opline->opcode == ZEND_NOP) { - if (opline + 1 < end - && ((opline + 1)->opcode == ZEND_JMPZ - || (opline + 1)->opcode == ZEND_JMPNZ) - && (opline + 1)->op1_type & (IS_CV|IS_CONST) - && is_predecessor_smart_branch(op_array->opcodes, opline)) { - /* don't remove NOP, that splits incorrect smart branch */ - opline++; - break; + i = j = b->start + 1; + while (i < b->start + b->len) { + if (op_array->opcodes[i].opcode != ZEND_NOP) { + if (i != j) { + op_array->opcodes[j] = op_array->opcodes[i]; } - src++; - opline++; + j++; } - - while (opline < end && opline->opcode != ZEND_NOP) { - opline++; + if (i + 1 < b->start + b->len + && (op_array->opcodes[i+1].opcode == ZEND_JMPZ + || op_array->opcodes[i+1].opcode == ZEND_JMPNZ) + && op_array->opcodes[i+1].op1_type & (IS_CV|IS_CONST) + && zend_is_smart_branch(op_array->opcodes + j - 1)) { + /* don't remove NOP, that splits incorrect smart branch */ + j++; } - len = opline - src; - - /* move up non-NOP opcodes */ - memmove(new_end, src, len*sizeof(zend_op)); - - new_end += len; + i++; + } + b->len = j - b->start; + while (j < i) { + MAKE_NOP(op_array->opcodes + j); + j++; } - block->len = new_end - block->start_opline; } -static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_optimizer_ctx *ctx) +static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_op **Tsource) { - zend_op *opline = block->start_opline; + zend_op *opline, *src; zend_op *end, *last_op = NULL; - zend_op **Tsource = cfg->Tsource; - - 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, ctx); - } - if (block->len == 2 - && ((block->start_opline + 1)->opcode == ZEND_JMPZ - || (block->start_opline + 1)->opcode == ZEND_JMPNZ) - && (block->start_opline + 1)->op1_type & (IS_CV|IS_CONST) - && block->start_opline > op_array->opcodes - && zend_is_smart_branch(block->start_opline - 1)) { - break; - } - return; - } - block->start_opline++; - block->start_opline_no++; - block->len--; - } + strip_leading_nops(op_array, block); - /* we track data dependencies only insight a single basic block */ - memset(Tsource, 0, (op_array->last_var + op_array->T) * sizeof(zend_op *)); - opline = block->start_opline; + opline = op_array->opcodes + block->start; end = opline + block->len; - while ((op_array->T) && (opline < end)) { - /* strip X = QM_ASSIGN(const) */ - if ((ZEND_OP1_TYPE(opline) & (IS_TMP_VAR|IS_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_LIST && - (opline->opcode != ZEND_FE_RESET_R || opline->opcode != ZEND_FE_RESET_RW) && - opline->opcode != ZEND_FREE + while (opline < end) { + /* Constant Propagation: strip X = QM_ASSIGN(const) */ + if ((opline->op1_type & (IS_TMP_VAR|IS_VAR)) && + opline->opcode != ZEND_FREE) { + src = VAR_SOURCE(opline->op1); + if (src && + src->opcode == ZEND_QM_ASSIGN && + src->op1_type == IS_CONST ) { - znode_op op1 = opline->op1; - zend_op *src = VAR_SOURCE(op1); - zval c = ZEND_OP1_LITERAL(src); - zval_copy_ctor(&c); - if (zend_optimizer_update_op1_const(op_array, opline, &c)) { - VAR_SOURCE(op1) = NULL; - literal_dtor(&ZEND_OP1_LITERAL(src)); - MAKE_NOP(src); + znode_op op1 = opline->op1; + if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) { + COPY_NODE(opline->result, opline->op1); + COPY_NODE(opline->op1, src->op1); + VAR_SOURCE(op1) = NULL; + MAKE_NOP(src); + } else if (opline->opcode != ZEND_FETCH_LIST && opline->opcode != ZEND_CASE) { + zval c = ZEND_OP1_LITERAL(src); + zval_copy_ctor(&c); + if (zend_optimizer_update_op1_const(op_array, opline, &c)) { + zend_optimizer_remove_live_range(op_array, op1.var); + VAR_SOURCE(op1) = NULL; + 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|IS_VAR)) && - VAR_SOURCE(opline->op2) && - VAR_SOURCE(opline->op2)->opcode == ZEND_QM_ASSIGN && - ZEND_OP1_TYPE(VAR_SOURCE(opline->op2)) == IS_CONST) { - znode_op op2 = opline->op2; - zend_op *src = VAR_SOURCE(op2); - zval c = ZEND_OP1_LITERAL(src); - zval_copy_ctor(&c); - if (zend_optimizer_update_op2_const(op_array, opline, &c)) { - VAR_SOURCE(op2) = NULL; - literal_dtor(&ZEND_OP1_LITERAL(src)); - MAKE_NOP(src); - } - } + /* Constant Propagation: strip X = QM_ASSIGN(const) */ + if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) { + src = VAR_SOURCE(opline->op2); + if (src && + src->opcode == ZEND_QM_ASSIGN && + src->op1_type == IS_CONST) { - /* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */ - if (opline->opcode == ZEND_ECHO && - ZEND_OP1_TYPE(opline) & (IS_TMP_VAR|IS_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); - } + znode_op op2 = opline->op2; + zval c = ZEND_OP1_LITERAL(src); -#if 0 - /* This pattern is unnecessary for PHP7, - * since compiler won't generate ZEND_FREE for ZEND_BOOL anymore */ - /* 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) { + zval_copy_ctor(&c); + if (zend_optimizer_update_op2_const(op_array, opline, &c)) { + zend_optimizer_remove_live_range(op_array, op2.var); + VAR_SOURCE(op2) = NULL; literal_dtor(&ZEND_OP1_LITERAL(src)); - } else if (ZEND_OP1_TYPE(src) == IS_TMP_VAR) { - src->opcode = ZEND_FREE; - } else { MAKE_NOP(src); } - MAKE_NOP(opline); } } + if (opline->opcode == ZEND_ECHO) { + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + src = VAR_SOURCE(opline->op1); + if (src && + src->opcode == ZEND_CAST && + src->extended_value == IS_STRING) { + /* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */ + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + } + } + + if (opline->op1_type == IS_CONST) { + if (last_op && last_op->opcode == ZEND_ECHO && + last_op->op1_type == IS_CONST && + Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_DOUBLE && + 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, old_len; + + 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)); + } + old_len = Z_STRLEN(ZEND_OP1_LITERAL(last_op)); + l = old_len + Z_STRLEN(ZEND_OP1_LITERAL(opline)); + if (!Z_REFCOUNTED(ZEND_OP1_LITERAL(last_op))) { + zend_string *tmp = zend_string_alloc(l, 0); + memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP1_LITERAL(last_op)), old_len); + Z_STR(ZEND_OP1_LITERAL(last_op)) = tmp; + } else { + Z_STR(ZEND_OP1_LITERAL(last_op)) = zend_string_extend(Z_STR(ZEND_OP1_LITERAL(last_op)), l, 0); + } + Z_TYPE_INFO(ZEND_OP1_LITERAL(last_op)) = IS_STRING_EX; + memcpy(Z_STRVAL(ZEND_OP1_LITERAL(last_op)) + old_len, Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline))); + Z_STRVAL(ZEND_OP1_LITERAL(last_op))[l] = '\0'; + zval_dtor(&ZEND_OP1_LITERAL(opline)); + ZVAL_STR(&ZEND_OP1_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP1_LITERAL(last_op)))); + ZVAL_NULL(&ZEND_OP1_LITERAL(last_op)); + MAKE_NOP(last_op); + } + last_op = opline; + } else { + last_op = NULL; + } + } else { + last_op = NULL; + } + + switch (opline->opcode) { + + case ZEND_FREE: + if (opline->op1_type == IS_TMP_VAR) { + src = VAR_SOURCE(opline->op1); + if (src && + (src->opcode == ZEND_BOOL || src->opcode == ZEND_BOOL_NOT)) { + /* T = BOOL(X), FREE(T) => T = BOOL(X) */ + /* The remaining BOOL is removed by a separate optimization */ + VAR_SOURCE(opline->op1) = NULL; + MAKE_NOP(opline); + } + } else if (opline->op1_type == IS_VAR) { + src = VAR_SOURCE(opline->op1); + /* V = OP, FREE(V) => OP. NOP */ + if (src && + src->opcode != ZEND_FETCH_R && + src->opcode != ZEND_FETCH_STATIC_PROP_R && + src->opcode != ZEND_FETCH_DIM_R && + src->opcode != ZEND_FETCH_OBJ_R && + src->opcode != ZEND_NEW) { + if (opline->extended_value & ZEND_FREE_ON_RETURN) { + /* mark as removed (empty live range) */ + op_array->live_range[opline->op2.num].var = (uint32_t)-1; + } + ZEND_RESULT_TYPE(src) = IS_UNUSED; + MAKE_NOP(opline); + } + } + break; + +#if 0 /* pre-evaluate functions: constant(x) defined(x) @@ -800,475 +347,657 @@ static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, } #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) - * CASE(TRUE, X) => BOOL(X) - * CASE(FALSE, X) => BOOL_NOT(X) - */ - if (opline->opcode == ZEND_IS_EQUAL || - opline->opcode == ZEND_IS_NOT_EQUAL || - /* CASE variable will be deleted later by FREE, so we can't optimize it */ - (opline->opcode == ZEND_CASE && (ZEND_OP1_TYPE(opline) & (IS_CONST|IS_CV)))) { - if (ZEND_OP1_TYPE(opline) == IS_CONST && - (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE || - Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_TRUE)) { - /* T = IS_EQUAL(TRUE, X) => T = BOOL(X) */ - /* T = IS_EQUAL(FALSE, X) => T = BOOL_NOT(X) */ - /* T = IS_NOT_EQUAL(TRUE, X) => T = BOOL_NOT(X) */ - /* T = IS_NOT_EQUAL(FALSE, X) => T = BOOL(X) */ - /* Optimization of comparison with "null" is not safe, - * because ("0" == null) is not equal to !("0") - */ - opline->opcode = - ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP1_LITERAL(opline))) == IS_TRUE)) ? - ZEND_BOOL : ZEND_BOOL_NOT; - COPY_NODE(opline->op1, opline->op2); - SET_UNUSED(opline->op2); - } else if (ZEND_OP2_TYPE(opline) == IS_CONST && - (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE || - Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) { - /* T = IS_EQUAL(X, TRUE) => T = BOOL(X) */ - /* T = IS_EQUAL(X, FALSE) => T = BOOL_NOT(X) */ - /* T = IS_NOT_EQUAL(X, TRUE) => T = BOOL_NOT(X) */ - /* T = IS_NOT_EQUAL(X, FALSE) => T = BOOL(X) */ - /* Optimization of comparison with "null" is not safe, - * because ("0" == null) is not equal to !("0") - */ - opline->opcode = - ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ? - ZEND_BOOL : ZEND_BOOL_NOT; - SET_UNUSED(opline->op2); - } - } + case ZEND_FETCH_LIST: + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + /* LIST variable will be deleted later by FREE */ + Tsource[VAR_NUM(opline->op1.var)] = NULL; + } + break; - 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 && - !zend_bitset_in(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; + case ZEND_CASE: + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + /* CASE variable will be deleted later by FREE, so we can't optimize it */ + Tsource[VAR_NUM(opline->op1.var)] = NULL; 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; + } + if (opline->op1_type == IS_CONST && + opline->op2_type == IS_CONST) { 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 missing intentionally */ + + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + if (opline->op1_type == IS_CONST && + opline->op2_type == IS_CONST) { + goto optimize_constant_binary_op; + } + /* IS_EQ(TRUE, X) => BOOL(X) + * IS_EQ(FALSE, X) => BOOL_NOT(X) + * IS_NOT_EQ(TRUE, X) => BOOL_NOT(X) + * IS_NOT_EQ(FALSE, X) => BOOL(X) + * CASE(TRUE, X) => BOOL(X) + * CASE(FALSE, X) => BOOL_NOT(X) + */ + if (opline->op1_type == IS_CONST && + (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE || + Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_TRUE)) { + /* Optimization of comparison with "null" is not safe, + * because ("0" == null) is not equal to !("0") + */ + opline->opcode = + ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP1_LITERAL(opline))) == IS_TRUE)) ? + ZEND_BOOL : ZEND_BOOL_NOT; + COPY_NODE(opline->op1, opline->op2); + SET_UNUSED(opline->op2); + goto optimize_bool; + } else if (opline->op2_type == IS_CONST && + (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE || + Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) { + /* Optimization of comparison with "null" is not safe, + * because ("0" == null) is not equal to !("0") + */ + opline->opcode = + ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ? + ZEND_BOOL : ZEND_BOOL_NOT; + SET_UNUSED(opline->op2); + goto optimize_bool; } break; - } - VAR_UNSET(opline->op1); - MAKE_NOP(src); - continue; - } else + case ZEND_BOOL: + case ZEND_BOOL_NOT: + optimize_bool: + if (opline->op1_type == IS_CONST) { + goto optimize_const_unary_op; + } + if (opline->op1_type == IS_TMP_VAR && + !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) { + src = VAR_SOURCE(opline->op1); + if (src) { + switch (src->opcode) { + case ZEND_BOOL_NOT: + /* T = BOOL_NOT(X) + BOOL(T) -> NOP, BOOL_NOT(X) */ + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + opline->opcode = (opline->opcode == ZEND_BOOL) ? ZEND_BOOL_NOT : ZEND_BOOL; + MAKE_NOP(src); + goto optimize_bool; + case ZEND_BOOL: + /* T = BOOL(X) + BOOL(T) -> NOP, BOOL(X) */ + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + goto optimize_bool; + case ZEND_IS_EQUAL: + if (opline->opcode == ZEND_BOOL_NOT) { + src->opcode = ZEND_IS_NOT_EQUAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + case ZEND_IS_NOT_EQUAL: + if (opline->opcode == ZEND_BOOL_NOT) { + src->opcode = ZEND_IS_EQUAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + case ZEND_IS_IDENTICAL: + if (opline->opcode == ZEND_BOOL_NOT) { + src->opcode = ZEND_IS_NOT_IDENTICAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + case ZEND_IS_NOT_IDENTICAL: + if (opline->opcode == ZEND_BOOL_NOT) { + src->opcode = ZEND_IS_IDENTICAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + case ZEND_IS_SMALLER: + if (opline->opcode == ZEND_BOOL_NOT) { + zend_uchar tmp_type; + uint32_t tmp; + + src->opcode = ZEND_IS_SMALLER_OR_EQUAL; + tmp_type = src->op1_type; + src->op1_type = src->op2_type; + src->op2_type = tmp_type; + tmp = src->op1.num; + src->op1.num = src->op2.num; + src->op2.num = tmp; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + case ZEND_IS_SMALLER_OR_EQUAL: + if (opline->opcode == ZEND_BOOL_NOT) { + zend_uchar tmp_type; + uint32_t tmp; + + src->opcode = ZEND_IS_SMALLER; + tmp_type = src->op1_type; + src->op1_type = src->op2_type; + src->op2_type = tmp_type; + tmp = src->op1.num; + src->op1.num = src->op2.num; + src->op2.num = tmp; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + case ZEND_ISSET_ISEMPTY_VAR: + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: + case ZEND_INSTANCEOF: + case ZEND_TYPE_CHECK: + case ZEND_DEFINED: + if (opline->opcode == ZEND_BOOL_NOT) { + break; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + break; + } + } + } + break; + + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_JMPZNZ: + optimize_jmpznz: + if (opline->op1_type == IS_TMP_VAR && + (!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var)) || + (opline->result_type == opline->op1_type && + opline->result.var == opline->op1.var))) { + src = VAR_SOURCE(opline->op1); + if (src) { + if (src->opcode == ZEND_BOOL_NOT && + opline->opcode != ZEND_JMPZ_EX && + opline->opcode != ZEND_JMPNZ_EX) { + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + if (opline->opcode == ZEND_JMPZ) { + /* T = BOOL_NOT(X) + JMPZ(T) -> NOP, JMPNZ(X) */ + opline->opcode = ZEND_JMPNZ; + } else if (opline->opcode == ZEND_JMPNZ) { + /* T = BOOL_NOT(X) + JMPNZ(T) -> NOP, JMPZ(X) */ + opline->opcode = ZEND_JMPZ; #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 + } else if (opline->opcode == ZEND_JMPZ_EX) { + /* T = BOOL_NOT(X) + JMPZ_EX(T) -> NOP, JMPNZ_EX(X) */ + opline->opcode = ZEND_JMPNZ_EX; + } else if (opline->opcode == ZEND_JMPNZ_EX) { + /* T = BOOL_NOT(X) + JMPNZ_EX(T) -> NOP, JMPZ_EX(X) */ + opline->opcode = ZEND_JMPZ; #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|IS_VAR)) && - VAR_SOURCE(opline->op1) != NULL && - (!zend_bitset_in(used_ext, VAR_NUM(ZEND_OP1(opline).var)) || - ((ZEND_RESULT_TYPE(opline) & (IS_TMP_VAR|IS_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, old_len; - - 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)); - } - old_len = Z_STRLEN(ZEND_OP1_LITERAL(last_op)); - l = old_len + Z_STRLEN(ZEND_OP1_LITERAL(opline)); - if (!Z_REFCOUNTED(ZEND_OP1_LITERAL(last_op))) { - zend_string *tmp = zend_string_alloc(l, 0); - memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP1_LITERAL(last_op)), old_len); - Z_STR(ZEND_OP1_LITERAL(last_op)) = tmp; - } else { - Z_STR(ZEND_OP1_LITERAL(last_op)) = zend_string_extend(Z_STR(ZEND_OP1_LITERAL(last_op)), l, 0); - } - Z_TYPE_INFO(ZEND_OP1_LITERAL(last_op)) = IS_STRING_EX; - memcpy(Z_STRVAL(ZEND_OP1_LITERAL(last_op)) + old_len, Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline))); - Z_STRVAL(ZEND_OP1_LITERAL(last_op))[l] = '\0'; - zval_dtor(&ZEND_OP1_LITERAL(opline)); - ZVAL_STR(&ZEND_OP1_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP1_LITERAL(last_op)))); - ZVAL_NULL(&ZEND_OP1_LITERAL(last_op)); - MAKE_NOP(last_op); - } 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_FAST_CONCAT) && - ZEND_OP2_TYPE(VAR_SOURCE(opline->op1)) == IS_CONST && - ZEND_RESULT(VAR_SOURCE(opline->op1)).var == ZEND_OP1(opline).var) { - /* compress consecutive CONCAT/ADD_STRING/ADD_CHARs */ - zend_op *src = VAR_SOURCE(opline->op1); - int l, old_len; - - 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)); - } + } else { + /* T = BOOL_NOT(X) + JMPZNZ(T,L1,L2) -> NOP, JMPZNZ(X,L2,L1) */ + uint32_t tmp; + + ZEND_ASSERT(opline->opcode == ZEND_JMPZNZ); + tmp = block->successors[0]; + block->successors[0] = block->successors[1]; + block->successors[1] = tmp; + } + MAKE_NOP(src); + goto optimize_jmpznz; + } else if (src->opcode == ZEND_BOOL || + src->opcode == ZEND_QM_ASSIGN) { + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + goto optimize_jmpznz; + } + } + } + break; - VAR_UNSET(opline->op1); - COPY_NODE(opline->op1, src->op1); - old_len = Z_STRLEN(ZEND_OP2_LITERAL(src)); - l = old_len + Z_STRLEN(ZEND_OP2_LITERAL(opline)); - if (!Z_REFCOUNTED(ZEND_OP2_LITERAL(src))) { - zend_string *tmp = zend_string_alloc(l, 0); - memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP2_LITERAL(src)), old_len); - Z_STR(ZEND_OP2_LITERAL(last_op)) = tmp; - } else { - Z_STR(ZEND_OP2_LITERAL(src)) = zend_string_extend(Z_STR(ZEND_OP2_LITERAL(src)), l, 0); - } - Z_TYPE_INFO(ZEND_OP2_LITERAL(last_op)) = IS_STRING_EX; - memcpy(Z_STRVAL(ZEND_OP2_LITERAL(src)) + old_len, Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline))); - Z_STRVAL(ZEND_OP2_LITERAL(src))[l] = '\0'; - zend_string_release(Z_STR(ZEND_OP2_LITERAL(opline))); - ZVAL_STR(&ZEND_OP2_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP2_LITERAL(src)))); - ZVAL_NULL(&ZEND_OP2_LITERAL(src)); - MAKE_NOP(src); - } 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_FAST_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 */ - binary_op_type binary_op = get_binary_op(opline->opcode); - zval result; - int er; - - if ((opline->opcode == ZEND_DIV || opline->opcode == ZEND_MOD) && - zval_get_long(&ZEND_OP2_LITERAL(opline)) == 0) { - if (RESULT_USED(opline)) { - SET_VAR_SOURCE(opline); + case ZEND_CONCAT: + case ZEND_FAST_CONCAT: + if (opline->op1_type == IS_CONST && + opline->op2_type == IS_CONST) { + goto optimize_constant_binary_op; } - opline++; - continue; - } else if ((opline->opcode == ZEND_SL || opline->opcode == ZEND_SR) && - zval_get_long(&ZEND_OP2_LITERAL(opline)) < 0) { - if (RESULT_USED(opline)) { - SET_VAR_SOURCE(opline); + + if (opline->op2_type == IS_CONST && + opline->op1_type == IS_TMP_VAR) { + + src = VAR_SOURCE(opline->op1); + if (src && + (src->opcode == ZEND_CONCAT || + src->opcode == ZEND_FAST_CONCAT) && + src->op2_type == IS_CONST) { + /* compress consecutive CONCATs */ + int l, old_len; + + if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) { + convert_to_string_safe(&ZEND_OP2_LITERAL(opline)); + } + if (Z_TYPE(ZEND_OP2_LITERAL(src)) != IS_STRING) { + convert_to_string_safe(&ZEND_OP2_LITERAL(src)); + } + + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + old_len = Z_STRLEN(ZEND_OP2_LITERAL(src)); + l = old_len + Z_STRLEN(ZEND_OP2_LITERAL(opline)); + if (!Z_REFCOUNTED(ZEND_OP2_LITERAL(src))) { + zend_string *tmp = zend_string_alloc(l, 0); + memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP2_LITERAL(src)), old_len); + Z_STR(ZEND_OP2_LITERAL(src)) = tmp; + } else { + Z_STR(ZEND_OP2_LITERAL(src)) = zend_string_extend(Z_STR(ZEND_OP2_LITERAL(src)), l, 0); + } + Z_TYPE_INFO(ZEND_OP2_LITERAL(src)) = IS_STRING_EX; + memcpy(Z_STRVAL(ZEND_OP2_LITERAL(src)) + old_len, Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline))); + Z_STRVAL(ZEND_OP2_LITERAL(src))[l] = '\0'; + zend_string_release(Z_STR(ZEND_OP2_LITERAL(opline))); + ZVAL_STR(&ZEND_OP2_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP2_LITERAL(src)))); + ZVAL_NULL(&ZEND_OP2_LITERAL(src)); + MAKE_NOP(src); + } } - opline++; - continue; - } - er = EG(error_reporting); - EG(error_reporting) = 0; - if (binary_op(&result, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) { - literal_dtor(&ZEND_OP1_LITERAL(opline)); - literal_dtor(&ZEND_OP2_LITERAL(opline)); - opline->opcode = ZEND_QM_ASSIGN; - SET_UNUSED(opline->op2); - zend_optimizer_update_op1_const(op_array, opline, &result); - } - 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) { - unary_op(&result, &ZEND_OP1_LITERAL(opline)); - literal_dtor(&ZEND_OP1_LITERAL(opline)); - } else { - /* BOOL */ - ZVAL_COPY_VALUE(&result, &ZEND_OP1_LITERAL(opline)); - convert_to_boolean(&result); - ZVAL_NULL(&ZEND_OP1_LITERAL(opline)); - } - opline->opcode = ZEND_QM_ASSIGN; - zend_optimizer_update_op1_const(op_array, opline, &result); - } else if ((opline->opcode == ZEND_RETURN || opline->opcode == ZEND_EXIT) && - (ZEND_OP1_TYPE(opline) & (IS_TMP_VAR|IS_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_CONCAT || opline->opcode == ZEND_FAST_CONCAT) { - if ((ZEND_OP1_TYPE(opline) & (IS_TMP_VAR|IS_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); - MAKE_NOP(src); - } - if ((ZEND_OP2_TYPE(opline) & (IS_TMP_VAR|IS_VAR)) && - VAR_SOURCE(opline->op2) && - VAR_SOURCE(opline->op2)->opcode == ZEND_CAST && - VAR_SOURCE(opline->op2)->extended_value == IS_STRING) { - /* convert T1 = CAST(STRING, X), T2 = CONCAT(Y, T1) to T2 = CONCAT(Y,X) */ - zend_op *src = VAR_SOURCE(opline->op2); - VAR_UNSET(opline->op2); - COPY_NODE(opline->op2, src->op1); - MAKE_NOP(src); - } - if (ZEND_OP1_TYPE(opline) == IS_CONST && - Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING && - Z_STRLEN(ZEND_OP1_LITERAL(opline)) == 0) { - /* convert CONCAT('', X) => CAST(STRING, X) */ - literal_dtor(&ZEND_OP1_LITERAL(opline)); - opline->opcode = ZEND_CAST; - opline->extended_value = IS_STRING; - COPY_NODE(opline->op1, opline->op2); - opline->op2_type = IS_UNUSED; - opline->op2.var = 0; - } else if (ZEND_OP2_TYPE(opline) == IS_CONST && + + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + src = VAR_SOURCE(opline->op1); + if (src && + src->opcode == ZEND_CAST && + src->extended_value == IS_STRING) { + /* convert T1 = CAST(STRING, X), T2 = CONCAT(T1, Y) to T2 = CONCAT(X,Y) */ + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + } + } + if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) { + src = VAR_SOURCE(opline->op2); + if (src && + src->opcode == ZEND_CAST && + src->extended_value == IS_STRING) { + /* convert T1 = CAST(STRING, X), T2 = CONCAT(Y, T1) to T2 = CONCAT(Y,X) */ + zend_op *src = VAR_SOURCE(opline->op2); + VAR_SOURCE(opline->op2) = NULL; + COPY_NODE(opline->op2, src->op1); + MAKE_NOP(src); + } + } + if (opline->op1_type == IS_CONST && + Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING && + Z_STRLEN(ZEND_OP1_LITERAL(opline)) == 0) { + /* convert CONCAT('', X) => CAST(STRING, X) */ + literal_dtor(&ZEND_OP1_LITERAL(opline)); + opline->opcode = ZEND_CAST; + opline->extended_value = IS_STRING; + COPY_NODE(opline->op1, opline->op2); + opline->op2_type = IS_UNUSED; + opline->op2.var = 0; + } else if (opline->op2_type == IS_CONST && Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING && Z_STRLEN(ZEND_OP2_LITERAL(opline)) == 0) { - /* convert CONCAT(X, '') => CAST(STRING, X) */ - literal_dtor(&ZEND_OP2_LITERAL(opline)); - opline->opcode = ZEND_CAST; - opline->extended_value = IS_STRING; - opline->op2_type = IS_UNUSED; - opline->op2.var = 0; - } else if (opline->opcode == ZEND_CONCAT && - (opline->op1_type == IS_CONST || - (opline->op1_type == IS_TMP_VAR && - VAR_SOURCE(opline->op1) && - (VAR_SOURCE(opline->op1)->opcode == ZEND_FAST_CONCAT || - VAR_SOURCE(opline->op1)->opcode == ZEND_ROPE_END || - VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CONSTANT))) && - (opline->op2_type == IS_CONST || - (opline->op2_type == IS_TMP_VAR && - VAR_SOURCE(opline->op2) && - (VAR_SOURCE(opline->op2)->opcode == ZEND_FAST_CONCAT || - VAR_SOURCE(opline->op2)->opcode == ZEND_ROPE_END || - VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CONSTANT)))) { - opline->opcode = ZEND_FAST_CONCAT; - } - } else if (opline->opcode == ZEND_QM_ASSIGN && - ZEND_OP1_TYPE(opline) == ZEND_RESULT_TYPE(opline) && - 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) && - !zend_bitset_in(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); + /* convert CONCAT(X, '') => CAST(STRING, X) */ + literal_dtor(&ZEND_OP2_LITERAL(opline)); + opline->opcode = ZEND_CAST; + opline->extended_value = IS_STRING; + opline->op2_type = IS_UNUSED; + opline->op2.var = 0; + } else if (opline->opcode == ZEND_CONCAT && + (opline->op1_type == IS_CONST || + (opline->op1_type == IS_TMP_VAR && + VAR_SOURCE(opline->op1) && + (VAR_SOURCE(opline->op1)->opcode == ZEND_FAST_CONCAT || + VAR_SOURCE(opline->op1)->opcode == ZEND_ROPE_END || + VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CONSTANT || + VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CLASS_CONSTANT))) && + (opline->op2_type == IS_CONST || + (opline->op2_type == IS_TMP_VAR && + VAR_SOURCE(opline->op2) && + (VAR_SOURCE(opline->op2)->opcode == ZEND_FAST_CONCAT || + VAR_SOURCE(opline->op2)->opcode == ZEND_ROPE_END || + VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CONSTANT || + VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CLASS_CONSTANT)))) { + opline->opcode = ZEND_FAST_CONCAT; + } + break; + + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + case ZEND_DIV: + case ZEND_MOD: + case ZEND_SL: + case ZEND_SR: + case ZEND_IS_SMALLER: + case ZEND_IS_SMALLER_OR_EQUAL: + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + case ZEND_BOOL_XOR: + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + if (opline->op1_type == IS_CONST && + opline->op2_type == IS_CONST) { + /* evaluate constant expressions */ + binary_op_type binary_op; + zval result; + int er; + +optimize_constant_binary_op: + binary_op = get_binary_op(opline->opcode); + if ((opline->opcode == ZEND_DIV || opline->opcode == ZEND_MOD) && + zval_get_long(&ZEND_OP2_LITERAL(opline)) == 0) { + SET_VAR_SOURCE(opline); + opline++; + continue; + } else if ((opline->opcode == ZEND_SL || opline->opcode == ZEND_SR) && + zval_get_long(&ZEND_OP2_LITERAL(opline)) < 0) { + SET_VAR_SOURCE(opline); + opline++; + continue; + } else if (zend_binary_op_produces_numeric_string_error(opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(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)) == SUCCESS) { + literal_dtor(&ZEND_OP1_LITERAL(opline)); + literal_dtor(&ZEND_OP2_LITERAL(opline)); + opline->opcode = ZEND_QM_ASSIGN; + SET_UNUSED(opline->op2); + zend_optimizer_update_op1_const(op_array, opline, &result); + } + EG(error_reporting) = er; + } + break; + + case ZEND_BW_NOT: + if (opline->op1_type == IS_CONST) { + /* evaluate constant unary ops */ + unary_op_type unary_op; + zval result; + +optimize_const_unary_op: + unary_op = get_unary_op(opline->opcode); + if (unary_op) { + unary_op(&result, &ZEND_OP1_LITERAL(opline)); + literal_dtor(&ZEND_OP1_LITERAL(opline)); + } else { + /* BOOL */ + ZVAL_COPY_VALUE(&result, &ZEND_OP1_LITERAL(opline)); + convert_to_boolean(&result); + ZVAL_NULL(&ZEND_OP1_LITERAL(opline)); + } + opline->opcode = ZEND_QM_ASSIGN; + zend_optimizer_update_op1_const(op_array, opline, &result); + } + break; + + case ZEND_RETURN: + case ZEND_EXIT: + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + src = VAR_SOURCE(opline->op1); + if (src && src->opcode == ZEND_QM_ASSIGN) { + zend_op *op = src + 1; + zend_bool optimize = 1; + + while (op < opline) { + if ((op->op1_type == opline->op1_type + && op->op1.var == opline->op1.var) + || (op->op2_type == opline->op1_type + && op->op2.var == opline->op1.var)) { + optimize = 0; + break; + } + op++; + } + + if (optimize) { + /* T = QM_ASSIGN(X), RETURN(T) to NOP, RETURN(X) */ + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + MAKE_NOP(src); + } + } + } + break; + + case ZEND_QM_ASSIGN: + if (opline->op1_type == opline->result_type && + opline->op1.var == opline->result.var) { + /* strip T = QM_ASSIGN(T) */ + MAKE_NOP(opline); + } + break; } + /* get variable source */ - if (RESULT_USED(opline)) { + if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { SET_VAR_SOURCE(opline); } - if (opline->opcode != ZEND_NOP) { - last_op = opline; - } opline++; } - strip_nop(block, op_array, ctx); + strip_nops(op_array, block); } /* 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; + zend_basic_block *blocks = cfg->blocks; + zend_basic_block *end = blocks + cfg->blocks_count; + zend_basic_block *b; + zend_op *new_opcodes; + zend_op *opline; + uint32_t len = 0; + int n; - /* 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; + for (b = blocks; b < end; b++) { + if (b->len == 0) { + continue; + } + if (b->flags & ZEND_BB_REACHABLE) { + opline = op_array->opcodes + b->start + b->len - 1; + if (opline->opcode == ZEND_JMP) { + zend_basic_block *next = b + 1; + + while (next < end && !(next->flags & ZEND_BB_REACHABLE)) { + next++; } - if (next && next == cur_block->op1_to) { + if (next < end && next == blocks + b->successors[0]) { /* 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--; + MAKE_NOP(opline); + b->len--; } + } else if (b->len == 1 && opline->opcode == ZEND_NOP) { + /* skip empty block */ + b->len--; } + len += b->len; } else { /* this block will not be used, delete all constants there */ - zend_op *_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)); + zend_op *op = op_array->opcodes + b->start; + zend_op *end = op + b->len; + for (; op < end; op++) { + if (ZEND_OP1_TYPE(op) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(op)); } - if (ZEND_OP2_TYPE(_opl) == IS_CONST) { - literal_dtor(&ZEND_OP2_LITERAL(_opl)); + if (ZEND_OP2_TYPE(op) == IS_CONST) { + literal_dtor(&ZEND_OP2_LITERAL(op)); } } } - cur_block = cur_block->next; } - op_array->last = opline-new_opcodes; + new_opcodes = emalloc(len * sizeof(zend_op)); + opline = new_opcodes; + + /* Copy code of reachable blocks into a single buffer */ + for (b = blocks; b < end; b++) { + if (b->flags & ZEND_BB_REACHABLE) { + memcpy(opline, op_array->opcodes + b->start, b->len * sizeof(zend_op)); + b->start = opline - new_opcodes; + opline += b->len; + } + } + + /* adjust jump targets */ + efree(op_array->opcodes); + op_array->opcodes = new_opcodes; + op_array->last = len; + + for (b = blocks; b < end; b++) { + if (!(b->flags & ZEND_BB_REACHABLE) || b->len == 0) { + continue; + } + opline = op_array->opcodes + b->start + b->len - 1; + switch (opline->opcode) { + case ZEND_FAST_CALL: + case ZEND_JMP: + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, new_opcodes + blocks[b->successors[0]].start); + break; + case ZEND_JMPZNZ: + opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[1]].start); + /* break missing intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start); + break; + case ZEND_CATCH: + if (!opline->result.var) { + opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[0]].start); + } + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[0]].start); + break; + } + } - /* adjust exception jump targets */ + /* adjust exception jump targets & remove unused try_catch_array entries */ if (op_array->last_try_catch) { int i, j; + uint32_t *map; + ALLOCA_FLAG(use_heap); + + map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_try_catch, use_heap); for (i = 0, j = 0; i< op_array->last_try_catch; i++) { - if (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; + if (blocks[cfg->map[op_array->try_catch_array[i].try_op]].flags & ZEND_BB_REACHABLE) { + map[i] = j; + op_array->try_catch_array[j].try_op = blocks[cfg->map[op_array->try_catch_array[i].try_op]].start; + if (op_array->try_catch_array[i].catch_op) { + op_array->try_catch_array[j].catch_op = blocks[cfg->map[op_array->try_catch_array[i].catch_op]].start; + } else { + op_array->try_catch_array[j].catch_op = 0; + } + if (op_array->try_catch_array[i].finally_op) { + op_array->try_catch_array[j].finally_op = blocks[cfg->map[op_array->try_catch_array[i].finally_op]].start; + } else { + op_array->try_catch_array[j].finally_op = 0; + } + if (!op_array->try_catch_array[i].finally_end) { + op_array->try_catch_array[j].finally_end = 0; + } else { + op_array->try_catch_array[j].finally_end = blocks[cfg->map[op_array->try_catch_array[i].finally_end]].start; + } j++; } } - op_array->last_try_catch = j; - } - - /* 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; + if (i != j) { + op_array->last_try_catch = j; + if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { + zend_op *opline = new_opcodes; + zend_op *end = opline + len; + while (opline < end) { + if (opline->opcode == ZEND_FAST_RET && + opline->op2.num != (uint32_t)-1 && + opline->op2.num < (uint32_t)j) { + opline->op2.num = map[opline->op2.num]; + } + opline++; + } + } } + free_alloca(map, use_heap); } - /* adjust 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; + /* adjust loop jump targets & remove unused live range entries */ + if (op_array->last_live_range) { + int i, j; + uint32_t *map; + ALLOCA_FLAG(use_heap); + + map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_live_range, use_heap); + + for (i = 0, j = 0; i < op_array->last_live_range; i++) { + if (op_array->live_range[i].var == (uint32_t)-1) { + /* this live range already removed */ + continue; + } + if (!(blocks[cfg->map[op_array->live_range[i].start]].flags & ZEND_BB_REACHABLE)) { + ZEND_ASSERT(!(blocks[cfg->map[op_array->live_range[i].end]].flags & ZEND_BB_REACHABLE)); + } else { + uint32_t start_op = blocks[cfg->map[op_array->live_range[i].start]].start; + uint32_t end_op = blocks[cfg->map[op_array->live_range[i].end]].start; + + if (start_op == end_op) { + /* skip empty live range */ + continue; + } + op_array->live_range[i].start = start_op; + op_array->live_range[i].end = end_op; + map[i] = j; + if (i != j) { + op_array->live_range[j] = op_array->live_range[i]; + } + j++; + } } - if (cur_block->ext_to) { - opline->extended_value = cur_block->ext_to->start_opline - new_opcodes; + + if (i != j) { + if ((op_array->last_live_range = j)) { + zend_op *opline = new_opcodes; + zend_op *end = opline + len; + while (opline != end) { + if ((opline->opcode == ZEND_FREE || opline->opcode == ZEND_FE_FREE) && + opline->extended_value == ZEND_FREE_ON_RETURN) { + ZEND_ASSERT(opline->op2.num < (uint32_t) i); + opline->op2.num = map[opline->op2.num]; + } + opline++; + } + } else { + efree(op_array->live_range); + op_array->live_range = NULL; + } } - print_block(cur_block, new_opcodes, "Out "); + free_alloca(map, use_heap); } - efree(op_array->opcodes); - op_array->opcodes = erealloc(new_opcodes, op_array->last * sizeof(zend_op)); /* adjust early binding list */ if (op_array->early_binding != (uint32_t)-1) { @@ -1286,45 +1015,56 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) } *opline_num = -1; } + + /* rebuild map (just for printing) */ + memset(cfg->map, -1, sizeof(int) * op_array->last); + for (n = 0; n < cfg->blocks_count; n++) { + if (cfg->blocks[n].flags & ZEND_BB_REACHABLE) { + cfg->map[cfg->blocks[n].start] = n; + } + } } -static void zend_jmp_optimization(zend_code_block *block, zend_op_array *op_array, zend_code_block *blocks, zend_cfg *cfg, zend_optimizer_ctx *ctx) +static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_array, zend_cfg *cfg, zend_uchar *same_t) { /* last_op is the last opcode of the current block */ - zend_op *last_op = (block->start_opline + block->len - 1); + zend_basic_block *blocks = cfg->blocks; + zend_op *last_op; - if (!block->len) { + if (block->len == 0) { return; } + + last_op = op_array->opcodes + block->start + block->len - 1; switch (last_op->opcode) { case ZEND_JMP: { - zend_op *target = block->op1_to->start_opline; - zend_code_block *next = block->next; + zend_basic_block *target_block = blocks + block->successors[0]; + zend_op *target = op_array->opcodes + target_block->start; + int next = (block - blocks) + 1; - while (next && !next->access) { + while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) { /* find used one */ - next = next->next; + next++; } /* JMP(next) -> NOP */ - if (block->op1_to == next) { - block->follow_to = block->op1_to; - block->op1_to = NULL; + if (block->successors[0] == next) { MAKE_NOP(last_op); block->len--; - if (block->len == 0) { - /* this block is nothing but NOP now */ - delete_code_block(block, ctx); - } break; } - if (((target->opcode == ZEND_JMP && - block->op1_to != block->op1_to->op1_to) || - target->opcode == ZEND_JMPZNZ) && - !block->op1_to->protected) { + if (target->opcode == ZEND_JMP && + block->successors[0] != target_block->successors[0] && + !(target_block->flags & ZEND_BB_PROTECTED)) { /* JMP L, L: JMP L1 -> JMP L1 */ + *last_op = *target; + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); + } else if (target->opcode == ZEND_JMPZNZ && + !(target_block->flags & ZEND_BB_PROTECTED)) { /* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */ *last_op = *target; if (ZEND_OP1_TYPE(last_op) == IS_CONST) { @@ -1332,25 +1072,15 @@ static void zend_jmp_optimization(zend_code_block *block, zend_op_array *op_arra zval_copy_ctor(&zv); last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv); } - 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 || - target->opcode == ZEND_RETURN_BY_REF || - target->opcode == ZEND_FAST_RET || - target->opcode == ZEND_EXIT) { + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + block->successors[1] = target_block->successors[1]; + ADD_SOURCE(block, block->successors[0]); + ADD_SOURCE(block, block->successors[1]); + } else if ((target->opcode == ZEND_RETURN || + target->opcode == ZEND_RETURN_BY_REF || + target->opcode == ZEND_EXIT) && + !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) { /* JMP L, L: RETURN to immediate RETURN */ *last_op = *target; if (ZEND_OP1_TYPE(last_op) == IS_CONST) { @@ -1358,8 +1088,8 @@ static void zend_jmp_optimization(zend_code_block *block, zend_op_array *op_arra zval_copy_ctor(&zv); last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv); } - del_source(block, block->op1_to); - block->op1_to = NULL; + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = -1; #if 0 /* Temporarily disabled - see bug #0025274 */ } else if (0&& block->op1_to != block && @@ -1392,7 +1122,7 @@ static void zend_jmp_optimization(zend_code_block *block, zend_op_array *op_arra next = next->follow_to; } if (can_reorder) { - zend_code_block *prev = blocks; + zend_basic_block *prev = blocks; while (prev->next != block->op1_to) { prev = prev->next; @@ -1431,161 +1161,138 @@ static void zend_jmp_optimization(zend_code_block *block, zend_op_array *op_arra 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; + DEL_SOURCE(block, block->successors[1]); + block->successors[1] = -1; } else { /* JMPNZ(false) -> NOP */ MAKE_NOP(last_op); - del_source(block, block->op2_to); - block->op2_to = NULL; + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = block->successors[1]; + block->successors[1] = -1; } break; } - if (block->op2_to == block->follow_to) { - /* L: JMPZ(X, L+1) -> NOP or FREE(X) */ - - if (last_op->op1_type == IS_VAR) { - zend_op **Tsource = cfg->Tsource; - zend_op *src = VAR_SOURCE(last_op->op1); + if (block->successors[0] == block->successors[1]) { + /* L: JMP[N]Z(X, L+1) -> NOP or FREE(X) */ - if (src && - src->opcode != ZEND_FETCH_R && - src->opcode != ZEND_FETCH_DIM_R && - src->opcode != ZEND_FETCH_OBJ_R) { - ZEND_RESULT_TYPE(src) |= EXT_TYPE_UNUSED; - MAKE_NOP(last_op); - block->op2_to = NULL; - break; - } - } if (last_op->op1_type == IS_CV) { - break; + last_op->opcode = ZEND_CHECK_VAR; + last_op->op2.num = 0; } else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) { last_op->opcode = ZEND_FREE; last_op->op2.num = 0; - block->op2_to = NULL; } else { MAKE_NOP(last_op); - block->op2_to = NULL; } + block->successors[1] = -1; break; } - if (block->op2_to) { + if (1) { zend_uchar same_type = ZEND_OP1_TYPE(last_op); uint32_t same_var = VAR_NUM_EX(last_op->op1); zend_op *target; zend_op *target_end; - zend_code_block *target_block = block->op2_to;; + zend_basic_block *target_block = blocks + block->successors[0]; next_target: - target = target_block->start_opline; - target_end = target_block->start_opline + target_block->len; + target = op_array->opcodes + target_block->start; + target_end = target + target_block->len; while (target < target_end && target->opcode == ZEND_NOP) { target++; } /* next block is only NOP's */ if (target == target_end) { - target_block = target_block->follow_to; + target_block = blocks + target_block->successors[0]; 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 + !(target_block->flags & ZEND_BB_PROTECTED) ) { - del_source(block, block->op2_to); - block->op2_to = target_block->follow_to; - ADD_SOURCE(block, block->op2_to); + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[1]; + ADD_SOURCE(block, block->successors[0]); } 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) { + !(target_block->flags & ZEND_BB_PROTECTED)) { /* JMPZ(X, L), L: T = JMPNZ_EX(X, L2) -> T = JMPZ_EX(X, L+1) */ last_op->opcode += 3; COPY_NODE(last_op->result, target->result); - del_source(block, block->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 && + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[1]; + ADD_SOURCE(block, block->successors[0]); + } else if (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) { + !(target_block->flags & ZEND_BB_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) { + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); + } else if (target->opcode == ZEND_JMP && + !(target_block->flags & ZEND_BB_PROTECTED)) { /* JMPZ(X, L), L: JMP(L2) -> JMPZ(X, L2) */ - del_source(block, block->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) { + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); + } else if (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->flags & ZEND_BB_PROTECTED)) { /* JMPZ(X, L), L: JMPZNZ(X, L2, L3) -> JMPZ(X, L2) */ - del_source(block, block->op2_to); + DEL_SOURCE(block, block->successors[0]); if (last_op->opcode == ZEND_JMPZ) { - block->op2_to = target_block->op2_to; + block->successors[0] = target_block->successors[0]; } else { - block->op2_to = target_block->ext_to; + block->successors[0] = target_block->successors[1]; } - ADD_SOURCE(block, block->op2_to); + ADD_SOURCE(block, block->successors[0]); } } - if (block->follow_to && - (last_op->opcode == ZEND_JMPZ || last_op->opcode == ZEND_JMPNZ)) { + if (last_op->opcode == ZEND_JMPZ || last_op->opcode == ZEND_JMPNZ) { zend_op *target; zend_op *target_end; + zend_basic_block *target_block; while (1) { - target = block->follow_to->start_opline; - target_end = block->follow_to->start_opline + block->follow_to->len; + target_block = blocks + block->successors[1]; + target = op_array->opcodes + target_block->start; + target_end = op_array->opcodes + target_block->start + 1; while (target < target_end && target->opcode == ZEND_NOP) { target++; } /* next block is only NOP's */ - if (target == target_end && ! block->follow_to->protected) { - del_source(block, block->follow_to); - block->follow_to = block->follow_to->follow_to; - ADD_SOURCE(block, block->follow_to); + if (target == target_end && !(target_block->flags & ZEND_BB_PROTECTED)) { + DEL_SOURCE(block, block->successors[1]); + block->successors[1] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[1]); } 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); + !(target_block->flags & ZEND_BB_PROTECTED)) { + DEL_SOURCE(block, block->successors[1]); if (last_op->opcode == ZEND_JMPZ) { - block->ext_to = block->follow_to->op1_to; - ADD_SOURCE(block, block->ext_to); + block->successors[1] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[1]); } else { - block->ext_to = block->op2_to; - block->op2_to = block->follow_to->op1_to; - ADD_SOURCE(block, block->op2_to); + block->successors[1] = block->successors[0]; + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); } - block->follow_to = NULL; last_op->opcode = ZEND_JMPZNZ; } } @@ -1606,207 +1313,182 @@ next_target: */ last_op->opcode = ZEND_QM_ASSIGN; SET_UNUSED(last_op->op2); - del_source(block, block->op2_to); - block->op2_to = NULL; + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = block->successors[1]; + block->successors[1] = -1; } break; } - if (block->op2_to) { + if (1) { zend_op *target, *target_end; - char *same_t=NULL; - zend_code_block *target_block; + zend_basic_block *target_block; int var_num = op_array->last_var + op_array->T; if (var_num <= 0) { return; } - same_t = cfg->same_t; memset(same_t, 0, var_num); 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; + target_block = blocks + block->successors[0]; next_target_ex: - target = target_block->start_opline; - target_end = target_block->start_opline + target_block->len; + target = op_array->opcodes + target_block->start; + target_end = target + target_block->len; while (target < target_end && target->opcode == ZEND_NOP) { target++; } /* next block is only NOP's */ if (target == target_end) { - target_block = target_block->follow_to; + target_block = blocks + target_block->successors[0]; goto next_target_ex; - } else if (target_block->op2_to && - target->opcode == last_op->opcode-3 && + } else if (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) { + !(target_block->flags & ZEND_BB_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) && + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); + } else if (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) { + !(target_block->flags & ZEND_BB_PROTECTED)) { /* T = JMPZ_EX(X, L1), L1: JMPNZ({X|T1}, L2) -> T = JMPZ_EX(X, L1+1) */ - del_source(block, block->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) && + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[1]; + ADD_SOURCE(block, block->successors[0]); + } else if (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) { + !(target_block->flags & ZEND_BB_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 && + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[1]; + ADD_SOURCE(block, block->successors[0]); + } else if (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) { + !(target_block->flags & ZEND_BB_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) { + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); + } else if (target->opcode == ZEND_JMP && + !(target_block->flags & ZEND_BB_PROTECTED)) { /* T = JMPZ_EX(X, L), L: JMP(L2) -> T = JMPZ(X, L2) */ - del_source(block, block->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 && + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); + } else if (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) { + !(target_block->flags & ZEND_BB_PROTECTED)) { /* T = JMPZ_EX(X, L), L: JMPZNZ({X|T}, L2, L3) -> T = JMPZ_EX(X, L2) */ - del_source(block, block->op2_to); + DEL_SOURCE(block, block->successors[0]); if (last_op->opcode == ZEND_JMPZ_EX) { - block->op2_to = target_block->op2_to; + block->successors[0] = target_block->successors[0]; } else { - block->op2_to = target_block->ext_to; + block->successors[0] = target_block->successors[1]; } - ADD_SOURCE(block, block->op2_to); + ADD_SOURCE(block, block->successors[0]); } } break; case ZEND_JMPZNZ: { - zend_code_block *next = block->next; + int next = (block - blocks) + 1; - while (next && !next->access) { + while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) { /* find first accessed one */ - next = 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); + DEL_SOURCE(block, block->successors[1]); + block->successors[1] = -1; } 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); + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = block->successors[1]; + block->successors[1] = -1; } - } else if (block->op2_to == block->ext_to) { + } else if (block->successors[0] == block->successors[1]) { /* both goto the same one - it's JMP */ if (!(last_op->op1_type & (IS_VAR|IS_TMP_VAR))) { /* JMPZNZ(?,L,L) -> JMP(L) */ last_op->opcode = ZEND_JMP; SET_UNUSED(last_op->op1); SET_UNUSED(last_op->op2); - block->op1_to = block->op2_to; - block->op2_to = NULL; - block->ext_to = NULL; + block->successors[1] = -1; } - } else if (block->op2_to == next) { + } else if (block->successors[0] == 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) { + block->successors[0] = block->successors[1]; + block->successors[1] = next; + /* no need to add source */ + } else if (block->successors[1] == 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 */ + /* no need to add source */ } - if (last_op->opcode == ZEND_JMPZNZ && block->op2_to) { + if (last_op->opcode == ZEND_JMPZNZ) { 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; + zend_basic_block *target_block = blocks + block->successors[0]; next_target_znz: - target = target_block->start_opline; - target_end = target_block->start_opline + target_block->len; + target = op_array->opcodes + target_block->start; + target_end = target + target_block->len; while (target < target_end && target->opcode == ZEND_NOP) { target++; } /* next block is only NOP's */ if (target == target_end) { - target_block = target_block->follow_to; + target_block = blocks + target_block->successors[0]; goto next_target_znz; - } else if (target_block->op2_to && - (target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) && + } else if ((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) { + !(target_block->flags & ZEND_BB_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); + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); } 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) { + !(target_block->flags & ZEND_BB_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) { + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[1]; + ADD_SOURCE(block, block->successors[0]); + } else if (target->opcode == ZEND_JMP && + !(target_block->flags & ZEND_BB_PROTECTED)) { /* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */ - del_source(block, block->op2_to); - block->op2_to = target_block->op1_to; - ADD_SOURCE(block, block->op2_to); + DEL_SOURCE(block, block->successors[0]); + block->successors[0] = target_block->successors[0]; + ADD_SOURCE(block, block->successors[0]); } } break; @@ -1816,25 +1498,19 @@ next_target_znz: /* Global data dependencies */ -#define T_USAGE(op) do { \ - if ((op ## _type & (IS_VAR | IS_TMP_VAR)) && \ - !zend_bitset_in(defined_here, VAR_NUM(op.var)) && !zend_bitset_in(used_ext, VAR_NUM(op.var))) { \ - zend_bitset_incl(used_ext, VAR_NUM(op.var)); \ - } \ - } while (0) - -#define NEVER_USED(op) ((op ## _type & (IS_VAR | IS_TMP_VAR)) && !zend_bitset_in(usage, VAR_NUM(op.var))) /* !zend_bitset_in(used_ext, op.var) && */ -#define RES_NEVER_USED(opline) (opline->result_type == IS_UNUSED || NEVER_USED(opline->result)) - /* Find a set of variables which are used outside of the block where they are * defined. We won't apply some optimization patterns for such variables. */ -static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx) +static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx) { - zend_code_block *next_block = block->next; + int n; + zend_basic_block *block, *next_block; + uint32_t var_num; uint32_t bitset_len; zend_bitset usage; zend_bitset defined_here; void *checkpoint; + zend_op *opline, *end; + if (op_array->T == 0) { /* shortcut - if no Ts, nothing to do */ @@ -1843,218 +1519,347 @@ static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, zend_b checkpoint = zend_arena_checkpoint(ctx->arena); bitset_len = zend_bitset_len(op_array->last_var + op_array->T); - usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE); - zend_bitset_clear(usage, bitset_len); defined_here = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE); - while (next_block) { - zend_op *opline = next_block->start_opline; - zend_op *end = opline + next_block->len; + zend_bitset_clear(defined_here, bitset_len); + for (n = 1; n < cfg->blocks_count; n++) { + block = cfg->blocks + n; - if (!next_block->access) { - next_block = next_block->next; + if (!(block->flags & ZEND_BB_REACHABLE)) { continue; } - zend_bitset_clear(defined_here, bitset_len); + + opline = op_array->opcodes + block->start; + end = opline + block->len; + if (!(block->flags & ZEND_BB_FOLLOW) || + (block->flags & ZEND_BB_TARGET)) { + /* Skip continuation of "extended" BB */ + zend_bitset_clear(defined_here, bitset_len); + } while (opline<end) { - T_USAGE(opline->op1); - if (opline->op2_type & (IS_VAR | IS_TMP_VAR)) { - if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) { + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + var_num = VAR_NUM(opline->op1.var); + if (!zend_bitset_in(defined_here, var_num)) { + zend_bitset_incl(used_ext, var_num); + } + } + if (opline->op2_type == IS_VAR) { + var_num = VAR_NUM(opline->op2.var); + if (opline->opcode == ZEND_FE_FETCH_R || + opline->opcode == ZEND_FE_FETCH_RW) { /* these opcode use the op2 as result */ - zend_bitset_incl(defined_here, VAR_NUM(ZEND_OP2(opline).var)); - } else { - T_USAGE(opline->op2); + zend_bitset_incl(defined_here, var_num); + } else if (!zend_bitset_in(defined_here, var_num)) { + zend_bitset_incl(used_ext, var_num); + } + } else if (opline->op2_type == IS_TMP_VAR) { + var_num = VAR_NUM(opline->op2.var); + if (!zend_bitset_in(defined_here, var_num)) { + zend_bitset_incl(used_ext, var_num); } } - if (RESULT_USED(opline)) { - if (!zend_bitset_in(defined_here, VAR_NUM(ZEND_RESULT(opline).var)) && !zend_bitset_in(used_ext, VAR_NUM(ZEND_RESULT(opline).var)) && - opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { - /* these opcode use the result as argument */ - zend_bitset_incl(used_ext, VAR_NUM(ZEND_RESULT(opline).var)); + if (opline->result_type == IS_VAR) { + var_num = VAR_NUM(opline->result.var); + zend_bitset_incl(defined_here, var_num); + } else if (opline->result_type == IS_TMP_VAR) { + var_num = VAR_NUM(opline->result.var); + switch (opline->opcode) { + case ZEND_ADD_ARRAY_ELEMENT: + case ZEND_ROPE_ADD: + /* these opcodes use the result as argument */ + if (!zend_bitset_in(defined_here, var_num)) { + zend_bitset_incl(used_ext, var_num); + } + break; + default : + zend_bitset_incl(defined_here, var_num); } - zend_bitset_incl(defined_here, VAR_NUM(ZEND_RESULT(opline).var)); } opline++; } - next_block = next_block->next; } -#if DEBUG_BLOCKPASS - { - int i; + if (ctx->debug_level & ZEND_DUMP_BLOCK_PASS_VARS) { + int printed = 0; + uint32_t i; + for (i = op_array->last_var; i< op_array->T; i++) { - fprintf(stderr, "T%d: %c\n", i, zend_bitset_in(used_ext, i) + '0'); + if (zend_bitset_in(used_ext, i)) { + if (!printed) { + fprintf(stderr, "NON-LOCAL-VARS: %d", i); + printed = 1; + } else { + fprintf(stderr, ", %d", i); + } + } + } + if (printed) { + fprintf(stderr, "\n"); } } -#endif - while (block) { - zend_op *opline = block->start_opline + block->len - 1; + usage = defined_here; + next_block = NULL; + for (n = cfg->blocks_count; n > 0;) { + block = cfg->blocks + (--n); - if (!block->access) { - block = block->next; + if (!(block->flags & ZEND_BB_REACHABLE) || block->len == 0) { continue; } - zend_bitset_copy(usage, used_ext, bitset_len); + end = op_array->opcodes + block->start; + opline = end + block->len - 1; + if (!next_block || + !(next_block->flags & ZEND_BB_FOLLOW) || + (next_block->flags & ZEND_BB_TARGET)) { + /* Skip continuation of "extended" BB */ + zend_bitset_copy(usage, used_ext, bitset_len); + } else if (block->successors[1] != -1) { + zend_bitset_union(usage, used_ext, bitset_len); + } + next_block = block; - while (opline >= block->start_opline) { + while (opline >= end) { /* 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_POW: - 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_ICALL: - case ZEND_DO_UCALL: - case ZEND_DO_FCALL_BY_NAME: - if (ZEND_RESULT_TYPE(opline) == IS_VAR) { - ZEND_RESULT_TYPE(opline) |= EXT_TYPE_UNUSED; - } - break; - case ZEND_QM_ASSIGN: - case ZEND_BOOL: - case ZEND_BOOL_NOT: - if (ZEND_OP1_TYPE(opline) == IS_TMP_VAR) { - opline->opcode = ZEND_FREE; - } else { - if (ZEND_OP1_TYPE(opline) == IS_CONST) { - literal_dtor(&ZEND_OP1_LITERAL(opline)); + if (opline->result_type == IS_VAR) { + if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) { + switch (opline->opcode) { + case ZEND_ASSIGN_ADD: + case ZEND_ASSIGN_SUB: + case ZEND_ASSIGN_MUL: + case ZEND_ASSIGN_DIV: + case ZEND_ASSIGN_POW: + 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_ASSIGN: + case ZEND_ASSIGN_REF: + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + opline->result_type = IS_UNUSED; + break; + } + } else { + zend_bitset_excl(usage, VAR_NUM(opline->result.var)); + } + } else if (opline->result_type == IS_TMP_VAR) { + if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) { + switch (opline->opcode) { + case ZEND_POST_INC: + case ZEND_POST_DEC: + opline->opcode -= 2; + opline->result_type = IS_UNUSED; + break; + case ZEND_QM_ASSIGN: + case ZEND_BOOL: + case ZEND_BOOL_NOT: + if (ZEND_OP1_TYPE(opline) == IS_CV) { + opline->opcode = ZEND_CHECK_VAR; + SET_UNUSED(opline->result); + } else if (ZEND_OP1_TYPE(opline) & (IS_TMP_VAR|IS_VAR)) { + opline->opcode = ZEND_FREE; + SET_UNUSED(opline->result); + } else { + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(opline)); + } + MAKE_NOP(opline); } - MAKE_NOP(opline); - } - break; - case ZEND_JMPZ_EX: - case ZEND_JMPNZ_EX: - opline->opcode -= 3; - SET_UNUSED(opline->result); - break; + break; + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + opline->opcode -= 3; + SET_UNUSED(opline->result); + break; + case ZEND_ADD_ARRAY_ELEMENT: + case ZEND_ROPE_ADD: + zend_bitset_incl(usage, VAR_NUM(opline->result.var)); + break; + } + } else { + switch (opline->opcode) { + case ZEND_ADD_ARRAY_ELEMENT: + case ZEND_ROPE_ADD: + break; + default: + zend_bitset_excl(usage, VAR_NUM(opline->result.var)); + break; + } } } - if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { - if (ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_TMP_VAR) { - zend_bitset_incl(usage, VAR_NUM(ZEND_RESULT(opline).var)); - } - } else { - if (RESULT_USED(opline)) { - zend_bitset_excl(usage, VAR_NUM(ZEND_RESULT(opline).var)); + if (opline->op2_type == IS_VAR) { + switch (opline->opcode) { + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + zend_bitset_excl(usage, VAR_NUM(opline->op2.var)); + break; + default: + zend_bitset_incl(usage, VAR_NUM(opline->op2.var)); + break; } + } else if (opline->op2_type == IS_TMP_VAR) { + zend_bitset_incl(usage, VAR_NUM(opline->op2.var)); } - if (ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_TMP_VAR) { - zend_bitset_incl(usage, VAR_NUM(ZEND_OP1(opline).var)); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + zend_bitset_incl(usage, VAR_NUM(opline->op1.var)); } - if (ZEND_OP2_TYPE(opline) == IS_VAR || ZEND_OP2_TYPE(opline) == IS_TMP_VAR) { - zend_bitset_incl(usage, VAR_NUM(ZEND_OP2(opline).var)); - } - - if ((ZEND_RESULT_TYPE(opline) & IS_VAR) && - (ZEND_RESULT_TYPE(opline) & EXT_TYPE_UNUSED) && - zend_bitset_in(usage, VAR_NUM(ZEND_RESULT(opline).var))) { - ZEND_RESULT_TYPE(opline) &= ~EXT_TYPE_UNUSED; - } - opline--; } - block = block->next; - } /* end blocks */ + } zend_arena_release(&ctx->arena, checkpoint); } +static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg) +{ + int i; + zend_basic_block *b, *bb; + zend_basic_block *prev = NULL; + + for (i = 0; i < cfg->blocks_count; i++) { + b = cfg->blocks + i; + if (b->flags & ZEND_BB_REACHABLE) { + if ((b->flags & ZEND_BB_FOLLOW) && + !(b->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED)) && + prev && + prev->successors[0] == i && prev->successors[1] == -1) + { + zend_op *last_op = op_array->opcodes + prev->start + prev->len - 1; + if (prev->len != 0 && last_op->opcode == ZEND_JMP) { + MAKE_NOP(last_op); + } + + for (bb = prev + 1; bb != b; bb++) { + zend_op *op = op_array->opcodes + bb->start; + zend_op *end = op + bb->len; + while (op < end) { + if (ZEND_OP1_TYPE(op) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(op)); + } + if (ZEND_OP2_TYPE(op) == IS_CONST) { + literal_dtor(&ZEND_OP2_LITERAL(op)); + } + MAKE_NOP(op); + op++; + } + /* make block empty */ + bb->len = 0; + } + + /* re-link */ + prev->flags |= (b->flags & ZEND_BB_EXIT); + prev->len = b->start + b->len - prev->start; + prev->successors[0] = b->successors[0]; + prev->successors[1] = b->successors[1]; + + /* unlink & make block empty and unreachable */ + b->flags = 0; + b->len = 0; + b->successors[0] = -1; + b->successors[1] = -1; + } else { + prev = b; + } + } + } +} + #define PASSES 3 -void optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx) +void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_cfg cfg; - zend_code_block *cur_block; + zend_basic_block *blocks, *end, *b; int pass; uint32_t bitset_len; zend_bitset usage; void *checkpoint; + zend_op **Tsource; + zend_uchar *same_t; -#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 (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { + /* Build CFG */ + checkpoint = zend_arena_checkpoint(ctx->arena); + if (zend_build_cfg(&ctx->arena, op_array, ZEND_CFG_SPLIT_AT_LIVE_RANGES, &cfg, NULL) != SUCCESS) { + zend_arena_release(&ctx->arena, checkpoint); return; } - if ((uint64_t) op_array->last * (op_array->last_var + op_array->T) > 512 * 1024 * 1024) { + if (cfg.blocks_count * (op_array->last_var + op_array->T) > 64 * 1024 * 1024) { + zend_arena_release(&ctx->arena, checkpoint); return; } - /* Build CFG */ - checkpoint = zend_arena_checkpoint(ctx->arena); - if (!find_code_blocks(op_array, &cfg, ctx)) { - zend_arena_release(&ctx->arena, checkpoint); - return; + if (ctx->debug_level & ZEND_DUMP_BEFORE_BLOCK_PASS) { + zend_dump_op_array(op_array, ZEND_DUMP_CFG, "before block pass", &cfg); } - zend_rebuild_access_path(&cfg, op_array, 0, ctx); - /* full rebuild here to produce correct sources! */ if (op_array->last_var || op_array->T) { bitset_len = zend_bitset_len(op_array->last_var + op_array->T); - cfg.Tsource = zend_arena_calloc(&ctx->arena, op_array->last_var + op_array->T, sizeof(zend_op *)); - cfg.same_t = zend_arena_alloc(&ctx->arena, op_array->last_var + op_array->T); + Tsource = zend_arena_calloc(&ctx->arena, op_array->last_var + op_array->T, sizeof(zend_op *)); + same_t = zend_arena_alloc(&ctx->arena, op_array->last_var + op_array->T); usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE); } else { bitset_len = 0; - cfg.Tsource = NULL; - cfg.same_t = NULL; + Tsource = NULL; + same_t = NULL; usage = NULL; } + blocks = cfg.blocks; + end = blocks + cfg.blocks_count; for (pass = 0; pass < PASSES; pass++) { /* Compute data dependencies */ zend_bitset_clear(usage, bitset_len); - zend_t_usage(cfg.blocks, op_array, usage, ctx); + zend_t_usage(&cfg, op_array, usage, ctx); /* optimize each basic block separately */ - for (cur_block = cfg.blocks; cur_block; cur_block = cur_block->next) { - if (!cur_block->access) { + for (b = blocks; b < end; b++) { + if (!(b->flags & ZEND_BB_REACHABLE)) { continue; } - zend_optimize_block(cur_block, op_array, usage, &cfg, ctx); + /* we track data dependencies only insight a single basic block */ + if (!(b->flags & ZEND_BB_FOLLOW) || + (b->flags & ZEND_BB_TARGET)) { + /* Skip continuation of "extended" BB */ + memset(Tsource, 0, (op_array->last_var + op_array->T) * sizeof(zend_op *)); + } + zend_optimize_block(b, op_array, usage, &cfg, Tsource); } /* Jump optimization for each block */ - for (cur_block = cfg.blocks; cur_block; cur_block = cur_block->next) { - if (!cur_block->access) { - continue; + for (b = blocks; b < end; b++) { + if (b->flags & ZEND_BB_REACHABLE) { + zend_jmp_optimization(b, op_array, &cfg, same_t); } - zend_jmp_optimization(cur_block, op_array, cfg.blocks, &cfg, ctx); } /* Eliminate unreachable basic blocks */ - zend_rebuild_access_path(&cfg, op_array, 1, ctx); + zend_cfg_remark_reachable_blocks(op_array, &cfg); + + /* Merge Blocks */ + zend_merge_blocks(op_array, &cfg); } zend_bitset_clear(usage, bitset_len); - zend_t_usage(cfg.blocks, op_array, usage, ctx); + zend_t_usage(&cfg, op_array, usage, ctx); assemble_code_blocks(&cfg, op_array); + if (ctx->debug_level & ZEND_DUMP_AFTER_BLOCK_PASS) { + zend_dump_op_array(op_array, ZEND_DUMP_CFG | ZEND_DUMP_HIDE_UNREACHABLE, "after block pass", &cfg); + } + /* Destroy CFG */ zend_arena_release(&ctx->arena, checkpoint); } diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c index f417bef5fb..c133cd9714 100644 --- a/ext/opcache/Optimizer/compact_literals.c +++ b/ext/opcache/Optimizer/compact_literals.c @@ -72,9 +72,9 @@ typedef struct _literal_info { info[n].u.num = (_num); \ } while (0) -#define LITERAL_INFO_OBJ(n, kind, merge, slots, related, _num) do { \ +#define LITERAL_INFO_OBJ(n, kind, merge, slots, related) do { \ info[n].flags = (LITERAL_EX_OBJ | ((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \ - info[n].u.num = (_num); \ + info[n].u.num = (uint32_t)-1; \ } while (0) static void optimizer_literal_obj_info(literal_info *info, @@ -92,7 +92,7 @@ static void optimizer_literal_obj_info(literal_info *info, */ if (Z_TYPE(op_array->literals[constant]) == IS_STRING && op_type == IS_UNUSED) { - LITERAL_INFO_OBJ(constant, kind, 1, slots, related, op_array->this_var); + LITERAL_INFO_OBJ(constant, kind, 1, slots, related); } else { LITERAL_INFO(constant, kind, 0, slots, related); } @@ -180,52 +180,45 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx LITERAL_INFO(opline->op1.constant, LITERAL_CONST, 1, 1, 2); break; case ZEND_FETCH_CONSTANT: - if (ZEND_OP1_TYPE(opline) == IS_UNUSED) { - if ((opline->extended_value & (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) == (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) { - LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 5); - } else { - LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 3); - } + if ((opline->extended_value & (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) == (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) { + LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 5); } else { - if (ZEND_OP1_TYPE(opline) == IS_CONST) { - LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2); - } + LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 3); + } + break; + case ZEND_FETCH_CLASS_CONSTANT: + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2); + } + optimizer_literal_class_info( + info, + opline->op1_type, + opline->op1, + opline->op2.constant, + LITERAL_CLASS_CONST, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 1, + op_array); + break; + case ZEND_FETCH_STATIC_PROP_R: + case ZEND_FETCH_STATIC_PROP_W: + case ZEND_FETCH_STATIC_PROP_RW: + case ZEND_FETCH_STATIC_PROP_IS: + case ZEND_FETCH_STATIC_PROP_UNSET: + case ZEND_FETCH_STATIC_PROP_FUNC_ARG: + case ZEND_UNSET_STATIC_PROP: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: + if (ZEND_OP2_TYPE(opline) == IS_CONST) { + LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2); + } + if (ZEND_OP1_TYPE(opline) == IS_CONST) { optimizer_literal_class_info( info, - opline->op1_type, - opline->op1, - opline->op2.constant, - LITERAL_CLASS_CONST, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 1, + opline->op2_type, + opline->op2, + opline->op1.constant, + LITERAL_STATIC_PROPERTY, 2, 1, op_array); } break; - 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_UNSET_VAR: - case ZEND_ISSET_ISEMPTY_VAR: - if (ZEND_OP2_TYPE(opline) == IS_UNUSED) { - if (ZEND_OP1_TYPE(opline) == IS_CONST) { - LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1, 0, 1); - } - } else { - if (ZEND_OP2_TYPE(opline) == IS_CONST) { - LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2); - } - if (ZEND_OP1_TYPE(opline) == IS_CONST) { - optimizer_literal_class_info( - info, - opline->op2_type, - opline->op2, - opline->op1.constant, - LITERAL_STATIC_PROPERTY, 2, 1, - op_array); - } - } - break; case ZEND_FETCH_CLASS: case ZEND_ADD_INTERFACE: case ZEND_ADD_TRAIT: @@ -293,15 +286,21 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx break; case ZEND_RECV_INIT: LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 0, 0, 1); - if (Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) != -1) { + if (Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) != (uint32_t)-1) { Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) = cache_size; cache_size += sizeof(void *); } break; + case ZEND_DECLARE_FUNCTION: + case ZEND_DECLARE_CLASS: + case ZEND_DECLARE_INHERITED_CLASS: + case ZEND_DECLARE_INHERITED_CLASS_DELAYED: + LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 0, 0, 2); + break; case ZEND_RECV: case ZEND_RECV_VARIADIC: case ZEND_VERIFY_RETURN_TYPE: - if (opline->op2.num != -1) { + if (opline->op2.num != (uint32_t)-1) { opline->op2.num = cache_size; cache_size += sizeof(void *); } @@ -425,9 +424,11 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case IS_CONSTANT: if (info[i].flags & LITERAL_MAY_MERGE) { if (info[i].flags & LITERAL_EX_OBJ) { - int key_len = MAX_LENGTH_OF_LONG + sizeof("->") - 1 + Z_STRLEN(op_array->literals[i]); + int key_len = sizeof("$this->") - 1 + Z_STRLEN(op_array->literals[i]); key = zend_string_alloc(key_len, 0); - ZSTR_LEN(key) = snprintf(ZSTR_VAL(key), ZSTR_LEN(key)-1, "%d->%s", info[i].u.num, Z_STRVAL(op_array->literals[i])); + memcpy(ZSTR_VAL(key), "$this->", sizeof("$this->") - 1); + memcpy(ZSTR_VAL(key) + sizeof("$this->") - 1, Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]) + 1); + ZSTR_LEN(key) = key_len; } else if (info[i].flags & LITERAL_EX_CLASS) { int key_len; zval *class_name = &op_array->literals[(info[i].u.num < i) ? map[info[i].u.num] : info[i].u.num]; diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c new file mode 100644 index 0000000000..3abf997d8d --- /dev/null +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -0,0 +1,670 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: 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" +#include "zend_vm.h" +#include "zend_bitset.h" +#include "zend_cfg.h" +#include "zend_ssa.h" +#include "zend_func_info.h" +#include "zend_call_graph.h" +#include "zend_inference.h" +#include "zend_dump.h" + +int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags) +{ + uint32_t build_flags; + + if (op_array->last_try_catch) { + /* TODO: we can't analyze functions with try/catch/finally ??? */ + return FAILURE; + } + + /* Build SSA */ + memset(ssa, 0, sizeof(zend_ssa)); + + if (zend_build_cfg(&ctx->arena, op_array, + ZEND_CFG_NO_ENTRY_PREDECESSORS, &ssa->cfg, flags) != SUCCESS) { + return FAILURE; + } + + if (*flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) { + /* TODO: we can't analyze functions with indirect variable access ??? */ + return FAILURE; + } + + if (zend_cfg_build_predecessors(&ctx->arena, &ssa->cfg) != SUCCESS) { + return FAILURE; + } + + if (ctx->debug_level & ZEND_DUMP_DFA_CFG) { + zend_dump_op_array(op_array, ZEND_DUMP_CFG, "dfa cfg", &ssa->cfg); + } + + /* Compute Dominators Tree */ + if (zend_cfg_compute_dominators_tree(op_array, &ssa->cfg) != SUCCESS) { + return FAILURE; + } + + /* Identify reducible and irreducible loops */ + if (zend_cfg_identify_loops(op_array, &ssa->cfg, flags) != SUCCESS) { + return FAILURE; + } + + if (ctx->debug_level & ZEND_DUMP_DFA_DOMINATORS) { + zend_dump_dominators(op_array, &ssa->cfg); + } + + build_flags = 0; + if (ctx->debug_level & ZEND_DUMP_DFA_LIVENESS) { + build_flags |= ZEND_SSA_DEBUG_LIVENESS; + } + if (ctx->debug_level & ZEND_DUMP_DFA_PHI) { + build_flags |= ZEND_SSA_DEBUG_PHI_PLACEMENT; + } + if (zend_build_ssa(&ctx->arena, ctx->script, op_array, build_flags, ssa, flags) != SUCCESS) { + return FAILURE; + } + + if (ctx->debug_level & ZEND_DUMP_DFA_SSA) { + zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa); + } + + + if (zend_ssa_compute_use_def_chains(&ctx->arena, op_array, ssa) != SUCCESS){ + return FAILURE; + } + + if (zend_ssa_find_false_dependencies(op_array, ssa) != SUCCESS) { + return FAILURE; + } + + if (zend_ssa_find_sccs(op_array, ssa) != SUCCESS){ + return FAILURE; + } + + if (zend_ssa_inference(&ctx->arena, op_array, ctx->script, ssa) != SUCCESS) { + return FAILURE; + } + + if (ctx->debug_level & ZEND_DUMP_DFA_SSA_VARS) { + zend_dump_ssa_variables(op_array, ssa, 0); + } + + return SUCCESS; +} + +static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa) +{ + zend_basic_block *blocks = ssa->cfg.blocks; + zend_basic_block *end = blocks + ssa->cfg.blocks_count; + zend_basic_block *b; + zend_func_info *func_info; + int j; + uint32_t i; + uint32_t target = 0; + uint32_t *shiftlist; + ALLOCA_FLAG(use_heap); + + shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); + memset(shiftlist, 0, sizeof(uint32_t) * op_array->last); + for (b = blocks; b < end; b++) { + if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { + uint32_t end; + if (b->flags & ZEND_BB_UNREACHABLE_FREE) { + /* Only keep the FREE for the loop var */ + ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE + || op_array->opcodes[b->start].opcode == ZEND_FE_FREE); + b->len = 1; + } + + end = b->start + b->len; + i = b->start; + b->start = target; + while (i < end) { + shiftlist[i] = i - target; + if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP) || + /*keep NOP to support ZEND_VM_SMART_BRANCH */ + (i > 0 && + i + 1 < op_array->last && + (op_array->opcodes[i+1].opcode == ZEND_JMPZ || + op_array->opcodes[i+1].opcode == ZEND_JMPNZ) && + zend_is_smart_branch(op_array->opcodes + i - 1))) { + if (i != target) { + op_array->opcodes[target] = op_array->opcodes[i]; + ssa->ops[target] = ssa->ops[i]; + } + target++; + } + i++; + } + if (target != end && b->len != 0) { + zend_op *opline; + zend_op *new_opline; + + b->len = target - b->start; + opline = op_array->opcodes + end - 1; + if (opline->opcode == ZEND_NOP) { + continue; + } + + new_opline = op_array->opcodes + target - 1; + switch (new_opline->opcode) { + case ZEND_JMP: + case ZEND_FAST_CALL: + ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline)); + break; + case ZEND_JMPZNZ: + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + /* break missing intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); + break; + case ZEND_CATCH: + if (!opline->result.num) { + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + } + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + break; + } + } + } + } + + if (target != op_array->last) { + /* reset rest opcodes */ + for (i = target; i < op_array->last; i++) { + MAKE_NOP(op_array->opcodes + i); + } + + /* update SSA variables */ + for (j = 0; j < ssa->vars_count; j++) { + if (ssa->vars[j].definition >= 0) { + ssa->vars[j].definition -= shiftlist[ssa->vars[j].definition]; + } + if (ssa->vars[j].use_chain >= 0) { + ssa->vars[j].use_chain -= shiftlist[ssa->vars[j].use_chain]; + } + } + for (i = 0; i < op_array->last; i++) { + if (ssa->ops[i].op1_use_chain >= 0) { + ssa->ops[i].op1_use_chain -= shiftlist[ssa->ops[i].op1_use_chain]; + } + if (ssa->ops[i].op2_use_chain >= 0) { + ssa->ops[i].op2_use_chain -= shiftlist[ssa->ops[i].op2_use_chain]; + } + if (ssa->ops[i].res_use_chain >= 0) { + ssa->ops[i].res_use_chain -= shiftlist[ssa->ops[i].res_use_chain]; + } + } + + /* update branch targets */ + for (b = blocks; b < end; b++) { + if ((b->flags & ZEND_BB_REACHABLE) && b->len != 0) { + zend_op *opline = op_array->opcodes + b->start + b->len - 1; + + switch (opline->opcode) { + case ZEND_JMP: + case ZEND_FAST_CALL: + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]); + break; + case ZEND_JMPZNZ: + opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + /* break missing intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + case ZEND_CATCH: + opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + break; + } + } + } + + /* update brk/cont array */ + for (j = 0; j < op_array->last_live_range; j++) { + op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; + op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; + } + + /* 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 (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]; + } + } + + /* update early binding list */ + if (op_array->early_binding != (uint32_t)-1) { + uint32_t *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 != (uint32_t)-1); + } + + /* update call graph */ + func_info = ZEND_FUNC_INFO(op_array); + if (func_info) { + zend_call_info *call_info = func_info->callee_info; + while (call_info) { + call_info->caller_init_opline -= + shiftlist[call_info->caller_init_opline - op_array->opcodes]; + call_info->caller_call_opline -= + shiftlist[call_info->caller_call_opline - op_array->opcodes]; + call_info = call_info->next_callee; + } + } + + op_array->last = target; + } + free_alloca(shiftlist, use_heap); +} + +static inline zend_bool can_elide_return_type_check( + zend_op_array *op_array, zend_ssa *ssa, zend_ssa_op *ssa_op) { + zend_arg_info *info = &op_array->arg_info[-1]; + zend_ssa_var_info *use_info = &ssa->var_info[ssa_op->op1_use]; + zend_ssa_var_info *def_info = &ssa->var_info[ssa_op->op1_def]; + + if (use_info->type & MAY_BE_REF) { + return 0; + } + + /* A type is possible that is not in the allowed types */ + if ((use_info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~(def_info->type & MAY_BE_ANY)) { + return 0; + } + + if (info->type_hint == IS_CALLABLE) { + return 0; + } + + if (info->class_name) { + if (!use_info->ce || !def_info->ce || !instanceof_function(use_info->ce, def_info->ce)) { + return 0; + } + } + + return 1; +} + +static zend_bool opline_supports_assign_contraction( + zend_ssa *ssa, zend_op *opline, int src_var, uint32_t cv_var) { + if (opline->opcode == ZEND_NEW) { + /* see Zend/tests/generators/aborted_yield_during_new.phpt */ + return 0; + } + + if (opline->opcode == ZEND_DO_ICALL || opline->opcode == ZEND_DO_UCALL + || opline->opcode == ZEND_DO_FCALL || opline->opcode == ZEND_DO_FCALL_BY_NAME) { + /* Function calls may dtor the return value after it has already been written -- allow + * direct assignment only for types where a double-dtor does not matter. */ + uint32_t type = ssa->var_info[src_var].type; + uint32_t simple = MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE; + return !((type & MAY_BE_ANY) & ~simple); + } + + if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) { + /* POST_INC/DEC write the result variable before performing the inc/dec. For $i = $i++ + * eliding the temporary variable would thus yield an incorrect result. */ + return opline->op1_type != IS_CV || opline->op1.var != cv_var; + } + + if (opline->opcode == ZEND_INIT_ARRAY) { + /* INIT_ARRAY initializes the result array before reading key/value. */ + return (opline->op1_type != IS_CV || opline->op1.var != cv_var) + && (opline->op2_type != IS_CV || opline->op2.var != cv_var); + } + + if (opline->opcode == ZEND_CAST + && (opline->extended_value == IS_ARRAY || opline->extended_value == IS_OBJECT)) { + /* CAST to array/object may initialize the result to an empty array/object before + * reading the expression. */ + return opline->op1_type != IS_CV || opline->op1.var != cv_var; + } + + return 1; +} + +void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa) +{ + if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) { + zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa); + } + + if (ssa->var_info) { + int op_1; + int v; + int remove_nops = 0; + zend_op *opline; + zval tmp; + + for (v = op_array->last_var; v < ssa->vars_count; v++) { + + op_1 = ssa->vars[v].definition; + + if (op_1 < 0) { + continue; + } + + opline = op_array->opcodes + op_1; + + /* Convert LONG constants to DOUBLE */ + if (ssa->var_info[v].use_as_double) { + if (opline->opcode == ZEND_ASSIGN + && opline->op2_type == IS_CONST + && ssa->ops[op_1].op1_def == v + && !RETURN_VALUE_USED(opline) + ) { + +// op_1: ASSIGN ? -> #v [use_as_double], long(?) => ASSIGN ? -> #v, double(?) + + zval *zv = CT_CONSTANT_EX(op_array, opline->op2.constant); + ZEND_ASSERT(Z_TYPE_INFO_P(zv) == IS_LONG); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp); + + } else if (opline->opcode == ZEND_QM_ASSIGN + && opline->op1_type == IS_CONST + ) { + +// op_1: QM_ASSIGN #v [use_as_double], long(?) => QM_ASSIGN #v, double(?) + + zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant); + ZEND_ASSERT(Z_TYPE_INFO_P(zv) == IS_LONG); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp); + } + + } else { + if (opline->opcode == ZEND_ADD + || opline->opcode == ZEND_SUB + || opline->opcode == ZEND_MUL + || opline->opcode == ZEND_IS_EQUAL + || opline->opcode == ZEND_IS_NOT_EQUAL + || opline->opcode == ZEND_IS_SMALLER + || opline->opcode == ZEND_IS_SMALLER_OR_EQUAL + ) { + + if (opline->op1_type == IS_CONST + && opline->op2_type != IS_CONST + && (OP2_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE + && Z_TYPE_INFO_P(CT_CONSTANT_EX(op_array, opline->op1.constant)) == IS_LONG + ) { + +// op_1: #v.? = ADD long(?), #?.? [double] => #v.? = ADD double(?), #?.? [double] + + zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp); + + } else if (opline->op1_type != IS_CONST + && opline->op2_type == IS_CONST + && (OP1_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE + && Z_TYPE_INFO_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG + ) { + +// op_1: #v.? = ADD #?.? [double], long(?) => #v.? = ADD #?.? [double], double(?) + + zval *zv = CT_CONSTANT_EX(op_array, opline->op2.constant); + ZVAL_DOUBLE(&tmp, zval_get_double(zv)); + opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp); + } + } + } + + if (ssa->vars[v].var >= op_array->last_var) { + /* skip TMP and VAR */ + continue; + } + + if (opline->opcode == ZEND_ASSIGN + && ssa->ops[op_1].op1_def == v + && !RETURN_VALUE_USED(opline) + ) { + int orig_var = ssa->ops[op_1].op1_use; + + if (orig_var >= 0 + && !(ssa->var_info[orig_var].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) + ) { + + int src_var = ssa->ops[op_1].op2_use; + + if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) + && src_var >= 0 + && !(ssa->var_info[src_var].type & MAY_BE_REF) + && ssa->vars[src_var].definition >= 0 + && ssa->ops[ssa->vars[src_var].definition].result_def == src_var + && ssa->ops[ssa->vars[src_var].definition].result_use < 0 + && ssa->vars[src_var].use_chain == op_1 + && ssa->ops[op_1].op2_use_chain < 0 + && !ssa->vars[src_var].phi_use_chain + && !ssa->vars[src_var].sym_use_chain + && opline_supports_assign_contraction( + ssa, &op_array->opcodes[ssa->vars[src_var].definition], + src_var, opline->op1.var) + ) { + + int op_2 = ssa->vars[src_var].definition; + +// op_2: #src_var.T = OP ... => #v.CV = OP ... +// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, #src_var.T NOP + + if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) { + /* Reconstruct SSA */ + ssa->vars[v].definition = op_2; + ssa->ops[op_2].result_def = v; + + ssa->vars[src_var].definition = -1; + ssa->vars[src_var].use_chain = -1; + + ssa->ops[op_1].op1_use = -1; + ssa->ops[op_1].op2_use = -1; + ssa->ops[op_1].op1_def = -1; + ssa->ops[op_1].op1_use_chain = -1; + + /* Update opcodes */ + op_array->opcodes[op_2].result_type = opline->op1_type; + op_array->opcodes[op_2].result.var = opline->op1.var; + MAKE_NOP(opline); + remove_nops = 1; + } + } else if (opline->op2_type == IS_CONST + || ((opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)) + && ssa->ops[op_1].op2_use >= 0 + && ssa->ops[op_1].op2_def < 0) + ) { + +// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, CONST|TMPVAR => QM_ASSIGN v.CV, CONST|TMPVAR + + if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) { + /* Reconstruct SSA */ + ssa->ops[op_1].result_def = v; + ssa->ops[op_1].op1_def = -1; + ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use; + ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain; + ssa->ops[op_1].op2_use = -1; + ssa->ops[op_1].op2_use_chain = -1; + + /* Update opcode */ + opline->result_type = opline->op1_type; + opline->result.var = opline->op1.var; + opline->op1_type = opline->op2_type; + opline->op1.var = opline->op2.var; + opline->op2_type = IS_UNUSED; + opline->op2.var = 0; + opline->opcode = ZEND_QM_ASSIGN; + } + } + } + + } else if (opline->opcode == ZEND_ASSIGN_ADD + && opline->extended_value == 0 + && ssa->ops[op_1].op1_def == v + && opline->op2_type == IS_CONST + && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG + && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1 + && ssa->ops[op_1].op1_use >= 0 + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) { + +// op_1: ASSIGN_ADD #?.CV [undef,null,int,foat] ->#v.CV, int(1) => PRE_INC #?.CV ->#v.CV + + opline->opcode = ZEND_PRE_INC; + SET_UNUSED(opline->op2); + + } else if (opline->opcode == ZEND_ASSIGN_SUB + && opline->extended_value == 0 + && ssa->ops[op_1].op1_def == v + && opline->op2_type == IS_CONST + && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG + && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1 + && ssa->ops[op_1].op1_use >= 0 + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) { + +// op_1: ASSIGN_SUB #?.CV [undef,null,int,foat] -> #v.CV, int(1) => PRE_DEC #?.CV ->#v.CV + + opline->opcode = ZEND_PRE_DEC; + SET_UNUSED(opline->op2); + + } else if (opline->opcode == ZEND_VERIFY_RETURN_TYPE + && ssa->ops[op_1].op1_def == v + && ssa->ops[op_1].op1_use >= 0 + && ssa->ops[op_1].op1_use_chain == -1 + && ssa->vars[v].use_chain >= 0 + && can_elide_return_type_check(op_array, ssa, &ssa->ops[op_1])) { + +// op_1: VERIFY_RETURN_TYPE #orig_var.CV [T] -> #v.CV [T] => NOP + + int orig_var = ssa->ops[op_1].op1_use; + if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) { + + int ret = ssa->vars[v].use_chain; + + ssa->ops[ret].op1_use = orig_var; + ssa->ops[ret].op1_use_chain = ssa->vars[orig_var].use_chain; + ssa->vars[orig_var].use_chain = ret; + + ssa->vars[v].definition = -1; + ssa->vars[v].use_chain = -1; + + ssa->ops[op_1].op1_def = -1; + ssa->ops[op_1].op1_use = -1; + + MAKE_NOP(opline); + remove_nops = 1; + } + + } else if (ssa->ops[op_1].op1_def == v + && !RETURN_VALUE_USED(opline) + && ssa->ops[op_1].op1_use >= 0 + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) + && (opline->opcode == ZEND_ASSIGN_ADD + || opline->opcode == ZEND_ASSIGN_SUB + || opline->opcode == ZEND_ASSIGN_MUL + || opline->opcode == ZEND_ASSIGN_DIV + || opline->opcode == ZEND_ASSIGN_MOD + || opline->opcode == ZEND_ASSIGN_SL + || opline->opcode == ZEND_ASSIGN_SR + || opline->opcode == ZEND_ASSIGN_BW_OR + || opline->opcode == ZEND_ASSIGN_BW_AND + || opline->opcode == ZEND_ASSIGN_BW_XOR) + && opline->extended_value == 0) { + +// op_1: ASSIGN_ADD #orig_var.CV [undef,null,bool,int,double] -> #v.CV, ? => #v.CV = ADD #orig_var.CV, ? + + /* Reconstruct SSA */ + ssa->ops[op_1].result_def = ssa->ops[op_1].op1_def; + ssa->ops[op_1].op1_def = -1; + + /* Update opcode */ + opline->opcode -= (ZEND_ASSIGN_ADD - ZEND_ADD); + opline->result_type = opline->op1_type; + opline->result.var = opline->op1.var; + + } + } + + if (remove_nops) { + zend_ssa_remove_nops(op_array, ssa); + } + } + + if (ctx->debug_level & ZEND_DUMP_AFTER_DFA_PASS) { + zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dfa pass", ssa); + } +} + +void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx) +{ + void *checkpoint = zend_arena_checkpoint(ctx->arena); + uint32_t flags = 0; + zend_ssa ssa; + + if (zend_dfa_analyze_op_array(op_array, ctx, &ssa, &flags) != SUCCESS) { + zend_arena_release(&ctx->arena, checkpoint); + return; + } + + zend_dfa_optimize_op_array(op_array, ctx, &ssa); + + /* Destroy SSA */ + zend_arena_release(&ctx->arena, checkpoint); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/nop_removal.c b/ext/opcache/Optimizer/nop_removal.c index a42ede8cc1..c7ff73a61b 100644 --- a/ext/opcache/Optimizer/nop_removal.c +++ b/ext/opcache/Optimizer/nop_removal.c @@ -39,15 +39,15 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) uint32_t *shiftlist; ALLOCA_FLAG(use_heap); - shiftlist = (uint32_t *)DO_ALLOCA(sizeof(uint32_t) * op_array->last); + shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); i = new_count = shift = 0; end = op_array->opcodes + op_array->last; for (opline = op_array->opcodes; opline < end; opline++) { /* Kill JMP-over-NOP-s */ - if (opline->opcode == ZEND_JMP && ZEND_OP1(opline).opline_num > i) { + if (opline->opcode == ZEND_JMP && ZEND_OP1_JMP_ADDR(opline) > op_array->opcodes + i) { /* check if there are only NOPs under the branch */ - zend_op *target = op_array->opcodes + ZEND_OP1(opline).opline_num - 1; + zend_op *target = ZEND_OP1_JMP_ADDR(opline) - 1; while (target->opcode == ZEND_NOP) { target--; @@ -63,7 +63,40 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) shift++; } else { if (shift) { - op_array->opcodes[new_count] = *opline; + zend_op *new_opline = op_array->opcodes + new_count; + + *new_opline = *opline; + switch (new_opline->opcode) { + case ZEND_JMP: + case ZEND_FAST_CALL: + ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline)); + break; + case ZEND_JMPZNZ: + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + /* break missing intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); + break; + case ZEND_CATCH: + if (!opline->result.num) { + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + } + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + break; + } } new_count++; } @@ -78,41 +111,36 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: - ZEND_OP1(opline).opline_num -= shiftlist[ZEND_OP1(opline).opline_num]; + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]); break; + case ZEND_JMPZNZ: + opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + /* break missing intentionally */ case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: - case ZEND_NEW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: - ZEND_OP2(opline).opline_num -= shiftlist[ZEND_OP2(opline).opline_num]; + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: - opline->extended_value -= shiftlist[opline->extended_value]; - 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]; + opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, 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]; + for (j = 0; j < op_array->last_live_range; j++) { + op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; + op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; } /* update try/catch array */ @@ -135,5 +163,5 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) } while (*opline_num != (uint32_t)-1); } } - FREE_ALLOCA(shiftlist); + free_alloca(shiftlist, use_heap); } diff --git a/ext/opcache/Optimizer/optimize_func_calls.c b/ext/opcache/Optimizer/optimize_func_calls.c index 3b1c84a00c..5d477c1a73 100644 --- a/ext/opcache/Optimizer/optimize_func_calls.c +++ b/ext/opcache/Optimizer/optimize_func_calls.c @@ -29,6 +29,9 @@ #include "zend_execute.h" #include "zend_vm.h" +#define ZEND_OP1_IS_CONST_STRING(opline) \ + (ZEND_OP1_TYPE(opline) == IS_CONST && \ + Z_TYPE(op_array->literals[(opline)->op1.constant]) == IS_STRING) #define ZEND_OP2_IS_CONST_STRING(opline) \ (ZEND_OP2_TYPE(opline) == IS_CONST && \ Z_TYPE(op_array->literals[(opline)->op2.constant]) == IS_STRING) @@ -36,9 +39,115 @@ typedef struct _optimizer_call_info { zend_function *func; zend_op *opline; + zend_bool try_inline; } optimizer_call_info; -void optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) +static void zend_delete_call_instructions(zend_op *opline) +{ + int call = 0; + + while (1) { + switch (opline->opcode) { + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_FCALL: + if (call == 0) { + MAKE_NOP(opline); + return; + } + /* break missing intentionally */ + case ZEND_NEW: + case ZEND_INIT_DYNAMIC_CALL: + case ZEND_INIT_USER_CALL: + call--; + break; + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + call++; + break; + case ZEND_SEND_VAL: + case ZEND_SEND_VAR: + if (call == 0) { + if (opline->op1_type == IS_CONST) { + MAKE_NOP(opline); + } else if (opline->op1_type == IS_CV) { + opline->opcode = ZEND_CHECK_VAR; + opline->extended_value = 0; + opline->result.var = 0; + } else { + opline->opcode = ZEND_FREE; + opline->extended_value = 0; + opline->result.var = 0; + } + } + break; + } + opline--; + } +} + +static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func) +{ + if (func->type == ZEND_USER_FUNCTION + && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS)) + && fcall->extended_value >= func->op_array.required_num_args + && func->op_array.opcodes[func->op_array.num_args].opcode == ZEND_RETURN) { + + zend_op *ret_opline = func->op_array.opcodes + func->op_array.num_args; + + if (ret_opline->op1_type == IS_CONST) { + uint32_t i, num_args = func->op_array.num_args; + num_args += (func->op_array.fn_flags & ZEND_ACC_VARIADIC) != 0; + + if (fcall->opcode == ZEND_INIT_METHOD_CALL && fcall->op1_type == IS_UNUSED) { + /* TODO: we can't inlne methods, because $this may be used + * not in object context ??? + */ + return; + } + + for (i = 0; i < num_args; i++) { + /* Don't inline functions with by-reference arguments. This would require + * correct handling of INDIRECT arguments. */ + if (func->op_array.arg_info[i].pass_by_reference) { + return; + } + } + + if (fcall->extended_value < func->op_array.num_args) { + /* don't inline funcions with named constants in default arguments */ + i = fcall->extended_value; + + do { + if (Z_CONSTANT_P(RT_CONSTANT(&func->op_array, func->op_array.opcodes[i].op2))) { + return; + } + i++; + } while (i < func->op_array.num_args); + } + + if (RETURN_VALUE_USED(opline)) { + zval zv; + + ZVAL_DUP(&zv, RT_CONSTANT(&func->op_array, ret_opline->op1)); + opline->opcode = ZEND_QM_ASSIGN; + opline->op1_type = IS_CONST; + opline->op1.constant = zend_optimizer_add_literal(op_array, &zv); + SET_UNUSED(opline->op2); + } else { + MAKE_NOP(opline); + } + + zend_delete_call_instructions(opline-1); + } + } +} + +void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *opline = op_array->opcodes; zend_op *end = opline + op_array->last; @@ -56,20 +165,15 @@ void optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) switch (opline->opcode) { case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: - if (ZEND_OP2_IS_CONST_STRING(opline)) { - zend_function *func; - zval *function_name = &op_array->literals[opline->op2.constant + 1]; - if ((func = zend_hash_find_ptr(&ctx->script->function_table, - Z_STR_P(function_name))) != NULL) { - call_stack[call].func = func; - } - } - /* break missing intentionally */ - case ZEND_NEW: - case ZEND_INIT_DYNAMIC_CALL: - case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: + case ZEND_NEW: + call_stack[call].func = zend_optimizer_get_called_func( + ctx->script, op_array, opline, 0); + call_stack[call].try_inline = opline->opcode != ZEND_NEW; + /* break missing intentionally */ + case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_USER_CALL: call_stack[call].opline = opline; call++; @@ -82,13 +186,15 @@ void optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (call_stack[call].func && call_stack[call].opline) { zend_op *fcall = call_stack[call].opline; - if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) { + if (fcall->opcode == ZEND_INIT_FCALL) { + /* nothing to do */ + } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); Z_CACHE_SLOT(op_array->literals[fcall->op2.constant + 1]) = Z_CACHE_SLOT(op_array->literals[fcall->op2.constant]); literal_dtor(&ZEND_OP2_LITERAL(fcall)); fcall->op2.constant = fcall->op2.constant + 1; - opline->opcode = zend_get_call_op(ZEND_INIT_FCALL, call_stack[call].func); + opline->opcode = zend_get_call_op(fcall, call_stack[call].func); } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); @@ -96,31 +202,51 @@ void optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) literal_dtor(&op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant + 2]); fcall->op2.constant = fcall->op2.constant + 1; - opline->opcode = zend_get_call_op(ZEND_INIT_FCALL, call_stack[call].func); + opline->opcode = zend_get_call_op(fcall, call_stack[call].func); + } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL + || fcall->opcode == ZEND_INIT_METHOD_CALL + || fcall->opcode == ZEND_NEW) { + /* We don't have specialized opcodes for this, do nothing */ } else { ZEND_ASSERT(0); } + + if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level) + && call_stack[call].try_inline) { + zend_try_inline_call(op_array, fcall, opline, call_stack[call].func); + } } call_stack[call].func = NULL; call_stack[call].opline = NULL; + call_stack[call].try_inline = 0; break; case ZEND_FETCH_FUNC_ARG: + case ZEND_FETCH_STATIC_PROP_FUNC_ARG: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_DIM_FUNC_ARG: if (call_stack[call - 1].func) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, (opline->extended_value & ZEND_FETCH_ARG_MASK))) { opline->extended_value &= ZEND_FETCH_TYPE_MASK; - opline->opcode -= 9; + if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { + opline->opcode -= 9; + } else { + opline->opcode = ZEND_FETCH_STATIC_PROP_W; + } } else { if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG && opline->op2_type == IS_UNUSED) { /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not. * Performing the replacement would create an invalid opcode. */ + call_stack[call - 1].try_inline = 0; break; } opline->extended_value &= ZEND_FETCH_TYPE_MASK; - opline->opcode -= 12; + if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { + opline->opcode -= 12; + } else { + opline->opcode = ZEND_FETCH_STATIC_PROP_R; + } } } break; @@ -143,27 +269,21 @@ void optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) } } break; - case ZEND_SEND_VAR_NO_REF: - if (!(opline->extended_value & ZEND_ARG_COMPILE_TIME_BOUND) && call_stack[call - 1].func) { - if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { - opline->extended_value |= ZEND_ARG_COMPILE_TIME_BOUND | ZEND_ARG_SEND_BY_REF; + case ZEND_SEND_VAR_NO_REF_EX: + if (call_stack[call - 1].func) { + if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { + opline->opcode = ZEND_SEND_VAR_NO_REF; + } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { + opline->opcode = ZEND_SEND_VAL; } else { opline->opcode = ZEND_SEND_VAR; - opline->extended_value = 0; } } break; -#if 0 - case ZEND_SEND_REF: - if (opline->extended_value != ZEND_ARG_COMPILE_TIME_BOUND && call_stack[call - 1].func) { - /* We won't handle run-time pass by reference */ - call_stack[call - 1].opline = NULL; - } - break; -#endif case ZEND_SEND_UNPACK: - call_stack[call - 1].func = NULL; - call_stack[call - 1].opline = NULL; + case ZEND_SEND_USER: + case ZEND_SEND_ARRAY: + call_stack[call - 1].try_inline = 0; break; default: break; diff --git a/ext/opcache/Optimizer/optimize_temp_vars_5.c b/ext/opcache/Optimizer/optimize_temp_vars_5.c index 5cc7b79e89..930a926a0e 100644 --- a/ext/opcache/Optimizer/optimize_temp_vars_5.c +++ b/ext/opcache/Optimizer/optimize_temp_vars_5.c @@ -39,7 +39,7 @@ max = i; \ } -void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx) +void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx) { int T = op_array->T; int offset = op_array->last_var; @@ -140,13 +140,6 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c } } - /* 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))) { currT = VAR_NUM(ZEND_OP2(opline).var) - offset; if (!zend_bitset_in(valid_T, currT)) { @@ -157,31 +150,6 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c ZEND_OP2(opline).var = NUM_VAR(map_T[currT] + offset); } - if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS || - opline->opcode == ZEND_DECLARE_ANON_INHERITED_CLASS || - opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) { - currT = VAR_NUM(opline->extended_value) - offset; - if (!zend_bitset_in(valid_T, currT)) { - GET_AVAILABLE_T(); - map_T[currT] = i; - zend_bitset_incl(valid_T, currT); - } - opline->extended_value = NUM_VAR(map_T[currT] + offset); - } - - /* 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) - offset; - GET_AVAILABLE_T(); - map_T[currT] = i; - zend_bitset_incl(valid_T, currT); - zend_bitset_excl(taken_T, i); - ZEND_OP2(opline + 1).var = NUM_VAR(i + offset); - var_to_free = i; - } - if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_RESULT(opline).var) - offset; if (zend_bitset_in(valid_T, currT)) { @@ -203,16 +171,11 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c } } } - } else { /* Au still needs to be assigned a T which is a bit dumb. Should consider changing Zend */ + } else { + /* Code which gets here is using a wrongly built opcode such as RECV() */ GET_AVAILABLE_T(); - - if (RESULT_UNUSED(opline)) { - zend_bitset_excl(taken_T, i); - } else { - /* Code which gets here is using a wrongly built opcode such as RECV() */ - map_T[currT] = i; - zend_bitset_incl(valid_T, currT); - } + map_T[currT] = i; + zend_bitset_incl(valid_T, currT); ZEND_RESULT(opline).var = NUM_VAR(i + offset); } } @@ -225,6 +188,14 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c opline--; } + if (op_array->live_range) { + for (i = 0; i < op_array->last_live_range; i++) { + op_array->live_range[i].var = + NUM_VAR(map_T[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK) - offset] + offset) | + (op_array->live_range[i].var & ZEND_LIVE_MASK); + } + } + zend_arena_release(&ctx->arena, checkpoint); op_array->T = max + 1; } diff --git a/ext/opcache/Optimizer/pass1_5.c b/ext/opcache/Optimizer/pass1_5.c index b29e90c767..fdfb16e328 100644 --- a/ext/opcache/Optimizer/pass1_5.c +++ b/ext/opcache/Optimizer/pass1_5.c @@ -42,7 +42,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) int i = 0; zend_op *opline = op_array->opcodes; zend_op *end = opline + op_array->last; - zend_bool collect_constants = (ZEND_OPTIMIZER_PASS_15 & OPTIMIZATION_LEVEL)? + zend_bool collect_constants = (ZEND_OPTIMIZER_PASS_15 & ctx->optimization_level)? (op_array == &ctx->script->main_op_array) : 0; while (opline < end) { @@ -84,6 +84,9 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) zval_get_long(&ZEND_OP2_LITERAL(opline)) < 0) { /* shift by negative number */ break; + } else if (zend_binary_op_produces_numeric_string_error(opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline))) { + /* produces numeric string E_NOTICE/E_WARNING */ + break; } er = EG(error_reporting); EG(error_reporting) = 0; @@ -242,8 +245,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) #endif case ZEND_FETCH_CONSTANT: - if (ZEND_OP1_TYPE(opline) == IS_UNUSED && - ZEND_OP2_TYPE(opline) == IS_CONST && + if (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) { @@ -267,8 +269,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) break; } - if (ZEND_OP1_TYPE(opline) == IS_UNUSED && - ZEND_OP2_TYPE(opline) == IS_CONST && + if (ZEND_OP2_TYPE(opline) == IS_CONST && Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) { /* substitute persistent constants */ uint32_t tv = ZEND_RESULT(opline).var; @@ -287,10 +288,10 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) MAKE_NOP(opline); } } + break; - /* class constant */ - if (ZEND_OP1_TYPE(opline) != IS_UNUSED && - ZEND_OP2_TYPE(opline) == IS_CONST && + case ZEND_FETCH_CLASS_CONSTANT: + if (ZEND_OP2_TYPE(opline) == IS_CONST && Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) { zend_class_entry *ce = NULL; @@ -308,15 +309,20 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) (ce->type == ZEND_INTERNAL_CLASS && ce->info.internal.module->type != MODULE_PERSISTENT) || (ce->type == ZEND_USER_CLASS && - ZEND_CE_FILENAME(ce) != op_array->filename)) { + ce->info.user.filename != op_array->filename)) { break; } } } else if (op_array->scope && + ZEND_OP1_TYPE(opline) == IS_UNUSED && + (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) { + /* for self::B */ + ce = op_array->scope; + } else if (op_array->scope && ZEND_OP1_TYPE(opline) == IS_VAR && (opline - 1)->opcode == ZEND_FETCH_CLASS && (ZEND_OP1_TYPE(opline - 1) == IS_UNUSED && - ((opline - 1)->extended_value & ~ZEND_FETCH_CLASS_NO_AUTOLOAD) == ZEND_FETCH_CLASS_SELF) && + ((opline - 1)->extended_value & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) && ZEND_RESULT((opline - 1)).var == ZEND_OP1(opline).var) { /* for self::B */ ce = op_array->scope; @@ -324,11 +330,13 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (ce) { uint32_t tv = ZEND_RESULT(opline).var; + zend_class_constant *cc; zval *c, t; - if ((c = zend_hash_find(&ce->constants_table, - Z_STR(ZEND_OP2_LITERAL(opline)))) != NULL) { - ZVAL_DEREF(c); + if ((cc = zend_hash_find_ptr(&ce->constants_table, + Z_STR(ZEND_OP2_LITERAL(opline)))) != NULL && + (Z_ACCESS_FLAGS(cc->value) & ZEND_ACC_PPP_MASK) == ZEND_ACC_PUBLIC) { + c = &cc->value; if (Z_TYPE_P(c) == IS_CONSTANT_AST) { break; } @@ -342,12 +350,12 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) zval_copy_ctor(&t); } - if (ZEND_OP1_TYPE(opline) == IS_CONST) { - literal_dtor(&ZEND_OP1_LITERAL(opline)); - } else { - MAKE_NOP((opline - 1)); - } if (zend_optimizer_replace_by_const(op_array, opline, IS_TMP_VAR, tv, &t)) { + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + literal_dtor(&ZEND_OP1_LITERAL(opline)); + } else if (ZEND_OP1_TYPE(opline) == IS_VAR) { + MAKE_NOP((opline - 1)); + } literal_dtor(&ZEND_OP2_LITERAL(opline)); MAKE_NOP(opline); } @@ -640,74 +648,11 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_FE_RESET_RW: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: - case ZEND_NEW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: collect_constants = 0; break; - 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") - 1) != 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_STR(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_STR(ZEND_OP1_LITERAL(opline))); - MAKE_NOP(opline); - } - } - } - break; } opline++; i++; diff --git a/ext/opcache/Optimizer/pass2.c b/ext/opcache/Optimizer/pass2.c index 098be5146b..d592938256 100644 --- a/ext/opcache/Optimizer/pass2.c +++ b/ext/opcache/Optimizer/pass2.c @@ -22,7 +22,6 @@ /* pass 2: * - convert non-numeric constants to numeric constants in numeric operators * - optimize constant conditional JMPs - * - optimize static BRKs and CONTs */ #include "php.h" @@ -48,7 +47,10 @@ void zend_optimizer_pass2(zend_op_array *op_array) case ZEND_POW: if (ZEND_OP1_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING) { - convert_scalar_to_number(&ZEND_OP1_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (is_numeric_string(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)), NULL, NULL, 0)) { + convert_scalar_to_number(&ZEND_OP1_LITERAL(opline)); + } } } /* break missing *intentionally* - the assign_op's may only optimize op2 */ @@ -63,7 +65,10 @@ void zend_optimizer_pass2(zend_op_array *op_array) } if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) { - convert_scalar_to_number(&ZEND_OP2_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (is_numeric_string(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), NULL, NULL, 0)) { + convert_scalar_to_number(&ZEND_OP2_LITERAL(opline)); + } } } break; @@ -73,7 +78,11 @@ void zend_optimizer_pass2(zend_op_array *op_array) case ZEND_SR: if (ZEND_OP1_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_LONG) { - convert_to_long(&ZEND_OP1_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (!(Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING + && !is_numeric_string(Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)), NULL, NULL, 0))) { + convert_to_long(&ZEND_OP1_LITERAL(opline)); + } } } /* break missing *intentionally - the assign_op's may only optimize op2 */ @@ -86,7 +95,11 @@ void zend_optimizer_pass2(zend_op_array *op_array) } if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_LONG) { - convert_to_long(&ZEND_OP2_LITERAL(opline)); + /* don't optimise if it should produce a runtime numeric string error */ + if (!(Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING + && !is_numeric_string(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), NULL, NULL, 0))) { + convert_to_long(&ZEND_OP2_LITERAL(opline)); + } } } break; @@ -114,14 +127,21 @@ void zend_optimizer_pass2(zend_op_array *op_array) 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 && +#if 0 + /* Disabled unsafe pattern: in conjunction with + * ZEND_VM_SMART_BRANCH() this may improperly eliminate + * assignment to Ti. + */ + if (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; + SET_UNUSED(opline->result); + } else +#endif /* 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) { + 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; @@ -154,10 +174,11 @@ void zend_optimizer_pass2(zend_op_array *op_array) 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) { + if (ZEND_OP2_JMP_ADDR(opline) == ZEND_OP1_JMP_ADDR(opline + 1)) { /* JMPZ(X, L1), JMP(L1) => NOP, JMP(L1) */ if (opline->op1_type == IS_CV) { - break; + opline->opcode = ZEND_CHECK_VAR; + opline->op2.num = 0; } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { opline->opcode = ZEND_FREE; opline->op2.num = 0; @@ -166,10 +187,10 @@ void zend_optimizer_pass2(zend_op_array *op_array) } } else { if (opline->opcode == ZEND_JMPZ) { - opline->extended_value = ZEND_OP1(opline + 1).opline_num; + opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, ZEND_OP1_JMP_ADDR(opline + 1)); } else { - opline->extended_value = ZEND_OP2(opline).opline_num; - COPY_NODE(opline->op2, (opline + 1)->op1); + opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, ZEND_OP2_JMP_ADDR(opline)); + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(opline + 1)); } opline->opcode = ZEND_JMPZNZ; } @@ -178,14 +199,15 @@ void zend_optimizer_pass2(zend_op_array *op_array) case ZEND_JMPZNZ: if (ZEND_OP1_TYPE(opline) == IS_CONST) { - int opline_num; + zend_op *target_opline; + if (zend_is_true(&ZEND_OP1_LITERAL(opline))) { - opline_num = opline->extended_value; /* JMPNZ */ + target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value); /* JMPNZ */ } else { - opline_num = ZEND_OP2(opline).opline_num; /* JMPZ */ + target_opline = ZEND_OP2_JMP_ADDR(opline); /* JMPZ */ } literal_dtor(&ZEND_OP1_LITERAL(opline)); - ZEND_OP1(opline).opline_num = opline_num; + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline); ZEND_OP1_TYPE(opline) = IS_UNUSED; opline->opcode = ZEND_JMP; } diff --git a/ext/opcache/Optimizer/pass3.c b/ext/opcache/Optimizer/pass3.c index 411764398f..e5d032cd29 100644 --- a/ext/opcache/Optimizer/pass3.c +++ b/ext/opcache/Optimizer/pass3.c @@ -39,31 +39,31 @@ /* 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) { \ + if (jmp_hitlist[i] == ZEND_OP1_JMP_ADDR(target)) { \ goto label; \ } \ } \ - jmp_hitlist[jmp_hitlist_count++] = ZEND_OP1(&op_array->opcodes[target]).opline_num; + jmp_hitlist[jmp_hitlist_count++] = ZEND_OP1_JMP_ADDR(target); #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) { \ + if (jmp_hitlist[i] == ZEND_OP2_JMP_ADDR(target)) { \ goto label; \ } \ } \ - jmp_hitlist[jmp_hitlist_count++] = ZEND_OP2(&op_array->opcodes[target]).opline_num; + jmp_hitlist[jmp_hitlist_count++] = ZEND_OP2_JMP_ADDR(target); void zend_optimizer_pass3(zend_op_array *op_array) { zend_op *opline; zend_op *end = op_array->opcodes + op_array->last; - uint32_t *jmp_hitlist; + zend_op **jmp_hitlist; int jmp_hitlist_count; int i; uint32_t opline_num = 0; ALLOCA_FLAG(use_heap); - jmp_hitlist = (uint32_t *)DO_ALLOCA(sizeof(uint32_t)*op_array->last); + jmp_hitlist = (zend_op**)do_alloca(sizeof(zend_op*)*op_array->last, use_heap); opline = op_array->opcodes; while (opline < end) { @@ -93,7 +93,7 @@ void zend_optimizer_pass3(zend_op_array *op_array) break; } - if ((ZEND_OP2_TYPE(opline) == IS_VAR || ZEND_OP2_TYPE(opline) == IS_CV) + if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_CV)) && ZEND_OP2(opline).var == ZEND_OP1(next_opline).var && (opline->opcode == ZEND_ADD || opline->opcode == ZEND_MUL || @@ -114,7 +114,7 @@ void zend_optimizer_pass3(zend_op_array *op_array) COPY_NODE(opline->op2, tmp); } } - if ((ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_CV) + if ((ZEND_OP1_TYPE(opline) & (IS_VAR | IS_CV)) && ZEND_OP1(opline).var == ZEND_OP1(next_opline).var && ZEND_OP1_TYPE(opline) == ZEND_OP1_TYPE(next_opline)) { switch (opline->opcode) { @@ -169,17 +169,17 @@ void zend_optimizer_pass3(zend_op_array *op_array) } /* convert L: JMP L+1 to NOP */ - if (ZEND_OP1(opline).opline_num == opline_num + 1) { + if (ZEND_OP1_JMP_ADDR(opline) == opline + 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; + while (ZEND_OP1_JMP_ADDR(opline) < end + && ZEND_OP1_JMP_ADDR(opline)->opcode == ZEND_JMP) { + zend_op *target = ZEND_OP1_JMP_ADDR(opline); CHECK_JMP(target, done_jmp_optimization); - ZEND_OP1(opline).opline_num = ZEND_OP1(&op_array->opcodes[target]).opline_num; + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(target)); } break; @@ -189,10 +189,10 @@ void zend_optimizer_pass3(zend_op_array *op_array) break; } - 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; + while (ZEND_OP2_JMP_ADDR(opline) < end) { + zend_op *target = ZEND_OP2_JMP_ADDR(opline); + if (target->opcode == ZEND_JMP) { + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(target)); } else { break; } @@ -204,40 +204,41 @@ void zend_optimizer_pass3(zend_op_array *op_array) break; } - while (ZEND_OP2(opline).opline_num < op_array->last) { - int target = ZEND_OP2(opline).opline_num; + while (ZEND_OP2_JMP_ADDR(opline) < end) { + zend_op *target = ZEND_OP2_JMP_ADDR(opline); - if (op_array->opcodes[target].opcode == ZEND_JMP) { + if (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)) { + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(target)); + } else if (target->opcode == opline->opcode && + SAME_VAR(opline->op1, 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)) { + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); + } else if (target->opcode == opline->opcode + 3 && + SAME_VAR(opline->op1, 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); + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); + opline->opcode += 3; + COPY_NODE(opline->result, target->result); break; - } else if (op_array->opcodes[target].opcode == INV_COND(opline->opcode) && - SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + } else if (target->opcode == INV_COND(opline->opcode) && + SAME_VAR(opline->op1, target->op1)) { /* convert JMPZ(X,L1), L1: JMPNZ(X,L2) to JMPZ(X,L1+1) */ - ZEND_OP2(opline).opline_num = target + 1; + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); break; - } else if (op_array->opcodes[target].opcode == INV_COND_EX(opline->opcode) && - SAME_VAR(opline->op1, op_array->opcodes[target].op1)) { + } else if (target->opcode == INV_COND_EX(opline->opcode) && + SAME_VAR(opline->op1, 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; + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); opline->opcode += 3; - COPY_NODE(opline->result, op_array->opcodes[target].result); + COPY_NODE(opline->result, target->result); break; } else { break; @@ -256,7 +257,7 @@ void zend_optimizer_pass3(zend_op_array *op_array) /* 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_OP2_JMP_ADDR(opline) == opline + 1) { if (ZEND_OP1(opline).var == ZEND_RESULT(opline).var) { MAKE_NOP(opline); } else { @@ -266,36 +267,38 @@ void zend_optimizer_pass3(zend_op_array *op_array) 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)) { + while (ZEND_OP2_JMP_ADDR(opline) < end) { + zend_op *target = ZEND_OP2_JMP_ADDR(opline); + + if (SAME_OPCODE_EX(opline->opcode, target->opcode) && + SAME_VAR(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) { + if (target->opcode == opline->opcode) { /* change T only if we have _EX opcode there */ - COPY_NODE(T, op_array->opcodes[target].result); + COPY_NODE(T, 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)) { + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); + } else if (target->opcode == ZEND_JMPZNZ && + SAME_VAR(target->op1, T)) { /* Check for JMPZNZ with same cond variable */ - int new_target; + zend_op *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; + new_target = ZEND_OP2_JMP_ADDR(target); } else { /* JMPNZ_EX */ - new_target = op_array->opcodes[target].extended_value; + new_target = ZEND_OFFSET_TO_OPLINE(target, 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)) { + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_target); + } else if ((target->opcode == INV_EX_COND_EX(opline->opcode) || + target->opcode == INV_EX_COND(opline->opcode)) && + SAME_VAR(opline->op1, 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; + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); + break; } else { break; } @@ -387,19 +390,19 @@ continue_jmp_ex_optimization: } /* 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; + while (ZEND_OP2_JMP_ADDR(opline) < end + && ZEND_OP2_JMP_ADDR(opline)->opcode == ZEND_JMP) { + zend_op *target = ZEND_OP2_JMP_ADDR(opline); CHECK_JMP(target, continue_jmpznz_optimization); - ZEND_OP2(opline).opline_num = ZEND_OP1(&op_array->opcodes[target]).opline_num; + ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(target)); } 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; + while (ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) < end + && ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value)->opcode == ZEND_JMP) { + zend_op *target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value); CHECK_JMP(target, done_jmp_optimization); - opline->extended_value = ZEND_OP1(&op_array->opcodes[target]).opline_num; + opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, ZEND_OP1_JMP_ADDR(target)); } break; @@ -414,15 +417,8 @@ continue_jmpznz_optimization: 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; - } - ZEND_RESULT_TYPE(opline) = IS_VAR | EXT_TYPE_UNUSED; + opline->opcode -= 2; + ZEND_RESULT_TYPE(opline) = IS_UNUSED; } } break; @@ -431,5 +427,5 @@ done_jmp_optimization: opline++; opline_num++; } - FREE_ALLOCA(jmp_hitlist); + free_alloca(jmp_hitlist, use_heap); } diff --git a/ext/opcache/Optimizer/zend_call_graph.c b/ext/opcache/Optimizer/zend_call_graph.c new file mode 100644 index 0000000000..5800a220bc --- /dev/null +++ b/ext/opcache/Optimizer/zend_call_graph.c @@ -0,0 +1,298 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, Call Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id:$ */ + +#include "php.h" +#include "zend_compile.h" +#include "zend_extensions.h" +#include "Optimizer/zend_optimizer.h" +#include "zend_optimizer_internal.h" +#include "zend_inference.h" +#include "zend_call_graph.h" +#include "zend_func_info.h" +#include "zend_inference.h" +#include "zend_call_graph.h" + +typedef int (*zend_op_array_func_t)(zend_call_graph *call_graph, zend_op_array *op_array); + +static int zend_op_array_calc(zend_call_graph *call_graph, zend_op_array *op_array) +{ + (void) op_array; + + call_graph->op_arrays_count++; + return SUCCESS; +} + +static int zend_op_array_collect(zend_call_graph *call_graph, zend_op_array *op_array) +{ + zend_func_info *func_info = call_graph->func_infos + call_graph->op_arrays_count; + + ZEND_SET_FUNC_INFO(op_array, func_info); + call_graph->op_arrays[call_graph->op_arrays_count] = op_array; + func_info->num = call_graph->op_arrays_count; + func_info->num_args = -1; + func_info->return_value_used = -1; + call_graph->op_arrays_count++; + return SUCCESS; +} + +static int zend_foreach_op_array(zend_call_graph *call_graph, zend_script *script, zend_op_array_func_t func) +{ + zend_class_entry *ce; + zend_op_array *op_array; + + if (func(call_graph, &script->main_op_array) != SUCCESS) { + return FAILURE; + } + + ZEND_HASH_FOREACH_PTR(&script->function_table, op_array) { + if (func(call_graph, op_array) != SUCCESS) { + return FAILURE; + } + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_FOREACH_PTR(&script->class_table, ce) { + ZEND_HASH_FOREACH_PTR(&ce->function_table, op_array) { + if (op_array->scope == ce) { + if (func(call_graph, op_array) != SUCCESS) { + return FAILURE; + } + } + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + return SUCCESS; +} + +int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_op_array *op_array, zend_func_info *func_info) +{ + zend_op *opline = op_array->opcodes; + zend_op *end = opline + op_array->last; + zend_function *func; + zend_call_info *call_info; + int call = 0; + zend_call_info **call_stack; + ALLOCA_FLAG(use_heap); + + call_stack = do_alloca((op_array->last / 2) * sizeof(zend_call_info*), use_heap); + call_info = NULL; + while (opline != end) { + switch (opline->opcode) { + case ZEND_INIT_FCALL: + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_STATIC_METHOD_CALL: + call_stack[call] = call_info; + func = zend_optimizer_get_called_func( + script, op_array, opline, (build_flags & ZEND_RT_CONSTANTS) != 0); + if (func) { + call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1))); + call_info->caller_op_array = op_array; + call_info->caller_init_opline = opline; + call_info->caller_call_opline = NULL; + call_info->callee_func = func; + call_info->num_args = opline->extended_value; + call_info->next_callee = func_info->callee_info; + func_info->callee_info = call_info; + + if (build_flags & ZEND_CALL_TREE) { + call_info->next_caller = NULL; + } else if (func->type == ZEND_INTERNAL_FUNCTION) { + call_info->next_caller = NULL; + } else { + zend_func_info *callee_func_info = ZEND_FUNC_INFO(&func->op_array); + if (callee_func_info) { + call_info->next_caller = callee_func_info->caller_info; + callee_func_info->caller_info = call_info; + } else { + call_info->next_caller = NULL; + } + } + } else { + call_info = NULL; + } + call++; + break; + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + case ZEND_INIT_DYNAMIC_CALL: + case ZEND_NEW: + case ZEND_INIT_USER_CALL: + call_stack[call] = call_info; + call_info = NULL; + call++; + break; + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + func_info->flags |= ZEND_FUNC_HAS_CALLS; + if (call_info) { + call_info->caller_call_opline = opline; + } + call--; + call_info = call_stack[call]; + break; + case ZEND_SEND_VAL: + case ZEND_SEND_VAR: + case ZEND_SEND_VAL_EX: + case ZEND_SEND_VAR_EX: + case ZEND_SEND_REF: + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: + if (call_info) { + uint32_t num = opline->op2.num; + + if (num > 0) { + num--; + } + call_info->arg_info[num].opline = opline; + } + break; + case ZEND_SEND_ARRAY: + case ZEND_SEND_USER: + case ZEND_SEND_UNPACK: + /* TODO: set info about var_arg call ??? */ + break; + } + opline++; + } + free_alloca(call_stack, use_heap); + return SUCCESS; +} + +static int zend_is_indirectly_recursive(zend_op_array *root, zend_op_array *op_array, zend_bitset visited) +{ + zend_func_info *func_info; + zend_call_info *call_info; + int ret = 0; + + if (op_array == root) { + return 1; + } + + func_info = ZEND_FUNC_INFO(op_array); + if (zend_bitset_in(visited, func_info->num)) { + return 0; + } + zend_bitset_incl(visited, func_info->num); + call_info = func_info->caller_info; + while (call_info) { + if (zend_is_indirectly_recursive(root, call_info->caller_op_array, visited)) { + call_info->recursive = 1; + ret = 1; + } + call_info = call_info->next_caller; + } + return ret; +} + +static void zend_analyze_recursion(zend_call_graph *call_graph) +{ + zend_op_array *op_array; + zend_func_info *func_info; + zend_call_info *call_info; + int i; + int set_len = zend_bitset_len(call_graph->op_arrays_count); + zend_bitset visited; + ALLOCA_FLAG(use_heap); + + visited = ZEND_BITSET_ALLOCA(set_len, use_heap); + for (i = 0; i < call_graph->op_arrays_count; i++) { + op_array = call_graph->op_arrays[i]; + func_info = call_graph->func_infos + i; + call_info = func_info->caller_info; + while (call_info) { + if (call_info->caller_op_array == op_array) { + call_info->recursive = 1; + func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_DIRECTLY; + } else { + memset(visited, 0, sizeof(zend_ulong) * set_len); + if (zend_is_indirectly_recursive(op_array, call_info->caller_op_array, visited)) { + call_info->recursive = 1; + func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_INDIRECTLY; + } + } + call_info = call_info->next_caller; + } + } + + free_alloca(visited, use_heap); +} + +static void zend_sort_op_arrays(zend_call_graph *call_graph) +{ + (void) call_graph; + + // TODO: perform topological sort of cyclic call graph +} + +int zend_build_call_graph(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_call_graph *call_graph) /* {{{ */ +{ + int i; + + call_graph->op_arrays_count = 0; + if (zend_foreach_op_array(call_graph, script, zend_op_array_calc) != SUCCESS) { + return FAILURE; + } + call_graph->op_arrays = (zend_op_array**)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_op_array*)); + call_graph->func_infos = (zend_func_info*)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_func_info)); + call_graph->op_arrays_count = 0; + if (zend_foreach_op_array(call_graph, script, zend_op_array_collect) != SUCCESS) { + return FAILURE; + } + for (i = 0; i < call_graph->op_arrays_count; i++) { + zend_analyze_calls(arena, script, build_flags, call_graph->op_arrays[i], call_graph->func_infos + i); + } + zend_analyze_recursion(call_graph); + zend_sort_op_arrays(call_graph); + + return SUCCESS; +} +/* }}} */ + +zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info *info, zend_op_array *op_array) /* {{{ */ +{ + zend_call_info **map, *call; + if (!info->callee_info) { + /* Don't build call map if function contains no calls */ + return NULL; + } + + map = zend_arena_calloc(arena, sizeof(zend_call_info *), op_array->last); + for (call = info->callee_info; call; call = call->next_callee) { + int i; + map[call->caller_init_opline - op_array->opcodes] = call; + map[call->caller_call_opline - op_array->opcodes] = call; + for (i = 0; i < call->num_args; i++) { + if (call->arg_info[i].opline) { + map[call->arg_info[i].opline - op_array->opcodes] = call; + } + } + } + return map; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_call_graph.h b/ext/opcache/Optimizer/zend_call_graph.h new file mode 100644 index 0000000000..49c7217c40 --- /dev/null +++ b/ext/opcache/Optimizer/zend_call_graph.h @@ -0,0 +1,86 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, Call Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_CALL_GRAPH_H +#define ZEND_CALL_GRAPH_H + +#include "zend_ssa.h" +#include "zend_func_info.h" +#include "zend_optimizer.h" + +typedef struct _zend_send_arg_info { + zend_op *opline; +} zend_send_arg_info; + +typedef struct _zend_recv_arg_info { + int ssa_var; + zend_ssa_var_info info; +} zend_recv_arg_info; + +struct _zend_call_info { + zend_op_array *caller_op_array; + zend_op *caller_init_opline; + zend_op *caller_call_opline; + zend_function *callee_func; + zend_call_info *next_caller; + zend_call_info *next_callee; + zend_func_info *clone; + int recursive; + int num_args; + zend_send_arg_info arg_info[1]; +}; + +struct _zend_func_info { + int num; + uint32_t flags; + zend_ssa ssa; /* Static Single Assignmnt Form */ + zend_call_info *caller_info; /* where this function is called from */ + zend_call_info *callee_info; /* which functions are called from this one */ + zend_call_info **call_map; /* Call info associated with init/call/send opnum */ + int num_args; /* (-1 - unknown) */ + zend_recv_arg_info *arg_info; + zend_ssa_var_info return_info; + zend_func_info *clone; + int clone_num; + int return_value_used; /* -1 unknown, 0 no, 1 yes */ + void *codegen_data; +}; + +typedef struct _zend_call_graph { + int op_arrays_count; + zend_op_array **op_arrays; + zend_func_info *func_infos; +} zend_call_graph; + +BEGIN_EXTERN_C() + +int zend_build_call_graph(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_call_graph *call_graph); +zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info *info, zend_op_array *op_array); +int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_op_array *op_array, zend_func_info *func_info); + +END_EXTERN_C() + +#endif /* ZEND_CALL_GRAPH_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c new file mode 100644 index 0000000000..ec7116691e --- /dev/null +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -0,0 +1,885 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, CFG - Control Flow Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "zend_compile.h" +#include "zend_cfg.h" +#include "zend_func_info.h" +#include "zend_worklist.h" +#include "zend_optimizer.h" +#include "zend_optimizer_internal.h" + +static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_block *b) /* {{{ */ +{ + zend_uchar opcode; + zend_basic_block *b0; + int successor_0, successor_1; + zend_basic_block *blocks = cfg->blocks; + + while (1) { + b->flags |= ZEND_BB_REACHABLE; + successor_0 = b->successors[0]; + if (successor_0 >= 0) { + successor_1 = b->successors[1]; + if (successor_1 >= 0) { + b0 = blocks + successor_0; + b0->flags |= ZEND_BB_TARGET; + if (!(b0->flags & ZEND_BB_REACHABLE)) { + zend_mark_reachable(opcodes, cfg, b0); + } + + ZEND_ASSERT(b->len != 0); + opcode = opcodes[b->start + b->len - 1].opcode; + b = blocks + successor_1; + if (opcode == ZEND_JMPZNZ) { + b->flags |= ZEND_BB_TARGET; + } else { + b->flags |= ZEND_BB_FOLLOW; + } + } else if (b->len != 0) { + opcode = opcodes[b->start + b->len - 1].opcode; + b = blocks + successor_0; + if (opcode == ZEND_JMP) { + b->flags |= ZEND_BB_TARGET; + } else { + b->flags |= ZEND_BB_FOLLOW; + + if (cfg->split_at_calls) { + if (opcode == ZEND_INCLUDE_OR_EVAL || + opcode == ZEND_GENERATOR_CREATE || + opcode == ZEND_YIELD || + opcode == ZEND_YIELD_FROM || + opcode == ZEND_DO_FCALL || + opcode == ZEND_DO_UCALL || + opcode == ZEND_DO_FCALL_BY_NAME) { + b->flags |= ZEND_BB_ENTRY; + } + } + if (cfg->split_at_recv) { + if (opcode == ZEND_RECV || + opcode == ZEND_RECV_INIT) { + b->flags |= ZEND_BB_RECV_ENTRY; + } + } + } + } else { + b = blocks + successor_0; + b->flags |= ZEND_BB_FOLLOW; + } + if (b->flags & ZEND_BB_REACHABLE) return; + } else { + b->flags |= ZEND_BB_EXIT; + return; + } + } +} +/* }}} */ + +static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg, int start) /* {{{ */ +{ + zend_basic_block *blocks = cfg->blocks; + + blocks[start].flags = ZEND_BB_START; + zend_mark_reachable(op_array->opcodes, cfg, blocks + start); + + if (op_array->last_live_range || op_array->last_try_catch) { + zend_basic_block *b; + int j, changed; + uint32_t *block_map = cfg->map; + + do { + changed = 0; + + /* Add live range paths */ + for (j = 0; j < op_array->last_live_range; j++) { + zend_live_range *live_range = &op_array->live_range[j]; + if (live_range->var == (uint32_t)-1) { + /* this live range already removed */ + continue; + } + b = blocks + block_map[live_range->start]; + if (b->flags & ZEND_BB_REACHABLE) { + while (b->len > 0 && op_array->opcodes[b->start].opcode == ZEND_NOP) { + /* check if NOP breaks incorrect smart branch */ + if (b->len == 2 + && (op_array->opcodes[b->start + 1].opcode == ZEND_JMPZ + || op_array->opcodes[b->start + 1].opcode == ZEND_JMPNZ) + && (op_array->opcodes[b->start + 1].op1_type & (IS_CV|IS_CONST)) + && b->start > 0 + && zend_is_smart_branch(op_array->opcodes + b->start - 1)) { + break; + } + b->start++; + b->len--; + } + if (b->len == 0 && (uint32_t)b->successors[0] == block_map[live_range->end]) { + /* mark as removed (empty live range) */ + live_range->var = (uint32_t)-1; + continue; + } + b->flags |= ZEND_BB_GEN_VAR; + b = blocks + block_map[live_range->end]; + b->flags |= ZEND_BB_KILL_VAR; + if (!(b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE))) { + if (cfg->split_at_live_ranges) { + changed = 1; + zend_mark_reachable(op_array->opcodes, cfg, b); + } else { + ZEND_ASSERT(b->start == live_range->end); + b->flags |= ZEND_BB_UNREACHABLE_FREE; + } + } + } else { + ZEND_ASSERT(!(blocks[block_map[live_range->end]].flags & ZEND_BB_REACHABLE)); + } + } + + /* Add exception paths */ + for (j = 0; j < op_array->last_try_catch; j++) { + + /* check for jumps into the middle of try block */ + b = blocks + block_map[op_array->try_catch_array[j].try_op]; + if (!(b->flags & ZEND_BB_REACHABLE)) { + zend_basic_block *end; + + if (op_array->try_catch_array[j].catch_op) { + end = blocks + block_map[op_array->try_catch_array[j].catch_op]; + while (b != end) { + if (b->flags & ZEND_BB_REACHABLE) { + op_array->try_catch_array[j].try_op = b->start; + break; + } + b++; + } + } + b = blocks + block_map[op_array->try_catch_array[j].try_op]; + if (!(b->flags & ZEND_BB_REACHABLE)) { + if (op_array->try_catch_array[j].finally_op) { + end = blocks + block_map[op_array->try_catch_array[j].finally_op]; + while (b != end) { + if (b->flags & ZEND_BB_REACHABLE) { + op_array->try_catch_array[j].try_op = op_array->try_catch_array[j].catch_op; + changed = 1; + zend_mark_reachable(op_array->opcodes, cfg, blocks + block_map[op_array->try_catch_array[j].try_op]); + break; + } + b++; + } + } + } + } + + b = blocks + block_map[op_array->try_catch_array[j].try_op]; + if (b->flags & ZEND_BB_REACHABLE) { + b->flags |= ZEND_BB_TRY; + if (op_array->try_catch_array[j].catch_op) { + b = blocks + block_map[op_array->try_catch_array[j].catch_op]; + b->flags |= ZEND_BB_CATCH; + if (!(b->flags & ZEND_BB_REACHABLE)) { + changed = 1; + zend_mark_reachable(op_array->opcodes, cfg, b); + } + } + if (op_array->try_catch_array[j].finally_op) { + b = blocks + block_map[op_array->try_catch_array[j].finally_op]; + b->flags |= ZEND_BB_FINALLY; + if (!(b->flags & ZEND_BB_REACHABLE)) { + changed = 1; + zend_mark_reachable(op_array->opcodes, cfg, b); + } + } + if (op_array->try_catch_array[j].finally_end) { + b = blocks + block_map[op_array->try_catch_array[j].finally_end]; + b->flags |= ZEND_BB_FINALLY_END; + if (!(b->flags & ZEND_BB_REACHABLE)) { + changed = 1; + zend_mark_reachable(op_array->opcodes, cfg, b); + } + } + } else { + if (op_array->try_catch_array[j].catch_op) { + ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].catch_op]].flags & ZEND_BB_REACHABLE)); + } + if (op_array->try_catch_array[j].finally_op) { + ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_op]].flags & ZEND_BB_REACHABLE)); + } + if (op_array->try_catch_array[j].finally_end) { + ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_end]].flags & ZEND_BB_REACHABLE)); + } + } + } + } while (changed); + } +} +/* }}} */ + +void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */ +{ + zend_basic_block *blocks = cfg->blocks; + int i; + int start = 0; + + for (i = 0; i < cfg->blocks_count; i++) { + if (blocks[i].flags & ZEND_BB_REACHABLE) { + start = i; + i++; + break; + } + } + + /* clear all flags */ + for (i = 0; i < cfg->blocks_count; i++) { + blocks[i].flags = 0; + } + + zend_mark_reachable_blocks(op_array, cfg, start); +} +/* }}} */ + +static void record_successor(zend_basic_block *blocks, int pred, int n, int succ) +{ + blocks[pred].successors[n] = succ; +} + +static void initialize_block(zend_basic_block *block) { + block->flags = 0; + block->successors[0] = -1; + block->successors[1] = -1; + block->predecessors_count = 0; + block->predecessor_offset = -1; + block->idom = -1; + block->loop_header = -1; + block->level = -1; + block->children = -1; + block->next_child = -1; +} + +#define BB_START(i) do { \ + if (!block_map[i]) { blocks_count++;} \ + block_map[i]++; \ + } while (0) + +int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg, uint32_t *func_flags) /* {{{ */ +{ + uint32_t flags = 0; + uint32_t i; + int j; + uint32_t *block_map; + zend_function *fn; + int blocks_count = 0; + zend_basic_block *blocks; + zval *zv; + zend_bool extra_entry_block = 0; + + cfg->split_at_live_ranges = (build_flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES) != 0; + cfg->split_at_calls = (build_flags & ZEND_CFG_STACKLESS) != 0; + cfg->split_at_recv = (build_flags & ZEND_CFG_RECV_ENTRY) != 0 && (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0; + + cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t)); + if (!block_map) { + return FAILURE; + } + + /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */ + BB_START(0); + for (i = 0; i < op_array->last; i++) { + zend_op *opline = op_array->opcodes + i; + switch(opline->opcode) { + case ZEND_RECV: + case ZEND_RECV_INIT: + if (build_flags & ZEND_CFG_RECV_ENTRY) { + BB_START(i + 1); + } + break; + case ZEND_RETURN: + case ZEND_RETURN_BY_REF: + case ZEND_GENERATOR_RETURN: + case ZEND_EXIT: + case ZEND_THROW: + if (i + 1 < op_array->last) { + BB_START(i + 1); + } + break; + case ZEND_INCLUDE_OR_EVAL: + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; + case ZEND_GENERATOR_CREATE: + case ZEND_YIELD: + case ZEND_YIELD_FROM: + if (build_flags & ZEND_CFG_STACKLESS) { + BB_START(i + 1); + } + break; + case ZEND_DO_FCALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + flags |= ZEND_FUNC_HAS_CALLS; + if (build_flags & ZEND_CFG_STACKLESS) { + BB_START(i + 1); + } + break; + case ZEND_DO_ICALL: + flags |= ZEND_FUNC_HAS_CALLS; + break; + case ZEND_INIT_FCALL: + case ZEND_INIT_NS_FCALL_BY_NAME: + zv = CRT_CONSTANT(opline->op2); + if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { + /* The third literal is the lowercased unqualified name */ + zv += 2; + } + if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { + if (fn->type == ZEND_INTERNAL_FUNCTION) { + flags |= zend_optimizer_classify_function( + Z_STR_P(zv), opline->extended_value); + } + } + break; + case ZEND_FAST_CALL: + BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); + BB_START(i + 1); + break; + case ZEND_FAST_RET: + if (i + 1 < op_array->last) { + BB_START(i + 1); + } + break; + case ZEND_JMP: + BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); + if (i + 1 < op_array->last) { + BB_START(i + 1); + } + break; + case ZEND_JMPZNZ: + BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); + BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + if (i + 1 < op_array->last) { + BB_START(i + 1); + } + break; + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); + BB_START(i + 1); + break; + case ZEND_CATCH: + if (!opline->result.num) { + BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + } + BB_START(i + 1); + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + BB_START(i + 1); + break; + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); + BB_START(i + 1); + break; + case ZEND_UNSET_VAR: + case ZEND_ISSET_ISEMPTY_VAR: + if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) && + !(opline->extended_value & ZEND_QUICK_SET)) { + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || + (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && + !op_array->function_name) { + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; + } + break; + 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->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) { + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || + (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && + !op_array->function_name) { + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; + } + break; + } + } + + /* If the entry block has predecessors, we may need to split it */ + if ((build_flags & ZEND_CFG_NO_ENTRY_PREDECESSORS) + && op_array->last > 0 && block_map[0] > 1) { + extra_entry_block = 1; + } + + if (cfg->split_at_live_ranges) { + for (j = 0; j < op_array->last_live_range; j++) { + BB_START(op_array->live_range[j].start); + BB_START(op_array->live_range[j].end); + } + } + + if (op_array->last_try_catch) { + for (j = 0; j < op_array->last_try_catch; j++) { + BB_START(op_array->try_catch_array[j].try_op); + if (op_array->try_catch_array[j].catch_op) { + BB_START(op_array->try_catch_array[j].catch_op); + } + if (op_array->try_catch_array[j].finally_op) { + BB_START(op_array->try_catch_array[j].finally_op); + } + if (op_array->try_catch_array[j].finally_end) { + BB_START(op_array->try_catch_array[j].finally_end); + } + } + } + + blocks_count += extra_entry_block; + cfg->blocks_count = blocks_count; + + /* Build CFG, Step 2: Build Array of Basic Blocks */ + cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count); + if (!blocks) { + return FAILURE; + } + + blocks_count = -1; + + if (extra_entry_block) { + initialize_block(&blocks[0]); + blocks[0].start = 0; + blocks[0].len = 0; + blocks_count++; + } + + for (i = 0; i < op_array->last; i++) { + if (block_map[i]) { + if (blocks_count >= 0) { + blocks[blocks_count].len = i - blocks[blocks_count].start; + } + blocks_count++; + initialize_block(&blocks[blocks_count]); + blocks[blocks_count].start = i; + } + block_map[i] = blocks_count; + } + + blocks[blocks_count].len = i - blocks[blocks_count].start; + blocks_count++; + + /* Build CFG, Step 3: Calculate successors */ + for (j = 0; j < blocks_count; j++) { + zend_op *opline; + if (blocks[j].len == 0) { + record_successor(blocks, j, 0, j + 1); + continue; + } + + opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1; + switch (opline->opcode) { + case ZEND_FAST_RET: + case ZEND_RETURN: + case ZEND_RETURN_BY_REF: + case ZEND_GENERATOR_RETURN: + case ZEND_EXIT: + case ZEND_THROW: + break; + case ZEND_JMP: + record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); + break; + case ZEND_JMPZNZ: + record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); + record_successor(blocks, j, 1, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + break; + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_ASSERT_CHECK: + record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); + record_successor(blocks, j, 1, j + 1); + break; + case ZEND_CATCH: + if (!opline->result.num) { + record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + record_successor(blocks, j, 1, j + 1); + } else { + record_successor(blocks, j, 0, j + 1); + } + break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); + record_successor(blocks, j, 1, j + 1); + break; + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); + record_successor(blocks, j, 1, j + 1); + break; + case ZEND_FAST_CALL: + record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); + record_successor(blocks, j, 1, j + 1); + break; + default: + record_successor(blocks, j, 0, j + 1); + break; + } + } + + /* Build CFG, Step 4, Mark Reachable Basic Blocks */ + zend_mark_reachable_blocks(op_array, cfg, 0); + + if (func_flags) { + *func_flags |= flags; + } + + return SUCCESS; +} +/* }}} */ + +int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */ +{ + int j, edges; + zend_basic_block *b; + zend_basic_block *blocks = cfg->blocks; + zend_basic_block *end = blocks + cfg->blocks_count; + int *predecessors; + + edges = 0; + for (b = blocks; b < end; b++) { + b->predecessors_count = 0; + } + for (b = blocks; b < end; b++) { + if (!(b->flags & ZEND_BB_REACHABLE)) { + b->successors[0] = -1; + b->successors[1] = -1; + b->predecessors_count = 0; + } else { + if (b->successors[0] >= 0) { + edges++; + blocks[b->successors[0]].predecessors_count++; + if (b->successors[1] >= 0 && b->successors[1] != b->successors[0]) { + edges++; + blocks[b->successors[1]].predecessors_count++; + } + } + } + } + + cfg->predecessors = predecessors = (int*)zend_arena_calloc(arena, sizeof(int), edges); + + if (!predecessors) { + return FAILURE; + } + + edges = 0; + for (b = blocks; b < end; b++) { + if (b->flags & ZEND_BB_REACHABLE) { + b->predecessor_offset = edges; + edges += b->predecessors_count; + b->predecessors_count = 0; + } + } + + for (j = 0; j < cfg->blocks_count; j++) { + if (blocks[j].flags & ZEND_BB_REACHABLE) { + if (blocks[j].successors[0] >= 0) { + zend_basic_block *b = blocks + blocks[j].successors[0]; + predecessors[b->predecessor_offset + b->predecessors_count] = j; + b->predecessors_count++; + if (blocks[j].successors[1] >= 0 + && blocks[j].successors[1] != blocks[j].successors[0]) { + zend_basic_block *b = blocks + blocks[j].successors[1]; + predecessors[b->predecessor_offset + b->predecessors_count] = j; + b->predecessors_count++; + } + } + } + } + + return SUCCESS; +} +/* }}} */ + +/* Computes a postorder numbering of the CFG */ +static void compute_postnum_recursive( + int *postnum, int *cur, const zend_cfg *cfg, int block_num) /* {{{ */ +{ + zend_basic_block *block = &cfg->blocks[block_num]; + if (postnum[block_num] != -1) { + return; + } + + postnum[block_num] = -2; /* Marker for "currently visiting" */ + if (block->successors[0] >= 0) { + compute_postnum_recursive(postnum, cur, cfg, block->successors[0]); + if (block->successors[1] >= 0) { + compute_postnum_recursive(postnum, cur, cfg, block->successors[1]); + } + } + postnum[block_num] = (*cur)++; +} +/* }}} */ + +/* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by + * Cooper, Harvey and Kennedy. */ +int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */ +{ + zend_basic_block *blocks = cfg->blocks; + int blocks_count = cfg->blocks_count; + int j, k, changed; + + ALLOCA_FLAG(use_heap) + int *postnum = do_alloca(sizeof(int) * cfg->blocks_count, use_heap); + memset(postnum, -1, sizeof(int) * cfg->blocks_count); + j = 0; + compute_postnum_recursive(postnum, &j, cfg, 0); + + /* FIXME: move declarations */ + blocks[0].idom = 0; + do { + changed = 0; + /* Iterating in RPO here would converge faster */ + for (j = 1; j < blocks_count; j++) { + int idom = -1; + + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + for (k = 0; k < blocks[j].predecessors_count; k++) { + int pred = cfg->predecessors[blocks[j].predecessor_offset + k]; + + if (idom < 0) { + if (blocks[pred].idom >= 0) + idom = pred; + continue; + } + + if (blocks[pred].idom >= 0) { + while (idom != pred) { + while (postnum[pred] < postnum[idom]) pred = blocks[pred].idom; + while (postnum[idom] < postnum[pred]) idom = blocks[idom].idom; + } + } + } + + if (idom >= 0 && blocks[j].idom != idom) { + blocks[j].idom = idom; + changed = 1; + } + } + } while (changed); + blocks[0].idom = -1; + + for (j = 1; j < blocks_count; j++) { + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + if (blocks[j].idom >= 0) { + /* Sort by block number to traverse children in pre-order */ + if (blocks[blocks[j].idom].children < 0 || + j < blocks[blocks[j].idom].children) { + blocks[j].next_child = blocks[blocks[j].idom].children; + blocks[blocks[j].idom].children = j; + } else { + int k = blocks[blocks[j].idom].children; + while (blocks[k].next_child >=0 && j > blocks[k].next_child) { + k = blocks[k].next_child; + } + blocks[j].next_child = blocks[k].next_child; + blocks[k].next_child = j; + } + } + } + + for (j = 0; j < blocks_count; j++) { + int idom = blocks[j].idom, level = 0; + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + while (idom >= 0) { + level++; + if (blocks[idom].level >= 0) { + level += blocks[idom].level; + break; + } else { + idom = blocks[idom].idom; + } + } + blocks[j].level = level; + } + + free_alloca(postnum, use_heap); + return SUCCESS; +} +/* }}} */ + +static int dominates(zend_basic_block *blocks, int a, int b) /* {{{ */ +{ + while (blocks[b].level > blocks[a].level) { + b = blocks[b].idom; + } + return a == b; +} +/* }}} */ + +typedef struct { + int id; + int level; +} block_info; +static int compare_block_level(const block_info *a, const block_info *b) { + return b->level - a->level; +} +static void swap_blocks(block_info *a, block_info *b) { + block_info tmp = *a; + *a = *b; + *b = tmp; +} + +int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32_t *flags) /* {{{ */ +{ + int i, j, k, n; + int time; + zend_basic_block *blocks = cfg->blocks; + int *entry_times, *exit_times; + zend_worklist work; + int flag = ZEND_FUNC_NO_LOOPS; + block_info *sorted_blocks; + ALLOCA_FLAG(list_use_heap) + ALLOCA_FLAG(tree_use_heap) + ALLOCA_FLAG(sorted_blocks_use_heap) + + ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap); + + /* We don't materialize the DJ spanning tree explicitly, as we are only interested in ancestor + * queries. These are implemented by checking entry/exit times of the DFS search. */ + entry_times = do_alloca(2 * sizeof(int) * cfg->blocks_count, tree_use_heap); + exit_times = entry_times + cfg->blocks_count; + memset(entry_times, -1, 2 * sizeof(int) * cfg->blocks_count); + + zend_worklist_push(&work, 0); + time = 0; + while (zend_worklist_len(&work)) { + next: + i = zend_worklist_peek(&work); + if (entry_times[i] == -1) { + entry_times[i] = time++; + } + /* Visit blocks immediately dominated by i. */ + for (j = blocks[i].children; j >= 0; j = blocks[j].next_child) { + if (zend_worklist_push(&work, j)) { + goto next; + } + } + /* Visit join edges. */ + for (j = 0; j < 2; j++) { + int succ = blocks[i].successors[j]; + if (succ < 0) { + continue; + } else if (blocks[succ].idom == i) { + continue; + } else if (zend_worklist_push(&work, succ)) { + goto next; + } + } + exit_times[i] = time++; + zend_worklist_pop(&work); + } + + /* Sort blocks by decreasing level, which is the order in which we want to process them */ + sorted_blocks = do_alloca(sizeof(block_info) * cfg->blocks_count, sorted_blocks_use_heap); + for (i = 0; i < cfg->blocks_count; i++) { + sorted_blocks[i].id = i; + sorted_blocks[i].level = blocks[i].level; + } + zend_sort(sorted_blocks, cfg->blocks_count, sizeof(block_info), + (compare_func_t) compare_block_level, (swap_func_t) swap_blocks); + + /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ + Graphs". */ + + for (n = 0; n < cfg->blocks_count; n++) { + i = sorted_blocks[n].id; + + zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count)); + for (j = 0; j < blocks[i].predecessors_count; j++) { + int pred = cfg->predecessors[blocks[i].predecessor_offset + j]; + + /* A join edge is one for which the predecessor does not + immediately dominate the successor. */ + if (blocks[i].idom == pred) { + continue; + } + + /* In a loop back-edge (back-join edge), the successor dominates + the predecessor. */ + if (dominates(blocks, i, pred)) { + blocks[i].flags |= ZEND_BB_LOOP_HEADER; + flag &= ~ZEND_FUNC_NO_LOOPS; + zend_worklist_push(&work, pred); + } else { + /* Otherwise it's a cross-join edge. See if it's a branch + to an ancestor on the DJ spanning tree. */ + if (entry_times[pred] > entry_times[i] && exit_times[pred] < exit_times[i]) { + blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP; + flag |= ZEND_FUNC_IRREDUCIBLE; + flag &= ~ZEND_FUNC_NO_LOOPS; + } + } + } + while (zend_worklist_len(&work)) { + j = zend_worklist_pop(&work); + while (blocks[j].loop_header >= 0) { + j = blocks[j].loop_header; + } + if (j != i) { + blocks[j].loop_header = i; + for (k = 0; k < blocks[j].predecessors_count; k++) { + zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]); + } + } + } + } + + free_alloca(sorted_blocks, sorted_blocks_use_heap); + free_alloca(entry_times, tree_use_heap); + ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap); + *flags |= flag; + + return SUCCESS; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_cfg.h b/ext/opcache/Optimizer/zend_cfg.h new file mode 100644 index 0000000000..7b80d83f11 --- /dev/null +++ b/ext/opcache/Optimizer/zend_cfg.h @@ -0,0 +1,137 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, CFG - Control Flow Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_CFG_H +#define ZEND_CFG_H + +/* zend_basic_bloc.flags */ +#define ZEND_BB_START (1<<0) /* fist block */ +#define ZEND_BB_FOLLOW (1<<1) /* follows the next block */ +#define ZEND_BB_TARGET (1<<2) /* jump taget */ +#define ZEND_BB_EXIT (1<<3) /* without successors */ +#define ZEND_BB_ENTRY (1<<4) /* stackless entry */ +#define ZEND_BB_TRY (1<<5) /* start of try block */ +#define ZEND_BB_CATCH (1<<6) /* start of catch block */ +#define ZEND_BB_FINALLY (1<<7) /* start of finally block */ +#define ZEND_BB_FINALLY_END (1<<8) /* end of finally block */ +#define ZEND_BB_GEN_VAR (1<<9) /* start of live range */ +#define ZEND_BB_KILL_VAR (1<<10) /* end of live range */ +#define ZEND_BB_UNREACHABLE_FREE (1<<11) /* unreachable loop free */ +#define ZEND_BB_RECV_ENTRY (1<<12) /* RECV entry */ + +#define ZEND_BB_LOOP_HEADER (1<<16) +#define ZEND_BB_IRREDUCIBLE_LOOP (1<<17) + +#define ZEND_BB_REACHABLE (1<<31) + +#define ZEND_BB_PROTECTED (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY|ZEND_BB_TRY|ZEND_BB_CATCH|ZEND_BB_FINALLY|ZEND_BB_FINALLY_END|ZEND_BB_GEN_VAR|ZEND_BB_KILL_VAR) + +typedef struct _zend_basic_block { + uint32_t flags; + uint32_t start; /* first opcode number */ + uint32_t len; /* number of opcodes */ + int successors[2]; /* up to 2 successor blocks */ + int predecessors_count; /* number of predecessors */ + int predecessor_offset; /* offset of 1-st predecessor */ + int idom; /* immediate dominator block */ + int loop_header; /* closest loop header, or -1 */ + int level; /* steps away from the entry in the dom. tree */ + int children; /* list of dominated blocks */ + int next_child; /* next dominated block */ +} zend_basic_block; + +/* ++------------+---+---+---+---+---+ +| |OP1|OP2|EXT| 0 | 1 | ++------------+---+---+---+---+---+ +|JMP |ADR| | |OP1| - | +|JMPZ | |ADR| |OP2|FOL| +|JMPNZ | |ADR| |OP2|FOL| +|JMPZNZ | |ADR|ADR|OP2|EXT| +|JMPZ_EX | |ADR| |OP2|FOL| +|JMPNZ_EX | |ADR| |OP2|FOL| +|JMP_SET | |ADR| |OP2|FOL| +|COALESCE | |ADR| |OP2|FOL| +|ASSERT_CHK | |ADR| |OP2|FOL| +|NEW | |ADR| |OP2|FOL| +|DCL_ANON* |ADR| | |OP1|FOL| +|FE_RESET_* | |ADR| |OP2|FOL| +|FE_FETCH_* | | |ADR|EXT|FOL| +|CATCH | | |ADR|EXT|FOL| +|FAST_CALL |ADR| | |OP1|FOL| +|FAST_RET | | | | - | - | +|RETURN* | | | | - | - | +|EXIT | | | | - | - | +|THROW | | | | - | - | +|* | | | |FOL| - | ++------------+---+---+---+---+---+ +*/ + +typedef struct _zend_cfg { + int blocks_count; /* number of basic blocks */ + zend_basic_block *blocks; /* array of basic blocks */ + int *predecessors; + uint32_t *map; + unsigned int split_at_live_ranges : 1; + unsigned int split_at_calls : 1; + unsigned int split_at_recv : 1; +} zend_cfg; + +/* Build Flags */ +#define ZEND_RT_CONSTANTS (1<<31) +#define ZEND_CFG_STACKLESS (1<<30) +#define ZEND_SSA_DEBUG_LIVENESS (1<<29) +#define ZEND_SSA_DEBUG_PHI_PLACEMENT (1<<28) +#define ZEND_SSA_RC_INFERENCE (1<<27) +#define ZEND_CFG_SPLIT_AT_LIVE_RANGES (1<<26) +#define ZEND_CFG_NO_ENTRY_PREDECESSORS (1<<25) +#define ZEND_CFG_RECV_ENTRY (1<<24) +#define ZEND_CALL_TREE (1<<23) + +#define CRT_CONSTANT_EX(op_array, node, rt_constants) \ + ((rt_constants) ? \ + RT_CONSTANT(op_array, (node)) \ + : \ + CT_CONSTANT_EX(op_array, (node).constant) \ + ) + +#define CRT_CONSTANT(node) \ + CRT_CONSTANT_EX(op_array, node, (build_flags & ZEND_RT_CONSTANTS)) + +#define RETURN_VALUE_USED(opline) \ + ((opline)->result_type != IS_UNUSED) + +BEGIN_EXTERN_C() + +int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg, uint32_t *func_flags); +void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg); +int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg); +int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg); +int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32_t *flags); + +END_EXTERN_C() + +#endif /* ZEND_CFG_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_dfg.c b/ext/opcache/Optimizer/zend_dfg.c new file mode 100644 index 0000000000..374c8146c8 --- /dev/null +++ b/ext/opcache/Optimizer/zend_dfg.c @@ -0,0 +1,254 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, DFG - Data Flow Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "zend_compile.h" +#include "zend_dfg.h" + +int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg *dfg, uint32_t build_flags) /* {{{ */ +{ + int set_size; + zend_basic_block *blocks = cfg->blocks; + int blocks_count = cfg->blocks_count; + zend_bitset tmp, def, use, in, out; + int k; + uint32_t var_num; + int j; + + set_size = dfg->size; + tmp = dfg->tmp; + def = dfg->def; + use = dfg->use; + in = dfg->in; + out = dfg->out; + + /* Collect "def" and "use" sets */ + for (j = 0; j < blocks_count; j++) { + zend_op *opline, *end; + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + + opline = op_array->opcodes + blocks[j].start; + end = opline + blocks[j].len; + for (; opline < end; opline++) { + if (opline->opcode != ZEND_OP_DATA) { + zend_op *next = opline + 1; + if (next < end && next->opcode == ZEND_OP_DATA) { + if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(next->op1.var); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); + } + } + if (next->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(next->op2.var); + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); + } + } + } + if (opline->op1_type == IS_CV) { + var_num = EX_VAR_TO_NUM(opline->op1.var); + switch (opline->opcode) { + case ZEND_ADD_ARRAY_ELEMENT: + case ZEND_INIT_ARRAY: + if ((build_flags & ZEND_SSA_RC_INFERENCE) + || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) { + goto op1_def; + } + goto op1_use; + case ZEND_FE_RESET_R: + case ZEND_SEND_VAR: + case ZEND_CAST: + case ZEND_QM_ASSIGN: + case ZEND_JMP_SET: + case ZEND_COALESCE: + if (build_flags & ZEND_SSA_RC_INFERENCE) { + goto op1_def; + } + goto op1_use; + case ZEND_YIELD: + if ((build_flags & ZEND_SSA_RC_INFERENCE) + || (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) { + goto op1_def; + } + goto op1_use; + case ZEND_UNSET_VAR: + ZEND_ASSERT(opline->extended_value & ZEND_QUICK_SET); + /* break missing intentionally */ + case ZEND_ASSIGN: + case ZEND_ASSIGN_REF: + case ZEND_BIND_GLOBAL: + case ZEND_BIND_STATIC: + case ZEND_SEND_VAR_EX: + case ZEND_SEND_REF: + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: + case ZEND_FE_RESET_RW: + 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_ASSIGN_POW: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_POST_INC: + case ZEND_POST_DEC: + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + case ZEND_UNSET_DIM: + case ZEND_UNSET_OBJ: + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: + case ZEND_FETCH_DIM_FUNC_ARG: + case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_OBJ_W: + case ZEND_FETCH_OBJ_RW: + case ZEND_FETCH_OBJ_FUNC_ARG: + case ZEND_FETCH_OBJ_UNSET: + case ZEND_VERIFY_RETURN_TYPE: +op1_def: + /* `def` always come along with dtor or separation, + * thus the origin var info might be also `use`d in the feature(CG) */ + DFG_SET(use, set_size, j, var_num); + DFG_SET(def, set_size, j, var_num); + break; + default: +op1_use: + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); + } + } + } else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(opline->op1.var); + if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) { + DFG_SET(use, set_size, j, var_num); + DFG_SET(def, set_size, j, var_num); + } else if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); + } + } + if (opline->op2_type == IS_CV) { + var_num = EX_VAR_TO_NUM(opline->op2.var); + switch (opline->opcode) { + case ZEND_ASSIGN: + if (build_flags & ZEND_SSA_RC_INFERENCE) { + goto op2_def; + } + goto op2_use; + case ZEND_BIND_LEXICAL: + if ((build_flags & ZEND_SSA_RC_INFERENCE) || opline->extended_value) { + goto op2_def; + } + goto op2_use; + case ZEND_ASSIGN_REF: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: +op2_def: + // FIXME: include into "use" too ...? + DFG_SET(use, set_size, j, var_num); + DFG_SET(def, set_size, j, var_num); + break; + default: +op2_use: + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); + } + break; + } + } else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(opline->op2.var); + if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) { + DFG_SET(def, set_size, j, var_num); + } else { + if (!DFG_ISSET(def, set_size, j, var_num)) { + DFG_SET(use, set_size, j, var_num); + } + } + } + if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + var_num = EX_VAR_TO_NUM(opline->result.var); + DFG_SET(def, set_size, j, var_num); + } + } + } + } + + /* Calculate "in" and "out" sets */ + { + uint32_t worklist_len = zend_bitset_len(blocks_count); + zend_bitset worklist; + ALLOCA_FLAG(use_heap); + worklist = ZEND_BITSET_ALLOCA(worklist_len, use_heap); + memset(worklist, 0, worklist_len * ZEND_BITSET_ELM_SIZE); + for (j = 0; j < blocks_count; j++) { + zend_bitset_incl(worklist, j); + } + while (!zend_bitset_empty(worklist, worklist_len)) { + /* We use the last block on the worklist, because predecessors tend to be located + * before the succeeding block, so this converges faster. */ + j = zend_bitset_last(worklist, worklist_len); + zend_bitset_excl(worklist, j); + + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + if (blocks[j].successors[0] >= 0) { + zend_bitset_copy(DFG_BITSET(out, set_size, j), DFG_BITSET(in, set_size, blocks[j].successors[0]), set_size); + if (blocks[j].successors[1] >= 0) { + zend_bitset_union(DFG_BITSET(out, set_size, j), DFG_BITSET(in, set_size, blocks[j].successors[1]), set_size); + } + } else { + zend_bitset_clear(DFG_BITSET(out, set_size, j), set_size); + } + zend_bitset_union_with_difference(tmp, DFG_BITSET(use, set_size, j), DFG_BITSET(out, set_size, j), DFG_BITSET(def, set_size, j), set_size); + if (!zend_bitset_equal(DFG_BITSET(in, set_size, j), tmp, set_size)) { + zend_bitset_copy(DFG_BITSET(in, set_size, j), tmp, set_size); + + /* Add predecessors of changed block to worklist */ + { + int *predecessors = &cfg->predecessors[blocks[j].predecessor_offset]; + for (k = 0; k < blocks[j].predecessors_count; k++) { + zend_bitset_incl(worklist, predecessors[k]); + } + } + } + } + + free_alloca(worklist, use_heap); + } + + return SUCCESS; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_dfg.h b/ext/opcache/Optimizer/zend_dfg.h new file mode 100644 index 0000000000..06ee46be32 --- /dev/null +++ b/ext/opcache/Optimizer/zend_dfg.h @@ -0,0 +1,58 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, DFG - Data Flow Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_DFG_H +#define ZEND_DFG_H + +#include "zend_bitset.h" +#include "zend_cfg.h" + +typedef struct _zend_dfg { + int vars; + uint32_t size; + zend_bitset tmp; + zend_bitset def; + zend_bitset use; + zend_bitset in; + zend_bitset out; +} zend_dfg; + +#define DFG_BITSET(set, set_size, block_num) \ + ((set) + ((block_num) * (set_size))) + +#define DFG_SET(set, set_size, block_num, var_num) \ + zend_bitset_incl(DFG_BITSET(set, set_size, block_num), (var_num)) + +#define DFG_ISSET(set, set_size, block_num, var_num) \ + zend_bitset_in(DFG_BITSET(set, set_size, block_num), (var_num)) + +BEGIN_EXTERN_C() + +int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg *dfg, uint32_t build_flags); + +END_EXTERN_C() + +#endif /* ZEND_DFG_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_dump.c b/ext/opcache/Optimizer/zend_dump.c new file mode 100644 index 0000000000..2167fa6e6b --- /dev/null +++ b/ext/opcache/Optimizer/zend_dump.c @@ -0,0 +1,1177 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, Bytecode Visualisation | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "zend_compile.h" +#include "zend_cfg.h" +#include "zend_ssa.h" +#include "zend_inference.h" +#include "zend_func_info.h" +#include "zend_call_graph.h" +#include "zend_dump.h" + +static void zend_dump_const(const zval *zv) +{ + switch (Z_TYPE_P(zv)) { + case IS_NULL: + fprintf(stderr, " null"); + break; + case IS_FALSE: + fprintf(stderr, " bool(false)"); + break; + case IS_TRUE: + fprintf(stderr, " bool(true)"); + break; + case IS_LONG: + fprintf(stderr, " int(" ZEND_LONG_FMT ")", Z_LVAL_P(zv)); + break; + case IS_DOUBLE: + fprintf(stderr, " float(%g)", Z_DVAL_P(zv)); + break; + case IS_STRING: + fprintf(stderr, " string(\"%s\")", Z_STRVAL_P(zv)); + break; + case IS_ARRAY: + fprintf(stderr, " array(...)"); + break; + default: + fprintf(stderr, " zval(type=%d)", Z_TYPE_P(zv)); + break; + } +} + +static void zend_dump_class_fetch_type(uint32_t fetch_type) +{ + switch (fetch_type & ZEND_FETCH_CLASS_MASK) { + case ZEND_FETCH_CLASS_SELF: + fprintf(stderr, " (self)"); + break; + case ZEND_FETCH_CLASS_PARENT: + fprintf(stderr, " (parent)"); + break; + case ZEND_FETCH_CLASS_STATIC: + fprintf(stderr, " (static)"); + break; + case ZEND_FETCH_CLASS_AUTO: + fprintf(stderr, " (auto)"); + break; + case ZEND_FETCH_CLASS_INTERFACE: + fprintf(stderr, " (interface)"); + break; + case ZEND_FETCH_CLASS_TRAIT: + fprintf(stderr, " (trait)"); + break; + } + if (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) { + fprintf(stderr, " (no-autolod)"); + } + if (fetch_type & ZEND_FETCH_CLASS_SILENT) { + fprintf(stderr, " (silent)"); + } + if (fetch_type & ZEND_FETCH_CLASS_EXCEPTION) { + fprintf(stderr, " (exception)"); + } +} + +static void zend_dump_unused_op(const zend_op *opline, znode_op op, uint32_t flags) { + if (ZEND_VM_OP_NUM == (flags & ZEND_VM_OP_MASK)) { + fprintf(stderr, " %u", op.num); + } else if (ZEND_VM_OP_TRY_CATCH == (flags & ZEND_VM_OP_MASK)) { + if (op.num != (uint32_t)-1) { + fprintf(stderr, " try-catch(%u)", op.num); + } + } else if (ZEND_VM_OP_LIVE_RANGE == (flags & ZEND_VM_OP_MASK)) { + if (opline->extended_value & ZEND_FREE_ON_RETURN) { + fprintf(stderr, " live-range(%u)", op.num); + } + } else if (ZEND_VM_OP_THIS == (flags & ZEND_VM_OP_MASK)) { + fprintf(stderr, " THIS"); + } else if (ZEND_VM_OP_NEXT == (flags & ZEND_VM_OP_MASK)) { + fprintf(stderr, " NEXT"); + } else if (ZEND_VM_OP_CLASS_FETCH == (flags & ZEND_VM_OP_MASK)) { + zend_dump_class_fetch_type(op.num); + } else if (ZEND_VM_OP_CONSTRUCTOR == (flags & ZEND_VM_OP_MASK)) { + fprintf(stderr, " CONSTRUCTOR"); + } +} + +void zend_dump_var(const zend_op_array *op_array, zend_uchar var_type, int var_num) +{ + if (var_type == IS_CV && var_num < op_array->last_var) { + fprintf(stderr, "CV%d($%s)", var_num, op_array->vars[var_num]->val); + } else if (var_type == IS_VAR) { + fprintf(stderr, "V%d", var_num); + } else if (var_type == IS_TMP_VAR) { + fprintf(stderr, "T%d", var_num); + } else { + fprintf(stderr, "X%d", var_num); + } +} + +static void zend_dump_range(const zend_ssa_range *r) +{ + if (r->underflow && r->overflow) { + return; + } + fprintf(stderr, " RANGE["); + if (r->underflow) { + fprintf(stderr, "--.."); + } else { + fprintf(stderr, ZEND_LONG_FMT "..", r->min); + } + if (r->overflow) { + fprintf(stderr, "++]"); + } else { + fprintf(stderr, ZEND_LONG_FMT "]", r->max); + } +} + +static void zend_dump_type_info(uint32_t info, zend_class_entry *ce, int is_instanceof, uint32_t dump_flags) +{ + int first = 1; + + fprintf(stderr, " ["); + if (info & MAY_BE_UNDEF) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "undef"); + } + if (info & MAY_BE_REF) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "ref"); + } + if (dump_flags & ZEND_DUMP_RC_INFERENCE) { + if (info & MAY_BE_RC1) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "rc1"); + } + if (info & MAY_BE_RCN) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "rcn"); + } + } + if (info & MAY_BE_CLASS) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "class"); + if (ce) { + if (is_instanceof) { + fprintf(stderr, " (instanceof %s)", ce->name->val); + } else { + fprintf(stderr, " (%s)", ce->name->val); + } + } + } else if ((info & MAY_BE_ANY) == MAY_BE_ANY) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "any"); + } else { + if (info & MAY_BE_NULL) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "null"); + } + if ((info & MAY_BE_FALSE) && (info & MAY_BE_TRUE)) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "bool"); + } else if (info & MAY_BE_FALSE) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "false"); + } else if (info & MAY_BE_TRUE) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "true"); + } + if (info & MAY_BE_LONG) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "long"); + } + if (info & MAY_BE_DOUBLE) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "double"); + } + if (info & MAY_BE_STRING) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "string"); + } + if (info & MAY_BE_ARRAY) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "array"); + if ((info & MAY_BE_ARRAY_KEY_ANY) != 0 && + (info & MAY_BE_ARRAY_KEY_ANY) != MAY_BE_ARRAY_KEY_ANY) { + int afirst = 1; + fprintf(stderr, " ["); + if (info & MAY_BE_ARRAY_KEY_LONG) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "long"); + } + if (info & MAY_BE_ARRAY_KEY_STRING) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "string"); + } + fprintf(stderr, "]"); + } + if (info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF)) { + int afirst = 1; + fprintf(stderr, " of ["); + if ((info & MAY_BE_ARRAY_OF_ANY) == MAY_BE_ARRAY_OF_ANY) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "any"); + } else { + if (info & MAY_BE_ARRAY_OF_NULL) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "null"); + } + if (info & MAY_BE_ARRAY_OF_FALSE) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "false"); + } + if (info & MAY_BE_ARRAY_OF_TRUE) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "true"); + } + if (info & MAY_BE_ARRAY_OF_LONG) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "long"); + } + if (info & MAY_BE_ARRAY_OF_DOUBLE) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "double"); + } + if (info & MAY_BE_ARRAY_OF_STRING) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "string"); + } + if (info & MAY_BE_ARRAY_OF_ARRAY) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "array"); + } + if (info & MAY_BE_ARRAY_OF_OBJECT) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "object"); + } + if (info & MAY_BE_ARRAY_OF_RESOURCE) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "resource"); + } + } + if (info & MAY_BE_ARRAY_OF_REF) { + if (afirst) afirst = 0; else fprintf(stderr, ", "); + fprintf(stderr, "ref"); + } + fprintf(stderr, "]"); + } + } + if (info & MAY_BE_OBJECT) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "object"); + if (ce) { + if (is_instanceof) { + fprintf(stderr, " (instanceof %s)", ce->name->val); + } else { + fprintf(stderr, " (%s)", ce->name->val); + } + } + } + if (info & MAY_BE_RESOURCE) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "resource"); + } + } + if (info & MAY_BE_ERROR) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "error"); + } +//TODO: this is useful only for JIT??? + if (info & MAY_BE_IN_REG) { + if (first) first = 0; else fprintf(stderr, ", "); + fprintf(stderr, "reg"); + } + fprintf(stderr, "]"); +} + +static void zend_dump_ssa_var_info(const zend_ssa *ssa, int ssa_var_num, uint32_t dump_flags) +{ + zend_dump_type_info( + ssa->var_info[ssa_var_num].type, + ssa->var_info[ssa_var_num].ce, + ssa->var_info[ssa_var_num].ce ? + ssa->var_info[ssa_var_num].is_instanceof : 0, + dump_flags); +} + +static void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa, int ssa_var_num, zend_uchar var_type, int var_num, uint32_t dump_flags) +{ + if (ssa_var_num >= 0) { + fprintf(stderr, "#%d.", ssa_var_num); + } else { + fprintf(stderr, "#?."); + } + zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : var_type), var_num); + + if (ssa_var_num >= 0 && ssa->vars) { + if (ssa_var_num >= 0 && ssa->vars[ssa_var_num].no_val) { + fprintf(stderr, " NOVAL"); + } + if (ssa->var_info) { + zend_dump_ssa_var_info(ssa, ssa_var_num, dump_flags); + if (ssa->var_info[ssa_var_num].has_range) { + zend_dump_range(&ssa->var_info[ssa_var_num].range); + } + } + } +} + +static void zend_dump_type_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_type_constraint *constraint, uint32_t dump_flags) +{ + fprintf(stderr, " TYPE"); + zend_dump_type_info(constraint->type_mask, constraint->ce, 1, dump_flags); +} + +static void zend_dump_range_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_range_constraint *r, uint32_t dump_flags) +{ + if (r->range.underflow && r->range.overflow) { + return; + } + fprintf(stderr, " RANGE"); + if (r->negative) { + fprintf(stderr, "~"); + } + fprintf(stderr, "["); + if (r->range.underflow) { + fprintf(stderr, "-- .. "); + } else { + if (r->min_ssa_var >= 0) { + zend_dump_ssa_var(op_array, ssa, r->min_ssa_var, (r->min_var < op_array->last_var ? IS_CV : 0), r->min_var, dump_flags); + if (r->range.min > 0) { + fprintf(stderr, " + " ZEND_LONG_FMT, r->range.min); + } else if (r->range.min < 0) { + fprintf(stderr, " - " ZEND_LONG_FMT, -r->range.min); + } + fprintf(stderr, " .. "); + } else { + fprintf(stderr, ZEND_LONG_FMT " .. ", r->range.min); + } + } + if (r->range.overflow) { + fprintf(stderr, "++]"); + } else { + if (r->max_ssa_var >= 0) { + zend_dump_ssa_var(op_array, ssa, r->max_ssa_var, (r->max_var < op_array->last_var ? IS_CV : 0), r->max_var, dump_flags); + if (r->range.max > 0) { + fprintf(stderr, " + " ZEND_LONG_FMT, r->range.max); + } else if (r->range.max < 0) { + fprintf(stderr, " - " ZEND_LONG_FMT, -r->range.max); + } + fprintf(stderr, "]"); + } else { + fprintf(stderr, ZEND_LONG_FMT "]", r->range.max); + } + } +} + +static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const void *data) +{ + const char *name = zend_get_opcode_name(opline->opcode); + uint32_t flags = zend_get_opcode_flags(opline->opcode); + uint32_t n = 0; + int len = 0; + const zend_ssa *ssa = NULL; + + if (dump_flags & ZEND_DUMP_SSA) { + ssa = (const zend_ssa*)data; + } + + if (!b) { + len = fprintf(stderr, "L%u:", (uint32_t)(opline - op_array->opcodes)); + } + fprintf(stderr, "%*c", 8-len, ' '); + + if (!ssa || !ssa->ops || ssa->ops[opline - op_array->opcodes].result_use < 0) { + if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + if (ssa && ssa->ops && ssa->ops[opline - op_array->opcodes].result_def >= 0) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_def; + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags); + } else { + zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var)); + } + fprintf(stderr, " = "); + } + } + + if (name) { + fprintf(stderr, "%s", (name + 5)); + } else { + fprintf(stderr, "OP_%d", (int)opline->opcode); + } + + if (ZEND_VM_EXT_NUM == (flags & ZEND_VM_EXT_MASK)) { + fprintf(stderr, " %u", opline->extended_value); + } else if (ZEND_VM_EXT_DIM_OBJ == (flags & ZEND_VM_EXT_MASK)) { + if (opline->extended_value == ZEND_ASSIGN_DIM) { + fprintf(stderr, " (dim)"); + } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { + fprintf(stderr, " (obj)"); + } + } else if (ZEND_VM_EXT_CLASS_FETCH == (flags & ZEND_VM_EXT_MASK)) { + zend_dump_class_fetch_type(opline->extended_value); + } else if (ZEND_VM_EXT_CONST_FETCH == (flags & ZEND_VM_EXT_MASK)) { + if (opline->extended_value & IS_CONSTANT_UNQUALIFIED) { + fprintf(stderr, " (unqualified)"); + } + if (opline->extended_value & IS_CONSTANT_CLASS) { + fprintf(stderr, " (__class__)"); + } + if (opline->extended_value & IS_CONSTANT_IN_NAMESPACE) { + fprintf(stderr, " (in-namespace)"); + } + } else if (ZEND_VM_EXT_TYPE == (flags & ZEND_VM_EXT_MASK)) { + switch (opline->extended_value) { + case IS_NULL: + fprintf(stderr, " (null)"); + break; + case IS_FALSE: + fprintf(stderr, " (false)"); + break; + case IS_TRUE: + fprintf(stderr, " (true)"); + break; + case IS_LONG: + fprintf(stderr, " (long)"); + break; + case IS_DOUBLE: + fprintf(stderr, " (double)"); + break; + case IS_STRING: + fprintf(stderr, " (string)"); + break; + case IS_ARRAY: + fprintf(stderr, " (array)"); + break; + case IS_OBJECT: + fprintf(stderr, " (object)"); + break; + case IS_RESOURCE: + fprintf(stderr, " (resource)"); + break; + case _IS_BOOL: + fprintf(stderr, " (bool)"); + break; + case IS_CALLABLE: + fprintf(stderr, " (callable)"); + break; + case IS_VOID: + fprintf(stderr, " (void)"); + break; + default: + fprintf(stderr, " (\?\?\?)"); + break; + } + } else if (ZEND_VM_EXT_EVAL == (flags & ZEND_VM_EXT_MASK)) { + switch (opline->extended_value) { + case ZEND_EVAL: + fprintf(stderr, " (eval)"); + break; + case ZEND_INCLUDE: + fprintf(stderr, " (include)"); + break; + case ZEND_INCLUDE_ONCE: + fprintf(stderr, " (include_once)"); + break; + case ZEND_REQUIRE: + fprintf(stderr, " (require)"); + break; + case ZEND_REQUIRE_ONCE: + fprintf(stderr, " (require_once)"); + break; + default: + fprintf(stderr, " (\?\?\?)"); + break; + } + } else if (ZEND_VM_EXT_SRC == (flags & ZEND_VM_EXT_MASK)) { + if (opline->extended_value == ZEND_RETURNS_VALUE) { + fprintf(stderr, " (value)"); + } else if (opline->extended_value == ZEND_RETURNS_FUNCTION) { + fprintf(stderr, " (function)"); + } + } else { + if (ZEND_VM_EXT_VAR_FETCH & flags) { + switch (opline->extended_value & ZEND_FETCH_TYPE_MASK) { + case ZEND_FETCH_GLOBAL: + fprintf(stderr, " (global)"); + break; + case ZEND_FETCH_LOCAL: + fprintf(stderr, " (local)"); + break; + case ZEND_FETCH_GLOBAL_LOCK: + fprintf(stderr, " (global+lock)"); + break; + } + } + if (ZEND_VM_EXT_ISSET & flags) { + if (opline->extended_value & ZEND_QUICK_SET) { + fprintf(stderr, " (quick)"); + } + if (opline->extended_value & ZEND_ISSET) { + fprintf(stderr, " (isset)"); + } else if (opline->extended_value & ZEND_ISEMPTY) { + fprintf(stderr, " (empty)"); + } + } + if (ZEND_VM_EXT_ARG_NUM & flags) { + fprintf(stderr, " %u", opline->extended_value & ZEND_FETCH_ARG_MASK); + } + if (ZEND_VM_EXT_ARRAY_INIT & flags) { + fprintf(stderr, " %u", opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT); + if (!(opline->extended_value & ZEND_ARRAY_NOT_PACKED)) { + fprintf(stderr, " (packed)"); + } + } + if (ZEND_VM_EXT_REF & flags) { + if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) { + fprintf(stderr, " (ref)"); + } + } + } + + if (opline->op1_type == IS_CONST) { + zend_dump_const(CRT_CONSTANT_EX(op_array, opline->op1, (dump_flags & ZEND_DUMP_RT_CONSTANTS))); + } else if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + if (ssa && ssa->ops) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].op1_use; + if (ssa_var_num >= 0) { + fprintf(stderr, " "); + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var), dump_flags); + } else if (ssa->ops[opline - op_array->opcodes].op1_def < 0) { + fprintf(stderr, " "); + zend_dump_var(op_array, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var)); + } + } else { + fprintf(stderr, " "); + zend_dump_var(op_array, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var)); + } + if (ssa && ssa->ops) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].op1_def; + if (ssa_var_num >= 0) { + fprintf(stderr, " -> "); + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var), dump_flags); + } + } + } else { + uint32_t op1_flags = ZEND_VM_OP1_FLAGS(flags); + if (ZEND_VM_OP_JMP_ADDR == (op1_flags & ZEND_VM_OP_MASK)) { + if (b) { + fprintf(stderr, " BB%d", b->successors[n++]); + } else { + fprintf(stderr, " L%u", (uint32_t)(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes)); + } + } else { + zend_dump_unused_op(opline, opline->op1, op1_flags); + } + } + + if (opline->op2_type == IS_CONST) { + zend_dump_const(CRT_CONSTANT_EX(op_array, opline->op2, (dump_flags & ZEND_DUMP_RT_CONSTANTS))); + } else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + if (ssa && ssa->ops) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].op2_use; + if (ssa_var_num >= 0) { + fprintf(stderr, " "); + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var), dump_flags); + } else if (ssa->ops[opline - op_array->opcodes].op2_def < 0) { + fprintf(stderr, " "); + zend_dump_var(op_array, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var)); + } + } else { + fprintf(stderr, " "); + zend_dump_var(op_array, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var)); + } + if (ssa && ssa->ops) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].op2_def; + if (ssa_var_num >= 0) { + fprintf(stderr, " -> "); + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var), dump_flags); + } + } + } else { + uint32_t op2_flags = ZEND_VM_OP2_FLAGS(flags); + if (ZEND_VM_OP_JMP_ADDR == (op2_flags & ZEND_VM_OP_MASK)) { + if (b) { + fprintf(stderr, " BB%d", b->successors[n++]); + } else { + fprintf(stderr, " L%u", (uint32_t)(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes)); + } + } else { + zend_dump_unused_op(opline, opline->op2, op2_flags); + } + } + + if (ZEND_VM_EXT_JMP_ADDR == (flags & ZEND_VM_EXT_MASK)) { + if (opline->opcode != ZEND_CATCH || !opline->result.num) { + if (b) { + fprintf(stderr, " BB%d", b->successors[n++]); + } else { + fprintf(stderr, " L%u", (uint32_t)ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); + } + } + } + if (opline->result_type == IS_CONST) { + zend_dump_const(CRT_CONSTANT_EX(op_array, opline->result, (dump_flags & ZEND_DUMP_RT_CONSTANTS))); + } else if (ssa && ssa->ops && ssa->ops[opline - op_array->opcodes].result_use >= 0) { + if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + if (ssa && ssa->ops) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_use; + if (ssa_var_num >= 0) { + fprintf(stderr, " "); + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags); + } + } else { + fprintf(stderr, " "); + zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var)); + } + if (ssa && ssa->ops) { + int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_def; + if (ssa_var_num >= 0) { + fprintf(stderr, " -> "); + zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags); + } + } + } + } + fprintf(stderr, "\n"); +} + +static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags) +{ + zend_basic_block *b = cfg->blocks + n; + int printed = 0; + + fprintf(stderr, "BB%d:", n); + if (b->flags & ZEND_BB_START) { + fprintf(stderr, " start"); + } + if (b->flags & ZEND_BB_FOLLOW) { + fprintf(stderr, " follow"); + } + if (b->flags & ZEND_BB_TARGET) { + fprintf(stderr, " target"); + } + if (b->flags & ZEND_BB_EXIT) { + fprintf(stderr, " exit"); + } + if (b->flags & (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY)) { + fprintf(stderr, " entry"); + } + if (b->flags & ZEND_BB_TRY) { + fprintf(stderr, " try"); + } + if (b->flags & ZEND_BB_CATCH) { + fprintf(stderr, " catch"); + } + if (b->flags & ZEND_BB_FINALLY) { + fprintf(stderr, " finally"); + } + if (b->flags & ZEND_BB_FINALLY_END) { + fprintf(stderr, " finally_end"); + } + if (b->flags & ZEND_BB_GEN_VAR) { + fprintf(stderr, " gen_var"); + } + if (b->flags & ZEND_BB_KILL_VAR) { + fprintf(stderr, " kill_var"); + } + if (!(dump_flags & ZEND_DUMP_HIDE_UNREACHABLE) & !(b->flags & ZEND_BB_REACHABLE)) { + fprintf(stderr, " unreachable"); + } + if (b->flags & ZEND_BB_LOOP_HEADER) { + fprintf(stderr, " loop_header"); + } + if (b->flags & ZEND_BB_IRREDUCIBLE_LOOP) { + fprintf(stderr, " irreducible"); + } + if (b->len != 0) { + fprintf(stderr, " lines=[%d-%d]", b->start, b->start + b->len - 1); + } else { + fprintf(stderr, " empty"); + } + fprintf(stderr, "\n"); + + if (b->predecessors_count) { + int *p = cfg->predecessors + b->predecessor_offset; + int *end = p + b->predecessors_count; + + fprintf(stderr, " ; from=(BB%d", *p); + for (p++; p < end; p++) { + fprintf(stderr, ", BB%d", *p); + } + fprintf(stderr, ")\n"); + } + + if (b->successors[0] != -1) { + fprintf(stderr, " ; to=(BB%d", b->successors[0]); + printed = 1; + if (b->successors[1] != -1) { + fprintf(stderr, ", BB%d", b->successors[1]); + } + } + if (printed) { + fprintf(stderr, ")\n"); + } + + if (b->idom >= 0) { + fprintf(stderr, " ; idom=BB%d\n", b->idom); + } + if (b->level >= 0) { + fprintf(stderr, " ; level=%d\n", b->level); + } + if (b->loop_header >= 0) { + fprintf(stderr, " ; loop_header=%d\n", b->loop_header); + } + if (b->children >= 0) { + int j = b->children; + fprintf(stderr, " ; children=(BB%d", j); + j = cfg->blocks[j].next_child; + while (j >= 0) { + fprintf(stderr, ", BB%d", j); + j = cfg->blocks[j].next_child; + } + fprintf(stderr, ")\n"); + } +} + +static void zend_dump_block_header(const zend_cfg *cfg, const zend_op_array *op_array, const zend_ssa *ssa, int n, uint32_t dump_flags) +{ + zend_dump_block_info(cfg, n, dump_flags); + if (ssa && ssa->blocks && ssa->blocks[n].phis) { + zend_ssa_phi *p = ssa->blocks[n].phis; + + do { + int j; + + fprintf(stderr, " "); + zend_dump_ssa_var(op_array, ssa, p->ssa_var, 0, p->var, dump_flags); + if (p->pi < 0) { + fprintf(stderr, " = Phi("); + for (j = 0; j < cfg->blocks[n].predecessors_count; j++) { + if (j > 0) { + fprintf(stderr, ", "); + } + zend_dump_ssa_var(op_array, ssa, p->sources[j], 0, p->var, dump_flags); + } + fprintf(stderr, ")\n"); + } else { + fprintf(stderr, " = Pi<BB%d>(", p->pi); + zend_dump_ssa_var(op_array, ssa, p->sources[0], 0, p->var, dump_flags); + fprintf(stderr, " &"); + if (p->has_range_constraint) { + zend_dump_range_constraint(op_array, ssa, &p->constraint.range, dump_flags); + } else { + zend_dump_type_constraint(op_array, ssa, &p->constraint.type, dump_flags); + } + fprintf(stderr, ")\n"); + } + p = p->next; + } while (p); + } +} + +static void zend_dump_op_array_name(const zend_op_array *op_array) +{ + zend_func_info *func_info = NULL; + + func_info = ZEND_FUNC_INFO(op_array); + if (op_array->function_name) { + if (op_array->scope && op_array->scope->name) { + fprintf(stderr, "%s::%s", op_array->scope->name->val, op_array->function_name->val); + } else { + fprintf(stderr, "%s", op_array->function_name->val); + } + } else { + fprintf(stderr, "%s", "$_main"); + } + if (func_info && func_info->clone_num > 0) { + fprintf(stderr, "_@_clone_%d", func_info->clone_num); + } +} + +void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, const char *msg, const void *data) +{ + int i; + const zend_cfg *cfg = NULL; + const zend_ssa *ssa = NULL; + zend_func_info *func_info = NULL; + uint32_t func_flags = 0; + + if (dump_flags & (ZEND_DUMP_CFG|ZEND_DUMP_SSA)) { + cfg = (const zend_cfg*)data; + if (!cfg->blocks) { + cfg = data = NULL; + } + } + if (dump_flags & ZEND_DUMP_SSA) { + ssa = (const zend_ssa*)data; + } + + func_info = ZEND_FUNC_INFO(op_array); + if (func_info) { + func_flags = func_info->flags; + } + + fprintf(stderr, "\n"); + zend_dump_op_array_name(op_array); + fprintf(stderr, ": ; (lines=%d, args=%d", + op_array->last, + op_array->num_args); + if (func_info && func_info->num_args >= 0) { + fprintf(stderr, "/%d", func_info->num_args); + } + fprintf(stderr, ", vars=%d, tmps=%d", op_array->last_var, op_array->T); + if (ssa) { + fprintf(stderr, ", ssa_vars=%d", ssa->vars_count); + } + if (func_flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) { + fprintf(stderr, ", dynamic"); + } + if (func_flags & ZEND_FUNC_RECURSIVE) { + fprintf(stderr, ", recursive"); + if (func_flags & ZEND_FUNC_RECURSIVE_DIRECTLY) { + fprintf(stderr, " directly"); + } + if (func_flags & ZEND_FUNC_RECURSIVE_INDIRECTLY) { + fprintf(stderr, " indirectly"); + } + } + if (func_flags & ZEND_FUNC_IRREDUCIBLE) { + fprintf(stderr, ", irreducable"); + } + if (func_flags & ZEND_FUNC_NO_LOOPS) { + fprintf(stderr, ", no_loops"); + } +//TODO: this is useful only for JIT??? +#if 0 + if (info->flags & ZEND_JIT_FUNC_NO_IN_MEM_CVS) { + fprintf(stderr, ", no_in_mem_cvs"); + } + if (info->flags & ZEND_JIT_FUNC_NO_USED_ARGS) { + fprintf(stderr, ", no_used_args"); + } + if (info->flags & ZEND_JIT_FUNC_NO_SYMTAB) { + fprintf(stderr, ", no_symtab"); + } + if (info->flags & ZEND_JIT_FUNC_NO_FRAME) { + fprintf(stderr, ", no_frame"); + } + if (info->flags & ZEND_JIT_FUNC_INLINE) { + fprintf(stderr, ", inline"); + } +#endif + if (func_info && func_info->return_value_used == 0) { + fprintf(stderr, ", no_return_value"); + } else if (func_info && func_info->return_value_used == 1) { + fprintf(stderr, ", return_value"); + } + fprintf(stderr, ")\n"); + if (msg) { + fprintf(stderr, " ; (%s)\n", msg); + } + fprintf(stderr, " ; %s:%u-%u\n", op_array->filename->val, op_array->line_start, op_array->line_end); + + if (func_info && func_info->num_args > 0) { + uint32_t j; + + for (j = 0; j < MIN(op_array->num_args, func_info->num_args ); j++) { + fprintf(stderr, " ; arg %d ", j); + zend_dump_type_info(func_info->arg_info[j].info.type, func_info->arg_info[j].info.ce, func_info->arg_info[j].info.is_instanceof, dump_flags); + zend_dump_range(&func_info->arg_info[j].info.range); + fprintf(stderr, "\n"); + } + } + + if (func_info) { + fprintf(stderr, " ; return "); + zend_dump_type_info(func_info->return_info.type, func_info->return_info.ce, func_info->return_info.is_instanceof, dump_flags); + zend_dump_range(&func_info->return_info.range); + fprintf(stderr, "\n"); + } + + if (ssa && ssa->var_info) { + for (i = 0; i < op_array->last_var; i++) { + fprintf(stderr, " ; "); + zend_dump_ssa_var(op_array, ssa, i, IS_CV, i, dump_flags); + fprintf(stderr, "\n"); + } + } + + if (cfg) { + int n; + zend_basic_block *b; + + for (n = 0; n < cfg->blocks_count; n++) { + b = cfg->blocks + n; + if (!(dump_flags & ZEND_DUMP_HIDE_UNREACHABLE) || (b->flags & ZEND_BB_REACHABLE)) { + const zend_op *opline; + const zend_op *end; + + zend_dump_block_header(cfg, op_array, ssa, n, dump_flags); + opline = op_array->opcodes + b->start; + end = opline + b->len; + while (opline < end) { + zend_dump_op(op_array, b, opline, dump_flags, data); + opline++; + } + } + } + if (op_array->last_live_range) { + fprintf(stderr, "LIVE RANGES:\n"); + for (i = 0; i < op_array->last_live_range; i++) { + if (cfg->split_at_live_ranges) { + fprintf(stderr, " %u: BB%u - BB%u ", + EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK), + cfg->map[op_array->live_range[i].start], + cfg->map[op_array->live_range[i].end]); + } else { + fprintf(stderr, " %u: L%u - L%u ", + EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK), + op_array->live_range[i].start, + op_array->live_range[i].end); + } + switch (op_array->live_range[i].var & ZEND_LIVE_MASK) { + case ZEND_LIVE_TMPVAR: + fprintf(stderr, "(tmp/var)\n"); + break; + case ZEND_LIVE_LOOP: + fprintf(stderr, "(loop)\n"); + break; + case ZEND_LIVE_SILENCE: + fprintf(stderr, "(silence)\n"); + break; + case ZEND_LIVE_ROPE: + fprintf(stderr, "(rope)\n"); + break; + } + } + } + if (op_array->last_try_catch) { + fprintf(stderr, "EXCEPTION TABLE:\n"); + for (i = 0; i < op_array->last_try_catch; i++) { + fprintf(stderr, " BB%u", + cfg->map[op_array->try_catch_array[i].try_op]); + if (op_array->try_catch_array[i].catch_op) { + fprintf(stderr, ", BB%u", + cfg->map[op_array->try_catch_array[i].catch_op]); + } else { + fprintf(stderr, ", -"); + } + if (op_array->try_catch_array[i].finally_op) { + fprintf(stderr, ", BB%u", + cfg->map[op_array->try_catch_array[i].finally_op]); + } else { + fprintf(stderr, ", -"); + } + if (op_array->try_catch_array[i].finally_end) { + fprintf(stderr, ", BB%u\n", + cfg->map[op_array->try_catch_array[i].finally_end]); + } else { + fprintf(stderr, ", -\n"); + } + } + } + } else { + const zend_op *opline = op_array->opcodes; + const zend_op *end = opline + op_array->last; + + while (opline < end) { + zend_dump_op(op_array, NULL, opline, dump_flags, data); + opline++; + } + if (op_array->last_live_range) { + fprintf(stderr, "LIVE RANGES:\n"); + for (i = 0; i < op_array->last_live_range; i++) { + fprintf(stderr, " %u: L%u - L%u ", + EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK), + op_array->live_range[i].start, + op_array->live_range[i].end); + switch (op_array->live_range[i].var & ZEND_LIVE_MASK) { + case ZEND_LIVE_TMPVAR: + fprintf(stderr, "(tmp/var)\n"); + break; + case ZEND_LIVE_LOOP: + fprintf(stderr, "(loop)\n"); + break; + case ZEND_LIVE_SILENCE: + fprintf(stderr, "(silence)\n"); + break; + case ZEND_LIVE_ROPE: + fprintf(stderr, "(rope)\n"); + break; + } + } + } + if (op_array->last_try_catch) { + fprintf(stderr, "EXCEPTION TABLE:\n"); + for (i = 0; i < op_array->last_try_catch; i++) { + fprintf(stderr, " L%u", + op_array->try_catch_array[i].try_op); + if (op_array->try_catch_array[i].catch_op) { + fprintf(stderr, ", L%u", + op_array->try_catch_array[i].catch_op); + } else { + fprintf(stderr, ", -"); + } + if (op_array->try_catch_array[i].finally_op) { + fprintf(stderr, ", L%u", + op_array->try_catch_array[i].finally_op); + } else { + fprintf(stderr, ", -"); + } + if (op_array->try_catch_array[i].finally_end) { + fprintf(stderr, ", L%u\n", + op_array->try_catch_array[i].finally_end); + } else { + fprintf(stderr, ", -\n"); + } + } + } + } +} + +void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg) +{ + int j; + + fprintf(stderr, "\nDOMINATORS-TREE for \""); + zend_dump_op_array_name(op_array); + fprintf(stderr, "\"\n"); + for (j = 0; j < cfg->blocks_count; j++) { + zend_basic_block *b = cfg->blocks + j; + if (b->flags & ZEND_BB_REACHABLE) { + zend_dump_block_info(cfg, j, 0); + } + } +} + +void zend_dump_variables(const zend_op_array *op_array) +{ + int j; + + fprintf(stderr, "\nCV Variables for \""); + zend_dump_op_array_name(op_array); + fprintf(stderr, "\"\n"); + for (j = 0; j < op_array->last_var; j++) { + fprintf(stderr, " "); + zend_dump_var(op_array, IS_CV, j); + fprintf(stderr, "\n"); + } +} + +void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa, uint32_t dump_flags) +{ + int j; + + if (ssa->vars) { + fprintf(stderr, "\nSSA Variable for \""); + zend_dump_op_array_name(op_array); + fprintf(stderr, "\"\n"); + + for (j = 0; j < ssa->vars_count; j++) { + fprintf(stderr, " "); + zend_dump_ssa_var(op_array, ssa, j, IS_CV, ssa->vars[j].var, dump_flags); + if (ssa->vars[j].scc >= 0) { + if (ssa->vars[j].scc_entry) { + fprintf(stderr, " *"); + } else { + fprintf(stderr, " "); + } + fprintf(stderr, "SCC=%d", ssa->vars[j].scc); + } + fprintf(stderr, "\n"); + } + } +} + +static void zend_dump_var_set(const zend_op_array *op_array, const char *name, zend_bitset set) +{ + int first = 1; + uint32_t i; + + fprintf(stderr, " ; %s = {", name); + for (i = 0; i < op_array->last_var + op_array->T; i++) { + if (zend_bitset_in(set, i)) { + if (first) { + first = 0; + } else { + fprintf(stderr, ", "); + } + zend_dump_var(op_array, IS_CV, i); + } + } + fprintf(stderr, "}\n"); +} + +void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zend_dfg *dfg) +{ + int j; + fprintf(stderr, "\nVariable Liveness for \""); + zend_dump_op_array_name(op_array); + fprintf(stderr, "\"\n"); + + for (j = 0; j < cfg->blocks_count; j++) { + fprintf(stderr, " BB%d:\n", j); + zend_dump_var_set(op_array, "def", DFG_BITSET(dfg->def, dfg->size, j)); + zend_dump_var_set(op_array, "use", DFG_BITSET(dfg->use, dfg->size, j)); + zend_dump_var_set(op_array, "in ", DFG_BITSET(dfg->in, dfg->size, j)); + zend_dump_var_set(op_array, "out", DFG_BITSET(dfg->out, dfg->size, j)); + } +} + +void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa) +{ + int j; + zend_ssa_block *ssa_blocks = ssa->blocks; + int blocks_count = ssa->cfg.blocks_count; + + fprintf(stderr, "\nSSA Phi() Placement for \""); + zend_dump_op_array_name(op_array); + fprintf(stderr, "\"\n"); + for (j = 0; j < blocks_count; j++) { + if (ssa_blocks && ssa_blocks[j].phis) { + zend_ssa_phi *p = ssa_blocks[j].phis; + int first = 1; + + fprintf(stderr, " BB%d:\n", j); + if (p->pi >= 0) { + fprintf(stderr, " ; pi={"); + } else { + fprintf(stderr, " ; phi={"); + } + do { + if (first) { + first = 0; + } else { + fprintf(stderr, ", "); + } + zend_dump_var(op_array, IS_CV, p->var); + p = p->next; + } while (p); + fprintf(stderr, "}\n"); + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_dump.h b/ext/opcache/Optimizer/zend_dump.h new file mode 100644 index 0000000000..11646d9a82 --- /dev/null +++ b/ext/opcache/Optimizer/zend_dump.h @@ -0,0 +1,51 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, Bytecode Visualisation | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_DUMP_H +#define ZEND_DUMP_H + +#include "zend_ssa.h" +#include "zend_dfg.h" + +#define ZEND_DUMP_HIDE_UNREACHABLE (1<<0) +#define ZEND_DUMP_RC_INFERENCE (1<<1) +#define ZEND_DUMP_CFG (1<<2) +#define ZEND_DUMP_SSA (1<<3) +#define ZEND_DUMP_RT_CONSTANTS ZEND_RT_CONSTANTS + +BEGIN_EXTERN_C() + +void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, const char *msg, const void *data); +void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg); +void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zend_dfg *dfg); +void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa); +void zend_dump_variables(const zend_op_array *op_array); +void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa, uint32_t dump_flags); +void zend_dump_var(const zend_op_array *op_array, zend_uchar var_type, int var_num); + +END_EXTERN_C() + +#endif /* ZEND_DUMP_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_func_info.c b/ext/opcache/Optimizer/zend_func_info.c new file mode 100644 index 0000000000..7a1e65f625 --- /dev/null +++ b/ext/opcache/Optimizer/zend_func_info.c @@ -0,0 +1,1288 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, Func Info | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id:$ */ + +#include "php.h" +#include "zend_compile.h" +#include "zend_extensions.h" +#include "zend_ssa.h" +#include "zend_optimizer_internal.h" +#include "zend_inference.h" +#include "zend_call_graph.h" +#include "zend_func_info.h" +#include "zend_inference.h" + +typedef uint32_t (*info_func_t)(const zend_call_info *call_info, const zend_ssa *ssa); + +typedef struct _func_info_t { + const char *name; + int name_len; + uint32_t info; + info_func_t info_func; +} func_info_t; + +#define F0(name, info) \ + {name, sizeof(name)-1, (FUNC_MAY_WARN | (info)), NULL} +#define F1(name, info) \ + {name, sizeof(name)-1, (FUNC_MAY_WARN | MAY_BE_RC1 | (info)), NULL} +#define FN(name, info) \ + {name, sizeof(name)-1, (FUNC_MAY_WARN | MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL} +#define FR(name, info) \ + {name, sizeof(name)-1, (FUNC_MAY_WARN | MAY_BE_REF | (info)), NULL} +#define FX(name, info) \ + {name, sizeof(name)-1, (FUNC_MAY_WARN | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | (info)), NULL} +#define I0(name, info) \ + {name, sizeof(name)-1, (info), NULL} +#define I1(name, info) \ + {name, sizeof(name)-1, (MAY_BE_RC1 | (info)), NULL} +#define IN(name, info) \ + {name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL} +#define FC(name, callback) \ + {name, sizeof(name)-1, 0, callback} + +static uint32_t zend_strlen_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + call_info->num_args == 1) { + + uint32_t tmp = 0; + if (call_info->arg_info[0].opline) { + uint32_t arg_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[0].opline); + + if (arg_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT)) { + tmp |= MAY_BE_LONG; + } + if (arg_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + /* warning, and returns NULL */ + tmp |= FUNC_MAY_WARN | MAY_BE_NULL; + } + } else { + tmp |= MAY_BE_LONG | FUNC_MAY_WARN | MAY_BE_NULL; + } + return tmp; + } else { + /* warning, and returns NULL */ + return FUNC_MAY_WARN | MAY_BE_NULL; + } +} + +static uint32_t zend_dechex_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + call_info->num_args == 1) { + return MAY_BE_RC1 | MAY_BE_STRING; + } else { + /* warning, and returns NULL */ + return FUNC_MAY_WARN | MAY_BE_NULL; + } +} + +static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + (call_info->num_args == 2 || call_info->num_args == 3)) { + + uint32_t t1 = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[0].opline); + uint32_t t2 = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[1].opline); + uint32_t t3 = 0; + uint32_t tmp = FUNC_MAY_WARN | MAY_BE_RC1 | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG; + + if (call_info->num_args == 3) { + t3 = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[2].opline); + } + if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) { + tmp |= MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING; + } + if ((t1 & (MAY_BE_DOUBLE|MAY_BE_STRING)) + || (t2 & (MAY_BE_DOUBLE|MAY_BE_STRING)) + || (t3 & (MAY_BE_DOUBLE|MAY_BE_STRING))) { + tmp |= MAY_BE_ARRAY_OF_DOUBLE; + } + if ((t1 & (MAY_BE_ANY-(MAY_BE_STRING|MAY_BE_DOUBLE))) && (t2 & (MAY_BE_ANY-(MAY_BE_STRING|MAY_BE_DOUBLE)))) { + if ((t3 & MAY_BE_ANY) != MAY_BE_DOUBLE) { + tmp |= MAY_BE_ARRAY_OF_LONG; + } + } + return tmp; + } else { + /* may warning, and return FALSE */ + return FUNC_MAY_WARN | MAY_BE_RC1 | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING; + } +} + +static uint32_t zend_is_type_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + call_info->num_args == 1) { + return MAY_BE_FALSE | MAY_BE_TRUE; + } else { + return MAY_BE_FALSE | MAY_BE_TRUE | FUNC_MAY_WARN; + } +} + +static uint32_t zend_l_ss_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + call_info->num_args == 2) { + + uint32_t arg1_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[0].opline); + uint32_t arg2_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[1].opline); + uint32_t tmp = 0; + + if ((arg1_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT)) && + (arg2_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT))) { + tmp |= MAY_BE_LONG; + } + if ((arg1_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) || + (arg2_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + /* warning, and returns NULL */ + tmp |= FUNC_MAY_WARN | MAY_BE_NULL; + } + return tmp; + } else { + /* warning, and returns NULL */ + return FUNC_MAY_WARN | MAY_BE_NULL | MAY_BE_LONG; + } +} + +static uint32_t zend_lb_ssn_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + call_info->num_args == 3) { + uint32_t arg1_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[0].opline); + uint32_t arg2_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[1].opline); + uint32_t arg3_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[2].opline); + uint32_t tmp = 0; + + if ((arg1_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT)) && + (arg2_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT)) && + (arg3_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT))) { + tmp |= MAY_BE_LONG | MAY_BE_FALSE; + } + if ((arg1_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) || + (arg2_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) || + (arg3_info & (MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_OBJECT))) { + /* warning, and returns NULL */ + tmp |= FUNC_MAY_WARN | MAY_BE_NULL; + } + return tmp; + } else { + /* warning, and returns NULL */ + return FUNC_MAY_WARN | MAY_BE_NULL | MAY_BE_LONG; + } +} + +static uint32_t zend_b_s_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + if (call_info->caller_init_opline->extended_value == (uint32_t)call_info->num_args && + call_info->num_args == 1) { + + uint32_t arg1_info = _ssa_op1_info(call_info->caller_op_array, ssa, call_info->arg_info[0].opline); + uint32_t tmp = 0; + + if (arg1_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT)) { + tmp |= MAY_BE_FALSE | MAY_BE_TRUE; + } + if (arg1_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + /* warning, and returns NULL */ + tmp |= FUNC_MAY_WARN | MAY_BE_NULL; + } + return tmp; + } else { + /* warning, and returns NULL */ + return FUNC_MAY_WARN | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE; + } +} + +#define UNKNOWN_INFO (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF) + +static const func_info_t func_infos[] = { + /* zend */ + I1("zend_version", MAY_BE_STRING), + I0("gc_collect_cycles", MAY_BE_LONG), + I0("gc_enabled", MAY_BE_FALSE | MAY_BE_TRUE), + F0("gc_enable", MAY_BE_NULL), + F0("gc_disable", MAY_BE_NULL), + F0("func_num_args", MAY_BE_LONG), + FN("func_get_arg", UNKNOWN_INFO), + F1("func_get_args", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF), + FC("strlen", zend_strlen_info), + FC("strcmp", zend_l_ss_info), + FC("strncmp", zend_lb_ssn_info), + FC("strcasecmp", zend_l_ss_info), + FC("strncasecmp", zend_lb_ssn_info), + F1("each", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_KEY_ANY), + F0("error_reporting", MAY_BE_NULL | MAY_BE_LONG), + F0("define", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_NULL), // TODO: inline + FC("defined", zend_b_s_info), // TODO: inline + FN("get_class", MAY_BE_FALSE | MAY_BE_STRING), + FN("get_called_class", MAY_BE_FALSE | MAY_BE_STRING), + FN("get_parrent_class", MAY_BE_FALSE | MAY_BE_STRING), + F0("is_subclass_of", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), // TODO: inline + F0("is_a", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), // TODO: inline + F1("get_class_vars", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF), + FN("get_object_vars", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF), + F1("get_class_methods", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("method_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("property_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("class_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("interface_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("trait_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + FC("function_exists", zend_b_s_info), // TODO: inline + F0("class_alias", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("get_included_files", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("trigger_error", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("user_error", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + FN("set_error_handler", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT), + I0("restore_error_handler", MAY_BE_NULL | MAY_BE_TRUE), + F1("get_declared_traits", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("get_declared_classes", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("get_declared_interfaces", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("get_defined_functions", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY), + I1("get_defined_vars", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF), + FN("create_function", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("get_resource_type", MAY_BE_NULL | MAY_BE_STRING), + F1("get_defined_constants", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_RESOURCE | MAY_BE_ARRAY_OF_ARRAY), + F0("debug_print_backtrace", MAY_BE_NULL), + F1("debug_backtrace", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY), + F1("get_loaded_extensions", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + FC("extension_loaded", zend_b_s_info), + F1("get_extension_funcs", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + + /* ext/statdard */ + FN("constant", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING | MAY_BE_RESOURCE), + F1("bin2hex", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("hex2bin", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("sleep", MAY_BE_FALSE | MAY_BE_LONG), + F0("usleep", MAY_BE_NULL | MAY_BE_FALSE), +#if HAVE_NANOSLEEP + F0("time_nanosleep", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("time_sleep_until", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif +#if HAVE_STRPTIME + F1("strptime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), +#endif + F0("flush", MAY_BE_NULL), + F1("wordwrap", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("htmlspecialchars", MAY_BE_NULL | MAY_BE_STRING), + F1("htmlentities", MAY_BE_NULL | MAY_BE_STRING), + F1("html_entity_decode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("htmlspecialchars_decode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("get_html_translation_table", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), + F1("sha1", MAY_BE_NULL | MAY_BE_STRING), + F1("sha1_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("md5", MAY_BE_NULL | MAY_BE_STRING), + F1("md5_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("crc32", MAY_BE_NULL | MAY_BE_LONG), + F1("iptcparse", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("iptcembed", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("getimagesize", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("getimagesizefromstring", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("image_type_to_mime_type", MAY_BE_NULL | MAY_BE_STRING), + F1("image_type_to_extension", MAY_BE_FALSE | MAY_BE_STRING), + F0("phpinfo", MAY_BE_NULL | MAY_BE_TRUE), + F1("phpversion", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("phpcredits", MAY_BE_NULL | MAY_BE_TRUE), + F1("php_sapi_name", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("php_uname", MAY_BE_NULL | MAY_BE_STRING), + F1("php_ini_scanned_files", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("php_ini_loaded_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("strnatcmp", MAY_BE_NULL | MAY_BE_LONG), + F0("strnatcasecmp", MAY_BE_NULL | MAY_BE_LONG), + F0("substr_count", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("strspn", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("strcspn", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("strtok", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + FN("strtoupper", MAY_BE_NULL | MAY_BE_STRING), + FN("strtolower", MAY_BE_NULL | MAY_BE_STRING), + F0("strpos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("stripos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("strrpos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("strripos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("strrev", MAY_BE_NULL | MAY_BE_STRING), + F1("hebrev", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("hebrevc", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("nl2br", MAY_BE_NULL | MAY_BE_STRING), + F1("basename", MAY_BE_NULL | MAY_BE_STRING), + F1("dirname", MAY_BE_NULL | MAY_BE_STRING), + F1("pathinfo", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), + F1("stripslashes", MAY_BE_NULL | MAY_BE_STRING), + F1("stripcslashes", MAY_BE_NULL | MAY_BE_STRING), + F1("strstr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("stristr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("strrchr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("str_shuffle", MAY_BE_NULL | MAY_BE_STRING), + F1("str_word_count", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("str_split", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("strpbrk", MAY_BE_FALSE | MAY_BE_STRING), + F0("substr_compare", MAY_BE_FALSE | MAY_BE_LONG), +#ifdef HAVE_STRCOLL + F0("strcoll", MAY_BE_NULL | MAY_BE_LONG), +#endif +#ifdef HAVE_STRFMON + F1("money_format", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif + F1("substr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + FN("substr_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING), + F1("quotemeta", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("ucfirst", MAY_BE_NULL | MAY_BE_STRING), + F1("lcfirst", MAY_BE_NULL | MAY_BE_STRING), + F1("ucwords", MAY_BE_NULL | MAY_BE_STRING), + FN("strtr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + FN("addslashes", MAY_BE_NULL | MAY_BE_STRING), + F1("addcslashes", MAY_BE_NULL | MAY_BE_STRING), + FN("rtrim", MAY_BE_NULL | MAY_BE_STRING), + FN("str_replace", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY | MAY_BE_ARRAY_OF_OBJECT), + FN("str_ireplace", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY | MAY_BE_ARRAY_OF_OBJECT), + F1("str_repeat", MAY_BE_NULL | MAY_BE_STRING), + F1("count_chars", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG), + F1("chunk_split", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + FN("trim", MAY_BE_NULL | MAY_BE_STRING), + FN("ltrim", MAY_BE_NULL | MAY_BE_STRING), + F1("strip_tags", MAY_BE_NULL | MAY_BE_STRING), + F0("similar_text", MAY_BE_NULL | MAY_BE_LONG), + F1("explode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + FN("implode", MAY_BE_NULL | MAY_BE_STRING), + FN("join", MAY_BE_NULL | MAY_BE_STRING), + FN("setlocale", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("localeconv", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), +#if HAVE_NL_LANGINFO + F1("nl_langinfo", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif + F1("soundex", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("levenshtein", MAY_BE_NULL | MAY_BE_LONG), + F1("chr", MAY_BE_NULL | MAY_BE_STRING), + F0("ord", MAY_BE_NULL | MAY_BE_LONG), + F0("parse_str", MAY_BE_NULL), + F1("str_getcsv", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING), + F1("str_pad", MAY_BE_NULL | MAY_BE_STRING), + F1("chop", MAY_BE_NULL | MAY_BE_STRING), + F1("strchr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("sprintf", MAY_BE_FALSE | MAY_BE_STRING), + F0("printf", MAY_BE_FALSE | MAY_BE_LONG), + F0("vprintf", MAY_BE_FALSE | MAY_BE_LONG), + F1("vsprintf", MAY_BE_FALSE | MAY_BE_STRING), + F0("fprintf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("vfprintf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("sscanf", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), + F1("fscanf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), + F1("parse_url", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_LONG), + F1("urlencode", MAY_BE_NULL | MAY_BE_STRING), + F1("urldecode", MAY_BE_NULL | MAY_BE_STRING), + F1("rawurlencode", MAY_BE_NULL | MAY_BE_STRING), + F1("rawurldecode", MAY_BE_NULL | MAY_BE_STRING), + F1("http_build_query", MAY_BE_FALSE | MAY_BE_STRING), +#if defined(HAVE_SYMLINK) || defined(PHP_WIN32) + F1("readlink", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("linkinfo", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("symlink", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("link", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F0("unlink", MAY_BE_FALSE | MAY_BE_TRUE), + F1("exec", MAY_BE_FALSE | MAY_BE_STRING), + F1("system", MAY_BE_FALSE | MAY_BE_STRING), + F1("escapeshellcmd", MAY_BE_NULL | MAY_BE_STRING), + F1("escapeshellarg", MAY_BE_NULL | MAY_BE_STRING), + F1("passthru", MAY_BE_FALSE | MAY_BE_STRING), + F1("shell_exec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#ifdef PHP_CAN_SUPPORT_PROC_OPEN + F1("proc_open", MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("proc_close", MAY_BE_FALSE | MAY_BE_LONG), + F0("proc_terminate", MAY_BE_FALSE | MAY_BE_TRUE), + F1("proc_get_status", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), +#endif +#ifdef HAVE_NICE + F0("proc_nice", MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F0("rand", MAY_BE_NULL | MAY_BE_LONG), + F0("srand", MAY_BE_NULL), + F0("getrandmax", MAY_BE_NULL | MAY_BE_LONG), + F0("mt_rand", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mt_srand", MAY_BE_NULL), + F0("mt_getrandmax", MAY_BE_NULL | MAY_BE_LONG), +#if HAVE_GETSERVBYNAME + F0("getservbyname", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), +#endif +#if HAVE_GETSERVBYPORT + F1("getservbyport", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif +#if HAVE_GETPROTOBYNAME + F0("getprotobyname", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), +#endif +#if HAVE_GETPROTOBYNUMBER + F1("getprotobynumber", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif + F0("getmyuid", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("getmygid", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("getmypid", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("getmyinode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("getlastmod", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("base64_decode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("base64_encode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("password_hash", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("password_get_info", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F0("password_needs_rehash", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("password_verify", MAY_BE_FALSE | MAY_BE_TRUE), + F1("convert_uuencode", MAY_BE_FALSE | MAY_BE_STRING), + F1("convert_uudecode", MAY_BE_FALSE | MAY_BE_STRING), + F0("abs", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE), + F0("ceil", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("floor", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("round", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("sin", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("cos", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("tan", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("asin", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("acos", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("atan", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("atanh", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("atan2", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("sinh", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("cosh", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("tanh", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("asinh", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("acosh", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("expm1", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("log1p", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("pi", MAY_BE_DOUBLE), + F0("is_finite", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_nan", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_infinite", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("pow", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), + F0("exp", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("log", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("log10", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("sqrt", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("hypot", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("deg2rad", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("rad2deg", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("bindec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE), + F0("hexdec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE), + F0("octdec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE), + F1("decbin", MAY_BE_NULL | MAY_BE_STRING), + F1("decoct", MAY_BE_NULL | MAY_BE_STRING), + FC("dechex", zend_dechex_info), + F1("base_convert", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("number_format", MAY_BE_NULL | MAY_BE_STRING), + F0("fmod", MAY_BE_NULL | MAY_BE_DOUBLE), +#ifdef HAVE_INET_NTOP + F1("inet_ntop", MAY_BE_FALSE | MAY_BE_STRING), +#endif +#ifdef HAVE_INET_PTON + F1("inet_pton", MAY_BE_FALSE | MAY_BE_STRING), +#endif + F0("ip2long", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("long2ip", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("getenv", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), +#ifdef HAVE_PUTENV + F0("putenv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F1("getopt", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), +#ifdef HAVE_GETLOADAVG + F1("sys_getloadavg", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_DOUBLE), +#endif +#ifdef HAVE_GETTIMEOFDAY + F1("microtime", MAY_BE_NULL | MAY_BE_DOUBLE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG | MAY_BE_STRING), + F1("gettimeofday", MAY_BE_NULL | MAY_BE_DOUBLE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_STRING), +#endif +#ifdef HAVE_GETRUSAGE + F1("getrusage", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG), +#endif +#ifdef HAVE_GETTIMEOFDAY + F1("uniqid", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif + F1("quoted_printable_decode", MAY_BE_NULL | MAY_BE_STRING), + F1("quoted_printable_encode", MAY_BE_NULL | MAY_BE_STRING), + F1("convert_cyr_string", MAY_BE_NULL | MAY_BE_STRING), + F1("get_current_user", MAY_BE_NULL | MAY_BE_STRING), + F0("set_time_limit", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("header_register_callback", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("get_cfg_var", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F0("magic_quotes_runtime", MAY_BE_NULL | MAY_BE_FALSE), + F0("set_magic_quotes_runtime", MAY_BE_NULL | MAY_BE_FALSE), + F0("get_magic_quotes_gpc", MAY_BE_NULL | MAY_BE_FALSE), + F0("get_magic_quotes_runtime", MAY_BE_NULL | MAY_BE_FALSE), + F0("error_log", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("error_get_last", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + FN("call_user_func", UNKNOWN_INFO), + FN("call_user_func_array", UNKNOWN_INFO), + FN("call_user_method", UNKNOWN_INFO), + FN("call_user_method_array", UNKNOWN_INFO), + FN("forward_static_call", UNKNOWN_INFO), + FN("forward_static_call_array", UNKNOWN_INFO), + F1("serialize", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + FN("unserialize", UNKNOWN_INFO), + F0("var_dump", MAY_BE_NULL), + F1("var_export", MAY_BE_NULL | MAY_BE_STRING), + F0("debug_zval_dump", MAY_BE_NULL), + F1("print_r", MAY_BE_FALSE | MAY_BE_STRING), + F0("memory_get_usage", MAY_BE_FALSE | MAY_BE_LONG), + F0("memory_get_peak_usage", MAY_BE_FALSE | MAY_BE_LONG), + F0("register_shutdown_function", MAY_BE_NULL | MAY_BE_FALSE), + F0("register_tick_function", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("unregister_tick_function", MAY_BE_NULL), + F1("highlight_file", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("show_source", MAY_BE_FALSE | MAY_BE_STRING), + F1("highlight_string", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("php_strip_whitespace", MAY_BE_FALSE | MAY_BE_STRING), + F1("ini_get", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("ini_get_all", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("ini_set", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("ini_alter", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("ini_restore", MAY_BE_NULL), + F1("get_include_path", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("set_include_path", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("restore_include_path", MAY_BE_NULL), + F0("setcookie", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("setrawcookie", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("header", MAY_BE_NULL), + F0("header_remove", MAY_BE_NULL), + F0("headers_sent", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("headers_list", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("http_response_code", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("connection_aborted", MAY_BE_LONG), + F0("connection_status", MAY_BE_LONG), + F0("ignore_user_abort", MAY_BE_NULL | MAY_BE_LONG), + F1("parse_ini_file", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("parse_ini_string", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), +#if ZEND_DEBUG + F1("config_get_hash", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), +#endif + F0("is_uploaded_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("move_uploaded_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("gethostbyaddr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("gethostbyname", MAY_BE_NULL | MAY_BE_STRING), + F1("gethostbynamel", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), +#ifdef HAVE_GETHOSTNAME + F1("gethostname", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif +#if defined(PHP_WIN32) || (HAVE_DNS_SEARCH_FUNC && !(defined(__BEOS__) || defined(NETWARE))) + F0("dns_check_record", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("checkdnsrr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +# if defined(PHP_WIN32) || HAVE_FULL_DNS_FUNCS + F0("dns_get_mx", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("getmxrr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("dns_get_record", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY), +# endif +#endif + F0("intval", MAY_BE_NULL | MAY_BE_LONG), + F0("floatval", MAY_BE_NULL | MAY_BE_DOUBLE), + F0("doubleval", MAY_BE_NULL | MAY_BE_DOUBLE), + FN("strval", MAY_BE_NULL | MAY_BE_STRING), + F0("boolval", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("gettype", MAY_BE_NULL | MAY_BE_STRING), + F0("settype", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + FC("is_null", zend_is_type_info), + F0("is_resource", MAY_BE_FALSE | MAY_BE_TRUE), // TODO: inline with support for closed resources + FC("is_bool", zend_is_type_info), + FC("is_long", zend_is_type_info), + FC("is_float", zend_is_type_info), + FC("is_int", zend_is_type_info), + FC("is_integer", zend_is_type_info), + FC("is_double", zend_is_type_info), + FC("is_real", zend_is_type_info), + F0("is_numeric", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + FC("is_string", zend_is_type_info), + FC("is_array", zend_is_type_info), + F0("is_object", MAY_BE_FALSE | MAY_BE_TRUE), // TODO: inline with support for incomplete class + F0("is_scalar", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_callable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("pclose", MAY_BE_FALSE | MAY_BE_LONG), + F1("popen", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("readfile", MAY_BE_FALSE | MAY_BE_LONG), + F0("rewind", MAY_BE_FALSE | MAY_BE_TRUE), + F0("rmdir", MAY_BE_FALSE | MAY_BE_TRUE), + F0("umask", MAY_BE_FALSE | MAY_BE_LONG), + F0("fclose", MAY_BE_FALSE | MAY_BE_TRUE), + F0("feof", MAY_BE_FALSE | MAY_BE_TRUE), + F1("fgetc", MAY_BE_FALSE | MAY_BE_STRING), + F1("fgets", MAY_BE_FALSE | MAY_BE_STRING), + F1("fgetss", MAY_BE_FALSE | MAY_BE_STRING), + F1("fread", MAY_BE_FALSE | MAY_BE_STRING), + F1("fopen", MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("fpassthru", MAY_BE_FALSE | MAY_BE_LONG), + F0("ftruncate", MAY_BE_FALSE | MAY_BE_TRUE), + F1("fstat", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG), + F0("fseek", MAY_BE_FALSE | MAY_BE_LONG), + F0("ftell", MAY_BE_FALSE | MAY_BE_LONG), + F0("fflush", MAY_BE_FALSE | MAY_BE_TRUE), + F0("fwrite", MAY_BE_FALSE | MAY_BE_LONG), + F0("fputs", MAY_BE_FALSE | MAY_BE_LONG), + F0("mkdir", MAY_BE_FALSE | MAY_BE_TRUE), + F0("rename", MAY_BE_FALSE | MAY_BE_TRUE), + F0("copy", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("tempnam", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("tmpfile", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("file_get_contents", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("file_put_contents", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("stream_select", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("stream_context_create", MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("stream_context_set_params", MAY_BE_FALSE | MAY_BE_TRUE), + F1("stream_context_get_params", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F0("stream_context_set_option", MAY_BE_FALSE | MAY_BE_TRUE), + FN("stream_context_get_options", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + FN("stream_context_get_default", MAY_BE_FALSE | MAY_BE_RESOURCE), + FN("stream_context_set_default", MAY_BE_FALSE | MAY_BE_RESOURCE), + FN("stream_filter_prepend", MAY_BE_FALSE | MAY_BE_RESOURCE), + FN("stream_filter_append", MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("stream_filter_remove", MAY_BE_FALSE | MAY_BE_TRUE), + F1("stream_socket_client", MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("stream_socket_server", MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("stream_socket_accept", MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("stream_socket_get_name", MAY_BE_FALSE | MAY_BE_STRING), + F1("stream_socket_recvfrom", MAY_BE_FALSE | MAY_BE_STRING), + F0("stream_socket_sendto", MAY_BE_FALSE | MAY_BE_LONG), + F0("stream_socket_enable_crypto", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_LONG), +#ifdef HAVE_SHUTDOWN + F0("stream_socket_shutdown", MAY_BE_FALSE | MAY_BE_TRUE), +#endif +#if HAVE_SOCKETPAIR + F1("stream_socket_pair", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_RESOURCE), +#endif + F0("stream_copy_to_stream", MAY_BE_FALSE | MAY_BE_LONG), + F1("stream_get_contents", MAY_BE_FALSE | MAY_BE_STRING), + F0("stream_supports_lock", MAY_BE_FALSE | MAY_BE_TRUE), + F1("fgetcsv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING), + F0("fputcsv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("flock", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("get_meta_tags", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), + F0("stream_set_read_buffer", MAY_BE_FALSE | MAY_BE_LONG), + F0("stream_set_write_buffer", MAY_BE_FALSE | MAY_BE_LONG), + F0("set_file_buffer", MAY_BE_FALSE | MAY_BE_LONG), + F0("stream_set_chunk_size", MAY_BE_FALSE | MAY_BE_LONG), + F0("set_socket_blocking", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("stream_set_blocking", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("socket_set_blocking", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("stream_get_meta_data", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F1("stream_get_line", MAY_BE_FALSE | MAY_BE_STRING), + F0("stream_wrapper_register", MAY_BE_FALSE | MAY_BE_TRUE), + F0("stream_register_wrapper", MAY_BE_FALSE | MAY_BE_TRUE), + F0("stream_wrapper_unregister", MAY_BE_FALSE | MAY_BE_TRUE), + F0("stream_wrapper_restore", MAY_BE_FALSE | MAY_BE_TRUE), + F1("stream_get_wrappers", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("stream_get_transports", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("stream_resolve_include_path", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("stream_is_local", MAY_BE_FALSE | MAY_BE_TRUE), + F1("get_headers", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), +#if HAVE_SYS_TIME_H || defined(PHP_WIN32) + F0("stream_set_timeout", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("socket_set_timeout", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F1("socket_get_status", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), +#if (!defined(__BEOS__) && !defined(NETWARE) && HAVE_REALPATH) || defined(ZTS) + F1("realpath", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +#endif +#ifdef HAVE_FNMATCH + F0("fnmatch", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F1("fsockopen", MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("pfsockopen", MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("pack", MAY_BE_FALSE | MAY_BE_STRING), + F1("unpack", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), + F1("get_browser", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F1("crypt", MAY_BE_NULL | MAY_BE_STRING), + FN("opendir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("closedir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("chdir", MAY_BE_FALSE | MAY_BE_TRUE), +#if defined(HAVE_CHROOT) && !defined(ZTS) && ENABLE_CHROOT_FUNC + F0("chroot", MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F1("getcwd", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("rewinddir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("readdir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("dir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT), + F1("scandir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), +#ifdef HAVE_GLOB + F1("glob", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), +#endif + F0("fileatime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("filectime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("filegroup", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("fileinode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("filemtime", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("fileowner", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("fileperms", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("filesize", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("filetype", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("file_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_writable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_writeable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_readable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_executable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_dir", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("is_link", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("stat", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("lstat", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), +#ifndef NETWARE + F0("chown", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("chgrp", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif +#if HAVE_LCHOWN + F0("lchown", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif +#if HAVE_LCHOWN + F0("lchgrp", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F0("chmod", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#if HAVE_UTIME + F0("touch", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F0("clearstatcache", MAY_BE_NULL), + F0("disk_total_space", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("disk_free_space", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("diskfreespace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_DOUBLE), + F0("realpath_cache_size", MAY_BE_NULL | MAY_BE_LONG), + F1("realpath_cache_get", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY), + F0("mail", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("ezmlm_hash", MAY_BE_NULL | MAY_BE_LONG), +#ifdef HAVE_SYSLOG_H + F0("openlog", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("syslog", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("closelog", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), +#endif + F0("lcg_value", MAY_BE_DOUBLE), + F1("metaphone", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("ob_start", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("ob_flush", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("ob_clean", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("ob_end_flush", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("ob_end_clean", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("ob_get_flush", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("ob_get_clean", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("ob_get_length", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("ob_get_level", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("ob_get_status", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + FN("ob_get_contents", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("ob_implicit_flush", MAY_BE_NULL), + F1("ob_list_handlers", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("ksort", MAY_BE_FALSE | MAY_BE_TRUE), + F0("krsort", MAY_BE_FALSE | MAY_BE_TRUE), + F0("natsort", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("natcasesort", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("asort", MAY_BE_FALSE | MAY_BE_TRUE), + F0("arsort", MAY_BE_FALSE | MAY_BE_TRUE), + F0("sort", MAY_BE_FALSE | MAY_BE_TRUE), + F0("rsort", MAY_BE_FALSE | MAY_BE_TRUE), + F0("usort", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("uasort", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("uksort", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("shuffle", MAY_BE_FALSE | MAY_BE_TRUE), + F0("array_walk", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("array_walk_recursive", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("count", MAY_BE_NULL | MAY_BE_LONG), + FN("end", UNKNOWN_INFO), + FN("prev", UNKNOWN_INFO), + FN("next", UNKNOWN_INFO), + FN("reset", UNKNOWN_INFO), + FN("current", UNKNOWN_INFO), + FN("key", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_STRING), + FN("min", UNKNOWN_INFO), + FN("max", UNKNOWN_INFO), + F0("in_array", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + FN("array_search", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING), + F0("extract", MAY_BE_NULL | MAY_BE_LONG), + F1("compact", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_fill", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), + F1("array_fill_keys", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + FC("range", zend_range_info), + F0("array_multisort", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("array_push", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + FN("array_pop", UNKNOWN_INFO), + FN("array_shift", UNKNOWN_INFO), + F0("array_unshift", MAY_BE_NULL | MAY_BE_LONG), + F1("array_splice", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_slice", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_merge", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_merge_recursive", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_replace", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_replace_recursive", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_keys", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("array_values", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_count_values", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG), + F1("array_column", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_reverse", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_reduce", UNKNOWN_INFO), + FN("array_pad", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_flip", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("array_change_key_case", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_rand", UNKNOWN_INFO), + FN("array_unique", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_intersect", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_intersect_key", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_intersect_ukey", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_uintersect", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_intersect_assoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_uintersect_assoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_intersect_uassoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_uintersect_uassoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + FN("array_diff", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_diff_key", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_diff_ukey", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_udiff", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_diff_assoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_udiff_assoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_diff_uassoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_udiff_uassoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F0("array_sum", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), + F0("array_product", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), + F1("array_filter", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + FN("array_map", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_chunk", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_combine", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F0("array_key_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("pos", UNKNOWN_INFO), + F0("sizeof", MAY_BE_NULL | MAY_BE_LONG), + F0("key_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("assert", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("assert_options", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT), + F0("version_compare", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_LONG), +#if HAVE_FTOK + F0("ftok", MAY_BE_NULL | MAY_BE_LONG), +#endif + F1("str_rot13", MAY_BE_NULL | MAY_BE_STRING), + F1("stream_get_filters", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("stream_filter_register", MAY_BE_FALSE | MAY_BE_TRUE), + F1("stream_bucket_make_writeable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT), + F1("stream_bucket_prepend", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("stream_bucket_append", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("stream_bucket_new", MAY_BE_FALSE | MAY_BE_OBJECT), + F0("output_add_rewrite_var", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("output_reset_rewrite_vars", MAY_BE_FALSE), + F1("sys_get_temp_dir", MAY_BE_NULL | MAY_BE_STRING), + + /* ext/date */ + F0("strtotime", MAY_BE_FALSE | MAY_BE_LONG), + F1("date", MAY_BE_FALSE | MAY_BE_STRING), + F0("idate", MAY_BE_FALSE | MAY_BE_LONG), + F1("gmdate", MAY_BE_FALSE | MAY_BE_STRING), + F0("mktime", MAY_BE_FALSE | MAY_BE_LONG), + F0("gmmktime", MAY_BE_FALSE | MAY_BE_LONG), + F0("checkdate", MAY_BE_FALSE | MAY_BE_TRUE), +#ifdef HAVE_STRFTIME + F1("strftime", MAY_BE_FALSE | MAY_BE_STRING), + F1("gmstrftime", MAY_BE_FALSE | MAY_BE_STRING), +#endif + F0("time", MAY_BE_LONG), + F1("localtime", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG), + F1("getdate", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), + F1("date_create", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_create_immutable", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_create_from_format", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_create_immutable_from_format", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_parse", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F1("date_parse_from_format", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F1("date_get_last_errors", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_ARRAY), + F1("date_format", MAY_BE_FALSE | MAY_BE_STRING), + FN("date_modify", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_add", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_sub", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_timezone_get", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_timezone_set", MAY_BE_FALSE | MAY_BE_OBJECT), + F0("date_offset_get", MAY_BE_FALSE | MAY_BE_LONG), + F1("date_diff", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_time_set", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_date_set", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_isodate_set", MAY_BE_FALSE | MAY_BE_OBJECT), + FN("date_timestamp_set", MAY_BE_FALSE | MAY_BE_OBJECT), + F0("date_timestamp_get", MAY_BE_FALSE | MAY_BE_LONG), + F1("timezone_open", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("timezone_name_get", MAY_BE_FALSE | MAY_BE_STRING), + F1("timezone_name_from_abbr", MAY_BE_FALSE | MAY_BE_STRING), + F0("timezone_offset_get", MAY_BE_FALSE | MAY_BE_LONG), + F1("timezone_transitions_get", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), + F1("timezone_location_get", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING), + F1("timezone_identifiers_list", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("timezone_abbreviations_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("timezone_version_get", MAY_BE_STRING), + F1("date_interval_create_from_date_string", MAY_BE_FALSE | MAY_BE_OBJECT), + F1("date_interval_format", MAY_BE_FALSE | MAY_BE_STRING), + F0("date_default_timezone_set", MAY_BE_FALSE | MAY_BE_TRUE), + F1("date_default_timezone_get", MAY_BE_STRING), + F1("date_sunrise", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), + F1("date_sunset", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), + F1("date_sun_info", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG), + + /* ext/preg */ + F0("preg_match", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("preg_match_all", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + FN("preg_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING), + FN("preg_replace_callback", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING), + F1("preg_filter", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING), + F1("preg_split", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F1("preg_quote", MAY_BE_NULL | MAY_BE_STRING), + F1("preg_grep", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F0("preg_last_error", MAY_BE_NULL | MAY_BE_LONG), + + /* ext/ereg */ + F0("ereg", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("ereg_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("eregi", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("eregi_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("split", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("spliti", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("sql_regcase", MAY_BE_NULL | MAY_BE_STRING), + + /* ext/mysql */ + F1("mysql_connect", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_pconnect", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("mysql_close", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_select_db", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_create_db", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_drop_db", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mysql_query", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE), + F1("mysql_unbuffered_query", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE), + F1("mysql_db_query", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE), + F1("mysql_list_dbs", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_list_tables", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_list_fields", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_list_processes", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_error", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_errno", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mysql_affected_rows", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mysql_insert_id", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_result", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_num_rows", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mysql_num_fields", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_fetch_row", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY), + F1("mysql_fetch_array", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), + F1("mysql_fetch_assoc", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY), + F1("mysql_fetch_object", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT), + F0("mysql_data_seek", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mysql_fetch_lengths", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG), + F1("mysql_fetch_field", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT), + F0("mysql_field_seek", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_free_result", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mysql_field_name", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_field_table", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_field_len", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_field_type", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_field_flags", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_escape_string", MAY_BE_NULL | MAY_BE_STRING), + F1("mysql_real_escape_string", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_stat", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_thread_id", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_client_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_ping", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mysql_get_client_info", MAY_BE_NULL | MAY_BE_STRING), + F1("mysql_get_host_info", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_get_proto_info", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_get_server_info", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_info", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_set_charset", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mysql", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_fieldname", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_fieldtable", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_fieldlen", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_fieldtype", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_fieldflags", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mysql_selectdb", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_createdb", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_dropdb", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_freeresult", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mysql_numfields", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mysql_numrows", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mysql_listdbs", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_listtables", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_listfields", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("mysql_db_name", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_dbname", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_tablename", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mysql_table_name", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + + /* ext/curl */ + F1("curl_init", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("curl_copy_handle", MAY_BE_NULL | MAY_BE_RESOURCE), + F1("curl_version", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F0("curl_setopt", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("curl_setopt_array", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + FN("curl_exec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("curl_getinfo", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), + F1("curl_error", MAY_BE_NULL | MAY_BE_STRING), + F0("curl_errno", MAY_BE_NULL | MAY_BE_LONG), + F0("curl_close", MAY_BE_NULL), + F1("curl_strerror", MAY_BE_NULL | MAY_BE_STRING), + F1("curl_multi_strerror", MAY_BE_NULL | MAY_BE_STRING), + F0("curl_reset", MAY_BE_NULL), + F1("curl_escape", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("curl_unescape", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("curl_pause", MAY_BE_NULL | MAY_BE_LONG), + F1("curl_multi_init", MAY_BE_NULL | MAY_BE_RESOURCE), + F0("curl_multi_add_handle", MAY_BE_NULL | MAY_BE_LONG), + F0("curl_multi_remove_handle", MAY_BE_NULL | MAY_BE_LONG), + F0("curl_multi_select", MAY_BE_NULL | MAY_BE_LONG), + F0("curl_multi_exec", MAY_BE_NULL | MAY_BE_LONG), + FN("curl_multi_getcontent", MAY_BE_NULL | MAY_BE_STRING), + F1("curl_multi_info_read", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_RESOURCE), + F0("curl_multi_close", MAY_BE_NULL), + F0("curl_multi_setopt", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("curl_share_init", MAY_BE_NULL | MAY_BE_RESOURCE), + F0("curl_share_close", MAY_BE_NULL), + F0("curl_share_setopt", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("curl_file_create", MAY_BE_OBJECT), + + /* ext/mbstring */ + F1("mb_convert_case", MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_strtoupper", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_strtolower", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_language", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("mb_internal_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("mb_http_input", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_http_output", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("mb_detect_order", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("mb_substitute_character", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_LONG | MAY_BE_STRING), + F0("mb_parse_str", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mb_output_handler", MAY_BE_NULL | MAY_BE_STRING), + F1("mb_preferred_mime_name", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mb_strlen", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mb_strpos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mb_strrpos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mb_stripos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("mb_strripos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mb_strstr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_strrchr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_stristr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_strrichr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mb_substr_count", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mb_substr", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_strcut", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mb_strwidth", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mb_strimwidth", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_convert_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), + F1("mb_detect_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_list_encodings", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("mb_encoding_aliases", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("mb_convert_kana", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_encode_mimeheader", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_decode_mimeheader", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_convert_variables", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_encode_numericentity", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_decode_numericentity", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mb_send_mail", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mb_get_info", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + F0("mb_check_encoding", MAY_BE_FALSE | MAY_BE_TRUE), + + F1("mb_regex_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING), + F1("mb_regex_set_options", MAY_BE_FALSE | MAY_BE_STRING), + F0("mb_ereg", MAY_BE_FALSE | MAY_BE_LONG), + F0("mb_eregi", MAY_BE_FALSE | MAY_BE_LONG), + F1("mb_ereg_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_eregi_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_ereg_replace_callback", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mb_split", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("mb_ereg_match", MAY_BE_FALSE | MAY_BE_TRUE), + F0("mb_ereg_search", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mb_ereg_search_pos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG), + F1("mb_ereg_search_regs", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING), + F0("mb_ereg_search_init", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mb_ereg_search_getregs", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING), + F0("mb_ereg_search_getpos", MAY_BE_LONG), + F0("mb_ereg_search_setpos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + + F0("mbregex_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("mbereg", MAY_BE_FALSE | MAY_BE_LONG), + F0("mberegi", MAY_BE_FALSE | MAY_BE_LONG), + F1("mbereg_replace", MAY_BE_FALSE | MAY_BE_STRING), + F1("mberegi_replace", MAY_BE_FALSE | MAY_BE_STRING), + F1("mbsplit", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F0("mbereg_match", MAY_BE_FALSE | MAY_BE_TRUE), + F0("mbereg_search", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mbereg_search_pos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG), + F1("mbereg_search_regs", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING), + F0("mbereg_search_init", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("mbereg_search_getregs", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING), + F0("mbereg_search_getpos", MAY_BE_LONG), + F0("mbereg_search_setpos", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + + /* ext/iconv */ + F1("iconv", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("iconv_get_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING), + F0("iconv_set_encoding", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("iconv_strlen", MAY_BE_FALSE | MAY_BE_LONG), + F1("iconv_substr", MAY_BE_FALSE | MAY_BE_STRING), + F0("iconv_strpos", MAY_BE_FALSE | MAY_BE_LONG), + F0("iconv_strrpos", MAY_BE_FALSE | MAY_BE_LONG), + F1("iconv_mime_encode", MAY_BE_FALSE | MAY_BE_STRING), + F1("iconv_mime_decode", MAY_BE_FALSE | MAY_BE_STRING), + F1("iconv_mime_decode_headers", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY), + + /* ext/json */ + F1("json_encode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("json_decode", MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY), + F0("json_last_error", MAY_BE_NULL | MAY_BE_LONG), + F1("json_last_error_msg", MAY_BE_NULL | MAY_BE_STRING), + + /* ext/xml */ + FN("xml_parser_create", MAY_BE_FALSE | MAY_BE_RESOURCE), + FN("xml_parser_create_ns", MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("xml_set_object", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_element_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_character_data_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_processing_instruction_handler",MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_default_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_unparsed_entity_decl_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_notation_decl_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_external_entity_ref_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_start_namespace_decl_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_set_end_namespace_decl_handler", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_parse", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("xml_parse_into_struct", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("xml_get_error_code", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("xml_error_string", MAY_BE_NULL | MAY_BE_STRING), + F0("xml_get_current_line_number", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("xml_get_current_column_number", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("xml_get_current_byte_index", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("xml_parser_free", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("xml_parser_set_option", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("xml_parser_get_option", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING), + F1("utf8_encode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("utf8_decode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + + /* ext/zlib */ + F0("readgzfile", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F0("gzrewind", MAY_BE_FALSE | MAY_BE_TRUE), + F0("gzclose", MAY_BE_FALSE | MAY_BE_TRUE), + F0("gzeof", MAY_BE_FALSE | MAY_BE_TRUE), + F1("gzgetc", MAY_BE_FALSE | MAY_BE_STRING), + F1("gzgets", MAY_BE_FALSE | MAY_BE_STRING), + F1("gzgetss", MAY_BE_FALSE | MAY_BE_STRING), + F1("gzread", MAY_BE_FALSE | MAY_BE_STRING), + F1("gzopen", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("gzpassthru", MAY_BE_FALSE | MAY_BE_LONG), + F0("gzseek", MAY_BE_FALSE | MAY_BE_LONG), + F0("gztell", MAY_BE_FALSE | MAY_BE_LONG), + F0("gzwrite", MAY_BE_FALSE | MAY_BE_LONG), + F0("gzputs", MAY_BE_FALSE | MAY_BE_LONG), + F1("gzfile", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("gzcompress", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("gzuncompress", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("gzdeflate", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("gzinflate", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("gzencode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("gzdecode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("zlib_encode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("zlib_decode", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("zlib_get_coding_type", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("ob_gzhandler", MAY_BE_FALSE | MAY_BE_STRING), + + /* ext/hash */ + F1("hash", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("hash_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("hash_hmac", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("hash_hmac_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("hash_init", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F0("hash_update", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F0("hash_update_stream", MAY_BE_NULL | MAY_BE_LONG), + F0("hash_update_file", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE), + F1("hash_final", MAY_BE_NULL | MAY_BE_STRING), + F1("hash_copy", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE), + F1("hash_algos", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING), + F1("hash_pbkdf2", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F1("mhash_keygen_s2k", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mhash_get_block_size", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG), + F1("mhash_get_hash_name", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), + F0("mhash_count", MAY_BE_NULL | MAY_BE_LONG), + F1("mhash", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING), +}; + +static HashTable func_info; +int zend_func_info_rid = -1; + +uint32_t zend_get_func_info(const zend_call_info *call_info, const zend_ssa *ssa) +{ + uint32_t ret = 0; + + if (call_info->callee_func->type == ZEND_INTERNAL_FUNCTION) { + func_info_t *info; + + if ((info = zend_hash_find_ptr(&func_info, Z_STR_P(CRT_CONSTANT_EX(call_info->caller_op_array, call_info->caller_init_opline->op2, ssa->rt_constants)))) != NULL) { + if (UNEXPECTED(zend_optimizer_is_disabled_func(info->name, info->name_len))) { + ret = MAY_BE_NULL; + } else if (info->info_func) { + ret = info->info_func(call_info, ssa); + } else { + ret = info->info; + } +#if 0 + } else { + fprintf(stderr, "Unknown internal function '%s'\n", func->common.function_name); +#endif + } + } else { + // FIXME: the order of functions matters!!! + zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)call_info->callee_func); + if (info) { + ret = info->return_info.type; + } + } + if (!ret) { + ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + if (call_info->callee_func->type == ZEND_INTERNAL_FUNCTION) { + ret |= FUNC_MAY_WARN; + } + if (call_info->callee_func->common.fn_flags & ZEND_ACC_GENERATOR) { + ret = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_OBJECT; + } else if (call_info->callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) { + ret |= MAY_BE_REF; + } else { + ret |= MAY_BE_RC1 | MAY_BE_RCN; + } + } + return ret; +} + +int zend_func_info_startup(void) +{ + zend_extension dummy; + size_t i; + + if (zend_func_info_rid == -1) { + zend_func_info_rid = zend_get_resource_handle(&dummy); + if (zend_func_info_rid < 0) { + return FAILURE; + } + + zend_hash_init(&func_info, sizeof(func_infos)/sizeof(func_info_t), NULL, NULL, 1); + for (i = 0; i < sizeof(func_infos)/sizeof(func_info_t); i++) { + if (zend_hash_str_add_ptr(&func_info, func_infos[i].name, func_infos[i].name_len, (void**)&func_infos[i]) == NULL) { + fprintf(stderr, "ERROR: Duplicate function info for \"%s\"\n", func_infos[i].name); + } + } + } + + return SUCCESS; +} + +int zend_func_info_shutdown(void) +{ + if (zend_func_info_rid != -1) { + zend_hash_destroy(&func_info); + zend_func_info_rid = -1; + } + return SUCCESS; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_func_info.h b/ext/opcache/Optimizer/zend_func_info.h new file mode 100644 index 0000000000..a126bef708 --- /dev/null +++ b/ext/opcache/Optimizer/zend_func_info.h @@ -0,0 +1,69 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, Func Info | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_FUNC_INFO_H +#define ZEND_FUNC_INFO_H + +#include "zend_ssa.h" + +/* func flags */ +#define ZEND_FUNC_INDIRECT_VAR_ACCESS (1<<0) +#define ZEND_FUNC_HAS_CALLS (1<<1) +#define ZEND_FUNC_VARARG (1<<2) +#define ZEND_FUNC_NO_LOOPS (1<<3) +#define ZEND_FUNC_IRREDUCIBLE (1<<4) +#define ZEND_FUNC_RECURSIVE (1<<7) +#define ZEND_FUNC_RECURSIVE_DIRECTLY (1<<8) +#define ZEND_FUNC_RECURSIVE_INDIRECTLY (1<<9) + +/* The following flags are valid only for return values of internal functions + * returned by zend_get_func_info() + */ +#define FUNC_MAY_WARN (1<<30) + +typedef struct _zend_func_info zend_func_info; +typedef struct _zend_call_info zend_call_info; + +#define ZEND_FUNC_INFO(op_array) \ + ((zend_func_info*)((op_array)->reserved[zend_func_info_rid])) + +#define ZEND_SET_FUNC_INFO(op_array, info) do { \ + zend_func_info** pinfo = (zend_func_info**)&(op_array)->reserved[zend_func_info_rid]; \ + *pinfo = info; \ + } while (0) + +BEGIN_EXTERN_C() + +extern int zend_func_info_rid; + +uint32_t zend_get_func_info(const zend_call_info *call_info, const zend_ssa *ssa); + +int zend_func_info_startup(void); +int zend_func_info_shutdown(void); + +END_EXTERN_C() + +#endif /* ZEND_FUNC_INFO_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c new file mode 100644 index 0000000000..a1a6c7b611 --- /dev/null +++ b/ext/opcache/Optimizer/zend_inference.c @@ -0,0 +1,3930 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, e-SSA based Type & Range Inference | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "zend_compile.h" +#include "zend_generators.h" +#include "zend_inference.h" +#include "zend_func_info.h" +#include "zend_call_graph.h" +#include "zend_worklist.h" + +/* The used range inference algorithm is described in: + * V. Campos, R. Rodrigues, I. de Assis Costa and F. Pereira. + * "Speed and Precision in Range Analysis", SBLP'12. + * + * There are a couple degrees of freedom, we use: + * * Propagation on SCCs. + * * e-SSA for live range splitting. + * * Only intra-procedural inference. + * * Widening with warmup passes, but without jump sets. + */ + +/* Whether to handle symbolic range constraints */ +#define SYM_RANGE + +/* Whether to handle negative range constraints */ +#define NEG_RANGE + +/* Number of warmup passes to use prior to widening */ +#define RANGE_WARMUP_PASSES 16 + +/* Logging for range inference in general */ +#if 0 +#define LOG_SSA_RANGE(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG_SSA_RANGE(...) +#endif + +/* Logging for negative range constraints */ +#if 0 +#define LOG_NEG_RANGE(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG_NEG_RANGE(...) +#endif + +/* Pop elements in unspecified order from worklist until it is empty */ +#define WHILE_WORKLIST(worklist, len, i) do { \ + zend_bool _done = 0; \ + while (!_done) { \ + _done = 1; \ + ZEND_BITSET_FOREACH(worklist, len, i) { \ + zend_bitset_excl(worklist, i); \ + _done = 0; + +#define WHILE_WORKLIST_END() \ + } ZEND_BITSET_FOREACH_END(); \ + } \ +} while (0) + +#define CHECK_SCC_VAR(var2) \ + do { \ + if (!ssa->vars[var2].no_val) { \ + if (dfs[var2] < 0) { \ + zend_ssa_check_scc_var(op_array, ssa, var2, index, dfs, root, stack); \ + } \ + if (ssa->vars[var2].scc < 0 && dfs[root[var]] >= dfs[root[var2]]) { \ + root[var] = root[var2]; \ + } \ + } \ + } while (0) + +#define CHECK_SCC_ENTRY(var2) \ + do { \ + if (ssa->vars[var2].scc != ssa->vars[var].scc) { \ + ssa->vars[var2].scc_entry = 1; \ + } \ + } while (0) + +#define ADD_SCC_VAR(_var) \ + do { \ + if (ssa->vars[_var].scc == scc) { \ + zend_bitset_incl(worklist, _var); \ + } \ + } while (0) + +#define ADD_SCC_VAR_1(_var) \ + do { \ + if (ssa->vars[_var].scc == scc && \ + !zend_bitset_in(visited, _var)) { \ + zend_bitset_incl(worklist, _var); \ + } \ + } while (0) + +#define FOR_EACH_DEFINED_VAR(line, MACRO) \ + do { \ + if (ssa->ops[line].op1_def >= 0) { \ + MACRO(ssa->ops[line].op1_def); \ + } \ + if (ssa->ops[line].op2_def >= 0) { \ + MACRO(ssa->ops[line].op2_def); \ + } \ + if (ssa->ops[line].result_def >= 0) { \ + MACRO(ssa->ops[line].result_def); \ + } \ + if (op_array->opcodes[line].opcode == ZEND_OP_DATA) { \ + if (ssa->ops[line-1].op1_def >= 0) { \ + MACRO(ssa->ops[line-1].op1_def); \ + } \ + if (ssa->ops[line-1].op2_def >= 0) { \ + MACRO(ssa->ops[line-1].op2_def); \ + } \ + if (ssa->ops[line-1].result_def >= 0) { \ + MACRO(ssa->ops[line-1].result_def); \ + } \ + } else if ((uint32_t)line+1 < op_array->last && \ + op_array->opcodes[line+1].opcode == ZEND_OP_DATA) { \ + if (ssa->ops[line+1].op1_def >= 0) { \ + MACRO(ssa->ops[line+1].op1_def); \ + } \ + if (ssa->ops[line+1].op2_def >= 0) { \ + MACRO(ssa->ops[line+1].op2_def); \ + } \ + if (ssa->ops[line+1].result_def >= 0) { \ + MACRO(ssa->ops[line+1].result_def); \ + } \ + } \ + } while (0) + + +#define FOR_EACH_VAR_USAGE(_var, MACRO) \ + do { \ + zend_ssa_phi *p = ssa->vars[_var].phi_use_chain; \ + int use = ssa->vars[_var].use_chain; \ + while (use >= 0) { \ + FOR_EACH_DEFINED_VAR(use, MACRO); \ + use = zend_ssa_next_use(ssa->ops, _var, use); \ + } \ + p = ssa->vars[_var].phi_use_chain; \ + while (p) { \ + MACRO(p->ssa_var); \ + p = zend_ssa_next_use_phi(ssa, _var, p); \ + } \ + } while (0) + +static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, int *dfs, int *root, zend_worklist_stack *stack) /* {{{ */ +{ +#ifdef SYM_RANGE + zend_ssa_phi *p; +#endif + + dfs[var] = *index; + (*index)++; + root[var] = var; + + FOR_EACH_VAR_USAGE(var, CHECK_SCC_VAR); + +#ifdef SYM_RANGE + /* Process symbolic control-flow constraints */ + p = ssa->vars[var].sym_use_chain; + while (p) { + CHECK_SCC_VAR(p->ssa_var); + p = p->sym_use_chain; + } +#endif + + if (root[var] == var) { + ssa->vars[var].scc = ssa->sccs; + while (stack->len > 0) { + int var2 = zend_worklist_stack_peek(stack); + if (dfs[var2] <= dfs[var]) { + break; + } + zend_worklist_stack_pop(stack); + ssa->vars[var2].scc = ssa->sccs; + } + ssa->sccs++; + } else { + zend_worklist_stack_push(stack, var); + } +} +/* }}} */ + +int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ +{ + int index = 0, *dfs, *root; + zend_worklist_stack stack; + int j; + ALLOCA_FLAG(dfs_use_heap) + ALLOCA_FLAG(root_use_heap) + ALLOCA_FLAG(stack_use_heap) + + dfs = do_alloca(sizeof(int) * ssa->vars_count, dfs_use_heap); + memset(dfs, -1, sizeof(int) * ssa->vars_count); + root = do_alloca(sizeof(int) * ssa->vars_count, root_use_heap); + ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap); + + /* Find SCCs using Tarjan's algorithm. */ + for (j = 0; j < ssa->vars_count; j++) { + if (!ssa->vars[j].no_val && dfs[j] < 0) { + zend_ssa_check_scc_var(op_array, ssa, j, &index, dfs, root, &stack); + } + } + + /* Revert SCC order. This results in a topological order. */ + for (j = 0; j < ssa->vars_count; j++) { + if (ssa->vars[j].scc >= 0) { + ssa->vars[j].scc = ssa->sccs - (ssa->vars[j].scc + 1); + } + } + + for (j = 0; j < ssa->vars_count; j++) { + if (ssa->vars[j].scc >= 0) { + int var = j; + if (root[j] == j) { + ssa->vars[j].scc_entry = 1; + } + FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY); + } + } + + ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap); + free_alloca(root, root_use_heap); + free_alloca(dfs, dfs_use_heap); + + return SUCCESS; +} +/* }}} */ + +static inline zend_bool is_no_val_use(const zend_op *opline, const zend_ssa_op *ssa_op, int var) +{ + if (opline->opcode == ZEND_ASSIGN || + (opline->opcode == ZEND_UNSET_VAR && (opline->extended_value & ZEND_QUICK_SET))) { + return ssa_op->op1_use == var && ssa_op->op2_use != var; + } + if (opline->opcode == ZEND_FE_FETCH_R) { + return ssa_op->op2_use == var && ssa_op->op1_use != var; + } + return 0; +} + +int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ +{ + zend_ssa_var *ssa_vars = ssa->vars; + zend_ssa_op *ssa_ops = ssa->ops; + int ssa_vars_count = ssa->vars_count; + zend_bitset worklist; + int i, j, use; + zend_ssa_phi *p; + ALLOCA_FLAG(use_heap); + + if (!op_array->function_name || !ssa->vars || !ssa->ops) { + return SUCCESS; + } + + worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap); + memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count)); + + for (i = 0; i < ssa_vars_count; i++) { + ssa_vars[i].no_val = 1; /* mark as unused */ + use = ssa->vars[i].use_chain; + while (use >= 0) { + if (!is_no_val_use(&op_array->opcodes[use], &ssa->ops[use], i)) { + ssa_vars[i].no_val = 0; /* used directly */ + zend_bitset_incl(worklist, i); + break; + } + use = zend_ssa_next_use(ssa_ops, i, use); + } + } + + WHILE_WORKLIST(worklist, zend_bitset_len(ssa_vars_count), i) { + if (ssa_vars[i].definition_phi) { + /* mark all possible sources as used */ + p = ssa_vars[i].definition_phi; + if (p->pi >= 0) { + if (ssa_vars[p->sources[0]].no_val) { + ssa_vars[p->sources[0]].no_val = 0; /* used indirectly */ + zend_bitset_incl(worklist, p->sources[0]); + } + } else { + for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) { + if (p->sources[j] >= 0 && ssa->vars[p->sources[j]].no_val) { + ssa_vars[p->sources[j]].no_val = 0; /* used indirectly */ + zend_bitset_incl(worklist, p->sources[j]); + } + } + } + } + } WHILE_WORKLIST_END(); + + free_alloca(worklist, use_heap); + + return SUCCESS; +} +/* }}} */ + +/* From "Hacker's Delight" */ +zend_ulong minOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d) +{ + zend_ulong m, temp; + + m = 1L << (sizeof(zend_ulong) * 8 - 1); + while (m != 0) { + if (~a & c & m) { + temp = (a | m) & -m; + if (temp <= b) { + a = temp; + break; + } + } else if (a & ~c & m) { + temp = (c | m) & -m; + if (temp <= d) { + c = temp; + break; + } + } + m = m >> 1; + } + return a | c; +} + +zend_ulong maxOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d) +{ + zend_ulong m, temp; + + m = 1L << (sizeof(zend_ulong) * 8 - 1); + while (m != 0) { + if (b & d & m) { + temp = (b - m) | (m - 1); + if (temp >= a) { + b = temp; + break; + } + temp = (d - m) | (m - 1); + if (temp >= c) { + d = temp; + break; + } + } + m = m >> 1; + } + return b | d; +} + +zend_ulong minAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d) +{ + zend_ulong m, temp; + + m = 1L << (sizeof(zend_ulong) * 8 - 1); + while (m != 0) { + if (~a & ~c & m) { + temp = (a | m) & -m; + if (temp <= b) { + a = temp; + break; + } + temp = (c | m) & -m; + if (temp <= d) { + c = temp; + break; + } + } + m = m >> 1; + } + return a & c; +} + +zend_ulong maxAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d) +{ + zend_ulong m, temp; + + m = 1L << (sizeof(zend_ulong) * 8 - 1); + while (m != 0) { + if (b & ~d & m) { + temp = (b | ~m) | (m - 1); + if (temp >= a) { + b = temp; + break; + } + } else if (~b & d & m) { + temp = (d | ~m) | (m - 1); + if (temp >= c) { + d = temp; + break; + } + } + m = m >> 1; + } + return b & d; +} + +zend_ulong minXOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d) +{ + return minAND(a, b, ~d, ~c) | minAND(~b, ~a, c, d); +} + +zend_ulong maxXOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d) +{ + return maxOR(0, maxAND(a, b, ~d, ~c), 0, maxAND(~b, ~a, c, d)); +} + +/* Based on "Hacker's Delight" */ + +/* +0: + + + + 0 0 0 0 => 0 0 + min/max +2: + + - + 0 0 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d) +3: + + - - 0 0 1 1 => 1 1 - min/max +8: - + + + 1 0 0 0 => 1 0 ? min(a,-1,b,d)/max(0,b,c,d) +a: - + - + 1 0 1 0 => 1 0 ? MIN(a,c)/max(0,b,0,d) +b: - + - - 1 0 1 1 => 1 1 - c/-1 +c: - - + + 1 1 0 0 => 1 1 - min/max +e: - - - + 1 1 1 0 => 1 1 - a/-1 +f - - - - 1 1 1 1 => 1 1 - min/max +*/ +static void zend_ssa_range_or(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp) +{ + int x = ((a < 0) ? 8 : 0) | + ((b < 0) ? 4 : 0) | + ((c < 0) ? 2 : 0) | + ((d < 0) ? 2 : 0); + switch (x) { + case 0x0: + case 0x3: + case 0xc: + case 0xf: + tmp->min = minOR(a, b, c, d); + tmp->max = maxOR(a, b, c, d); + break; + case 0x2: + tmp->min = minOR(a, b, c, -1); + tmp->max = maxOR(a, b, 0, d); + break; + case 0x8: + tmp->min = minOR(a, -1, c, d); + tmp->max = maxOR(0, b, c, d); + break; + case 0xa: + tmp->min = MIN(a, c); + tmp->max = maxOR(0, b, 0, d); + break; + case 0xb: + tmp->min = c; + tmp->max = -1; + break; + case 0xe: + tmp->min = a; + tmp->max = -1; + break; + } +} + +/* +0: + + + + 0 0 0 0 => 0 0 + min/max +2: + + - + 0 0 1 0 => 0 0 + 0/b +3: + + - - 0 0 1 1 => 0 0 + min/max +8: - + + + 1 0 0 0 => 0 0 + 0/d +a: - + - + 1 0 1 0 => 1 0 ? min(a,-1,c,-1)/NAX(b,d) +b: - + - - 1 0 1 1 => 1 0 ? min(a,-1,c,d)/max(0,b,c,d) +c: - - + + 1 1 0 0 => 1 1 - min/max +e: - - - + 1 1 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d) +f - - - - 1 1 1 1 => 1 1 - min/max +*/ +static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp) +{ + int x = ((a < 0) ? 8 : 0) | + ((b < 0) ? 4 : 0) | + ((c < 0) ? 2 : 0) | + ((d < 0) ? 2 : 0); + switch (x) { + case 0x0: + case 0x3: + case 0xc: + case 0xf: + tmp->min = minAND(a, b, c, d); + tmp->max = maxAND(a, b, c, d); + break; + case 0x2: + tmp->min = 0; + tmp->max = b; + break; + case 0x8: + tmp->min = 0; + tmp->max = d; + break; + case 0xa: + tmp->min = minAND(a, -1, c, -1); + tmp->max = MAX(b, d); + break; + case 0xb: + tmp->min = minAND(a, -1, c, d); + tmp->max = maxAND(0, b, c, d); + break; + case 0xe: + tmp->min = minAND(a, b, c, -1); + tmp->max = maxAND(a, b, 0, d); + break; + } +} + +/* Get the normal op corresponding to a compound assignment op */ +static inline zend_uchar get_compound_assign_op(zend_uchar opcode) { + switch (opcode) { + case ZEND_ASSIGN_ADD: return ZEND_ADD; + case ZEND_ASSIGN_SUB: return ZEND_SUB; + case ZEND_ASSIGN_MUL: return ZEND_MUL; + case ZEND_ASSIGN_DIV: return ZEND_DIV; + case ZEND_ASSIGN_MOD: return ZEND_MOD; + case ZEND_ASSIGN_SL: return ZEND_SL; + case ZEND_ASSIGN_SR: return ZEND_SR; + case ZEND_ASSIGN_CONCAT: return ZEND_CONCAT; + case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR; + case ZEND_ASSIGN_BW_AND: return ZEND_BW_AND; + case ZEND_ASSIGN_BW_XOR: return ZEND_BW_XOR; + case ZEND_ASSIGN_POW: return ZEND_POW; + EMPTY_SWITCH_DEFAULT_CASE() + } +} + +static int zend_inference_calc_binary_op_range( + const zend_op_array *op_array, zend_ssa *ssa, + zend_op *opline, zend_ssa_op *ssa_op, zend_uchar opcode, zend_ssa_range *tmp) { + zend_long op1_min, op2_min, op1_max, op2_max, t1, t2, t3, t4; + + switch (opcode) { + case ZEND_ADD: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + tmp->min = op1_min + op2_min; + tmp->max = op1_max + op2_max; + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + (op1_min < 0 && op2_min < 0 && tmp->min >= 0)) { + tmp->underflow = 1; + tmp->min = ZEND_LONG_MIN; + } + if (OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW() || + (op1_max > 0 && op2_max > 0 && tmp->max <= 0)) { + tmp->overflow = 1; + tmp->max = ZEND_LONG_MAX; + } + return 1; + } + break; + case ZEND_SUB: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + tmp->min = op1_min - op2_max; + tmp->max = op1_max - op2_min; + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_OVERFLOW() || + (op1_min < 0 && op2_max > 0 && tmp->min >= 0)) { + tmp->underflow = 1; + tmp->min = ZEND_LONG_MIN; + } + if (OP1_RANGE_OVERFLOW() || + OP2_RANGE_UNDERFLOW() || + (op1_max > 0 && op2_min < 0 && tmp->max <= 0)) { + tmp->overflow = 1; + tmp->max = ZEND_LONG_MAX; + } + return 1; + } + break; + case ZEND_MUL: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + t1 = op1_min * op2_min; + t2 = op1_min * op2_max; + t3 = op1_max * op2_min; + t4 = op1_max * op2_max; + // FIXME: more careful overflow checks? + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW() || + (double)t1 != (double)op1_min * (double)op2_min || + (double)t2 != (double)op1_min * (double)op2_max || + (double)t3 != (double)op1_max * (double)op2_min || + (double)t4 != (double)op1_max * (double)op2_max) { + tmp->underflow = 1; + tmp->overflow = 1; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + tmp->min = MIN(MIN(t1, t2), MIN(t3, t4)); + tmp->max = MAX(MAX(t1, t2), MAX(t3, t4)); + } + return 1; + } + break; + case ZEND_DIV: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + if (op2_min <= 0 && op2_max >= 0) { + break; + } + if (op1_min == ZEND_LONG_MIN && op2_max == -1) { + /* Avoid ill-defined division, which may trigger SIGFPE. */ + break; + } + t1 = op1_min / op2_min; + t2 = op1_min / op2_max; + t3 = op1_max / op2_min; + t4 = op1_max / op2_max; + // FIXME: more careful overflow checks? + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW() || + t1 != (zend_long)((double)op1_min / (double)op2_min) || + t2 != (zend_long)((double)op1_min / (double)op2_max) || + t3 != (zend_long)((double)op1_max / (double)op2_min) || + t4 != (zend_long)((double)op1_max / (double)op2_max)) { + tmp->underflow = 1; + tmp->overflow = 1; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + tmp->min = MIN(MIN(t1, t2), MIN(t3, t4)); + tmp->max = MAX(MAX(t1, t2), MAX(t3, t4)); + } + return 1; + } + break; + case ZEND_MOD: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW()) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + if (op2_min == 0 || op2_max == 0) { + /* avoid division by zero */ + break; + } + t1 = (op2_min == -1) ? 0 : (op1_min % op2_min); + t2 = (op2_max == -1) ? 0 : (op1_min % op2_max); + t3 = (op2_min == -1) ? 0 : (op1_max % op2_min); + t4 = (op2_max == -1) ? 0 : (op1_max % op2_max); + tmp->min = MIN(MIN(t1, t2), MIN(t3, t4)); + tmp->max = MAX(MAX(t1, t2), MAX(t3, t4)); + } + return 1; + } + break; + case ZEND_SL: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW()) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + t1 = op1_min << op2_min; + t2 = op1_min << op2_max; + t3 = op1_max << op2_min; + t4 = op1_max << op2_max; + tmp->min = MIN(MIN(t1, t2), MIN(t3, t4)); + tmp->max = MAX(MAX(t1, t2), MAX(t3, t4)); + } + return 1; + } + break; + case ZEND_SR: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW()) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + t1 = op1_min >> op2_min; + t2 = op1_min >> op2_max; + t3 = op1_max >> op2_min; + t4 = op1_max >> op2_max; + tmp->min = MIN(MIN(t1, t2), MIN(t3, t4)); + tmp->max = MAX(MAX(t1, t2), MAX(t3, t4)); + } + return 1; + } + break; + case ZEND_BW_OR: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW()) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + zend_ssa_range_or(op1_min, op1_max, op2_min, op2_max, tmp); + } + return 1; + } + break; + case ZEND_BW_AND: + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + if (OP1_RANGE_UNDERFLOW() || + OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || + OP2_RANGE_OVERFLOW()) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + zend_ssa_range_and(op1_min, op1_max, op2_min, op2_max, tmp); + } + return 1; + } + break; + case ZEND_BW_XOR: + // TODO + break; + EMPTY_SWITCH_DEFAULT_CASE() + } + return 0; +} + +int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int var, int widening, int narrowing, zend_ssa_range *tmp) +{ + uint32_t line; + zend_op *opline; + zend_long op1_min, op2_min, op1_max, op2_max; + + if (ssa->vars[var].definition_phi) { + zend_ssa_phi *p = ssa->vars[var].definition_phi; + int i; + + tmp->underflow = 0; + tmp->min = ZEND_LONG_MAX; + tmp->max = ZEND_LONG_MIN; + tmp->overflow = 0; + if (p->pi >= 0 && p->has_range_constraint) { + zend_ssa_range_constraint *constraint = &p->constraint.range; + if (constraint->negative) { + if (ssa->var_info[p->sources[0]].has_range) { + *tmp = ssa->var_info[p->sources[0]].range; + } else if (narrowing) { + tmp->underflow = 1; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + tmp->overflow = 1; + } + +#ifdef NEG_RANGE + if (constraint->min_ssa_var < 0 && + constraint->max_ssa_var < 0 && + ssa->var_info[p->ssa_var].has_range) { + LOG_NEG_RANGE("%s() #%d [%ld..%ld] -> [%ld..%ld]?\n", + ZSTR_VAL(op_array->function_name), + p->ssa_var, + ssa->var_info[p->ssa_var].range.min, + ssa->var_info[p->ssa_var].range.max, + tmp->min, + tmp->max); + if (constraint->negative == NEG_USE_LT && + tmp->max >= constraint->range.min) { + tmp->overflow = 0; + tmp->max = constraint->range.min - 1; + LOG_NEG_RANGE(" => [%ld..%ld]\n", tmp->min, tmp->max); + } else if (constraint->negative == NEG_USE_GT && + tmp->min <= constraint->range.max) { + tmp->underflow = 0; + tmp->min = constraint->range.max + 1; + LOG_NEG_RANGE(" => [%ld..%ld]\n", tmp->min, tmp->max); + } + } +#endif + } else if (ssa->var_info[p->sources[0]].has_range) { + /* intersection */ + *tmp = ssa->var_info[p->sources[0]].range; + if (constraint->min_ssa_var < 0) { + tmp->underflow = constraint->range.underflow && tmp->underflow; + tmp->min = MAX(constraint->range.min, tmp->min); +#ifdef SYM_RANGE + } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) { + tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow && tmp->underflow; + tmp->min = MAX(ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min, tmp->min); +#endif + } + if (constraint->max_ssa_var < 0) { + tmp->max = MIN(constraint->range.max, tmp->max); + tmp->overflow = constraint->range.overflow && tmp->overflow; +#ifdef SYM_RANGE + } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) { + tmp->max = MIN(ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max, tmp->max); + tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow && tmp->overflow; +#endif + } + } else if (narrowing) { + if (constraint->min_ssa_var < 0) { + tmp->underflow = constraint->range.underflow; + tmp->min = constraint->range.min; +#ifdef SYM_RANGE + } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) { + tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow; + tmp->min = ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min; +#endif + } else { + tmp->underflow = 1; + tmp->min = ZEND_LONG_MIN; + } + if (constraint->max_ssa_var < 0) { + tmp->max = constraint->range.max; + tmp->overflow = constraint->range.overflow; +#ifdef SYM_RANGE + } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) { + tmp->max = ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max; + tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow; +#endif + } else { + tmp->max = ZEND_LONG_MAX; + tmp->overflow = 1; + } + } + } else { + for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) { + if (p->sources[i] >= 0 && ssa->var_info[p->sources[i]].has_range) { + /* union */ + tmp->underflow |= ssa->var_info[p->sources[i]].range.underflow; + tmp->min = MIN(tmp->min, ssa->var_info[p->sources[i]].range.min); + tmp->max = MAX(tmp->max, ssa->var_info[p->sources[i]].range.max); + tmp->overflow |= ssa->var_info[p->sources[i]].range.overflow; + } else if (narrowing) { + tmp->underflow = 1; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + tmp->overflow = 1; + } + } + } + return (tmp->min <= tmp->max); + } else if (ssa->vars[var].definition < 0) { + if (var < op_array->last_var && + op_array->function_name) { + + tmp->min = 0; + tmp->max = 0; + tmp->underflow = 0; + tmp->overflow = 0; + return 1; + } + return 0; + } + line = ssa->vars[var].definition; + opline = op_array->opcodes + line; + + tmp->underflow = 0; + tmp->overflow = 0; + 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_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + if (ssa->ops[line].result_def == var) { + return zend_inference_calc_binary_op_range( + op_array, ssa, opline, &ssa->ops[line], opline->opcode, tmp); + } + break; + + case ZEND_BW_NOT: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + if (OP1_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW()) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else { + op1_min = OP1_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + tmp->min = ~op1_max; + tmp->max = ~op1_min; + } + return 1; + } + } + break; + case ZEND_CAST: + if (ssa->ops[line].op1_def == var) { + if (ssa->ops[line].op1_def >= 0) { + if (OP1_HAS_RANGE()) { + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + } else if (ssa->ops[line].result_def == var) { + if (opline->extended_value == IS_NULL) { + tmp->min = 0; + tmp->max = 0; + return 1; + } else if (opline->extended_value == _IS_BOOL) { + if (OP1_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + tmp->min = (op1_min > 0 || op1_max < 0); + tmp->max = (op1_min != 0 || op1_max != 0); + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } else if (opline->extended_value == IS_LONG) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + return 1; + } else { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + return 1; + } + } + } + break; + case ZEND_BOOL: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + tmp->min = (op1_min > 0 || op1_max < 0); + tmp->max = (op1_min != 0 || op1_max != 0); + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_BOOL_NOT: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + tmp->min = (op1_min == 0 && op1_max == 0); + tmp->max = (op1_min <= 0 && op1_max >= 0); + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_BOOL_XOR: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + op1_min = (op1_min > 0 || op1_max < 0); + op1_max = (op1_min != 0 || op1_max != 0); + op2_min = (op2_min > 0 || op2_max < 0); + op2_max = (op2_min != 0 || op2_max != 0); + tmp->min = 0; + tmp->max = 1; + if (op1_min == op1_max && op2_min == op2_max) { + if (op1_min == op2_min) { + tmp->max = 0; + } else { + tmp->min = 1; + } + } + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_IS_IDENTICAL: + case ZEND_IS_EQUAL: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + + tmp->min = (op1_min == op1_max && + op2_min == op2_max && + op1_min == op2_max); + tmp->max = (op1_min <= op2_max && op1_max >= op2_min); + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_IS_NOT_IDENTICAL: + case ZEND_IS_NOT_EQUAL: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + + tmp->min = (op1_min > op2_max || op1_max < op2_min); + tmp->max = (op1_min != op1_max || + op2_min != op2_max || + op1_min != op2_max); + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_IS_SMALLER: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + + tmp->min = op1_max < op2_min; + tmp->max = op1_min < op2_max; + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_IS_SMALLER_OR_EQUAL: + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) { + op1_min = OP1_MIN_RANGE(); + op2_min = OP2_MIN_RANGE(); + op1_max = OP1_MAX_RANGE(); + op2_max = OP2_MAX_RANGE(); + + tmp->min = op1_max <= op2_min; + tmp->max = op1_min <= op2_max; + return 1; + } else { + tmp->min = 0; + tmp->max = 1; + return 1; + } + } + break; + case ZEND_QM_ASSIGN: + case ZEND_JMP_SET: + case ZEND_COALESCE: + if (ssa->ops[line].op1_def == var) { + if (ssa->ops[line].op1_def >= 0) { + if (OP1_HAS_RANGE()) { + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + } + if (ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + break; + case ZEND_ASSERT_CHECK: + if (ssa->ops[line].result_def == var) { + tmp->min = 0; + tmp->max = 1; + return 1; + } + break; + case ZEND_SEND_VAR: + if (ssa->ops[line].op1_def == var) { + if (ssa->ops[line].op1_def >= 0) { + if (OP1_HAS_RANGE()) { + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + } + break; + case ZEND_PRE_INC: + if (ssa->ops[line].op1_def == var || ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + if (tmp->max < ZEND_LONG_MAX) { + tmp->max++; + } else { + tmp->overflow = 1; + } + if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) { + tmp->min++; + } + return 1; + } + } + break; + case ZEND_PRE_DEC: + if (ssa->ops[line].op1_def == var || ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + if (tmp->min > ZEND_LONG_MIN) { + tmp->min--; + } else { + tmp->underflow = 1; + } + if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) { + tmp->max--; + } + return 1; + } + } + break; + case ZEND_POST_INC: + if (ssa->ops[line].op1_def == var || ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + if (ssa->ops[line].result_def == var) { + return 1; + } + if (tmp->max < ZEND_LONG_MAX) { + tmp->max++; + } else { + tmp->overflow = 1; + } + if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) { + tmp->min++; + } + return 1; + } + } + break; + case ZEND_POST_DEC: + if (ssa->ops[line].op1_def == var || ssa->ops[line].result_def == var) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + if (ssa->ops[line].result_def == var) { + return 1; + } + if (tmp->min > ZEND_LONG_MIN) { + tmp->min--; + } else { + tmp->underflow = 1; + } + if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) { + tmp->max--; + } + return 1; + } + } + break; + case ZEND_UNSET_DIM: + case ZEND_UNSET_OBJ: + if (ssa->ops[line].op1_def == var) { + /* If op1 is scalar, UNSET_DIM and UNSET_OBJ have no effect, so we can keep + * the previous ranges. */ + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + break; + case ZEND_ASSIGN: + if (ssa->ops[line].op1_def == var || ssa->ops[line].op2_def == var || ssa->ops[line].result_def == var) { + if (OP2_HAS_RANGE()) { + tmp->min = OP2_MIN_RANGE(); + tmp->max = OP2_MAX_RANGE(); + tmp->underflow = OP2_RANGE_UNDERFLOW(); + tmp->overflow = OP2_RANGE_OVERFLOW(); + return 1; + } + } + break; + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + if (ssa->ops[line+1].op1_def == var) { + if ((opline+1)->opcode == ZEND_OP_DATA) { + opline++; + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + 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_BW_OR: + case ZEND_ASSIGN_BW_AND: + case ZEND_ASSIGN_BW_XOR: + if (opline->extended_value == 0) { + if (ssa->ops[line].op1_def == var || ssa->ops[line].result_def == var) { + return zend_inference_calc_binary_op_range( + op_array, ssa, opline, &ssa->ops[line], + get_compound_assign_op(opline->opcode), tmp); + } + } else if ((opline+1)->opcode == ZEND_OP_DATA) { + if (ssa->ops[line+1].op1_def == var) { + opline++; + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + } + break; +// case ZEND_ASSIGN_CONCAT: + case ZEND_OP_DATA: + if ((opline-1)->opcode == ZEND_ASSIGN_DIM || + (opline-1)->opcode == ZEND_ASSIGN_OBJ || + (opline-1)->opcode == ZEND_ASSIGN_ADD || + (opline-1)->opcode == ZEND_ASSIGN_SUB || + (opline-1)->opcode == ZEND_ASSIGN_MUL) { + if (ssa->ops[line].op1_def == var) { + if (OP1_HAS_RANGE()) { + tmp->min = OP1_MIN_RANGE(); + tmp->max = OP1_MAX_RANGE(); + tmp->underflow = OP1_RANGE_UNDERFLOW(); + tmp->overflow = OP1_RANGE_OVERFLOW(); + return 1; + } + } + break; + } + break; + case ZEND_RECV: + case ZEND_RECV_INIT: + if (ssa->ops[line].result_def == var) { + zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + + if (func_info && + (int)opline->op1.num-1 < func_info->num_args && + func_info->arg_info[opline->op1.num-1].info.has_range) { + *tmp = func_info->arg_info[opline->op1.num-1].info.range; + return 1; + } else if (op_array->arg_info && + opline->op1.num <= op_array->num_args) { + if (op_array->arg_info[opline->op1.num-1].type_hint == IS_LONG) { + tmp->underflow = 0; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + tmp->overflow = 0; + return 1; + } else if (op_array->arg_info[opline->op1.num-1].type_hint == _IS_BOOL) { + tmp->underflow = 0; + tmp->min = 0; + tmp->max = 1; + tmp->overflow = 0; + return 1; + } + } + } + break; + case ZEND_STRLEN: + if (ssa->ops[line].result_def == var) { +#if SIZEOF_ZEND_LONG == 4 + /* The length of a string is a non-negative integer. However, on 32-bit + * platforms overflows into negative lengths may occur, so it's better + * to not assume any particular range. */ + tmp->min = ZEND_LONG_MIN; +#else + tmp->min = 0; +#endif + tmp->max = ZEND_LONG_MAX; + return 1; + } + break; + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + if (ssa->ops[line].result_def == var) { + zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + zend_call_info *call_info; + if (!func_info || !func_info->call_map) { + break; + } + + call_info = func_info->call_map[opline - op_array->opcodes]; + if (!call_info) { + break; + } + if (call_info->callee_func->type == ZEND_USER_FUNCTION) { + func_info = ZEND_FUNC_INFO(&call_info->callee_func->op_array); + if (func_info && func_info->return_info.has_range) { + *tmp = func_info->return_info.range; + return 1; + } + } +//TODO: we can't use type inference for internal functions at this point ??? +#if 0 + uint32_t type; + + type = zend_get_func_info(call_info, ssa); + if (!(type & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)))) { + tmp->underflow = 0; + tmp->min = 0; + tmp->max = 0; + tmp->overflow = 0; + if (type & MAY_BE_LONG) { + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + } else if (type & MAY_BE_TRUE) { + if (!(type & (MAY_BE_NULL|MAY_BE_FALSE))) { + tmp->min = 1; + } + tmp->max = 1; + } + return 1; + } +#endif + } + break; + // FIXME: support for more opcodes + default: + break; + } + return 0; +} + +void zend_inference_init_range(const zend_op_array *op_array, zend_ssa *ssa, int var, zend_bool underflow, zend_long min, zend_long max, zend_bool overflow) +{ + if (underflow) { + min = ZEND_LONG_MIN; + } + if (overflow) { + max = ZEND_LONG_MAX; + } + ssa->var_info[var].has_range = 1; + ssa->var_info[var].range.underflow = underflow; + ssa->var_info[var].range.min = min; + ssa->var_info[var].range.max = max; + ssa->var_info[var].range.overflow = overflow; + LOG_SSA_RANGE(" change range (init SCC %2d) %2d [%s%ld..%ld%s]\n", ssa->vars[var].scc, var, (underflow?"-- ":""), min, max, (overflow?" ++":"")); +} + +int zend_inference_widening_meet(zend_ssa_var_info *var_info, zend_ssa_range *r) +{ + if (!var_info->has_range) { + var_info->has_range = 1; + } else { + if (r->underflow || + var_info->range.underflow || + r->min < var_info->range.min) { + r->underflow = 1; + r->min = ZEND_LONG_MIN; + } + if (r->overflow || + var_info->range.overflow || + r->max > var_info->range.max) { + r->overflow = 1; + r->max = ZEND_LONG_MAX; + } + if (var_info->range.min == r->min && + var_info->range.max == r->max && + var_info->range.underflow == r->underflow && + var_info->range.overflow == r->overflow) { + return 0; + } + } + var_info->range = *r; + return 1; +} + +static int zend_ssa_range_widening(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc) +{ + zend_ssa_range tmp; + + if (zend_inference_calc_range(op_array, ssa, var, 1, 0, &tmp)) { + if (zend_inference_widening_meet(&ssa->var_info[var], &tmp)) { + LOG_SSA_RANGE(" change range (widening SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":"")); + return 1; + } + } + return 0; +} + +int zend_inference_narrowing_meet(zend_ssa_var_info *var_info, zend_ssa_range *r) +{ + if (!var_info->has_range) { + var_info->has_range = 1; + } else { + if (!r->underflow && + !var_info->range.underflow && + var_info->range.min < r->min) { + r->min = var_info->range.min; + } + if (!r->overflow && + !var_info->range.overflow && + var_info->range.max > r->max) { + r->max = var_info->range.max; + } + if (r->underflow) { + r->min = ZEND_LONG_MIN; + } + if (r->overflow) { + r->max = ZEND_LONG_MAX; + } + if (var_info->range.min == r->min && + var_info->range.max == r->max && + var_info->range.underflow == r->underflow && + var_info->range.overflow == r->overflow) { + return 0; + } + } + var_info->range = *r; + return 1; +} + +static int zend_ssa_range_narrowing(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc) +{ + zend_ssa_range tmp; + + if (zend_inference_calc_range(op_array, ssa, var, 0, 1, &tmp)) { + if (zend_inference_narrowing_meet(&ssa->var_info[var], &tmp)) { + LOG_SSA_RANGE(" change range (narrowing SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":"")); + return 1; + } + } + return 0; +} + +#ifdef NEG_RANGE +# define CHECK_INNER_CYCLE(var2) \ + do { \ + if (ssa->vars[var2].scc == ssa->vars[var].scc && \ + !ssa->vars[var2].scc_entry && \ + !zend_bitset_in(visited, var2) && \ + zend_check_inner_cycles(op_array, ssa, worklist, visited, var2)) { \ + return 1; \ + } \ + } while (0) + +static int zend_check_inner_cycles(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, zend_bitset visited, int var) +{ + if (zend_bitset_in(worklist, var)) { + return 1; + } + zend_bitset_incl(worklist, var); + FOR_EACH_VAR_USAGE(var, CHECK_INNER_CYCLE); + zend_bitset_incl(visited, var); + return 0; +} +#endif + +static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ssa, int *scc_var, int *next_scc_var, int scc) +{ + int worklist_len = zend_bitset_len(ssa->vars_count); + int j, n; + zend_ssa_range tmp; + ALLOCA_FLAG(use_heap); + zend_bitset worklist = do_alloca(sizeof(zend_ulong) * worklist_len * 2, use_heap); + zend_bitset visited = worklist + worklist_len; +#ifdef NEG_RANGE + int has_inner_cycles = 0; + + memset(worklist, 0, sizeof(zend_ulong) * worklist_len); + memset(visited, 0, sizeof(zend_ulong) * worklist_len); + j = scc_var[scc]; + while (j >= 0) { + if (!zend_bitset_in(visited, j) && + zend_check_inner_cycles(op_array, ssa, worklist, visited, j)) { + has_inner_cycles = 1; + break; + } + j = next_scc_var[j]; + } +#endif + + memset(worklist, 0, sizeof(zend_ulong) * worklist_len); + + for (n = 0; n < RANGE_WARMUP_PASSES; n++) { + j= scc_var[scc]; + while (j >= 0) { + if (ssa->vars[j].scc_entry) { + zend_bitset_incl(worklist, j); + } + j = next_scc_var[j]; + } + + memset(visited, 0, sizeof(zend_ulong) * worklist_len); + + WHILE_WORKLIST(worklist, worklist_len, j) { + if (zend_inference_calc_range(op_array, ssa, j, 0, 0, &tmp)) { +#ifdef NEG_RANGE + if (!has_inner_cycles && + ssa->var_info[j].has_range && + ssa->vars[j].definition_phi && + ssa->vars[j].definition_phi->pi >= 0 && + ssa->vars[j].definition_phi->has_range_constraint && + ssa->vars[j].definition_phi->constraint.range.negative && + ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0 && + ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0) { + zend_ssa_range_constraint *constraint = + &ssa->vars[j].definition_phi->constraint.range; + if (tmp.min == ssa->var_info[j].range.min && + tmp.max == ssa->var_info[j].range.max) { + if (constraint->negative == NEG_INIT) { + LOG_NEG_RANGE("#%d INVARIANT\n", j); + constraint->negative = NEG_INVARIANT; + } + } else if (tmp.min == ssa->var_info[j].range.min && + tmp.max == ssa->var_info[j].range.max + 1 && + tmp.max < constraint->range.min) { + if (constraint->negative == NEG_INIT || + constraint->negative == NEG_INVARIANT) { + LOG_NEG_RANGE("#%d LT\n", j); + constraint->negative = NEG_USE_LT; +//???NEG + } else if (constraint->negative == NEG_USE_GT) { + LOG_NEG_RANGE("#%d UNKNOWN\n", j); + constraint->negative = NEG_UNKNOWN; + } + } else if (tmp.max == ssa->var_info[j].range.max && + tmp.min == ssa->var_info[j].range.min - 1 && + tmp.min > constraint->range.max) { + if (constraint->negative == NEG_INIT || + constraint->negative == NEG_INVARIANT) { + LOG_NEG_RANGE("#%d GT\n", j); + constraint->negative = NEG_USE_GT; +//???NEG + } else if (constraint->negative == NEG_USE_LT) { + LOG_NEG_RANGE("#%d UNKNOWN\n", j); + constraint->negative = NEG_UNKNOWN; + } + } else { + LOG_NEG_RANGE("#%d UNKNOWN\n", j); + constraint->negative = NEG_UNKNOWN; + } + } +#endif + if (zend_inference_narrowing_meet(&ssa->var_info[j], &tmp)) { + LOG_SSA_RANGE(" change range (warmup %2d SCC %2d) %2d [%s%ld..%ld%s]\n", n, scc, j, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":"")); + zend_bitset_incl(visited, j); + FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR_1); + } + } + } WHILE_WORKLIST_END(); + } + free_alloca(worklist, use_heap); +} + +static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ +{ + int worklist_len = zend_bitset_len(ssa->vars_count); + zend_bitset worklist; + int *next_scc_var; + int *scc_var; + zend_ssa_phi *p; + zend_ssa_range tmp; + int scc, j; + ALLOCA_FLAG(use_heap); + + worklist = do_alloca( + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len) + + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count) + + sizeof(int) * ssa->sccs, use_heap); + next_scc_var = (int*)((char*)worklist + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len)); + scc_var = (int*)((char*)next_scc_var + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count)); + + LOG_SSA_RANGE("Range Inference\n"); + + /* Create linked lists of SSA variables for each SCC */ + memset(scc_var, -1, sizeof(int) * ssa->sccs); + for (j = 0; j < ssa->vars_count; j++) { + if (ssa->vars[j].scc >= 0) { + next_scc_var[j] = scc_var[ssa->vars[j].scc]; + scc_var[ssa->vars[j].scc] = j; + } + } + + for (scc = 0; scc < ssa->sccs; scc++) { + j = scc_var[scc]; + if (next_scc_var[j] < 0) { + /* SCC with a single element */ + if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) { + zend_inference_init_range(op_array, ssa, j, tmp.underflow, tmp.min, tmp.max, tmp.overflow); + } else { + zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1); + } + } else { + /* Find SCC entry points */ + memset(worklist, 0, sizeof(zend_ulong) * worklist_len); + do { + if (ssa->vars[j].scc_entry) { + zend_bitset_incl(worklist, j); + } + j = next_scc_var[j]; + } while (j >= 0); + +#if RANGE_WARMUP_PASSES > 0 + zend_infer_ranges_warmup(op_array, ssa, scc_var, next_scc_var, scc); + j = scc_var[scc]; + do { + zend_bitset_incl(worklist, j); + j = next_scc_var[j]; + } while (j >= 0); +#endif + + /* widening */ + WHILE_WORKLIST(worklist, worklist_len, j) { + if (zend_ssa_range_widening(op_array, ssa, j, scc)) { + FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR); + } + } WHILE_WORKLIST_END(); + + /* Add all SCC entry variables into worklist for narrowing */ + for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) { + if (!ssa->var_info[j].has_range) { + zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1); + } + zend_bitset_incl(worklist, j); + } + + /* narrowing */ + WHILE_WORKLIST(worklist, worklist_len, j) { + if (zend_ssa_range_narrowing(op_array, ssa, j, scc)) { + FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR); +#ifdef SYM_RANGE + /* Process symbolic control-flow constraints */ + p = ssa->vars[j].sym_use_chain; + while (p) { + ADD_SCC_VAR(p->ssa_var); + p = p->sym_use_chain; + } +#endif + } + } WHILE_WORKLIST_END(); + } + } + + free_alloca(worklist, use_heap); + + return SUCCESS; +} +/* }}} */ + +#define UPDATE_SSA_TYPE(_type, _var) \ + do { \ + uint32_t __type = (_type); \ + int __var = (_var); \ + if (__type & MAY_BE_REF) { \ + __type |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; \ + } \ + if (__var >= 0) { \ + if (ssa_vars[__var].var < op_array->last_var) { \ + if (__type & (MAY_BE_REF|MAY_BE_RCN)) { \ + __type |= MAY_BE_RC1 | MAY_BE_RCN; \ + } \ + if ((__type & MAY_BE_RC1) && (__type & MAY_BE_STRING)) {\ + /* TODO: support for array keys and ($str . "")*/ \ + __type |= MAY_BE_RCN; \ + } \ + } \ + if (ssa_var_info[__var].type != __type) { \ + if (ssa_var_info[__var].type & ~__type) { \ + handle_type_narrowing(op_array, ssa, worklist, \ + __var, ssa_var_info[__var].type, __type); \ + return FAILURE; \ + } \ + ssa_var_info[__var].type = __type; \ + add_usages(op_array, ssa, worklist, __var); \ + } \ + /*zend_bitset_excl(worklist, var);*/ \ + } \ + } while (0) + +#define UPDATE_SSA_OBJ_TYPE(_ce, _is_instanceof, var) \ + do { \ + if (var >= 0) { \ + if (ssa_var_info[var].ce != (_ce) || \ + ssa_var_info[var].is_instanceof != (_is_instanceof)) { \ + ssa_var_info[var].ce = (_ce); \ + ssa_var_info[var].is_instanceof = (_is_instanceof); \ + add_usages(op_array, ssa, worklist, var); \ + } \ + /*zend_bitset_excl(worklist, var);*/ \ + } \ + } while (0) + +#define COPY_SSA_OBJ_TYPE(from_var, to_var) do { \ + if ((from_var) >= 0 && (ssa_var_info[(from_var)].type & MAY_BE_OBJECT) \ + && ssa_var_info[(from_var)].ce) { \ + UPDATE_SSA_OBJ_TYPE(ssa_var_info[(from_var)].ce, \ + ssa_var_info[(from_var)].is_instanceof, (to_var)); \ + } else { \ + UPDATE_SSA_OBJ_TYPE(NULL, 0, (to_var)); \ + } \ +} while (0) + +static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var) +{ + if (ssa->vars[var].phi_use_chain) { + zend_ssa_phi *p = ssa->vars[var].phi_use_chain; + do { + zend_bitset_incl(worklist, p->ssa_var); + p = zend_ssa_next_use_phi(ssa, var, p); + } while (p); + } + if (ssa->vars[var].use_chain >= 0) { + int use = ssa->vars[var].use_chain; + zend_ssa_op *op; + + do { + op = ssa->ops + use; + if (op->result_def >= 0) { + zend_bitset_incl(worklist, op->result_def); + } + if (op->op1_def >= 0) { + zend_bitset_incl(worklist, op->op1_def); + } + if (op->op2_def >= 0) { + zend_bitset_incl(worklist, op->op2_def); + } + if (op_array->opcodes[use].opcode == ZEND_OP_DATA) { + op--; + if (op->result_def >= 0) { + zend_bitset_incl(worklist, op->result_def); + } + if (op->op1_def >= 0) { + zend_bitset_incl(worklist, op->op1_def); + } + if (op->op2_def >= 0) { + zend_bitset_incl(worklist, op->op2_def); + } + } + use = zend_ssa_next_use(ssa->ops, var, use); + } while (use >= 0); + } +} + +static void reset_dependent_vars(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var) +{ + zend_ssa_op *ssa_ops = ssa->ops; + zend_ssa_var *ssa_vars = ssa->vars; + zend_ssa_var_info *ssa_var_info = ssa->var_info; + zend_ssa_phi *p; + int use; + + p = ssa_vars[var].phi_use_chain; + while (p) { + if (ssa_var_info[p->ssa_var].type) { + ssa_var_info[p->ssa_var].type = 0; + zend_bitset_incl(worklist, p->ssa_var); + reset_dependent_vars(op_array, ssa, worklist, p->ssa_var); + } + p = zend_ssa_next_use_phi(ssa, var, p); + } + use = ssa_vars[var].use_chain; + while (use >= 0) { + if (ssa_ops[use].op1_def >= 0 && ssa_var_info[ssa_ops[use].op1_def].type) { + ssa_var_info[ssa_ops[use].op1_def].type = 0; + zend_bitset_incl(worklist, ssa_ops[use].op1_def); + reset_dependent_vars(op_array, ssa, worklist, ssa_ops[use].op1_def); + } + if (ssa_ops[use].op2_def >= 0 && ssa_var_info[ssa_ops[use].op2_def].type) { + ssa_var_info[ssa_ops[use].op2_def].type = 0; + zend_bitset_incl(worklist, ssa_ops[use].op2_def); + reset_dependent_vars(op_array, ssa, worklist, ssa_ops[use].op2_def); + } + if (ssa_ops[use].result_def >= 0 && ssa_var_info[ssa_ops[use].result_def].type) { + ssa_var_info[ssa_ops[use].result_def].type = 0; + zend_bitset_incl(worklist, ssa_ops[use].result_def); + reset_dependent_vars(op_array, ssa, worklist, ssa_ops[use].result_def); + } + if (op_array->opcodes[use+1].opcode == ZEND_OP_DATA) { + if (ssa_ops[use+1].op1_def >= 0 && ssa_var_info[ssa_ops[use+1].op1_def].type) { + ssa_var_info[ssa_ops[use+1].op1_def].type = 0; + zend_bitset_incl(worklist, ssa_ops[use+1].op1_def); + reset_dependent_vars(op_array, ssa, worklist, ssa_ops[use+1].op1_def); + } + if (ssa_ops[use+1].op2_def >= 0 && ssa_var_info[ssa_ops[use+1].op2_def].type) { + ssa_var_info[ssa_ops[use+1].op2_def].type = 0; + zend_bitset_incl(worklist, ssa_ops[use+1].op2_def); + reset_dependent_vars(op_array, ssa, worklist, ssa_ops[use+1].op2_def); + } + if (ssa_ops[use+1].result_def >= 0 && ssa_var_info[ssa_ops[use+1].result_def].type) { + ssa_var_info[ssa_ops[use+1].result_def].type = 0; + zend_bitset_incl(worklist, ssa_ops[use+1].result_def); + reset_dependent_vars(op_array, ssa, worklist, ssa_ops[use+1].result_def); + } + } + use = zend_ssa_next_use(ssa_ops, var, use); + } +#ifdef SYM_RANGE + /* Process symbolic control-flow constraints */ + p = ssa->vars[var].sym_use_chain; + while (p) { + ssa_var_info[p->ssa_var].type = 0; + zend_bitset_incl(worklist, p->ssa_var); + reset_dependent_vars(op_array, ssa, worklist, p->ssa_var); + p = p->sym_use_chain; + } +#endif +} + +static void handle_type_narrowing(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var, uint32_t old_type, uint32_t new_type) +{ + if (1) { + /* Right now, this is always a bug */ + zend_error(E_WARNING, "Narrowing occurred during type inference. Please file a bug report on bugs.php.net"); + } else { + /* if new_type set resets some bits from old_type set + * We have completely recalculate types of some dependent SSA variables + * (this may occurs mainly because of incremental inter-precudure + * type inference) + */ + reset_dependent_vars(op_array, ssa, worklist, var); + } +} + +uint32_t zend_array_element_type(uint32_t t1, int write, int insert) +{ + uint32_t tmp = 0; + + if (t1 & MAY_BE_OBJECT) { + tmp |= MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + if (t1 & MAY_BE_ARRAY) { + if (insert) { + tmp |= MAY_BE_NULL; + } else { + tmp |= MAY_BE_NULL | ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT); + if (tmp & MAY_BE_ARRAY) { + tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + if (t1 & MAY_BE_ARRAY_OF_REF) { + tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN; + } else if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + } + } + if (t1 & MAY_BE_STRING) { + tmp |= MAY_BE_STRING | MAY_BE_RC1; + if (write) { + tmp |= MAY_BE_NULL; + } + } + if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + tmp |= MAY_BE_NULL; + if (t1 & MAY_BE_ERROR) { + if (write) { + tmp |= MAY_BE_ERROR; + } + } + } + if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_NULL; + if (write) { + tmp |= MAY_BE_ERROR; + } + } + return tmp; +} + +static uint32_t assign_dim_result_type( + uint32_t arr_type, uint32_t dim_type, uint32_t value_type, zend_uchar dim_op_type) { + uint32_t tmp = arr_type & ~(MAY_BE_RC1|MAY_BE_RCN); + + if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE); + tmp |= MAY_BE_ARRAY|MAY_BE_RC1; + } + if (tmp & (MAY_BE_ARRAY|MAY_BE_STRING)) { + tmp |= MAY_BE_RC1; + } + if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + if (tmp & MAY_BE_ARRAY) { + tmp |= (value_type & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT; + if (value_type & MAY_BE_UNDEF) { + tmp |= MAY_BE_ARRAY_OF_NULL; + } + if (dim_op_type == IS_UNUSED) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } else { + if (dim_type & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + if (dim_type & MAY_BE_STRING) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + if (dim_op_type != IS_CONST) { + // FIXME: numeric string + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + } + if (dim_type & (MAY_BE_UNDEF|MAY_BE_NULL)) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + } + } + } + return tmp; +} + +/* For binary ops that have compound assignment operators */ +static uint32_t binary_op_result_type( + zend_ssa *ssa, zend_uchar opcode, uint32_t t1, uint32_t t2, uint32_t result_var) { + uint32_t tmp = 0; + uint32_t t1_type = (t1 & MAY_BE_ANY) | (t1 & MAY_BE_UNDEF ? MAY_BE_NULL : 0); + uint32_t t2_type = (t2 & MAY_BE_ANY) | (t2 & MAY_BE_UNDEF ? MAY_BE_NULL : 0); + switch (opcode) { + case ZEND_ADD: + if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) { + if (!ssa->var_info[result_var].has_range || + ssa->var_info[result_var].range.underflow || + ssa->var_info[result_var].range.overflow) { + /* may overflow */ + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } else { + tmp |= MAY_BE_LONG; + } + } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) { + tmp |= MAY_BE_DOUBLE; + } else if (t1_type == MAY_BE_ARRAY && t2_type == MAY_BE_ARRAY) { + tmp |= MAY_BE_ARRAY | MAY_BE_RC1; + tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); + tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); + } else { + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + if ((t1_type & MAY_BE_ARRAY) && (t2_type & MAY_BE_ARRAY)) { + tmp |= MAY_BE_ARRAY | MAY_BE_RC1; + tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); + tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF); + } + } + break; + case ZEND_SUB: + case ZEND_MUL: + if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) { + if (!ssa->var_info[result_var].has_range || + ssa->var_info[result_var].range.underflow || + ssa->var_info[result_var].range.overflow) { + /* may overflow */ + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } else { + tmp |= MAY_BE_LONG; + } + } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) { + tmp |= MAY_BE_DOUBLE; + } else { + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } + break; + case ZEND_DIV: + case ZEND_POW: + if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) { + tmp |= MAY_BE_DOUBLE; + } else { + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } + /* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special + * handling */ + break; + case ZEND_MOD: + tmp = MAY_BE_LONG; + /* Division by zero results in an exception, so it doesn't need any special handling */ + break; + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + if ((t1_type & MAY_BE_STRING) && (t2_type & MAY_BE_STRING)) { + tmp |= MAY_BE_STRING | MAY_BE_RC1; + } + if ((t1_type & ~MAY_BE_STRING) || (t2_type & ~MAY_BE_STRING)) { + tmp |= MAY_BE_LONG; + } + break; + case ZEND_SL: + case ZEND_SR: + tmp = MAY_BE_LONG; + break; + case ZEND_CONCAT: + case ZEND_FAST_CONCAT: + /* TODO: +MAY_BE_OBJECT ??? */ + tmp = MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN; + break; + EMPTY_SWITCH_DEFAULT_CASE() + } + return tmp; +} + +static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) { + zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL; + if (ce) { + return ce; + } + + ce = zend_hash_find_ptr(CG(class_table), lcname); + if (ce && ce->type == ZEND_INTERNAL_CLASS) { + return ce; + } + + return NULL; +} + +static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce) +{ + uint32_t tmp = 0; + + *pce = NULL; + if (arg_info->class_name) { + // class type hinting... + zend_string *lcname = zend_string_tolower(arg_info->class_name); + tmp |= MAY_BE_OBJECT; + *pce = get_class_entry(script, lcname); + zend_string_release(lcname); + } else if (arg_info->type_hint != IS_UNDEF) { + if (arg_info->type_hint == IS_VOID) { + tmp |= MAY_BE_NULL; + } else if (arg_info->type_hint == IS_CALLABLE) { + tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else if (arg_info->type_hint == IS_ITERABLE) { + tmp |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else if (arg_info->type_hint == IS_ARRAY) { + tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else if (arg_info->type_hint == _IS_BOOL) { + tmp |= MAY_BE_TRUE|MAY_BE_FALSE; + } else { + ZEND_ASSERT(arg_info->type_hint < IS_REFERENCE); + tmp |= 1 << arg_info->type_hint; + } + } else { + tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } + if (arg_info->allow_null) { + tmp |= MAY_BE_NULL; + } + return tmp; +} + +static int zend_update_type_info(const zend_op_array *op_array, + zend_ssa *ssa, + const zend_script *script, + zend_bitset worklist, + int i) +{ + uint32_t t1, t2; + uint32_t tmp, orig; + zend_op *opline = op_array->opcodes + i; + zend_ssa_op *ssa_ops = ssa->ops; + zend_ssa_var *ssa_vars = ssa->vars; + zend_ssa_var_info *ssa_var_info = ssa->var_info; + zend_class_entry *ce; + int j; + + if (opline->opcode == ZEND_OP_DATA) { + opline--; + i--; + } + + t1 = OP1_INFO(); + t2 = OP2_INFO(); + + switch (opline->opcode) { + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + case ZEND_DIV: + case ZEND_POW: + case ZEND_MOD: + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + case ZEND_SL: + case ZEND_SR: + case ZEND_CONCAT: + tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_ops[i].result_def); + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_BW_NOT: + tmp = 0; + if (t1 & MAY_BE_STRING) { + tmp |= MAY_BE_STRING | MAY_BE_RC1; + } + if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) { + tmp |= MAY_BE_LONG; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_BEGIN_SILENCE: + UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].result_def); + break; + case ZEND_BOOL_NOT: + case ZEND_BOOL_XOR: + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + case ZEND_IS_SMALLER: + case ZEND_IS_SMALLER_OR_EQUAL: + case ZEND_INSTANCEOF: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_CASE: + case ZEND_BOOL: + case ZEND_ISSET_ISEMPTY_VAR: + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: + case ZEND_ASSERT_CHECK: + UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_ops[i].result_def); + break; + case ZEND_CAST: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1; + if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) && + (opline->op1_type == IS_CV) && + (opline->extended_value == IS_ARRAY || + opline->extended_value == IS_OBJECT)) { + tmp |= MAY_BE_RCN; + } else if ((t1 & MAY_BE_STRING) && + (opline->op1_type == IS_CV) && + opline->extended_value == IS_STRING) { + tmp |= MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + tmp = 0; + if (opline->extended_value == _IS_BOOL) { + tmp |= MAY_BE_TRUE|MAY_BE_FALSE; + } else { + tmp |= 1 << opline->extended_value; + if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + if ((tmp & MAY_BE_ANY) == (t1 & MAY_BE_ANY)) { + tmp |= (t1 & MAY_BE_RC1) | MAY_BE_RCN; + } else if ((opline->extended_value == IS_ARRAY || + opline->extended_value == IS_OBJECT) && + (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT))) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else if (opline->extended_value == IS_STRING && + (t1 & (MAY_BE_STRING|MAY_BE_OBJECT))) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else { + tmp |= MAY_BE_RC1; + } + } + } + if (opline->extended_value == IS_ARRAY) { + if (t1 & MAY_BE_ARRAY) { + tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF); + } + if (t1 & MAY_BE_OBJECT) { + tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } else { + tmp |= ((t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT) | MAY_BE_ARRAY_KEY_LONG; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_QM_ASSIGN: + case ZEND_JMP_SET: + case ZEND_COALESCE: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1; + if ((t1 & (MAY_BE_RC1|MAY_BE_REF)) && (opline->op1_type == IS_CV)) { + tmp |= MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + tmp = t1 & ~(MAY_BE_UNDEF|MAY_BE_REF); + if (t1 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= (t1 & (MAY_BE_RC1|MAY_BE_RCN)); + if (opline->op1_type == IS_CV) { + tmp |= MAY_BE_RCN; + } + } + if (opline->opcode != ZEND_QM_ASSIGN) { + /* COALESCE and JMP_SET result can't be null */ + tmp &= ~MAY_BE_NULL; + if (opline->opcode == ZEND_JMP_SET) { + /* JMP_SET result can't be false either */ + tmp &= ~MAY_BE_FALSE; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def); + break; + case ZEND_ASSIGN_ADD: + case ZEND_ASSIGN_SUB: + case ZEND_ASSIGN_MUL: + case ZEND_ASSIGN_DIV: + case ZEND_ASSIGN_POW: + case ZEND_ASSIGN_MOD: + case ZEND_ASSIGN_SL: + case ZEND_ASSIGN_SR: + case ZEND_ASSIGN_BW_OR: + case ZEND_ASSIGN_BW_AND: + case ZEND_ASSIGN_BW_XOR: + case ZEND_ASSIGN_CONCAT: + orig = 0; + tmp = 0; + if (opline->extended_value == ZEND_ASSIGN_OBJ) { + tmp |= MAY_BE_REF; + orig = t1; + t1 = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + t2 = OP1_DATA_INFO(); + } else if (opline->extended_value == ZEND_ASSIGN_DIM) { + if (t1 & MAY_BE_ARRAY_OF_REF) { + tmp |= MAY_BE_REF; + } + orig = t1; + t1 = zend_array_element_type(t1, 1, 0); + t2 = OP1_DATA_INFO(); + } else { + if (t1 & MAY_BE_REF) { + tmp |= MAY_BE_REF; + } + } + + tmp |= binary_op_result_type( + ssa, get_compound_assign_op(opline->opcode), t1, t2, ssa_ops[i].op1_def); + if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY)) { + tmp |= MAY_BE_RC1; + } + if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + + if (opline->extended_value == ZEND_ASSIGN_DIM) { + if (opline->op1_type == IS_CV) { + orig = assign_dim_result_type(orig, OP2_INFO(), tmp, opline->op1_type); + UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { + if (opline->op1_type == IS_CV) { + if (orig & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + orig &= (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE); + orig |= MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN; + } + if (orig & MAY_BE_OBJECT) { + orig |= (MAY_BE_RC1|MAY_BE_RCN); + } + UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + } else { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + if (opline->extended_value == ZEND_ASSIGN_DIM) { + if (opline->op2_type == IS_UNUSED) { + /* When appending to an array and the LONG_MAX key is already used + * null will be returned. */ + tmp |= MAY_BE_NULL; + } + if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) { + /* Arrays and objects cannot be used as keys. */ + tmp |= MAY_BE_NULL; + } + if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) { + /* null and false are implicitly converted to array, anything else + * results in a null return value. */ + tmp |= MAY_BE_NULL; + } + } else if (opline->extended_value == ZEND_ASSIGN_OBJ) { + if (orig & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT))) { + /* null and false (and empty string) are implicitly converted to object, + * anything else results in a null return value. */ + tmp |= MAY_BE_NULL; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + tmp = 0; + if (t1 & MAY_BE_REF) { + tmp |= MAY_BE_REF; + } + if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RC1; + if (ssa_ops[i].result_def >= 0) { + tmp |= MAY_BE_RCN; + } + } + if ((t1 & MAY_BE_ANY) == MAY_BE_LONG) { + if (!ssa_var_info[ssa_ops[i].op1_use].has_range || + (opline->opcode == ZEND_PRE_DEC && + (ssa_var_info[ssa_ops[i].op1_use].range.underflow || + ssa_var_info[ssa_ops[i].op1_use].range.min == ZEND_LONG_MIN)) || + (opline->opcode == ZEND_PRE_INC && + (ssa_var_info[ssa_ops[i].op1_use].range.overflow || + ssa_var_info[ssa_ops[i].op1_use].range.max == ZEND_LONG_MAX))) { + /* may overflow */ + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } else { + tmp |= MAY_BE_LONG; + } + } else { + if (t1 & MAY_BE_ERROR) { + tmp |= MAY_BE_NULL; + } + if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) { + if (opline->opcode == ZEND_PRE_INC) { + tmp |= MAY_BE_LONG; + } else { + tmp |= MAY_BE_NULL; + } + } + if (t1 & MAY_BE_LONG) { + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } + if (t1 & MAY_BE_DOUBLE) { + tmp |= MAY_BE_DOUBLE; + } + if (t1 & MAY_BE_STRING) { + tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE; + } + tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY); + } + if (ssa_ops[i].op1_def >= 0) { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_POST_INC: + case ZEND_POST_DEC: + if (ssa_ops[i].result_def >= 0) { + tmp = 0; + if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RC1|MAY_BE_RCN; + } + tmp |= t1 & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_REF|MAY_BE_RCN); + if (t1 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + tmp = 0; + if (t1 & MAY_BE_REF) { + tmp |= MAY_BE_REF; + } + if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RC1; + } + if ((t1 & MAY_BE_ANY) == MAY_BE_LONG) { + if (!ssa_var_info[ssa_ops[i].op1_use].has_range || + (opline->opcode == ZEND_PRE_DEC && + (ssa_var_info[ssa_ops[i].op1_use].range.underflow || + ssa_var_info[ssa_ops[i].op1_use].range.min == ZEND_LONG_MIN)) || + (opline->opcode == ZEND_PRE_INC && + (ssa_var_info[ssa_ops[i].op1_use].range.overflow || + ssa_var_info[ssa_ops[i].op1_use].range.max == ZEND_LONG_MAX))) { + /* may overflow */ + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } else { + tmp |= MAY_BE_LONG; + } + } else { + if (t1 & MAY_BE_ERROR) { + tmp |= MAY_BE_NULL; + } + if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) { + if (opline->opcode == ZEND_POST_INC) { + tmp |= MAY_BE_LONG; + } else { + tmp |= MAY_BE_NULL; + } + } + if (t1 & MAY_BE_LONG) { + tmp |= MAY_BE_LONG | MAY_BE_DOUBLE; + } + if (t1 & MAY_BE_DOUBLE) { + tmp |= MAY_BE_DOUBLE; + } + if (t1 & MAY_BE_STRING) { + tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE; + } + tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY); + } + if (ssa_ops[i].op1_def >= 0) { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; + case ZEND_ASSIGN_DIM: + if (opline->op1_type == IS_CV) { + tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type); + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + tmp = 0; + if (t1 & MAY_BE_STRING) { + tmp |= MAY_BE_STRING; + } + if (t1 & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_STRING)) { + tmp |= (OP1_DATA_INFO() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF)); + + if (opline->op2_type == IS_UNUSED) { + /* When appending to an array and the LONG_MAX key is already used + * null will be returned. */ + tmp |= MAY_BE_NULL; + } + if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) { + /* Arrays and objects cannot be used as keys. */ + tmp |= MAY_BE_NULL; + } + if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) { + /* undef, null and false are implicitly converted to array, anything else + * results in a null return value. */ + tmp |= MAY_BE_NULL; + } + } + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + if (t1 & MAY_BE_OBJECT) { + tmp |= MAY_BE_REF; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + if ((opline+1)->op1_type == IS_CV && ssa_ops[i+1].op1_def >= 0) { + opline++; + i++; + tmp = OP1_INFO(); + if (tmp & (MAY_BE_ANY | MAY_BE_REF)) { + if (tmp & MAY_BE_RC1) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; + case ZEND_ASSIGN_OBJ: + if (opline->op1_type == IS_CV) { + tmp = t1; + if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE); + tmp |= MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN; + } + if (tmp & MAY_BE_OBJECT) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + // TODO: ??? + tmp = MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + if ((opline+1)->op1_type == IS_CV) { + opline++; + i++; + tmp = OP1_INFO(); + if (tmp & (MAY_BE_ANY | MAY_BE_REF)) { + if (tmp & MAY_BE_RC1) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; + case ZEND_ASSIGN: + if (opline->op2_type == IS_CV && ssa_ops[i].op2_def >= 0) { + tmp = t2; + if (tmp & (MAY_BE_ANY | MAY_BE_REF)) { + if (tmp & MAY_BE_RC1) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def); + } + tmp = t2 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN); + if (t2 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + if (t1 & MAY_BE_REF) { + tmp |= MAY_BE_REF; + } + if (t2 & MAY_BE_REF) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) { + tmp |= t2 & (MAY_BE_RC1|MAY_BE_RCN); + } else if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RCN; + } + if (RETURN_VALUE_USED(opline) && (tmp & MAY_BE_RC1)) { + tmp |= MAY_BE_RCN; + } + if (ssa_ops[i].op1_def >= 0) { + if (ssa_var_info[ssa_ops[i].op1_def].use_as_double) { + tmp &= ~MAY_BE_LONG; + tmp |= MAY_BE_DOUBLE; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_ops[i].result_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def); + } + break; + case ZEND_ASSIGN_REF: +// TODO: ??? + if (opline->op2_type == IS_CV) { + tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN); + if (t2 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def); + } + if (opline->op2_type == IS_VAR && opline->extended_value == ZEND_RETURNS_FUNCTION) { + tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF; + } else { + tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_RC1|MAY_BE_RCN); + } + if (t2 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + if (ssa_ops[i].result_def >= 0) { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_BIND_GLOBAL: + tmp = MAY_BE_REF | MAY_BE_ANY + | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + break; + case ZEND_BIND_STATIC: + tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF + | (opline->extended_value ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN)); + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + break; + case ZEND_SEND_VAR: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1; + if ((t1 & (MAY_BE_RC1|MAY_BE_REF)) && (opline->op1_type == IS_CV)) { + tmp |= MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + break; + case ZEND_BIND_LEXICAL: + if (ssa_ops[i].op2_def >= 0) { + if (opline->extended_value) { + tmp = t2 | MAY_BE_REF; + } else { + tmp = t2 & ~(MAY_BE_RC1|MAY_BE_RCN); + if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op2_def); + } + break; + case ZEND_YIELD: + if (ssa_ops[i].op1_def >= 0) { + if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) { + tmp = t1 | MAY_BE_REF; + } else { + tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN); + if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF + | MAY_BE_RC1 | MAY_BE_RCN; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_SEND_VAR_EX: + if (ssa_ops[i].op1_def >= 0) { + tmp = (t1 & MAY_BE_UNDEF)|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; + case ZEND_SEND_REF: + if (ssa_ops[i].op1_def >= 0) { + tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; + case ZEND_SEND_UNPACK: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1; + if (t1 & MAY_BE_ARRAY) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + /* SEND_UNPACK may acquire references into the array */ + tmp |= MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + if (t1 & MAY_BE_OBJECT) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + break; + case ZEND_FAST_CONCAT: + case ZEND_ROPE_INIT: + case ZEND_ROPE_ADD: + case ZEND_ROPE_END: + UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_ops[i].result_def); + break; + case ZEND_RECV: + case ZEND_RECV_INIT: + { + /* Typehinting */ + zend_func_info *func_info; + zend_arg_info *arg_info = NULL; + if (op_array->arg_info && opline->op1.num <= op_array->num_args) { + arg_info = &op_array->arg_info[opline->op1.num-1]; + } + + ce = NULL; + if (arg_info) { + tmp = zend_fetch_arg_info(script, arg_info, &ce); + if (opline->opcode == ZEND_RECV_INIT && + Z_CONSTANT_P(CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants))) { + /* The constant may resolve to NULL */ + tmp |= MAY_BE_NULL; + } + if (arg_info->pass_by_reference) { + tmp |= MAY_BE_REF; + } else if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_RC1|MAY_BE_RCN; + } + } else { + tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } + func_info = ZEND_FUNC_INFO(op_array); + if (func_info && (int)opline->op1.num-1 < func_info->num_args) { + tmp = (tmp & (MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF)) | + (tmp & func_info->arg_info[opline->op1.num-1].info.type); + } +#if 0 + /* We won't recieve unused arguments */ + if (ssa_vars[ssa_ops[i].result_def].use_chain < 0 && + ssa_vars[ssa_ops[i].result_def].phi_use_chain == NULL && + op_array->arg_info && + opline->op1.num <= op_array->num_args && + op_array->arg_info[opline->op1.num-1].class_name == NULL && + !op_array->arg_info[opline->op1.num-1].type_hint) { + tmp = MAY_BE_UNDEF|MAY_BE_RCN; + } +#endif + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + if (func_info && + (int)opline->op1.num-1 < func_info->num_args && + func_info->arg_info[opline->op1.num-1].info.ce) { + UPDATE_SSA_OBJ_TYPE( + func_info->arg_info[opline->op1.num-1].info.ce, + func_info->arg_info[opline->op1.num-1].info.is_instanceof, + ssa_ops[i].result_def); + } else if (ce) { + UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + break; + } + case ZEND_DECLARE_CLASS: + case ZEND_DECLARE_INHERITED_CLASS: + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: + UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_ops[i].result_def); + if (script && (ce = zend_hash_find_ptr(&script->class_table, Z_STR_P(CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants)))) != NULL) { + UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def); + } + break; + case ZEND_FETCH_CLASS: + UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_ops[i].result_def); + if (opline->op2_type == IS_UNUSED) { + switch (opline->extended_value & ZEND_FETCH_CLASS_MASK) { + case ZEND_FETCH_CLASS_SELF: + if (op_array->scope) { + UPDATE_SSA_OBJ_TYPE(op_array->scope, 0, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + break; + case ZEND_FETCH_CLASS_PARENT: + if (op_array->scope && op_array->scope->parent) { + UPDATE_SSA_OBJ_TYPE(op_array->scope->parent, 0, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + break; + case ZEND_FETCH_CLASS_STATIC: + default: + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + break; + } + } else if (opline->op2_type == IS_CONST) { + zval *zv = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants); + if (Z_TYPE_P(zv) == IS_STRING) { + ce = get_class_entry(script, Z_STR_P(zv+1)); + UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + } else { + COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def); + } + break; + case ZEND_NEW: + tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT; + if (opline->op1_type == IS_CONST && + (ce = get_class_entry(script, Z_STR_P(CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants)+1))) != NULL) { + UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def); + } else if ((t1 & MAY_BE_CLASS) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) { + UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_CLONE: + UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def); + break; + case ZEND_INIT_ARRAY: + case ZEND_ADD_ARRAY_ELEMENT: + if (opline->op1_type == IS_CV && ssa_ops[i].op1_def >= 0) { + if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) { + tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN); + if (t1 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + } else if ((t1 & (MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_REF) { + tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN); + if (t1 & MAY_BE_UNDEF) { + tmp |= MAY_BE_NULL; + } + } else if (t1 & MAY_BE_REF) { + tmp = (MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | t1); + } else { + tmp = t1; + if (t1 & MAY_BE_RC1) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + tmp = MAY_BE_RC1|MAY_BE_ARRAY; + if (opline->op1_type != IS_UNUSED) { + tmp |= (t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT; + if (t1 & MAY_BE_UNDEF) { + tmp |= MAY_BE_ARRAY_OF_NULL; + } + if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) { + tmp |= MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } + } + if (ssa_ops[i].result_use >= 0) { + tmp |= ssa_var_info[ssa_ops[i].result_use].type; + } + if (opline->op2_type == IS_UNUSED) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } else { + if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_DOUBLE)) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + if (t2 & (MAY_BE_STRING)) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + if (opline->op2_type != IS_CONST) { + // FIXME: numeric string + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + } + if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_UNSET_VAR: + ZEND_ASSERT(opline->extended_value & ZEND_QUICK_SET); + tmp = MAY_BE_UNDEF; + if (!op_array->function_name) { + /* In global scope, we know nothing */ + tmp |= MAY_BE_REF; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + break; + case ZEND_UNSET_DIM: + case ZEND_UNSET_OBJ: + if (ssa_ops[i].op1_def >= 0) { + UPDATE_SSA_TYPE(t1, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + break; + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1; + if (opline->opcode == ZEND_FE_RESET_RW) { + tmp |= MAY_BE_REF; + } else { + if ((t1 & MAY_BE_RC1) && opline->op1_type != IS_TMP_VAR) { + tmp |= MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + if (opline->opcode == ZEND_FE_RESET_RW) { +//??? + tmp = MAY_BE_REF | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT)); + } else { + tmp = MAY_BE_RC1 | MAY_BE_RCN | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF)); + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def); + break; + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + tmp = (t2 & MAY_BE_REF); + if (t1 & MAY_BE_OBJECT) { + if (opline->opcode == ZEND_FE_FETCH_RW) { + tmp |= MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } else { + tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + } + if (t1 & MAY_BE_ARRAY) { + if (opline->opcode == ZEND_FE_FETCH_RW) { + tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } else { + tmp |= ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT); + if (tmp & MAY_BE_ARRAY) { + tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + if (t1 & MAY_BE_ARRAY_OF_REF) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def); + if (ssa_ops[i].result_def >= 0) { + tmp = 0; + if (t1 & MAY_BE_OBJECT) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + if (t1 & MAY_BE_ARRAY) { + if (t1 & MAY_BE_ARRAY_KEY_LONG) { + tmp |= MAY_BE_LONG; + } + if (t1 & MAY_BE_ARRAY_KEY_STRING) { + tmp |= MAY_BE_STRING | MAY_BE_RCN; + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_FETCH_DIM_R: + case ZEND_FETCH_DIM_IS: + case ZEND_FETCH_DIM_RW: + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_DIM_FUNC_ARG: + case ZEND_FETCH_LIST: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN); + if (opline->opcode == ZEND_FETCH_DIM_W || + opline->opcode == ZEND_FETCH_DIM_RW || + opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) { + if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG) { + tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE); + } + tmp |= MAY_BE_ARRAY | MAY_BE_RC1; + } + if (t1 & (MAY_BE_STRING|MAY_BE_ARRAY)) { + tmp |= MAY_BE_RC1; + } + if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN); + } + if (opline->op2_type == IS_UNUSED) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } else { + if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + if (t2 & MAY_BE_STRING) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + if (opline->op2_type != IS_CONST) { + // FIXME: numeric string + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + } + if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + } + } + } + j = ssa_vars[ssa_ops[i].result_def].use_chain; + while (j >= 0) { + switch (op_array->opcodes[j].opcode) { + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: + case ZEND_FETCH_DIM_FUNC_ARG: + 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_ASSIGN_POW: + case ZEND_ASSIGN_DIM: + tmp |= MAY_BE_ARRAY | MAY_BE_ARRAY_OF_ARRAY; + break; + case ZEND_FETCH_OBJ_W: + case ZEND_FETCH_OBJ_RW: + case ZEND_FETCH_OBJ_FUNC_ARG: + case ZEND_ASSIGN_OBJ: + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_INC_OBJ: + case ZEND_POST_DEC_OBJ: + tmp |= MAY_BE_ARRAY_OF_OBJECT; + break; + case ZEND_SEND_VAR_EX: + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: + case ZEND_SEND_REF: + case ZEND_ASSIGN_REF: + case ZEND_YIELD: + case ZEND_INIT_ARRAY: + case ZEND_ADD_ARRAY_ELEMENT: + case ZEND_RETURN_BY_REF: + case ZEND_VERIFY_RETURN_TYPE: + case ZEND_MAKE_REF: + tmp |= MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + break; + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_POST_INC: + case ZEND_POST_DEC: + if (tmp & MAY_BE_ARRAY_OF_LONG) { + /* may overflow */ + tmp |= MAY_BE_ARRAY_OF_DOUBLE; + } else if (!(tmp & (MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE))) { + tmp |= MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE; + } + break; + case ZEND_UNSET_DIM: + case ZEND_UNSET_OBJ: + case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_OBJ_UNSET: + break; + default : + break; + } + j = zend_ssa_next_use(ssa_ops, ssa_ops[i].result_def, j); + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + /* FETCH_LIST on a string behaves like FETCH_R on null */ + tmp = zend_array_element_type( + opline->opcode != ZEND_FETCH_LIST ? t1 : ((t1 & ~MAY_BE_STRING) | MAY_BE_NULL), + opline->opcode != ZEND_FETCH_DIM_R && opline->opcode != ZEND_FETCH_DIM_IS + && opline->opcode != ZEND_FETCH_LIST, + opline->op2_type == IS_UNUSED); + if (opline->opcode == ZEND_FETCH_DIM_W || + opline->opcode == ZEND_FETCH_DIM_RW || + opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) { + if (t1 & (MAY_BE_ERROR|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE|MAY_BE_OBJECT)) { + tmp |= MAY_BE_ERROR; + } else if (opline->op2_type == IS_UNUSED) { + tmp |= MAY_BE_ERROR; + } else if (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) { + tmp |= MAY_BE_ERROR; + } + } else if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) { + tmp |= MAY_BE_NULL; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_FETCH_THIS: + UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_ops[i].result_def); + UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def); + break; + case ZEND_FETCH_OBJ_R: + case ZEND_FETCH_OBJ_IS: + case ZEND_FETCH_OBJ_RW: + case ZEND_FETCH_OBJ_W: + case ZEND_FETCH_OBJ_UNSET: + case ZEND_FETCH_OBJ_FUNC_ARG: + if (ssa_ops[i].op1_def >= 0) { + tmp = t1; + if (opline->opcode == ZEND_FETCH_OBJ_W || + opline->opcode == ZEND_FETCH_OBJ_RW || + opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG) { + if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG) { + if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE); + tmp |= MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN; + } + } + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + if (opline->opcode != ZEND_FETCH_OBJ_R && opline->opcode != ZEND_FETCH_OBJ_IS) { + tmp |= MAY_BE_ERROR; + } + if (opline->result_type == IS_TMP_VAR) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else { + tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + if (ssa_ops[i].result_def >= 0) { + zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + zend_call_info *call_info; + + if (!func_info || !func_info->call_map) { + goto unknown_opcode; + } + call_info = func_info->call_map[opline - op_array->opcodes]; + if (!call_info) { + goto unknown_opcode; + } + tmp = zend_get_func_info(call_info, ssa) & ~FUNC_MAY_WARN; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + if (call_info->callee_func->type == ZEND_USER_FUNCTION) { + func_info = ZEND_FUNC_INFO(&call_info->callee_func->op_array); + if (func_info) { + UPDATE_SSA_OBJ_TYPE( + func_info->return_info.ce, + func_info->return_info.is_instanceof, + ssa_ops[i].result_def); + } + } + } + break; + case ZEND_FETCH_CONSTANT: + case ZEND_FETCH_CLASS_CONSTANT: + UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_ops[i].result_def); + break; + case ZEND_STRLEN: + tmp = MAY_BE_LONG; + if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) { + tmp |= MAY_BE_NULL; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + break; + case ZEND_TYPE_CHECK: + case ZEND_DEFINED: + UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_ops[i].result_def); + break; + case ZEND_VERIFY_RETURN_TYPE: + if (t1 & MAY_BE_REF) { + tmp = t1; + ce = NULL; + } else { + zend_arg_info *ret_info = op_array->arg_info - 1; + + tmp = zend_fetch_arg_info(script, ret_info, &ce); + if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } + } + if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + if (ce) { + UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].op1_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def); + } + } else { + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + if (ce) { + UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def); + } else { + UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def); + } + } + break; + case ZEND_CATCH: + case ZEND_INCLUDE_OR_EVAL: + /* Forbidden opcodes */ + ZEND_ASSERT(0); + break; + default: +unknown_opcode: + if (ssa_ops[i].op1_def >= 0) { + tmp = MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def); + } + if (ssa_ops[i].result_def >= 0) { + tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + if (opline->result_type == IS_TMP_VAR) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else { + tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN; + } + UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def); + } + break; + } + + return SUCCESS; +} + +static uint32_t get_class_entry_rank(zend_class_entry *ce) { + uint32_t rank = 0; + while (ce->parent) { + rank++; + ce = ce->parent; + } + return rank; +} + +/* Compute least common ancestor on class inheritance tree only */ +static zend_class_entry *join_class_entries( + zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) { + uint32_t rank1, rank2; + if (ce1 == ce2) { + return ce1; + } + if (!ce1 || !ce2) { + return NULL; + } + + rank1 = get_class_entry_rank(ce1); + rank2 = get_class_entry_rank(ce2); + + while (rank1 != rank2) { + if (rank1 > rank2) { + ce1 = ce1->parent; + rank1--; + } else { + ce2 = ce2->parent; + rank2--; + } + } + + while (ce1 != ce2) { + ce1 = ce1->parent; + ce2 = ce2->parent; + } + + if (ce1) { + *is_instanceof = 1; + } + return ce1; +} + +int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist) +{ + zend_basic_block *blocks = ssa->cfg.blocks; + zend_ssa_var *ssa_vars = ssa->vars; + zend_ssa_var_info *ssa_var_info = ssa->var_info; + int ssa_vars_count = ssa->vars_count; + int i, j; + uint32_t tmp; + + WHILE_WORKLIST(worklist, zend_bitset_len(ssa_vars_count), j) { + if (ssa_vars[j].definition_phi) { + zend_ssa_phi *p = ssa_vars[j].definition_phi; + if (p->pi >= 0) { + zend_class_entry *ce = ssa_var_info[p->sources[0]].ce; + int is_instanceof = ssa_var_info[p->sources[0]].is_instanceof; + tmp = get_ssa_var_info(ssa, p->sources[0]); + + if (!p->has_range_constraint) { + zend_ssa_type_constraint *constraint = &p->constraint.type; + tmp &= constraint->type_mask; + if ((tmp & MAY_BE_OBJECT) && constraint->ce && ce != constraint->ce) { + if (!ce) { + ce = constraint->ce; + is_instanceof = 1; + } else if (is_instanceof && instanceof_function(constraint->ce, ce)) { + ce = constraint->ce; + } else { + /* Ignore the constraint (either ce instanceof constraint->ce or + * they are unrelated, as far as we can statically determine) */ + } + } + } + + UPDATE_SSA_TYPE(tmp, j); + UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j); + } else { + int first = 1; + int is_instanceof = 0; + zend_class_entry *ce = NULL; + + tmp = 0; + for (i = 0; i < blocks[p->block].predecessors_count; i++) { + tmp |= get_ssa_var_info(ssa, p->sources[i]); + } + UPDATE_SSA_TYPE(tmp, j); + for (i = 0; i < blocks[p->block].predecessors_count; i++) { + if (p->sources[i] >= 0) { + zend_ssa_var_info *info = &ssa_var_info[p->sources[i]]; + if (info->type & MAY_BE_OBJECT) { + if (first) { + ce = info->ce; + is_instanceof = info->is_instanceof; + first = 0; + } else { + is_instanceof |= info->is_instanceof; + ce = join_class_entries(ce, info->ce, &is_instanceof); + } + } + } + } + UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j); + } + } else if (ssa_vars[j].definition >= 0) { + i = ssa_vars[j].definition; + if (zend_update_type_info(op_array, ssa, script, worklist, i) == FAILURE) { + return FAILURE; + } + } + } WHILE_WORKLIST_END(); + return SUCCESS; +} + +static zend_bool is_narrowable_instr(zend_op *opline) { + return opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB + || opline->opcode == ZEND_MUL || opline->opcode == ZEND_DIV; +} + +static zend_bool is_effective_op1_double_cast(zend_op *opline, zval *op2) { + return (opline->opcode == ZEND_ADD && Z_LVAL_P(op2) == 0) + || (opline->opcode == ZEND_SUB && Z_LVAL_P(op2) == 0) + || (opline->opcode == ZEND_MUL && Z_LVAL_P(op2) == 1) + || (opline->opcode == ZEND_DIV && Z_LVAL_P(op2) == 1); +} +static zend_bool is_effective_op2_double_cast(zend_op *opline, zval *op1) { + /* In PHP it holds that (double)(0-$int) is bitwise identical to 0.0-(double)$int, + * so allowing SUB here is fine. */ + return (opline->opcode == ZEND_ADD && Z_LVAL_P(op1) == 0) + || (opline->opcode == ZEND_SUB && Z_LVAL_P(op1) == 0) + || (opline->opcode == ZEND_MUL && Z_LVAL_P(op1) == 1); +} + +/* This function recursively checks whether it's possible to convert an integer variable + * initialization to a double initialization. The basic idea is that if the value is used + * only in add/sub/mul/div ("narrowable" instructions) with a double result value, then it + * will be cast to double at that point anyway, so we may as well do it earlier already. + * + * The tricky case are chains of operations, where it's not necessarily a given that converting + * an integer to double before the chain of operations is the same as converting it after the + * chain. What this function does is detect two cases where it is safe: + * * If the operations only involve constants, then we can simply verify that performing the + * calculation on integers and doubles yields the same value. + * * Even if one operand is not known, we may be able to determine that the operations with the + * integer replaced by a double only acts as an effective double cast on the unknown operand. + * E.g. 0+$i and 0.0+$i only differ by that cast. If then the consuming instruction of this + * result will perform a double cast anyway, the conversion is safe. + * + * The checks happens recursively, while keeping track of which variables are already visisted to + * avoid infinite loops. An iterative, worklist driven approach would be possible, but the state + * management more cumbersome to implement, so we don't bother for now. + */ +static zend_bool can_convert_to_double( + const zend_op_array *op_array, zend_ssa *ssa, int var_num, + zval *value, zend_bitset visited) { + zend_ssa_var *var = &ssa->vars[var_num]; + zend_ssa_phi *phi; + int use; + uint32_t type; + + if (zend_bitset_in(visited, var_num)) { + return 1; + } + zend_bitset_incl(visited, var_num); + + for (use = var->use_chain; use >= 0; use = zend_ssa_next_use(ssa->ops, var_num, use)) { + zend_op *opline = &op_array->opcodes[use]; + zend_ssa_op *ssa_op = &ssa->ops[use]; + + if (is_no_val_use(opline, ssa_op, var_num)) { + continue; + } + + if (!is_narrowable_instr(opline)) { + return 0; + } + + /* Instruction always returns double, the conversion is certainly fine */ + type = ssa->var_info[ssa_op->result_def].type; + if ((type & MAY_BE_ANY) == MAY_BE_DOUBLE) { + continue; + } + + /* UNDEF signals that the previous result is an effective double cast, this is only allowed + * if this instruction would have done the cast anyway (previous check). */ + if (Z_ISUNDEF_P(value)) { + return 0; + } + + /* Check that narrowing can actually be useful */ + if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) { + return 0; + } + + { + /* For calculation on original values */ + zval orig_op1, orig_op2, orig_result; + /* For calculation with var_num cast to double */ + zval dval_op1, dval_op2, dval_result; + + ZVAL_UNDEF(&orig_op1); + ZVAL_UNDEF(&dval_op1); + if (ssa_op->op1_use == var_num) { + ZVAL_COPY_VALUE(&orig_op1, value); + ZVAL_DOUBLE(&dval_op1, (double) Z_LVAL_P(value)); + } else if (opline->op1_type == IS_CONST) { + zval *zv = CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants); + if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) { + ZVAL_COPY_VALUE(&orig_op1, zv); + ZVAL_COPY_VALUE(&dval_op1, zv); + } + } + + ZVAL_UNDEF(&orig_op2); + ZVAL_UNDEF(&dval_op2); + if (ssa_op->op2_use == var_num) { + ZVAL_COPY_VALUE(&orig_op2, value); + ZVAL_DOUBLE(&dval_op2, (double) Z_LVAL_P(value)); + } else if (opline->op2_type == IS_CONST) { + zval *zv = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants); + if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) { + ZVAL_COPY_VALUE(&orig_op2, zv); + ZVAL_COPY_VALUE(&dval_op2, zv); + } + } + + ZEND_ASSERT(!Z_ISUNDEF(orig_op1) || !Z_ISUNDEF(orig_op2)); + if (Z_ISUNDEF(orig_op1)) { + if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op2) == 0) { + ZVAL_LONG(&orig_result, 0); + } else if (is_effective_op1_double_cast(opline, &orig_op2)) { + ZVAL_UNDEF(&orig_result); + } else { + return 0; + } + } else if (Z_ISUNDEF(orig_op2)) { + if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op1) == 0) { + ZVAL_LONG(&orig_result, 0); + } else if (is_effective_op2_double_cast(opline, &orig_op1)) { + ZVAL_UNDEF(&orig_result); + } else { + return 0; + } + } else { + /* Avoid division by zero */ + if (opline->opcode == ZEND_DIV && zval_get_double(&orig_op2) == 0.0) { + return 0; + } + + get_binary_op(opline->opcode)(&orig_result, &orig_op1, &orig_op2); + get_binary_op(opline->opcode)(&dval_result, &dval_op1, &dval_op2); + ZEND_ASSERT(Z_TYPE(dval_result) == IS_DOUBLE); + if (zval_get_double(&orig_result) != Z_DVAL(dval_result)) { + return 0; + } + } + + if (!can_convert_to_double(op_array, ssa, ssa_op->result_def, &orig_result, visited)) { + return 0; + } + } + } + + for (phi = var->phi_use_chain; phi; phi = zend_ssa_next_use_phi(ssa, var_num, phi)) { + /* Check that narrowing can actually be useful */ + type = ssa->var_info[phi->ssa_var].type; + if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) { + return 0; + } + + if (!can_convert_to_double(op_array, ssa, phi->ssa_var, value, visited)) { + return 0; + } + } + + return 1; +} + +static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa) +{ + uint32_t bitset_len = zend_bitset_len(ssa->vars_count); + zend_bitset visited, worklist; + int i, v; + zend_op *opline; + zend_bool narrowed = 0; + ALLOCA_FLAG(use_heap) + + visited = ZEND_BITSET_ALLOCA(2 * bitset_len, use_heap); + worklist = visited + bitset_len; + + zend_bitset_clear(worklist, bitset_len); + + for (v = op_array->last_var; v < ssa->vars_count; v++) { + if ((ssa->var_info[v].type & (MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF)) != MAY_BE_LONG) continue; + if (ssa->vars[v].definition < 0) continue; + if (ssa->vars[v].no_val) continue; + opline = op_array->opcodes + ssa->vars[v].definition; + /* Go through assignments of literal integers and check if they can be converted to + * doubles instead, in the hope that we'll narrow long|double to double. */ + if (opline->opcode == ZEND_ASSIGN && opline->result_type == IS_UNUSED && + opline->op1_type == IS_CV && opline->op2_type == IS_CONST) { + zval *value = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants); + + zend_bitset_clear(visited, bitset_len); + if (can_convert_to_double(op_array, ssa, v, value, visited)) { + narrowed = 1; + ssa->var_info[v].use_as_double = 1; + /* The "visited" vars are exactly those which may change their type due to + * narrowing. Reset their types and add them to the type inference worklist */ + ZEND_BITSET_FOREACH(visited, bitset_len, i) { + ssa->var_info[i].type &= ~MAY_BE_ANY; + } ZEND_BITSET_FOREACH_END(); + zend_bitset_union(worklist, visited, bitset_len); + } + } + } + + if (!narrowed) { + free_alloca(visited, use_heap); + return SUCCESS; + } + + if (zend_infer_types_ex(op_array, script, ssa, worklist) != SUCCESS) { + free_alloca(visited, use_heap); + return FAILURE; + } + + free_alloca(visited, use_heap); + return SUCCESS; +} + +static int is_recursive_tail_call(const zend_op_array *op_array, + zend_op *opline) +{ + zend_func_info *info = ZEND_FUNC_INFO(op_array); + + if (info->ssa.ops && info->ssa.vars && info->call_map && + info->ssa.ops[opline - op_array->opcodes].op1_use >= 0 && + info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition >= 0) { + + zend_op *op = op_array->opcodes + info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition; + + if (op->opcode == ZEND_DO_UCALL) { + zend_call_info *call_info = info->call_map[op - op_array->opcodes]; + if (call_info && op_array == &call_info->callee_func->op_array) { + return 1; + } + } + } + return 0; +} + +void zend_init_func_return_info(const zend_op_array *op_array, + const zend_script *script, + zend_ssa_var_info *ret) +{ + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_arg_info *ret_info = op_array->arg_info - 1; + zend_ssa_range tmp_range = {0, 0, 0, 0}; + + ret->type = zend_fetch_arg_info(script, ret_info, &ret->ce); + if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) { + ret->type |= MAY_BE_REF; + } + ret->is_instanceof = (ret->ce) ? 1 : 0; + ret->range = tmp_range; + ret->has_range = 0; + } +} + +void zend_func_return_info(const zend_op_array *op_array, + const zend_script *script, + int recursive, + int widening, + zend_ssa_var_info *ret) +{ + zend_func_info *info = ZEND_FUNC_INFO(op_array); + zend_ssa *ssa = &info->ssa; + int blocks_count = info->ssa.cfg.blocks_count; + zend_basic_block *blocks = info->ssa.cfg.blocks; + int j; + uint32_t t1; + uint32_t tmp = 0; + zend_class_entry *tmp_ce = NULL; + int tmp_is_instanceof = -1; + zend_class_entry *arg_ce; + int arg_is_instanceof; + zend_ssa_range tmp_range = {0, 0, 0, 0}; + int tmp_has_range = -1; + + if (op_array->fn_flags & ZEND_ACC_GENERATOR) { + ret->type = MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN; + ret->ce = zend_ce_generator; + ret->is_instanceof = 0; + ret->range = tmp_range; + ret->has_range = 0; + return; + } + + for (j = 0; j < blocks_count; j++) { + if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) { + zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1; + + if (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_RETURN_BY_REF) { + if (!recursive && + info->ssa.ops && + info->ssa.var_info && + info->ssa.ops[opline - op_array->opcodes].op1_use >= 0 && + info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].recursive) { + continue; + } + if (is_recursive_tail_call(op_array, opline)) { + continue; + } + t1 = OP1_INFO(); + if (t1 & MAY_BE_UNDEF) { + t1 |= MAY_BE_NULL; + } + if (opline->opcode == ZEND_RETURN) { + if (t1 & MAY_BE_RC1) { + t1 |= MAY_BE_RCN; + } + t1 &= ~(MAY_BE_UNDEF | MAY_BE_REF); + } else { + t1 |= MAY_BE_REF; + t1 &= ~(MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN); + } + tmp |= t1; + + if (info->ssa.ops && + info->ssa.var_info && + info->ssa.ops[opline - op_array->opcodes].op1_use >= 0 && + info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].ce) { + arg_ce = info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].ce; + arg_is_instanceof = info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].is_instanceof; + } else { + arg_ce = NULL; + arg_is_instanceof = 0; + } + + if (tmp_is_instanceof < 0) { + tmp_ce = arg_ce; + tmp_is_instanceof = arg_is_instanceof; + } else if (arg_ce && arg_ce == tmp_ce) { + if (tmp_is_instanceof != arg_is_instanceof) { + tmp_is_instanceof = 1; + } + } else { + tmp_ce = NULL; + tmp_is_instanceof = 0; + } + + if (opline->op1_type == IS_CONST) { + zval *zv = CRT_CONSTANT_EX(op_array, opline->op1, info->ssa.rt_constants); + + if (Z_TYPE_P(zv) == IS_NULL) { + if (tmp_has_range < 0) { + tmp_has_range = 1; + tmp_range.underflow = 0; + tmp_range.min = 0; + tmp_range.max = 0; + tmp_range.overflow = 0; + } else if (tmp_has_range) { + if (!tmp_range.underflow) { + tmp_range.min = MIN(tmp_range.min, 0); + } + if (!tmp_range.overflow) { + tmp_range.max = MAX(tmp_range.max, 0); + } + } + } else if (Z_TYPE_P(zv) == IS_FALSE) { + if (tmp_has_range < 0) { + tmp_has_range = 1; + tmp_range.underflow = 0; + tmp_range.min = 0; + tmp_range.max = 0; + tmp_range.overflow = 0; + } else if (tmp_has_range) { + if (!tmp_range.underflow) { + tmp_range.min = MIN(tmp_range.min, 0); + } + if (!tmp_range.overflow) { + tmp_range.max = MAX(tmp_range.max, 0); + } + } + } else if (Z_TYPE_P(zv) == IS_TRUE) { + if (tmp_has_range < 0) { + tmp_has_range = 1; + tmp_range.underflow = 0; + tmp_range.min = 1; + tmp_range.max = 1; + tmp_range.overflow = 0; + } else if (tmp_has_range) { + if (!tmp_range.underflow) { + tmp_range.min = MIN(tmp_range.min, 1); + } + if (!tmp_range.overflow) { + tmp_range.max = MAX(tmp_range.max, 1); + } + } + } else if (Z_TYPE_P(zv) == IS_LONG) { + if (tmp_has_range < 0) { + tmp_has_range = 1; + tmp_range.underflow = 0; + tmp_range.min = Z_LVAL_P(zv); + tmp_range.max = Z_LVAL_P(zv); + tmp_range.overflow = 0; + } else if (tmp_has_range) { + if (!tmp_range.underflow) { + tmp_range.min = MIN(tmp_range.min, Z_LVAL_P(zv)); + } + if (!tmp_range.overflow) { + tmp_range.max = MAX(tmp_range.max, Z_LVAL_P(zv)); + } + } + } else { + tmp_has_range = 0; + } + } else if (info->ssa.ops && + info->ssa.var_info && + info->ssa.ops[opline - op_array->opcodes].op1_use >= 0) { + if (info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].has_range) { + if (tmp_has_range < 0) { + tmp_has_range = 1; + tmp_range = info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].range; + } else if (tmp_has_range) { + /* union */ + if (info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].range.underflow) { + tmp_range.underflow = 1; + tmp_range.min = ZEND_LONG_MIN; + } else { + tmp_range.min = MIN(tmp_range.min, info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].range.min); + } + if (info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].range.overflow) { + tmp_range.overflow = 1; + tmp_range.max = ZEND_LONG_MAX; + } else { + tmp_range.max = MAX(tmp_range.max, info->ssa.var_info[info->ssa.ops[opline - op_array->opcodes].op1_use].range.max); + } + } + } else if (!widening) { + tmp_has_range = 1; + tmp_range.underflow = 1; + tmp_range.min = ZEND_LONG_MIN; + tmp_range.max = ZEND_LONG_MAX; + tmp_range.overflow = 1; + } + } else { + tmp_has_range = 0; + } + } + } + } + + if (!(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + if (tmp_is_instanceof < 0) { + tmp_is_instanceof = 0; + tmp_ce = NULL; + } + if (tmp_has_range < 0) { + tmp_has_range = 0; + } + ret->type = tmp; + ret->ce = tmp_ce; + ret->is_instanceof = tmp_is_instanceof; + } + ret->range = tmp_range; + ret->has_range = tmp_has_range; +} + +static int zend_infer_types(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa) +{ + zend_ssa_var_info *ssa_var_info = ssa->var_info; + int ssa_vars_count = ssa->vars_count; + int j; + zend_bitset worklist; + ALLOCA_FLAG(use_heap); + + worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap); + memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count)); + + /* Type Inference */ + for (j = op_array->last_var; j < ssa_vars_count; j++) { + zend_bitset_incl(worklist, j); + ssa_var_info[j].type = 0; + } + + if (zend_infer_types_ex(op_array, script, ssa, worklist) != SUCCESS) { + free_alloca(worklist, use_heap); + return FAILURE; + } + + /* Narrowing integer initialization to doubles */ + zend_type_narrowing(op_array, script, ssa); + + for (j = 0; j < op_array->last_var; j++) { + /* $php_errormsg and $http_response_header may be updated indirectly */ + if (zend_string_equals_literal(op_array->vars[j], "php_errormsg")) { + int i; + for (i = 0; i < ssa_vars_count; i++) { + if (ssa->vars[i].var == j) { + ssa_var_info[i].type |= MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN; + } + } + } else if (zend_string_equals_literal(op_array->vars[j], "http_response_header")) { + int i; + for (i = 0; i < ssa_vars_count; i++) { + if (ssa->vars[i].var == j) { + ssa_var_info[i].type |= MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_RC1 | MAY_BE_RCN; + } + } + } + } + + if (ZEND_FUNC_INFO(op_array)) { + zend_func_return_info(op_array, script, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info); + } + + free_alloca(worklist, use_heap); + return SUCCESS; +} + +int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa) /* {{{ */ +{ + zend_ssa_var_info *ssa_var_info; + int i; + + if (!ssa->var_info) { + ssa->var_info = zend_arena_calloc(arena, ssa->vars_count, sizeof(zend_ssa_var_info)); + } + ssa_var_info = ssa->var_info; + + if (!op_array->function_name) { + for (i = 0; i < op_array->last_var; i++) { + ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + ssa_var_info[i].has_range = 0; + } + } else { + for (i = 0; i < op_array->last_var; i++) { + ssa_var_info[i].type = MAY_BE_UNDEF; + ssa_var_info[i].has_range = 0; + } + } + for (i = op_array->last_var; i < ssa->vars_count; i++) { + ssa_var_info[i].type = 0; + ssa_var_info[i].has_range = 0; + } + + if (zend_infer_ranges(op_array, ssa) != SUCCESS) { + return FAILURE; + } + + if (zend_infer_types(op_array, script, ssa) != SUCCESS) { + return FAILURE; + } + + return SUCCESS; +} +/* }}} */ + +void zend_inference_check_recursive_dependencies(zend_op_array *op_array) +{ + zend_func_info *info = ZEND_FUNC_INFO(op_array); + zend_call_info *call_info; + zend_bitset worklist; + int worklist_len, i; + ALLOCA_FLAG(use_heap); + + if (!info->ssa.var_info || !(info->flags & ZEND_FUNC_RECURSIVE)) { + return; + } + worklist_len = zend_bitset_len(info->ssa.vars_count); + worklist = do_alloca(sizeof(zend_ulong) * worklist_len, use_heap); + memset(worklist, 0, sizeof(zend_ulong) * worklist_len); + call_info = info->callee_info; + while (call_info) { + if (call_info->recursive && + info->ssa.ops[call_info->caller_call_opline - op_array->opcodes].result_def >= 0) { + zend_bitset_incl(worklist, info->ssa.ops[call_info->caller_call_opline - op_array->opcodes].result_def); + } + call_info = call_info->next_callee; + } + WHILE_WORKLIST(worklist, worklist_len, i) { + if (!info->ssa.var_info[i].recursive) { + info->ssa.var_info[i].recursive = 1; + add_usages(op_array, &info->ssa, worklist, i); + } + } WHILE_WORKLIST_END(); + free_alloca(worklist, use_heap); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_inference.h b/ext/opcache/Optimizer/zend_inference.h new file mode 100644 index 0000000000..25b5cba4ca --- /dev/null +++ b/ext/opcache/Optimizer/zend_inference.h @@ -0,0 +1,275 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, e-SSA based Type & Range Inference | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_INFERENCE_H +#define ZEND_INFERENCE_H + +#include "zend_optimizer.h" +#include "zend_ssa.h" +#include "zend_bitset.h" + +/* Bitmask for type inference (zend_ssa_var_info.type) */ +#include "zend_type_info.h" + +#define MAY_BE_IN_REG (1<<25) /* value allocated in CPU register */ + +//TODO: remome MAY_BE_RC1, MAY_BE_RCN??? +#define MAY_BE_RC1 (1<<27) /* may be non-reference with refcount == 1 */ +#define MAY_BE_RCN (1<<28) /* may be non-reference with refcount > 1 */ + + +#define DEFINE_SSA_OP_HAS_RANGE(opN) \ + static zend_always_inline zend_bool _ssa_##opN##_has_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + if (opline->opN##_type == IS_CONST) { \ + zval *zv = CRT_CONSTANT_EX(op_array, opline->opN, ssa->rt_constants); \ + return (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL); \ + } else { \ + return (opline->opN##_type != IS_UNUSED && \ + ssa->ops && \ + ssa->var_info && \ + ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \ + ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range); \ + } \ + return 0; \ + } + +#define DEFINE_SSA_OP_MIN_RANGE(opN) \ + static zend_always_inline zend_long _ssa_##opN##_min_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + if (opline->opN##_type == IS_CONST) { \ + zval *zv = CRT_CONSTANT_EX(op_array, opline->opN, ssa->rt_constants); \ + if (Z_TYPE_P(zv) == IS_LONG) { \ + return Z_LVAL_P(zv); \ + } else if (Z_TYPE_P(zv) == IS_TRUE) { \ + return 1; \ + } else if (Z_TYPE_P(zv) == IS_FALSE) { \ + return 0; \ + } else if (Z_TYPE_P(zv) == IS_NULL) { \ + return 0; \ + } \ + } else if (opline->opN##_type != IS_UNUSED && \ + ssa->ops && \ + ssa->var_info && \ + ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \ + ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \ + return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.min; \ + } \ + return ZEND_LONG_MIN; \ + } + +#define DEFINE_SSA_OP_MAX_RANGE(opN) \ + static zend_always_inline zend_long _ssa_##opN##_max_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + if (opline->opN##_type == IS_CONST) { \ + zval *zv = CRT_CONSTANT_EX(op_array, opline->opN, ssa->rt_constants); \ + if (Z_TYPE_P(zv) == IS_LONG) { \ + return Z_LVAL_P(zv); \ + } else if (Z_TYPE_P(zv) == IS_TRUE) { \ + return 1; \ + } else if (Z_TYPE_P(zv) == IS_FALSE) { \ + return 0; \ + } else if (Z_TYPE_P(zv) == IS_NULL) { \ + return 0; \ + } \ + } else if (opline->opN##_type != IS_UNUSED && \ + ssa->ops && \ + ssa->var_info && \ + ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \ + ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \ + return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.max; \ + } \ + return ZEND_LONG_MAX; \ + } + +#define DEFINE_SSA_OP_RANGE_UNDERFLOW(opN) \ + static zend_always_inline char _ssa_##opN##_range_underflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + if (opline->opN##_type == IS_CONST) { \ + zval *zv = CRT_CONSTANT_EX(op_array, opline->opN, ssa->rt_constants); \ + if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL) { \ + return 0; \ + } \ + } else if (opline->opN##_type != IS_UNUSED && \ + ssa->ops && \ + ssa->var_info && \ + ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \ + ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \ + return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.underflow; \ + } \ + return 1; \ + } + +#define DEFINE_SSA_OP_RANGE_OVERFLOW(opN) \ + static zend_always_inline char _ssa_##opN##_range_overflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + if (opline->opN##_type == IS_CONST) { \ + zval *zv = CRT_CONSTANT_EX(op_array, opline->opN, ssa->rt_constants); \ + if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL) { \ + return 0; \ + } \ + } else if (opline->opN##_type != IS_UNUSED && \ + ssa->ops && \ + ssa->var_info && \ + ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \ + ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \ + return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.overflow; \ + } \ + return 1; \ + } + +DEFINE_SSA_OP_HAS_RANGE(op1) +DEFINE_SSA_OP_MIN_RANGE(op1) +DEFINE_SSA_OP_MAX_RANGE(op1) +DEFINE_SSA_OP_RANGE_UNDERFLOW(op1) +DEFINE_SSA_OP_RANGE_OVERFLOW(op1) +DEFINE_SSA_OP_HAS_RANGE(op2) +DEFINE_SSA_OP_MIN_RANGE(op2) +DEFINE_SSA_OP_MAX_RANGE(op2) +DEFINE_SSA_OP_RANGE_UNDERFLOW(op2) +DEFINE_SSA_OP_RANGE_OVERFLOW(op2) + +#define OP1_HAS_RANGE() (_ssa_op1_has_range (op_array, ssa, opline)) +#define OP1_MIN_RANGE() (_ssa_op1_min_range (op_array, ssa, opline)) +#define OP1_MAX_RANGE() (_ssa_op1_max_range (op_array, ssa, opline)) +#define OP1_RANGE_UNDERFLOW() (_ssa_op1_range_underflow (op_array, ssa, opline)) +#define OP1_RANGE_OVERFLOW() (_ssa_op1_range_overflow (op_array, ssa, opline)) +#define OP2_HAS_RANGE() (_ssa_op2_has_range (op_array, ssa, opline)) +#define OP2_MIN_RANGE() (_ssa_op2_min_range (op_array, ssa, opline)) +#define OP2_MAX_RANGE() (_ssa_op2_max_range (op_array, ssa, opline)) +#define OP2_RANGE_UNDERFLOW() (_ssa_op2_range_underflow (op_array, ssa, opline)) +#define OP2_RANGE_OVERFLOW() (_ssa_op2_range_overflow (op_array, ssa, opline)) + +static zend_always_inline uint32_t _const_op_type(const zval *zv) { + if (Z_TYPE_P(zv) == IS_CONSTANT) { + return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY; + } else if (Z_TYPE_P(zv) == IS_CONSTANT_AST) { + return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY; + } else if (Z_TYPE_P(zv) == IS_ARRAY) { + HashTable *ht = Z_ARRVAL_P(zv); + uint32_t tmp = MAY_BE_ARRAY; + + if (Z_REFCOUNTED_P(zv)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else { + tmp |= MAY_BE_RCN; + } + zend_string *str; + zval *val; + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, str, val) { + if (str) { + tmp |= MAY_BE_ARRAY_KEY_STRING; + } else { + tmp |= MAY_BE_ARRAY_KEY_LONG; + } + tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT); + } ZEND_HASH_FOREACH_END(); + return tmp; + } else { + uint32_t tmp = (1 << Z_TYPE_P(zv)); + + if (Z_REFCOUNTED_P(zv)) { + tmp |= MAY_BE_RC1 | MAY_BE_RCN; + } else if (Z_TYPE_P(zv) == IS_STRING) { + tmp |= MAY_BE_RCN; + } + return tmp; + } +} + +static zend_always_inline uint32_t get_ssa_var_info(const zend_ssa *ssa, int ssa_var_num) +{ + if (ssa->var_info && ssa_var_num >= 0) { + return ssa->var_info[ssa_var_num].type; + } else { + return MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ERROR; + } +} + +#define DEFINE_SSA_OP_INFO(opN) \ + static zend_always_inline uint32_t _ssa_##opN##_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + if (opline->opN##_type == IS_CONST) { \ + return _const_op_type(CRT_CONSTANT_EX(op_array, opline->opN, ssa->rt_constants)); \ + } else { \ + return get_ssa_var_info(ssa, ssa->ops ? ssa->ops[opline - op_array->opcodes].opN##_use : -1); \ + } \ + } + +#define DEFINE_SSA_OP_DEF_INFO(opN) \ + static zend_always_inline uint32_t _ssa_##opN##_def_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \ + { \ + return get_ssa_var_info(ssa, ssa->ops ? ssa->ops[opline - op_array->opcodes].opN##_def : -1); \ + } + + +DEFINE_SSA_OP_INFO(op1) +DEFINE_SSA_OP_INFO(op2) +DEFINE_SSA_OP_INFO(result) +DEFINE_SSA_OP_DEF_INFO(op1) +DEFINE_SSA_OP_DEF_INFO(op2) +DEFINE_SSA_OP_DEF_INFO(result) + +#define OP1_INFO() (_ssa_op1_info(op_array, ssa, opline)) +#define OP2_INFO() (_ssa_op2_info(op_array, ssa, opline)) +#define OP1_DATA_INFO() (_ssa_op1_info(op_array, ssa, (opline+1))) +#define OP2_DATA_INFO() (_ssa_op2_info(op_array, ssa, (opline+1))) +#define RES_USE_INFO() (_ssa_result_info(op_array, ssa, opline)) +#define OP1_DEF_INFO() (_ssa_op1_def_info(op_array, ssa, opline)) +#define OP2_DEF_INFO() (_ssa_op2_def_info(op_array, ssa, opline)) +#define OP1_DATA_DEF_INFO() (_ssa_op1_def_info(op_array, ssa, (opline+1))) +#define OP2_DATA_DEF_INFO() (_ssa_op2_def_info(op_array, ssa, (opline+1))) +#define RES_INFO() (_ssa_result_def_info(op_array, ssa, opline)) + + +BEGIN_EXTERN_C() + +int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa); +int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa); +int zend_ssa_inference(zend_arena **raena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa); + +uint32_t zend_array_element_type(uint32_t t1, int write, int insert); + +int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int var, int widening, int narrowing, zend_ssa_range *tmp); +void zend_inference_init_range(const zend_op_array *op_array, zend_ssa *ssa, int var, zend_bool underflow, zend_long min, zend_long max, zend_bool overflow); +int zend_inference_narrowing_meet(zend_ssa_var_info *var_info, zend_ssa_range *r); +int zend_inference_widening_meet(zend_ssa_var_info *var_info, zend_ssa_range *r); +void zend_inference_check_recursive_dependencies(zend_op_array *op_array); + +int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist); + +void zend_init_func_return_info(const zend_op_array *op_array, + const zend_script *script, + zend_ssa_var_info *ret); +void zend_func_return_info(const zend_op_array *op_array, + const zend_script *script, + int recursive, + int widening, + zend_ssa_var_info *ret); + +END_EXTERN_C() + +#endif /* ZEND_INFERENCE_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 7ae0e06127..08ac084713 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -26,6 +26,15 @@ #include "zend_constants.h" #include "zend_execute.h" #include "zend_vm.h" +#include "zend_cfg.h" +#include "zend_func_info.h" +#include "zend_call_graph.h" +#include "zend_inference.h" +#include "zend_dump.h" + +#ifndef HAVE_DFA_PASS +# define HAVE_DFA_PASS 1 +#endif static void zend_optimizer_zval_dtor_wrapper(zval *zvalue) { @@ -88,11 +97,6 @@ int zend_optimizer_lookup_cv(zend_op_array *op_array, zend_string* name) if (opline->result_type & (IS_TMP_VAR|IS_VAR)) { opline->result.var += sizeof(zval); } - if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS || - opline->opcode == ZEND_DECLARE_ANON_INHERITED_CLASS || - opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) { - opline->extended_value += sizeof(zval); - } opline++; } } @@ -168,6 +172,7 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array, case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_CATCH: case ZEND_FETCH_CONSTANT: + case ZEND_FETCH_CLASS_CONSTANT: case ZEND_DEFINED: case ZEND_NEW: REQUIRES_STRING(val); @@ -176,6 +181,32 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array, alloc_cache_slots_op1(op_array, opline, 1); zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); break; + case ZEND_FETCH_STATIC_PROP_R: + case ZEND_FETCH_STATIC_PROP_W: + case ZEND_FETCH_STATIC_PROP_RW: + case ZEND_FETCH_STATIC_PROP_IS: + case ZEND_FETCH_STATIC_PROP_UNSET: + case ZEND_FETCH_STATIC_PROP_FUNC_ARG: + TO_STRING_NOWARN(val); + opline->op1.constant = zend_optimizer_add_literal(op_array, val); + alloc_cache_slots_op1(op_array, opline, 2); + break; + case ZEND_SEND_VAR: + opline->opcode = ZEND_SEND_VAL; + opline->op1.constant = zend_optimizer_add_literal(op_array, val); + break; + case ZEND_SEPARATE: + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: + zval_ptr_dtor(val); + return 0; + case ZEND_VERIFY_RETURN_TYPE: + /* This would require a non-local change. + * zend_optimizer_replace_by_const() supports this. */ + zval_ptr_dtor(val); + return 0; + case ZEND_CONCAT: + case ZEND_FAST_CONCAT: case ZEND_FETCH_R: case ZEND_FETCH_W: case ZEND_FETCH_RW: @@ -183,14 +214,6 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array, case ZEND_FETCH_UNSET: case ZEND_FETCH_FUNC_ARG: TO_STRING_NOWARN(val); - opline->op1.constant = zend_optimizer_add_literal(op_array, val); - if (opline->extended_value == ZEND_FETCH_STATIC_MEMBER) { - alloc_cache_slots_op1(op_array, opline, 2); - } - break; - case ZEND_CONCAT: - case ZEND_FAST_CONCAT: - TO_STRING_NOWARN(val); /* break missing intentionally */ default: opline->op1.constant = zend_optimizer_add_literal(op_array, val); @@ -210,22 +233,23 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array, { switch (opline->opcode) { case ZEND_ASSIGN_REF: + case ZEND_FAST_CALL: zval_dtor(val); return 0; - 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: case ZEND_INSTANCEOF: + case ZEND_FETCH_STATIC_PROP_R: + case ZEND_FETCH_STATIC_PROP_W: + case ZEND_FETCH_STATIC_PROP_RW: + case ZEND_FETCH_STATIC_PROP_IS: + case ZEND_FETCH_STATIC_PROP_UNSET: + case ZEND_FETCH_STATIC_PROP_FUNC_ARG: + case ZEND_UNSET_STATIC_PROP: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: REQUIRES_STRING(val); drop_leading_backslash(val); opline->op2.constant = zend_optimizer_add_literal(op_array, val); @@ -245,6 +269,13 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array, return 0; } + if (zend_optimizer_classify_function(Z_STR_P(val), opline->extended_value)) { + /* Dynamic call to various special functions must stay dynamic, + * otherwise would drop a warning */ + zval_dtor(val); + return 0; + } + opline->opcode = ZEND_INIT_FCALL_BY_NAME; drop_leading_backslash(val); opline->op2.constant = zend_optimizer_add_literal(op_array, val); @@ -261,7 +292,7 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array, zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); alloc_cache_slots_op2(op_array, opline, 2); break; - /*case ZEND_FETCH_CONSTANT:*/ + /*case ZEND_FETCH_CLASS_CONSTANT:*/ case ZEND_ASSIGN_OBJ: case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_W: @@ -299,26 +330,6 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array, opline->op2.constant = zend_optimizer_add_literal(op_array, val); } break; - 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_POW && - (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)) - ) { - opline->op2.constant = zend_optimizer_add_literal(op_array, val); - break; - } - /* break missing intentionally */ case ZEND_ISSET_ISEMPTY_DIM_OBJ: case ZEND_ADD_ARRAY_ELEMENT: case ZEND_INIT_ARRAY: @@ -359,6 +370,47 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array, return 1; } +void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var) +{ + if (op_array->last_live_range) { + int i = 0; + int j = 0; + uint32_t *map; + ALLOCA_FLAG(use_heap); + + map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_live_range, use_heap); + + do { + if ((op_array->live_range[i].var & ~ZEND_LIVE_MASK) != var) { + map[i] = j; + if (i != j) { + op_array->live_range[j] = op_array->live_range[i]; + } + j++; + } + i++; + } while (i < op_array->last_live_range); + if (i != j) { + if ((op_array->last_live_range = j)) { + zend_op *opline = op_array->opcodes; + zend_op *end = opline + op_array->last; + + while (opline != end) { + if ((opline->opcode == ZEND_FREE || opline->opcode == ZEND_FE_FREE) && + opline->extended_value == ZEND_FREE_ON_RETURN) { + opline->op2.num = map[opline->op2.num]; + } + opline++; + } + } else { + efree(op_array->live_range); + op_array->live_range = NULL; + } + } + free_alloca(map, use_heap); + } +} + int zend_optimizer_replace_by_const(zend_op_array *op_array, zend_op *opline, zend_uchar type, @@ -389,17 +441,10 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, opline->opcode = ZEND_SEND_VAL_EX; break; case ZEND_SEND_VAR_NO_REF: - if (opline->extended_value & ZEND_ARG_COMPILE_TIME_BOUND) { - if (opline->extended_value & ZEND_ARG_SEND_BY_REF) { - zval_dtor(val); - return 0; - } - opline->extended_value = 0; - opline->opcode = ZEND_SEND_VAL_EX; - } else { - opline->extended_value = 0; - opline->opcode = ZEND_SEND_VAL; - } + zval_dtor(val); + return 0; + case ZEND_SEND_VAR_NO_REF_EX: + opline->opcode = ZEND_SEND_VAL; break; case ZEND_SEND_USER: opline->opcode = ZEND_SEND_VAL_EX; @@ -422,16 +467,17 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, } while (m->opcode != ZEND_FREE || ZEND_OP1_TYPE(m) != type || ZEND_OP1(m).var != var); ZEND_ASSERT(m->opcode == ZEND_FREE && ZEND_OP1_TYPE(m) == type && ZEND_OP1(m).var == var); MAKE_NOP(m); + zend_optimizer_remove_live_range(op_array, var); return 1; } case ZEND_CASE: case ZEND_FREE: { zend_op *m, *n; - int brk = op_array->last_brk_cont; + int brk = op_array->last_live_range; zend_bool in_switch = 0; while (brk--) { - if (op_array->brk_cont_array[brk].start <= (opline - op_array->opcodes) && - op_array->brk_cont_array[brk].brk > (opline - op_array->opcodes)) { + if (op_array->live_range[brk].start <= (uint32_t)(opline - op_array->opcodes) && + op_array->live_range[brk].end > (uint32_t)(opline - op_array->opcodes)) { in_switch = 1; break; } @@ -445,7 +491,13 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, } m = opline; - n = op_array->opcodes + op_array->brk_cont_array[brk].brk + 1; + n = op_array->opcodes + op_array->live_range[brk].end; + if (n->opcode == ZEND_FREE && + !(n->extended_value & ZEND_FREE_ON_RETURN)) { + n++; + } else { + n = op_array->opcodes + op_array->last; + } while (m < n) { if (ZEND_OP1_TYPE(m) == type && ZEND_OP1(m).var == var) { @@ -464,11 +516,11 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, m++; } zval_dtor(val); + zend_optimizer_remove_live_range(op_array, var); return 1; } case ZEND_VERIFY_RETURN_TYPE: { zend_arg_info *ret_info = op_array->arg_info - 1; - ZEND_ASSERT((opline + 1)->opcode == ZEND_RETURN || (opline + 1)->opcode == ZEND_RETURN_BY_REF); if (ret_info->class_name || ret_info->type_hint == IS_CALLABLE || !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(val)) @@ -477,18 +529,32 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, return 0; } MAKE_NOP(opline); - zend_optimizer_update_op1_const(op_array, opline + 1, val); - return 1; + + /* zend_handle_loops_and_finally may inserts other oplines */ + do { + ++opline; + } while (opline->opcode != ZEND_RETURN && opline->opcode != ZEND_RETURN_BY_REF); + ZEND_ASSERT(ZEND_OP1(opline).var == var); + + break; } default: break; } - return zend_optimizer_update_op1_const(op_array, opline, val); + if (zend_optimizer_update_op1_const(op_array, opline, val)) { + zend_optimizer_remove_live_range(op_array, var); + return 1; + } + return 0; } if (ZEND_OP2_TYPE(opline) == type && ZEND_OP2(opline).var == var) { - return zend_optimizer_update_op2_const(op_array, opline, val); + if (zend_optimizer_update_op2_const(op_array, opline, val)) { + zend_optimizer_remove_live_range(op_array, var); + return 1; + } + return 0; } opline++; } @@ -496,6 +562,139 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, return 1; } +static zend_class_entry *get_class_entry_from_op1( + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants) { + if (opline->op1_type == IS_CONST) { + zval *op1 = CRT_CONSTANT_EX(op_array, opline->op1, rt_constants); + if (Z_TYPE_P(op1) == IS_STRING) { + zend_string *class_name = Z_STR_P(op1 + 1); + zend_class_entry *ce; + if (script && (ce = zend_hash_find_ptr(&script->class_table, class_name))) { + return ce; + } else if ((ce = zend_hash_find_ptr(EG(class_table), class_name))) { + if (ce->type == ZEND_INTERNAL_CLASS) { + return ce; + } else if (ce->type == ZEND_USER_CLASS && + ce->info.user.filename && + ce->info.user.filename == op_array->filename) { + return ce; + } + } + } + } else if (opline->op1_type == IS_UNUSED && op_array->scope + && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) + && (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) { + return op_array->scope; + } + return NULL; +} + +zend_function *zend_optimizer_get_called_func( + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants) +{ +#define GET_OP(op) CRT_CONSTANT_EX(op_array, opline->op, rt_constants) + switch (opline->opcode) { + case ZEND_INIT_FCALL: + { + zend_string *function_name = Z_STR_P(GET_OP(op2)); + zend_function *func; + if (script && (func = zend_hash_find_ptr(&script->function_table, function_name)) != NULL) { + return func; + } else if ((func = zend_hash_find_ptr(EG(function_table), function_name)) != NULL) { + if (func->type == ZEND_INTERNAL_FUNCTION) { + return func; + } else if (func->type == ZEND_USER_FUNCTION && + func->op_array.filename && + func->op_array.filename == op_array->filename) { + return func; + } + } + break; + } + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + if (opline->op2_type == IS_CONST && Z_TYPE_P(GET_OP(op2)) == IS_STRING) { + zval *function_name = GET_OP(op2) + 1; + zend_function *func; + if (script && (func = zend_hash_find_ptr(&script->function_table, Z_STR_P(function_name)))) { + return func; + } else if ((func = zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name))) != NULL) { + if (func->type == ZEND_INTERNAL_FUNCTION) { + return func; + } else if (func->type == ZEND_USER_FUNCTION && + func->op_array.filename && + func->op_array.filename == op_array->filename) { + return func; + } + } + } + break; + case ZEND_INIT_STATIC_METHOD_CALL: + if (opline->op2_type == IS_CONST && Z_TYPE_P(GET_OP(op2)) == IS_STRING) { + zend_class_entry *ce = get_class_entry_from_op1( + script, op_array, opline, rt_constants); + if (ce) { + zend_string *func_name = Z_STR_P(GET_OP(op2) + 1); + return zend_hash_find_ptr(&ce->function_table, func_name); + } + } + break; + case ZEND_INIT_METHOD_CALL: + if (opline->op1_type == IS_UNUSED + && opline->op2_type == IS_CONST && Z_TYPE_P(GET_OP(op2)) == IS_STRING + && op_array->scope && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)) { + zend_string *method_name = Z_STR_P(GET_OP(op2) + 1); + zend_function *fbc = zend_hash_find_ptr( + &op_array->scope->function_table, method_name); + if (fbc) { + zend_bool is_private = (fbc->common.fn_flags & ZEND_ACC_PRIVATE) != 0; + zend_bool is_final = (fbc->common.fn_flags & ZEND_ACC_FINAL) != 0; + zend_bool same_scope = fbc->common.scope == op_array->scope; + if ((is_private && same_scope) + || (is_final && (!is_private || same_scope))) { + return fbc; + } + } + } + break; + case ZEND_NEW: + { + zend_class_entry *ce = get_class_entry_from_op1( + script, op_array, opline, rt_constants); + if (ce && ce->type == ZEND_USER_CLASS) { + return ce->constructor; + } + break; + } + } + return NULL; +#undef GET_OP +} + +uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args) { + if (zend_string_equals_literal(name, "extract")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "compact")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "parse_str") && num_args <= 1) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "mb_parse_str") && num_args <= 1) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "get_defined_vars")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "assert")) { + return ZEND_FUNC_INDIRECT_VAR_ACCESS; + } else if (zend_string_equals_literal(name, "func_num_args")) { + return ZEND_FUNC_VARARG; + } else if (zend_string_equals_literal(name, "func_get_arg")) { + return ZEND_FUNC_VARARG; + } else if (zend_string_equals_literal(name, "func_get_args")) { + return ZEND_FUNC_VARARG; + } else { + return 0; + } +} + static void zend_optimize(zend_op_array *op_array, zend_optimizer_ctx *ctx) { @@ -503,24 +702,33 @@ static void zend_optimize(zend_op_array *op_array, return; } + if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) { + zend_dump_op_array(op_array, 0, "before optimizer", NULL); + } + /* 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) + * - pre-evaluate constant function calls */ - if (ZEND_OPTIMIZER_PASS_1 & OPTIMIZATION_LEVEL) { + if (ZEND_OPTIMIZER_PASS_1 & ctx->optimization_level) { zend_optimizer_pass1(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_1) { + zend_dump_op_array(op_array, 0, "after pass 1", NULL); + } } /* pass 2: * - convert non-numeric constants to numeric constants in numeric operators * - optimize constant conditional JMPs - * - optimize static BRKs and CONTs - * - pre-evaluate constant function calls */ - if (ZEND_OPTIMIZER_PASS_2 & OPTIMIZATION_LEVEL) { + if (ZEND_OPTIMIZER_PASS_2 & ctx->optimization_level) { zend_optimizer_pass2(op_array); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_2) { + zend_dump_op_array(op_array, 0, "after pass 2", NULL); + } } /* pass 3: @@ -528,52 +736,85 @@ static void zend_optimize(zend_op_array *op_array, * - optimize series of JMPs * - change $i++ to ++$i where possible */ - if (ZEND_OPTIMIZER_PASS_3 & OPTIMIZATION_LEVEL) { + if (ZEND_OPTIMIZER_PASS_3 & ctx->optimization_level) { zend_optimizer_pass3(op_array); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_3) { + zend_dump_op_array(op_array, 0, "after pass 3", NULL); + } } /* pass 4: * - INIT_FCALL_BY_NAME -> DO_FCALL */ - if (ZEND_OPTIMIZER_PASS_4 & OPTIMIZATION_LEVEL) { - optimize_func_calls(op_array, ctx); + if (ZEND_OPTIMIZER_PASS_4 & ctx->optimization_level) { + zend_optimize_func_calls(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_4) { + zend_dump_op_array(op_array, 0, "after pass 4", NULL); + } } /* pass 5: * - CFG optimization */ - if (ZEND_OPTIMIZER_PASS_5 & OPTIMIZATION_LEVEL) { - optimize_cfg(op_array, ctx); + if (ZEND_OPTIMIZER_PASS_5 & ctx->optimization_level) { + zend_optimize_cfg(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_5) { + zend_dump_op_array(op_array, 0, "after pass 5", NULL); + } + } + +#if HAVE_DFA_PASS + /* pass 6: + * - DFA optimization + */ + if ((ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) && + !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) { + zend_optimize_dfa(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_6) { + zend_dump_op_array(op_array, 0, "after pass 6", NULL); + } } +#endif /* pass 9: * - Optimize temp variables usage */ - if (ZEND_OPTIMIZER_PASS_9 & OPTIMIZATION_LEVEL) { - optimize_temporary_variables(op_array, ctx); + if (ZEND_OPTIMIZER_PASS_9 & ctx->optimization_level) { + zend_optimize_temporary_variables(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_9) { + zend_dump_op_array(op_array, 0, "after pass 9", NULL); + } } /* pass 10: * - remove NOPs */ - if (((ZEND_OPTIMIZER_PASS_10|ZEND_OPTIMIZER_PASS_5) & OPTIMIZATION_LEVEL) == ZEND_OPTIMIZER_PASS_10) { + if (((ZEND_OPTIMIZER_PASS_10|ZEND_OPTIMIZER_PASS_5) & ctx->optimization_level) == ZEND_OPTIMIZER_PASS_10) { zend_optimizer_nop_removal(op_array); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_10) { + zend_dump_op_array(op_array, 0, "after pass 10", NULL); + } } /* pass 11: * - Compact literals table */ - if (ZEND_OPTIMIZER_PASS_11 & OPTIMIZATION_LEVEL) { + if (ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) { zend_optimizer_compact_literals(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_11) { + zend_dump_op_array(op_array, 0, "after pass 11", NULL); + } + } + + if (ctx->debug_level & ZEND_DUMP_AFTER_OPTIMIZER) { + zend_dump_op_array(op_array, 0, "after optimizer", NULL); } } -static void zend_accel_optimize(zend_op_array *op_array, - zend_optimizer_ctx *ctx) +static void zend_revert_pass_two(zend_op_array *op_array) { zend_op *opline, *end; - /* Revert pass_two() */ opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { @@ -583,41 +824,14 @@ static void zend_accel_optimize(zend_op_array *op_array, if (opline->op2_type == IS_CONST) { ZEND_PASS_TWO_UNDO_CONSTANT(op_array, opline->op2); } - switch (opline->opcode) { - case ZEND_JMP: - case ZEND_FAST_CALL: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: - ZEND_PASS_TWO_UNDO_JMP_TARGET(op_array, opline, ZEND_OP1(opline)); - break; - case ZEND_JMPZNZ: - /* relative offset into absolute index */ - opline->extended_value = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value); - /* break omitted intentionally */ - case ZEND_JMPZ: - case ZEND_JMPNZ: - case ZEND_JMPZ_EX: - case ZEND_JMPNZ_EX: - case ZEND_JMP_SET: - case ZEND_COALESCE: - case ZEND_NEW: - case ZEND_FE_RESET_R: - case ZEND_FE_RESET_RW: - case ZEND_ASSERT_CHECK: - ZEND_PASS_TWO_UNDO_JMP_TARGET(op_array, opline, ZEND_OP2(opline)); - break; - case ZEND_FE_FETCH_R: - case ZEND_FE_FETCH_RW: - opline->extended_value = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value); - break; - } opline++; } +} - /* Do actual optimizations */ - zend_optimize(op_array, ctx); +static void zend_redo_pass_two(zend_op_array *op_array) +{ + zend_op *opline, *end; - /* Redo pass_two() */ opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { @@ -627,40 +841,53 @@ static void zend_accel_optimize(zend_op_array *op_array, if (opline->op2_type == IS_CONST) { ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2); } - switch (opline->opcode) { - case ZEND_JMP: - case ZEND_FAST_CALL: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: - ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, ZEND_OP1(opline)); - break; - case ZEND_JMPZNZ: - /* absolute index to relative offset */ - opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, opline->extended_value); - /* break omitted intentionally */ - case ZEND_JMPZ: - case ZEND_JMPNZ: - case ZEND_JMPZ_EX: - case ZEND_JMPNZ_EX: - case ZEND_JMP_SET: - case ZEND_COALESCE: - case ZEND_NEW: - case ZEND_FE_RESET_R: - case ZEND_FE_RESET_RW: - case ZEND_ASSERT_CHECK: - ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, ZEND_OP2(opline)); - break; - case ZEND_FE_FETCH_R: - case ZEND_FE_FETCH_RW: - opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, opline->extended_value); - break; - } ZEND_VM_SET_OPCODE_HANDLER(opline); opline++; } } -static void zend_accel_adjust_fcall_stack_size(zend_op_array *op_array, zend_optimizer_ctx *ctx) +#if HAVE_DFA_PASS +static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa) +{ + zend_op *opline, *end; + + opline = op_array->opcodes; + end = opline + op_array->last; + while (opline < end) { + zend_vm_set_opcode_handler_ex(opline, + opline->op1_type == IS_UNUSED ? 0 : (OP1_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)), + opline->op2_type == IS_UNUSED ? 0 : (OP2_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)), + (opline->opcode == ZEND_PRE_INC || + opline->opcode == ZEND_PRE_DEC || + opline->opcode == ZEND_POST_INC || + opline->opcode == ZEND_POST_DEC) ? + ((ssa->ops[opline - op_array->opcodes].op1_def >= 0) ? (OP1_DEF_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)) : MAY_BE_ANY) : + (opline->result_type == IS_UNUSED ? 0 : (RES_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)))); + if (opline->op1_type == IS_CONST) { + ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1); + } + if (opline->op2_type == IS_CONST) { + ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2); + } + opline++; + } +} +#endif + +static void zend_optimize_op_array(zend_op_array *op_array, + zend_optimizer_ctx *ctx) +{ + /* Revert pass_two() */ + zend_revert_pass_two(op_array); + + /* Do actual optimizations */ + zend_optimize(op_array, ctx); + + /* Redo pass_two() */ + zend_redo_pass_two(op_array); +} + +static void zend_adjust_fcall_stack_size(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_function *func; zend_op *opline, *end; @@ -680,78 +907,151 @@ static void zend_accel_adjust_fcall_stack_size(zend_op_array *op_array, zend_opt } } -int zend_accel_script_optimize(zend_persistent_script *script) +#if HAVE_DFA_PASS +static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array) +{ + zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + + if (func_info) { + zend_call_info *call_info =func_info->callee_info; + + while (call_info) { + zend_op *opline = call_info->caller_init_opline; + + if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) { + opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func); + } + call_info = call_info->next_callee; + } + } +} +#endif + +int zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level) { - uint idx, j; - Bucket *p, *q; zend_class_entry *ce; zend_op_array *op_array; + zend_string *name; zend_optimizer_ctx ctx; +#if HAVE_DFA_PASS + zend_call_graph call_graph; +#endif ctx.arena = zend_arena_create(64 * 1024); ctx.script = script; ctx.constants = NULL; + ctx.optimization_level = optimization_level; + ctx.debug_level = debug_level; - zend_accel_optimize(&script->main_op_array, &ctx); + zend_optimize_op_array(&script->main_op_array, &ctx); - for (idx = 0; idx < script->function_table.nNumUsed; idx++) { - p = script->function_table.arData + idx; - if (Z_TYPE(p->val) == IS_UNDEF) continue; - op_array = (zend_op_array*)Z_PTR(p->val); - zend_accel_optimize(op_array, &ctx); - } + ZEND_HASH_FOREACH_PTR(&script->function_table, op_array) { + zend_optimize_op_array(op_array, &ctx); + } ZEND_HASH_FOREACH_END(); - for (idx = 0; idx < script->class_table.nNumUsed; idx++) { - p = script->class_table.arData + idx; - if (Z_TYPE(p->val) == IS_UNDEF) continue; - ce = (zend_class_entry*)Z_PTR(p->val); - for (j = 0; j < ce->function_table.nNumUsed; j++) { - q = ce->function_table.arData + j; - if (Z_TYPE(q->val) == IS_UNDEF) continue; - op_array = (zend_op_array*)Z_PTR(q->val); + ZEND_HASH_FOREACH_PTR(&script->class_table, ce) { + ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->function_table, name, op_array) { if (op_array->scope == ce) { - zend_accel_optimize(op_array, &ctx); + zend_optimize_op_array(op_array, &ctx); } else if (op_array->type == ZEND_USER_FUNCTION) { zend_op_array *orig_op_array; - if ((orig_op_array = zend_hash_find_ptr(&op_array->scope->function_table, q->key)) != NULL) { + if ((orig_op_array = zend_hash_find_ptr(&op_array->scope->function_table, name)) != NULL) { HashTable *ht = op_array->static_variables; *op_array = *orig_op_array; op_array->static_variables = ht; } } + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + +#if HAVE_DFA_PASS + if ((ZEND_OPTIMIZER_PASS_6 & optimization_level) && + (ZEND_OPTIMIZER_PASS_7 & optimization_level) && + zend_build_call_graph(&ctx.arena, script, ZEND_RT_CONSTANTS, &call_graph) == SUCCESS) { + /* Optimize using call-graph */ + void *checkpoint = zend_arena_checkpoint(ctx.arena); + int i; + zend_func_info *func_info; + + for (i = 0; i < call_graph.op_arrays_count; i++) { + zend_revert_pass_two(call_graph.op_arrays[i]); + } + + for (i = 0; i < call_graph.op_arrays_count; i++) { + func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); + if (func_info) { + func_info->call_map = zend_build_call_map(&ctx.arena, func_info, call_graph.op_arrays[i]); + if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_init_func_return_info(call_graph.op_arrays[i], script, &func_info->return_info); + } + } + } + + for (i = 0; i < call_graph.op_arrays_count; i++) { + func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); + if (func_info) { + zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, &func_info->flags); + } + } + + //TODO: perform inner-script inference??? + for (i = 0; i < call_graph.op_arrays_count; i++) { + func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); + if (func_info) { + zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa); + } + } + + if (debug_level & ZEND_DUMP_AFTER_PASS_7) { + for (i = 0; i < call_graph.op_arrays_count; i++) { + zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 7", NULL); + } + } + + if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { + for (i = 0; i < call_graph.op_arrays_count; i++) { + zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]); + } } - } - if (ZEND_OPTIMIZER_PASS_12 & OPTIMIZATION_LEVEL) { - zend_accel_adjust_fcall_stack_size(&script->main_op_array, &ctx); + for (i = 0; i < call_graph.op_arrays_count; i++) { + func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); + if (func_info && func_info->ssa.var_info) { + zend_redo_pass_two_ex(call_graph.op_arrays[i], &func_info->ssa); + } else { + zend_redo_pass_two(call_graph.op_arrays[i]); + } + } - for (idx = 0; idx < script->function_table.nNumUsed; idx++) { - p = script->function_table.arData + idx; - if (Z_TYPE(p->val) == IS_UNDEF) continue; - op_array = (zend_op_array*)Z_PTR(p->val); - zend_accel_adjust_fcall_stack_size(op_array, &ctx); + for (i = 0; i < call_graph.op_arrays_count; i++) { + ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); } - for (idx = 0; idx < script->class_table.nNumUsed; idx++) { - p = script->class_table.arData + idx; - if (Z_TYPE(p->val) == IS_UNDEF) continue; - ce = (zend_class_entry*)Z_PTR(p->val); - for (j = 0; j < ce->function_table.nNumUsed; j++) { - q = ce->function_table.arData + j; - if (Z_TYPE(q->val) == IS_UNDEF) continue; - op_array = (zend_op_array*)Z_PTR(q->val); + zend_arena_release(&ctx.arena, checkpoint); + } else +#endif + + if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { + zend_adjust_fcall_stack_size(&script->main_op_array, &ctx); + + ZEND_HASH_FOREACH_PTR(&script->function_table, op_array) { + zend_adjust_fcall_stack_size(op_array, &ctx); + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_FOREACH_PTR(&script->class_table, ce) { + ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->function_table, name, op_array) { if (op_array->scope == ce) { - zend_accel_adjust_fcall_stack_size(op_array, &ctx); + zend_adjust_fcall_stack_size(op_array, &ctx); } else if (op_array->type == ZEND_USER_FUNCTION) { zend_op_array *orig_op_array; - if ((orig_op_array = zend_hash_find_ptr(&op_array->scope->function_table, q->key)) != NULL) { + if ((orig_op_array = zend_hash_find_ptr(&op_array->scope->function_table, name)) != NULL) { HashTable *ht = op_array->static_variables; *op_array = *orig_op_array; op_array->static_variables = ht; } } - } - } + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); } if (ctx.constants) { @@ -761,3 +1061,21 @@ int zend_accel_script_optimize(zend_persistent_script *script) return 1; } + +int zend_optimizer_startup(void) +{ + return zend_func_info_startup(); +} + +int zend_optimizer_shutdown(void) +{ + return zend_func_info_shutdown(); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_optimizer.h b/ext/opcache/Optimizer/zend_optimizer.h index 76f6a24264..69c89d7234 100644 --- a/ext/opcache/Optimizer/zend_optimizer.h +++ b/ext/opcache/Optimizer/zend_optimizer.h @@ -30,8 +30,8 @@ #define ZEND_OPTIMIZER_PASS_3 (1<<2) /* ++, +=, series of jumps */ #define ZEND_OPTIMIZER_PASS_4 (1<<3) /* INIT_FCALL_BY_NAME -> DO_FCALL */ #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_6 (1<<5) /* DFA based optimization */ +#define ZEND_OPTIMIZER_PASS_7 (1<<6) /* CALL GRAPH optimization */ #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 */ @@ -40,9 +40,53 @@ #define ZEND_OPTIMIZER_PASS_13 (1<<12) #define ZEND_OPTIMIZER_PASS_14 (1<<13) #define ZEND_OPTIMIZER_PASS_15 (1<<14) /* Collect constants */ +#define ZEND_OPTIMIZER_PASS_16 (1<<15) /* Inline functions */ #define ZEND_OPTIMIZER_ALL_PASSES 0x7FFFFFFF #define DEFAULT_OPTIMIZATION_LEVEL "0x7FFFBFFF" + +#define ZEND_DUMP_AFTER_PASS_1 ZEND_OPTIMIZER_PASS_1 +#define ZEND_DUMP_AFTER_PASS_2 ZEND_OPTIMIZER_PASS_2 +#define ZEND_DUMP_AFTER_PASS_3 ZEND_OPTIMIZER_PASS_3 +#define ZEND_DUMP_AFTER_PASS_4 ZEND_OPTIMIZER_PASS_4 +#define ZEND_DUMP_AFTER_PASS_5 ZEND_OPTIMIZER_PASS_5 +#define ZEND_DUMP_AFTER_PASS_6 ZEND_OPTIMIZER_PASS_6 +#define ZEND_DUMP_AFTER_PASS_7 ZEND_OPTIMIZER_PASS_7 +#define ZEND_DUMP_AFTER_PASS_8 ZEND_OPTIMIZER_PASS_8 +#define ZEND_DUMP_AFTER_PASS_9 ZEND_OPTIMIZER_PASS_9 +#define ZEND_DUMP_AFTER_PASS_10 ZEND_OPTIMIZER_PASS_10 +#define ZEND_DUMP_AFTER_PASS_11 ZEND_OPTIMIZER_PASS_11 +#define ZEND_DUMP_AFTER_PASS_12 ZEND_OPTIMIZER_PASS_12 +#define ZEND_DUMP_AFTER_PASS_13 ZEND_OPTIMIZER_PASS_13 +#define ZEND_DUMP_AFTER_PASS_14 ZEND_OPTIMIZER_PASS_14 + +#define ZEND_DUMP_BEFORE_OPTIMIZER (1<<16) +#define ZEND_DUMP_AFTER_OPTIMIZER (1<<17) + +#define ZEND_DUMP_BEFORE_BLOCK_PASS (1<<18) +#define ZEND_DUMP_AFTER_BLOCK_PASS (1<<19) +#define ZEND_DUMP_BLOCK_PASS_VARS (1<<20) + +#define ZEND_DUMP_BEFORE_DFA_PASS (1<<21) +#define ZEND_DUMP_AFTER_DFA_PASS (1<<22) +#define ZEND_DUMP_DFA_CFG (1<<23) +#define ZEND_DUMP_DFA_DOMINATORS (1<<24) +#define ZEND_DUMP_DFA_LIVENESS (1<<25) +#define ZEND_DUMP_DFA_PHI (1<<26) +#define ZEND_DUMP_DFA_SSA (1<<27) +#define ZEND_DUMP_DFA_SSA_VARS (1<<28) + +typedef struct _zend_script { + zend_string *filename; + zend_op_array main_op_array; + HashTable function_table; + HashTable class_table; +} zend_script; + +int zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level); +int zend_optimizer_startup(void); +int zend_optimizer_shutdown(void); + #endif diff --git a/ext/opcache/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h index 257a54ea93..90297ad816 100644 --- a/ext/opcache/Optimizer/zend_optimizer_internal.h +++ b/ext/opcache/Optimizer/zend_optimizer_internal.h @@ -22,7 +22,18 @@ #ifndef ZEND_OPTIMIZER_INTERNAL_H #define ZEND_OPTIMIZER_INTERNAL_H -#include "ZendAccelerator.h" +#include "zend_ssa.h" + +#define ZEND_RESULT_TYPE(opline) (opline)->result_type +#define ZEND_RESULT(opline) (opline)->result +#define ZEND_OP1_TYPE(opline) (opline)->op1_type +#define ZEND_OP1(opline) (opline)->op1 +#define ZEND_OP1_LITERAL(opline) (op_array)->literals[(opline)->op1.constant] +#define ZEND_OP1_JMP_ADDR(opline) OP_JMP_ADDR(opline, (opline)->op1) +#define ZEND_OP2_TYPE(opline) (opline)->op2_type +#define ZEND_OP2(opline) (opline)->op2 +#define ZEND_OP2_LITERAL(opline) (op_array)->literals[(opline)->op2.constant] +#define ZEND_OP2_JMP_ADDR(opline) OP_JMP_ADDR(opline, (opline)->op2) #define VAR_NUM(v) EX_VAR_TO_NUM(v) #define NUM_VAR(v) ((uint32_t)(zend_uintptr_t)ZEND_CALL_VAR_NUM(0, v)) @@ -32,65 +43,17 @@ #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) -#undef MAKE_NOP - -#define MAKE_NOP(opline) do { \ - (opline)->op1.num = 0; \ - (opline)->op2.num = 0; \ - (opline)->result.num = 0; \ - (opline)->opcode = ZEND_NOP; \ - (opline)->op1_type = IS_UNUSED; \ - (opline)->op2_type = IS_UNUSED; \ - (opline)->result_type = IS_UNUSED; \ - zend_vm_set_opcode_handler(opline); \ -} while (0) - -#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) +#define RESULT_UNUSED(op) (op->result_type == IS_UNUSED) +#define SAME_VAR(op1, op2) (op1 ## _type == op2 ## _type && op1.var == op2.var) typedef struct _zend_optimizer_ctx { zend_arena *arena; - zend_persistent_script *script; + zend_script *script; HashTable *constants; + zend_long optimization_level; + zend_long debug_level; } zend_optimizer_ctx; -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_op **Tsource; - char *same_t; -} zend_cfg; - -struct _zend_block_source { - zend_code_block *from; - zend_block_source *next; -}; - -#define OPTIMIZATION_LEVEL \ - ZCG(accel_directives).optimization_level - #define LITERAL_LONG(op, val) do { \ zval _c; \ ZVAL_LONG(&_c, val); \ @@ -130,14 +93,21 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, uint32_t var, zval *val); +void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var); void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx); void zend_optimizer_pass2(zend_op_array *op_array); void zend_optimizer_pass3(zend_op_array *op_array); -void optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx); -void optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx); -void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx); +void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx); +void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx); +void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx); +int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags); +void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa); +void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx); void zend_optimizer_nop_removal(zend_op_array *op_array); void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx); int zend_optimizer_is_disabled_func(const char *name, size_t len); +zend_function *zend_optimizer_get_called_func( + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants); +uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args); #endif diff --git a/ext/opcache/Optimizer/zend_ssa.c b/ext/opcache/Optimizer/zend_ssa.c new file mode 100644 index 0000000000..c902e51766 --- /dev/null +++ b/ext/opcache/Optimizer/zend_ssa.c @@ -0,0 +1,1155 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, SSA - Static Single Assignment Form | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "zend_compile.h" +#include "zend_dfg.h" +#include "zend_ssa.h" +#include "zend_dump.h" +#include "zend_inference.h" + +static zend_bool dominates(const zend_basic_block *blocks, int a, int b) { + while (blocks[b].level > blocks[a].level) { + b = blocks[b].idom; + } + return a == b; +} + +static zend_bool dominates_other_predecessors( + const zend_cfg *cfg, const zend_basic_block *block, int check, int exclude) { + int i; + for (i = 0; i < block->predecessors_count; i++) { + int predecessor = cfg->predecessors[block->predecessor_offset + i]; + if (predecessor != exclude && !dominates(cfg->blocks, check, predecessor)) { + return 0; + } + } + return 1; +} + +static zend_bool needs_pi(const zend_op_array *op_array, zend_dfg *dfg, zend_ssa *ssa, int from, int to, int var) /* {{{ */ +{ + zend_basic_block *from_block, *to_block; + int other_successor; + + if (!DFG_ISSET(dfg->in, dfg->size, to, var)) { + /* Variable is not live, certainly won't benefit from pi */ + return 0; + } + + to_block = &ssa->cfg.blocks[to]; + if (to_block->predecessors_count == 1) { + /* Always place pi if one predecessor (an if branch) */ + return 1; + } + + /* Check that the other successor of the from block does not dominate all other predecessors. + * If it does, we'd probably end up annihilating a positive+negative pi assertion. */ + from_block = &ssa->cfg.blocks[from]; + other_successor = from_block->successors[0] == to + ? from_block->successors[1] : from_block->successors[0]; + return !dominates_other_predecessors(&ssa->cfg, to_block, other_successor, from); +} +/* }}} */ + +static zend_ssa_phi *add_pi( + zend_arena **arena, const zend_op_array *op_array, zend_dfg *dfg, zend_ssa *ssa, + int from, int to, int var) /* {{{ */ +{ + zend_ssa_phi *phi; + if (!needs_pi(op_array, dfg, ssa, from, to, var)) { + return NULL; + } + + phi = zend_arena_calloc(arena, 1, + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)) + + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->cfg.blocks[to].predecessors_count) + + sizeof(void*) * ssa->cfg.blocks[to].predecessors_count); + phi->sources = (int*)(((char*)phi) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi))); + memset(phi->sources, 0xff, sizeof(int) * ssa->cfg.blocks[to].predecessors_count); + phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->cfg.blocks[to].predecessors_count)); + + phi->pi = from; + phi->var = var; + phi->ssa_var = -1; + phi->next = ssa->blocks[to].phis; + ssa->blocks[to].phis = phi; + + /* Block "to" now defines "var" via the pi statement, so add it to the "def" set. Note that + * this is not entirely accurate, because the pi is actually placed along the edge from->to. + * If there is a back-edge to "to" this may result in non-minimal SSA form. */ + DFG_SET(dfg->def, dfg->size, to, var); + + /* If there are multiple predecessors in the target block, we need to place a phi there. + * However this can (generally) not be expressed in terms of dominance frontiers, so place it + * explicitly. dfg->use here really is dfg->phi, we're reusing the set. */ + if (ssa->cfg.blocks[to].predecessors_count > 1) { + DFG_SET(dfg->use, dfg->size, to, var); + } + + return phi; +} +/* }}} */ + +static void pi_range( + zend_ssa_phi *phi, int min_var, int max_var, zend_long min, zend_long max, + char underflow, char overflow, char negative) /* {{{ */ +{ + zend_ssa_range_constraint *constraint = &phi->constraint.range; + constraint->min_var = min_var; + constraint->max_var = max_var; + constraint->min_ssa_var = -1; + constraint->max_ssa_var = -1; + constraint->range.min = min; + constraint->range.max = max; + constraint->range.underflow = underflow; + constraint->range.overflow = overflow; + constraint->negative = negative ? NEG_INIT : NEG_NONE; + phi->has_range_constraint = 1; +} +/* }}} */ + +static inline void pi_range_equals(zend_ssa_phi *phi, int var, zend_long val) { + pi_range(phi, var, var, val, val, 0, 0, 0); +} +static inline void pi_range_not_equals(zend_ssa_phi *phi, int var, zend_long val) { + pi_range(phi, var, var, val, val, 0, 0, 1); +} +static inline void pi_range_min(zend_ssa_phi *phi, int var, zend_long val) { + pi_range(phi, var, -1, val, ZEND_LONG_MAX, 0, 1, 0); +} +static inline void pi_range_max(zend_ssa_phi *phi, int var, zend_long val) { + pi_range(phi, -1, var, ZEND_LONG_MIN, val, 1, 0, 0); +} + +static void pi_type_mask(zend_ssa_phi *phi, uint32_t type_mask) { + phi->has_range_constraint = 0; + phi->constraint.type.ce = NULL; + phi->constraint.type.type_mask = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN; + phi->constraint.type.type_mask |= type_mask; + if (type_mask & MAY_BE_NULL) { + phi->constraint.type.type_mask |= MAY_BE_UNDEF; + } +} +static inline void pi_not_type_mask(zend_ssa_phi *phi, uint32_t type_mask) { + uint32_t relevant = MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + pi_type_mask(phi, ~type_mask & relevant); +} +static inline uint32_t mask_for_type_check(uint32_t type) { + if (type == _IS_BOOL) { + return MAY_BE_TRUE|MAY_BE_FALSE; + } else if (type == IS_ARRAY) { + return MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else { + return 1 << type; + } +} + +/* We can interpret $a + 5 == 0 as $a = 0 - 5, i.e. shift the adjustment to the other operand. + * This negated adjustment is what is written into the "adjustment" parameter. */ +static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_flags, zend_op *opline, uint32_t var_num, zend_long *adjustment) /* {{{ */ +{ + zend_op *op = opline; + zval *zv; + + while (op != op_array->opcodes) { + op--; + if (op->result_type != IS_TMP_VAR || op->result.var != var_num) { + continue; + } + + if (op->opcode == ZEND_POST_DEC) { + if (op->op1_type == IS_CV) { + *adjustment = -1; + return EX_VAR_TO_NUM(op->op1.var); + } + } else if (op->opcode == ZEND_POST_INC) { + if (op->op1_type == IS_CV) { + *adjustment = 1; + return EX_VAR_TO_NUM(op->op1.var); + } + } else if (op->opcode == ZEND_ADD) { + if (op->op1_type == IS_CV && op->op2_type == IS_CONST) { + zv = CRT_CONSTANT(op->op2); + if (Z_TYPE_P(zv) == IS_LONG + && Z_LVAL_P(zv) != ZEND_LONG_MIN) { + *adjustment = -Z_LVAL_P(zv); + return EX_VAR_TO_NUM(op->op1.var); + } + } else if (op->op2_type == IS_CV && op->op1_type == IS_CONST) { + zv = CRT_CONSTANT(op->op1); + if (Z_TYPE_P(zv) == IS_LONG + && Z_LVAL_P(zv) != ZEND_LONG_MIN) { + *adjustment = -Z_LVAL_P(zv); + return EX_VAR_TO_NUM(op->op2.var); + } + } + } else if (op->opcode == ZEND_SUB) { + if (op->op1_type == IS_CV && op->op2_type == IS_CONST) { + zv = CRT_CONSTANT(op->op2); + if (Z_TYPE_P(zv) == IS_LONG) { + *adjustment = Z_LVAL_P(zv); + return EX_VAR_TO_NUM(op->op1.var); + } + } + } + break; + } + return -1; +} +/* }}} */ + +static inline zend_bool add_will_overflow(zend_long a, zend_long b) { + return (b > 0 && a > ZEND_LONG_MAX - b) + || (b < 0 && a < ZEND_LONG_MIN - b); +} +static inline zend_bool sub_will_overflow(zend_long a, zend_long b) { + return (b > 0 && a < ZEND_LONG_MIN + b) + || (b < 0 && a > ZEND_LONG_MAX + b); +} + +/* e-SSA construction: Pi placement (Pi is actually a Phi with single + * source and constraint). + * Order of Phis is importent, Pis must be placed before Phis + */ +static void place_essa_pis( + zend_arena **arena, const zend_script *script, const zend_op_array *op_array, + uint32_t build_flags, zend_ssa *ssa, zend_dfg *dfg) /* {{{ */ { + zend_basic_block *blocks = ssa->cfg.blocks; + int j, blocks_count = ssa->cfg.blocks_count; + for (j = 0; j < blocks_count; j++) { + zend_ssa_phi *pi; + zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1; + int bt; /* successor block number if a condition is true */ + int bf; /* successor block number if a condition is false */ + + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0 || blocks[j].len == 0) { + continue; + } + /* the last instruction of basic block is conditional branch, + * based on comparison of CV(s) + */ + switch (opline->opcode) { + case ZEND_JMPZ: + case ZEND_JMPZNZ: + bf = blocks[j].successors[0]; + bt = blocks[j].successors[1]; + break; + case ZEND_JMPNZ: + bt = blocks[j].successors[0]; + bf = blocks[j].successors[1]; + break; + default: + continue; + } + if (opline->op1_type == IS_TMP_VAR && + ((opline-1)->opcode == ZEND_IS_EQUAL || + (opline-1)->opcode == ZEND_IS_NOT_EQUAL || + (opline-1)->opcode == ZEND_IS_SMALLER || + (opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) && + opline->op1.var == (opline-1)->result.var) { + int var1 = -1; + int var2 = -1; + zend_long val1 = 0; + zend_long val2 = 0; +// long val = 0; + + if ((opline-1)->op1_type == IS_CV) { + var1 = EX_VAR_TO_NUM((opline-1)->op1.var); + } else if ((opline-1)->op1_type == IS_TMP_VAR) { + var1 = find_adjusted_tmp_var( + op_array, build_flags, opline, (opline-1)->op1.var, &val2); + } + + if ((opline-1)->op2_type == IS_CV) { + var2 = EX_VAR_TO_NUM((opline-1)->op2.var); + } else if ((opline-1)->op2_type == IS_TMP_VAR) { + var2 = find_adjusted_tmp_var( + op_array, build_flags, opline, (opline-1)->op2.var, &val1); + } + + if (var1 >= 0 && var2 >= 0) { + if (!sub_will_overflow(val1, val2) && !sub_will_overflow(val2, val1)) { + zend_long tmp = val1; + val1 -= val2; + val2 -= tmp; + } else { + var1 = -1; + var2 = -1; + } + } else if (var1 >= 0 && var2 < 0) { + zend_long add_val2 = 0; + if ((opline-1)->op2_type == IS_CONST) { + zval *zv = CRT_CONSTANT((opline-1)->op2); + + if (Z_TYPE_P(zv) == IS_LONG) { + add_val2 = Z_LVAL_P(zv); + } else if (Z_TYPE_P(zv) == IS_FALSE) { + add_val2 = 0; + } else if (Z_TYPE_P(zv) == IS_TRUE) { + add_val2 = 1; + } else { + var1 = -1; + } + } else { + var1 = -1; + } + if (!add_will_overflow(val2, add_val2)) { + val2 += add_val2; + } else { + var1 = -1; + } + } else if (var1 < 0 && var2 >= 0) { + zend_long add_val1 = 0; + if ((opline-1)->op1_type == IS_CONST) { + zval *zv = CRT_CONSTANT((opline-1)->op1); + if (Z_TYPE_P(zv) == IS_LONG) { + add_val1 = Z_LVAL_P(CRT_CONSTANT((opline-1)->op1)); + } else if (Z_TYPE_P(zv) == IS_FALSE) { + add_val1 = 0; + } else if (Z_TYPE_P(zv) == IS_TRUE) { + add_val1 = 1; + } else { + var2 = -1; + } + } else { + var2 = -1; + } + if (!add_will_overflow(val1, add_val1)) { + val1 += add_val1; + } else { + var2 = -1; + } + } + + if (var1 >= 0) { + if ((opline-1)->opcode == ZEND_IS_EQUAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { + pi_range_equals(pi, var2, val2); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { + pi_range_not_equals(pi, var2, val2); + } + } else if ((opline-1)->opcode == ZEND_IS_NOT_EQUAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { + pi_range_equals(pi, var2, val2); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { + pi_range_not_equals(pi, var2, val2); + } + } else if ((opline-1)->opcode == ZEND_IS_SMALLER) { + if (val2 > ZEND_LONG_MIN) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { + pi_range_max(pi, var2, val2-1); + } + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { + pi_range_min(pi, var2, val2); + } + } else if ((opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { + pi_range_max(pi, var2, val2); + } + if (val2 < ZEND_LONG_MAX) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { + pi_range_min(pi, var2, val2+1); + } + } + } + } + if (var2 >= 0) { + if((opline-1)->opcode == ZEND_IS_EQUAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { + pi_range_equals(pi, var1, val1); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { + pi_range_not_equals(pi, var1, val1); + } + } else if ((opline-1)->opcode == ZEND_IS_NOT_EQUAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { + pi_range_equals(pi, var1, val1); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { + pi_range_not_equals(pi, var1, val1); + } + } else if ((opline-1)->opcode == ZEND_IS_SMALLER) { + if (val1 < ZEND_LONG_MAX) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { + pi_range_min(pi, var1, val1+1); + } + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { + pi_range_max(pi, var1, val1); + } + } else if ((opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { + pi_range_min(pi, var1, val1); + } + if (val1 > ZEND_LONG_MIN) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { + pi_range_max(pi, var1, val1-1); + } + } + } + } + } else if (opline->op1_type == IS_TMP_VAR && + ((opline-1)->opcode == ZEND_POST_INC || + (opline-1)->opcode == ZEND_POST_DEC) && + opline->op1.var == (opline-1)->result.var && + (opline-1)->op1_type == IS_CV) { + int var = EX_VAR_TO_NUM((opline-1)->op1.var); + + if ((opline-1)->opcode == ZEND_POST_DEC) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { + pi_range_equals(pi, -1, -1); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_range_not_equals(pi, -1, -1); + } + } else if ((opline-1)->opcode == ZEND_POST_INC) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { + pi_range_equals(pi, -1, 1); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_range_not_equals(pi, -1, 1); + } + } + } else if (opline->op1_type == IS_VAR && + ((opline-1)->opcode == ZEND_PRE_INC || + (opline-1)->opcode == ZEND_PRE_DEC) && + opline->op1.var == (opline-1)->result.var && + (opline-1)->op1_type == IS_CV) { + int var = EX_VAR_TO_NUM((opline-1)->op1.var); + + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { + pi_range_equals(pi, -1, 0); + } + /* speculative */ + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_range_not_equals(pi, -1, 0); + } + } else if (opline->op1_type == IS_TMP_VAR && (opline-1)->opcode == ZEND_TYPE_CHECK && + opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV) { + int var = EX_VAR_TO_NUM((opline-1)->op1.var); + uint32_t type = (opline-1)->extended_value; + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_type_mask(pi, mask_for_type_check(type)); + } + if (type != IS_OBJECT && type != IS_RESOURCE) { + /* is_object() and is_resource() may return false, even though the value is + * an object/resource. */ + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { + pi_not_type_mask(pi, mask_for_type_check(type)); + } + } + } else if (opline->op1_type == IS_TMP_VAR && + ((opline-1)->opcode == ZEND_IS_IDENTICAL + || (opline-1)->opcode == ZEND_IS_NOT_IDENTICAL) && + opline->op1.var == (opline-1)->result.var) { + int var; + zval *val; + uint32_t type_mask; + if ((opline-1)->op1_type == IS_CV && (opline-1)->op2_type == IS_CONST) { + var = EX_VAR_TO_NUM((opline-1)->op1.var); + val = CRT_CONSTANT((opline-1)->op2); + } else if ((opline-1)->op1_type == IS_CONST && (opline-1)->op2_type == IS_CV) { + var = EX_VAR_TO_NUM((opline-1)->op2.var); + val = CRT_CONSTANT((opline-1)->op1); + } else { + continue; + } + + /* We're interested in === null/true/false comparisons here, because they eliminate + * a type in the false-branch. Other === VAL comparisons are unlikely to be useful. */ + if (Z_TYPE_P(val) != IS_NULL && Z_TYPE_P(val) != IS_TRUE && Z_TYPE_P(val) != IS_FALSE) { + continue; + } + + type_mask = _const_op_type(val); + if ((opline-1)->opcode == ZEND_IS_IDENTICAL) { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_type_mask(pi, type_mask); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { + pi_not_type_mask(pi, type_mask); + } + } else { + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { + pi_type_mask(pi, type_mask); + } + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_not_type_mask(pi, type_mask); + } + } + } else if (opline->op1_type == IS_TMP_VAR && (opline-1)->opcode == ZEND_INSTANCEOF && + opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV && + (opline-1)->op2_type == IS_CONST) { + int var = EX_VAR_TO_NUM((opline-1)->op1.var); + zend_string *lcname = Z_STR_P(CRT_CONSTANT((opline-1)->op2) + 1); + zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL; + if (!ce) { + ce = zend_hash_find_ptr(CG(class_table), lcname); + if (!ce || ce->type != ZEND_INTERNAL_CLASS) { + continue; + } + } + + if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { + pi_type_mask(pi, MAY_BE_OBJECT); + pi->constraint.type.ce = ce; + } + } + } +} +/* }}} */ + +static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ +{ + zend_basic_block *blocks = ssa->cfg.blocks; + zend_ssa_block *ssa_blocks = ssa->blocks; + zend_ssa_op *ssa_ops = ssa->ops; + int ssa_vars_count = ssa->vars_count; + int i, j; + zend_op *opline, *end; + int *tmp = NULL; + ALLOCA_FLAG(use_heap); + + // FIXME: Can we optimize this copying out in some cases? + if (blocks[n].next_child >= 0) { + tmp = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), use_heap); + memcpy(tmp, var, sizeof(int) * (op_array->last_var + op_array->T)); + var = tmp; + } + + if (ssa_blocks[n].phis) { + zend_ssa_phi *phi = ssa_blocks[n].phis; + do { + if (phi->ssa_var < 0) { + phi->ssa_var = ssa_vars_count; + var[phi->var] = ssa_vars_count; + ssa_vars_count++; + } else { + var[phi->var] = phi->ssa_var; + } + phi = phi->next; + } while (phi); + } + + opline = op_array->opcodes + blocks[n].start; + end = opline + blocks[n].len; + for (; opline < end; opline++) { + uint32_t k = opline - op_array->opcodes; + if (opline->opcode != ZEND_OP_DATA) { + zend_op *next = opline + 1; + if (next < end && next->opcode == ZEND_OP_DATA) { + if (next->op1_type == IS_CV) { + ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)]; + //USE_SSA_VAR(next->op1.var); + } else if (next->op1_type & (IS_VAR|IS_TMP_VAR)) { + ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)]; + //USE_SSA_VAR(op_array->last_var + next->op1.var); + } + if (next->op2_type == IS_CV) { + ssa_ops[k + 1].op2_use = var[EX_VAR_TO_NUM(next->op2.var)]; + //USE_SSA_VAR(next->op2.var); + } else if (next->op2_type & (IS_VAR|IS_TMP_VAR)) { + ssa_ops[k + 1].op2_use = var[EX_VAR_TO_NUM(next->op2.var)]; + //USE_SSA_VAR(op_array->last_var + next->op2.var); + } + } + if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(opline->op1.var)]; + //USE_SSA_VAR(op_array->last_var + opline->op1.var) + } + if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) { + if (opline->op2_type == IS_CV) { + ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)]; + } + ssa_ops[k].op2_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op2.var) + } else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { + ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)]; + //USE_SSA_VAR(op_array->last_var + opline->op2.var) + } + switch (opline->opcode) { + case ZEND_ASSIGN: + if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) { + ssa_ops[k].op2_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op2.var) + } + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_ASSIGN_REF: +//TODO: ??? + if (opline->op2_type == IS_CV) { + ssa_ops[k].op2_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op2.var) + } + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_BIND_GLOBAL: + case ZEND_BIND_STATIC: + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) { + ssa_ops[k + 1].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(next->op1.var) + } + break; + case ZEND_ADD_ARRAY_ELEMENT: + ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)]; + case ZEND_INIT_ARRAY: + if (((build_flags & ZEND_SSA_RC_INFERENCE) + || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) + && opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline+->op1.var) + } + break; + case ZEND_SEND_VAR: + case ZEND_CAST: + case ZEND_QM_ASSIGN: + case ZEND_JMP_SET: + case ZEND_COALESCE: + if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: + case ZEND_SEND_VAR_EX: + case ZEND_SEND_REF: + case ZEND_SEND_UNPACK: + case ZEND_FE_RESET_RW: +//TODO: ??? + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_FE_RESET_R: + if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + 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: + case ZEND_ASSIGN_POW: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_POST_INC: + case ZEND_POST_DEC: + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_UNSET_VAR: + if (opline->extended_value & ZEND_QUICK_SET) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + } + break; + case ZEND_UNSET_DIM: + case ZEND_UNSET_OBJ: + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: + case ZEND_FETCH_DIM_FUNC_ARG: + case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_OBJ_W: + case ZEND_FETCH_OBJ_RW: + case ZEND_FETCH_OBJ_FUNC_ARG: + case ZEND_FETCH_OBJ_UNSET: + if (opline->op1_type == IS_CV) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + case ZEND_BIND_LEXICAL: + if (opline->extended_value || (build_flags & ZEND_SSA_RC_INFERENCE)) { + ssa_ops[k].op2_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; + ssa_vars_count++; + } + break; + case ZEND_YIELD: + if (opline->op1_type == IS_CV + && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) + || (build_flags & ZEND_SSA_RC_INFERENCE))) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + } + break; + case ZEND_VERIFY_RETURN_TYPE: + if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) { + ssa_ops[k].op1_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->op1.var) + } + break; + default: + break; + } + if (opline->result_type == IS_CV) { + ssa_ops[k].result_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(opline->result.var) + } else if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { + ssa_ops[k].result_def = ssa_vars_count; + var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count; + ssa_vars_count++; + //NEW_SSA_VAR(op_array->last_var + opline->result.var) + } + } + } + + for (i = 0; i < 2; i++) { + int succ = blocks[n].successors[i]; + if (succ >= 0) { + zend_ssa_phi *p; + for (p = ssa_blocks[succ].phis; p; p = p->next) { + if (p->pi == n) { + /* e-SSA Pi */ + if (p->has_range_constraint) { + if (p->constraint.range.min_var >= 0) { + p->constraint.range.min_ssa_var = var[p->constraint.range.min_var]; + } + if (p->constraint.range.max_var >= 0) { + p->constraint.range.max_ssa_var = var[p->constraint.range.max_var]; + } + } + for (j = 0; j < blocks[succ].predecessors_count; j++) { + p->sources[j] = var[p->var]; + } + if (p->ssa_var < 0) { + p->ssa_var = ssa_vars_count; + ssa_vars_count++; + } + } else if (p->pi < 0) { + /* Normal Phi */ + for (j = 0; j < blocks[succ].predecessors_count; j++) + if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) { + break; + } + ZEND_ASSERT(j < blocks[succ].predecessors_count); + p->sources[j] = var[p->var]; + } + } + for (p = ssa_blocks[succ].phis; p && (p->pi >= 0); p = p->next) { + if (p->pi == n) { + zend_ssa_phi *q = p->next; + while (q) { + if (q->pi < 0 && q->var == p->var) { + for (j = 0; j < blocks[succ].predecessors_count; j++) { + if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) { + break; + } + } + ZEND_ASSERT(j < blocks[succ].predecessors_count); + q->sources[j] = p->ssa_var; + } + q = q->next; + } + } + } + } + } + + ssa->vars_count = ssa_vars_count; + + j = blocks[n].children; + while (j >= 0) { + // FIXME: Tail call optimization? + if (zend_ssa_rename(op_array, build_flags, ssa, var, j) != SUCCESS) + return FAILURE; + j = blocks[j].next_child; + } + + if (tmp) { + free_alloca(tmp, use_heap); + } + + return SUCCESS; +} +/* }}} */ + +int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags) /* {{{ */ +{ + zend_basic_block *blocks = ssa->cfg.blocks; + zend_ssa_block *ssa_blocks; + int blocks_count = ssa->cfg.blocks_count; + uint32_t set_size; + zend_bitset def, in, phi; + int *var = NULL; + int i, j, k, changed; + zend_dfg dfg; + ALLOCA_FLAG(dfg_use_heap) + ALLOCA_FLAG(var_use_heap) + + if ((blocks_count * (op_array->last_var + op_array->T)) > 4 * 1024 * 1024) { + /* Don't buld SSA for very big functions */ + return FAILURE; + } + + ssa->rt_constants = (build_flags & ZEND_RT_CONSTANTS); + ssa_blocks = zend_arena_calloc(arena, blocks_count, sizeof(zend_ssa_block)); + if (!ssa_blocks) { + return FAILURE; + } + ssa->blocks = ssa_blocks; + + /* Compute Variable Liveness */ + dfg.vars = op_array->last_var + op_array->T; + dfg.size = set_size = zend_bitset_len(dfg.vars); + dfg.tmp = do_alloca((set_size * sizeof(zend_ulong)) * (blocks_count * 4 + 1), dfg_use_heap); + memset(dfg.tmp, 0, (set_size * sizeof(zend_ulong)) * (blocks_count * 4 + 1)); + dfg.def = dfg.tmp + set_size; + dfg.use = dfg.def + set_size * blocks_count; + dfg.in = dfg.use + set_size * blocks_count; + dfg.out = dfg.in + set_size * blocks_count; + + if (zend_build_dfg(op_array, &ssa->cfg, &dfg, build_flags) != SUCCESS) { + free_alloca(dfg.tmp, dfg_use_heap); + return FAILURE; + } + + if (build_flags & ZEND_SSA_DEBUG_LIVENESS) { + zend_dump_dfg(op_array, &ssa->cfg, &dfg); + } + + def = dfg.def; + in = dfg.in; + + /* Reuse the "use" set, as we no longer need it */ + phi = dfg.use; + zend_bitset_clear(phi, set_size * blocks_count); + + /* Place e-SSA pis. This will add additional "def" points, so it must + * happen before def propagation. */ + place_essa_pis(arena, script, op_array, build_flags, ssa, &dfg); + + /* SSA construction, Step 1: Propagate "def" sets in merge points */ + do { + changed = 0; + for (j = 0; j < blocks_count; j++) { + zend_bitset def_j = def + j * set_size, phi_j = phi + j * set_size; + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + if (blocks[j].predecessors_count > 1) { + if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) { + /* Prevent any values from flowing into irreducible loops by + replacing all incoming values with explicit phis. The + register allocator depends on this property. */ + zend_bitset_union(phi_j, in + (j * set_size), set_size); + } else { + for (k = 0; k < blocks[j].predecessors_count; k++) { + i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; + while (i != -1 && i != blocks[j].idom) { + zend_bitset_union_with_intersection( + phi_j, phi_j, def + (i * set_size), in + (j * set_size), set_size); + i = blocks[i].idom; + } + } + } + if (!zend_bitset_subset(phi_j, def_j, set_size)) { + zend_bitset_union(def_j, phi_j, set_size); + changed = 1; + } + } + } + } while (changed); + + /* SSA construction, Step 2: Phi placement based on Dominance Frontiers */ + var = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), var_use_heap); + if (!var) { + free_alloca(dfg.tmp, dfg_use_heap); + return FAILURE; + } + + for (j = 0; j < blocks_count; j++) { + if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { + continue; + } + if (!zend_bitset_empty(phi + j * set_size, set_size)) { + ZEND_BITSET_REVERSE_FOREACH(phi + j * set_size, set_size, i) { + zend_ssa_phi *phi = zend_arena_calloc(arena, 1, + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)) + + ZEND_MM_ALIGNED_SIZE(sizeof(int) * blocks[j].predecessors_count) + + sizeof(void*) * blocks[j].predecessors_count); + + phi->sources = (int*)(((char*)phi) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi))); + memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); + phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->cfg.blocks[j].predecessors_count)); + + phi->pi = -1; + phi->var = i; + phi->ssa_var = -1; + + /* Place phis after pis */ + { + zend_ssa_phi **pp = &ssa_blocks[j].phis; + while (*pp) { + if ((*pp)->pi < 0) { + break; + } + pp = &(*pp)->next; + } + phi->next = *pp; + *pp = phi; + } + } ZEND_BITSET_FOREACH_END(); + } + } + + if (build_flags & ZEND_SSA_DEBUG_PHI_PLACEMENT) { + zend_dump_phi_placement(op_array, ssa); + } + + /* SSA construction, Step 3: Renaming */ + ssa->ops = zend_arena_calloc(arena, op_array->last, sizeof(zend_ssa_op)); + memset(ssa->ops, 0xff, op_array->last * sizeof(zend_ssa_op)); + memset(var + op_array->last_var, 0xff, op_array->T * sizeof(int)); + /* Create uninitialized SSA variables for each CV */ + for (j = 0; j < op_array->last_var; j++) { + var[j] = j; + } + ssa->vars_count = op_array->last_var; + if (zend_ssa_rename(op_array, build_flags, ssa, var, 0) != SUCCESS) { + free_alloca(var, var_use_heap); + free_alloca(dfg.tmp, dfg_use_heap); + return FAILURE; + } + + free_alloca(var, var_use_heap); + free_alloca(dfg.tmp, dfg_use_heap); + + return SUCCESS; +} +/* }}} */ + +int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */ +{ + zend_ssa_var *ssa_vars; + int i; + + if (!ssa->vars) { + ssa->vars = zend_arena_calloc(arena, ssa->vars_count, sizeof(zend_ssa_var)); + } + ssa_vars = ssa->vars; + + for (i = 0; i < op_array->last_var; i++) { + ssa_vars[i].var = i; + ssa_vars[i].scc = -1; + ssa_vars[i].definition = -1; + ssa_vars[i].use_chain = -1; + } + for (i = op_array->last_var; i < ssa->vars_count; i++) { + ssa_vars[i].var = -1; + ssa_vars[i].scc = -1; + ssa_vars[i].definition = -1; + ssa_vars[i].use_chain = -1; + } + + for (i = op_array->last - 1; i >= 0; i--) { + zend_ssa_op *op = ssa->ops + i; + + if (op->op1_use >= 0) { + op->op1_use_chain = ssa_vars[op->op1_use].use_chain; + ssa_vars[op->op1_use].use_chain = i; + } + if (op->op2_use >= 0 && op->op2_use != op->op1_use) { + op->op2_use_chain = ssa_vars[op->op2_use].use_chain; + ssa_vars[op->op2_use].use_chain = i; + } + if (op->result_use >= 0) { + op->res_use_chain = ssa_vars[op->result_use].use_chain; + ssa_vars[op->result_use].use_chain = i; + } + if (op->op1_def >= 0) { + ssa_vars[op->op1_def].var = EX_VAR_TO_NUM(op_array->opcodes[i].op1.var); + ssa_vars[op->op1_def].definition = i; + } + if (op->op2_def >= 0) { + ssa_vars[op->op2_def].var = EX_VAR_TO_NUM(op_array->opcodes[i].op2.var); + ssa_vars[op->op2_def].definition = i; + } + if (op->result_def >= 0) { + ssa_vars[op->result_def].var = EX_VAR_TO_NUM(op_array->opcodes[i].result.var); + ssa_vars[op->result_def].definition = i; + } + } + + for (i = 0; i < ssa->cfg.blocks_count; i++) { + zend_ssa_phi *phi = ssa->blocks[i].phis; + while (phi) { + phi->block = i; + ssa_vars[phi->ssa_var].var = phi->var; + ssa_vars[phi->ssa_var].definition_phi = phi; + if (phi->pi >= 0) { + if (phi->sources[0] >= 0) { + zend_ssa_phi *p = ssa_vars[phi->sources[0]].phi_use_chain; + while (p && p != phi) { + p = zend_ssa_next_use_phi(ssa, phi->sources[0], p); + } + if (!p) { + phi->use_chains[0] = ssa_vars[phi->sources[0]].phi_use_chain; + ssa_vars[phi->sources[0]].phi_use_chain = phi; + } + } + if (phi->has_range_constraint) { + /* min and max variables can't be used together */ + zend_ssa_range_constraint *constraint = &phi->constraint.range; + if (constraint->min_ssa_var >= 0) { + phi->sym_use_chain = ssa_vars[constraint->min_ssa_var].sym_use_chain; + ssa_vars[constraint->min_ssa_var].sym_use_chain = phi; + } else if (constraint->max_ssa_var >= 0) { + phi->sym_use_chain = ssa_vars[constraint->max_ssa_var].sym_use_chain; + ssa_vars[constraint->max_ssa_var].sym_use_chain = phi; + } + } + } else { + int j; + + for (j = 0; j < ssa->cfg.blocks[i].predecessors_count; j++) { + if (phi->sources[j] >= 0) { + zend_ssa_phi *p = ssa_vars[phi->sources[j]].phi_use_chain; + while (p && p != phi) { + p = zend_ssa_next_use_phi(ssa, phi->sources[j], p); + } + if (!p) { + phi->use_chains[j] = ssa_vars[phi->sources[j]].phi_use_chain; + ssa_vars[phi->sources[j]].phi_use_chain = phi; + } + } + } + } + phi = phi->next; + } + } + + return SUCCESS; +} +/* }}} */ + +int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) /* {{{ */ +{ + if (ssa->vars[var].use_chain == op) { + ssa->vars[var].use_chain = zend_ssa_next_use(ssa->ops, var, op); + return 1; + } else { + int use = ssa->vars[var].use_chain; + + while (use >= 0) { + if (ssa->ops[use].result_use == var) { + if (ssa->ops[use].res_use_chain == op) { + ssa->ops[use].res_use_chain = zend_ssa_next_use(ssa->ops, var, op); + return 1; + } else { + use = ssa->ops[use].res_use_chain; + } + } else if (ssa->ops[use].op1_use == var) { + if (ssa->ops[use].op1_use_chain == op) { + ssa->ops[use].op1_use_chain = zend_ssa_next_use(ssa->ops, var, op); + return 1; + } else { + use = ssa->ops[use].op1_use_chain; + } + } else if (ssa->ops[use].op2_use == var) { + if (ssa->ops[use].op2_use_chain == op) { + ssa->ops[use].op2_use_chain = zend_ssa_next_use(ssa->ops, var, op); + return 1; + } else { + use = ssa->ops[use].op2_use_chain; + } + } else { + break; + } + } + /* something wrong */ + ZEND_ASSERT(0); + return 0; + } +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_ssa.h b/ext/opcache/Optimizer/zend_ssa.h new file mode 100644 index 0000000000..5e03f8ba69 --- /dev/null +++ b/ext/opcache/Optimizer/zend_ssa.h @@ -0,0 +1,168 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, SSA - Static Single Assignment Form | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_SSA_H +#define ZEND_SSA_H + +#include "zend_optimizer.h" +#include "zend_cfg.h" + +typedef struct _zend_ssa_range { + zend_long min; + zend_long max; + zend_bool underflow; + zend_bool overflow; +} zend_ssa_range; + +typedef enum _zend_ssa_negative_lat { + NEG_NONE = 0, + NEG_INIT = 1, + NEG_INVARIANT = 2, + NEG_USE_LT = 3, + NEG_USE_GT = 4, + NEG_UNKNOWN = 5 +} zend_ssa_negative_lat; + +/* Special kind of SSA Phi function used in eSSA */ +typedef struct _zend_ssa_range_constraint { + zend_ssa_range range; /* simple range constraint */ + int min_var; + int max_var; + int min_ssa_var; /* ((min_var>0) ? MIN(ssa_var) : 0) + range.min */ + int max_ssa_var; /* ((max_var>0) ? MAX(ssa_var) : 0) + range.max */ + zend_ssa_negative_lat negative; +} zend_ssa_range_constraint; + +typedef struct _zend_ssa_type_constraint { + uint32_t type_mask; /* Type mask to intersect with */ + zend_class_entry *ce; /* Class entry for instanceof constraints */ +} zend_ssa_type_constraint; + +typedef union _zend_ssa_pi_constraint { + zend_ssa_range_constraint range; + zend_ssa_type_constraint type; +} zend_ssa_pi_constraint; + +/* SSA Phi - ssa_var = Phi(source0, source1, ...sourceN) */ +typedef struct _zend_ssa_phi zend_ssa_phi; +struct _zend_ssa_phi { + zend_ssa_phi *next; /* next Phi in the same BB */ + int pi; /* if >= 0 this is actually a e-SSA Pi */ + zend_ssa_pi_constraint constraint; /* e-SSA Pi constraint */ + int var; /* Original CV, VAR or TMP variable index */ + int ssa_var; /* SSA variable index */ + int block; /* current BB index */ + int visited : 1; /* flag to avoid recursive processing */ + int has_range_constraint : 1; + zend_ssa_phi **use_chains; + zend_ssa_phi *sym_use_chain; + int *sources; /* Array of SSA IDs that produce this var. + As many as this block has + predecessors. */ +}; + +typedef struct _zend_ssa_block { + zend_ssa_phi *phis; +} zend_ssa_block; + +typedef struct _zend_ssa_op { + int op1_use; + int op2_use; + int result_use; + int op1_def; + int op2_def; + int result_def; + int op1_use_chain; + int op2_use_chain; + int res_use_chain; +} zend_ssa_op; + +typedef struct _zend_ssa_var { + int var; /* original var number; op.var for CVs and following numbers for VARs and TMP_VARs */ + int scc; /* strongly connected component */ + int definition; /* opcode that defines this value */ + zend_ssa_phi *definition_phi; /* phi that defines this value */ + int use_chain; /* uses of this value, linked through opN_use_chain */ + zend_ssa_phi *phi_use_chain; /* uses of this value in Phi, linked through use_chain */ + zend_ssa_phi *sym_use_chain; /* uses of this value in Pi constaints */ + unsigned int no_val : 1; /* value doesn't mater (used as op1 in ZEND_ASSIGN) */ + unsigned int scc_entry : 1; +} zend_ssa_var; + +typedef struct _zend_ssa_var_info { + uint32_t type; /* inferred type (see zend_inference.h) */ + zend_ssa_range range; + zend_class_entry *ce; + unsigned int has_range : 1; + unsigned int is_instanceof : 1; /* 0 - class == "ce", 1 - may be child of "ce" */ + unsigned int recursive : 1; + unsigned int use_as_double : 1; +} zend_ssa_var_info; + +typedef struct _zend_ssa { + zend_cfg cfg; /* control flow graph */ + int rt_constants; /* run-time or compile-time */ + int vars_count; /* number of SSA variables */ + zend_ssa_block *blocks; /* array of SSA blocks */ + zend_ssa_op *ops; /* array of SSA instructions */ + zend_ssa_var *vars; /* use/def chain of SSA variables */ + int sccs; /* number of SCCs */ + zend_ssa_var_info *var_info; +} zend_ssa; + +BEGIN_EXTERN_C() + +int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags); +int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa); +int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var); + +END_EXTERN_C() + +static zend_always_inline int zend_ssa_next_use(const zend_ssa_op *ssa_op, int var, int use) +{ + ssa_op += use; + if (ssa_op->result_use == var) { + return ssa_op->res_use_chain; + } + return (ssa_op->op1_use == var) ? ssa_op->op1_use_chain : ssa_op->op2_use_chain; +} + +static zend_always_inline zend_ssa_phi* zend_ssa_next_use_phi(const zend_ssa *ssa, int var, const zend_ssa_phi *p) +{ + if (p->pi >= 0) { + return p->use_chains[0]; + } else { + int j; + for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) { + if (p->sources[j] == var) { + return p->use_chains[j]; + } + } + } + return NULL; +} + +#endif /* ZEND_SSA_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/Optimizer/zend_worklist.h b/ext/opcache/Optimizer/zend_worklist.h new file mode 100644 index 0000000000..73c0bca854 --- /dev/null +++ b/ext/opcache/Optimizer/zend_worklist.h @@ -0,0 +1,137 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2017 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: Andy Wingo <wingo@igalia.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id:$ */ + +#ifndef _ZEND_WORKLIST_H_ +#define _ZEND_WORKLIST_H_ + +#include "zend_arena.h" +#include "zend_bitset.h" + +typedef struct _zend_worklist_stack { + int *buf; + int len; + int capacity; +} zend_worklist_stack; + +#define ZEND_WORKLIST_STACK_ALLOCA(s, _len, use_heap) do { \ + (s)->buf = (int*)do_alloca(sizeof(int) * _len, use_heap); \ + (s)->len = 0; \ + (s)->capacity = _len; \ + } while (0) + +#define ZEND_WORKLIST_STACK_FREE_ALLOCA(s, use_heap) \ + free_alloca((s)->buf, use_heap) + +static inline int zend_worklist_stack_prepare(zend_arena **arena, zend_worklist_stack *stack, int len) +{ + ZEND_ASSERT(len >= 0); + + stack->buf = (int*)zend_arena_calloc(arena, sizeof(*stack->buf), len); + if (!stack->buf) { + return FAILURE; + } + stack->len = 0; + stack->capacity = len; + + return SUCCESS; +} + +static inline void zend_worklist_stack_push(zend_worklist_stack *stack, int i) +{ + ZEND_ASSERT(stack->len < stack->capacity); + stack->buf[stack->len++] = i; +} + +static inline int zend_worklist_stack_peek(zend_worklist_stack *stack) +{ + ZEND_ASSERT(stack->len); + return stack->buf[stack->len - 1]; +} + +static inline int zend_worklist_stack_pop(zend_worklist_stack *stack) +{ + ZEND_ASSERT(stack->len); + return stack->buf[--stack->len]; +} + +typedef struct _zend_worklist { + zend_bitset visited; + zend_worklist_stack stack; +} zend_worklist; + +#define ZEND_WORKLIST_ALLOCA(w, _len, use_heap) do { \ + (w)->stack.buf = (int*)do_alloca(ZEND_MM_ALIGNED_SIZE(sizeof(int) * _len) + sizeof(zend_ulong) * zend_bitset_len(_len), use_heap); \ + (w)->stack.len = 0; \ + (w)->stack.capacity = _len; \ + (w)->visited = (zend_bitset)((char*)(w)->stack.buf + ZEND_MM_ALIGNED_SIZE(sizeof(int) * _len)); \ + memset((w)->visited, 0, sizeof(zend_ulong) * zend_bitset_len(_len)); \ + } while (0) + +#define ZEND_WORKLIST_FREE_ALLOCA(w, use_heap) \ + free_alloca((w)->stack.buf, use_heap) + +static inline int zend_worklist_prepare(zend_arena **arena, zend_worklist *worklist, int len) +{ + ZEND_ASSERT(len >= 0); + worklist->visited = (zend_bitset)zend_arena_calloc(arena, sizeof(zend_ulong), zend_bitset_len(len)); + if (!worklist->visited) { + return FAILURE; + } + return zend_worklist_stack_prepare(arena, &worklist->stack, len); +} + +static inline int zend_worklist_len(zend_worklist *worklist) +{ + return worklist->stack.len; +} + +static inline int zend_worklist_push(zend_worklist *worklist, int i) +{ + ZEND_ASSERT(i >= 0 && i < worklist->stack.capacity); + + if (zend_bitset_in(worklist->visited, i)) { + return 0; + } + + zend_bitset_incl(worklist->visited, i); + zend_worklist_stack_push(&worklist->stack, i); + return 1; +} + +static inline int zend_worklist_peek(zend_worklist *worklist) +{ + return zend_worklist_stack_peek(&worklist->stack); +} + +static inline int zend_worklist_pop(zend_worklist *worklist) +{ + /* Does not clear visited flag */ + return zend_worklist_stack_pop(&worklist->stack); +} + +#endif /* _ZEND_WORKLIST_H_ */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 4c57e5c91e..c77b2a7f72 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -525,6 +525,9 @@ static void accel_use_shm_interned_strings(void) s[1] = 0; CG(one_char_string)[j] = accel_new_interned_string(zend_string_init(s, 1, 0)); } + for (j = 0; j < CG(known_strings_count); j++) { + CG(known_strings)[j] = accel_new_interned_string(CG(known_strings)[j]); + } /* function table hash keys */ for (idx = 0; idx < CG(function_table)->nNumUsed; idx++) { @@ -697,7 +700,7 @@ static inline int accel_is_inactive(void) if (ZCG(accel_directives).force_restart_timeout && ZCSG(force_restart_time) && time(NULL) >= ZCSG(force_restart_time)) { - zend_accel_error(ACCEL_LOG_WARNING, "Forced restart at %d (after %d seconds), locked by %d", time(NULL), ZCG(accel_directives).force_restart_timeout, mem_usage_check.l_pid); + zend_accel_error(ACCEL_LOG_WARNING, "Forced restart at %ld (after " ZEND_LONG_FMT " seconds), locked by %d", time(NULL), ZCG(accel_directives).force_restart_timeout, mem_usage_check.l_pid); kill_all_lockers(&mem_usage_check); return FAILURE; /* next request should be able to restart it */ @@ -896,17 +899,17 @@ static inline int do_validate_timestamps(zend_persistent_script *persistent_scri * See bug #15140 */ if (file_handle->opened_path) { - if (persistent_script->full_path != file_handle->opened_path && - (ZSTR_LEN(persistent_script->full_path) != ZSTR_LEN(file_handle->opened_path) || - memcmp(ZSTR_VAL(persistent_script->full_path), ZSTR_VAL(file_handle->opened_path), ZSTR_LEN(file_handle->opened_path)) != 0)) { + if (persistent_script->script.filename != file_handle->opened_path && + (ZSTR_LEN(persistent_script->script.filename) != ZSTR_LEN(file_handle->opened_path) || + memcmp(ZSTR_VAL(persistent_script->script.filename), ZSTR_VAL(file_handle->opened_path), ZSTR_LEN(file_handle->opened_path)) != 0)) { return FAILURE; } } else { full_path_ptr = accelerator_orig_zend_resolve_path(file_handle->filename, strlen(file_handle->filename)); if (full_path_ptr && - persistent_script->full_path != full_path_ptr && - (ZSTR_LEN(persistent_script->full_path) != ZSTR_LEN(full_path_ptr) || - memcmp(ZSTR_VAL(persistent_script->full_path), ZSTR_VAL(full_path_ptr), ZSTR_LEN(full_path_ptr)) != 0)) { + persistent_script->script.filename != full_path_ptr && + (ZSTR_LEN(persistent_script->script.filename) != ZSTR_LEN(full_path_ptr) || + memcmp(ZSTR_VAL(persistent_script->script.filename), ZSTR_VAL(full_path_ptr), ZSTR_LEN(full_path_ptr)) != 0)) { zend_string_release(full_path_ptr); return FAILURE; } @@ -934,8 +937,8 @@ static inline int do_validate_timestamps(zend_persistent_script *persistent_scri } ps_handle.type = ZEND_HANDLE_FILENAME; - ps_handle.filename = ZSTR_VAL(persistent_script->full_path); - ps_handle.opened_path = persistent_script->full_path; + ps_handle.filename = ZSTR_VAL(persistent_script->script.filename); + ps_handle.opened_path = persistent_script->script.filename; if (zend_get_file_handle_timestamp(&ps_handle, NULL) == persistent_script->timestamp) { return SUCCESS; @@ -1014,6 +1017,7 @@ char *accel_make_persistent_key(const char *path, int path_length, int *key_len) zend_string *str = accel_find_interned_string(cwd_str); if (!str) { + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); zend_shared_alloc_lock(); str = accel_new_interned_string(zend_string_copy(cwd_str)); @@ -1023,6 +1027,7 @@ char *accel_make_persistent_key(const char *path, int path_length, int *key_len) } zend_shared_alloc_unlock(); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } if (str) { char buf[32]; @@ -1054,6 +1059,7 @@ char *accel_make_persistent_key(const char *path, int path_length, int *key_len) zend_string *str = accel_find_interned_string(ZCG(include_path)); if (!str) { + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); zend_shared_alloc_lock(); str = accel_new_interned_string(zend_string_copy(ZCG(include_path))); @@ -1062,6 +1068,7 @@ char *accel_make_persistent_key(const char *path, int path_length, int *key_len) } zend_shared_alloc_unlock(); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } if (str) { char buf[32]; @@ -1159,6 +1166,7 @@ int zend_accel_invalidate(const char *filename, int filename_len, zend_bool forc if (force || !ZCG(accel_directives).validate_timestamps || do_validate_timestamps(persistent_script, &file_handle) == FAILURE) { + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); zend_shared_alloc_lock(); if (!persistent_script->corrupted) { @@ -1173,6 +1181,7 @@ int zend_accel_invalidate(const char *filename, int filename_len, zend_bool forc } zend_shared_alloc_unlock(); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } } @@ -1214,7 +1223,7 @@ static zend_persistent_script *cache_script_in_file_cache(zend_persistent_script return new_persistent_script; } - if (!zend_accel_script_optimize(new_persistent_script)) { + if (!zend_optimize_script(&new_persistent_script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level)) { return new_persistent_script; } @@ -1238,19 +1247,19 @@ static zend_persistent_script *cache_script_in_file_cache(zend_persistent_script zend_shared_alloc_destroy_xlat_table(); new_persistent_script->is_phar = - new_persistent_script->full_path && - strstr(ZSTR_VAL(new_persistent_script->full_path), ".phar") && - !strstr(ZSTR_VAL(new_persistent_script->full_path), "://"); + new_persistent_script->script.filename && + strstr(ZSTR_VAL(new_persistent_script->script.filename), ".phar") && + !strstr(ZSTR_VAL(new_persistent_script->script.filename), "://"); /* Consistency check */ if ((char*)new_persistent_script->mem + new_persistent_script->size != (char*)ZCG(mem)) { zend_accel_error( ((char*)new_persistent_script->mem + new_persistent_script->size < (char*)ZCG(mem)) ? ACCEL_LOG_ERROR : ACCEL_LOG_WARNING, - "Internal error: wrong size calculation: %s start=0x%08x, end=0x%08x, real=0x%08x\n", - ZSTR_VAL(new_persistent_script->full_path), - new_persistent_script->mem, - (char *)new_persistent_script->mem + new_persistent_script->size, - ZCG(mem)); + "Internal error: wrong size calculation: %s start=" ZEND_ADDR_FMT ", end=" ZEND_ADDR_FMT ", real=" ZEND_ADDR_FMT "\n", + ZSTR_VAL(new_persistent_script->script.filename), + (size_t)new_persistent_script->mem, + (size_t)((char *)new_persistent_script->mem + new_persistent_script->size), + (size_t)ZCG(mem)); } new_persistent_script->dynamic_members.checksum = zend_accel_script_checksum(new_persistent_script); @@ -1272,7 +1281,7 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr return new_persistent_script; } - if (!zend_accel_script_optimize(new_persistent_script)) { + if (!zend_optimize_script(&new_persistent_script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level)) { return new_persistent_script; } @@ -1290,7 +1299,7 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr /* Check if we still need to put the file into the cache (may be it was * already stored by another process. This final check is done under * exclusive lock) */ - bucket = zend_accel_hash_find_entry(&ZCSG(hash), new_persistent_script->full_path); + bucket = zend_accel_hash_find_entry(&ZCSG(hash), new_persistent_script->script.filename); if (bucket) { zend_persistent_script *existing_persistent_script = (zend_persistent_script *)bucket->data; @@ -1332,32 +1341,32 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr zend_shared_alloc_destroy_xlat_table(); new_persistent_script->is_phar = - new_persistent_script->full_path && - strstr(ZSTR_VAL(new_persistent_script->full_path), ".phar") && - !strstr(ZSTR_VAL(new_persistent_script->full_path), "://"); + new_persistent_script->script.filename && + strstr(ZSTR_VAL(new_persistent_script->script.filename), ".phar") && + !strstr(ZSTR_VAL(new_persistent_script->script.filename), "://"); /* Consistency check */ if ((char*)new_persistent_script->mem + new_persistent_script->size != (char*)ZCG(mem)) { zend_accel_error( ((char*)new_persistent_script->mem + new_persistent_script->size < (char*)ZCG(mem)) ? ACCEL_LOG_ERROR : ACCEL_LOG_WARNING, - "Internal error: wrong size calculation: %s start=0x%08x, end=0x%08x, real=0x%08x\n", - ZSTR_VAL(new_persistent_script->full_path), - new_persistent_script->mem, - (char *)new_persistent_script->mem + new_persistent_script->size, - ZCG(mem)); + "Internal error: wrong size calculation: %s start=" ZEND_ADDR_FMT ", end=" ZEND_ADDR_FMT ", real=" ZEND_ADDR_FMT "\n", + ZSTR_VAL(new_persistent_script->script.filename), + (size_t)new_persistent_script->mem, + (size_t)((char *)new_persistent_script->mem + new_persistent_script->size), + (size_t)ZCG(mem)); } new_persistent_script->dynamic_members.checksum = zend_accel_script_checksum(new_persistent_script); /* store script structure in the hash table */ - bucket = zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(new_persistent_script->full_path), ZSTR_LEN(new_persistent_script->full_path), 0, new_persistent_script); + bucket = zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(new_persistent_script->script.filename), ZSTR_LEN(new_persistent_script->script.filename), 0, new_persistent_script); if (bucket) { - zend_accel_error(ACCEL_LOG_INFO, "Cached script '%s'", ZSTR_VAL(new_persistent_script->full_path)); + zend_accel_error(ACCEL_LOG_INFO, "Cached script '%s'", ZSTR_VAL(new_persistent_script->script.filename)); if (key && /* key may contain non-persistent PHAR aliases (see issues #115 and #149) */ memcmp(key, "phar://", sizeof("phar://") - 1) != 0 && - (ZSTR_LEN(new_persistent_script->full_path) != key_length || - memcmp(ZSTR_VAL(new_persistent_script->full_path), key, key_length) != 0)) { + (ZSTR_LEN(new_persistent_script->script.filename) != key_length || + memcmp(ZSTR_VAL(new_persistent_script->script.filename), key, key_length) != 0)) { /* link key to the same persistent script in hash table */ if (zend_accel_hash_update(&ZCSG(hash), key, key_length, 1, bucket)) { zend_accel_error(ACCEL_LOG_INFO, "Added key '%s'", key); @@ -1446,7 +1455,7 @@ static void zend_accel_init_auto_globals(void) } } -static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handle, int type, char *key, unsigned int key_length, zend_op_array **op_array_p) +static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handle, int type, char *key, zend_op_array **op_array_p) { zend_persistent_script *new_persistent_script; zend_op_array *orig_active_op_array; @@ -1459,12 +1468,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl /* Try to open file */ if (file_handle->type == ZEND_HANDLE_FILENAME) { - if (accelerator_orig_zend_stream_open_function(file_handle->filename, file_handle) == SUCCESS) { - /* key may be changed by zend_stream_open_function() */ - if (key == ZCG(key)) { - key_length = ZCG(key_len); - } - } else { + if (accelerator_orig_zend_stream_open_function(file_handle->filename, file_handle) != SUCCESS) { *op_array_p = NULL; if (type == ZEND_REQUIRE) { zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename); @@ -1503,7 +1507,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl /* check if file is too new (may be it's not written completely yet) */ if (ZCG(accel_directives).file_update_protection && - (ZCG(request_time) - ZCG(accel_directives).file_update_protection < timestamp)) { + ((accel_time_t)(ZCG(request_time) - ZCG(accel_directives).file_update_protection) < timestamp)) { *op_array_p = accelerator_orig_compile_file(file_handle, type); return NULL; } @@ -1525,7 +1529,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl /* Override them with ours */ CG(function_table) = &ZCG(function_table); - EG(class_table) = CG(class_table) = &new_persistent_script->class_table; + EG(class_table) = CG(class_table) = &new_persistent_script->script.class_table; ZVAL_UNDEF(&EG(user_error_handler)); zend_try { @@ -1562,8 +1566,8 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl Here we aren't sure we would store it, but we will need it further anyway. */ - zend_accel_move_user_functions(&ZCG(function_table), &new_persistent_script->function_table); - new_persistent_script->main_op_array = *op_array; + zend_accel_move_user_functions(&ZCG(function_table), &new_persistent_script->script.function_table); + new_persistent_script->script.main_op_array = *op_array; efree(op_array); /* we have valid persistent_script, so it's safe to free op_array */ @@ -1584,11 +1588,11 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl } if (file_handle->opened_path) { - new_persistent_script->full_path = zend_string_copy(file_handle->opened_path); + new_persistent_script->script.filename = zend_string_copy(file_handle->opened_path); } else { - new_persistent_script->full_path = zend_string_init(file_handle->filename, strlen(file_handle->filename), 0); + new_persistent_script->script.filename = zend_string_init(file_handle->filename, strlen(file_handle->filename), 0); } - zend_string_hash_val(new_persistent_script->full_path); + zend_string_hash_val(new_persistent_script->script.filename); /* Now persistent_script structure is ready in process memory */ return new_persistent_script; @@ -1619,26 +1623,28 @@ zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type) } } + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); persistent_script = zend_file_cache_script_load(file_handle); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); if (persistent_script) { /* see bug #15471 (old BTS) */ - if (persistent_script->full_path) { + if (persistent_script->script.filename) { if (!EG(current_execute_data) || !EG(current_execute_data)->opline || !EG(current_execute_data)->func || !ZEND_USER_CODE(EG(current_execute_data)->func->common.type) || EG(current_execute_data)->opline->opcode != ZEND_INCLUDE_OR_EVAL || (EG(current_execute_data)->opline->extended_value != ZEND_INCLUDE_ONCE && EG(current_execute_data)->opline->extended_value != ZEND_REQUIRE_ONCE)) { - if (zend_hash_add_empty_element(&EG(included_files), persistent_script->full_path) != NULL) { + if (zend_hash_add_empty_element(&EG(included_files), persistent_script->script.filename) != NULL) { /* ext/phar has to load phar's metadata into memory */ if (persistent_script->is_phar) { php_stream_statbuf ssb; - char *fname = emalloc(sizeof("phar://") + ZSTR_LEN(persistent_script->full_path)); + char *fname = emalloc(sizeof("phar://") + ZSTR_LEN(persistent_script->script.filename)); memcpy(fname, "phar://", sizeof("phar://") - 1); - memcpy(fname + sizeof("phar://") - 1, ZSTR_VAL(persistent_script->full_path), ZSTR_LEN(persistent_script->full_path) + 1); + memcpy(fname + sizeof("phar://") - 1, ZSTR_VAL(persistent_script->script.filename), ZSTR_LEN(persistent_script->script.filename) + 1); php_stream_stat_path(fname, &ssb); efree(fname); } @@ -1654,7 +1660,7 @@ zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type) return zend_accel_load_script(persistent_script, 1); } - persistent_script = opcache_compile_file(file_handle, type, NULL, 0, &op_array); + persistent_script = opcache_compile_file(file_handle, type, NULL, &op_array); if (persistent_script) { from_memory = 0; @@ -1744,11 +1750,13 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) persistent_script = (zend_persistent_script *)bucket->data; if (key && !persistent_script->corrupted) { + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); zend_shared_alloc_lock(); zend_accel_add_key(key, key_length, bucket); zend_shared_alloc_unlock(); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } } } @@ -1783,7 +1791,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) if (EXPECTED(persistent_script != NULL) && UNEXPECTED(ZCG(accel_directives).validate_permission) && file_handle->type == ZEND_HANDLE_FILENAME && - UNEXPECTED(access(ZSTR_VAL(persistent_script->full_path), R_OK) != 0)) { + UNEXPECTED(access(ZSTR_VAL(persistent_script->script.filename), R_OK) != 0)) { if (type == ZEND_REQUIRE) { zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename); zend_bailout(); @@ -1793,6 +1801,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) return NULL; } + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); /* If script is found then validate_timestamps if option is enabled */ @@ -1821,8 +1830,8 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) unsigned int checksum = zend_accel_script_checksum(persistent_script); if (checksum != persistent_script->dynamic_members.checksum ) { /* The checksum is wrong */ - zend_accel_error(ACCEL_LOG_INFO, "Checksum failed for '%s': expected=0x%0.8X, found=0x%0.8X", - ZSTR_VAL(persistent_script->full_path), persistent_script->dynamic_members.checksum, checksum); + zend_accel_error(ACCEL_LOG_INFO, "Checksum failed for '%s': expected=0x%08x, found=0x%08x", + ZSTR_VAL(persistent_script->script.filename), persistent_script->dynamic_members.checksum, checksum); zend_shared_alloc_lock(); if (!persistent_script->corrupted) { persistent_script->corrupted = 1; @@ -1857,6 +1866,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) /* No memory left. Behave like without the Accelerator */ if (ZSMMG(memory_exhausted) || ZCSG(restart_pending)) { SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); return accelerator_orig_compile_file(file_handle, type); } @@ -1864,7 +1874,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) * If it isn't compile_and_cache_file() changes the flag to 0 */ from_shared_memory = 0; - persistent_script = opcache_compile_file(file_handle, type, key, key ? key_length : 0, &op_array); + persistent_script = opcache_compile_file(file_handle, type, key, &op_array); if (persistent_script) { persistent_script = cache_script_in_shared_memory(persistent_script, key, key ? key_length : 0, &from_shared_memory); } @@ -1874,6 +1884,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) */ if (!persistent_script) { SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); return op_array; } if (from_shared_memory) { @@ -1899,21 +1910,21 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) #endif /* see bug #15471 (old BTS) */ - if (persistent_script->full_path) { + if (persistent_script->script.filename) { if (!EG(current_execute_data) || !EG(current_execute_data)->opline || !EG(current_execute_data)->func || !ZEND_USER_CODE(EG(current_execute_data)->func->common.type) || EG(current_execute_data)->opline->opcode != ZEND_INCLUDE_OR_EVAL || (EG(current_execute_data)->opline->extended_value != ZEND_INCLUDE_ONCE && EG(current_execute_data)->opline->extended_value != ZEND_REQUIRE_ONCE)) { - if (zend_hash_add_empty_element(&EG(included_files), persistent_script->full_path) != NULL) { + if (zend_hash_add_empty_element(&EG(included_files), persistent_script->script.filename) != NULL) { /* ext/phar has to load phar's metadata into memory */ if (persistent_script->is_phar) { php_stream_statbuf ssb; - char *fname = emalloc(sizeof("phar://") + ZSTR_LEN(persistent_script->full_path)); + char *fname = emalloc(sizeof("phar://") + ZSTR_LEN(persistent_script->script.filename)); memcpy(fname, "phar://", sizeof("phar://") - 1); - memcpy(fname + sizeof("phar://") - 1, ZSTR_VAL(persistent_script->full_path), ZSTR_LEN(persistent_script->full_path) + 1); + memcpy(fname + sizeof("phar://") - 1, ZSTR_VAL(persistent_script->script.filename), ZSTR_LEN(persistent_script->script.filename) + 1); php_stream_stat_path(fname, &ssb); efree(fname); } @@ -1927,6 +1938,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) persistent_script->dynamic_members.last_used = ZCG(request_time); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); /* Fetch jit auto globals used in the script before execution */ if (persistent_script->ping_auto_globals_mask) { @@ -1952,7 +1964,7 @@ static int persistent_stream_open_function(const char *filename, zend_file_handl /* we are in include_once or FastCGI request */ handle->filename = (char*)filename; handle->free_filename = 0; - handle->opened_path = zend_string_copy(ZCG(cache_persistent_script)->full_path); + handle->opened_path = zend_string_copy(ZCG(cache_persistent_script)->script.filename); handle->type = ZEND_HANDLE_FILENAME; return SUCCESS; } @@ -1994,7 +2006,7 @@ static zend_string* persistent_zend_resolve_path(const char *filename, int filen if (!persistent_script->corrupted) { ZCG(cache_opline) = EG(current_execute_data) ? EG(current_execute_data)->opline : NULL; ZCG(cache_persistent_script) = persistent_script; - return zend_string_copy(persistent_script->full_path); + return zend_string_copy(persistent_script->script.filename); } } } else { @@ -2015,11 +2027,13 @@ static zend_string* persistent_zend_resolve_path(const char *filename, int filen if (!persistent_script->corrupted) { if (key) { /* add another "key" for the same bucket */ + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); zend_shared_alloc_lock(); zend_accel_add_key(key, key_length, bucket); zend_shared_alloc_unlock(); SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } else { ZCG(key_len) = 0; } @@ -2086,7 +2100,7 @@ static void accel_activate(void) ZCG(include_path_check) = 1; /* check if ZCG(function_table) wasn't somehow polluted on the way */ - if (ZCG(internal_functions_count) != zend_hash_num_elements(&ZCG(function_table))) { + if (ZCG(internal_functions_count) != (zend_long)zend_hash_num_elements(&ZCG(function_table))) { zend_accel_error(ACCEL_LOG_WARNING, "Internal functions count changed - was %d, now %d", ZCG(internal_functions_count), zend_hash_num_elements(&ZCG(function_table))); } @@ -2123,6 +2137,7 @@ static void accel_activate(void) } #endif + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); if (ZCG(counted)) { @@ -2179,6 +2194,7 @@ static void accel_activate(void) } SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); if (ZCSG(last_restart_time) != ZCG(last_restart_time)) { /* SHM was reinitialized. */ @@ -2497,7 +2513,7 @@ static int zend_accel_init_shm(void) ZCSG(interned_strings_start) = ZCSG(interned_strings_end) = NULL; # ifndef ZTS - zend_hash_init(&ZCSG(interned_strings), (ZCG(accel_directives).interned_strings_buffer * 1024 * 1024) / (sizeof(Bucket) + sizeof(Bucket*) + 8 /* average string length */), NULL, NULL, 1); + zend_hash_init(&ZCSG(interned_strings), (ZCG(accel_directives).interned_strings_buffer * 1024 * 1024) / _ZSTR_STRUCT_SIZE(8 /* average string length */), NULL, NULL, 1); if (ZCG(accel_directives).interned_strings_buffer) { void *data; @@ -2883,6 +2899,8 @@ file_cache_fallback: zend_accel_blacklist_load(&accel_blacklist, ZCG(accel_directives.user_blacklist_filename)); } + zend_optimizer_startup(); + return SUCCESS; } @@ -2900,6 +2918,8 @@ void accel_shutdown(void) zend_ini_entry *ini_entry; zend_bool file_cache_only = 0; + zend_optimizer_shutdown(); + zend_accel_blacklist_shutdown(&accel_blacklist); if (!ZCG(enabled) || !accel_startup_ok) { @@ -2953,6 +2973,7 @@ void zend_accel_schedule_restart(zend_accel_restart_reason reason) zend_accel_error(ACCEL_LOG_DEBUG, "Restart Scheduled! Reason: %s", zend_accel_restart_reason_text[reason]); + HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); ZCSG(restart_pending) = 1; ZCSG(restart_reason) = reason; @@ -2965,6 +2986,7 @@ void zend_accel_schedule_restart(zend_accel_restart_reason reason) ZCSG(force_restart_time) = 0; } SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } /* this is needed because on WIN32 lock is not decreased unless ZCG(counted) is set */ diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 8f2349a010..6faa263db2 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -75,7 +75,8 @@ #ifdef ZEND_WIN32 # ifndef MAXPATHLEN -# define MAXPATHLEN _MAX_PATH +# include "win32/ioutil.h" +# define MAXPATHLEN PHP_WIN32_IOUTIL_MAXPATHLEN # endif # include <direct.h> #else @@ -85,14 +86,6 @@ # include <sys/param.h> #endif -#define PHP_5_0_X_API_NO 220040412 -#define PHP_5_1_X_API_NO 220051025 -#define PHP_5_2_X_API_NO 220060519 -#define PHP_5_3_X_API_NO 220090626 -#define PHP_5_4_X_API_NO 220100525 -#define PHP_5_5_X_API_NO 220121212 -#define PHP_5_6_X_API_NO 220131226 - /*** file locking ***/ #ifndef ZEND_WIN32 extern int lock_file; @@ -125,18 +118,6 @@ extern int lock_file; # endif #endif -#define PZ_REFCOUNT_P(pz) (pz)->refcount__gc -#define PZ_SET_REFCOUNT_P(pz, v) (pz)->refcount__gc = (v) -#define PZ_ADDREF_P(pz) ++((pz)->refcount__gc) -#define PZ_DELREF_P(pz) --((pz)->refcount__gc) -#define PZ_ISREF_P(pz) (pz)->is_ref__gc -#define PZ_SET_ISREF_P(pz) Z_SET_ISREF_TO_P(pz, 1) -#define PZ_UNSET_ISREF_P(pz) Z_SET_ISREF_TO_P(pz, 0) -#define PZ_SET_ISREF_TO_P(pz, isref) (pz)->is_ref__gc = (isref) - -#define DO_ALLOCA(x) do_alloca(x, use_heap) -#define FREE_ALLOCA(x) free_alloca(x, use_heap) - #if defined(HAVE_OPCACHE_FILE_CACHE) && defined(ZEND_WIN32) # define ENABLE_FILE_CACHE_FALLBACK 1 #endif @@ -154,11 +135,8 @@ typedef enum _zend_accel_restart_reason { } zend_accel_restart_reason; typedef struct _zend_persistent_script { - zend_string *full_path; /* full real path with resolved symlinks */ - zend_op_array main_op_array; - HashTable function_table; - HashTable class_table; - zend_long compiler_halt_offset; /* position of __HALT_COMPILER or -1 */ + zend_script script; + zend_long compiler_halt_offset; /* position of __HALT_COMPILER or -1 */ int ping_auto_globals_mask; /* which autoglobals are used by the script */ accel_time_t timestamp; /* the script modification time */ zend_bool corrupted; @@ -216,6 +194,7 @@ typedef struct _zend_accel_directives { zend_long log_verbosity_level; zend_long optimization_level; + zend_long opt_debug_level; zend_long max_file_size; zend_long interned_strings_buffer; char *restrict_api; @@ -333,37 +312,15 @@ accel_time_t zend_get_file_handle_timestamp(zend_file_handle *file_handle, size_ int validate_timestamp_and_record(zend_persistent_script *persistent_script, zend_file_handle *file_handle); int validate_timestamp_and_record_ex(zend_persistent_script *persistent_script, zend_file_handle *file_handle); int zend_accel_invalidate(const char *filename, int filename_len, zend_bool force); -int zend_accel_script_optimize(zend_persistent_script *persistent_script); int accelerator_shm_read_lock(void); void accelerator_shm_read_unlock(void); char *accel_make_persistent_key(const char *path, int path_length, int *key_len); zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type); -#if !defined(ZEND_DECLARE_INHERITED_CLASS_DELAYED) -# define ZEND_DECLARE_INHERITED_CLASS_DELAYED 145 -#endif - -#define ZEND_DECLARE_INHERITED_CLASS_DELAYED_FLAG 0x80 - #define IS_ACCEL_INTERNED(str) \ ((char*)(str) >= ZCSG(interned_strings_start) && (char*)(str) < ZCSG(interned_strings_end)) zend_string *accel_new_interned_string(zend_string *str); -# define ZEND_RESULT_TYPE(opline) (opline)->result_type -# define ZEND_RESULT(opline) (opline)->result -# define ZEND_OP1_TYPE(opline) (opline)->op1_type -# define ZEND_OP1(opline) (opline)->op1 -# define ZEND_OP1_CONST(opline) (*(opline)->op1.zv) -# define ZEND_OP1_LITERAL(opline) (op_array)->literals[(opline)->op1.constant] -# define ZEND_OP2_TYPE(opline) (opline)->op2_type -# define ZEND_OP2(opline) (opline)->op2 -# define ZEND_OP2_CONST(opline) (*(opline)->op2.zv) -# define ZEND_OP2_LITERAL(opline) (op_array)->literals[(opline)->op2.constant] -# define ZEND_DONE_PASS_TWO(op_array) (((op_array)->fn_flags & ZEND_ACC_DONE_PASS_TWO) != 0) -# define ZEND_CE_FILENAME(ce) (ce)->info.user.filename -# define ZEND_CE_DOC_COMMENT(ce) (ce)->info.user.doc_comment -# define ZEND_CE_DOC_COMMENT_LEN(ce) (ce)->info.user.doc_comment_len - #endif /* ZEND_ACCELERATOR_H */ diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index fbb9b21c94..ded7f3dab2 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -402,7 +402,15 @@ fi Optimizer/block_pass.c \ Optimizer/optimize_temp_vars_5.c \ Optimizer/nop_removal.c \ - Optimizer/compact_literals.c, + Optimizer/compact_literals.c \ + Optimizer/zend_cfg.c \ + Optimizer/zend_dfg.c \ + Optimizer/dfa_pass.c \ + Optimizer/zend_ssa.c \ + Optimizer/zend_inference.c \ + Optimizer/zend_func_info.c \ + Optimizer/zend_call_graph.c \ + Optimizer/zend_dump.c, shared,,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1,,yes) PHP_ADD_BUILD_DIR([$ext_builddir/Optimizer], 1) diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32 index ba6fd621bd..35c4645620 100644 --- a/ext/opcache/config.w32 +++ b/ext/opcache/config.w32 @@ -23,7 +23,7 @@ if (PHP_OPCACHE != "no") { zend_shared_alloc.c \ shared_alloc_win32.c", true, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); - ADD_SOURCES(configure_module_dirname + "/Optimizer", "zend_optimizer.c pass1_5.c pass2.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c", "opcache", "OptimizerObj"); + ADD_SOURCES(configure_module_dirname + "/Optimizer", "zend_optimizer.c pass1_5.c pass2.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c", "opcache", "OptimizerObj"); ADD_FLAG('CFLAGS_OPCACHE', "/I " + configure_module_dirname); diff --git a/ext/opcache/shared_alloc_win32.c b/ext/opcache/shared_alloc_win32.c index a0398b2d90..13a4cf5e8c 100644 --- a/ext/opcache/shared_alloc_win32.c +++ b/ext/opcache/shared_alloc_win32.c @@ -77,28 +77,38 @@ static void zend_win_error_message(int type, char *msg, int err) static char *create_name_with_username(char *name) { static char newname[MAXPATHLEN + UNLEN + 4 + 1 + 32]; - char uname[UNLEN + 1]; - DWORD unsize = UNLEN; + char *uname; - GetUserName(uname, &unsize); + uname = php_win32_get_username(); + if (!uname) { + return NULL; + } snprintf(newname, sizeof(newname) - 1, "%s@%s@%.32s", name, uname, ZCG(system_id)); + + free(uname); + return newname; } static char *get_mmap_base_file(void) { static char windir[MAXPATHLEN+UNLEN + 3 + sizeof("\\\\@") + 1 + 32]; - char uname[UNLEN + 1]; - DWORD unsize = UNLEN; + char *uname; int l; + uname = php_win32_get_username(); + if (!uname) { + return NULL; + } GetTempPath(MAXPATHLEN, windir); - GetUserName(uname, &unsize); l = strlen(windir); if ('\\' == windir[l-1]) { l--; } snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s@%.32s", ACCEL_FILEMAP_BASE, uname, ZCG(system_id)); + + free(uname); + return windir; } diff --git a/ext/opcache/tests/blacklist-win32.phpt b/ext/opcache/tests/blacklist-win32.phpt index 1e479b6c2e..fab0698f7f 100644 --- a/ext/opcache/tests/blacklist-win32.phpt +++ b/ext/opcache/tests/blacklist-win32.phpt @@ -18,7 +18,7 @@ $conf[4] = preg_replace("!^\\Q".dirname(__FILE__)."\\E!", "__DIR__", $conf[4]); print_r($conf); include("blacklist.inc"); $status = opcache_get_status(); -print_r(count($status['scripts'])); +print_r(count($status['scripts']) > 0); ?> --EXPECTF-- Array diff --git a/ext/opcache/tests/block_pass_001.phpt b/ext/opcache/tests/block_pass_001.phpt new file mode 100644 index 0000000000..556e76f543 --- /dev/null +++ b/ext/opcache/tests/block_pass_001.phpt @@ -0,0 +1,10 @@ +--TEST-- +Block pass: Bugs in BOOL/QM_ASSIGN elision +--FILE-- +<?php +(bool) (true ? $x : $y); +(bool) (true ? new stdClass : null); +(bool) new stdClass; +?> +--EXPECTF-- +Notice: Undefined variable: x in %s on line %d diff --git a/ext/opcache/tests/bug71127.phpt b/ext/opcache/tests/bug71127.phpt index 5770aea1fb..0c606097fe 100644 --- a/ext/opcache/tests/bug71127.phpt +++ b/ext/opcache/tests/bug71127.phpt @@ -3,7 +3,7 @@ Bug #71127 (Define in auto_prepend_file is overwrite) --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.optimization_level=0xFFFFBFFF +opcache.optimization_level=0x7FFFBFFF --SKIPIF-- <?php if (!extension_loaded('Zend OPcache')) die("skip"); ?> --FILE-- diff --git a/ext/opcache/tests/bug71843.phpt b/ext/opcache/tests/bug71843.phpt index 32af61bf74..73301a6e96 100644 --- a/ext/opcache/tests/bug71843.phpt +++ b/ext/opcache/tests/bug71843.phpt @@ -15,7 +15,11 @@ okey --EXPECTF-- Notice: Use of undefined constant E - assumed 'E' in %sbug71843.php on line %d +Warning: A non-numeric value encountered in %s on line %d + Notice: Use of undefined constant R - assumed 'R' in %sbug71843.php on line %d +Warning: A non-numeric value encountered in %s on line %d + Notice: Use of undefined constant See - assumed 'See' in %sbug71843.php on line %d okey diff --git a/ext/opcache/tests/bug72762.phpt b/ext/opcache/tests/bug72762.phpt new file mode 100644 index 0000000000..8ce98bf438 --- /dev/null +++ b/ext/opcache/tests/bug72762.phpt @@ -0,0 +1,23 @@ +--TEST-- +Bug #72762: Infinite loop while parsing a file with opcache enabled +--FILE-- +<?php + +class foo +{ + function bar() + { + $b = array(); + + foreach ($a as $a) { + foreach ($b as $k => $v) { + } + $b[$k] = $v; + } + } +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/ext/opcache/tests/bug73583.phpt b/ext/opcache/tests/bug73583.phpt new file mode 100644 index 0000000000..e947b451c9 --- /dev/null +++ b/ext/opcache/tests/bug73583.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #73583 (Segfaults when conditionally declared class and function have the same name) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0x4ff +opcache.file_update_protection=0 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +if (true) { + class A { } + function A() { } + function A() { } +} +?> +--EXPECTF-- +Fatal error: Cannot redeclare A() (previously declared in %sbug73583.php:4) in %sbug73583.php on line 5 diff --git a/ext/opcache/tests/bug73654.phpt b/ext/opcache/tests/bug73654.phpt new file mode 100644 index 0000000000..164e10829c --- /dev/null +++ b/ext/opcache/tests/bug73654.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #73654: Segmentation fault in zend_call_function +--FILE-- +<?php +echo xyz(); + +function x () : string { + return 'x'; +} + +function xyz() : string { + return x().'yz'; +} + +?> +--EXPECT-- +xyz diff --git a/ext/opcache/tests/bug73668.phpt b/ext/opcache/tests/bug73668.phpt new file mode 100644 index 0000000000..aac5c9e65c --- /dev/null +++ b/ext/opcache/tests/bug73668.phpt @@ -0,0 +1,8 @@ +--TEST-- +Bug #73668: "SIGFPE Arithmetic exception" in opcache when divide by minus 1 +--FILE-- +<?php +$a/-1; +?> +--EXPECTF-- +Notice: Undefined variable: a in %s on line %d diff --git a/ext/opcache/tests/bug73746.phpt b/ext/opcache/tests/bug73746.phpt new file mode 100644 index 0000000000..c97833abcc --- /dev/null +++ b/ext/opcache/tests/bug73746.phpt @@ -0,0 +1,28 @@ +--TEST-- +Bug #73746 (Method that returns string returns UNKNOWN:0 instead) +--FILE-- +<?php +namespace Core\Bundle\Service\Property\Room\Rooms; + +class CountryMapping +{ + const CZ = 'CZ'; + const EN = 'EN'; + + public function get(string $countryIsoCode = null) : string // Works correctly if return type is removed + { + switch (strtoupper($countryIsoCode)) { + case 'CZ': + case 'SK': + return self::CZ; // Works correctly if changed to CountryMapping::CZ + default: + return self::EN; // Works correctly if changed to CountryMapping::EN + } + } +} + +$mapping = new CountryMapping(); +var_dump($mapping->get('CZ')); +?> +--EXPECT-- +string(2) "CZ" diff --git a/ext/opcache/tests/bug73789.phpt b/ext/opcache/tests/bug73789.phpt new file mode 100644 index 0000000000..142d5229f9 --- /dev/null +++ b/ext/opcache/tests/bug73789.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug #73789 (Strange behavior of class constants in switch/case block) +--FILE-- +<?php +class Lexer +{ + const T_NONE = 1; + const T_STRING = 2; + const T_DOT = 8; + public function getType($value): int + { + $type = self::T_NONE; + switch (true) { + case ctype_alpha($value[0]): + $name = 'Lexer::T_' . strtoupper($value); + $type = constant($name); + if ($type > 100) { + return $type; + } + return self::T_STRING; + case $value === '.': + return self::T_DOT; + default: + } + return $type; + } +} +var_dump((new Lexer())->getType("dot")); +--EXPECT-- +int(2) diff --git a/ext/opcache/tests/bug73847.phpt b/ext/opcache/tests/bug73847.phpt new file mode 100644 index 0000000000..7010dfbfb7 --- /dev/null +++ b/ext/opcache/tests/bug73847.phpt @@ -0,0 +1,44 @@ +--TEST-- +Bug #73847: Recursion when a variable is redefined as array +--FILE-- +<?php +function test() { + $a = 42; + $a = array($a); + var_dump($a); + + $a = 42; + $a = array($a => 24); + var_dump($a); + + $a = 42; + $a = array($a, 24); + var_dump($a); + + $a = 42; + $a = array(24, $a); + var_dump($a); +} +test(); +?> +--EXPECT-- +array(1) { + [0]=> + int(42) +} +array(1) { + [42]=> + int(24) +} +array(2) { + [0]=> + int(42) + [1]=> + int(24) +} +array(2) { + [0]=> + int(24) + [1]=> + int(42) +} diff --git a/ext/opcache/tests/bug74431.phpt b/ext/opcache/tests/bug74431.phpt new file mode 100644 index 0000000000..40948bac21 --- /dev/null +++ b/ext/opcache/tests/bug74431.phpt @@ -0,0 +1,28 @@ +--TEST-- +Bug #74431 - foreach infinite loop +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0xffffffff +--FILE-- +<?php +function test(){ + $arr = [1,2]; + $j = 0; + $cond = true; + foreach ($arr as $i => $v){ + while(1){ + if($cond){ + break; + } + } + $j++; + echo $j."\n"; + if ($j>10) break; + } +} +test(); +?> +--EXPECT-- +1 +2 diff --git a/ext/opcache/tests/bug74442.phpt b/ext/opcache/tests/bug74442.phpt new file mode 100644 index 0000000000..a4ad8e0540 --- /dev/null +++ b/ext/opcache/tests/bug74442.phpt @@ -0,0 +1,39 @@ +--TEST-- +Bug #74442: Opcached version produces a nested array +--CREDITS-- +Eric Norris <erictnorris@gmail.com> +--FILE-- +<?php +class Schema_Base { + public function addField($typeclass, array $params = null) { + $field = new $typeclass($params); + return $field; + } +} + +class Field_Base { + public function __construct(array $params = null) { + if (! is_array($params)) { + $params = (array) $params; + } + call_user_func_array(array($this, 'acceptParams'), $params); + } +} + +class Field_Integer extends Field_Base { + protected function acceptParams($bytes = 4) { + echo print_r($bytes, true); + } +} + +try { + $schema = new Schema_Base; + $schema->addField('Field_Integer'); +} catch (Throwable $ex) { + echo "CAUGHT EXCEPTION"; + echo (string)$ex; +} + +?> +--EXPECT-- +4 diff --git a/ext/opcache/tests/bug74456.phpt b/ext/opcache/tests/bug74456.phpt new file mode 100644 index 0000000000..9c9a286e2f --- /dev/null +++ b/ext/opcache/tests/bug74456.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #74456 (Segmentation error while running a script in CLI mode) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php + + +function small_numbers() { + return [0,1,2]; +} + +list ($zero, $one, $two) = small_numbers(); + +var_dump($zero, $one, $two); +?> +--EXPECT-- +int(0) +int(1) +int(2) diff --git a/ext/opcache/tests/bug74623.phpt b/ext/opcache/tests/bug74623.phpt new file mode 100644 index 0000000000..4cd0b26135 --- /dev/null +++ b/ext/opcache/tests/bug74623.phpt @@ -0,0 +1,22 @@ +--TEST-- +Bug #74623: Infinite loop in type inference when using HTMLPurifier +--FILE-- +<?php + +function crash($arr) { + $current_item = false; + + foreach($arr as $item) { + if($item->name === 'string') { + $current_item = $item; + } else { + $current_item->a[] = ''; + } + } + +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/ext/opcache/tests/issue0140.phpt b/ext/opcache/tests/issue0140.phpt index 98e0e45cc2..97fc11b3c7 100644 --- a/ext/opcache/tests/issue0140.phpt +++ b/ext/opcache/tests/issue0140.phpt @@ -8,6 +8,7 @@ opcache.file_update_protection=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> <?php if (php_sapi_name() != "cli") die("skip CLI only"); ?> +<?php if (getenv("SKIP_SLOW_TESTS")) die("skip slow tests excluded by request") ?> --FILE-- <?php define("FILENAME", dirname(__FILE__) . "/issuer0140.inc.php"); diff --git a/ext/opcache/tests/ssa_bug_001.phpt b/ext/opcache/tests/ssa_bug_001.phpt new file mode 100644 index 0000000000..56757f56a4 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +SSA constrution for CFG with unreachable basic blocks +--FILE-- +<?php +class X { + public function __get($n) { + if ($n === 'type') { + trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE); + switch (get_class($this)) { + case 'HTMLPurifier_Token_Start': return 'start'; + default: return null; + } + } + } +} +?> +OK +--EXPECT-- +OK diff --git a/ext/opcache/tests/ssa_bug_002.phpt b/ext/opcache/tests/ssa_bug_002.phpt new file mode 100644 index 0000000000..9ff6f799f4 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Incorrect NOP removal on jump to NOP +--FILE-- +<?php + +function test(int $i) : int { + if ($i == 1) { + $x = $i + 1; + } + return $i; +} +var_dump(test(42)); + +?> +--EXPECT-- +int(42) diff --git a/ext/opcache/tests/ssa_bug_003.phpt b/ext/opcache/tests/ssa_bug_003.phpt new file mode 100644 index 0000000000..2895551c08 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_003.phpt @@ -0,0 +1,38 @@ +--TEST-- +Incorrect elision of return type checks +--FILE-- +<?php + +function test1($x) : callable { + if ($x == 1) { + $c = 'foo'; + } elseif ($x == 2) { + $c = new stdClass; + } else { + $c = [$x => &$x]; + } + return $c; +} + +try { + test1(1); +} catch (Error $e) { + echo "Error: {$e->getMessage()}\n"; +} + +class Foo {} +function test2() : Foo { + $obj = new stdClass; + return $obj; +} + +try { + test2(); +} catch (Error $e) { + echo "Error: {$e->getMessage()}\n"; +} + +?> +--EXPECT-- +Error: Return value of test1() must be callable, string returned +Error: Return value of test2() must be an instance of Foo, instance of stdClass returned diff --git a/ext/opcache/tests/ssa_bug_004.phpt b/ext/opcache/tests/ssa_bug_004.phpt new file mode 100644 index 0000000000..20e313045a --- /dev/null +++ b/ext/opcache/tests/ssa_bug_004.phpt @@ -0,0 +1,19 @@ +--TEST-- +Assign elision exception safety: ICALL +--FILE-- +<?php + +set_error_handler(function() { throw new Exception; }); + +function test() { + $x = str_replace(['foo'], [[]], ['foo']); +} +try { + test(); +} catch (Exception $e) { + echo "caught\n"; +} + +?> +--EXPECT-- +caught diff --git a/ext/opcache/tests/ssa_bug_005.phpt b/ext/opcache/tests/ssa_bug_005.phpt new file mode 100644 index 0000000000..fb373e36b3 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_005.phpt @@ -0,0 +1,24 @@ +--TEST-- +Assign elision exception safety: UCALL +--FILE-- +<?php + +function test() { + $dtor = new class { function __destruct() { throw new Exception; } }; + $a = 1; + return [0, $a]; +} + +function test2() { + $x = test(); +} + +try { + test2(); +} catch (Exception $e) { + echo "caught\n"; +} + +?> +--EXPECT-- +caught diff --git a/ext/opcache/tests/ssa_bug_006.phpt b/ext/opcache/tests/ssa_bug_006.phpt new file mode 100644 index 0000000000..624c1e0047 --- /dev/null +++ b/ext/opcache/tests/ssa_bug_006.phpt @@ -0,0 +1,20 @@ +--TEST-- +Incorrect optimization of $i = $i++ +--FILE-- +<?php + +function test() { + $i = 1; + $i = $i++; + var_dump($i); + + $i = 1; + $i = $i--; + var_dump($i); +} +test(); + +?> +--EXPECT-- +int(1) +int(1) diff --git a/ext/opcache/tests/wrong_inlining_001.phpt b/ext/opcache/tests/wrong_inlining_001.phpt new file mode 100644 index 0000000000..5cc515c4af --- /dev/null +++ b/ext/opcache/tests/wrong_inlining_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +Pass result of inlined function by reference +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function get_const() { + return 42; +} + +function test() { + foo(get_const()); +} + +if (true) { + function foo(&$ref) {} +} + +test(); +?> +OK +--EXPECTF-- +Notice: Only variables should be passed by reference in %swrong_inlining_001.php on line 7 +OK
\ No newline at end of file diff --git a/ext/opcache/tests/wrong_inlining_002.phpt b/ext/opcache/tests/wrong_inlining_002.phpt new file mode 100644 index 0000000000..4e71a96d10 --- /dev/null +++ b/ext/opcache/tests/wrong_inlining_002.phpt @@ -0,0 +1,29 @@ +--TEST-- +$this not in object context +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +class Foo { + private function getConst() { + return 42; + } + public function test() { + var_dump($this->getConst()); + } +} + +Foo::test(); +?> +--EXPECTF-- +Deprecated: Non-static method Foo::test() should not be called statically in %swrong_inlining_002.php on line 11 + +Fatal error: Uncaught Error: Using $this when not in object context in %swrong_inlining_002.php:7 +Stack trace: +#0 %swrong_inlining_002.php(11): Foo::test() +#1 {main} + thrown in %swrong_inlining_002.php on line 7 diff --git a/ext/opcache/tests/wrong_inlining_003.phpt b/ext/opcache/tests/wrong_inlining_003.phpt new file mode 100644 index 0000000000..a7e4a11b76 --- /dev/null +++ b/ext/opcache/tests/wrong_inlining_003.phpt @@ -0,0 +1,23 @@ +--TEST-- +foo($bar) with undefined $bar +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function get_const() { + return 42; +} + +function test() { + var_dump(get_const($undef)); +} + +test(); +?> +--EXPECTF-- +Notice: Undefined variable: undef in %swrong_inlining_003.php on line 7 +int(42) diff --git a/ext/opcache/tests/wrong_inlining_004.phpt b/ext/opcache/tests/wrong_inlining_004.phpt new file mode 100644 index 0000000000..d4b2d391a0 --- /dev/null +++ b/ext/opcache/tests/wrong_inlining_004.phpt @@ -0,0 +1,23 @@ +--TEST-- +Inlining throgh call_user_func() +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +function get_const() { + return 42; +} + +function test() { + $x = new stdClass; + var_dump(call_user_func('get_const', $x)); +} + +test(); +?> +--EXPECTF-- +int(42) diff --git a/ext/opcache/tests/wrong_inlining_005.phpt b/ext/opcache/tests/wrong_inlining_005.phpt new file mode 100644 index 0000000000..b34cd1b12f --- /dev/null +++ b/ext/opcache/tests/wrong_inlining_005.phpt @@ -0,0 +1,22 @@ +--TEST-- +Inlining of functions with ref arguments +--FILE-- +<?php + +function by_ref(&$var) +{ +} +function &get_array() { + $array = [new stdClass]; + return $array; +} +function test() +{ + by_ref(get_array()[0]); + print "ok!\n"; +} +test(); + +?> +--EXPECT-- +ok! diff --git a/ext/opcache/zend_accelerator_debug.h b/ext/opcache/zend_accelerator_debug.h index fbe3127864..6445254232 100644 --- a/ext/opcache/zend_accelerator_debug.h +++ b/ext/opcache/zend_accelerator_debug.h @@ -28,6 +28,6 @@ #define ACCEL_LOG_INFO 3 #define ACCEL_LOG_DEBUG 4 -void zend_accel_error(int type, const char *format, ...); +void zend_accel_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);; #endif /* _ZEND_ACCELERATOR_DEBUG_H */ diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 6112027cd8..259f75c2f2 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -80,13 +80,13 @@ static zend_function_entry accel_functions[] = { /* Private functions */ ZEND_FE(opcache_get_configuration, arginfo_opcache_none) ZEND_FE(opcache_get_status, arginfo_opcache_get_status) - { NULL, NULL, NULL, 0, 0 } + ZEND_FE_END }; static int validate_api_restriction(void) { if (ZCG(accel_directives).restrict_api && *ZCG(accel_directives).restrict_api) { - int len = strlen(ZCG(accel_directives).restrict_api); + size_t len = strlen(ZCG(accel_directives).restrict_api); if (!SG(request_info).path_translated || strlen(SG(request_info).path_translated) < len || @@ -204,7 +204,7 @@ static ZEND_INI_MH(OnUpdateMaxWastedPercentage) percentage = 5; zend_accel_error(ACCEL_LOG_WARNING, "opcache.max_wasted_percentage must be set between 1 and 50.\n"); - zend_accel_error(ACCEL_LOG_WARNING, ACCELERATOR_PRODUCT_NAME " will use 5%.\n"); + zend_accel_error(ACCEL_LOG_WARNING, ACCELERATOR_PRODUCT_NAME " will use 5%%.\n"); if ((ini_entry = zend_hash_str_find_ptr(EG(ini_directives), "opcache.max_wasted_percentage", sizeof("opcache.max_wasted_percentage")-1)) == NULL) { @@ -285,9 +285,9 @@ ZEND_INI_BEGIN() STD_PHP_INI_BOOLEAN("opcache.revalidate_path" , "0", PHP_INI_ALL , OnUpdateBool, accel_directives.revalidate_path , zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.log_verbosity_level" , "1" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.log_verbosity_level, zend_accel_globals, accel_globals) - STD_PHP_INI_ENTRY("opcache.memory_consumption" , "64" , PHP_INI_SYSTEM, OnUpdateMemoryConsumption, accel_directives.memory_consumption, zend_accel_globals, accel_globals) - STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "4" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals) - STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "2000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.memory_consumption" , "128" , PHP_INI_SYSTEM, OnUpdateMemoryConsumption, accel_directives.memory_consumption, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "8" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "10000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_wasted_percentage" , "5" , PHP_INI_SYSTEM, OnUpdateMaxWastedPercentage, accel_directives.max_wasted_percentage, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.consistency_checks" , "0" , PHP_INI_ALL , OnUpdateLong, accel_directives.consistency_checks, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.force_restart_timeout" , "180" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.force_restart_timeout, zend_accel_globals, accel_globals) @@ -302,6 +302,7 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.fast_shutdown" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.fast_shutdown, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.optimization_level" , DEFAULT_OPTIMIZATION_LEVEL , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.optimization_level, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.opt_debug_level" , "0" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.opt_debug_level, zend_accel_globals, accel_globals) STD_PHP_INI_BOOLEAN("opcache.enable_file_override" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_override_enabled, zend_accel_globals, accel_globals) STD_PHP_INI_BOOLEAN("opcache.enable_cli" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.enable_cli, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.error_log" , "" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.error_log, zend_accel_globals, accel_globals) @@ -481,33 +482,33 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS) char buf[32]; php_info_print_table_row(2, "Startup", "OK"); php_info_print_table_row(2, "Shared memory model", zend_accel_get_shared_model()); - snprintf(buf, sizeof(buf), "%pd", (zend_ulong)ZCSG(hits)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, (zend_ulong)ZCSG(hits)); php_info_print_table_row(2, "Cache hits", buf); - snprintf(buf, sizeof(buf), "%pd", ZSMMG(memory_exhausted)?ZCSG(misses):ZCSG(misses)-ZCSG(blacklist_misses)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZSMMG(memory_exhausted)?ZCSG(misses):ZCSG(misses)-ZCSG(blacklist_misses)); php_info_print_table_row(2, "Cache misses", buf); snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZCG(accel_directives).memory_consumption-zend_shared_alloc_get_free_memory()-ZSMMG(wasted_shared_memory)); php_info_print_table_row(2, "Used memory", buf); - snprintf(buf, sizeof(buf), "%pd", zend_shared_alloc_get_free_memory()); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, zend_shared_alloc_get_free_memory()); php_info_print_table_row(2, "Free memory", buf); - snprintf(buf, sizeof(buf), "%pd", ZSMMG(wasted_shared_memory)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZSMMG(wasted_shared_memory)); php_info_print_table_row(2, "Wasted memory", buf); if (ZCSG(interned_strings_start) && ZCSG(interned_strings_end) && ZCSG(interned_strings_top)) { - snprintf(buf, sizeof(buf), "%pd", ZCSG(interned_strings_top) - ZCSG(interned_strings_start)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZCSG(interned_strings_top) - ZCSG(interned_strings_start)); php_info_print_table_row(2, "Interned Strings Used memory", buf); - snprintf(buf, sizeof(buf), "%pd", ZCSG(interned_strings_end) - ZCSG(interned_strings_top)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZCSG(interned_strings_end) - ZCSG(interned_strings_top)); php_info_print_table_row(2, "Interned Strings Free memory", buf); } - snprintf(buf, sizeof(buf), "%ld", ZCSG(hash).num_direct_entries); + snprintf(buf, sizeof(buf), "%d", ZCSG(hash).num_direct_entries); php_info_print_table_row(2, "Cached scripts", buf); - snprintf(buf, sizeof(buf), "%ld", ZCSG(hash).num_entries); + snprintf(buf, sizeof(buf), "%d", ZCSG(hash).num_entries); php_info_print_table_row(2, "Cached keys", buf); - snprintf(buf, sizeof(buf), "%pd", ZCSG(hash).max_num_entries); + snprintf(buf, sizeof(buf), "%d", ZCSG(hash).max_num_entries); php_info_print_table_row(2, "Max keys", buf); - snprintf(buf, sizeof(buf), "%pd", ZCSG(oom_restarts)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZCSG(oom_restarts)); php_info_print_table_row(2, "OOM restarts", buf); - snprintf(buf, sizeof(buf), "%pd", ZCSG(hash_restarts)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZCSG(hash_restarts)); php_info_print_table_row(2, "Hash keys restarts", buf); - snprintf(buf, sizeof(buf), "%pd", ZCSG(manual_restarts)); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, ZCSG(manual_restarts)); php_info_print_table_row(2, "Manual restarts", buf); } } @@ -563,7 +564,7 @@ static int accelerator_get_scripts(zval *return_value) script = (zend_persistent_script *)cache_entry->data; array_init(&persistent_script_report); - add_assoc_str(&persistent_script_report, "full_path", zend_string_dup(script->full_path, 0)); + add_assoc_str(&persistent_script_report, "full_path", zend_string_dup(script->script.filename, 0)); add_assoc_long(&persistent_script_report, "hits", (zend_long)script->dynamic_members.hits); add_assoc_long(&persistent_script_report, "memory_consumption", script->dynamic_members.memory_consumption); ta = localtime(&script->dynamic_members.last_used); diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index e527a7b83c..3d845f389e 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -40,8 +40,6 @@ typedef int (*id_function_t)(void *, void *); typedef void (*unique_copy_ctor_func_t)(void *pElement); -static zend_ast *zend_ast_clone(zend_ast *ast); - static void zend_accel_destroy_zend_function(zval *zv) { zend_function *function = Z_PTR_P(zv); @@ -72,12 +70,12 @@ zend_persistent_script* create_persistent_script(void) zend_persistent_script *persistent_script = (zend_persistent_script *) emalloc(sizeof(zend_persistent_script)); memset(persistent_script, 0, sizeof(zend_persistent_script)); - zend_hash_init(&persistent_script->function_table, 128, NULL, ZEND_FUNCTION_DTOR, 0); + zend_hash_init(&persistent_script->script.function_table, 128, NULL, ZEND_FUNCTION_DTOR, 0); /* class_table is usually destroyed by free_persistent_script() that * overrides destructor. ZEND_CLASS_DTOR may be used by standard * PHP compiler */ - zend_hash_init(&persistent_script->class_table, 16, NULL, ZEND_CLASS_DTOR, 0); + zend_hash_init(&persistent_script->script.class_table, 16, NULL, ZEND_CLASS_DTOR, 0); return persistent_script; } @@ -85,18 +83,18 @@ zend_persistent_script* create_persistent_script(void) void free_persistent_script(zend_persistent_script *persistent_script, int destroy_elements) { if (destroy_elements) { - persistent_script->function_table.pDestructor = zend_accel_destroy_zend_function; - persistent_script->class_table.pDestructor = zend_accel_destroy_zend_class; + persistent_script->script.function_table.pDestructor = zend_accel_destroy_zend_function; + persistent_script->script.class_table.pDestructor = zend_accel_destroy_zend_class; } else { - persistent_script->function_table.pDestructor = NULL; - persistent_script->class_table.pDestructor = NULL; + persistent_script->script.function_table.pDestructor = NULL; + persistent_script->script.class_table.pDestructor = NULL; } - zend_hash_destroy(&persistent_script->function_table); - zend_hash_destroy(&persistent_script->class_table); + zend_hash_destroy(&persistent_script->script.function_table); + zend_hash_destroy(&persistent_script->script.class_table); - if (persistent_script->full_path) { - zend_string_release(persistent_script->full_path); + if (persistent_script->script.filename) { + zend_string_release(persistent_script->script.filename); } efree(persistent_script); @@ -169,67 +167,13 @@ static inline void zend_clone_zval(zval *src) src = Z_REFVAL_P(src); } } - if (Z_TYPE_P(src) == IS_CONSTANT_AST) { - if (Z_REFCOUNT_P(src) > 1 && (ptr = accel_xlat_get(Z_AST_P(src))) != NULL) { - Z_AST_P(src) = ptr; - } else { - zend_ast_ref *old = Z_AST_P(src); - - ZVAL_NEW_AST(src, old->ast); - Z_AST_P(src)->gc = old->gc; - if (Z_REFCOUNT_P(src) > 1) { - accel_xlat_set(old, Z_AST_P(src)); - } - Z_ASTVAL_P(src) = zend_ast_clone(Z_ASTVAL_P(src)); - } - } -} - -static zend_ast *zend_ast_clone(zend_ast *ast) -{ - uint32_t i; - - if (ast->kind == ZEND_AST_ZVAL) { - zend_ast_zval *copy = emalloc(sizeof(zend_ast_zval)); - copy->kind = ZEND_AST_ZVAL; - copy->attr = ast->attr; - ZVAL_COPY_VALUE(©->val, zend_ast_get_zval(ast)); - return (zend_ast *) copy; - } else if (zend_ast_is_list(ast)) { - zend_ast_list *list = zend_ast_get_list(ast); - zend_ast_list *copy = emalloc( - sizeof(zend_ast_list) - sizeof(zend_ast *) + sizeof(zend_ast *) * list->children); - copy->kind = list->kind; - copy->attr = list->attr; - copy->children = list->children; - for (i = 0; i < list->children; i++) { - if (list->child[i]) { - copy->child[i] = zend_ast_clone(list->child[i]); - } else { - copy->child[i] = NULL; - } - } - return (zend_ast *) copy; - } else { - uint32_t children = zend_ast_get_num_children(ast); - zend_ast *copy = emalloc(sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children); - copy->kind = ast->kind; - copy->attr = ast->attr; - for (i = 0; i < children; i++) { - if (ast->child[i]) { - copy->child[i] = zend_ast_clone(ast->child[i]); - } else { - copy->child[i] = NULL; - } - } - return copy; - } } static void zend_hash_clone_constants(HashTable *ht, HashTable *source) { Bucket *p, *q, *end; zend_ulong nIndex; + zend_class_constant *c; ht->nTableSize = source->nTableSize; ht->nTableMask = source->nTableMask; @@ -265,8 +209,14 @@ static void zend_hash_clone_constants(HashTable *ht, HashTable *source) q->key = p->key; /* Copy data */ - ZVAL_COPY_VALUE(&q->val, &p->val); - zend_clone_zval(&q->val); + c = ARENA_REALLOC(Z_PTR(p->val)); + ZVAL_PTR(&q->val, c); + + zend_clone_zval(&c->value); + if ((void*)c->ce >= ZCG(current_persistent_script)->arena_mem && + (void*)c->ce < (void*)((char*)ZCG(current_persistent_script)->arena_mem + ZCG(current_persistent_script)->arena_size)) { + c->ce = ARENA_REALLOC(c->ce); + } } } @@ -681,7 +631,7 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script, zend_op_array *op_array; op_array = (zend_op_array *) emalloc(sizeof(zend_op_array)); - *op_array = persistent_script->main_op_array; + *op_array = persistent_script->script.main_op_array; if (EXPECTED(from_shared_memory)) { zend_hash_init(&ZCG(bind_hash), 10, NULL, NULL, 0); @@ -701,22 +651,22 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script, } /* Copy all the necessary stuff from shared memory to regular memory, and protect the shared script */ - if (zend_hash_num_elements(&persistent_script->class_table) > 0) { - zend_accel_class_hash_copy(CG(class_table), &persistent_script->class_table, (unique_copy_ctor_func_t) zend_class_copy_ctor); + if (zend_hash_num_elements(&persistent_script->script.class_table) > 0) { + zend_accel_class_hash_copy(CG(class_table), &persistent_script->script.class_table, (unique_copy_ctor_func_t) zend_class_copy_ctor); } /* we must first to copy all classes and then prepare functions, since functions may try to bind classes - which depend on pre-bind class entries existent in the class table */ - if (zend_hash_num_elements(&persistent_script->function_table) > 0) { - zend_accel_function_hash_copy_from_shm(CG(function_table), &persistent_script->function_table); + if (zend_hash_num_elements(&persistent_script->script.function_table) > 0) { + zend_accel_function_hash_copy_from_shm(CG(function_table), &persistent_script->script.function_table); } /* Register __COMPILER_HALT_OFFSET__ constant */ if (persistent_script->compiler_halt_offset != 0 && - persistent_script->full_path) { + persistent_script->script.filename) { zend_string *name; char haltoff[] = "__COMPILER_HALT_OFFSET__"; - name = zend_mangle_property_name(haltoff, sizeof(haltoff) - 1, ZSTR_VAL(persistent_script->full_path), ZSTR_LEN(persistent_script->full_path), 0); + name = zend_mangle_property_name(haltoff, sizeof(haltoff) - 1, ZSTR_VAL(persistent_script->script.filename), ZSTR_LEN(persistent_script->script.filename), 0); if (!zend_hash_exists(EG(zend_constants), name)) { zend_register_long_constant(ZSTR_VAL(name), ZSTR_LEN(name), persistent_script->compiler_halt_offset, CONST_CS, 0); } @@ -726,17 +676,17 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script, zend_hash_destroy(&ZCG(bind_hash)); ZCG(current_persistent_script) = NULL; } else /* if (!from_shared_memory) */ { - if (zend_hash_num_elements(&persistent_script->function_table) > 0) { - zend_accel_function_hash_copy(CG(function_table), &persistent_script->function_table); + if (zend_hash_num_elements(&persistent_script->script.function_table) > 0) { + zend_accel_function_hash_copy(CG(function_table), &persistent_script->script.function_table); } - if (zend_hash_num_elements(&persistent_script->class_table) > 0) { - zend_accel_class_hash_copy(CG(class_table), &persistent_script->class_table, NULL); + if (zend_hash_num_elements(&persistent_script->script.class_table) > 0) { + zend_accel_class_hash_copy(CG(class_table), &persistent_script->script.class_table, NULL); } } if (op_array->early_binding != (uint32_t)-1) { zend_string *orig_compiled_filename = CG(compiled_filename); - CG(compiled_filename) = persistent_script->full_path; + CG(compiled_filename) = persistent_script->script.filename; zend_do_delayed_early_binding(op_array); CG(compiled_filename) = orig_compiled_filename; } diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 2b65685d21..dea427fcaf 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -368,7 +368,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_PTR(op_array->vars); SERIALIZE_STR(op_array->function_name); SERIALIZE_STR(op_array->filename); - SERIALIZE_PTR(op_array->brk_cont_array); + SERIALIZE_PTR(op_array->live_range); SERIALIZE_PTR(op_array->scope); SERIALIZE_STR(op_array->doc_comment); SERIALIZE_PTR(op_array->try_catch_array); @@ -392,7 +392,6 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra } if (!IS_SERIALIZED(op_array->opcodes)) { -#if ZEND_USE_ABS_CONST_ADDR || ZEND_USE_ABS_JMP_ADDR zend_op *opline, *end; SERIALIZE_PTR(op_array->opcodes); @@ -400,20 +399,18 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra UNSERIALIZE_PTR(opline); end = opline + op_array->last; while (opline < end) { -# if ZEND_USE_ABS_CONST_ADDR - if (ZEND_OP1_TYPE(opline) == IS_CONST) { +#if ZEND_USE_ABS_CONST_ADDR + if (opline->op1_type == IS_CONST) { SERIALIZE_PTR(opline->op1.zv); } - if (ZEND_OP2_TYPE(opline) == IS_CONST) { + if (opline->op2_type == IS_CONST) { SERIALIZE_PTR(opline->op2.zv); } -# endif -# if ZEND_USE_ABS_JMP_ADDR +#endif +#if ZEND_USE_ABS_JMP_ADDR switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: SERIALIZE_PTR(opline->op1.jmp_addr); break; case ZEND_JMPZNZ: @@ -425,23 +422,22 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: - case ZEND_NEW: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: SERIALIZE_PTR(opline->op2.jmp_addr); break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: /* relative extended_value don't have to be changed */ break; } -# endif +#endif + zend_serialize_opcode_handler(opline); opline++; } -#else - SERIALIZE_PTR(op_array->opcodes); -#endif if (op_array->arg_info) { zend_arg_info *p, *end; @@ -483,7 +479,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_STR(op_array->function_name); SERIALIZE_STR(op_array->filename); - SERIALIZE_PTR(op_array->brk_cont_array); + SERIALIZE_PTR(op_array->live_range); SERIALIZE_PTR(op_array->scope); SERIALIZE_STR(op_array->doc_comment); SERIALIZE_PTR(op_array->try_catch_array); @@ -528,6 +524,28 @@ static void zend_file_cache_serialize_prop_info(zval *zv, } } +static void zend_file_cache_serialize_class_constant(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + if (!IS_SERIALIZED(Z_PTR_P(zv))) { + zend_class_constant *c; + + SERIALIZE_PTR(Z_PTR_P(zv)); + c = Z_PTR_P(zv); + UNSERIALIZE_PTR(c); + + zend_file_cache_serialize_zval(&c->value, script, info, buf); + if (c->ce && !IS_SERIALIZED(c->ce)) { + SERIALIZE_PTR(c->ce); + } + if (c->doc_comment && !IS_SERIALIZED(c->doc_comment)) { + SERIALIZE_STR(c->doc_comment); + } + } +} + static void zend_file_cache_serialize_class(zval *zv, zend_persistent_script *script, zend_file_cache_metainfo *info, @@ -565,9 +583,9 @@ static void zend_file_cache_serialize_class(zval *zv, p++; } } - zend_file_cache_serialize_hash(&ce->constants_table, script, info, buf, zend_file_cache_serialize_zval); - SERIALIZE_STR(ZEND_CE_FILENAME(ce)); - SERIALIZE_STR(ZEND_CE_DOC_COMMENT(ce)); + zend_file_cache_serialize_hash(&ce->constants_table, script, info, buf, zend_file_cache_serialize_class_constant); + SERIALIZE_STR(ce->info.user.filename); + SERIALIZE_STR(ce->info.user.doc_comment); zend_file_cache_serialize_hash(&ce->properties_info, script, info, buf, zend_file_cache_serialize_prop_info); if (ce->trait_aliases) { @@ -679,11 +697,11 @@ static void zend_file_cache_serialize(zend_persistent_script *script, memcpy(buf, script->mem, script->size); new_script = (zend_persistent_script*)((char*)buf + info->script_offset); - SERIALIZE_STR(new_script->full_path); + SERIALIZE_STR(new_script->script.filename); - zend_file_cache_serialize_hash(&new_script->class_table, script, info, buf, zend_file_cache_serialize_class); - zend_file_cache_serialize_hash(&new_script->function_table, script, info, buf, zend_file_cache_serialize_func); - zend_file_cache_serialize_op_array(&new_script->main_op_array, script, info, buf); + zend_file_cache_serialize_hash(&new_script->script.class_table, script, info, buf, zend_file_cache_serialize_class); + zend_file_cache_serialize_hash(&new_script->script.function_table, script, info, buf, zend_file_cache_serialize_func); + zend_file_cache_serialize_op_array(&new_script->script.main_op_array, script, info, buf); SERIALIZE_PTR(new_script->arena_mem); new_script->mem = NULL; @@ -731,7 +749,7 @@ int zend_file_cache_script_store(zend_persistent_script *script, int in_shm) #endif void *mem, *buf; - filename = zend_file_cache_get_bin_file_path(script->full_path); + filename = zend_file_cache_get_bin_file_path(script->script.filename); if (zend_file_cache_mkdir(filename, strlen(ZCG(accel_directives).file_cache)) != SUCCESS) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot create directory for file '%s'\n", filename); @@ -946,7 +964,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_PTR(op_array->vars); UNSERIALIZE_STR(op_array->function_name); UNSERIALIZE_STR(op_array->filename); - UNSERIALIZE_PTR(op_array->brk_cont_array); + UNSERIALIZE_PTR(op_array->live_range); UNSERIALIZE_PTR(op_array->scope); UNSERIALIZE_STR(op_array->doc_comment); UNSERIALIZE_PTR(op_array->try_catch_array); @@ -974,10 +992,10 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr end = opline + op_array->last; while (opline < end) { # if ZEND_USE_ABS_CONST_ADDR - if (ZEND_OP1_TYPE(opline) == IS_CONST) { + if (opline->op1_type == IS_CONST) { UNSERIALIZE_PTR(opline->op1.zv); } - if (ZEND_OP2_TYPE(opline) == IS_CONST) { + if (opline->op2_type == IS_CONST) { UNSERIALIZE_PTR(opline->op2.zv); } # endif @@ -985,8 +1003,6 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: UNSERIALIZE_PTR(opline->op1.jmp_addr); break; case ZEND_JMPZNZ: @@ -998,19 +1014,20 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: - case ZEND_NEW: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: UNSERIALIZE_PTR(opline->op2.jmp_addr); break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: /* relative extended_value don't have to be changed */ break; } # endif - ZEND_VM_SET_OPCODE_HANDLER(opline); + zend_deserialize_opcode_handler(opline); opline++; } @@ -1052,7 +1069,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_STR(op_array->function_name); UNSERIALIZE_STR(op_array->filename); - UNSERIALIZE_PTR(op_array->brk_cont_array); + UNSERIALIZE_PTR(op_array->live_range); UNSERIALIZE_PTR(op_array->scope); UNSERIALIZE_STR(op_array->doc_comment); UNSERIALIZE_PTR(op_array->try_catch_array); @@ -1093,6 +1110,26 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, } } +static void zend_file_cache_unserialize_class_constant(zval *zv, + zend_persistent_script *script, + void *buf) +{ + if (!IS_UNSERIALIZED(Z_PTR_P(zv))) { + zend_class_constant *c; + + UNSERIALIZE_PTR(Z_PTR_P(zv)); + c = Z_PTR_P(zv); + + zend_file_cache_unserialize_zval(&c->value, script, buf); + if (c->ce && !IS_UNSERIALIZED(c->ce)) { + UNSERIALIZE_PTR(c->ce); + } + if (c->doc_comment && !IS_UNSERIALIZED(c->doc_comment)) { + UNSERIALIZE_STR(c->doc_comment); + } + } +} + static void zend_file_cache_unserialize_class(zval *zv, zend_persistent_script *script, void *buf) @@ -1128,9 +1165,9 @@ static void zend_file_cache_unserialize_class(zval *zv, } } zend_file_cache_unserialize_hash(&ce->constants_table, - script, buf, zend_file_cache_unserialize_zval, NULL); - UNSERIALIZE_STR(ZEND_CE_FILENAME(ce)); - UNSERIALIZE_STR(ZEND_CE_DOC_COMMENT(ce)); + script, buf, zend_file_cache_unserialize_class_constant, NULL); + UNSERIALIZE_STR(ce->info.user.filename); + UNSERIALIZE_STR(ce->info.user.doc_comment); zend_file_cache_unserialize_hash(&ce->properties_info, script, buf, zend_file_cache_unserialize_prop_info, ZVAL_PTR_DTOR); @@ -1230,13 +1267,13 @@ static void zend_file_cache_unserialize(zend_persistent_script *script, { script->mem = buf; - UNSERIALIZE_STR(script->full_path); + UNSERIALIZE_STR(script->script.filename); - zend_file_cache_unserialize_hash(&script->class_table, + zend_file_cache_unserialize_hash(&script->script.class_table, script, buf, zend_file_cache_unserialize_class, ZEND_CLASS_DTOR); - zend_file_cache_unserialize_hash(&script->function_table, + zend_file_cache_unserialize_hash(&script->script.function_table, script, buf, zend_file_cache_unserialize_func, ZEND_FUNCTION_DTOR); - zend_file_cache_unserialize_op_array(&script->main_op_array, script, buf); + zend_file_cache_unserialize_op_array(&script->script.main_op_array, script, buf); UNSERIALIZE_PTR(script->arena_mem); } @@ -1399,7 +1436,7 @@ use_process_mem: script->dynamic_members.checksum = zend_accel_script_checksum(script); script->dynamic_members.last_used = ZCG(request_time); - zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(script->full_path), ZSTR_LEN(script->full_path), 0, script); + zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(script->script.filename), ZSTR_LEN(script->script.filename), 0, script); zend_shared_alloc_unlock(); zend_arena_release(&CG(arena), checkpoint); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index eca90b30ef..9574e43d6f 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -77,7 +77,6 @@ typedef void (*zend_persist_func_t)(zval*); static void zend_persist_zval(zval *z); -static void zend_persist_zval_const(zval *z); static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX}; @@ -102,21 +101,21 @@ static void zend_hash_persist(HashTable *ht, zend_persist_func_t pPersistElement void *data = HT_GET_DATA_ADDR(ht); zend_accel_store(data, HT_USED_SIZE(ht)); HT_SET_DATA_ADDR(ht, data); - } else if (ht->nNumUsed < -(int32_t)ht->nTableMask / 2) { + } else if (ht->nNumUsed < (uint32_t)(-(int32_t)ht->nTableMask) / 2) { /* compact table */ void *old_data = HT_GET_DATA_ADDR(ht); Bucket *old_buckets = ht->arData; - int32_t hash_size; + uint32_t hash_size; if (ht->nNumUsed <= HT_MIN_SIZE) { hash_size = HT_MIN_SIZE; } else { - hash_size = -(int32_t)ht->nTableMask; + hash_size = (uint32_t)(-(int32_t)ht->nTableMask); while (hash_size >> 1 > ht->nNumUsed) { hash_size >>= 1; } } - ht->nTableMask = -hash_size; + ht->nTableMask = (uint32_t)(-(int32_t)hash_size); ZEND_ASSERT(((zend_uintptr_t)ZCG(mem) & 0x7) == 0); /* should be 8 byte aligned */ HT_SET_DATA_ADDR(ht, ZCG(mem)); ZCG(mem) = (void*)((char*)ZCG(mem) + ZEND_ALIGNED_SIZE((hash_size * sizeof(uint32_t)) + (ht->nNumUsed * sizeof(Bucket)))); @@ -184,21 +183,21 @@ static void zend_hash_persist_immutable(HashTable *ht) } if (ht->u.flags & HASH_FLAG_PACKED) { HT_SET_DATA_ADDR(ht, zend_accel_memdup(HT_GET_DATA_ADDR(ht), HT_USED_SIZE(ht))); - } else if (ht->nNumUsed < -(int32_t)ht->nTableMask / 2) { + } else if (ht->nNumUsed < (uint32_t)(-(int32_t)ht->nTableMask) / 2) { /* compact table */ void *old_data = HT_GET_DATA_ADDR(ht); Bucket *old_buckets = ht->arData; - int32_t hash_size; + uint32_t hash_size; if (ht->nNumUsed <= HT_MIN_SIZE) { hash_size = HT_MIN_SIZE; } else { - hash_size = -(int32_t)ht->nTableMask; + hash_size = (uint32_t)(-(int32_t)ht->nTableMask); while (hash_size >> 1 > ht->nNumUsed) { hash_size >>= 1; } } - ht->nTableMask = -hash_size; + ht->nTableMask = (uint32_t)(-(int32_t)hash_size); ZEND_ASSERT(((zend_uintptr_t)ZCG(mem) & 0x7) == 0); /* should be 8 byte aligned */ HT_SET_DATA_ADDR(ht, ZCG(mem)); ZCG(mem) = (void*)((char*)ZCG(mem) + (hash_size * sizeof(uint32_t)) + (ht->nNumUsed * sizeof(Bucket))); @@ -216,7 +215,7 @@ static void zend_hash_persist_immutable(HashTable *ht) } /* persist the data itself */ - zend_persist_zval_const(&p->val); + zend_persist_zval(&p->val); nIndex = p->h | ht->nTableMask; Z_NEXT(p->val) = HT_HASH(ht, nIndex); @@ -241,7 +240,7 @@ static void zend_hash_persist_immutable(HashTable *ht) } /* persist the data itself */ - zend_persist_zval_const(&p->val); + zend_persist_zval(&p->val); } } @@ -290,63 +289,10 @@ static void zend_persist_zval(zval *z) zend_accel_store_interned_string(Z_STR_P(z)); Z_GC_FLAGS_P(z) |= flags; Z_TYPE_FLAGS_P(z) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE); - break; - case IS_ARRAY: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_ARR_P(z)); - if (new_ptr) { - Z_ARR_P(z) = new_ptr; - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - } else { - if (Z_IMMUTABLE_P(z)) { - Z_ARR_P(z) = zend_accel_memdup(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist_immutable(Z_ARRVAL_P(z)); - } else { - GC_REMOVE_FROM_BUFFER(Z_ARR_P(z)); - zend_accel_store(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist(Z_ARRVAL_P(z), zend_persist_zval); - /* make immutable array */ - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - GC_REFCOUNT(Z_COUNTED_P(z)) = 2; - GC_FLAGS(Z_COUNTED_P(z)) |= IS_ARRAY_IMMUTABLE; - Z_ARRVAL_P(z)->u.flags |= HASH_FLAG_STATIC_KEYS; - Z_ARRVAL_P(z)->u.flags &= ~HASH_FLAG_APPLY_PROTECTION; - } - } - break; - case IS_REFERENCE: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_REF_P(z)); - if (new_ptr) { - Z_REF_P(z) = new_ptr; - } else { - zend_accel_store(Z_REF_P(z), sizeof(zend_reference)); - zend_persist_zval(Z_REFVAL_P(z)); + if (Z_TYPE_P(z) == IS_CONSTANT) { + Z_TYPE_FLAGS_P(z) |= IS_TYPE_IMMUTABLE; } break; - case IS_CONSTANT_AST: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_AST_P(z)); - if (new_ptr) { - Z_AST_P(z) = new_ptr; - } else { - zend_accel_store(Z_AST_P(z), sizeof(zend_ast_ref)); - Z_ASTVAL_P(z) = zend_persist_ast(Z_ASTVAL_P(z)); - } - break; - } -} - -static void zend_persist_zval_static(zval *z) -{ - zend_uchar flags; - void *new_ptr; - - switch (Z_TYPE_P(z)) { - case IS_STRING: - case IS_CONSTANT: - flags = Z_GC_FLAGS_P(z) & ~ (IS_STR_PERSISTENT | IS_STR_INTERNED | IS_STR_PERMANENT); - zend_accel_store_interned_string(Z_STR_P(z)); - Z_GC_FLAGS_P(z) |= flags; - Z_TYPE_FLAGS_P(z) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE); - break; case IS_ARRAY: new_ptr = zend_shared_alloc_get_xlat_entry(Z_ARR_P(z)); if (new_ptr) { @@ -393,62 +339,6 @@ static void zend_persist_zval_static(zval *z) } } -static void zend_persist_zval_const(zval *z) -{ - zend_uchar flags; - void *new_ptr; - - switch (Z_TYPE_P(z)) { - case IS_STRING: - case IS_CONSTANT: - flags = Z_GC_FLAGS_P(z) & ~ (IS_STR_PERSISTENT | IS_STR_INTERNED | IS_STR_PERMANENT); - zend_accel_memdup_interned_string(Z_STR_P(z)); - Z_GC_FLAGS_P(z) |= flags; - Z_TYPE_FLAGS_P(z) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COPYABLE); - break; - case IS_ARRAY: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_ARR_P(z)); - if (new_ptr) { - Z_ARR_P(z) = new_ptr; - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - } else { - if (Z_IMMUTABLE_P(z)) { - Z_ARR_P(z) = zend_accel_memdup(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist_immutable(Z_ARRVAL_P(z)); - } else { - GC_REMOVE_FROM_BUFFER(Z_ARR_P(z)); - zend_accel_store(Z_ARR_P(z), sizeof(zend_array)); - zend_hash_persist(Z_ARRVAL_P(z), zend_persist_zval); - /* make immutable array */ - Z_TYPE_FLAGS_P(z) = IS_TYPE_IMMUTABLE; - GC_REFCOUNT(Z_COUNTED_P(z)) = 2; - GC_FLAGS(Z_COUNTED_P(z)) |= IS_ARRAY_IMMUTABLE; - Z_ARRVAL_P(z)->u.flags |= HASH_FLAG_STATIC_KEYS; - Z_ARRVAL_P(z)->u.flags &= ~HASH_FLAG_APPLY_PROTECTION; - } - } - break; - case IS_REFERENCE: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_REF_P(z)); - if (new_ptr) { - Z_REF_P(z) = new_ptr; - } else { - zend_accel_store(Z_REF_P(z), sizeof(zend_reference)); - zend_persist_zval(Z_REFVAL_P(z)); - } - break; - case IS_CONSTANT_AST: - new_ptr = zend_shared_alloc_get_xlat_entry(Z_AST_P(z)); - if (new_ptr) { - Z_AST_P(z) = new_ptr; - } else { - zend_accel_store(Z_AST_P(z), sizeof(zend_ast_ref)); - Z_ASTVAL_P(z) = zend_persist_ast(Z_ASTVAL_P(z)); - } - break; - } -} - static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_script* main_persistent_script) { int already_stored = 0; @@ -484,7 +374,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc if (stored) { op_array->static_variables = stored; } else { - zend_hash_persist(op_array->static_variables, zend_persist_zval_static); + zend_hash_persist(op_array->static_variables, zend_persist_zval); zend_accel_store(op_array->static_variables, sizeof(HashTable)); /* make immutable array */ GC_REFCOUNT(op_array->static_variables) = 2; @@ -529,22 +419,20 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc for (; opline < end ; opline++, offset++) { # if ZEND_USE_ABS_CONST_ADDR - if (ZEND_OP1_TYPE(opline) == IS_CONST) { + if (opline->op1_type == IS_CONST) { opline->op1.zv = (zval*)((char*)opline->op1.zv + ((char*)op_array->literals - (char*)orig_literals)); } - if (ZEND_OP2_TYPE(opline) == IS_CONST) { + if (opline->op2_type == IS_CONST) { opline->op2.zv = (zval*)((char*)opline->op2.zv + ((char*)op_array->literals - (char*)orig_literals)); } # endif # if ZEND_USE_ABS_JMP_ADDR - if (ZEND_DONE_PASS_TWO(op_array)) { + if (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) { /* fix jumps to point to new array */ switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: - case ZEND_DECLARE_ANON_CLASS: - case ZEND_DECLARE_ANON_INHERITED_CLASS: - ZEND_OP1(opline).jmp_addr = &new_opcodes[ZEND_OP1(opline).jmp_addr - op_array->opcodes]; + opline->op1.jmp_addr = &new_opcodes[opline->op1.jmp_addr - op_array->opcodes]; break; case ZEND_JMPZNZ: /* relative extended_value don't have to be changed */ @@ -555,12 +443,13 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: - case ZEND_NEW: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: - ZEND_OP2(opline).jmp_addr = &new_opcodes[ZEND_OP2(opline).jmp_addr - op_array->opcodes]; + opline->op2.jmp_addr = &new_opcodes[opline->op2.jmp_addr - op_array->opcodes]; break; + case ZEND_DECLARE_ANON_CLASS: + case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: /* relative extended_value don't have to be changed */ @@ -629,8 +518,8 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc op_array->arg_info = arg_info; } - if (op_array->brk_cont_array) { - zend_accel_store(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont); + if (op_array->live_range) { + zend_accel_store(op_array->live_range, sizeof(zend_live_range) * op_array->last_live_range); } if (op_array->scope) { @@ -728,6 +617,39 @@ static void zend_persist_property_info(zval *zv) } } +static void zend_persist_class_constant(zval *zv) +{ + zend_class_constant *c = zend_shared_alloc_get_xlat_entry(Z_PTR_P(zv)); + + if (c) { + Z_PTR_P(zv) = c; + return; + } + memcpy(ZCG(arena_mem), Z_PTR_P(zv), sizeof(zend_class_constant)); + zend_shared_alloc_register_xlat_entry(Z_PTR_P(zv), ZCG(arena_mem)); + c = Z_PTR_P(zv) = ZCG(arena_mem); + ZCG(arena_mem) = (void*)((char*)ZCG(arena_mem) + ZEND_ALIGNED_SIZE(sizeof(zend_class_constant))); + zend_persist_zval(&c->value); + c->ce = zend_shared_alloc_get_xlat_entry(c->ce); + if (c->doc_comment) { + if (ZCG(accel_directives).save_comments) { + zend_string *doc_comment = zend_shared_alloc_get_xlat_entry(c->doc_comment); + if (doc_comment) { + c->doc_comment = doc_comment; + } else { + zend_accel_store_string(c->doc_comment); + } + } else { + zend_string *doc_comment = zend_shared_alloc_get_xlat_entry(c->doc_comment); + if (!doc_comment) { + zend_shared_alloc_register_xlat_entry(c->doc_comment, c->doc_comment); + zend_string_release(c->doc_comment); + } + c->doc_comment = NULL; + } + } +} + static void zend_persist_class_entry(zval *zv) { zend_class_entry *ce = Z_PTR_P(zv); @@ -757,21 +679,21 @@ static void zend_persist_class_entry(zval *zv) } ce->static_members_table = NULL; - zend_hash_persist(&ce->constants_table, zend_persist_zval); + zend_hash_persist(&ce->constants_table, zend_persist_class_constant); - if (ZEND_CE_FILENAME(ce)) { + if (ce->info.user.filename) { /* do not free! PHP has centralized filename storage, compiler will free it */ - zend_accel_memdup_string(ZEND_CE_FILENAME(ce)); + zend_accel_memdup_string(ce->info.user.filename); } - if (ZEND_CE_DOC_COMMENT(ce)) { + if (ce->info.user.doc_comment) { if (ZCG(accel_directives).save_comments) { - zend_accel_store_string(ZEND_CE_DOC_COMMENT(ce)); + zend_accel_store_string(ce->info.user.doc_comment); } else { - if (!zend_shared_alloc_get_xlat_entry(ZEND_CE_DOC_COMMENT(ce))) { - zend_shared_alloc_register_xlat_entry(ZEND_CE_DOC_COMMENT(ce), ZEND_CE_DOC_COMMENT(ce)); - zend_string_release(ZEND_CE_DOC_COMMENT(ce)); + if (!zend_shared_alloc_get_xlat_entry(ce->info.user.doc_comment)) { + zend_shared_alloc_register_xlat_entry(ce->info.user.doc_comment, ce->info.user.doc_comment); + zend_string_release(ce->info.user.doc_comment); } - ZEND_CE_DOC_COMMENT(ce) = NULL; + ce->info.user.doc_comment = NULL; } } zend_hash_persist(&ce->properties_info, zend_persist_property_info); @@ -918,7 +840,7 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script if (key && *key) { *key = zend_accel_memdup(*key, key_length + 1); } - zend_accel_store_string(script->full_path); + zend_accel_store_string(script->script.filename); #ifdef __SSE2__ /* Align to 64-byte boundary */ @@ -930,9 +852,9 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script script->arena_mem = ZCG(arena_mem) = ZCG(mem); ZCG(mem) = (void*)((char*)ZCG(mem) + script->arena_size); - zend_accel_persist_class_table(&script->class_table); - zend_hash_persist(&script->function_table, zend_persist_op_array); - zend_persist_op_array_ex(&script->main_op_array, script); + zend_accel_persist_class_table(&script->script.class_table); + zend_hash_persist(&script->script.function_table, zend_persist_op_array); + zend_persist_op_array_ex(&script->script.main_op_array, script); return script; } diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 369c57cf84..d4f1e56c4f 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -60,14 +60,14 @@ static void zend_hash_persist_calc(HashTable *ht, void (*pPersistElement)(zval * return; } - if (!(ht->u.flags & HASH_FLAG_PACKED) && ht->nNumUsed < -(int32_t)ht->nTableMask / 2) { + if (!(ht->u.flags & HASH_FLAG_PACKED) && ht->nNumUsed < (uint32_t)(-(int32_t)ht->nTableMask) / 2) { /* compact table */ - int32_t hash_size; + uint32_t hash_size; if (ht->nNumUsed <= HT_MIN_SIZE) { hash_size = HT_MIN_SIZE; } else { - hash_size = -(int32_t)ht->nTableMask; + hash_size = (uint32_t)(-(int32_t)ht->nTableMask); while (hash_size >> 1 > ht->nNumUsed) { hash_size >>= 1; } @@ -236,8 +236,8 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) } } - if (op_array->brk_cont_array) { - ADD_DUP_SIZE(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont); + if (op_array->live_range) { + ADD_DUP_SIZE(op_array->live_range, sizeof(zend_live_range) * op_array->last_live_range); } if (ZCG(accel_directives).save_comments && op_array->doc_comment) { @@ -294,6 +294,21 @@ static void zend_persist_property_info_calc(zval *zv) } } +static void zend_persist_class_constant_calc(zval *zv) +{ + zend_class_constant *c = Z_PTR_P(zv); + + if (!zend_shared_alloc_get_xlat_entry(c)) { + zend_shared_alloc_register_xlat_entry(c, c); + ADD_ARENA_SIZE(sizeof(zend_class_constant)); + zend_persist_zval_calc(&c->value); + if (ZCG(accel_directives).save_comments && c->doc_comment) { + ADD_STRING(c->doc_comment); + } + } +} + + static void zend_persist_class_entry_calc(zval *zv) { zend_class_entry *ce = Z_PTR_P(zv); @@ -318,13 +333,13 @@ static void zend_persist_class_entry_calc(zval *zv) zend_persist_zval_calc(&ce->default_static_members_table[i]); } } - zend_hash_persist_calc(&ce->constants_table, zend_persist_zval_calc); + zend_hash_persist_calc(&ce->constants_table, zend_persist_class_constant_calc); - if (ZEND_CE_FILENAME(ce)) { - ADD_STRING(ZEND_CE_FILENAME(ce)); + if (ce->info.user.filename) { + ADD_STRING(ce->info.user.filename); } - if (ZCG(accel_directives).save_comments && ZEND_CE_DOC_COMMENT(ce)) { - ADD_STRING(ZEND_CE_DOC_COMMENT(ce)); + if (ZCG(accel_directives).save_comments && ce->info.user.doc_comment) { + ADD_STRING(ce->info.user.doc_comment); } zend_hash_persist_calc(&ce->properties_info, zend_persist_property_info_calc); @@ -397,16 +412,16 @@ uint zend_accel_script_persist_calc(zend_persistent_script *new_persistent_scrip /* script is not going to be saved in SHM */ new_persistent_script->corrupted = 1; } - ADD_STRING(new_persistent_script->full_path); + ADD_STRING(new_persistent_script->script.filename); #ifdef __SSE2__ /* Align size to 64-byte boundary */ new_persistent_script->size = (new_persistent_script->size + 63) & ~63; #endif - zend_accel_persist_class_table_calc(&new_persistent_script->class_table); - zend_hash_persist_calc(&new_persistent_script->function_table, zend_persist_op_array_calc); - zend_persist_op_array_calc_ex(&new_persistent_script->main_op_array); + zend_accel_persist_class_table_calc(&new_persistent_script->script.class_table); + zend_hash_persist_calc(&new_persistent_script->script.function_table, zend_persist_op_array_calc); + zend_persist_op_array_calc_ex(&new_persistent_script->script.main_op_array); #ifdef __SSE2__ /* Align size to 64-byte boundary */ diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index 3f2f048178..b7940ad39b 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -99,7 +99,7 @@ void zend_shared_alloc_create_lock(char *lockfile_path) static void no_memory_bailout(size_t allocate_size, char *error) { - zend_accel_error(ACCEL_LOG_FATAL, "Unable to allocate shared memory segment of %ld bytes: %s: %s (%d)", allocate_size, error?error:"unknown", strerror(errno), errno ); + zend_accel_error(ACCEL_LOG_FATAL, "Unable to allocate shared memory segment of %zu bytes: %s: %s (%d)", allocate_size, error?error:"unknown", strerror(errno), errno ); } static void copy_shared_segments(void *to, void *from, int count, int size) |