ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LLVM实战指南:解析编译器基础设施与自定义Pass开发

LLVM实战指南:解析编译器基础设施与自定义Pass开发 很多开发者第一次接触“llvm-project”这个仓库时会被它的代码量和复杂度吓一跳。我第一次把仓库clone下来时光看到那一长串子目录就头皮发麻——clang、lld、libcxx、mlir、flang、compiler-rt……心想这不是一个编译器吗怎么搞出这么一堆东西但等我真的在里面泡了一段时间才意识到LLVM项目的核心价值恰恰在于它不是“一个编译器”而是一整套可以自由拼装的编译器组件库。这篇文章我就以自己的使用经验为主线把这个项目到底是什么、核心模块怎么协作、如何从零开始跑通一个完整的自定义Pass、以及在实际工作中怎么把它的能力“借用”到自己的业务场景里一次讲清楚。无论你是做编译原理课程设计的学生还是要给公司研发一套静态检查工具的工程师或者对底层技术纯粹好奇的开发者这篇内容都值得你看完。1. llvm-project到底是什么不是“一个编译器”而是“编译器零件的乐高仓库”我见过太多人把“LLVM”和“Clang”混为一谈。严格来说Clang只是LLVM项目里的C/C前端而LLVM项目本身是一个巨大的基础设施集合。理解这一点你才能真正知道该怎么用它。1.1 三个改变命运的设计决策LLVM项目的源头是伊利诺伊大学香槟分校的Chris Lattner在2000年发起的研究课题。当年这个课题想解决的问题很朴素传统的静态编译器比如GCC把前端、优化器、后端死死捆在一起想换一种语言支持、想加一个目标平台几乎等于重写整个工具链。Lattner做了一个到今天看来依然激进的设计而且这三个设计几乎是LLVM后来能统治编译器基础设施领域的关键。第一个决策是**“库化”一切**。LLVM的所有功能都尽量设计成可链接的库而不是一个孤立的可执行程序。你只用Clang的前端库就可以解析C代码生成抽象语法树不需要拖上整个编译器的优化和后端。我做过一个脚本语言的解析器当时就直接调了clang的库来做C头文件解析省了至少两个月的工期——这就是库化设计给你的自由度。第二个决策是用稳定的中间表示IR作为前端和后端的契约。前端负责把源代码翻译成IR后端负责把IR翻译成机器码中间优化器针对IR做变换。这个契约一旦稳定下来就会出现一个奇观无论你是Swift、Rust、Julia还是C写的前端只要能产出合格的LLVM IR就能自动享受所有后端平台的支持。苹果当年敢放弃GCC全力押注LLVM就是看上了这一点——他们想自己搞前端语言但不想重新实现各CPU架构的后端。第三个决策是**“选件自由”**。你可以只用Pass优化框架写自己的分析或变换你也可以只用Clang做代码格式化你甚至可以绕过编译器直接调用LLVM的JIT引擎在运行时生成机器码。每一层都被设计成可以单独拿出来使用的模块。1.2 这个仓库里到底装了什么“llvm-project”是一个聚合仓库核心内容大致可以分为下面这些我用实际用途来标注比官方的一句话描述直观很多子项目角色实际用途LLVM Core优化器 后端 IR框架写Pass、做代码变换、生成各平台机器码ClangC/C/Objective-C前端日常编译代码、静态分析、重写工具lld链接器替代系统ld速度快很多libc / libcabiC标准库实现跨平台C运行时compiler-rt运行时库提供Sanitizer、__divdi3之类内建函数MLIR多级IR框架做深度学习编译器、自定义DSL、芯片编译器FlangFortran前端科学计算场景的老代码迁移polly循环优化基于多面体模型的自动并行和向量化clang-tools-extra各种基于Clang的小工具clang-tidy、clangd、clang-format等我最早真正“用”LLVM其实是冲着clang-format去的——就是一个给代码排版的小工具。后来才发现这工具背后连着的是一整套解析C语法的重型基础设施。也就是说LLVM的成功在于它的每一层都做到了“可以单独用也可以组合用”。1.3 跟虚拟机有什么关系“Low Level Virtual Machine”这个名字确实极具误导性。它听起来像个虚拟机实际上跟Java虚拟机、Python解释器完全不是一个东西。它叫“Virtual Machine”的原因是在LLVM的架构中IR指令集被设计成类似一种虚拟机的指令集——它有无限数量的虚拟寄存器、有类型系统、有控制流图。你的代码先“运行”在这个假想的机器上被优化然后再降级翻译成真实硬件的机器码。所以“LLVM”这个名字现在更像一个历史遗留的品牌名。到了今天官方文档里几乎统一用“The LLVM Project”来称呼整个项目不再纠结“Low Level Virtual Machine”这个全称了。2. 编译流水线上三层架构前端、IR与后端的接力赛理解LLVM架构最直观的方式就是跟着一个C文件从头到尾走一遍编译流程。很多教材直接抛术语我这里换一个更贴近实际操作的视角。2.1 一次hello.c的完整旅程假设你写了一个最简单的C程序然后执行clang -S hello.c -o hello.s这条命令实际上把三个互相独立的阶段串起来了第一棒是Clang前端。它做词法分析把字符流切成token、语法分析把token组装成AST、语义分析检查类型是否匹配、是否符合语言规则。Clang和传统编译器很大的一个不同是它把AST设计得极其扁平、可访问这让很多代码分析和重构工具变得非常容易。GCC里你想搞代码级分析得破解它内部复杂的GIMPLE表示而在Clang里AST直接暴露给开发者。第二棒是LLVM优化器。Clang把AST转换成LLVM IR然后交给优化器。优化器会一遍遍地跑Pass每一个Pass只做一个很小的变换比如“删除永远执行不到的代码块”“把常量运算直接算出结果”“识别出循环不变式并挪到循环外”。这种“小步快跑”的Pass流水线是LLVM极其核心的工程思想。第三棒是LLVM后端。优化后的IR最后被分配到具体目标平台的寄存器然后被翻译成汇编指令最后经汇编器变成目标文件。为了让你对这三段有感觉我提一个实操过的对比用同样一份C代码在x86主机上编译再用交叉编译工具链编出ARM的版本。你会发现前端和优化器完全不用变只需要换一个后端Target描述文件。这就是LLVM所谓“一次IR到处生成”的威力——当然这是简化说法交叉编译还涉及系统头文件、库路径一大摊子事但从纯编译器内核的视角来看确实如此。2.2 IR到底长什么样很多人会觉得“IR”是个抽象概念难以落地。实际上IR是一段真实存在的文本或者比特流。你可以自己动手生成出来看看clang -emit-llvm -S hello.c -o hello.ll打开hello.ll就能看到类似这样的内容define dso_local i32 main() { %1 alloca i32, align 4 store i32 0, i32* %1, align 4 %2 call i32 (i8*, ...) printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* .str, i64 0, i64 0)) ret i32 0 }这段文本看起来有点像汇编但它是平台无关的。注意看三点第一每条指令都带显式类型比如i32就是32位整数类型第二它在使用虚拟寄存器总数不受真实硬件限制第三它是一种静态单赋值SSA形式意味着每个变量只被赋值一次。SSA形式是LLVM优化的秘诀之一——因为每个值只有一个定义点数据流分析就变得非常容易可以高效地追踪一个值的“出生”和“消费”。2.3 为什么后端可以自成一体后端要做的事情本质上是一个资源调度问题把无限的虚拟寄存器映射到有限的物理寄存器把IR指令翻译成目标CPU的指令。LLVM把这块做成了一种“描述驱动”的方式——你在TableGen文件里描述指令格式、寄存器类别、寻址模式然后LLVM的代码生成器根据这些描述自动生成匹配器。如果你要支持一种全新的CPU架构最笨的办法是把目标架构的指令都写进TableGen描述文件剩下的大部分工作由框架自动完成。这种设计极大地降低了引入一种新硬件平台的门槛。我有个朋友在搞RISC-V相关的工具链他们就是复用LLVM后端的这套框架只写RISC-V的指令描述和寄存器约束很快就跑通了完整工具链。如果你没见过“TableGen描述”长什么样可以在llvm-project/llvm/lib/Target/RISCV目录里翻一翻RISCVInstrInfo.td那个文件就是用TableGen语言编写指令模板。我第一次看的时候也觉得新奇——原来CPU指令在这种框架里是“数据”而不是“代码”。3. 深入项目核心Pass基础设施与Pipeline机制如果你问一个LLVM开发者llvm-project里最值得深入研究的模块是哪个十有八九会回答“Pass框架”。从表面看Pass就是一个个遍历IR并做修改的“遍历器”但从工程实践上看Pass框架的设计直接决定了整个优化器是否可扩展、可维护。3.1 新旧两代Pass框架LLVM近几年的版本里Pass框架经历了一次很大的重构从“legacy Pass Manager”过渡到了“New Pass Manager”。两代之间的核心差异不只是API不同而是管理逻辑变了老框架对每个Pass单独做生命周期管理导致分析结果很难跨Pass复用每次想用某个分析数据都要重新算一遍新框架引入了分析管理器分析结果可以被多个Pass共享。写新代码时强烈建议直接用新Pass Manager的接口官方对新功能的开发也基本只围绕新接口展开。一个最简的新版FunctionPass大概长这样#include llvm/IR/Function.h #include llvm/IR/Instructions.h #include llvm/Pass.h #include llvm/IR/LegacyPassManager.h #include llvm/Passes/PassBuilder.h #include llvm/Passes/PassPlugin.h using namespace llvm; namespace { struct MyFirstPass : public PassInfoMixinMyFirstPass { PreservedAnalyses run(Function F, FunctionAnalysisManager AM) { int count 0; for (auto BB : F) { for (auto I : BB) { if (auto *op dyn_castBinaryOperator(I)) { count; } } } if (count 0) { errs() Function F.getName() has count binary ops\n; } return PreservedAnalyses::all(); } }; } // namespace不要被模板代码吓到你只需要关注run函数里的逻辑遍历函数的每一个基本块BasicBlock再遍历基本块里的每一条指令如果指令是二元运算类型就计数。这个模式是所有静态分析Pass的原型。你写代码风格检查、复杂度分析、插桩逻辑全都是建立在这样的遍历基础上。3.2 自己动手编写并加载一个Pass有了代码之后我们要把它编成一个动态库然后让opt工具加载它。这里给出完整操作链路我已经在Ubuntu 22.04 LLVM 17的版本上实测过。首先新建一个目录比如llvm-pass-demo里面放两个文件CMakeLists.txt和MyFirstPass.cpp。CMakeLists.txt内容cmake_minimum_required(VERSION 3.20) project(MyFirstPass) find_package(LLVM REQUIRED CONFIG) message(STATUS Found LLVM ${LLVM_PACKAGE_VERSION}) message(STATUS Using LLVMConfig.cmake in: ${LLVM_DIR}) include_directories(${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS_LIST}) add_library(MyFirstPass MODULE MyFirstPass.cpp) if(MSVC) set_target_properties(MyFirstPass PROPERTIES PREFIX ) endif() install(TARGETS MyFirstPass LIBRARY DESTINATION lib)然后编译并运行mkdir build cd build cmake -DLLVM_DIR/usr/lib/llvm-17/lib/cmake/llvm .. make加载并运行clang -emit-llvm -S test.c -o test.ll opt -load-pass-plugin./libMyFirstPass.so -passesplugin(MyFirstPass) test.ll如果运行成功你会在终端上看到每个函数里有多少二元运算。这个从零到一的流程走通过一次之后你对LLVM的恐惧感基本就消除一大半了——本质无非是“写遍历代码、编译成插件、加载进框架跑一遍”。3.3 优化Pipeline到底是怎么编排的opt -passesdefault这一行命令背后实际会跑几十上百个Pass。官网文档和很多书都列出了它们的名字但对于初学者来说关键不是记住每个Pass的名字而是理解Pipeline组织的基本逻辑代码先被规范化一些Pass会把复杂变体拆成简单变体比如mem2reg会把alloca和load/store模式提升成SSA虚拟寄存器这为后续优化统一了处理基础。局部优化 全局优化交替先做基本块内部能做的比如指令合并再做跨基本块的比如循环优化。最后一次清场删除死代码、合并重复指令让生成的IR尽量干净。我自己调试时最常用的命令是把opt和llvm-dis组合起来跑完某个Pass后立刻导出IR文本脑补一下变换前后的差异。你不需要一次理解所有Pass只需要在对特定模式产生好奇时找到那个Pass的源码读一遍实现效率远比硬啃文档高。4. 从零到一复现LLVM环境下载、构建与避坑手册这部分是给打算真正动手的读者的。LLVM官方提供了预编译的二进制包但对于想深入源码的人来说自己构建一遍几乎是必经之路——因为只有本地有完整源码和符号信息你才能真正单步调试一个Pass。而且只有自己构建过你才会理解llvm-project作为一个巨型项目构建系统是怎么把几千个模块组织起来的。4.1 源码获取与构建参数克隆仓库git clone --depth1 https://github.com/llvm/llvm-project.git注意--depth1能大幅减少首次克隆时间。如果是完整克隆这个仓库的体积非常可观网络条件不好时容易中断。创建独立的构建目录强烈建议放在源码目录外面。我就见过有人直接在源码目录里建build导致后面clean的时候把src文件误删的情况。cd llvm-project mkdir build cd buildCMake配置命令是重头戏。我常用的参数组合cmake -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang;lld;compiler-rt \ -DLLVM_TARGETS_TO_BUILDX86;AArch64 \ ../llvmLLVM_ENABLE_PROJECTS控制要额外构建哪些子项目。如果你只想做和Clang相关的工作这个列表里至少要包含clang如果做链接相关的工作把lld加进来。LLVM_TARGETS_TO_BUILD控制要为哪些CPU架构生成后端代码。这里强烈建议不要用all因为每多一个后端编译时间就显著上升。如果只是在本机研究只留X86就够加上AArch64是为了交叉编译实验留一点余地。CMAKE_BUILD_TYPE用Release能显著减少你等待构建的时间但如果你要调试Pass源码那还是老老实实用Debug或RelWithDebInfo。我个人的折中是RelWithDebInfo它在运行时性能接近Release同时保留调试信息对日常调试完全够用。构建ninja我见过很多人在这一步卡住。第一次构建clanglld在普通笔记本上可能要1到2小时这还不算中途报错修复的时间。所以强烈建议先构建最小集跑通流程再按需增加组件。这里我重新给一份最小构建命令专门给第一次尝试的人cmake -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang \ -DLLVM_TARGETS_TO_BUILDX86 \ ../llvm ninja clang optninja clang opt只构建这两个目标时间会短很多我的机器大概二十分钟能完成。想做Pass实验的opt是必需的。4.2 我把构建过程中踩过的坑整理成了一张表现象原因解决办法编译中途OOMDebug构建符号信息巨大改用Release或RelWithDebInfo减少LLVM_TARGETS_TO_BUILDninja时报Error: /usr/bin/ld: cannot find -lz等缺少系统依赖库Ubuntu装zlib1g-dev、libncurses-dev等本地跑Pass报API不匹配头文件和库版本不一致用llvm-config --version核对确保CMake的LLVM_DIR指向对应版本磁盘空间爆炸Debug构建所有项目完整仓库预留至少50GB推荐100GB加载Pass插件提示Pass plugin does not provide entry point插件编译模式不对确保编译时加了-fPIC且在CMake中用add_library(... MODULE ...)做事的顺序也很重要先看LLVMConfig.cmake的输出确认找到的确实是你要的那个版本。很多人系统里同时装了包管理器的LLVM、自己编译的LLVM、还有IDE自带的LLVMfind_package很容易“找错人”。4.3 一个非常顺手的实验方式用“鸡生蛋”问题验证工具链自己构建完成后最让我踏实的验证方式是“用自己编译的Clang再编译一个Clang”。假如你下载了LLVM 17的release tarball里面自带的clang号称能编译C17那你用这个clang去编译llvm-project里的clang源码如果能成功产生一个可用的clang二进制这说明整条工具链的稳定性是没问题的。这个“鸡生蛋”的过程在发行版打包领域叫Stage2 bootstrap。我第一次成功完成时那种成就感到现在还记忆犹新。你不需要完整跑完整个bootstrap流程只需要拿自己编的clang去编译一个稍大一点的项目比如编译一个简单的GUI程序或一个开源库就能测出基本功能是否正常了。构建LLVM这事急不得。我第一次构建时看到终端上几千行的滚动输出第一反应是“是不是哪里错了”后来才知道这是ninja构建十几万个编译单元的常态。给自己留出完整的一天准备好咖啡慢慢来就好。5. 实战场景如何借力LLVM为普通项目赋能聊完底层回到大家最关心的问题我不是编译器专家我能在工作中怎么“白嫖”LLVM的能力这里我结合自己做过的项目和看过的团队案例整理了几个高性价比的切入场景。5.1 用Clang的库写一个代码风格检查工具很多人提到代码检查第一反应是正则表达式它对付简单规则还行可一旦涉及真实的C语法结构正则就开始胡言乱语。与其费劲调正则不如直接基于Clang Tooling来写一个检查。核心思路是写一个ASTConsumer遍历AST中的每个函数声明节点检查函数名是否遵循命名规范、函数行数是否超限等。代码结构如下class MyAstConsumer : public ASTConsumer { public: void HandleTranslationUnit(ASTContext Ctx) override { auto Decls Ctx.getTranslationUnitDecl()-decls(); for (auto *D : Decls) { if (auto *FD dyn_castFunctionDecl(D)) { if (!FD-getName().startswith(my_)) { DiagnosticsEngine DE Ctx.getDiagnostics(); DE.Report(FD-getLocation(), DE.getCustomDiagID(DiagnosticsEngine::Warning, function name should start with my_)); } } } } }; int main(int argc, const char **argv) { // 省略 CommonOptionsParser 初始化 ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList()); return Tool.run(newFrontendActionFactoryMyFrontendAction().get()); }这种做法的好处是你的检查规则有完整的语言语义支撑。比如要检查“指针没有判空就解引用”不只靠字符串匹配而是分析CFG看解引用路径上是否存在空指针检查。这是正则永远做不到的。5.2 想基于LLVM做门面不一定非要碰编译器如果觉得ASTConsumer这套东西上手门槛还是高也可以考虑更上一层直接写Clang Static Analyzer的Checker插件。这个框架帮你处理了路径敏感分析、符号执行这些复杂机制你要做的只是“声明什么情况算bug”。一个最小的Checker长这样class MyChecker : public Checkercheck::PreStmtUnaryOperator { public: void checkPreStmt(const UnaryOperator *U, CheckerContext C) const { // 若出现 *p 且p可能为空则报告 } }; void registerMyChecker(CheckerManager Mgr) { Mgr.registerCheckerMyChecker(); }文化的差异在于普通开发者的思维是“怎么让程序跑起来”而静态分析领域的思维是“怎么在程序没跑的时候找出问题”。一旦你适应了这种“没有运行时的检查”你会觉得静态分析是个宝藏。5.3 借助libclang曲线救国的方案如果你不想编译整个llvm-project或者想用Python快速做事情libclang会是个不错的接口。它把Clang的解析能力暴露成C接口Python的clang.cindex库就是基于它的。我自己就用Python写过一个C头文件的依赖关系可视化工具核心就三行import clang.cindex index clang.cindex.Index.create() tu index.parse(example.cpp)只要拿到TranslationUnit就可以遍历AST做各种统计。这种方式的启动速度比编译自己的C工具要快得多适合快速原型验证。代价是灵活性不如直接用Clang Tooling因为libclang封装掉了很多底层控制。5.4 跟LLVM生态一起看MLIR的想象力MLIR是近年来LLVM生态里最热的分支之一本质是“多级IR框架”。它允许你在一个自定义的高层IR和LLVM的底层IR之间搭建一座桥。做AI芯片编译器的那波人特别喜欢用它——你可以把神经网络的计算图映射成一个自定义IR然后一层层降级最终变成某款NPU的指令。TensorFlow、PyTorch的编译后端都有MLIR的参与。对于普通工程师MLIR的意义在于如果你有一个DSL需要编译到不同硬件MLIR能让你不用每次从零写编译器。它的学习曲线比LLVM Core更深但它在业界的前景非常明确——未来的编译器基础设施大概率就是LLVM Core做底层、MLIR做中间层、各语言前端做顶层的格局。6. 给从源码读起的人的几条路线建议说到读源码很多人兴致勃勃地打开llvm-project结果翻了两眼就放弃了。这里我按自己的经验给出一条相对舒服的路径。6.1 从现象找入口而不是按目录从头读不要试图把llvm-project/llvm/lib从头读到尾那是几百人全职维护多年的代码量。聪明做法是带着问题去读。比如你想搞清楚“clang -O2到底对我的代码做了什么”那就先把IR导出来找一个感兴趣的变化然后用git blame找到对应Pass的源码顺着链路往下读。带着具体问题找代码效率远高于教科书式通读。6.2 优先读三个小核心以我自己的理解最应该精读的三个小核心是llvm/include/llvm/IR/Instruction.h和llvm/lib/IR/Instructions.cpp了解IR中指令的类型体系和继承结构。llvm/lib/Transforms/Utils/Local.cpp这里面是大量局部优化工具函数帮你理解“一个指令级优化”是如何实现的。llvm/lib/Passes/PassBuilder.cpp这里定义了默认的Pass Pipeline是理解整个优化流程的纲要文件。这三个核心加起来不超过一万行把它们读完读透比什么都强。6.3 调试工具是你的眼睛用opt -debug-onlyxxx还能打开某个Pass内部的调试输出这比自己在代码里加errs()要快得多。我调试自身写的Pass时最常用的是GDB断点加F.dump()打印IR。LLVM里几乎所有数据结构都有dump()方法这在开发时极其顺手——任何一个环节想看看当前状态直接调一下dump()就行。6.4 不要急着看后端对多数研究型工作来说前端和优化层的锻炼价值最大。后端涉及指令选择、寄存器分配、指令调度这些深层领域入门成本极高。除非你的项目真的需要支持一种新处理器否则可以先跳过llvm/lib/Target下的绝大多数内容。我见过一个极端的例子有位同事花了半年时间研究LLVM后端最后发现自己只是为了做代码风格检查工具白白浪费了时间。他后来坦白说如果当初把精力放在Clang Tooling上工具早就能上线了。不是后端不精彩是很多业务场景根本不需要到那么底层。7. 我最常被问到的几个问题以及我的真实回答这么多年下来不少朋友问过我关于llvm-project的问题我挑几个高频的在这里统一说下。Q我完全不懂编译原理能学LLVM吗能但会痛苦一些。我的建议是先补一点基础至少知道词法分析、语法分析、AST的概念知道汇编语言里寄存器、指令是什么。不用提前看太多理论书带着问题在LLVM源码里翻边做边学最快。QLLVM项目代码量巨大为什么还要“库化”如果设计成一个只完成“源文件进、可执行文件出”的黑盒那除了编译器开发者别人根本用不上它。库化设计让LLVM成为所有编程工具的基础设施——代码补全、静态检查、格式整理、反编译都用得到它。这也是我认为LLVM能持续壮大的一个核心竞争力。Q要不要直接用源码构建还是装预编译包想深入研究的至少源码构建一次。没有经历过构建过程的“奇奇怪怪”问题后面遇到绝对module系统交叉编译时会更抓狂。想快速做别的业务直接用预编译包节省时间是合理的。QRust和LLVM是什么关系Rust的官方编译器rustc早期直接使用LLVM作为后端。你写好Rust代码rustc把MIR降级成LLVM IR剩下的优化和机器码生成全交给LLVM。所以现代编程语言的“编译器实现复用”很大程度是在复用LLVM。8. 写在后面一点决定是否真的值得投入的判断回到开头的那个对比llvm-project不是“编译器”而是一个平台。这句话背后有一个很实际的含义——它迫使你换一种思考方式不再追求“写一个完整的编译器”而是思考“我的程序需要哪个组件”。根据我个人的实战体会如果你做的是应用程序开发日常用用Clang、lld、clang-format、clang-tidy就够了没必要深入源码。但如果你的工作中出现了下面任一情况需要自定义语法检查、需要把某种DSL编译到多种硬件、需要跨语言互操作且性能要求极高、或者对编译器优化有强烈的定制需求——那投入时间研究LLVM就是一笔非常划算的投资。最后分享两个很实用的小技巧。一是善用clang -Xclang -ast-dump它能以文本形式打印出整个AST结构这对理解Clang内部机制非常直观。二是多关注LLVM官方的文档和邮件列表很多隐藏的设计意图不会写进教科书但会出现在开发者的讨论里。学习LLVM不是一条线性的路保持好奇心比什么路线都重要。愿你能在这个巨大的代码宝库里淘到自己想要的东西。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进