支配关系以及流图中的循环(一)

  支配树这个结构在后端优化中有很重要的作用,尤其是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可能还没计算:


支配关系以及流图中的循环(一)_第1张图片

  上图中,在计算B3的前继在支配树中的LCA时发现B2的LCA其实还没计算,因此这时B3的LCA是无法计算的。很显然,先假设各个节点的支配节点是自身,然后在迭代中不断的完善各个节点的信息。Cooper给出了一个经典的迭代法计算支配树的伪代码[1]:

for all nodes, n
 DOM[n] ← {1 …N}
 Changed ← true
 while (Changed)
  Changed ← false
  for all nodes, n, in reverse postorder
  new_set ← ( ∏ \prod p ∈ preds(n)DOM [p]) ∐ \coprod {n}
  if (new_set != DOM [n])
   DOM[n] ← new_set
   Changed ← true

  Cooper发现,在计算交集时,利用节点在支配树中的顺序关系,可以用一个双指针算法来加速交集的计算。如果所有节点按后序序列编号,则序号越高的节点在支配树中也越高,也就是说,支配树中一个节点的所有孩子节点是这个节点在后序序列中的前继。算法伪代码[1]:

for all nodes, b /* initialize the dominators array */
	doms[b] ← Undefined
	doms[start node] ← start_node
	Changed ← true
	while (Changed)
		Changed ← false
		for all nodes, b, in reverse postorder (except start node)
			new_idom ← first (processed) predecessor of b /* (pick one) */
			for all other predecessors, p, of b
				if doms[p] != Undefined /* i.e., if doms[p] already calculated */
					new_idom ← intersect(p, new_idom)
			if doms[b] != new_idom
				doms[b] ← new_idom
				Changed ← true
				
function intersect(b1, b2) returns node
	finger1 ← b1
	finger2 ← b2
	while (finger1 != finger2)
		while (finger1 < finger2)
			finger1 = doms[finger1]
		while (finger2 < finger1)
			finger2 = doms[finger2]
	return finger1

  Cooper的论文中也给出了算法的时间复杂度。迭代的最大次数是d(G)+3,也就是和结点数量有关系。而具体的迭代次数则和图的循环连通度有关(即最大回边数量)。通过实验数据,这种快速算法在规模不是很庞大的图中性能往往比LT算法更好。(现实中的流图,往往节点数量不多并且循环连通度比较低。)

  C++实现算法。一般情况下,我们希望基本块结构中只保存和基本块相关的信息,而对于全局信息,则通过和基本块建立映射来保存:

void Dominator::calcFrontDominator(CFGNode* node) {

		bool changed = true;
		
		// 获得节点逆后序
		DFSOrder& doPass = getPM().getPass<DFSOrder>(string("DFSOrderPass"));
		DFSOrder::BBOrderList& rpo_sequence = doPass.getDFSReversePostOrder();

		// 所有节点的idom首先被初始化为空
		vector<unsigned> idoms(rpo_sequence.size(), (unsigned)-1);
		idoms[0] = 0;

		unsigned index = 0;
		// 给所有节点建立逆后序序号映射
		map<BasicBlock*, unsigned> block_map;
		for (auto i : rpo_sequence) {
			block_map[i] = index++;
		}

		while (changed) {
			changed = false;

			for (auto i = 1;i < rpo_sequence.size();i++) {

				BasicBlock* block = rpo_sequence[i];

				auto prev_iter = block->prev_begin();
				BasicBlock* pred = *prev_iter;

				// 本轮的idom首先初始化成第一个前继的idom
				unsigned new_idom = block_map[pred];
				
				// 找到第一个已经计算过idom的前继
				while (idoms[new_idom] == -1) {
					++prev_iter;
					assert(prev_iter != block->prev_end());
					pred = *prev_iter;
					new_idom = block_map[pred];
				}

				// 求出所有前继的LCA
				for (++prev_iter;
					prev_iter != block->prev_end();
					++prev_iter) {

					unsigned block_id = block_map[*prev_iter];

					if (idoms[block_id] != (unsigned)-1)
						new_idom = intersect(new_idom, block_id, idoms);
				}
				
				// 更新idom
				if (idoms[i] != new_idom) {
					idoms[i] = new_idom;
					changed = true;
				}
			}

		}

		// 构造dom tree
		for (size_t i = 0;i < idoms.size();i++) {
			front_dom_tree[rpo_sequence[i]] = rpo_sequence[idoms[i]];
		}
	}

// 双指针计算交集
unsigned intersect(unsigned B1, unsigned B2,
	vector<unsigned>& idoms) {

	while (B1 != B2) {
		while (B1 > B2) {
			B1 = idoms[B1];
		}
		while (B2 > B1) {
			B2 = idoms[B2];
		}
	}
	return B1;
}

参考资料:
1,《A Simple, Fast Dominance Algorithm》
2,《On Loops, Dominators, and Dominance Frontiers》

你可能感兴趣的:(编译与反编译)