2026/9/19 17:58:32

深入LLVM项目:从源码构建到自定义Pass开发全指南

深入LLVM项目:从源码构建到自定义Pass开发全指南 提到llvm-project很多人的第一反应是“Clang编译器”但实际上这套代码仓库远远不止一个C/C编译器那么简单。它是一整套编译器基础设施覆盖了从编程语言前端、中间表示、优化器、代码生成到链接器、调试器、运行时库、汇编器、二进制工具链的完整环节。过去十几年里Swift、Rust、Zig、Julia这些新语言的前端实现Android NDK、iOS工具链、Apple全家桶的底层编译以及NVIDIA、AMD的GPU编译栈底层都跟llvm-project脱不开关系。这篇内容适合两类人一类是想真正搞懂编译器前端、优化器、后端怎么协同工作的开发者另一类是准备基于LLVM做二次开发、给自研语言写编译器、或者想做静态分析和代码插桩的工程师。我会从整体目录结构讲起把核心设计逻辑拆开再给出一套经过实测的源码构建方案和开发路径最后把那些不看源码根本不知道的坑和排查方法一并写清楚。1. 整个仓库到底装了什么llvm-project全景拆解1.1 top-level目录之间的分工逻辑llvm-project是一个monorepo也就是把多个独立项目塞进同一个仓库里管理。这个设计在大型基础软件里很流行因为LLVM各个子项目之间的版本耦合非常紧clang依赖LLVM的APIlibc又跟编译器的内置函数、ABI接口有强绑定如果每个项目各自一套仓库、各自打tag维护成本会高得离谱而且很容易出现“clang 15配LLVM 14”这种版本错位问题。仓库根目录下真正会频繁接触的核心项目有这么几个我列了一张表目录名作用典型使用者llvm核心IR、优化Pass、目标后端、llvm-as/llc/opt等工具编译开发者、工具链二次开发者clangC/C/Objective-C前端普通开发者、嵌入式、移动端clang-tools-extraclang-tidy、clangd、include-what-you-use等周边工具做代码分析、IDE插件的人lld高性能链接器替代系统ld构建系统优化、交叉编译lldb调试器对标gdb调试底层代码、逆向分析libc / libcabiLLVM自己的C标准库实现及其ABI层新平台移植、需要完全控制ABI的场景compiler-rt运行时库sanitizer、builtins、profile等做内存检测、覆盖率统计、底层优化mlir面向编译器/硬件加速的多级IR框架AI编译器、芯片工具链开发者flangFortran前端基于MLIR重新实现高性能计算领域polly基于多面体模型的循环优化器做数值计算优化的同学bolt面向机器码的profile引导二进制优化工具数据中心、大型二进制性能工程师openmpOpenMP运行时实现并行计算开发者核心逻辑是llvm目录是底座所有子项目都长在一个统一的中间表示和Pass基础设施之上。理解了这个你就知道为什么很多人说“学习LLVM要先学llvm目录而不是先学clang”。你写的每一步代码最终都是通过LLVM IR进入优化管线再落到目标机器的指令上。1.2 llvm目录内部的关键路径进入llvm目录后还有几个子目录需要优先认路。include/llvm和lib/llvm这两个是学习重点。include里是整套公开头文件lib里是对应的实现。比如include/llvm/IR存放IR相关类lib/Transforms存放优化Pass实现。如果你打算写一个自己的Pass基本路径就是在include里加头文件、在lib里加实现然后通过CMake挂载进构建系统这大概是所有LLVM二次开发的第一步。tools/目录下是各种命令行工具。opt用来跑Passllc用来做代码生成llvm-as和llvm-dis负责把IR在文本形式和bitcode之间来回转换llvm-nm、llvm-objdump用来分析二进制。初次接触的人容易把opt当成“优化工具”而忽略它的核心身份opt是一个Pass运行的测试平台你可以用它单独加载一个写好的Pass在.ll文件上跑一遍看效果这是开发期间最高效的验证闭环。我把这个目录关系打一个比方LLVM Core像一个“标准化工厂”IR是工厂里唯一流转的“中间零件”Pass是流水线上的工序而后端是“最终装配线”。Clang只是把C/C源代码翻译成“中间零件”的入口之一你可以随时在IR这一层介入换成自己的语言前端或者增加自己的优化工序。2. 为什么LLVM的设计能赢IR、Pass与分层思想2.1 三段式架构前端、中端、后端LLVM最核心的架构思想是三层分离前端负责把源代码变成IR中端负责在IR上做一轮又一轮的优化后端负责把IR变成目标机器码。这个思想和Java的字节码在不同点上有异曲同工都提供了一种“语言无关的中间表示”但LLVM的IR是静态编译场景下的设计目标侧重于优化和分析而不是虚拟机执行。这种分层让“新语言”的编译器实现成本大大降低。你只需要写一个能生成IR的前端整个中端优化器、包括寄存器分配、指令调度、平台相关的代码生成细节都不用自己操心。Rust为什么能在几年内获得战斗力极强的编译器一个重要原因就是rustc把MIR降到LLVM IR之后直接吃掉了LLVM多年积累的优化能力。这也是llvm-project生态繁荣的核心原因它把“写一个编译器”这个原本极其昂贵的工程拆成了“写前端”和“选后端”两个相对可控的任务。Clang能快速支持新的C语言标准特性、能在iOS和Android之间做ARM和X86的跨平台编译都是因为这层抽象的功劳。需要说明的是LLVM对前端的抽象也不是完全透明语言语义和IR能力之间仍有缝隙比如C异常、协程、虚函数、objc的消息转发其实都在IR层面有对应的“约定”和运行时配合。严格说是从“完全隔离”变成“约定接口”但比起传统编译器把前后端绑死已经是碾压级优势。2.2 IR为什么要设计成三种形式LLVM IR有三种形态内存表示、以及磁盘上的bitcode.bc和文本表示.ll。这个三重设计不是没事找事每种形态服务的场景完全不同。内存表示是编译流程中真正在跑的状态优化器和代码生成器都在它上面操作。bitcode是紧凑的序列化格式适合存储和增量编译比如iOS的bitcode提交、LTO跨编译单元优化靠的就是它。文本表示是给人看的调试Pass、理解IR结构、分析问题基本离不开它。实操中你最常用的两个命令就是llvm-dis和llvm-as一个把.bc转成.ll一个反向转换。另外clang可以加-emit-llvm参数直接输出IR文本这个参数在学习阶段极其好用你可以写一段简单的C代码编译成.ll文件肉眼观察“for循环是怎么变成IR的”、“结构体是怎么被layout的”这比我当时啃《编译器设计》那种抽象描述来得快得多。IR本身是SSA形式的也就是每个变量只能被赋值一次这种静态单赋值形式让数据流分析变得简单因为值的定义和使用关系是显式的优化器可以更快地判断一个值是否被用到。写Pass时你实际上就是在一张SSA图上做模式匹配和重写而传统编译器那种扫描字节码、自己维护活跃变量表的做法已经过时了。2.3 Pass机制优化器为什么是可插拔的在LLVM里“优化”不是写死的一次性大流程而是由大量可插拔、可排序的Pass组成。每个Pass做一件事有的做死代码消除有的做公共子表达式消除有的做循环展开有的做内联。它们跑在IR上按顺序形成一条“优化流水线”。Legacy PassManager旧版已经用了很多年靠全局注册表管理Pass用字符串ID标识。而New PassManager在LLVM 14之后变成默认核心区别是它把分析结果和变换Pass的依赖关系管理得更加明确可以重复使用分析结果、支持更多层次的缓存性能也更好。如果你从网上看到老的教程还在写“opt -mem2reg”那是在旧框架下新写法通常是“opt -passesmem2reg”。Pass框架给你的真正能力是“在编译流程中间插入自己的代码”。这个在工业界有非常广泛的应用做系统级优化、做代码插桩、做安全加固、做二进制瘦身、做内存安全检查甚至做混淆和反混淆。后面的实操章节我会给一个可运行的插件示例那时候你会真正感觉到这套架构的设计魅力写一个优化Pass竟然可以像写一个命令行工具一样干净。写Pass最需要注意的一件事是Pass是有生命周期和缓存机制的分析Pass的结果可能被多个变换Pass复用如果某个变换Pass修改IR后没有正确invalidate分析结果后续处理会读到脏数据产生极难定位的bug。踩过一次之后你就明白为什么新PassManager会在PassBuilder里引入一整套AnalysisManager机制这不是学术炫技而是实际工程问题倒推的设计。3. 从源码构建llvm-project完整流程与调优记录3.1 构建前的准备工作在动手之前先确认硬件。LLVM的完整构建非常消耗资源全量构建llvm-project所有子项目需要的内存随便能摸到16GB以上磁盘空间要预留大约60到80GB。如果机器配置一般强烈建议在第一次构建时只选需要的子项目把Target只保留本机架构先让编译跑通后面再加模块增量构建。依赖方面cmake版本不要低于3.20编译器和标准库需要支持C17。构建生成器我强烈推荐用Ninja而不是Unix MakefilesNinja的增量构建和并行调度都要好很多后面你改了头文件重新编译的时候会非常感激这个选择。另一个能救命的是ccache。LLVM头文件极其多哪怕改一个公共头文件也可能触发几百个文件重编用ccache做编译缓存能省掉一半甚至更多的后续构建时间。我在一次改动include/llvm/IR/Function.h之后没有ccache的情况下花了20多分钟重编加上ccache之后只需要几分钟。这差距不是“体验差异”而是“能不能高效迭代”的本质区别。3.2 CMake配置和常用开关解析LLVM用CMake作为构建系统配置工具核心是通过CMakeLists.txt把项目组织成target再传给Ninja去构建。第一次配置时你拼的命令大致长这样cmake -G Ninja -S llvm -B build \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang;lld;libcxx;libcxxabi;compiler-rt;mlir;clang-tools-extra \ -DLLVM_TARGETS_TO_BUILDX86;AArch64;RISCV \ -DLLVM_ENABLE_ASSERTIONSON \ -DLLVM_USE_LINKERlld \ -DLLVM_CCACHE_BUILDON \ -DCMAKE_C_COMPILERclang \ -DCMAKE_CXX_COMPILERclang每个开关选择背后都有逻辑不是瞎填的。CMAKE_BUILD_TYPE用Release是为了让LLVM本身跑得快但如果你要调试Pass或者跟踪LLVM内部状态可以改用Debug或RelWithDebInfo代价是编译时间更长、二进制大很多。LLVM_ENABLE_PROJECTS控制了要构建哪些子项目第一次不建议贪多。LLVM_TARGETS_TO_BUILD这个参数是个大坑默认值会尝试构建所有平台后端CPU时间蹭蹭涨精简成自己需要的架构后构建速度差异巨大。LLVM_ENABLE_ASSERTIONS在开发阶段尤其重要编译器内部很多数据结构和算法依赖assert来校验不变量比如IR合法性检查。如果你关了断言很多问题不会被发现而是作为莫名崩溃在遥远的某个地方炸出来。我建议是只要你不是为了发布最终产品一律ON。LLVM_USE_LINKERlld这一点也非常重要。LLVM自身的可执行文件和共享库非常庞大链接阶段如果调用系统binutils的ld内存占用高、速度慢而用lld是并行链接的速度快一个量级。当你要连续验证多个改动时这个选择能不能节省时间试过一次就懂。3.3 实战构建从cmake到验证配置完成后执行关键一步ninja -C build clang lld只构建clang和lld两个target而不是直接跑全量ninja。这能明显减少第一次的等待时间因为很多工具链组件你暂时用不到。构建完成之后先跑一个版本检查build/bin/clang --version然后写个测试文件echo int main() { return 42; } hello.c build/bin/clang hello.c -o hello ./hello echo $?shell里输出42说明工具链已经可以正常工作了。接下来验证刚刚构建出来的LLVM本身是否健康可以跑一套基础测试ninja -C build check-llvm check-clangcheck-llvm会跑LLVM核心单元测试和lit测试check-clang跑Clang前端测试。第一次执行测试大概需要10到20多分钟。这里给新手一个排查思路如果测试失败先看失败case集中在哪个目录如果集中在某个后端或某个Pass大概率是你裁剪了Target或某个实验特性没开如果是大面积失败先怀疑编译器版本或系统库不匹配。构建过程中最容易被忽视的是并行度和内存的平衡。Ninja默认按CPU核心数并行在核多的机器上链接阶段的并行链接会直接吃满内存。如果构建过程中发现内存接近耗尽用-j参数限制一下并行数量ninja -C build -j 8 clang lld或者干脆关掉并行链接。LLVM有专门的开关LLVM_PARALLEL_LINK_JOBS1表示只允许一个链接任务同时进行编译可以并行但链接串行化。这条配置在低内存云主机上几乎是必设项。3.4 增量构建的加速心法第一次构建只是入场券后面的日常开发才是真正考验。这里分享几个我用下来觉得最有效的增量加速办法。首先ccache配合CMAKE_C_COMPILER_LAUNCHER和CMAKE_CXX_COMPILER_LAUNCHER使用。上面的cmake命令里写的LLVM_CCACHE_BUILDON实际效果就是替你在两个launcher变量里自动配置好ccache。如果不想在CMake里开也可以在ninja命令行层面用ccache但不如直接配置后端干净。其次把调试信息和优化级别调整为合理组合。开发周期里我经常在RelWithDebInfo下调试既保留栈信息和变量信息又不会像Debug模式那样把所有优化全部关掉避免“Debug下正常、Release下崩溃”这种两难问题。第三善用ninja的target白名单。不要每次都用默认alltarget那会把所有组件全部链接一遍。谁知道你是只改了一个Pass想测试一下只要构建opt、llc或者自己的插件就够了。用ninja -t targets命令列出可用target然后精确指定。最后一个容易被忽略但非常实用的开关LLVM_ENABLE_MODULES。开启C20 Modules之后头文件的编译依赖会有显著改善但目前在部分平台上仍然和某些第三方头文件有兼容性坑如果你对Modules不熟建议先别开等对LLVM构建系统足够了解再折腾。4. 上手实践写一个自定义Pass和Clang工具4.1 从零写一个Function Pass插件先声明结论在现在的大环境下写Pass不推荐直接改LLVM源码树而是用动态插件方式。LLVM提供了PassPlugin机制可以把你写的Pass编译成一个.so文件然后用opt的-load-pass-plugin参数加载这样不污染主仓库也不用等全量重编。下面是一个最简单的FunctionPass插件代码功能是打印出当前函数的名字和基本块数量让你先走通机制#include llvm/IR/Function.h #include llvm/IR/BasicBlock.h #include llvm/Passes/PassBuilder.h #include llvm/Passes/PassPlugin.h #include llvm/IR/PassManager.h using namespace llvm; namespace { static bool runOnFunction(Function F) { errs() Function: F.getName() \n; errs() BasicBlock count: F.size() \n; return false; // 返回false表示没有修改IR } struct MyPass : public PassInfoMixinMyPass { PreservedAnalyses run(Function F, FunctionAnalysisManager AM) { runOnFunction(F); return PreservedAnalyses::all(); } }; } // namespace PassPluginLibraryInfo getMyPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, MyPass, LLVM_VERSION_STRING, [](PassBuilder PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager FPM, ArrayRefPassBuilder::PipelineElement) { if (Name my-pass) { FPM.addPass(MyPass()); return true; } return false; }); }}; } extern C LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getMyPassPluginInfo(); }把这段代码保存成MyPass.cpp用下面的CMakeLists编译成插件cmake_minimum_required(VERSION 3.20) project(MyPass) find_package(LLVM REQUIRED CONFIG) find_package(Clang REQUIRED CONFIG) message(STATUS Found LLVM ${LLVM_PACKAGE_VERSION}) message(STATUS LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}) add_library(MyPass MODULE MyPass.cpp) target_include_directories(MyPass PRIVATE ${LLVM_INCLUDE_DIRS}) target_compile_definitions(MyPass PRIVATE ${LLVM_DEFINITIONS}) target_compile_options(MyPass PRIVATE -fno-rtti -stdc17) target_link_libraries(MyPass PRIVATE LLVMCore LLVMPasses LLVMSupport)注意两点。第一LLVM的头文件默认是关闭RTTI编译的所以插件也要用-fno-rtti否则会在编译期报出一堆类型信息不匹配的错误。第二find_package(LLVM REQUIRED CONFIG)依赖你的LLVM安装或构建产物里有LLVMConfig.cmake如果你是用源码构建的这个文件在build/lib/cmake/llvm/目录下。编译cmake -G Ninja -S . -B build -DLLVM_DIR~/llvm-project/build/lib/cmake/llvm ninja -C build然后找一个测试IR文件来验证插件效果。先用clang把C源码转成IRbuild/bin/clang -O0 -emit-llvm -c test.c -o test.bc接着用opt加载插件跑一下build/bin/opt -load-pass-pluginbuild/MyPass.so -passesmy-pass test.bc如果能看到每个函数名和基本块数量打印出来说明你的第一个LLVM插件已经成功跑通了。从这个基础上往里加逻辑就很简单了遍历指令、做模式匹配、插入新指令、修改CFG都有现成的IRBuilder和Instruction API可以用。4.2 趁热打铁用Clang库写一个源码工具Pass是在IR层做的修改但很多时候你需要的是在源码层做分析这就轮到Clang库出场了。Clang提供了一套LibTooling框架可以让你写一个独立的C程序通过clang tool解析C/C源码得到完整的AST然后在AST上做检查和改写。它的基本流程是用CommonOptionsParser接收命令行参数它内部会调用ClangTool对每个输入文件做预处理和语法分析然后遍历AST。比如你想统计一下源代码里有多少个函数、每个函数有多少个参数只需要在VisitFunctionDecl里做计数。LibTooling最常见的应用是clang-tidy检查项。每个clang-tidy检查本质上就是一个AST访客挂载在ClangTidy框架里。如果你想给团队写一个自定义的代码规范检查比如“不要用裸指针传递给智能指针函数”“函数内循环不要超过三层”这种检查用正则表达式是做不到的但用ASTVisitor做模式匹配就非常自然。我建议初学者先跑通一个最简单的tool用clang-query或者自己用LibTooling写一个AST转储小工具对一段有继承、有lambda、有模板的C代码做AST dump看它和C语法的对应关系。这样你会快速建立起“源码到AST再到IR”的整个映射认知对后面做任何深度的工具开发都有帮助。4.3 当Clang和IR都不够用MLIR和自定义方言传统LLVM IR的优点在于它能完美表达机器码层面的优化信息但它的缺点也随之而来IR层级还是比较低你去做AI编译器里的算子融合、数据布局转换、循环分块直接写在LLVM IR上会极其痛苦。因为那些高层次结构信息在降到IR时已经丢失了。MLIR就是为了解决这个问题出现的。它允许开发者自己定义“方言”每种方言相当于一种特定领域的IR。比如TensorFlow就把自己的计算图表达成tf方言然后逐步lowering到linalg、scf、affine最后到LLVM方言。这个设计非常灵活可以理解为“IR的分层折叠”你在越高的IR上做变换信息越丰富、优化空间越大。如果你所在的领域是深度学习编译器、芯片编译器、或者某种DSL编译器我建议在熟悉LLVM之后尽早去接触MLIR。它的概念比传统LLVM多了一层抽象但底层思路是一脉相承的像Pass一样操作IR像定义C类一样定义Op像流水线一样做lowering。5. 学了llvm-project能做什么真实场景与学习路径5.1 编译器/新语言开发这是最直接的应用场景。无论是公司内部自研DSL、学术界的教学编译器还是开源社区的新语言只要有了前端并能生成LLVM IR就可以立刻获得优化和代码生成能力。Rust、Zig、Swift已经验证了这条路而且他们还在迭代中不断反哺LLVM本身。学习编译器不一定要从零写一个gcc级别的编译器用LLVM作为底座把精力花在语言语义和类型系统上才是工业界的现代做法。如果你正在开发一门解释型语言也可以考虑先让语法树解释执行再把热点路径逐步下沉到LLVM JIT也就是LLVM的MCJIT和ORC框架支持的即时编译能力。5.2 静态分析、代码插桩与安全加固编译器的IR层数据流分析是静态分析工具的理想底座。市面上不少商业级的安全扫描产品底层就用了LLVM的Pass和AST分析。你在Pass里可以插桩给每个函数执行前加一条日志调用、给每个malloc/free配上一对记录事件、给每个内存访问加上边界检查。这类操作如果手工改代码工程量巨大而且容易漏通过编译器Pass统一插桩逻辑一致、覆盖完整。Sanitizer全家桶是另一个杀手级应用。AddressSanitizer在编译器插桩阶段给每个全局变量、栈对象、堆对象周围加入毒药内存并拦截每一次访问能检测出各类越界、UAF问题。这套工具能浮出水面说明llvm-project里的compiler-rt运行时库对系统级调试非常重要。你甚至可以基于这种模式开发自己的运行时检查工具。5.3 异构计算与GPU编译GPU编译器是llvm-project近年投入极大的方向。NVIDIA的NVVM、AMD的ROCm、Intel的oneAPI都在LLVM基础上做GPU代码生成MLIR也成为AI芯片编译器的事实标准。做异构计算的同学经常要理解“主机代码和设备代码如何通过编译器前端分流”。如果你要进入高性能计算或异构计算这个方向LLVM的知识几乎是必须的因为CPU和GPU的代码生成现在都统一在LLVM后端里只有理解了PTX/AMDGCN这些后端目标才能真正懂得为什么核函数会有那些性能限制、为什么bank conflict会被生成成那样。这一块门槛相对高但天花板也很高。5.4 学习路径建议我的建议是按照“从外到内、从用到改”的节奏走。第一阶段会用clang、opt、llc、llvm-dis这些工具会读IR文本能看懂一个C源文件编译成IR后的结构。第二阶段重点看llvm/lib/Transforms/InstCombine和llvm/lib/Transforms/Scalar里的几个经典Pass源码结合opt的-pass-parameters启停选项理解IR优化到底在做什么。第三阶段自己动手写插件Pass从剪枝拿到一个修改过的IR开始做点真正有用的变换比如死代码清理、条件分支反转、或者某种自定义的instrumentation。第四阶段进入Clang前端研究AST和Sema理解静态分析和重写工具。第五阶段按需深入MLIR或后端代码生成。这个路径每走一步都会碰上大量在这篇文章里没法展开的细节但核心思维不会变任何一次变换都是“在某个IR层级上做模式匹配与重写”的过程。6. 常见问题与避坑记录构建和开发路上的真实教训6.1 高频问题速查表现象原因解决方法cmake配置时报LLVM_CONFIG_NOT_FOUND找不到LLVMConfig.cmake通过-DLLVM_DIR指向build/lib/cmake/llvm构建时内存瞬间飙升然后OOM并行链接了多个大型二进制设置LLVM_PARALLEL_LINK_JOBS1或ninja -j限制链接时报undefined reference to vtable用了RTTI和LLVM头文件不匹配编译插件/项目时加-fno-rttiopt加载插件失败报PLUGIN_API_VERSION不匹配插件编译时用的LLVM API版本和当前opt不一致确保插件用同一个build编译而不是系统自带的LLVMIR文件打开时报Expected Instruction.ll文件损坏或版本不兼容用llvm-dis重新生成检查是否为文本IR修改Pass后不生效用了旧的PassManager命令行语法新PM用opt -passesyour-passclang编译C代码报找不到内部头文件只构建了clang没装libc/compiler-rt更新clang-resource-headers或构建libc测试大面积失败全是某个target的crash可能是Target裁剪后缺了依赖重新加全LLVM_TARGETS_TO_BUILD比如加回X86check-llvm过慢并行数太大导致资源争抢限制lit并行数llvm-lit -j 46.2 从崩溃日志定位Pass问题的方法写Pass的时候最经典的场景是opt加载你的Pass后直接segfault。很多新手第一反应是“我的IR遍历逻辑写错了”但真正常见的原因有三个一是Pass修改了IR但返回值没有正确说明哪些分析被“破坏”导致后续Pass基于无效分析数据做决策二是直接遍历了正在被删除或替换的Instruction悬挂指针被解引用三是函数签名和PassManager期望的接口不一致典型的比如run函数没有正确返回PreservedAnalyses。排查顺序建议是先用llvm-lit把最小复现case单独跑一遍减少干扰然后启用LLVM_DEBUG在你的Pass里加DEBUG宏输出观察是哪条指令或哪个基本块触发崩溃。如果连崩溃位置都定位不了就考虑先用-enable-new-pmfalse降到legacy PM重试因为Legacy PM的稳定性在边缘case上仍然有参考价值。6.3 版本敏感性与环境隔离llvm-project的API变动速度相当快。网上教程和博客大量都是几年前的最坑的是头文件名、Pass接口名、CMake变量全部翻新照着复制大概率编译失败。我刚开始点的时候网上搜到一段mem2reg的例子编译时报错说头文件不存在折腾了很久才发现API已经彻底变了。应对办法是永远以你当前源码树里的头文件和源码示例为准。每个子项目目录下都有examples或unittests那里面的代码是真实构建出来的比任何博客都可靠。另外尽量在独立环境里构建不要把系统自带LLVM和你自己构建的LLVM混在一起。ldconfig和PATH里谁先找到库决定了你编译时链接的是哪个版本这个坑可以让你折腾一整天。最后再说一个我自己的真实体会如果你决定深入学习llvm-project一定不要只停留在“会用工具”的层面要把自己想象成一个正在构建这个编译器的人。读文档时多问一句“为什么会设计成这样”读源码时多看一眼“这个Pass想表达什么不变量”。这套系统里藏着的是过去二十多年编译器工程领域最优秀的一批脑力劳动成果值得你慢慢拆开看。