编译器的发展
过去的编译器,大多是“铁板一块”的紧耦合设计;而LLVM,是“乐高积木”式的模块化设计。
1. 经典编译器:紧耦合的“黑盒”
在LLVM之前,主流的编译器(如早期的GCC)设计更像一个封闭的整体。
-
“前端 + 后端”的直连模式:典型的经典编译器就是一个前端直接连接一个后端。前端解析C语言,生成抽象语法树(AST),然后直接翻译成目标机器的汇编代码。
-
问题所在:这种设计的最大问题是强耦合。如果要支持3种语言和3种CPU,就得开发9套完全独立的编译器。这导致了大量的重复劳动,而且调试和优化非常困难,因为代码优化和代码生成是交织在一起的。
2. 抽象语法树(AST) vs. LLVM中间表示(IR)
这是理解两者区别的关键。经典编译器和LLVM都使用中间表示,但它们的设计目标和用途完全不同。
| 特性 | 抽象语法树 (AST) - 经典编译器 | LLVM中间表示 (IR) - LLVM |
|---|---|---|
| 定位 | 主要用于语法分析阶段,是解析器的产物。 | 是编译器整个核心基础设施,独立于任何语言和CPU。 |
| 信息层级 | 高层次的、树形结构,很接近源代码。(例如,能看到while循环、if条件) |
低层次的、线性指令序列,像一种通用的、精简的机器语言。(循环会被转换成br跳转指令) |
| 是否包含类型 | 包含,但通常是语言的类型系统。 | 包含一套强类型的、与语言无关的类型系统。 |
| 主要用途 | 用于语法检查、初步语义分析和一些简单的代码生成。 | 作为优化的核心对象,承载所有与语言、CPU无关的优化。 |
| 能否直接执行 | 通常不能,必须遍历树结构才能生成代码。 | 可以,LLVM JIT引擎可以直接解释和执行IR。 |
简单来说,经典编译器的“中间环节”是一个高层次的树,主要用于“理解代码”;而LLVM的“中间环节”是一个低层次的、类似汇编的指令流,主要用于“变换代码”和“生成代码”。
3. 优化哲学:被动 vs. 主动
-
经典编译器:优化通常是“顺便”或“后期”的事情。因为AST到机器码的转换路径很直接,很多优化在AST层面做不了,只能在生成汇编后硬着头皮做,效果有限。
-
LLVM:优化是它的核心功能。所有关键的优化(如常量折叠、死代码消除、循环展开等)都在IR层面完成。IR被设计得极易分析和变换,因此LLVM可以实现极其强大的、与语言和目标平台无关的优化。
4. 典型的经典编译器代表
除了早期的GCC,你还可能听说过:
-
Borland Turbo C / C++ Builder:在DOS时代和早期Windows时代非常流行的集成开发环境(IDE),其背后的编译器就是典型的“一体式”设计,专为x86处理器优化,很少考虑跨平台。
-
微软Visual C++(早期版本):在其早期版本中,也是紧密耦合的架构,主要服务于自己的Windows平台。
-
LCC(The Little C Compiler):经典的教材式编译器,代码简洁清晰,展示了如何从C语言解析直接生成汇编代码。
总结
| 维度 | 经典编译器 | LLVM |
|---|---|---|
| 核心架构 | 前端直连后端,紧耦合 | 前端-优化器-后端,松耦合 |
| 中间表示 | 高层次的抽象语法树(AST) | 低层次的LLVM IR |
| 优化位置 | 零散、后期 | 核心环节,在IR上系统化进行 |
| 可移植性 | 移植到新CPU成本高 | 只需实现一个新的后端即可 |
可以说,LLVM代表了一次编译器设计的范式转移:从“实现一套编译器”,变成了“构建一套编译器的工具箱”。 这种思想上的转变,让后来的仓颉、MoonBit、香山等国产软硬件项目,都能站在一个强大的巨人肩上,用更低的成本实现更好的性能和安全。这也是为什么我们刚才聊到的国产技术,几乎都和LLVM生态绑定在了一起。
浙公网安备 33010602011771号