答案 0 :(得分:10)
我曾经在JVM分配器中使用了一种贪婪的方法,效果非常好。基本上从基本块的顶部开始,所有值都存储在堆栈中。然后只需向前扫描指令,维护一个包含值的寄存器列表,以及值是否为脏(需要写回)。如果指令使用的值不在寄存器中(或者不在正确的寄存器中),则在指令之前发出加载(或移动)以将其置于空闲寄存器中。如果指令写入值,请确保它在寄存器中并在指令后将其标记为脏。
如果您需要一个寄存器,请通过从中取消分配值来溢出已使用的寄存器,如果它是脏的并且存在,则将其写入堆栈。在基本块的末尾,写回所有脏和实时寄存器。
此方案清楚地说明了所有装载/存储的确切位置,您可以随时生成它们。它很容易适应在内存中取值的指令,或者可以在内存中取两个参数中的任何一个,但不能同时取两者。
如果您可以在每个基本块边界处拥有堆栈上的所有数据,则此方案可以很好地工作。它应该在基本块中给出类似于线性扫描的结果,因为它基本上做了非常相似的事情。
如何决定溢出哪些值以及分配哪些寄存器,您可能会变得任意复杂。一些前瞻可能是有用的,例如通过在基本块中的某个点(例如,eax表示返回值,或ecx表示移位量)标记每个值,并且在值时更喜欢该寄存器首先分配(并避免该寄存器用于其他分配)。但是很容易将算法的正确性与改进启发式分开。
我在SSA编译器中使用了这个分配器,YMMV。
答案 1 :(得分:7)
第一:没有明智的方法可以做到这一点。问题是NP完全; - )
如何进行溢出:
您运行注册分配算法并获取必须泄漏的变量列表。现在,您可以在函数开头的堆栈中分配一些空间。将每个溢出变量链接到堆栈上的某个位置。如果你想要智能合并内存与非重叠的有效范围。 每当需要溢出寄存器时,将其保存到内存中并在需要时再加载它。
如何处理eax:
将寄存器标记为已填充,但不在其中存储任何变量(预分配)。这将使代码生成器清除该寄存器。如果有益,可以将值存储在另一个寄存器中。
处理溢出的简单而正确的方法:
只是泄漏一切。这假设每个变量的有效范围都是整个程序。这可以通过使用LRU或使用计数等内容来选择应该释放哪些寄存器来增强。
接下来最好的事情可能是linear scan register allocation。即使使用预分配,它也应该很容易实现。我建议你查看链接的论文。
具体答案
正确性对您意味着什么?如果不编程错误,即使是简单的分配算法也是正确的。打样(数学)正确性要困难得多。在再次需要值/寄存器之前,需要插入加载和存储。两者都需要在存储/创建值后插入。
是。如果你这样编程。如果您的算法可以在其实时时间内处理多个寄存器中的值,则可以使用这些优化。
再次由您来实施某些改进。一种可能性是仅在需要时阻止eax,而不是整个程序。
在某些情况下,SSA会提供帮助。 SSA代码的推理图总是chordal,这意味着没有超过3个节点的循环。这是图着色的一种特殊情况,其中可以在多项式时间中找到最小着色。转换为SSA并不一定意味着更多或更少的注册压力。虽然SSA表单通常有更多变量,但这些变量往往具有较小的生存时间。