From a2ff3a46a4c9b064b0866729f0a4a24fba834c1d Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 19 Jan 2016 15:33:08 +0300 Subject: Use ZEND_FUNC_INDIRECT_VAR_ASSESS instead of ZEND_FUNC_TOO_DYNAMIC. Handle function with exceptions handlers and generators separately. --- ext/opcache/Optimizer/zend_cfg.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index 728d586026..8258e0e25a 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -251,10 +251,6 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */ BB_START(0); - if ((op_array->fn_flags & ZEND_ACC_CLOSURE) && op_array->static_variables) { - // FIXME: Really we should try to perform variable initialization - flags |= ZEND_FUNC_TOO_DYNAMIC; - } for (i = 0; i < op_array->last; i++) { zend_op *opline = op_array->opcodes + i; switch(opline->opcode) { @@ -268,9 +264,9 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b } break; case ZEND_INCLUDE_OR_EVAL: + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; case ZEND_YIELD: case ZEND_YIELD_FROM: - flags |= ZEND_FUNC_TOO_DYNAMIC; if (build_flags & ZEND_CFG_STACKLESS) { BB_START(i + 1); } @@ -296,15 +292,17 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { if (fn->type == ZEND_INTERNAL_FUNCTION) { if (zend_string_equals_literal(Z_STR_P(zv), "extract")) { - flags |= ZEND_FUNC_TOO_DYNAMIC; + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "compact")) { - flags |= ZEND_FUNC_TOO_DYNAMIC; - } else if (zend_string_equals_literal(Z_STR_P(zv), "parse_str")) { - flags |= ZEND_FUNC_TOO_DYNAMIC; - } else if (zend_string_equals_literal(Z_STR_P(zv), "mb_parse_str")) { - flags |= ZEND_FUNC_TOO_DYNAMIC; + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + } else if (zend_string_equals_literal(Z_STR_P(zv), "parse_str") && + opline->extended_value == 1) { + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + } else if (zend_string_equals_literal(Z_STR_P(zv), "mb_parse_str") && + opline->extended_value == 1) { + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "get_defined_vars")) { - flags |= ZEND_FUNC_TOO_DYNAMIC; + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_num_args")) { flags |= ZEND_FUNC_VARARG; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_arg")) { @@ -316,12 +314,10 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b } break; case ZEND_FAST_CALL: - flags |= ZEND_FUNC_TOO_DYNAMIC; BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); BB_START(i + 1); break; case ZEND_FAST_RET: - flags |= ZEND_FUNC_TOO_DYNAMIC; if (i + 1 < op_array->last) { BB_START(i + 1); } @@ -350,7 +346,6 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b BB_START(i + 1); break; case ZEND_CATCH: - flags |= ZEND_FUNC_TOO_DYNAMIC; if (!opline->result.num) { BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); } @@ -371,7 +366,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; case ZEND_UNSET_VAR: if (!(opline->extended_value & ZEND_QUICK_SET)) { - flags |= ZEND_FUNC_TOO_DYNAMIC; + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; } break; case ZEND_FETCH_R: @@ -381,11 +376,11 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b case ZEND_FETCH_IS: case ZEND_FETCH_UNSET: if ((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) { - flags |= ZEND_FUNC_TOO_DYNAMIC; + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; } 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_TOO_DYNAMIC; + flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; } break; } -- cgit v1.2.1 From a8900b563643dffe74c5be5d400ee31be0ce7df5 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 19 Jan 2016 15:54:44 +0300 Subject: Typo (ASSESS->ACCESS) --- ext/opcache/Optimizer/zend_cfg.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index 8258e0e25a..9dd0367776 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -264,7 +264,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b } break; case ZEND_INCLUDE_OR_EVAL: - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; case ZEND_YIELD: case ZEND_YIELD_FROM: if (build_flags & ZEND_CFG_STACKLESS) { @@ -292,17 +292,17 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { if (fn->type == ZEND_INTERNAL_FUNCTION) { if (zend_string_equals_literal(Z_STR_P(zv), "extract")) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "compact")) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "parse_str") && opline->extended_value == 1) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "mb_parse_str") && opline->extended_value == 1) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "get_defined_vars")) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_num_args")) { flags |= ZEND_FUNC_VARARG; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_arg")) { @@ -366,7 +366,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; case ZEND_UNSET_VAR: if (!(opline->extended_value & ZEND_QUICK_SET)) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } break; case ZEND_FETCH_R: @@ -376,11 +376,11 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b case ZEND_FETCH_IS: case ZEND_FETCH_UNSET: if ((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) { - flags |= ZEND_FUNC_INDIRECT_VAR_ASSESS; + 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_ASSESS; + flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } break; } -- cgit v1.2.1 From dcf3db6ac86d5a2fb58cd337b691de820c160c3f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 19 Jan 2016 22:25:21 +0100 Subject: Mark isset($$var) as INDIRECT_VAR_ACCESS --- ext/opcache/Optimizer/zend_cfg.c | 1 + 1 file changed, 1 insertion(+) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index 9dd0367776..b8121808d2 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -365,6 +365,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b BB_START(i + 1); break; case ZEND_UNSET_VAR: + case ZEND_ISSET_ISEMPTY_VAR: if (!(opline->extended_value & ZEND_QUICK_SET)) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } -- cgit v1.2.1 From 9b854ebab4ddd729a878b094268f639d2fa7b228 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Fri, 22 Jan 2016 13:32:20 +0300 Subject: More accurate handling of isset() and unset() --- ext/opcache/Optimizer/zend_cfg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index b8121808d2..a7122319e7 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -366,7 +366,12 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; case ZEND_UNSET_VAR: case ZEND_ISSET_ISEMPTY_VAR: - if (!(opline->extended_value & ZEND_QUICK_SET)) { + 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; -- cgit v1.2.1 From 949aaea66ee2bd7171e802a2f1a4fa2efbd03e6c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 23 Jan 2016 16:27:19 +0100 Subject: Don't insert duplicate predecessors Otherwise we'll get corrupt phis --- ext/opcache/Optimizer/zend_cfg.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index a7122319e7..6df4d3c6b8 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -535,7 +535,7 @@ int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */ if (b->successors[0] >= 0) { edges++; blocks[b->successors[0]].predecessors_count++; - if (b->successors[1] >= 0) { + if (b->successors[1] >= 0 && b->successors[1] != b->successors[0]) { edges++; blocks[b->successors[1]].predecessors_count++; } @@ -564,7 +564,8 @@ int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */ 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) { + 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++; -- cgit v1.2.1 From 7174af40748ff0c2ac77481f5710ee20b41985fa Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 9 Feb 2016 12:40:02 +0100 Subject: Support CFG construction without live range splitting We must not split at live range boundaries for SSA constructions, otherwise an OP_DATA instruction may be separated into new block and not picked up during renaming. It's also unnecessary for this use case and only blows up the CFG. --- ext/opcache/Optimizer/zend_cfg.c | 61 ++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 27 deletions(-) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index 6df4d3c6b8..3e50bb910c 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -91,33 +91,35 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg * do { changed = 0; - /* Add brk/cont paths */ - for (j = 0; j < op_array->last_live_range; j++) { - if (op_array->live_range[j].var == (uint32_t)-1) { - /* this live range already removed */ - continue; - } - b = blocks + block_map[op_array->live_range[j].start]; - if (b->flags & ZEND_BB_REACHABLE) { - while (op_array->opcodes[b->start].opcode == ZEND_NOP && b->start != b->end) { - b->start++; - } - if (op_array->opcodes[b->start].opcode == ZEND_NOP && - b->start == b->end && - b->successors[0] == block_map[op_array->live_range[j].end]) { - /* mark as removed (empty live range) */ - op_array->live_range[j].var = (uint32_t)-1; + if (cfg->split_at_live_ranges) { + /* Add live range paths */ + for (j = 0; j < op_array->last_live_range; j++) { + if (op_array->live_range[j].var == (uint32_t)-1) { + /* this live range already removed */ continue; } - b->flags |= ZEND_BB_GEN_VAR; - b = blocks + block_map[op_array->live_range[j].end]; - b->flags |= ZEND_BB_KILL_VAR; - if (!(b->flags & ZEND_BB_REACHABLE)) { - changed = 1; - zend_mark_reachable(op_array->opcodes, blocks, b); + b = blocks + block_map[op_array->live_range[j].start]; + if (b->flags & ZEND_BB_REACHABLE) { + while (op_array->opcodes[b->start].opcode == ZEND_NOP && b->start != b->end) { + b->start++; + } + if (op_array->opcodes[b->start].opcode == ZEND_NOP && + b->start == b->end && + b->successors[0] == block_map[op_array->live_range[j].end]) { + /* mark as removed (empty live range) */ + op_array->live_range[j].var = (uint32_t)-1; + continue; + } + b->flags |= ZEND_BB_GEN_VAR; + b = blocks + block_map[op_array->live_range[j].end]; + b->flags |= ZEND_BB_KILL_VAR; + if (!(b->flags & ZEND_BB_REACHABLE)) { + changed = 1; + zend_mark_reachable(op_array->opcodes, blocks, b); + } + } else { + ZEND_ASSERT(!(blocks[block_map[op_array->live_range[j].end]].flags & ZEND_BB_REACHABLE)); } - } else { - ZEND_ASSERT(!(blocks[block_map[op_array->live_range[j].end]].flags & ZEND_BB_REACHABLE)); } } @@ -244,6 +246,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b zend_basic_block *blocks; zval *zv; + cfg->split_at_live_ranges = (build_flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES) != 0; cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t)); if (!block_map) { return FAILURE; @@ -391,10 +394,14 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b break; } } - 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 (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); -- cgit v1.2.1 From 8d758e7be9fe4a54425f0f7bd71744e22eb7b77f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 27 Feb 2016 15:02:48 +0100 Subject: Fix dominator tree construction The used dominator intersection algorithm assumes a postorder numbering of the CFG. The reversal of our natural numbering is quite similar to postorder, but not the same. In the future we should precompute both preorder/postorder numberings and orderings, as these are useful in many places. --- ext/opcache/Optimizer/zend_cfg.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'ext/opcache/Optimizer/zend_cfg.c') diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c index 3e50bb910c..63f14634cb 100644 --- a/ext/opcache/Optimizer/zend_cfg.c +++ b/ext/opcache/Optimizer/zend_cfg.c @@ -585,16 +585,45 @@ int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */ } /* }}} */ +/* 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; @@ -612,8 +641,8 @@ int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cf if (blocks[pred].idom >= 0) { while (idom != pred) { - while (pred > idom) pred = blocks[pred].idom; - while (idom > pred) idom = blocks[idom].idom; + while (postnum[pred] < postnum[idom]) pred = blocks[pred].idom; + while (postnum[idom] < postnum[pred]) idom = blocks[idom].idom; } } } @@ -664,6 +693,7 @@ int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cf blocks[j].level = level; } + free_alloca(postnum, use_heap); return SUCCESS; } /* }}} */ -- cgit v1.2.1