LLVM 和 GCC 的区别

最近在 Mac OS X Mountain Lion 下用 Xcode 进行开发,发现在编译选项里有如下所示的这两种编译器:一个是 Apple LLVM compiler 4.2,另外一个是 LLVM GCC 4.2。

近几年一直听人说 LLVM 比 GCC 好,但是我一直没有时间研究这二者的差别。由此问题出发,我又给自己抛出了很多疑问:

接下来让我们一起补补历史课。

CC, C89, C99

Unix 诞生之后,很多公司都开发了自己的 Unix 系统并且使用了自己专门的编译器。这样就导致在不同的 Unix 系统上,想编译 C 语言代码就需要使用不同的命令。于是 POSIX 标准 Commands and Utilities 中就规定了将 CC 作为不同编译器的统一命令接口,并且也规定了 CC 命令需要提供哪些必须的参数。

随着后续 ISO C 标准的确定,POSIX 标准又规定分别将 C89、C99 作为 ISO C 的接口,而 CC 则继续作为非标准 C 的接口。但实际上后续大多数 C 语言编译器都实现了 ISO C 标准,所以 POSIX 标准规定后续应将 CC 这一历史遗留的命令取消。

GCC, G++, CPP, GPP

随着开源运动的兴起,自由软件基金会开发了自己的开源免费的 C 语言编译器 GNU C Compiler,简称 GCC。GCC 中提供了 C Preprocessor 这个 C 语言的预处理器,简称 CPP。后来 GCC 又加入了对 C++ 等其它语言的支持,所以他的名字也改为 GNU Compiler Collection。G++ 则是专门用来处理 C++ 语言的。在 GNU 的官方手册中,有一个章节叫做 G++ and GCC 介绍了这二者的区别。G++ 是 GCC 编译器集合的一个前端。关于前端、后端的概念下面有更详细的介绍。而 GPP 呢,这个名字比较特殊,如果你用的是 Linux 系统,可能并没有这个命令。但是在某些特殊的系统下,例如 DOS,是无法创建 G++ 这样带有特殊符号的文件名的。所以按照 DJGPP 编译器的做法,GPP 其实就是 G++。

LLVM 与 GCC

回顾 GCC 的历史,虽然它取得了巨大的成功,但开发 GCC 的初衷是提供一款免费的开源的编译器,仅此而已。可后来随着 GCC 支持了越来越多的语言,GCC 架构的问题也逐渐暴露出来。但 GCC 到底有什么问题呢?我们一起看看这篇文章:The Architecture of Open Source Applications: LLVM。LLVM 的优点也正是 GCC 的缺点。

传统编译器

传统编译器的工作原理基本上都是三段式的,可以分为前端(Frontend)、优化器(Optimizer)、后端(Backend)。前端负责解析源代码,检查语法错误,并将其翻译为抽象的语法树(Abstract Syntax Tree)。优化器对这一中间代码进行优化,试图使代码更高效。后端则负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能。

事实上,不光静态语言如此,动态语言也符合上面这个模型,例如 Java。Java Virtual Machine 也利用上面这个模型,将 Java 代码翻译为 Java bytecode。

这一模型的好处是,当我们要支持多种语言时,只需要添加多个前端就可以了。当需要支持多种目标机器时,只需要添加多个后端就可以了。对于中间的优化器,我们可以使用通用的中间代码。

这种三段式的结构还有一个好处,开发前端的人只需要知道如何将源代码转换为优化器能够理解的中间代码就可以了,他不需要知道优化器的工作原理,也不需要了解目标机器的知识。这大大降低了编译器的开发难度,使更多的开发人员可以参与进来。

虽然这种三段式的编译器有很多有点,并且被写到了教科书上,但是在实际中这一结构却从来没有被完美实现过。做的比较好的应该属 Java 和. NET 虚拟机。虚拟机可以将目标语言翻译为 bytecode,所以理论上讲我们可以将任何语言翻译为 bytecode,然后输入虚拟机中运行。但是这一动态语言的模型并不太适合 C 语言,所以硬将 C 语言翻译为 bytecode 并实现垃圾回收机制的效率是非常低的。

GCC 也将三段式做的比较好,并且实现了很多前端,支持了很多语言。但是上述这些编译器的致命缺陷是,他们是一个完整的可执行文件,没有给其它语言的开发者提供代码重用的接口。即使 GCC 是开源的,但是源代码重用的难度也比较大。

LLVM

LLVM 最初是 Low Level Virtual Machine 的缩写,定位是一个虚拟机,但是是比较底层的虚拟机。它的出现正是为了解决编译器代码重用的问题,LLVM 一上来就站在比较高的角度,制定了 LLVM IR 这一中间代码表示语言。LLVM IR 充分考虑了各种应用场景,例如在 IDE 中调用 LLVM 进行实时的代码语法检查,对静态语言、动态语言的编译、优化等。

从上面这个图中我们发现 LLVM 与 GCC 在三段式架构上并没有本质区别。LLVM 与其它编译器最大的差别是,它不仅仅是 Compiler Collection,也是 Libraries Collection。举个例子,假如说我要写一个 XYZ 语言的优化器,我自己实现了 PassXYZ 算法,用以处理 XYZ 语言与其它语言差别最大的地方。而 LLVM 优化器提供的 PassA 和 PassB 算法则提供了 XYZ 语言与其它语言共性的优化算法。那么我可以选择 XYZ 优化器在链接的时候把 LLVM 提供的算法链接进来。LLVM 不仅仅是编译器,也是一个 SDK。

Apple LLVM compiler 4.2 和 LLVM GCC 4.2

现在我们可以回答本文最前面我遇到的那个问题了。Apple LLVM compiler 4.2 是一个真正的 LLVM 编译器,前端使用的是 Clang,基于最新的 LLVM 3.2 编译的。LLVM GCC 4.2 编译器的核心仍然是 LLVM,但是前端使用的是 GCC 4.2 编译器。从 LLVM 的下载页面可以看出,LLVM 从 1.0 到 2.5 使用的都是 GCC 作为前端,直到 2.6 开始才提供了 Clang 前端。

-->