您的位置:首页 > 其它

GCC 编译器寄存器分配各阶段主要任务

2010-01-08 14:15 253 查看
本文是阅读了Vladimir Makarov的《Fighting register pressure in GCC》后的小结,一些文字是直接翻译过来的。

GCC寄存器分配工作依次执行下面阶段:

一 regmove(对应regmove.c)
当目的和源寄存器应该相同时,生成MOVE指令,以满足双操作数指令的约束。

二 指令调度(The instruction scheduler)
降低“寄存器压力”(即分配过程中硬寄存器短缺的现象)。

三 寻找寄存器类(对应regclass.c)
执行2遍工作。
1 为每个待分配的伪寄存器找到首选的(preferred)和候选的(alternative)寄存器类。
2 暗中执行代码选择(code selection)。
3 扫描伪寄存器的一般信息(引用次数、赋值次数、引用伪寄存器的第一条和最后一条指令)。

四 局部分配(对应local-alloc.c)
1 对活跃于一个基本块内的伪寄存器分配硬寄存器。结果存到全局数组reg_renumber中。
2 执行一些寄存器拼接工作,例如,如果二个或更多个不冲突的伪寄存器被若干MOVE指令搅乱了,它们通常能够分配给相同的硬寄存器。
3 执行简单的复制传播和常量传播(在update_reg_equiv函数中实现)。

五 全局分配(对应global.c)
1 对活跃于多个基本块内的伪寄存器分配硬寄存器。当它找到比局部分配更有利的硬寄存器时,它可能会改变局部分配的结果。
为每个伪寄存器生成一个位向量,该向量包含与伪寄存器冲突的硬寄存器,为每个伪寄存器构建一个冲突图,并根据下面的优先权值对所有伪寄存器排序:
( log2(Nrefs) * Freq / Live_Length ) * Size
这里Nrefs是伪寄存器出现的次数,Freq是它使用的频率,Live_Length是伪寄存器在指令中活跃范围的长度,Size是它在硬寄存器中的大小。
之后,全局分配器试图按由高到低的优先权顺序将硬寄存器分配给伪寄存器。
如果当前伪寄存器获得了一个硬寄存器,该硬寄存器被加入(与该伪寄存器冲突的所有伪寄存器的)硬寄存器冲突向量。
2 通过将伪寄存器赋给硬寄存器,试图将MOVE指令中遇到的伪寄存器与硬寄存器接合起来。

六 reload阶段(对应reload.c和reload1.c)
1 如果RTL中的操作数的不满足约束,那么就转换使其满足这些约束。这里的伪寄存器可能会被转换为硬寄存器、内存或常量。
reload遍跟在全局和局部分配之后,根据需要它可能会改变前二者的赋值。
例如:如果硬寄存器A分配给了伪寄存器V1,但引用V1的指令要求用其它寄存器类,则会生成
MOVE A to C
MOVE C to B
其中B是满足V1指令寄存器类的硬寄存器,而C可能是内存。如果B或C已被伪寄存器V2占用,则调用global.c中的retry_global函数为V2分配其它的硬寄存器,如果分配失败,则V2被放于程序的堆栈中。
2 消除虚拟的硬寄存器(如实参指针)、和实际的硬寄存器(如帧指针)。
3 对那些溢出的硬寄存器,以及最终未获得硬寄存器的伪寄存器,将堆栈槽赋给它们。
4 复制传播等。

七 postreload阶段(对应postreload.c)
根据基本块上下文,删除reload阶段生成的多余的move, load, store等指令。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: