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

6.3.3        为跳转指令产生汇编代码

在这一小节中,我们要为“有条件跳转”、“无条件跳转”和“间接跳转”产生相应的汇编指令。中间指令的四元式如下所示:

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

(1) 有条件跳转,例如“if (a <= b) goto BB2;”,四元式为:

<JLE,BB2,a,b>

////////对应的汇编代码//////////

movl  a, %eax    //把SRC1的值暂存在寄存器eax中

cmpl b, %eax     //比较eax和b的大小

jle   .BB2       //进行有条件跳转

(2) 无条件跳转,例如“goto  BB3;”,四元式为:

<JMP,BB3,NULL,NULL>

////////////对应的汇编代码///////////////

jmp          .BB3

(3) 间接跳转,例如“goto (BB4,BB5,BB6)[t0];”,在翻译switch语句时会产生间接跳转指令,其四元式为:

<IJMP,[BB4,BB5,BB6,NULL],t0,NULL>

////////////对应的汇编代码///////////////

.data

swtchTable1: .long         .BB4

.long         .BB5

.long         .BB6

.text

jmp*swtchTable1(,%eax,4)

由于跳转指令位于基本块的最末尾,我们在第6.2节的“图6.2.3 EmitBBlock()”中,已对x87栈顶寄存器做过回写操作。在本小节,我们还要中调用ClearRegs函数,对于x86 CPU中的寄存器进行回写,如图6.3.7第7行、第26行和第54行所示。控制流即将随着跳转指令的执行而进入另一个基本块,而UCC编译器仅在同一基本块内对公共子表达式进行重用,因此,即使跳转指令里的操作数DST、SRC1和SRC2是临时变量,我们没有必要为其“长期”分配寄存器,换言之,我们不会调用在前面的章节中介绍过的AllocateReg函数,而是调用如图6.3.7第13行所示的PutInReg函数,把操作数SRC1的值暂存到某个寄存器中。

图6.3.7第1至30行的EmitBranch函数用于为“有条件跳转”产生汇编代码,当操作数是浮点数时,我们在第8行调用EmitX87Branch函数来处理。当操作数是整数时,我们会在第11行做进一步判断。由于常数会以“立即数”的形式存在于代码区中,当程序运行时,CPU会从代码区里预读机器指令,从而把立即数也加载入CPU,因此当操作数SRC2是常数时,我们可以不必把SRC1的值加载到寄存器中,这不会违反“同一条X86汇编指令的两个操作数不可以都在内存中”的寻址要求。这意味着我们可以生成形如“cmpl
 $3, a”的比较指令,但不可以生成形如“cmpl  b, a”的比较指令。图6.3.7第11至14行会在“SRC2存在且SRC2不是常数时”,通过第13行调用的PutInReg函数,把SRC1的值加载到某个寄存器中。第17行判断SRC1的值是否在寄存器中,如果已载入寄存器,我们可在第18行把中间指令里的源操作数SRC1改为SRC1->reg,之后在就可生成形如“cmpl  b, %eax”的比较指令。第28行调用PutASMCode函数来产生比较和跳转指令,形如“cmpl  b, %eax;jle .BB2
”。

图6.3.7  EmitBranch()和EmitJump()

图6.3.7第31至39行的EmitX87Branch用于处理浮点数的有条件跳转,我们会在第34行先把操作数SRC1加载到x87栈顶寄存器中,然后在第37行生成浮点数比较和跳转的指令,形如第35至36行的模板所示。我们已在“1.5  结合C语言来学汇编”介绍过这些指令,这里不再重复。图6.3.7第50至56行的函数EmitJump会为无条件跳转生成汇编代码。

接下来,我们来讨论一下为“间接跳转”产生汇编代码的函数EmitIndirectJump,如图6.3.8所示。图6.3.8第7行调用PutInReg函数把操作数SRC1加载到寄存器中,第10行用于产生“.data”,表示接下来的内容为数据区,第11至17行创建一个名称形如“swtchTable1”的符号对象,第23至33行会在数据区中创建跳转表,形如第19至21行所示。第36行输出“.text”,表示接下来的内容为代码区。由于已在第7行把SRC1加载到寄存器reg中,我们就可在汇编指令中使用该寄存器的名称,为此我们要在第37行把中间指令的SRC1改为相应的寄存器。第38行调用ClearRegs对x86
CPU中的寄存器进行了必要的回写,第41行调用PutASMCode产生了形如“jmp*swtchTable1(,%eax,4)”的汇编指令。

图6.3.8  EmitIndirectJump()

时间: 2024-12-09 07:30:11

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

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

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

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

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

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

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

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

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

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

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

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行局

C编译器剖析_5.4.2 中间代码生成及优化_基本块的合并

5.4.2  基本块的合并 我们在第5.4.1节时给出了由基本块构成的双向链表和控制流图,为阅读方便,我们这里再次给出"图5.1.4 基本块的静态结构和动态结构".在这一小节中,我们试图把双向链表中相邻的基本块进行合并,当然这种合并需要满足一定条件,同时要保持程序的原有语义.在合并后,控制流图中的前驱与后继关系也要进行调整.我们需要改动的数据结构有图5.4.1中的双向链表和控制流图.需要注意的是,虽然基本块BB5和BB6在双向链表中相邻,但控制流却不会由BB5流入BB6,双向链表只是维

C编译器剖析_5.3.1 中间代码生成及优化_If语句和复合语句的翻译

5.3.1   If语句和复合语句的翻译 我们先简单回顾一下对布尔表达式的翻译,我们通过调用TranslateBranch函数来产生跳转指令,从而实现布尔表达式的语义.在使用函数TranslateBranch(expr, bt, bn)时,有这么两个约定: (1) 当expr为真时,跳往bt基本块: (2) 紧随"函数TranslateBranch所生成的跳转指令"之后的基本块为bn. 在表达式的基础上,我们来讨论一下语句的翻译.图5.3.1用于if语句的翻译,第4至8行说明了如何翻译

C编译器剖析_5.3.2 中间代码生成及优化_switch语句的翻译

5.3.2.Switch语句的翻译 在这一小节中,我们来讨论一下switch语句的翻译,switch语句的产生式如下所示. SwitchStatement: switch( expr ) statement 当C程序员编写出如下代码时,UCC编译器会在语义检查阶段进行报错"error:The  break shall  appear  in  a  switch  or loop",从语法上来看,以下"case  3: b = 30;"是case语句,而"