C编译器剖析_6.3.6 汇编代码生成_为“取地址”产生汇编指令

6.3.6  为“取地址”产生汇编指令

在这一小节中,我们来讨论一下以下两条中间指令的翻译:

(1)取地址指令<ADDR,DST,SRC1,NULL>

例如 <ADDR,t0,  number, NULL>,表示取number的地址并保存到临时变量t0中

(2)对象清零指令< CLR,DST,SRC1,NULL>

例如<CLR,arr,16,NULL>,表示把arr所占16字节的内存清零

我们先举一个例子来说明,对于图6.3.14第4行局部数组arr的初始化来说,我们可以先通过第11行的CLR中间指令把数组arr所占16字节内存清零,然后再通过第12行的MOV指令对arr[0]进行赋值。第21至26行是CLR指令对应的汇编代码,我们调用在汇编中调用库函数memset实现了对象清零的操作。而对于第5行的“ptr1 = &num1;”而言,我们可以通过第13行的ADDR中间指令来取num1的地址,并保存于临时变量t0中,再通过第14行的MOV指令对ptr1进行赋值。第28行“leal 
num1,%eax”是ADDR指令对应的汇编代码。

图6.3.14 取地址和对象清零的例子

与取地址指令有关的中间指令还有第17行的DEREF指令和第18行的IMOV指令,我们已在前面的章节中介绍过这两条中间指令的翻译。通过这两条中间指令,我们可以实现第7行的C语句“*ptr1 = *ptr2;”所需的语义。

接下来,我们来看看用于产生相应汇编代码的函数EmitAddress和函数EmitClear,如图6.3.15所示。第5行调用AllocateReg函数为ADDR指令里的临时变量DST分配一个寄存器,第7行产生汇编指令把SRC1的地址加载到DST对应的寄存器中,第8行调用ModifyVar来设置临时变量DST的回写标志位,表示“其在内存中的值”与“在寄存器中的值”已经不一致。

6.3.15     EmitAddress()和EmitClear()

图6.3.15第10至35行用于为CLR指令产生汇编代码,当要清零的对象大小为{1,2,4}字节时,我们可以在第15至22行产生mov汇编指令来实现清零,否则我们就通过第32行产生汇编代码,在其中调用memset函数来实现对象清零。由于在汇编指令模板X86_CLEAR中使用了寄存器eax,我们需要在第28行调用SpillReg函数对寄存器eax进行回写。

至此,我们完成了为各中间指令产生汇编代码的工作。

最后,让我们再看一下Compile函数,如图6.3.16所示,这是我们从第3章开始一路走来的路线图,我们历经了“语法分析”、“语义检查”、“中间代码生成及优化”和“汇编代码生成”这几个阶段。

图6.3.16  Compile()

时间: 2024-10-07 19:53:53

C编译器剖析_6.3.6 汇编代码生成_为“取地址”产生汇编指令的相关文章

C编译器剖析_6.1 汇编代码生成_简介

6.1 汇编代码生成简介 历经词法分析.语法分析.语义检查和中间代码生成阶段,我们终于来到了"目标代码生成阶段",由于UCC编译器的目标代码即为32位x86汇编代码,因此我们就把本章称为"汇编代码生成".UCC编译器中的大部分源代码都适用于Windows和Linux平台,但Windows平台上缺省的汇编器支持Intel风格的x86汇编代码,而Linux平台默认的汇编器则采用AT&T风格的x86汇编代码,两者在汇编语法上有一些差别,为节省篇幅,我们主要针对Li

C编译器剖析_6.3.1 汇编代码生成_由中间指令产生汇编代码的主要流程

6.3.1  由中间指令产生汇编代码的主要流程 在这一小节,我们可把关注的焦点放在"如何把某条中间代码翻译成汇编代码"上.UCC编译器的中间代码是如下所示的四元式,包括运算符和3个操作数. <运算符opcode,目的操作数DST,源操作数SRC1,源操作数SRC2> 当然有些中间代码只需要用到opcode和DST就可以了,例如,无条件跳转指令"goto  BB2;"就不需要SRC1和SRC2.为了便于汇编代码的生成,UCC编译器在ucl\X86Linux

C编译器剖析_6.3.5 汇编代码生成_为类型转换产生汇编代码

6.3.5  为类型转换产生汇编代码 在这一小节中,我们来讨论一下整型和浮点型之间的类型转换.有些类型转换并不需要在汇编层次进行数据转换,例如int和unsigned  int之间的转换只是改变了表达式的类型,对数据本身并无影响,以下表达式"(unsigned int) a"对应的二进制数据为0xFFFFFFFF,而表达式"a"对应的二进制数据也为0xFFFFFFFF.但对相同内容的二进制数据来说,进行"有符号整数的右移"和"无符号整数

C编译器剖析_6.2 汇编代码生成_寄存器的管理

在计算机中,CPU的速度比内存的速度快得多,编译器应尽量有效地利用寄存器资源,减少对内存的不必要访问,从而提高由编译器生成的汇编代码的运行速度.在中间代码生成阶段,UCC编译器用临时变量t来存放形如"t: a+b;"的公共子表达式的值:到了汇编代码生成时,UCC编译器会尽可能地把这些公共子表达式的值存放在寄存器,当需要再次重用时,就可以直接由相应的寄存器中得到.不过,CPU中寄存器的资源是很有限的,在32位的x86 芯片上,汇编程序员可用的寄存器有{eax,ebx, ecx,edx,

C编译器剖析_5.2.2 中间代码生成及优化_再论符号symbol与公共子表达式

5.2.2    再论符号symbol与公共子表达式 在介绍算术表达式的翻译前,让我们简单重温一下第2.5节中的"图2.5.4 公共子表达式"及"图2.5.5 valueDef和valueUse".为阅读方便,我们再次给出这两张图,更详细的说明请参见第2.5节.对于图2.5.4第2行的a+b,我们会由第7行的中间代码来对a+b进行求值,其结果存于临时变量t1中,之后在第3行中再次遇到表达式a+b时,a和b的值并没有发生变化,我们可在第9行直接把t1赋给变量d.由于我

C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译

5.2.4 后缀表达式的翻译 在前面的章节中,我们介绍了用于对数组元素和结构体成员进行访问的函数Offset,其接口如下所示,参数addr代表了基地址,参数voff代表可变偏移,而参数coff则代表常量偏移. Symbol Offset(Type ty, Symbol addr,Symbol voff, int coff): 函数Offset的基本想法是产生以下中间代码,我们要先对addr.voff和coff进行相加,得到目标地址(addr+voff+coff),然后再进行"提领Derefere

C编译器剖析_6.3.4 汇编代码生成_为函数调用与返回产生汇编代码

6.3.4        为函数调用与返回产生汇编代码 在这一小节中,我们来讨论一下如何为函数调用和函数返回生成汇编代码.函数调用对应的中间指令如下所示: //中间指令的四元式: < opcode, DST, SRC1, SRC2> <CALL, 用于接收返回值的变量retVal, 函数名func,  参数列表[arg1,arg2, -,argn]> 让我们先熟悉一下C函数的调用约定CallingConvention,我们需要把参数从右向左入栈(即从argn到arg1依次入栈),不

C编译器剖析_6.3.3 汇编代码生成_为跳转指令产生汇编代码

6.3.3        为跳转指令产生汇编代码 在这一小节中,我们要为"有条件跳转"."无条件跳转"和"间接跳转"产生相应的汇编指令.中间指令的四元式如下所示: <运算符opcode,目的操作数DST,源操作数SRC1,源操作数SRC2> (1) 有条件跳转,例如"if (a <= b) goto BB2;",四元式为: <JLE,BB2,a,b> ////////对应的汇编代码////////

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

6.3.2  由EmitAssign函数产生算术运算的汇编代码 在这一小节中,我们要讨论的中间指令形如"t1: a+b;"或者"t2:&number",这些指令用于进行一元或二元算术运算,并把运算结果保存在临时变量t1或者t2中.UCC中间指令的格式如下所示: <运算符opcode,目的操作数DST,源操作数SRC1,源操作数SRC2> <ADD,t1,a,b>           //  t1: a+b; <ADDR,t2,n