为算术运算产生汇编代码

6.3.2 为算术运算产生汇编代码

在这一小节中,我们要讨论的中间指令形如“t1: a+b;”或者“t2:~number”,这些指令用于进行一元或二元算术运算,并把运算结果保存在临时变量t1或者t2中。UCC中间指令的格式如下所示:

<运算符opcode,,目的操作数DST,源操作数SRC1,源操作数SRC2>

<ADD,t1,a,b> // t1:a+b;

<BCOM,t2,number,NULL> // t2:~number;

由于在一条x86汇编指令中,最多只允许出现2个操作数,而中间指令“DST:SRC1+SRC2”有3个操作数,我们需要产生多条x86汇编指令来实现该中间指令。在汇编指令中,整数加法运算对寄存器没什么特别要求,我们可按以下步骤来处理:

(1) 调用AllocateReg函数依次为SRC1、SRC2和DST分配寄存器。DST是用于保存运算结果的临时变量,必然可分配到一个寄存器,不妨记为R0。而如果SRC1和SRC2不是临时变量,则没有分配到寄存器。

(2) 若DST和SRC1对应的寄存器不一样,我们可产生一条movl指令,把SRC1的值传送到寄存器R0中。

(3) 产生加法指令,进行SRC2和R0的加法,并把结果存于寄存器R0中。

按这样的思路,我们可为“t1 : a+b;”产生以下汇编代码:

movl a, %eax

addl b, %eax

而形如“t2: &number”的中间指令只有两个操作数,在上述第(1)步中,我们就不必为

SRC2分配寄存器,其他的步骤类似,我们可为“t2: &number”产生以下汇编代码:

leal a, %ecx

不过,有些x86汇编指令对寄存器有特定的要求,比如整数的乘法运算就要求源操作数SRC1的值被加载到寄存器eax中。而整数的除法运算或者取余运算,要求源操作数SRC1的值被加载到eax中,如果要进行的是有符号数的除法运算,则寄存器edx的所有位都被设置为SRC1的符号位;如果要进行的是无符号数的除法运算,则寄存器edx被置为全0。例如我们可为中间指令“t3: a /b;”产生以下汇编代码,其中a和b为有符号整数。

movl a, %eax //把SRC1加载到eax

cdq //把符号位扩展到edx寄存器

idivl b //进行除法运算[edx: eax] / SRC2,商存于eax,余数存于edx,

//此时eax中的值就是临时变量t3的值

在x86“左移或右移”的汇编指令中,如果要把左移或右移的位数存放于寄存器中,则必须使用单字节寄存器cl,如下所示:

inta, c; char len = 3;

c = a <<len;

////中间代码//////////////

t4 : (int)(char)len; //把char提升为int

t5 : a << t4;

c = t5;

对应的汇编代码如下所示,我们可以看到,在汇编指令“shll %cl,%edx”中,我们是用单字节寄存器cl来存放操作数len的值。

movsbl len, %eax // t4 : (int)(char)len;

movl %eax, %ecx //t5:a << t4

movl a, %edx

shll %cl, %edx

movl %edx, c // c = t5;

有了这些基础后,我们可以来讨论一下“为算术运算生成汇编代码”的函数EmitAssign,如图6.3.5所示。第47至56行用于处理对寄存器没有特别要求的二元运算,形如“DST:SRC1+SRC2;”。第44至45行用于处理形如“DST: ~SRC1”的一元运算,此时我们不必为SRC2分配寄存器。第33至43行用于为左移或右移指令里的SRC2分配寄存器ecx,并在第39行把SRC2加载寄存器ecx,之后在第41行把SRC2改为单字节寄存器cl,即4字节寄存器ecx的低8位。

图6.3.5 EmitAssign()

长江后浪催前浪,一辈新人换旧人。

为算术运算产生汇编代码

相关文章:

你感兴趣的文章:

标签云: