LLVM是一款开源的编译器框架,近年来已经逐渐超越GCC。
许多深度学习编译框架TVM、Tensorflow XLA的后端也是使用的它。 正是由于其友好的Lisense,模块化及统一的IR,使得其越来越流行。因此对LLVM的研究很有必要。
图1. LLVM的应用
文中介绍了LLVM中构造支配树的两种算法,分别是SLT算法与Semi-NCA算法。构造支配树的算法,就是图论在编译器中的一个应用。如果蜕去LLVM的外衣,相信很多参加过ACM比赛的选手应该对支配树的构造很熟悉。
本文的目的是以一种通俗易懂的方式给需要了解这个算法的朋友一个感性的认识。如果需要看原论文或者关于深度学习编译器论文的可以后台回复idom获取。
对于一张有向图(可以有环),我们规定一个起点,从点到图上另一个点可能存在多条路径,对于从到的任意路径中,都存在一个点 ,即从到必须经过,那么我们称为的支配点。
用 表示离点最近的支配点, 对于原图除外,每一个点,从向建一条边,最后我们得到了一颗以为根的树,这棵树就是支配树(Dominator tree)
更多应用欢迎补充。
对图进行深度优先遍历得到的一颗树称为DFS树。树上的每一个节点都有一个按照深度优先遍历的顺序得到的编号。
图2.深度优先搜索树
如图2所示,节点和实线虚线共同构成了一个有向图,对有向图进行深度优先遍历就形成了DFS树。其中实线是DFS的树边。红色数字表示按照深度优先遍历的顺序得到的编号,红色字母表示该节点的半必经节点。
如果在DFS树中存在一条由到的边,则顶点是顶点的父节点,这条边称为树边。
记作
如果在有向图中存在一条到的边,则顶点是顶点的前驱节点。注意要与父节点相区别,因为父节点是在DFS树上存在由到的边。到的边中除去树边以外的边称作非树边。
的前驱节点记作
非树边记作
如图2中是的父节点。到的边为树边
是的祖先,如果在DFS树中存在一条由到的路径,可以等于。
记作
如图2中都是的祖先,因为这些点都可以沿着实线边(DFS树边)到点
是的完全祖先,如果在树中存在一条由到的路径,不等于。
记作
如图2中都是的完全祖先,因为这些点都可以沿着实线边(DFS 树边)到点。与祖先的唯一区别就是不包括自身。
右子树的节点指向左子树节点的边。横跨边的起点永远大与终点编号,因为DFS树中右子树的遍号永远大于左子树的编号。
记作
如图2所示,的这四条边都是横跨边
子节点到其完全祖先的边叫返祖边。
记作
如图2所示,这两条边都是返祖边。
在求支配节点之前,我们首先需要了解半支配路径,然后求出半必经节点及必经节点,最终得到整个支配节点树。
图3. 求支配节点树路线图
公式表示:
通俗解释:
在DFS树中存在一条路径,如果这条路径中(不包括起点和终点)的每一个点的编号都大于终点的编号,则该路径为一条半支配路径。
根据定义可以将半支配路径分为两类:
树边半支配路径比较特殊,只包含两个点,这两个点在一条树边上。
非树边半支配路径即路径上指向终点的边为非树边,这条非树边要么是横跨边要么是返祖边。
如图4所示,黄色加粗的线为树边半支配路径,绿色和紫色是非树边半支配路径,其中绿色边含有横跨边,紫色边含有返祖边。
图4. 半支配路径示意图
公式表示:
通俗解释:
V的半支配节点为所有终点为V的半支配路径中,起点值最小的那个。
因为半支配路径有两类,一是树边半支配路径,二是非树边半支配路径,因此也可以将半支配节点的求法化简为这两类
公式化简:
根据图形理解更加简单:
其中黄色线对应公式中的第(1)种情况
紫色线和绿色线对应公式中的第(2)种情况
其中的可以取下图中和两种情况, 可以是绿色线或紫色线上的任意一个点,包括或。绿色线或紫色线就是公式中的条件
图5.支配节点的三种情况
求半支配节点的伪代码
Create a DFS tree T.
semi(w) = w | w ∈ V
for w ∈ V − {r} in reverse preorder by the DFS
for v ∈ pred G (w)
u = eval(v)
if semi(u) < semi(w)
semi(w) = semi(u)
end for
Link parent(w) and w
end for
其中的 eval(v)就是在求黄色、紫色、绿色各条线上 semi 最小的点。因为是对 DFS 树进行逆序,因此求 的时候紫色线和绿色线上各节点的 semi 值已经是已知的了。
LLVM在2017年之前采用的是SLT算法,新的版本使用的是semi-NCA算法。两者都是在上一节介绍的半必经节点的基础上求得必经节点。下文会分别对这两种算法进行介绍,并比较其时间复杂度。
SLT算法会根据前文求出的半支配节点进一步求出直接支配节点。
公式表示:
其中在
u
通俗解释:
在DFS树中,到的路径上有一点,的sdom值是该路径上最小的点,如果等于则等于,否则等于
下面的两张图是对求 idom 的公式两种情况的一个总结,可以让我们的理解更加直观。
公式中的情况1公式中的情况2
计算支配节点树的伪代码:
Create a DFS tree T.
for w ∈ V − {r} in reverse preorder by the DFS
Calculate semi dominator for w
Add w to bucket of semi(w)
while bucket of parent(w) is not empty do
v = pop one element from the bucket
u = eval(v)
if semi(u) < semi(v) then
idom(v) = u
else
idom(v) = semi(v)
end if
end while
end for
for w ∈ V − {r} in preorder by the DFS do
if idom(w) != semi(w) then
idom(w) = idom(idom(w))
end if
end for
以一个实例加深对SLT算法的理解:
与上文介绍的SLT算法相比,semi-NCA算法无疑更容易理解,这也是目前 LLVM正在使用的算法。下面直接上代码,相信大家一看就能够理解。
Create a DFS tree T.
Calculate semidominator for w
Create a tree D and initialize it with r as the root.
for v ∈ V − {r} in preorder by the DFS do
Ascend the path r *—>DparentT(v) and find the deepest vertex which number is smaller than or equal to sdom(v).
set this vertex as parent for v in D.
end for
为了方便理解,来看下面这个简单实例:
semi-NCA算法实例
本节主要介绍了 LLVM 中求支配节点树的两种算法,分别是 SLT 和 semi-NCA 算法。两种算法的时间复杂度和空间复杂度如下。
算法 | 时间复杂度 | 空间复杂度 |
---|---|---|
SLT | O(mlogn) | O(m+n) |
semi-NCA | O(n^2) | O(m+n) |
对于算法的详细分析、证明和实验结果可以参考原论文。
本篇文章缺少算法的证明,仅提供一些自己在学习过程中对这两个算法感性的认识,避免枯燥的公式。希望能够给需要学习这个算法的人提供一些帮助。
后续准备写一个编译器中的图论算法系列,题目如下:
欢迎各位朋友帮忙补充更多的编译器中用到的图论算法或者其它感兴趣的编译器中的算法。
全部0条评论
快来发表一下你的评论吧 !