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

来源:互联网 发布:linux ftp目录设置 编辑:程序博客网 时间:2024/05/23 19:15

5.4.2  基本块的合并

    我们在第5.4.1节时给出了由基本块构成的双向链表和控制流图,为阅读方便,我们这里再次给出“图5.1.4 基本块的静态结构和动态结构”。在这一小节中,我们试图把双向链表中相邻的基本块进行合并,当然这种合并需要满足一定条件,同时要保持程序的原有语义。在合并后,控制流图中的前驱与后继关系也要进行调整。我们需要改动的数据结构有图5.4.1中的双向链表和控制流图。需要注意的是,虽然基本块BB5和BB6在双向链表中相邻,但控制流却不会由BB5流入BB6,双向链表只是维持各基本块在中间代码里的先后顺序,控制流图中的有向边才真正代表了控制流的流向。在本小节中,如未特别声明,“前驱和后继”是针对控制流图而言。


图5.1.4 基本块的静态结构和动态结构

    UCC编译器在中间代码生成时,会产生一些不包含任何中间代码的基本块,如图5.4.4第22至24行所示,其中的基本块BB4和BB5中都没有中间代码,这相当于控制流可以从BB4流入BB5,然后再流入BB6,这也意味着BB4是BB5的前驱,而BB5又是BB6的前驱。我们可以将这两个基本块与BB6合并。同时,还要把第20行的跳转指令改为第37行的跳转指令,此时第35行的基本块BB2是第38行基本块BB6的唯一前驱,我们可把BB2和BB6再进行合并,删去第37行的无用跳转语句,最终优化后的中间代码如图5.4.4第45至52行所示。


图5.4.4 无代码的空基本块

    UCC编译器的TryMergeBBlock函数会对双向链表的相邻基本块进行判断,当满足以下5种情况时可以进行合并操作,如图5.4.5所示。对于某个基本块bb而言,我们用bb->next来表示在静态结构中紧随bb之后的基本块,例如对于图5.4.4第22行的BB4来说,BB4->next即为BB5。

    (1) 在情况1中,bb2是基本块bb1的唯一后继,而bb2也只有一个前驱,此时我们可以把bb1和bb2进行合并。但有一个特殊情况要处理,即我们要删去如图5.4.4第37行的间接跳转指令IJMP。

    (2) 在情况2中,基本块bb1没有中间代码,此时可删去基本块bb1,bb1也不再是bb2的前驱。同时,还要修改bb1所有前驱的后继链表,使将其中的bb1改为bb2,bb1的所有前驱要成为bb2的前驱。

    (3) 在情况3中,基本块bb2没有中间代码,且无前驱,此时可删去bb2。

    (4)在情况4中,基本块bb2没有中间代码,其唯一前驱为bb1,由于bb2没有中间代码,控制流一旦进入bb2,则必然可进入bb2->next,此时bb2->next是bb2的后继。我们可删去bb2,还要使bb2->next成为bb1的后继。

    (5) 在情况5中,基本块bb1的最末一条指令为“jump  bb2;”,且其中的bb2在静态结构中是紧随bb1之后的基本块,即bb2就是bb1->next,此时我们可以删去基本块bb1中位于最末尾的无条件跳转指令“jump bb2”。


图5.4.5 基本块合并的5种情况

   有了上面的基础后,就不难理解函数TryMergeBBlock,其主要代码如图5.4.6所示,第5至34行对应“情况1”,第35至36行对应“情况2”,第37至39行对应“情况3”,第41至44行对应“情况4”,而第46至49行对应“情况5”。我们只给了“情况1”的代码,省略了其他情况的代码。


图5.4.6  TryMergeBBlock()

   图5.4.6第10至17行用于删除形如图5.4.4第37行的指令“goto(BB6,BB6,BB6,)[t0];”,基本块BB6在该指令中被多次重复引用,因此第10行的if条件会成立。当我们把满足图5.4.4“情况1”的基本块bb1和bb2进行合并时,由于前驱和后继关系发生变化,我们要通过图5.4.6第19至26行来修改控制流图,在第28行调用函数MergeInstructions来合并这两个基本块里的中间代码,还需要在第29至33行修改双向链表。

   至此,我们完成了UCC编译器“中间代码生成与优化”的讨论,UCC编译器可以通过ucl\uilasm.c中的函数DAssemTranslationUnit,把优化后的中间代码打印出来,该函数并不复杂,我们就从略,在下一章中,我们要开始讨论“目标代码生成”,UCC编译器的目标代码即为32位的x86汇编代码。
0 0