目的:由于GPU属于SIMD架构,也就是单指令多数据,当一堆数据过来时,不一定同时跳转,因此这里通过预测寄存器来控制,也就是通过If-Conversion算法消除所有的跳转指令,这样带来的好处就是,将控制依赖转换为数据依赖,同时增大了Basic Block的大小,为有利于后面的指令调度。
这个算法其实很简单,我们可以这么理解:
-
每个Basic Block都要有一个PRF去控制当前的Block是否被执行,这里的控制,我们可以理解为,每个Block需要Use一个PRF去控制当前Block(假定BB2用P2去控制,那么BB2的所有的指令都会用P2去guard)。
-
既然需要一个PRF,那么这个PRF应该在哪里define比较好呢?
对1)来说就是考虑为每个Block分配哪个PRF去控制Block,也就是论文中的p = R(x)算法,这里的x指某一个Block,p指某一个PRF,比如上面提到的R(BB2) = p2;
对2)来说就是考虑每个Block中用的PRF应该在哪里去定义呢,也就是论文中的K(p) = {BB, BB…},还是上面的那个例子,假定p2在BB1和BB3中定义,那么K(p) = {BB1, BB3};
简单介绍了上面的算法后,首先要了解一个概念,即控制依赖,control dependency.我们需要明白的是,关于控制依赖的算法已经存在了,并不是在这里发明的,我们看看控制依赖的定义:
A node (basic block) Y iscontrol-dependent on another X iff
X determines whether Yexecutes, i.e.
• there exists a pathfrom X to Y s.t. every node in the path
other than X & Y ispost-dominated by Y
• X is notpost-dominated by Y
//参考ControlDependence.pdf
从上面的定义我们看到,其实在论文中讲到的算CD(t)就是来自于控制依赖的定义而已。控制依赖就解决了在哪定义的问题,也就是上面说到的K算法。
我们下面说说论文中的几个结论:
RK function:
-
x≈y if CD(x)=CD(y)
//如果Block x和y,都同时控制依赖一个集合,那么x≈y,约等于在//这里的意思是说x和y可以分得相同的PRF.
-
Every node x belongs to one and only one class p=R(x).
//每个Node x,也就每个Block,只需要用一个PRF去guard;
-
Nodes with identical set K(p) of control dependencies belongto the same class p
-
//这个结论跟1)的结论是一致的,在后面的结论:
//p = R(x) iff K(p)=CD(x),这个结论的意思就是说,Block x用p
//去guard,K(p)表示预测寄存器p应该被定义的位置,其实就应该//被定义在Block x控制依赖的地方。
综上所述,RK算法就很好理解了,过程如下:
-
根据控制依赖的定义,算出每一个Block的控制依赖集合,也就是CD(x);
-
根据CD(x)分配PRF,也就是R算法,分配的原则也很简单,就是上面介绍的,如果CD(x) = CD(y),那么x和y分配相同的PRF;当Block x分配好了p,那么K(p)= CD(x);
-
最后一步,就是解决一个序列化的问题;
下面我们来说说为什么会有第三步,我们先看论文中给的例子:
Topological order
B1B3B2B4B5B6B7
-
我们在上面的图上看到,我们在原有控制流图的基础上多加了两个Block,也就是START和STOP,这两个Block除了分别用来标记控制流图的起始和结束外,START还有一个用处,就是给某些PRF初始化,可能有人要问了:所有的PRF不是已经在K(p)对应的Block中定义了吗?怎么还要在START中进行初始化呢?那么最后一个算法就是为了用来解决这个问题!
-
首先要理解一下,什么是序列化,序列化就是说,虽然程序中有控制流图,但需要按一定顺序文本化,就是上面列出的一种拓扑排序:
B1B3B2B4B5B6B7,按B1->B3->B2->B4->B5->B6->B7的顺序执行。加入我们不在START中将P4=F,按照这个拓扑排序执行,且t1= False, t3 = true ,会发现在执行B4时,P4是没有被初始化的,这是因为序列化后,B2对应的guard寄存器P2=False,因此B2这个Block中的指令没有执行,因此在B2中初始化的P4就没有被初始化,因此会有问题。
-
现在引出了我们算法第三步要解决的问题,先看论文给的结论:
A(x) == {p ∈ P': exist path(x,Stop)such that K(p) ∩ path(x,Stop) =ϕ}
这个意思是说,从Block x到Stop的所有可能的控制流路径中,如果存在某条路径上,没有经过K(p),也就说,p没有在这条路径上被初始化。那么我们需要计算的是A(Start),从Start到Stop的所有的路径中,是否有某个PRF没有被初始化,在上面的例子中,P4就在一条路径上没有被初始化,因此需要将其在Start进行初始化;
-
最后就来说说如何去得到这些没有被初始化的PRF,其实这个算法也是很容易理解的。这需要我们理解变量的活跃性分析,在论文中就是将这个问题转换成了变量的活跃性分析的问题,也就是如果在START的起始处还活跃的变量,说明这些变量这些变量是没有被初始化的。其实也可以通过计算在START结束处还活跃的变量,来说明这些变量仍然是活跃的,这两种都是可行的。
Def(b) = {p ∈ P': ±b∈K(p)}
//计算在Block b中定义的PRF,凡是控制依赖Block b的Block
//guardPRF,都是在b中定义的。
Use(b) ={p ∈ P': p=R(b)}
//用来guard Block b的PRF都是在b中使用的。
IN(b) =Use(b)U(OUT(b)-Def(b))
OUT(b) =U IN(s) for s ∈ succ(b)
//这是活跃性分析的公式。
下面跟简单推倒下活跃变量分析的公式:
在计算活跃性分析的时候需要注意一点:1)自底向上计算,2) 从最后一条指令向上进行分析,为什么要这样呢?
-
因为最后的Block的LiveOut肯定是NULL;
-
从最后一条指令往上分析更加直观,比如上面的Block 3,我们先看最后一条指令 d = k + 1;在这里d被define了,那此时LiveIn(B3)中肯定不会有d,也就是说d要从B3的LiveIn活跃变量中删除;而
d = k + 1中use了k,那么也就是k要加到LiveIn(B3),这就是
IN(b) =Use(b)U(OUT(b)-Def(b))
-
OUT(b) =U IN(s) for s ∈ succ(b),这个应该更好理解了,一个Block的LiveOut变量等于这个Block的所有succssor Block的LiveIn变量。就不多解释了。