forked from M-Labs/artiq
compiler.analyses.domination: implement new dominator tree algorithm.
This commit is contained in:
parent
73c22b0b1e
commit
19fae9181c
|
@ -2,65 +2,154 @@
|
||||||
:class:`DominatorTree` computes the dominance relation over
|
:class:`DominatorTree` computes the dominance relation over
|
||||||
control flow graphs.
|
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
|
def _start_blocks(self):
|
||||||
# If a node dominates all
|
"""
|
||||||
# predecessors of node n, then it
|
Returns a starting collection of basic blocks (entry block
|
||||||
# also dominates node n
|
for dominator tree and exit blocks for postdominator tree).
|
||||||
class DominatorTree:
|
"""
|
||||||
def __init__(self, func):
|
raise NotImplementedError
|
||||||
entry = func.entry()
|
|
||||||
|
|
||||||
self.dominated_by = { entry: {entry} }
|
def _next_blocks(self, block):
|
||||||
for block in func.basic_blocks:
|
"""
|
||||||
if block != entry:
|
Returns the collection of blocks to be traversed after `block`
|
||||||
self.dominated_by[block] = set(func.basic_blocks)
|
(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
|
changed = False
|
||||||
|
for block_name in reversed(range(self._last_name)):
|
||||||
for block in func.basic_blocks:
|
if block_name in start_block_names:
|
||||||
if block == entry:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_dominated_by = {block}.union(
|
new_idom, prev_block_names = None, []
|
||||||
reduce(lambda a, b: a.intersection(b),
|
for prev_block_name in self._prev_block_names(block_name):
|
||||||
(self.dominated_by[pred] for pred in predecessors[block])))
|
if new_idom is None and self._doms[prev_block_name] is not None:
|
||||||
if new_dominated_by != self.dominated_by[block]:
|
new_idom = prev_block_name
|
||||||
self.dominated_by[block] = new_dominated_by
|
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
|
changed = True
|
||||||
|
|
||||||
if not changed:
|
def immediate_dominator(self, block):
|
||||||
break
|
return self._block_of_name[self._doms[self._name_of_block[block]]]
|
||||||
|
|
||||||
class PostDominatorTree:
|
def dominators(self, block):
|
||||||
def __init__(self, func):
|
yield block
|
||||||
exits = [block for block in func.basic_blocks if none(block.successors())]
|
|
||||||
|
|
||||||
self.dominated_by = { exit: {exit} for exit in exits }
|
block_name = self._name_of_block[block]
|
||||||
for block in func.basic_blocks:
|
while block_name != self._doms[block_name]:
|
||||||
if block != entry:
|
block_name = self._doms[block_name]
|
||||||
self.dominated_by[block] = set(func.basic_blocks)
|
yield self._block_of_name[block_name]
|
||||||
|
|
||||||
successors = {block: block.successors() for block in func.basic_blocks}
|
class DominatorTree(GenericDominatorTree):
|
||||||
while True:
|
def __init__(self, function):
|
||||||
changed = False
|
self.function = function
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
for block in func.basic_blocks:
|
def _start_blocks(self):
|
||||||
if block in exits:
|
return [self.function.entry()]
|
||||||
continue
|
|
||||||
|
|
||||||
new_dominated_by = {block}.union(
|
def _next_blocks(self, block):
|
||||||
reduce(lambda a, b: a.intersection(b),
|
return block.successors()
|
||||||
(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
|
|
||||||
|
|
||||||
if not changed:
|
def _prev_blocks(self, block):
|
||||||
break
|
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()
|
||||||
|
|
|
@ -38,8 +38,8 @@ class LocalAccessValidator:
|
||||||
|
|
||||||
# Traverse the acyclic graph made of basic blocks and forward edges only,
|
# Traverse the acyclic graph made of basic blocks and forward edges only,
|
||||||
# while updating the environment state.
|
# while updating the environment state.
|
||||||
dom = analyses.DominatorTree(func)
|
domtree = analyses.DominatorTree(func)
|
||||||
state = {}
|
state = {}
|
||||||
def traverse(block):
|
def traverse(block):
|
||||||
# Have we computed the state of this block already?
|
# Have we computed the state of this block already?
|
||||||
if block in state:
|
if block in state:
|
||||||
|
@ -48,7 +48,7 @@ class LocalAccessValidator:
|
||||||
# No! Which forward edges lead to this block?
|
# No! Which forward edges lead to this block?
|
||||||
# If we dominate a predecessor, it's a back edge instead.
|
# If we dominate a predecessor, it's a back edge instead.
|
||||||
forward_edge_preds = [pred for pred in block.predecessors()
|
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
|
# Figure out what the state is before the leader
|
||||||
# instruction of this block.
|
# instruction of this block.
|
||||||
|
|
|
@ -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))
|
Loading…
Reference in New Issue