gdnt的专栏

LLVM与Clang是近来风头强劲的C/C++编译器套件。其发起者在《开源应用架构》(The architecture of open source applications)中简略介绍了LLVM的架构与设计思想。下面就是这篇文章的翻译。

第11章.LLVM

本章讨论形成LLVM的某些设计决定,一个承载及开发了一套严密的低级工具组件(比如,汇编器,编译器,调试器等)的综合项目,它被设计为与通常用在Unix系统上现存的工具兼容。名字“LLVM”过去是一个首字母缩略词,但现在只是这个综合项目的一个标签。虽然LLVM提供了某些唯一的功能,并因某些很好的工具而闻名(比如Clang编译器,,一个在GCC编译器上提供了若干好处的C/C++/Objective-C编译器),把LLVM与其它编译器区分开来的主要是其内部架构。

自2000年12月肇始,LLVM被设计为一组带有良好定义接口可重用的库[LA04]。在那时,开源编程语言实现被设计为专用工具,通常有单一可执行文件。例如,重用来自一个静态编译器(比如GCC)的解析器进行静态分析或重构非常困难。虽然脚本化语言通常提供了一个方法向更大的应用嵌入它们的运行时及解释器,这个运行时是要么包括、要么排除的单一整体代码块。没有办法重用部分,在语言实现项目间的共享非常有限。

除了编译器本身的组成,围绕流行语言实现的社区通常有强烈的倾向:一个实现通常提供一个传统的静态编译器,像GCC,Free Pascal,及FreeBASIC,或者以解释器或即时(JIT)编译器的形式提供一个运行时编译器。支持两者的语言实现十分罕见,而且如果它们这么做,通常共享的代码很少。

在过去的十多年中, LLVM已大大改变了这一局面。LLVM现在被用作一个通用的基础设施来实现各种静态及运行时编译的语言(比如由GCC,Java,.NET,Python,Ruby,Scheme,Haskell,D的语言家族,以及不计其数不那么知名的语言)。它还替换了各种专

它还替换了各种专用编译器,比如在Apple的OpenGL栈里的运行时专用引擎,以及Adobe的AfterEffects产品中的图形处理库。最后LLVM还以及用于创建各种新产品,其中最知名的可能是OpenCL GPU编程语言及运行时。

11.1. 经典编译器设计的简要介绍一个传统静态编译器(像大多数C编译器)最流行的设计是3阶段的设计,其中主要组件是前端,优化器及后端(图11.1)。前端解析源代码,检查错误,并构建一个特定于语言的抽象语法树(AST)来代表输入的代码。可选地AST被转换到一个新的用于优化的表示,优化器及后端可以运行这个代码。

图11.1:一个3阶段编译器的3个主要组件

优化器负责进行各种转换尝试改进代码的运行时间,比如重复计算消除,通常或多或少与语言及目标无关。然后后端(也被称为代码产生器)把代码映射入目标指令集。除了制作正确的代码,它负责产生利用所支持架构不寻常功能的好代码。一个编译器后端的通用部分包括指令选择,寄存器分配,及指令调度。

这个模型同样适用于解释器及JIT编译器。Java性能机(JVM)也是这个模型的一个实现,它使用Java字节码作为前端及优化器间的接口。

11.1.1.这种设计的启示当一个编译器决定支持多个源语言或目标架构时,这种经典设计最重要的收益出现了。如果编译器在其优化器中使用一个通用的代码表示,那么可以任何可以编译到这个表示的语言编写一个前端,且可以为任何可以从这个表示编译得到的目标编写一个后端,如图11.2所示。

图11.2:可重定目标性

使用这个设计,移植编译器来支持一个新的源语言(比如Algol或BASIC)要求实现一个新的前端,但现有的优化器及后端可以重用。如果这些部分不是分开的,实现一个新源语言将要求从头开始,因此支持N个目标及M个源语言将需要N*M种编译器。

这个3阶段设计(它是可重定目标性直接的结果)的另一个好处是,相比如果仅支持一种源语言、一个目标,编译器服务更广大的程序员。对于一个开源项目,这意味着从中会得到一个更大的潜在贡献者社区,自然导致对编译器更多的增强与改进。这是为什么服务许多社区的开源编译器(像GCC)比应用范围更窄的编译器,像FreePASCAL,往往会产生更好的优化的机器码的原因。这不是私有编译器的情形,其质量与项目经费直接相关。例如,Intel ICC编译器以产生代码的质量而著称,虽然它服务于一个小众群体。

3阶段设计最好一个主要收益是,要求实现一个前端的技能与实现优化器与后端要求的技能不同。分开这些使得一个“前端家伙”更容易增强及维护编译器他们的部分。虽然这是一个社会问题,不是一个技术问题,在实践中它关系重大,特别对于希望减少阻碍得到尽可能多贡献的开源项目。

11.2. 现存语言的实现

虽然3阶段设计的好处引人注目,而且在编译器教科书中有充分的记载,在实践中它几乎没有被完整实现。看看开源语言实现(回到LLVM开始时),你会发现Perl,Python,Ruby及Java的实现没有共享代码。另外,像Glasgow Haskell编译器(GHC)及FreeBASIC的项目是可重定目标到不同CPU,但它们的实现是非常特定于它们支持的一个源语言。还有各种各样特殊用途的编译技术部署来为图形处理,正则表达式,显卡驱动及其它要求密集CPU工作的子领域实现JIT编译器。

这就是说,对于这个模型有3个主要的成功的故事,第一个是Java与.NET虚拟机。这些系统提供了一个JIT编译器,运行时支持,及一个定义得非常好的字节码格式。这意味着可以编译到这个字节码格式(它们有数十个)的任何语言可以利用投入优化器与JIT以及运行时努力的成果。权衡是这些实现在运行时选择方面提供了极少的灵活性:它们都有效地推行JIT编译,垃圾收集,及一个非常特别的目标模型的使用。当编译不是严格匹配这个模型的语言,像C时,这导致次优的性能(比如使用LLJVM项目)。

第二个成功的故事可能是最不幸的,但也是重用编译器技术流行的方式:把输入源代码翻译为C代码(或某些其它语言),并把它送入现存的C编译器。这允许重用优化器及代码生成器,给出好的灵活性,运行时控制,并且确实容易为前端实现者理解、实现及维护。不幸的是,这样做阻止了异常处理的高效实现,提供了一个差劲的调试体验,拖慢了编译,对于要求确保尾调用(tail call)(或其它C不支持的特性)的语言会问题重重。

发光并非太阳的专利,你也可以发光

gdnt的专栏

相关文章:

你感兴趣的文章:

标签云: