理论教育 编译器设计之路:寄存器分配与优化

编译器设计之路:寄存器分配与优化

时间:2023-11-04 理论教育 版权反馈
【摘要】:根据寄存器的大小,将寄存器分为3个组,即1字节寄存器组、2字节寄存器组、4字节寄存器组。这类寄存器是分配算法优先考虑的。对于这类寄存器资源,原则上尽可能不作分配。程序9-1 Target.cpp第3、4行:参数iTarget是目标寄存器的编号。第53、54行:如果没有适合的寄存器予以分配,则表示寄存器己耗尽。本节所讨论的寄存器分配算法主要应用于较低层次的实现。

编译器设计之路:寄存器分配与优化

本书并不打算深入讨论如何寻求相对更优的寄存器分配方案,这将是一个复杂的话题。这里,关注的是一种简单且相对有效的寄存器分配方式。它的目标任务是根据指令模板中操作数的相关描述信息,分配一个能够满足应用需求的寄存器。为了跟踪寄存器的绑定情况,引入一个用于记录寄存器绑定信息的数据结构,声明形式如下:

【声明9-5】

978-7-111-32164-4-Chapter09-31.jpg

其中,RegVal的关键字是寄存器号。需要说明的是,一个寄存器是可能同时与多个操作数绑定的。不过,这种情况必须谨慎处理。下面,就来看看寄存器分配的基本思想。

Neo Pascal寄存器分配的基本思想是比较简单的。根据寄存器的大小,将寄存器分为3个组,即1字节寄存器组、2字节寄存器组、4字节寄存器组。并且设置一组游标变量,用于分配寄存器,即AllocCursor数组,如图9-4所示。

978-7-111-32164-4-Chapter09-32.jpg

图9-4 寄存器分配示意

以1字节寄存器分配为例,AllocCursor[0]指向的就是当前可分配的寄存器,而每一次分配请求之后,AllocCursor[0]游标后移一位。当AllocCursor[0]游标移到行末时,则重新指向行首。这种分配策略与操作系统中的存储分配比较类似,当然,这里并不需要考虑回收的问题。值得注意的是,对分配算法而言,可以将寄存器资源分为三类,即“未绑定”寄存器、“己绑定”寄存器、“不可用”寄存器。

“未绑定”寄存器就是指未绑定具体变量且未被禁用的寄存器资源。这类寄存器是分配算法优先考虑的。

“己绑定”寄存器就是指已绑定了具体变量的寄存器资源。对于这类寄存器资源,原则上尽可能不作分配。不过,当“未绑定”寄存器己分配殆尽时,这类寄存器依然是可以参与分配的。

“不可用”寄存器就是指当前指令模板中有特殊限制的寄存器资源,这些寄存器是绝对不能参与分配的,否则会导致所生成的代码不正确。

下面,就来看看寄存器分配的源代码实现。

程序9-1 Target.cpp

978-7-111-32164-4-Chapter09-33.jpg

978-7-111-32164-4-Chapter09-34.jpg

第3、4行:参数iTarget是目标寄存器的编号。在默认情况下,iTarget的值为一1,即表示由代码生成器随机分配寄存器。如果iTarget的值为一2,即表示由代码生成器随机分配可以交织寻址的寄存器。其他情况则表示必须分配参数iTarget所指定的寄存器。

第6行:iReg用于记录分配得到的寄存器号。

第7行:TmpSet用于记录算法曾经考察过的寄存器号,避免同一寄存器被重复考察。

第8行:AllocCursor是用于分配寄存器的一组游标。

第9行:根据所需分配寄存器的大小,iTmpCursor指向相应的游标。(www.daowen.com)

第11~25行:考察大小满足条件的寄存器组,检索是否存在“未绑定”寄存器,如果存在未绑定寄存器,则直接予以分配。

第13、14行:在循环考察寄存器组时,TmpSet集合用于避免同一寄存器被重复考察。如果当前寄存器号己存在于TmpSet集合中,则结束循环考察。

第17~20行:如果iTarget为2,则不能分配ESI、EDI。

第22~28行:判断当前寄存器是否为“未绑定”寄存器。Forbid是一个全局集合,其中记录的是“不可用”寄存器。

第32~51行:如果iReg为l,则表示当前己无“未绑定”寄存器可以分配。在这种情况下,就必须从“已绑定”寄存器中选择合适的寄存器予以分配,此时,就产生了寄存器溢出的情况。当然,“不可用”寄存器仍然是不参与分配的。

第52行:修改游标的指向。

第53、54行:如果没有适合的寄存器予以分配,则表示寄存器己耗尽。

本节所讨论的寄存器分配算法主要应用于较低层次的实现。对于更优的寄存器分配算法,这种修改并不会对当前的实现造成太大的影响。最经典的寄存器分配算法就是“图着色”算法,当所有“未绑定”寄存器均被占用时,如何选择溢出寄存器是该算法所关注的。寄存器溢出是有代价的,因此,需要评估决策。Neo Pascal对此并没有作太多考虑,有兴趣的读者可以参考“鲸书”。

最后,再来看看寄存器溢出的相关实现。

程序9-2 Target.cpp

978-7-111-32164-4-Chapter09-35.jpg

以上代码遍历与当前寄存器有依赖关系的所有寄存器,并调用SaveReg函数,将寄存器的值回存到存储器中。

程序9-3 Target.cpp

978-7-111-32164-4-Chapter09-36.jpg

978-7-111-32164-4-Chapter09-37.jpg

第4~22行:遍历指定寄存器的所有绑定变量信息,生成相应的mov指令,以便将寄存器的值回存到存储器中。

第6~10行:在bFlg为真的情况下,如果溢出变量为死变量,则不需要回存。在其他情况下,都需要回存。

第11~20行:调用IRtoAsmList函数生成mov指令,其中ParaStr用于传递参数。在指令模板库中,专门设置了三个模板(即#MOV_1、#MOV_2、#MOV_4)用于将寄存器的值回存到内存,这三个指令模板仅限寄存器溢出使用。当然,这里需要借助于TmpSet集合避免出现多次回存的情况。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈