支配树这个结构在后端优化中有很重要的作用,尤其是SSA格式的程序中,高效的支配树生成尤其重要。
我们说对于一个图G=(V,E,entry),
- 节点m支配节点n当且仅当从流图入口entry到n的所有路径都经过m,记做:m dom n。
- 所有支配n的节点组成一个集合叫做支配集,记做dom(n)。
- 所有节点均支配自己,即 ∀ \forall ∀n ∈ \in ∈V,n dom n。
- 如果存在节点x,y,z,x dom y,y dom z,则x dom z。
- 很显然,支配关系具有自反性,反对称性和传递性,所以支配关系是G上的一个偏序关系,由此我们可以根据节点间的关系构造一个树形结构,这个树形结构也被叫做支配树。
- 在支配树上,如果一个节点n的父节点是m,则称m是n的立即支配节点,记做m=idom(n)。
- 对dom(n)中的一个节点m,如果m ≠ \neq ̸=n,则说m严格支配n,记做m sdom n。
- 与支配对应的还有后支配,即从n到流图出口处的所有路径均进过m,记做m postdom n。
计算支配关系主要有数据流法, Lengauer-Tarjan法和消去法,其中前两种是主流算法。
第一个算法是最简单的,通过计算LCA得到idom从而构造出支配树,其实也就是常规的数据流算法的基本方法。对于一个有向无环图,如果有一个非root的子节点n,其前继为n1…nk,那么节点m dom n当且仅当m dom (n的所有前继n1…nk),也就是说m是n1…nk在支配树上的公共祖先,而idom(n)也就是idom(n1)…idom(nk)的最近公共祖先了。
idom(n)=LCA(idom(pred(n))) (G是可规约流图)
Ramalingam在论文中给出的伪代码[2]:
function ConstructDominatorTree(G : Acyclic Graph)
DomTree := an empty tree;
add entry(G) as the root of DomTree ;
for every vertex u in V (G) − { entry(G) } in topological sort order do
let z denote the least-common-ancestor, in DomTree, of u’s predecessors;
add u as a child of z in DomTree ;
end for
return DomTree;
这种算法在我之前分析libpcap源码时已经出现了,在函数find_dom中,其计算LCA用的是位向量的交集运算。
而对于一个带循环的可规约流图,如果去除其中的所有回边,整个流图就退化成了一个有向无环图。根据循环的性质,在可规约流图中循环头是支配其循环体中所有结点的,也就是说,回边的存在并不会改变支配关系。所以对于可规约流图,可以简单的DFS遍历去除掉回边后再使用上面的算法来计算支配关系。
无论是DAG还是带循环的可规约流图,都只需要计算一次就可以得到支配树,因为在计算子节点时,父节点的数据流值已经被计算完毕了。而对于不可规约流图,由于在遍历顺序问题,在计算一些节点的LCA时,其前继的LCA可能还没计算:
上图中,在计算B3的前继在支配树中的LCA时发现B2的LCA