作为做 LLVM 的来说一下吧,学 C++ 看 GCC 更像是“学开车去看发动机制造原理”,GCC 代码是 C 的底子,本身确实“跟 C++ 没啥关系”,LLVM 则是个彻底的现代 C++ 项目
首先,LLVM 早已经不是一个简单的“编译框架”,现在的 LLVM 既可以指狭义的“LLVM Proper”(这词是我自己 coin 的,只是为了区分,社区没这说法),即“基于 LLVM IR,提供优化和代码生成的框架”,也可以指广义的“LLVM Project”,即“以 LLVM Proper 为中心,由 LLVM 社区所管理的一系列项目”,LLVM Project 包含 “LLVM Proper” 编译框架,Clang C/C++/Objective-C/OpenCL/CUDA ... 前端,LLD 链接器,LLDB 调试器,libc++ C++ 标准库,compiler-rt 运行库,llvm-libc C 标准库,Flang Fortran 前端和 MLIR 等一系列项目,一起构成一个 monorepo,但是每个项目的实践都有或大或小的不同
对于一个非编译器相关的开发者而言,我认为 LLVM 最值得学习的是它的基础设施
LLVM 的核心理念之一是“把一切功能封装成一个库”,也就是说 LLVM 本体是“库”。LLVM 会编译出许多 executable,但是这些 executable 绝大多数都是对库的简单 CLI 封装( Clang 除外,因为要与其他编译器兼容,Clang 的 Driver 层做得格外重),也就是说你自己的程序里面也可以调同样的库来实现类似的功能。举个例子,比如 clang 带一个程序叫 clang-rename,实现了“重命名符号”的代码重构操作,但是这个程序只有几百行,功能实际是在 clang/Tooling/Refactoring/Rename 这个库里实现,而这个库又依赖于 Clang 的 Refactoring 库和语义分析库 ... 我自己就利用这个特性做过简单的代码生成小工具:
https://github.com/secondwtq/einstein/blob/master/Einstein.cpp 把一切封装为“库”的目的就是最大化复用,也就是说这个理念背后的隐含意思是“基础设施能帮你做的都会尽量帮你做,你只需要写你自己需要的东西就行”
比如说 LLVM 提供了一整套的容器和数据结构,这些基本上都是通用的,很值得一看
LLVM 的若干公用模块大量使用了模板
LLVM 大多数模块都天生非常适合自动化测试,LLVM 也有完整的测试设施,常用的有 gtest 单元测试,LIT 测试和运行时集成测试三级。做编译器经常出现改东边的东西影响到西边的测试的情况,针对这种状况 LLVM 给你准备好了自动更新测试用例的脚本。
作为一个通用的编译器框架,LLVM 支持很多指令集,不同指令集通用的算法(如指令选择、寄存器分配等)被封装成了一个叫做 CodeGen 的库,而针对每一个指令集的支持都是一个单独的模块(前两个月刚有人搞了个 68000 的支持 ...),每一个指令集都需要定义大量的指令,为了解决这个问题,LLVM 搞了一个叫 TableGen 的 DSL 做代码生成,这个东西统一地解决了指令集定义、指令选择、调用约定、微架构细节定义、CLI 选项定义等一系列问题。这么一整套框架做下来,添加新指令集,添加新指令,很大程度上都是重复性工作。
另外,抛开 LLVM 源码来说,学习 C++,不可不学各种新的工具,而 Clang 占据了半壁江山;不可不了解编译器优化,一般非编译器开发者研究优化都是看汇编,也就是看个一头一尾,但是了解 LLVM IR 就能看中间;不可不看 CppCon 等前沿实践,而其中最有趣的之一就是 LLVM 社区大佬 Chandler Carruth 的基于自己开发经验的系列演讲
不足的地方:
LLVM 作为传统的编译器(也就是说还是主要面向 C/C++)整体设计并没有偏离传统太多,后果之一是里面跟多线程有关的东西很少(因为传统编译器不需要这玩意,直接多进程点到为止就行了),还有一堆全局状态,学这方面的你得另请高明
编译时间太 TM 长 ... 普通 PC 少说半小时吧,我用服务器也得 10 分钟,而且还很大,GCC 就很快(但是好处是需要的依赖很少,只是吃 CPU 而已,基本不用折腾,浏览器则是真正的 worst case,代码量和依赖都很多)
说是把所有东西都做成了库,实际上不同的库之间是互相依赖的,加起来光库就有好几十 M,也就是说很难做到单独拿出一个东西能用。这个理念的意义更主要还是在于,只要你依赖了 LLVM,里面的东西可以随便单独拿出来调用——但是前提是你依赖了 LLVM,这个逻辑就好像“你如果入了苹果的坑,很多东西都会看上去很好(之所以‘看上去很好’是因为金玉其外败絮其中),但是你必须听苹果的话”( GitHub 上甚至有人做把 LLVM 项目里面特定模块单独拎出来的项目)
如果你是 LLVM 的用户,你会发现它的 API 也不是很稳定,很多老项目都直接钉死某一个 LLVM 版本,因为维护成本不低(这进一步说明了 LLVM 的“现代性”:写程序一把梭,刷版本号就得了)