编译器全貌介绍

·第一段叫前端(Frontend):其输入为源代码,输出为中间表示(IntermediateRepresentation,简写为IR,IR也被称作中间代码、中间语言)。IR没有标准语法。各编译器都可以自定义IR。比如LLVM就有LLVM IR,而Java字节码也是一种IR。前端的工作主要是解析输入的源码,并对其进行词法分析、语法分析、语义分析、生成对应的IR等。

·第二段叫优化器(Optimizer):优化器的输入是未优化的IR,输出是优化后的IR。常用的优化手段有循环优化、常量传播和折叠、无用代码消除、方法内联优化等。另外,优化器在优化阶段的最后还要执行一项非常重要的工作,即考虑如何分配物理寄存器。比如,IR中往往使用不限个数的虚拟寄存器,而目标机器的物理寄存器的个数却是有限的。所以优化器需要有一种方法来合理分配物理寄存器。而对那些不能保存在物理寄存器中的值,优化器还需要生成将这些值存储到内存、从内存中读取它们的指令。

·最后一段叫后端(Backend):其输入为优化后的IR,输出为目标机器的机器码。后端的主要功能是将IR翻译成机器码。

- 不同语言的开发者可以开发针对特定语言的前端模块,比如C、C++、Fortran。这些前端模块只要将对应语言的源代码转换成IR即可。

- 优化器模块的输入是IR,输出也是IR。这种设计的好处是擅长优化的开发者可以将精力集中在如何优化IR上,而不会被输入的编程语言以及目标机器的特性所束缚。

- 同理,开发者可为不同目标机器开发对应的后端模块。如图6-2中针对X86、ARM和MIPS机器的后端模块。

总体而言,三段式编译器架构的设计充分体现了“术业有专攻”的重要性和必要性。因为对难度如此之大的编译器开发而言,很少有开发者能同时精通这三个模块。通过将编译器拆分成三个较为独立的模块,开发者就能集中精力于他们所擅长的部分,不断去发展和完善它们。

 

LLVM和GCC

LLVM只是一个编译器基础架构和编译器开发库,而不是一个完整的编译器(LLVM不包括Frontend)。不过,LLVM从设计之初就高瞻远瞩地定义了LLVMIR,并且其内部采用高度独立的模块化设计。在LLVM架构上,搭配针对不同编程语言的前端就可以构造相应语言的编译器。比如Clang搭配LLVM就构成了C/C++编译器。另外,LLVM内部模块也可以单独提炼出来以库(动态库或静态库都支持)的方式供其他程序使用。这些因素都使得LLVM展现出了强大的可扩展性和生命力。相比而言,GCC也有自定义的IR。不过由于历史原因,其三段之间的耦合非常大,开发者很难优化其中某些部分或者单独使用某个模块。参考资料[1]对LLVM的架构、GCC的问题等有更详细的介绍,读者不妨一看。另外,LLVM可以搭配GCC使用,只要将GCC的前端模块和LLVM组合,就能得到一个性能比GCC还强大的编译器[插图]。

对ART虚拟机而言,其编译模块没有包含Frontend,因为从Java源代码到Dex字节码的前端工作是在APP开发过程中由Java编译器、dex工具完成的。图6-3为ART虚拟机中编译模块的情况。