C编译器剖析_6.3.2 汇编代码生成_由EmitAssign函数产生算术运算的汇编代码

6.3.2  由EmitAssign函数产生算术运算的汇编代码

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

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

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

         <ADDR,t2,number,NULL>  // t2: &number;

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

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

   (2) 若DST和SRC1对应的寄存器不1样,我们可产生1条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,以下所示:

         int  a, 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;

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


图6.3.5  EmitAssign()

     图6.3.5第8至32行用于为整数乘法、除法和取余运算产生汇编指令,第11至16行把SRC1的值暂存于寄存器eax中,第17行把edx寄存器的值回写到内存中,我们会在[edx:eax]中寄存经符号位扩大后的SRC1。第19至24行用于为SRC2分配必要的寄存器,第26行产生汇编指令来进行乘法或除法运算,以后寄存器eax中寄存的是“乘法运算的结果”或“除法运算的商”,而寄存器edx中寄存的是“除法运算的余数”,第27至31行用于记录“寄存运算结果的临时变量DST对应的寄存器为edx或eax”。

     由于浮点数运算的汇编指令与整数不同,我们需要在图6.3.5第4行调用EmitX87Assign函数,来对浮点数算术运算进行处理。我们先举1个例子来讲明,对形如“DST: SRC1+SRC2”的2元浮点数运算来讲,我们可按以下步骤来生成汇编代码:

   (1)若SRC1不在x87栈顶寄存器中,则先对x87栈顶寄存器进行必要的回写,然后把SRC1加载到x87栈顶寄存器中。

   (2)对x87栈顶寄存器与SRC2进行加法运算,结果存于x87栈顶寄存器中。

    按这个思路,我们可为浮点数中间指令“t6: d+e”产生以下汇编代码:

flds  d     //把浮点数d从内存加载到x87栈顶寄存器

         fadds  e    //完成加法运算后,x87栈顶寄存器即为t6的值

    而对形如“DST:-SRC1”的1元运算来讲,我们在上述第(2)步中,只要对x87栈顶寄存器进行1元运算便可,运算后的结果仍存于x87栈顶寄存器中。例如,我们可为“t7: -d”产生以下汇编代码。

         flds  d     //把浮点数d从内存加载到x87栈顶寄存器

         fchs        //把符号位取反,x87栈顶寄存器即为t7的值

     现在,我们可以来分析1下“用于产生这些浮点运算汇编代码”的函数EmitX87Assign,如图6.3.6所示。如果SRC1还未加载到x87栈顶寄存器中,我们就通过第5行进行必要的回写,然后在第6行把SRC1从内存加载到x87栈顶寄存器中。如果SRC1的值已在x87栈顶寄存器中,此时SRC1必为临时变量,若SRC1还要在后续的中间指令中被使用,我们需要在第10行把SRC1的值回写到内存中,由于进行浮点运算后,栈顶寄存器保存的是DST的值,而非SRC1的值。如果操作数SRC2就是SRC1,由于遇到形如第13行的模板“faddl 
%2”时,我们要把x87栈顶寄存器与位于内存中的SRC2进行加法运算,而此时SRC2就是SRC1,因此我们也要在第10行把SRC1的值回写到内存中。第15行根据汇编指令模板,调用PutASMCode函数产生浮点数运算的汇编指令。


图6.3.6  EmitX87Assign()

        

波比源码 – 精品源码模版分享 | www.bobi11.com
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!

波比源码 » C编译器剖析_6.3.2 汇编代码生成_由EmitAssign函数产生算术运算的汇编代码

发表评论

Hi, 如果你对这款模板有疑问,可以跟我联系哦!

联系站长
赞助VIP 享更多特权,建议使用 QQ 登录
喜欢我嘛?喜欢就按“ctrl+D”收藏我吧!♡