diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py index 50aacc52d..7b6b54f72 100644 --- a/artiq/compiler/analyses/domination.py +++ b/artiq/compiler/analyses/domination.py @@ -2,65 +2,154 @@ :class:`DominatorTree` computes the dominance relation over control flow graphs. -See http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf. +See http://www.cs.rice.edu/~keith/EMBED/dom.pdf. """ -from functools import reduce, cmp_to_key +class GenericDominatorTree: + def __init__(self): + self._assign_names() + self._compute() -# Key Idea -# If a node dominates all -# predecessors of node n, then it -# also dominates node n -class DominatorTree: - def __init__(self, func): - entry = func.entry() + def _start_blocks(self): + """ + Returns a starting collection of basic blocks (entry block + for dominator tree and exit blocks for postdominator tree). + """ + raise NotImplementedError - self.dominated_by = { entry: {entry} } - for block in func.basic_blocks: - if block != entry: - self.dominated_by[block] = set(func.basic_blocks) + def _next_blocks(self, block): + """ + Returns the collection of blocks to be traversed after `block` + (successors for dominator tree and predecessors for postdominator + tree). + """ + raise NotImplementedError + + def _prev_blocks(self, block): + """ + Returns the collection of blocks to be traversed before `block` + (predecessors for dominator tree and successors for postdominator + tree). + """ + raise NotImplementedError + + def _assign_names(self): + """Assigns names to basic blocks in postorder.""" + visited = set() + postorder = [] + + def visit(block): + visited.add(block) + for next_block in self._next_blocks(block): + if next_block not in visited: + visit(next_block) + postorder.append(block) + + for block in self._start_blocks(): + visit(block) + + self._last_name = len(postorder) + self._block_of_name = postorder + self._name_of_block = {} + for block_name, block in enumerate(postorder): + # print("name", block_name + 1, block.name) + self._name_of_block[block] = block_name + + def _start_block_names(self): + for block in self._start_blocks(): + yield self._name_of_block[block] + + def _next_block_names(self, block_name): + for block in self._next_blocks(self._block_of_name[block_name]): + yield self._name_of_block[block] + + def _prev_block_names(self, block_name): + for block in self._prev_blocks(self._block_of_name[block_name]): + yield self._name_of_block[block] + + def _intersect(self, block_name_1, block_name_2): + finger_1, finger_2 = block_name_1, block_name_2 + while finger_1 != finger_2: + while finger_1 < finger_2: + finger_1 = self._doms[finger_1] + while finger_2 < finger_1: + finger_2 = self._doms[finger_2] + return finger_1 + + def _compute(self): + self._doms = {} + + for block_name in range(self._last_name): + self._doms[block_name] = None + + start_block_names = set() + for block_name in self._start_block_names(): + self._doms[block_name] = block_name + start_block_names.add(block_name) + + changed = True + while changed: + # print("doms", {k+1: self._doms[k]+1 if self._doms[k] is not None else None for k in self._doms}) - predecessors = {block: block.predecessors() for block in func.basic_blocks} - while True: changed = False - - for block in func.basic_blocks: - if block == entry: + for block_name in reversed(range(self._last_name)): + if block_name in start_block_names: continue - new_dominated_by = {block}.union( - reduce(lambda a, b: a.intersection(b), - (self.dominated_by[pred] for pred in predecessors[block]))) - if new_dominated_by != self.dominated_by[block]: - self.dominated_by[block] = new_dominated_by + new_idom, prev_block_names = None, [] + for prev_block_name in self._prev_block_names(block_name): + if new_idom is None and self._doms[prev_block_name] is not None: + new_idom = prev_block_name + else: + prev_block_names.append(prev_block_name) + + # print("block_name", block_name + 1, "new_idom", new_idom + 1) + for prev_block_name in prev_block_names: + # print("prev_block_name", prev_block_name + 1) + if self._doms[prev_block_name] is not None: + new_idom = self._intersect(prev_block_name, new_idom) + # print("new_idom+", new_idom + 1) + + if self._doms[block_name] != new_idom: + self._doms[block_name] = new_idom changed = True - if not changed: - break + def immediate_dominator(self, block): + return self._block_of_name[self._doms[self._name_of_block[block]]] -class PostDominatorTree: - def __init__(self, func): - exits = [block for block in func.basic_blocks if none(block.successors())] + def dominators(self, block): + yield block - self.dominated_by = { exit: {exit} for exit in exits } - for block in func.basic_blocks: - if block != entry: - self.dominated_by[block] = set(func.basic_blocks) + block_name = self._name_of_block[block] + while block_name != self._doms[block_name]: + block_name = self._doms[block_name] + yield self._block_of_name[block_name] - successors = {block: block.successors() for block in func.basic_blocks} - while True: - changed = False +class DominatorTree(GenericDominatorTree): + def __init__(self, function): + self.function = function + super().__init__() - for block in func.basic_blocks: - if block in exits: - continue + def _start_blocks(self): + return [self.function.entry()] - new_dominated_by = {block}.union( - reduce(lambda a, b: a.intersection(b), - (self.dominated_by[pred] for pred in successors[block]))) - if new_dominated_by != self.dominated_by[block]: - self.dominated_by[block] = new_dominated_by - changed = True + def _next_blocks(self, block): + return block.successors() - if not changed: - break + def _prev_blocks(self, block): + return block.predecessors() + +class PostDominatorTree(GenericDominatorTree): + def __init__(self, function): + self.function = function + super().__init__() + + def _start_blocks(self): + return [block for block in self.function.basic_blocks + if none(block.successors())] + + def _next_blocks(self, block): + return block.predecessors() + + def _prev_blocks(self, block): + return block.successors() diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index f3429140e..fed87092b 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -38,8 +38,8 @@ class LocalAccessValidator: # Traverse the acyclic graph made of basic blocks and forward edges only, # while updating the environment state. - dom = analyses.DominatorTree(func) - state = {} + domtree = analyses.DominatorTree(func) + state = {} def traverse(block): # Have we computed the state of this block already? if block in state: @@ -48,7 +48,7 @@ class LocalAccessValidator: # No! Which forward edges lead to this block? # If we dominate a predecessor, it's a back edge instead. forward_edge_preds = [pred for pred in block.predecessors() - if block not in dom.dominated_by[pred]] + if block not in domtree.dominators(pred)] # Figure out what the state is before the leader # instruction of this block. diff --git a/artiq/test/compiler/domination.py b/artiq/test/compiler/domination.py new file mode 100644 index 000000000..fa1c79ea0 --- /dev/null +++ b/artiq/test/compiler/domination.py @@ -0,0 +1,115 @@ +import unittest +from artiq.compiler.analyses.domination import DominatorTree, PostDominatorTree + +class MockBasicBlock: + def __init__(self, name): + self.name = name + self._successors = [] + self._predecessors = [] + + def successors(self): + return self._successors + + def predecessors(self): + return self._predecessors + + def set_successors(self, successors): + self._successors = list(successors) + for block in self._successors: + block._predecessors.append(self) + +class MockFunction: + def __init__(self, entry, basic_blocks): + self._entry = entry + self.basic_blocks = basic_blocks + + def entry(self): + return self._entry + +def makefn(entry_name, graph): + blocks = {} + for block_name in graph: + blocks[block_name] = MockBasicBlock(block_name) + for block_name in graph: + successors = list(map(lambda name: blocks[name], graph[block_name])) + blocks[block_name].set_successors(successors) + return MockFunction(blocks[entry_name], blocks.values()) + +def dom(function, domtree): + dom = {} + for block in function.basic_blocks: + dom[block.name] = [dom_block.name for dom_block in domtree.dominators(block)] + return dom + +def idom(function, domtree): + idom = {} + for block in function.basic_blocks: + idom[block.name] = domtree.immediate_dominator(block).name + return idom + +class TestDominatorTree(unittest.TestCase): + def test_linear(self): + func = makefn('A', { + 'A': ['B'], + 'B': ['C'], + 'C': [] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 'C': 'B', 'B': 'A', 'A': 'A' + }, idom(func, domtree)) + self.assertEqual({ + 'C': ['C', 'B', 'A'], 'B': ['B', 'A'], 'A': ['A'] + }, dom(func, domtree)) + + def test_diamond(self): + func = makefn('A', { + 'A': ['C', 'B'], + 'B': ['D'], + 'C': ['D'], + 'D': [] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 'D': 'A', 'C': 'A', 'B': 'A', 'A': 'A' + }, idom(func, domtree)) + + def test_combined(self): + func = makefn('A', { + 'A': ['B', 'D'], + 'B': ['C'], + 'C': ['E'], + 'D': ['E'], + 'E': [] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 'A': 'A', 'B': 'A', 'C': 'B', 'D': 'A', 'E': 'A' + }, idom(func, domtree)) + + def test_figure_2(self): + func = makefn(5, { + 5: [3, 4], + 4: [1], + 3: [2], + 2: [1], + 1: [2] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 1: 5, 2: 5, 3: 5, 4: 5, 5: 5 + }, idom(func, domtree)) + + def test_figure_4(self): + func = makefn(6, { + 6: [4, 5], + 5: [1], + 4: [3, 2], + 3: [2], + 2: [1, 3], + 1: [2] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 1: 6, 2: 6, 3: 6, 4: 6, 5: 6, 6: 6 + }, idom(func, domtree))