LLVM(还没译完, 回头再译)

本文讨论一些影响LLVM结构的设计决策, LLVM是一个项目的总称, 该项目主办并开发了一些与底层密切相关的工具链组件(例如:assemblers(汇编器), compilers(编译器),debuggers(调试器),等等),这些组件被设计成与现有的unix系统上常用的工具集相兼容。“LLVM”以前是一个缩写,但是现在它只是项目的名字。LLVM提供一些特有的功能,并因它的强大工具集中的一些工具广为人知(如,clang编译器, 一个可以编译C/C++/Objective-C的 编译器, 拥有许多超越GCC的特性),使LLVM有别于其他编译器的最重要的因素是LLVM的内部结构。

从2000年12月该项目成立之初,LLVM被设计为一个接口定义良好的可重用的库的集合。那时候, 开源编程语言实现被设计为特殊用途的工具,这些工具的可执行文件通常很大。 比如, 很难重用静态编译器(比如GCC)的语法解析器来做静态检查或者重构。 然而,脚本语言常常提供一种方式,嵌入它们的runtime和interpreter到更大的应用程序中,这里的runtime是一个被包含或者没有被包含的代码块。没有办法以分片的方式复用代码, 并且不同的语言实现项目之间几乎没有共享。

除了编译器自身的组成之外,围绕通用语言的实现分化为两极:一种实现提供传统的静态编译器,如GCC、 Free Pascal 和 FreeBASIC, 另外一种以interpreter或者JIT的方式提供运行时编译器。同时实现这两种方式的编译器十分少见,即便有这样的编译器,他们通常不分享任何源码。

在过去的十年里,LLVM在很大程度上改变了上述现状。现在,LLVM作为一个通用的基础框架被用于实现许多静态、运行时的编译型语言(比如GCC支持的语言集, Java、 .NET、 Python、Ruby、Scheme、Haskell、D,还有许多小众语言)。它还替代了许多特殊用途的编译器, 比如Apple‘s OpenGL stack的运行时特效引擎 和 Adobe's After Effects的图像处理库。 最后, LLVM还被用于实现许多新的产品, 其中一个较为有名的是the OpenCL GPU programming language and runtime.。

1.经典的编译器设计简介

传统静态编译器最为常用的设计方案分为三个阶段(Figure 11.1),主要组件是前端,优化器和后端。前端解析源码、检查错误、构建特定语言的抽象语法树用来表示输入的代码。中间语法树可以转换成一种新的表示方式用于优化,优化器和后端对其进行处理。

[Three Major Components of a Three-Phase Compiler]

Figure 11.1: Three Major Components of a Three-Phase Compiler

优化器负责各种各样的转化,用于提高代码运行时的效率,比如消除冗余的计算,语言和目标依赖。 后端将代码转换成目标机器的指令。为了生成正确的代码,生成好的代码使用目标机器的特性。编译器后端通常包含 instruction selection, register allocation, and instruction scheduling.

这个模型适用于 interpreters and JIT compilers. The Java Virtual Machine (JVM) 的实现模型也是如此, 使用Java bytecode 作为前端与优化器的接口.

1.1. Implications of this Design

这种经典设计的最重要的胜利是在编译器支持多种语言或者多种目标机器。如果编译器使用通用的代码表示在它的优化器,那么前端可以写成任何可以编译成它的,后端也可以将它编译成它支持的任何编译器,s shown in Figure 11.2.

[Retargetablity]

Figure 11.2: Retargetablity

使用这种设计,移植编译器让其支持一种新的语言需要实现一个新的前端,但是已有的优化器和后端能够被重用。如果这些组件没有被分开实现,支持一个新的语言需要从头实现一个新的编译器。支持N种语言且支持M种平台需要实现N*M个编译器。 

三段式设计的优势是更多的程序员使用该编译器(相比只支持一种语言,一个平台的编译器)。一个开源醒目,意味着比潜在的贡献者大得多的社区,这些使得编译器更多更强大的功能和改进。这也是为什么开源编译器服务于社区生成更加优化根号的机器代码,相比小重编译器,比如FreePASCAL.。这个不适用于 proprietary compilers, 它的质量和它的项目预算直接相关。比如 Intel ICC Compiler is widely known for the quality of code it generates, even though it serves a narrow audience.

最后,三段式设计的好处是实现前端的技术与优化器和后端的实现技术不一样。分开实现这些使得前端人员更容易enhance和maintain他们的代码。然而,这是一个社会问题,不是一个技术问题,在现实总关系到很多方面,特别对于一个开源项目来说,尽可能的减少障碍多做贡献。 

2. Existing Language Implementations

虽然三段式设计引人注目并且在编译器的树种很好的描述,但是实际上它几乎从没有被完完全全的实现过。纵观开源语言实现,你会发现 Perl, Python, Ruby and Java 没有分享实现代码。并且, 像the Glasgow Haskell Compiler (GHC) and FreeBASIC 这些项目are retargetable to multiple different CPUs, 但是实现仅仅这对他们支持的一种语言。还有许多各种各样的特殊用途的编译器用于 implement JIT compilers for image processing, regular expressions, graphics card drivers, and other subdomains that require CPU intensive work.

也就是说,这个模型有三个主要的成功,第一JAVA 和.NET虚拟机。这些系统提供了JIT compiler, runtime support 和定义良好的bytecode格式。

  This means that any language that can compile to the bytecode format (and there are dozens of them3) can take advantage of the effort put into the optimizer and JIT as well as the runtime. The tradeoff is that these implementations provide little flexibility in the choice of runtime: they both effectively force JIT compilation, garbage collection, and the use of a very particular object model. This leads to suboptimal performance when compiling languages that don't match this model closely, such as C (e.g., with the LLJVM project).

第二个成功也是最大的不幸, 但却是最流行的方式重用编译器的技术:转换输入代码成C语言, 将其传递给C编译器。这个是的优化器和代码生成器得到复用,

 This allows reuse of the optimizer and code generator, gives good flexibility, control over the runtime, and is really easy for front-end implementers to understand, implement, and maintain.不幸的是,这样做阻止异常处理的有效实现,糟糕的调试体验,slows down compilation, and can be problematic for languages that require guaranteed tail calls .

最后的一个成功实现是GCC。GCC支持多个前端和后端, 并且有一个活跃、广泛的贡献者社区。 GCC 作为C编译器很长的历史,支持多个目标,支持几个别的语言.时间推移,GCC社区在演化cleaner design 变慢。 在 GCC 4.4, 它有一个新的中间层表示 (known as "GIMPLE Tuples") , 比以前更进一步的与前端分宜。它的 Fortran and Ada 的前端使用了更为简洁的AST。.


While very successful, these three approaches have strong limitations to what they can be used for, because they are designed as monolithic applications. As one example, it is not realistically possible to embed GCC into other applications, to use GCC as a runtime/JIT compiler, or extract and reuse pieces of GCC without pulling in most of the compiler. People who have wanted to use GCC's C++ front end for documentation generation, code indexing, refactoring, and static analysis tools have had to use GCC as a monolithic application that emits interesting information as XML, or write plugins to inject foreign code into the GCC process.

 

有许多原因是的GCC的组件无法像库一样被复用,包括到处使用全局变量,weakly enforced invariants, 糟糕的数据结构设计, sprawling code base, 和宏的使用阻止codebase一次被编译成支持多个前端/目标对。最难得问题是  固有的结构问题从它早起设计之初。

特别是, GCC 遭受分层和糟糕的抽象问题,the back end walks front-end ASTs to generate debug info, the front ends generate back-end data structures, and the entire compiler depends on global data structures set up by the command line interface.




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值