x86的简单寄存器分配方案

时间:2014-05-21 20:40:52

标签: assembly compiler-construction x86 allocation cpu-registers

我正在编写一个简单的玩具编译器,我来到了生成机器的一部分 代码(在这种情况下为x86-32汇编)。这就是我现在所拥有的:

鉴于作业陈述: d:=(a-b)+(c-a) - (d + b)*(c + 1)

我首先生成以下中间代码(Triples表单中的3个地址代码):

(0) sub, a, b
(1) sub, c, a
(2) add, (0), (1)
(3) add, d, b
(4) add, c, 1
(5) mul, (3), (4)
(6) sub, (2), (5)
(7) asn, d, (6)

我正在使用中间代码,希望稍后执行一些优化 在上面。现在,我没有操纵3AC并直接从它生成组件。

我的寄存器使用方案如下:我执行所有算术运算 使用EAX并将中间结果保存在其他寄存器EBX,ECX和EDX中。对于 例如,从之前的3AC我生成以下程序集:

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值,以后再恢复?如果是这样的话,我应该保留 对于那些额外的临时工具,每个函数的堆栈框架中有一些额外的空间吗?

我再说一遍,这只是我现在所要做的。如果还有其他简单的话 分配寄存器的方案我想知道(我知道存在的 更复杂的解决方案涉及图形着色等;但我只是寻找一些东西 简单,只是工作)。

2 个答案:

答案 0 :(得分:1)

考虑将结果计算到目标位置(可以是寄存器或存储位置),而不是将结果总是计算到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

您将使用提供寄存器名称或临时内存位置的分配器,具体取决于可用的内容。分配器保留一个“反向映射”,允许在上面查找指令的每个操作数所在的位置。分配器可以使用各种策略。最简单的方法是首先用完所有寄存器,然后开始分配内存。

请注意,当生成整个函数的代码时,分配器将知道该函数需要多少总临时内存位置。函数前导码必须在创建堆栈帧时设置它们。您需要一种机制来使用正确数量的位置“回补”前导码。这有各种可能性。询问您是否需要创意。然后,在继续编译下一个函数之前重置分配器。

上面的算法在使用其值时立即释放相应的资源(寄存器或内存位置),因为简单的代码生成器允许这种不变量。如果消除常见的子表达式或进行其他优化,那么决定何时释放寄存器会变得更复杂,因为它的值可能会被多次使用。

表达式中嵌入的函数调用会引发其他有趣的案例。如何保存寄存器?

答案 1 :(得分:0)

如果您拥有的数据多于“注册”并推送多余数据,则必须在使用之前将其弹出。如果你最终没有使用它(由于分支),你仍然必须弹出它。

因此,您无需使用数据即可推送和弹出。

您将把数据推入堆栈并在需要时将其弹出。

您还要丢弃寄存器中保存数据的数据。

您必须让编译器记住堆栈的深度,并确保从函数返回时它是正确的。

在功能之前或之后简单地使用本地存储会更容易。而不是推动你可以 mov (e),可能有偏移, bp xchg ,可能有一个偏移,进出这个本地存储区。

您可以随时重新(urn),而无需弹回您推送的相同数据量,只需将其放弃在本地存储中即可。

显然,这支持几乎无限数量的“注册表”。非常容易,而推动和弹出变成了#3; 3x3(或更多)Slider Puzzle"的游戏。

那些滑块拼图(以及Rubic&#39多维数据集)可以用螺丝刀更快解决(除了世界纪录冠军之外的所有人)。只需撕掉你想要的东西(访问任何记忆位置)并将它按照你想要的方式重新组合(不要在 Ret (urning)之前弹回东西) - 没有所有的滑动和河内塔'河内塔风格。

使用局部变量而不是堆栈(除非你只有一个或两个寄存器短,然后一个智能例程来推送和弹出可能比内存访问更快;就像一个几乎解决的立方体可以比弹出更快地解决用螺丝刀打开的东西。)

屈服点为2或3(寄存器低于您想要的值),其中局部变量比推送和弹出更快,具体取决于代码以及如何进行混洗(优化)。