在汇编代码生成中,一个很重要的问题就是寄存器的分配。
在计算机中,CPU 的速度比内存的速度快得多,编译器应尽量有效地利用寄存器资源, 减少对内存的不必要访问,从而提高由编译器生成的汇编代码的运行速度。在中间代码生成 阶段,UCC 编译器用临时变量 t 来存放形如“t: a+b;”的公共子表达式的值;到了汇编代码 生成时,UCC 编译器会尽可能地把这些公共子表达式的值存放在寄存器,当需要再次重用 时,就可以直接由相应的寄存器中得到。不过,CPU 中寄存器的资源是很有限的,在 32 位 的 x86 芯片上,汇编程序员可用的寄存器有{eax, ebx, ecx,edx, esi,edi,esp,ebp}8个,其中寄存器 esp 一般用于指向栈顶,而 ebp 一般用于指向活动记录的底部。真正可供选择的寄存器就只 有{eax,ebx,ecx,edx,esi,edi}这 6 个,当公共子表达式的个数比可用寄存器更多时,我们就要 把某些寄存器的值回写 WriteBack 到“临时变量对应的内存单元”中,以便腾出可用寄存器来存放其他值。为了简化寄存器的分配算法,UCC 编译器只为临时变量分配寄存器,这意味 着我们希望尽可能地重用形如“t: a+b;”这样的公共子表达式。
与之形成对比的是在函数 g 的第 57 行,我们把“(a+b)+(c+d)”的值存于寄存器 eax,其 原因是在第 57 行后,基本块 BB1 中不再使用第 25 行的临时变量“t0: a+b”。
如果把寄存器分配的范围扩大到有名的变量(即 C 程序员命名的全局、静态和局部变 量),还可进一步地减少内存的访问次数。例如,当我们把全局变量 a 读入某个寄存器 R1 后,之后再次需要 a 的值时,可直接由寄存器 R1 得到,不必再去访问内存。但是由于 C 程 序员既可通过变量名来访问“有名的变量”,也可以通过变量的地址 addr 来间接访问,其访 问方式比较灵活。如果通过*addr 的形式来访问全局变量 a,我们可能会把 a 的值加载到另 一个寄存器 R2 中,对全局变量 a 再进行写操作时,就可能出现寄存器 R1 和 R2 中的内容不 一致的问题。为了避免这样的问题,编译器需要做较复杂的分析。因此UCC只为临时变量分配寄存器,因为临时变量是UCC自己产生的,对 C 程序员不可见,UCC当然知道使用情况,而且不会通过地址去访问它们。
|