您的位置:首页 > 其它

RA, 寄存器分配

2016-05-26 17:21 316 查看

寄存器分配

1. 如何计算D-U链

(1) 首先遍历整个routine,保存所有def值;

问题:

你如何去保存这些def值呢?要保证能快速的得到每个def值,因为每一 个 def 值不仅仅只包含dst寄存器号,还包括这条指令,以及其它信息。

解答之一:

最快速的查找算法,就是hash算法。在我们这里通过构建一个含有256个元素的hash表,hash[regNo & (256-1)] = defIndex;对于hash冲突的处理方法是,在def值的结构中存放一个变量,用来保存冲突的defIndex.

比如:

r1 = r2 + r3;//defIndex = x;

r1 = r1 * r4;//defIndex = x + 1;

很显然,此时两次def的r1的hash值是一样的,但两次def的defIndex是不一样的, 假定第一次的defIndex = x, 那么(pDefs + defIndex) ->hashNextDefIndex = x + 1;其实可以把这理解为链表的形式处理hash冲突,但冲突不是在hash表上处理的而已。

(2) 自顶向下计算每个Basic Block的LiveOut def值,这里的LiveOut def值表示在离开当前block后还可能活跃的def值。然后通过数据流分析,得到每个Basic Block的LiveIn def值。

问题:

如何计算?

解答之一 :

根据routine中的def值的数目,来定义一个长度为32bit倍数的变量(假定变量名字为FlowFunction),每一位用来表示对应的defIndex, 为1,表示活跃, 为0, 表示不活跃。

1) 分block扫描所有指令,在每一个block里,对有dst的指令,将FlowFunction变量的对应位置为1;如果出现多次定义一个变量的情况,则只会把最后一次的defIndex置为1,也就是在每次扫描的过程中,在最后一次def的时候,会把前一次的def clear 掉;这样每个Basic Block的FlowFunction变量的值就得到了;

2) 上一步只是得到每一个Block中的LiveOut def变量,因为在整个routine中block是有数据流关系的,如果当前Block有predecessor Block,则当前Block的LiveIn def变量 = 所有predecessor Block的LiveOut的或运算。

比如:

LiveIn(Block 5) = LiveOut(Block 3) | LiveOut(Block 4);



(3) 计算完每个Basic Block的LiveIn def值后,自顶向下扫描每个Block。首先检查每条指令的src, 然后根据src可以得到其所有的defIndex, 如果这个defIndex在LiveIn中,那么就将这个Usage添加到这个def中。然后检查这条指令的dst,这个dst是否已经在LiveIn中,如果是,则将 旧的defIndex从LiveIn中删掉,否则将这个dst的defIndex添加到LiveIn中。(此时的LiveIn已经不是原来的LiveIn,只能说初始值是原来的LiveIn吧)。

问题:为什么要计算LiveIn的def变量?如下图所示,如果不计算第1个block的LiveOut def值,那么在其successor block中use变量i就不知道来自于哪个def了



2. 如何构建Web?

构建web很简单,遍历所有的def变量,如果两个def变量对应的register相同,且有相同的use,则这两个def属于相同的web.

3. 构建冲突图

在构建寄存器的冲突图,也就是构建web之间的冲突图。在构建冲突图的过程中,主要分两步,首先计算出CFG中每个Basic Block的LiveOut变量,注意这里的LiveOut跟上面的的LiveOut是有区别的,上面的LiveOut可以理解为计算变量的可达性,而这里的LiveOut指变量的活跃性。然后根据LiveOut变量,以及当前Basic Block的指令得到冲突图。

(1)如何计算每个Basic Block的LiveOut?

我们采用自底向上的遍历方式来计算,

1) 首先假定每一个Block的LiveOut为NULL,然后根据算法LiveIn = LiveOut + use - def,要计算LiveIn,必须知道use和def,此时我们计算出每一个Block的use和def

2) 然后根据迭代算法, 当前Block的LiveOut = 所有后继结点的LiveIn的并集。

注意,在整个过程中,我们只计算了LiveOut,并没有计算出LiveIn.

(2) 构建冲突图,从最后一条指令开始扫描Block,首先检查当前指令的dst,然后将此dst跟{LiveOut}中的所有变量构建冲突边;最后重新检查dst,如果这个dst的所有def都已经kill了(注意,必须是所有的def都已经被kill掉了),那么就将这个def对应的node从{LiveOut}中删除,然后检查src,并将src的加入到集合{ LiveOut, src}中;

4. 修剪冲突图

我们尝试着用R种颜色给图着色,其中R是可用寄存器的个数。当R>=3时,这是一个NP完全问题。一种方法保证能使得图的一部分是可R色着色的,只要这个图的剩余部分是可R色着色的。另一种方法则在第一种方法没有穷尽的情况下继续乐观地进行处理。后一种方法不会直接产生一种R色着色,但与只使用前一种方法相比,它常常能使图的更多部分成为可着色的,因此非常有助于它的启发式值。

前一种方法基于一种简单但非常有效的观察,我们称之为度<R规则:在图中选定一个度数小于R的结点,如果删除这个结点后,这个图还是可R作色的,则原图是R可作色的。但这种方法可能会漏掉某些可以R作色的图。比如下图是一个可2色作色的,用之前的方法就没法得到这个结论。



后一种方法即乐观启发式方法,通过删除度>=R的结点来推广度<R规则。这种方法的理由是因为观察到一个结点虽有R个以上的相邻结点,但它们不必都有不同的颜色,因而它们可能不会使用到所有的R种颜色。上面右边的图就是3色可作色的。

4.1 PRF的分配策略

1.首先按顺序遍历所有node, 根据冲突图,如果这个node的冲突边<=R的node,将其压入栈(用来存放寄存器分配顺序的栈colorStackDepth),并删除与这个结点相关的冲突边,同时标记这个node已经被spruned;直到遍历完所有node;

2.第1步做完之后,如果发现还有node没有压入栈colorStackDepth, 很显然这些没有压入栈的node可能是需要spill的node, 因此找出这些没有压入栈中spill代价最小的node,并将其压入栈;并删除与这个结点有关的冲突边,同时标记这个node已经被spruned,然后再执行第1步;

3.直到第2步中没有其它的node结束;

4.然后从栈顶拿出一个node,根据冲突图,为其按序分配寄存器,如果没有可用的寄存器用来分配,则将这个node标记为spill;

5.如果存在某些node需要spill,则执行相应的spill操作;

下面描述下简单的spill过程,由于在执行PRF spill操作时,是通过将PRF spill 到CRF的,下面举个简单的例子:

P4 = P2 & P3; //def P4;



P5 = P4 | P6; //use P4;

现在假定要将P4 spill到CRF中,经过处理后的代码如下:

Rx = P2 & P3;



MOV Py, Rx;

P5 = Py | P6;

这里有2点需要注意:

1)这里的Rx是临时的CRF,因为首先是做PRF的分配,然后才是CRF的分配,因此,这里可以用一个临时的CRF处理;

2)在PRF spill的时候,首先找到需要需要spill的PRF,然后寻找其use,找到use后,在use的指令前添加一条MOV Py, Rx指令,并将这个use的PRF替换为Py;在这里有一个技巧,我们没有必要碰到一个use就为其添加一个MOV指令,这就太浪费了,但我们也不能只添加一条MOV指令,这样Py的生存期又太长了;折中的方法是适当添加MOV指令,比如碰到如下的情况:

P4 = P2 & P3; //def P4;



(P4) R1 = R2 + R3;

P5 = P4 | P6; //use P4;

那么此时通过spill操作的代码为:

Rx = P2 & P3;



MOV Py, Rx;

(Py) R1 = R2 + R3;

P5 = Py | P6;

4.2 CRF的分配策略

在这里我们只考虑两种类型的CRF的分配,也就是range CRF和single CRF.具体步骤如下:

1.首先统计range CRF的node数目和single CRF的node数目,比如standalone CRF的node数目为SingleCRFNodeColorStackDepth;

2.遍历所有的node,如果发现其为range CRF的Node,则将其保存到pColorStack中,但此时的偏移为SingleCRFNodeColorStackDepth,也就是range CRF从地址

PcolorStack + SingleCRFNodeColorStackDepth开始进行保存;如果发现是single CRF,则先通过位域变量先保存在pLiveNodeMask中;

3.然后通过扫描pLiveNodeMask,来确定single CRF的node,找出其中冲突边最少的node,如果这个node的冲突边< R,则将其压入栈pColorStack,并将pLiveNodeMask对应位置为0,并删除与这个结点有关的冲突边,如果这个node的冲突边>=R,则从剩下的node中选择一个SpillCost最小的node 压入栈;并将pLiveNodeMask对应位置为0,并删除与这个结点有关的冲突边;然后继续执行第3步,直到pLiveNodeMask为0时结束;

4.然后从栈顶(pColorStack)拿出一个node(很显然,从前面的过程来看,先是给range CRF分配寄存器的),根据冲突图,为其按序分配寄存器,如果没有可用的寄存器用来分配,则将这个node标记为spill;这里需要注意的是,对于range CRF,必须是为其分配连续的CRF;对于single CRF来说,选择一个最小的没有分配的CRF即可;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  RA 寄存器分配