Skip to content

Commit 6633e84

Browse files
committed
Sort blocks in loop identification
The previous implementation was doing O(blocks*levels) iterations, which for a linear-ish domtree is O(blocks^2). Avoid this by sorting the blocks by decreasing level upfront.
1 parent 63e59c7 commit 6633e84

File tree

1 file changed

+62
-45
lines changed

1 file changed

+62
-45
lines changed

ext/opcache/Optimizer/zend_cfg.c

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -749,16 +749,31 @@ static int dominates(zend_basic_block *blocks, int a, int b) /* {{{ */
749749
}
750750
/* }}} */
751751

752+
typedef struct {
753+
int id;
754+
int level;
755+
} block_info;
756+
static int compare_block_level(const block_info *a, const block_info *b) {
757+
return b->level - a->level;
758+
}
759+
static void swap_blocks(block_info *a, block_info *b) {
760+
block_info tmp = *a;
761+
*a = *b;
762+
*b = tmp;
763+
}
764+
752765
int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32_t *flags) /* {{{ */
753766
{
754-
int i, j, k;
767+
int i, j, k, n;
755768
int depth;
756769
zend_basic_block *blocks = cfg->blocks;
757770
int *dj_spanning_tree;
758771
zend_worklist work;
759772
int flag = ZEND_FUNC_NO_LOOPS;
773+
block_info *sorted_blocks;
760774
ALLOCA_FLAG(list_use_heap)
761775
ALLOCA_FLAG(tree_use_heap)
776+
ALLOCA_FLAG(sorted_blocks_use_heap)
762777

763778
ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap);
764779
dj_spanning_tree = do_alloca(sizeof(int) * cfg->blocks_count, tree_use_heap);
@@ -792,64 +807,66 @@ int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32
792807
zend_worklist_pop(&work);
793808
}
794809

810+
/* Sort blocks by decreasing level, which is the order in which we want to process them */
811+
sorted_blocks = do_alloca(sizeof(block_info) * cfg->blocks_count, sorted_blocks_use_heap);
812+
for (i = 0; i < cfg->blocks_count; i++) {
813+
sorted_blocks[i].id = i;
814+
sorted_blocks[i].level = blocks[i].level;
815+
}
816+
zend_sort(sorted_blocks, cfg->blocks_count, sizeof(block_info),
817+
(compare_func_t) compare_block_level, (swap_func_t) swap_blocks);
818+
795819
/* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ
796820
Graphs". */
797821

798-
for (i = 0, depth = 0; i < cfg->blocks_count; i++) {
799-
if (blocks[i].level > depth) {
800-
depth = blocks[i].level;
801-
}
802-
}
803-
for (; depth >= 0; depth--) {
804-
for (i = 0; i < cfg->blocks_count; i++) {
805-
if (blocks[i].level != depth) {
822+
for (n = 0; n < cfg->blocks_count; n++) {
823+
i = sorted_blocks[n].id;
824+
825+
zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count));
826+
for (j = 0; j < blocks[i].predecessors_count; j++) {
827+
int pred = cfg->predecessors[blocks[i].predecessor_offset + j];
828+
829+
/* A join edge is one for which the predecessor does not
830+
immediately dominate the successor. */
831+
if (blocks[i].idom == pred) {
806832
continue;
807833
}
808-
zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count));
809-
for (j = 0; j < blocks[i].predecessors_count; j++) {
810-
int pred = cfg->predecessors[blocks[i].predecessor_offset + j];
811834

812-
/* A join edge is one for which the predecessor does not
813-
immediately dominate the successor. */
814-
if (blocks[i].idom == pred) {
815-
continue;
816-
}
817-
818-
/* In a loop back-edge (back-join edge), the successor dominates
819-
the predecessor. */
820-
if (dominates(blocks, i, pred)) {
821-
blocks[i].flags |= ZEND_BB_LOOP_HEADER;
822-
flag &= ~ZEND_FUNC_NO_LOOPS;
823-
zend_worklist_push(&work, pred);
824-
} else {
825-
/* Otherwise it's a cross-join edge. See if it's a branch
826-
to an ancestor on the dominator spanning tree. */
827-
int dj_parent = pred;
828-
while (dj_parent >= 0) {
829-
if (dj_parent == i) {
830-
/* An sp-back edge: mark as irreducible. */
831-
blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP;
832-
flag |= ZEND_FUNC_IRREDUCIBLE;
833-
flag &= ~ZEND_FUNC_NO_LOOPS;
834-
break;
835-
} else {
836-
dj_parent = dj_spanning_tree[dj_parent];
837-
}
835+
/* In a loop back-edge (back-join edge), the successor dominates
836+
the predecessor. */
837+
if (dominates(blocks, i, pred)) {
838+
blocks[i].flags |= ZEND_BB_LOOP_HEADER;
839+
flag &= ~ZEND_FUNC_NO_LOOPS;
840+
zend_worklist_push(&work, pred);
841+
} else {
842+
/* Otherwise it's a cross-join edge. See if it's a branch
843+
to an ancestor on the dominator spanning tree. */
844+
int dj_parent = pred;
845+
while (dj_parent >= 0) {
846+
if (dj_parent == i) {
847+
/* An sp-back edge: mark as irreducible. */
848+
blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP;
849+
flag |= ZEND_FUNC_IRREDUCIBLE;
850+
flag &= ~ZEND_FUNC_NO_LOOPS;
851+
break;
852+
} else {
853+
dj_parent = dj_spanning_tree[dj_parent];
838854
}
839855
}
840856
}
841-
while (zend_worklist_len(&work)) {
842-
j = zend_worklist_pop(&work);
843-
if (blocks[j].loop_header < 0 && j != i) {
844-
blocks[j].loop_header = i;
845-
for (k = 0; k < blocks[j].predecessors_count; k++) {
846-
zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]);
847-
}
857+
}
858+
while (zend_worklist_len(&work)) {
859+
j = zend_worklist_pop(&work);
860+
if (blocks[j].loop_header < 0 && j != i) {
861+
blocks[j].loop_header = i;
862+
for (k = 0; k < blocks[j].predecessors_count; k++) {
863+
zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]);
848864
}
849865
}
850866
}
851867
}
852868

869+
free_alloca(sorted_blocks, sorted_blocks_use_heap);
853870
free_alloca(dj_spanning_tree, tree_use_heap);
854871
ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap);
855872
*flags |= flag;

0 commit comments

Comments
 (0)