C编译器剖析_5.4.1 中间代码生成与优化_删除无用的临时变量和优化跳转目标

5.4.1  删除无用的临时变量和优化跳转目标

    UCC编译器在优化方面做的工作不多,其中与优化有关的函数主要有以下几个:

(1)    Symbol  Simplify(Type ty, int opcode, Symbol src1,Symbol src2);

用于进行“代数恒等式”的简化,例如表达式“a<<0”可简化为a。

(2)    Symbol  TryAddValue(Type ty, int op, Symbol src1,Symbol src2);

用于处理“公共子表达式”。

(3)    void Optimize(FunctionSymbolfsym);

用于对“C源代码中的某一函数”进行优化。

     在前面的章节中,我们已经对函数Simplify和TryAddValue做过讨论,在这一小节中,我们主要讨论函数Optimize。我们在中间代码生成阶段,产生了许多临时变量,用来存放表达式的值,在优化时,如果发现有些临时变量的值并没有在其他地方被使用(即该临时变量的引用计数为1),则可删除该临时变量。如图5.4.1所示,对于第14至18行的C程序而言,优化前的中间代码在第24至28行,其中的临时变量t0至t4并没有在其他地方被使用,优化时,我们可删除t1、t2、t3和t4,第31和32行是优化后的中间代码。

C编译器剖析_5.4.1 中间代码生成与优化_删除无用的临时变量和优化跳转目标_第1张图片

图5.4.1 删除无用的临时变量

     我们注意到,图5.4.1第24行的t0在优化后仍然得到了保留,其原因在于函数GetData()的返回值为结构体对象。为第24行的中间代码“t0:GetData();”生成汇编代码时,UCC编译器将其视为“GetData(&t0);”,我们会在主调函数main的栈中为t0分配内存,然后把t0的首地址传递给被调函数GetData()。对UCC编译器而言,第5行的函数GetData的接口相当于是:

         void  GetData(Data * ptr);

     按照C的语义,函数的返回值可以是整数、浮点数和结构体对象,但不可以是数组。如果返回值是整数,这些值可以暂存于X86的通用寄存器eax和edx中;而如果返回值是浮点数,则可暂存于浮点协处理器X87的寄存器中;但对于图5.4.1第4行的结构体来说,其结构体对象要占16字节的空间,如果CPU中的寄存器无法存放结构体对象,UCC编译器在生成汇编代码时,就会隐式地按照GetData(&t0)来处理第31行的函数调用。C标准要为“在x86平台上,函数调用的返回值应如何传递等细节”进行统一约定,即“调用约定 Calling Convention”。在后续的“目标代码生成”章节中,我们会遇到用于为“函数调用生成汇编代码”的EmitCall(),到时我们再进一步展开讨论。图5.4.1第34至38行是与函数调用“(GetData(&t0));”对应的汇编代码。

     接下来,我们来看一下“删除无用临时变量”的函数EliminateCode,如图5.3.2第17至50行所示,第25至30行用于删除形如“t4:f();”中的无用临时变量t4,而第31至41行用于删除形如“t2:~a;”的无用临时变量t2,由于表达式“~a”没有副作用,不仅可删去临时变量t2,同时还可在第38至39行删去整条中间代码。而函数调用f()有副作用,我们就只能在第29行删去临时变量t4。

C编译器剖析_5.4.1 中间代码生成与优化_删除无用的临时变量和优化跳转目标_第2张图片

图5.4.2 Optimize()

     图5.4.2第1至16行的函数Optimize用于对C源代码中的某一函数进行优化,第4行调用的PeepHole会对基本块进行窥孔优化,我们已在前面的章节中介绍过这个函数,第8行调用的EliminateCode函数用于删除无用的临时变量,第9行调用的ExamineJump函数用于优化跳转语句的跳转目标,而第14行调用的TryMergeBBlock则尝试进行相邻基本块之间的合并。

     图5.4.3给出了函数ExamineJump的代码,当我们面对形如第2至10行的中间代码时,第4行的跳转语句是基本块BB的最末一条指令,其跳转目标为BB1,而基本块BB1又只有一条跳往BB2的无条件跳转指令,此时我们可把基本块BB最末一条指令的跳转目标优化为BB2,如第14行所示。图5.4.3第26行用于获取基本块BB的最后一条指令,当该指令不是跳转指令时,我们从第29行直接返回。对基本块BB而言,当我们将第4行的跳转目标BB1改为第14行的BB2后,基本块BB、BB1和BB2之间的“前驱与后继的关系”发生了变化,我们要从BB基本块的后继链表中找到BB1,将该链表元素改为BB2,第31至37行的do语句用于在后继链表bb->succs中查找BB1,之后在第42行将其改为BB2,这样BB2就成了BB基本块的后继结点。而第43行的代码用于从BB1基本块的前驱链表中删去BB基本块,第44行用于在基本块BB2的前驱链表中添加基本块BB。第47行实现了将BB基本块最末一条指令的跳转目标从BB1改为BB2的优化操作。

C编译器剖析_5.4.1 中间代码生成与优化_删除无用的临时变量和优化跳转目标_第3张图片

图5.4.3 ExamineJump()

     在下一小节中,我们会对图5.4.2第14行调用的TryMergeBBlock函数进行分析,该函数用于基本块的合并。

你可能感兴趣的:(C编译器剖析)