Assembly x86的简单寄存器分配方案
我正在写一个简单的玩具编译器,我来到了生成机器的部分 代码(本例中为x86-32程序集)。这就是我现在所拥有的: 鉴于转让声明: d:=(a-b)+(c-a)-(d+b)*(c+1) 我首先生成以下中间代码(三元组形式的3个地址代码): 我正在使用一个中间代码,希望以后能执行一些优化 在上面。现在,我不操纵3AC并直接从它生成程序集 我的寄存器使用方案如下:我执行所有算术运算 使用EAX并将中间结果保存在其他寄存器EBX、ECX和EDX中。对于 例如,从前面的3AC生成以下组件:Assembly x86的简单寄存器分配方案,assembly,compiler-construction,x86,allocation,cpu-registers,Assembly,Compiler Construction,X86,Allocation,Cpu Registers,我正在写一个简单的玩具编译器,我来到了生成机器的部分 代码(本例中为x86-32程序集)。这就是我现在所拥有的: 鉴于转让声明: d:=(a-b)+(c-a)-(d+b)*(c+1) 我首先生成以下中间代码(三元组形式的3个地址代码): 我正在使用一个中间代码,希望以后能执行一些优化 在上面。现在,我不操纵3AC并直接从它生成程序集 我的寄存器使用方案如下:我执行所有算术运算 使用EAX并将中间结果保存在其他寄存器EBX、ECX和EDX中。对于 例如,从前面的3AC生成以下组件: mov
mov eax, a
sub eax, b ; eax = (0)
mov ebx, eax ; ebx = (0) & eax = free
mov eax, c
sub eax, a ; eax = (1)
add ebx, eax ; ebx = (2) & eax = free
mov eax, d
add eax, b ; eax = (3)
mov ecx, eax ; ecx = (3) & eax = free
mov eax, c
add eax, 1 ; eax = (4)
imul ecx ; eax = (5) & ecx = free
sub ebx, eax ; ebx = (6) & eax = free
mov eax, ebx ; eax = (6) & ebx = free
mov d, eax
我的问题是:当我需要泄漏EAX的结果但所有
寄存器正忙(EBX、ECX和EDX处于临时状态)。我应该保存文件吗
堆栈中EAX的值,并在以后恢复?如果是这样,我应该保留吗
在每个函数的堆栈框架中为额外的临时变量留出一些额外的空间
我再说一遍,这只是我现在的想法。如果有其他简单的
分配寄存器的方案我想知道(我知道
更复杂的解决方案包括图形着色等,但我只是在寻找一些东西
这很简单。)如果您的数据多于寄存器,并且推送了多余的数据,那么您必须在使用它之前将其弹出。如果您最终没有使用它(由于分支),那么无论如何您都必须弹出它 因此,您甚至没有使用数据就在推送和弹出 您将把数据推入堆栈,并在需要时将其弹出 您还将抛出保存在将弹出数据移动到的寄存器中的数据 您必须让编译器记住堆栈的深度,并确保从函数返回时堆栈的深度是正确的 在函数之前或之后简单地使用本地存储并不容易。您可以将mov(e)(可能带有偏移量)推到bp或xchg(可能带有偏移量)进出本地存储区域,而无需推送 您可以在任何时候从函数中Ret(urn),而无需弹出与您推送的数据量相同的数据,只需将其丢弃在本地存储器中即可 显然,这支持几乎无限数量的“寄存器”非常容易,而推和弹出变成了“3x3(或更多)滑块拼图”游戏 这些滑块难题(以及Rubic的立方体)用螺丝刀解决起来更快(除了世界纪录冠军)。只要把你想要的东西撕成碎片(访问任何内存位置),然后按照你的意愿把它重新组装起来(不要在Ret之前把东西弹出来)——“河内之塔”式的东西不需要来回滑动 使用本地变量而不是堆栈(除非您只缺少一个或两个寄存器,否则推送和弹出的智能例程可能比内存访问更快;就像几乎解决的立方体比用螺丝刀打开更快地解决一样)
将有一个屈服点在2或3(寄存器短于您想要的),其中局部变量的推送和弹出速度比推送和弹出速度要快,这取决于代码以及它如何被洗牌(优化)。而不是总是将结果计算到EAX中,考虑将结果计算到目标位置,该目标位置可以是寄存器或内存位置 在伪代码中:
for each 3AC instruction I
Look up the set S of places that hold operands of I
R = allocate_place(I) // register or memory for the result
Emit code that uses S and puts the result of I into R
// code emitted differs depending on whether R, S are registers or memory
free_places S
您将使用一个分配器,根据可用的内容提供寄存器名或临时内存位置。分配器保留一个“反向映射”,允许在上面查找指令的每个操作数所在的位置。分配器可以使用多种策略。最简单的方法是先用完所有寄存器,然后开始分配内存
请注意,当生成整个函数的代码时,分配器将知道该函数总共需要多少临时内存位置。函数前导码必须在创建堆栈帧时设置这些。您需要一种机制来“回补”具有正确数量的位置的前导。这有多种可能性。问问你是否需要想法。然后,在继续编译下一个函数之前重置分配器
上述算法在使用其值时立即释放相应的资源(寄存器或内存位置),因为您的简单代码生成器允许此不变量。如果您消除了公共子表达式或进行了其他优化,那么决定何时释放寄存器会变得更加复杂,因为它的值可能会被多次使用
嵌入在表达式中的函数调用会引发其他有趣的情况,需要仔细考虑。如何保存寄存器?因为这是一个玩具编译器,所以您不必太担心。您可以使用一个简单的
推送
和弹出
,只有在必要时才不会中断堆栈。此外,这可能会有所帮助:。哦,还请记住,如果您不需要完整的32位,您可以将它们拆分,然后使用ah
/al
,bh
/bl
,等等。您可以用另一种方式来执行此操作。在堆栈框架中为每个变量分配一个插槽,然后开始尝试不使用它们。显而易见的好处是,你总能找到一种方法来泄漏寄存器。谢谢,这正是我想要的。
for each 3AC instruction I
Look up the set S of places that hold operands of I
R = allocate_place(I) // register or memory for the result
Emit code that uses S and puts the result of I into R
// code emitted differs depending on whether R, S are registers or memory
free_places S