From: Dmitry Stogov Date: Mon, 29 Aug 2016 09:07:54 +0000 (+0300) Subject: Merge branch 'PHP-7.0' into PHP-7.1 X-Git-Tag: php-7.1.0RC1~43 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b5bab0fce89f2f4dd3e04bce60dcd2bcd85f6a03;p=php Merge branch 'PHP-7.0' into PHP-7.1 * PHP-7.0: Fixed bug #72944 (Null pointer deref in zval_delref_p). --- b5bab0fce89f2f4dd3e04bce60dcd2bcd85f6a03 diff --cc NEWS index 30415fe2c9,522076e9c7..6dc4f1aeef --- a/NEWS +++ b/NEWS @@@ -1,10 -1,16 +1,11 @@@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? 2016 PHP 7.0.11 +?? ??? 2016, PHP 7.1.0RC1 - Core: + . Fixed bug #72944 (Null pointer deref in zval_delref_p). (Dmitry) . Fixed bug #72943 (assign_dim on string doesn't reset hval). (Laruence) - . Fixed bug #72911 (Memleak in zend_binary_assign_op_obj_helper). (Laruence) - . Fixed bug #72813 (Segfault with __get returned by ref). (Laruence) - . Fixed bug #72767 (PHP Segfaults when trying to expand an infinite operator). - (Nikita) - . Fixed bug #72854 (PHP Crashes on duplicate destructor call). (Nikita) - . Fixed bug #72857 (stream_socket_recvfrom read access violation). (Anatol) + . Fixed bug #72598 (Reference is lost after array_slice()) (Nikita) - COM: . Fixed bug #72922 (COM called from PHP does not return out parameters). diff --cc Zend/zend_compile.h index 56b80eed6a,f946448ffa..1f0773d9a7 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@@ -790,7 -760,8 +790,8 @@@ ZEND_API zend_bool zend_is_compiling(vo ZEND_API char *zend_make_compiled_string_description(const char *name); ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify_handlers); uint32_t zend_get_class_fetch_type(zend_string *name); -ZEND_API zend_uchar zend_get_call_op(zend_uchar init_op, zend_function *fbc); +ZEND_API zend_uchar zend_get_call_op(const zend_op *init_op, zend_function *fbc); + ZEND_API int zend_is_smart_branch(zend_op *opline); typedef zend_bool (*zend_auto_global_callback)(zend_string *name); typedef struct _zend_auto_global { diff --cc ext/opcache/Optimizer/block_pass.c index 3bd86e60c0,4b532c4b20..3490a73e03 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@@ -67,32 -67,382 +67,41 @@@ int zend_optimizer_get_persistent_const 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(from, to) +#define ADD_SOURCE(from, to) -#define DEL_SOURCE(cs) do { \ - *(cs) = (*(cs))->next; \ - } while (0) +/* Data dependencies macros */ +#define VAR_NUM_EX(op) VAR_NUM((op).var) -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; - } - } +#define VAR_SOURCE(op) Tsource[VAR_NUM(op.var)] +#define SET_VAR_SOURCE(opline) Tsource[VAR_NUM(opline->result.var)] = opline - if ((*cs)->from == old) { - if (found) { - DEL_SOURCE(cs); - } else { - (*cs)->from = new; - found = 1; - } - } +#define convert_to_string_safe(v) \ + if (Z_TYPE_P((v)) == IS_NULL) { \ + ZVAL_STRINGL((v), "", 0); \ + } else { \ + convert_to_string((v)); \ } -} -static inline void del_source(zend_code_block *from, zend_code_block *to) +static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b) { - /* 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); + 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; + } - 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 */ - } + b->start++; + b->len--; } } @@@ -105,21 -484,64 +114,29 @@@ static void strip_nops(zend_op_array *o 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; + /* strip the inside NOPs */ + i = j = b->start + 1; + while (i < b->start + b->len) { + if (op_array->opcodes[i].opcode != ZEND_NOP) { + if (i != j) { + op_array->opcodes[j] = op_array->opcodes[i]; + } + j++; } - 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); ++ if (i + 1 < b->start + b->len ++ && (op_array->opcodes[i+1].opcode == ZEND_JMPZ ++ || op_array->opcodes[i+1].opcode == ZEND_JMPNZ) ++ && op_array->opcodes[i+1].op1_type & (IS_CV|IS_CONST) ++ && zend_is_smart_branch(op_array->opcodes + j - 1)) { ++ /* don't remove NOP, that splits incorrect smart branch */ ++ j++; + } + i++; } - - /* 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); - } - } + b->len = j - b->start; + while (j < i) { + MAKE_NOP(op_array->opcodes + j); + j++; } } diff --cc ext/opcache/Optimizer/dfa_pass.c index a99d50fc41,0000000000..63faaf62c3 mode 100644,000000..100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@@ -1,658 -1,0 +1,645 @@@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 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 | + +----------------------------------------------------------------------+ +*/ + +#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) && - (op_array->opcodes[i-1].opcode == ZEND_IS_IDENTICAL || - op_array->opcodes[i-1].opcode == ZEND_IS_NOT_IDENTICAL || - op_array->opcodes[i-1].opcode == ZEND_IS_EQUAL || - op_array->opcodes[i-1].opcode == ZEND_IS_NOT_EQUAL || - op_array->opcodes[i-1].opcode == ZEND_IS_SMALLER || - op_array->opcodes[i-1].opcode == ZEND_IS_SMALLER_OR_EQUAL || - op_array->opcodes[i-1].opcode == ZEND_CASE || - op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_VAR || - op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_STATIC_PROP || - op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ || - op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_PROP_OBJ || - op_array->opcodes[i-1].opcode == ZEND_INSTANCEOF || - op_array->opcodes[i-1].opcode == ZEND_TYPE_CHECK || - op_array->opcodes[i-1].opcode == ZEND_DEFINED))) { ++ 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; + + opline = op_array->opcodes + end - 1; + b->len = target - b->start; + 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]; + + /* 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; + } + + 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; + int ret = ssa->vars[v].use_chain; + + ssa->vars[orig_var].use_chain = ret; + ssa->ops[ret].op1_use = orig_var; + + 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 --cc ext/opcache/Optimizer/zend_cfg.c index 92d648dc23,0000000000..04be6a2e26 mode 100644,000000..100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@@ -1,846 -1,0 +1,855 @@@ +/* + +----------------------------------------------------------------------+ + | Zend Engine, CFG - Control Flow Graph | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 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 | + +----------------------------------------------------------------------+ +*/ + +#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; + } + } + } + } 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)) { + if (cfg->split_at_live_ranges) { + changed = 1; + zend_mark_reachable(op_array->opcodes, cfg, b); + } else { + ZEND_ASSERT(!(b->flags & ZEND_BB_UNREACHABLE_FREE)); + 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->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_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; +} +/* }}} */ + +int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32_t *flags) /* {{{ */ +{ + int i, j, k; + int depth; + zend_basic_block *blocks = cfg->blocks; + int *dj_spanning_tree; + zend_worklist work; + int flag = ZEND_FUNC_NO_LOOPS; + ALLOCA_FLAG(list_use_heap) + ALLOCA_FLAG(tree_use_heap) + + ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap); + dj_spanning_tree = do_alloca(sizeof(int) * cfg->blocks_count, tree_use_heap); + + for (i = 0; i < cfg->blocks_count; i++) { + dj_spanning_tree[i] = -1; + } + zend_worklist_push(&work, 0); + while (zend_worklist_len(&work)) { + next: + i = zend_worklist_peek(&work); + /* Visit blocks immediately dominated by i. */ + for (j = blocks[i].children; j >= 0; j = blocks[j].next_child) { + if (zend_worklist_push(&work, j)) { + dj_spanning_tree[j] = i; + 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)) { + dj_spanning_tree[succ] = i; + goto next; + } + } + zend_worklist_pop(&work); + } + + /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ + Graphs". */ + + for (i = 0, depth = 0; i < cfg->blocks_count; i++) { + if (blocks[i].level > depth) { + depth = blocks[i].level; + } + } + for (; depth >= 0; depth--) { + for (i = 0; i < cfg->blocks_count; i++) { + if (blocks[i].level != depth) { + continue; + } + 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 dominator spanning tree. */ + int dj_parent = pred; + while (dj_parent >= 0) { + if (dj_parent == i) { + /* An sp-back edge: mark as irreducible. */ + blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP; + flag |= ZEND_FUNC_IRREDUCIBLE; + flag &= ~ZEND_FUNC_NO_LOOPS; + break; + } else { + dj_parent = dj_spanning_tree[dj_parent]; + } + } + } + } + while (zend_worklist_len(&work)) { + j = zend_worklist_pop(&work); + if (blocks[j].loop_header < 0 && 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(dj_spanning_tree, 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: + */