5.3.1 中间代码生成及优化

5.3.1 If语句和复合语句的翻译

我们先简单回顾一下对布尔表达式的翻译,我们通过调用TranslateBranch函数来产生跳转指令,从而实现布尔表达式的语义。在使用函数TranslateBranch(expr, bt, bn)时,有这么两个约定:

(1) 当expr为真时,跳往bt基本块;

(2) 紧随“函数TranslateBranch所生成的跳转指令”之后的基本块为bn。

在表达式的基础上,我们来讨论一下语句的翻译。图5.3.1用于if语句的翻译,第4至8行说明了如何翻译“if(expr) stmt”,我们需要创建trueBB和nextBB这两个基本块;而第12至18行阐述了如何翻译“if(expr) stmt elsestmt”语句,我们需要创建trueBB、falseBB和nextBB这3个基本块。图5.3.1第29至34行的代码用于翻译“if(expr) stmt”,而第35至43行的代码用于翻译“if(expr) stmt else stmt”。

图5.3.1 TranslateIfStatement()

由图5.3.1可以发现,在理解了表达式翻译的前提下,if语句的翻译就较容易理解了。而其他的控制流语句,包括do语句、while语句和for语句也与此类似,我们就不再啰嗦。接下来,我们来讨论复合语句的翻译。

语法上,复合语句的产生式如下所示,由可选的声明列表和语句列表构成。其中的声明可以带有初值。

CompoundStatement –> { DeclarationListopt StatementListopt}

由于局部变量的存储空间是运行时在栈中动态分配的,UCC编译器需要在编译时产生中间代码来实现对局部变量的初始化,而在对应的汇编代码中,由于无法预知相应的存储单元,我们只能采用“ebp寄存器+常量偏移”的模式来对其寻址。程序运行时,我们会在栈中为被调用的函数分配一块内存,用于存放其形参、局部变量、函数返回地址等信息,这块内存通常被称为“活动记录activation record”或者“帧frame”。大部分的C编译器,会使用x86的ebp寄存器来指向当前函数的活动记录;而“常量偏移”则可由C编译器在编译时,根据局部变量声明在函数中出现的先后顺序和所占内存大小来确定。

对于以下局部数组arr的初始化,我们可以先把arr所占的栈空间清0,然后再根据“初值及其偏移”来产生初始化相应数组元素的指令。

int arr[8] = {10,20,30};

我们在语义检查时介绍过以下结构体struct initData,其中的offset即可用于描述偏移,,而expr对应的表达式为初值,而next用于构造链表。

struct initData{

intoffset; //偏移

AstExpressionexpr; //初值对应的表达式

InitDatanext;

};

typedef struct initData* InitData;

上述初值{10,20,30}经语义检查后,我们可得到以下链表,在中间代码生成阶段,我们

可由该链表产生“对相应数组元素进行初始化”的中间代码。

(表达式10,偏移0) –> (表达式20,偏移4) –> (表达式30,偏移8)

///////对应的中间代码////////////

arr : 40; //把arr所占40字节栈内存清0

arr[0] = 10;

arr[4] = 20;

arr[8] = 30;

而到了汇编层次,局部变量的名字不再可用,我们改用“ebp寄存器+常量偏移”的方

式来表示局部变量。对应的汇编代码如下所示:

pushl$40

pushl$0

leal-40(%ebp), %eax

pushl%eax

callmemset //对数组arr清0,相当于调用memset(&arr[0], 0, 40);

addl$12, %esp

movl$10, -40(%ebp) //arr[0] = 10;

movl$20, -36(%ebp)

movl$30, -32(%ebp)

对于以下局部数组num来说,由于初值{1,2,3,4}覆盖了数组num所占的所有内存空间,我们并不需要额外产生对数组num进行清0的指令。

intnum[4] = {1,2,3,4};

有了这些基础后,我们就来看一下复合语句的翻译,如图5.3.2所示,第1至9行的函

放下一处烦恼,收获一个惊喜;放下一种偏见,收获一种幸福;

5.3.1 中间代码生成及优化

相关文章:

你感兴趣的文章:

标签云: