llvm-project 深度解析:从编译器基础到源码构建实战
发布时间:2026/9/20 5:23:46 作者:尧图编辑部 阅读量:1,286

GitHub 上 star 数最高的编译器项目绕不开 llvm-project 这个名字。很多人一听到 LLVM第一反应是哦就是那个 C/C 编译器这个理解不算错但远不够。真正把 llvm-project 仓库拉下来、看过源码结构之后会发现它远远不止一个 clang 编译器而是一整套编译器基础设施、优化框架、代码生成工具链甚至还包括调试器、标准库、链接器。这篇文章我会从一个长期使用和二次开发 LLVM 的从业者角度把这个项目到底是什么、仓库里有什么、核心设计为什么能赢、怎么从源码构建一个可用的环境以及类似 llvmpipe 中用到的 LLVM 15.0.7 和 256 bits 这类细节都拆开讲清楚。无论是刚开始接触编译器的新人还是准备基于 LLVM 做工具链开发的工程师这篇内容都能帮你少走不少弯路。1. llvm-project 到底是什么为什么值得花时间研究1.1 从编译器的编译器说起LLVM 的全称是 Low Level Virtual Machine虽然名字里有 Virtual Machine但它跟 Java 虚拟机、Python 虚拟机完全是两回事。它最早是伊利诺伊大学的一个研究项目目的是做一个可持续使用的、模块化的编译器基础设施。什么叫基础设施你可以把它理解成一套乐高积木每个积木负责编译器流程里的一个小环节你可以任意组合甚至替换其中任意一块。我见过太多人第一次打开 llvm-project 的时候被吓到目录又多又杂光 top-level 目录就二十多个每个都有几万到几十万行代码。但实际上整个项目的核心逻辑非常清晰就一条线前端把源码转成中间表示优化器处理中间表示后端把中间表示转成机器码。LLVM 的聪明之处在于它把这条线切成了三段中间用一个统一的通用语言LLVM IR衔接。只要你的语言前端能产出 LLVM IR那后面的优化和后端生成就能白嫖只要你的硬件后端能接收 LLVM IR那前面所有语言前端就都能支持你的硬件。这个设计放在今天看司空见惯但在 2000 年代初是相当超前的。当年 GCC 是统一的前端转 AST 再转 tree 结构各个语言和处理器支持耦合在一起前端加语言、后端加芯片都极其费劲。LLVM 用 IR 这个中间层彻底解耦才让后来 Swift、Rust、Julia 这一批语言能快速落地。1.2 谁在用 LLVM解决了什么真问题聊到应用场景我感觉能写一本书。举几个我最熟悉的例子Apple 全系工具链Xcode 的 clang、lldb、libc 都是 LLVM 体系的产物macOS 和 iOS 上的编译工作基本都是 LLVM 在前面顶着的。Rust 编译器 rustcRust 官方后端的默认实现就是基于 LLVMRust 代码最终会生成到 LLVM IR 再进优化器和后端。Android/ARM 生态Android NDK 提供的编译器就是 clangARM 芯片厂商的工具链也大量基于 LLVM 二次开发。GPU 与 AI 编译器NVIDIA 的 CUDA 编译器、AI 芯片公司们做的深度学习编译器底层基本都是 LLVM。比如MLIRMulti-Level Intermediate Representation就是构建在 LLVM 之上的一个子项目专门解决多级 IR 和硬件加速问题。还有个很多人没注意到的场景llvmpipe。这是 Mesa 项目里的一个纯软件光栅化渲染器它用 LLVM 做 JIT 动态生成渲染代码在没有独立显卡的机器或者虚拟机上也能跑 OpenGL。很多云服务器、CI 环境里的图形测试就靠它兜底。所以你看LLVM 已经远远不只是一个编译器这么简单它是一个各行各业都在依赖的底层软件基础设施。理解 llvm-project某种程度上就是在理解现代编译技术和语言工具链的底层框架。2. 仓库全景llvm-project 里到底装了什么2.1 核心子项目的分工llvm-project 这个仓库是 2019 年之后从多个独立仓库合并而来的最初 clang、lldb、libcxx 等都是分散维护的。合并成一个 mega-repo 之后最大的好处是发布、版本管理、跨项目改动都简单了但代价是仓库体积非常大clone 下来通常要几个 GB 甚至十几个 GB。第一次拉代码的朋友我建议先不要编译把目录结构过一遍搞清楚每个目录的职责。下面这张表是我整理的最核心的几个子项目目录/子项目作用一句话定位llvm/核心基础库包括 IR、优化器、后端、CodeGen整个体系的发动机clang/C/C/Objective-C 前端最常用的编译器前端clang-tools-extra/clang-tidy、clang-format 等辅助工具代码质量与格式化工具集lld/链接器支持 ELF/Mach-O/COFF号称比 GNU ld 快数倍的链接器lldb/调试器与 LLVM 同架构的调试工具libcxx/、libcxxabi/C 标准库和 ABI 层纯 LLVM 实现的 C 运行库compiler-rt/运行时库包括 sanitizer、builtins内存检测、性能剖析等依赖的运行时flang/Fortran 前端LLVM 对老牌科学计算语言的官方支持mlir/多级中间表示框架用于构建可复用的编译基础设施AI 编译器核心依赖polly/循环优化与多面体模型面向数据局部性和并行性的高级优化openmp/OpenMP 运行时和编译支持并行计算的重要组件libunwind/栈回溯库异常处理和调试的基础bolt/二进制优化工具针对已经编译好的二进制做性能优化这里我想多强调一下llvm/这个根目录。它本身就是个完整的小系统往下又有include/、lib/、tools/、test/、utils/等子目录。tools/里有我们最常用的命令行工具比如opt跑优化 Pass 的工具、llc把 IR 转成汇编/目标码、llvm-config获取编译/链接参数、lli直接用 JIT 执行 IR等。很多人以为LLVM 就是 clang其实 clang 只是tools/里的一个小兄弟真正的基础库在lib/目录下。2.2 为什么一个仓库要塞这么多东西在 llvm-project 合并之前各个项目分散在不同仓库版本对齐是件特别痛苦的事。clang 依赖 LLVM 某个版本的新接口而接口可能还在测试中lldb 又依赖 clang 和 LLVM 的某几个特定提交结果就是发版时各种依赖地狱。合并成 monorepo 之后代码提交、构建配置、issue 跟踪全都在一个地方任何人都可以用git log直接看到这个 commit 同时改了 clang 的啥、LLVM 的啥关联性变得极其直观。这也是为什么现在几乎所有大型基础设施项目都在考虑 monorepo 方案的原因——全量确定性、原子提交、统一版本、简化 CI。当然缺点也实实在在的。全量 clone 很大全量构建很慢。就算只编译 LLVM Clang在一台 16 核机器上跑也要十分钟往上内存 16GB 以下基本会卡。所以我在后面第 4 章会详细介绍怎么按需构建、怎么配置LLVM_ENABLE_PROJECTS和LLVM_TARGETS_TO_BUILD把这俩变量理解透能帮你省一半以上时间。2.3 从版本号看项目演进LLVM 的版本号策略跟很多开源项目不太一样它不搞奇数开发版、偶数稳定版那套而是每半年一个大版本新功能持续合并。比如用户提到的LLVM 15.0.715 就是大版本号0 是次要版本7 是补丁版本。补丁版本通常只修 bug、修关键安全漏洞不引入新功能。LLVM 15 是 2022 年 9 月发布的一个标志性特性是它把默认的 C 标准切到了 C17同时把 OpenMP 的基本支持进一步增强。15.0.7 则是 15 系列里比较靠后的补丁版修复了一批代码生成和数据对齐方面的问题所以在一些长期维护的发行版里会默认带着。如果你用的是发行版自带的 llvmpipe 或者 Mesa看到类似 llvmpipe (LLVM 15.0.7, 256 bits) 的输出说明系统里的 Mesa 默认链接了 LLVM 15.0.7 的运行时256 bits 通常指的是 CPU 支持的矢量位宽对应 AVX2/AVX512 的 256 位矢量寄存器这个我后面第 5 章会展开。对开发者来说版本选择值得讲究。我个人建议如果只是学习研究跟最新 release 就好如果做生产工具链选一个维护周期内的老版本比如 15.x 或者 16.x别追太新因为新版本工具链对构建环境要求更高某些老项目也来不及适配。3. LLVM 核心设计思路拆解三段式架构与 IR3.1 三段式架构为什么能赢前面我提过 LLVM 的核心是前端-优化器-后端三段式这个架构在编译器领域叫 GCC 的前端-中端-后端其实也是类似的但 LLVM 和 GCC 有个根本差别GCC 的中端是树结构GIMPLE它和前端、后端耦合成一个单体程序LLVM 把中端做成了独立可调用的库IR 既是内存中的数据结构也是可以落到磁盘的文本/二进制文件。这意味着什么意味着你可以在不写任何编译器代码的情况下手动写一个.ll文件跑opt去优化它再跑llc生成汇编全程不需要任何高级语言前端。这种透明、可组合、可脚本化的流程对研究者、编译器工程师、教学来说都极其友好。再来看看它对硬件厂商的吸引力。假设你是某家芯片公司的编译器团队要支持自家新 ISA传统的做法是改 GCC 的后端跟 GCC 社区的多轮 review 斗智斗勇周期长得吓人。用 LLVM 的话你只需要写一个 Target backend继承几个基类实现指令选择、寄存器分配、指令调度等关键接口就能快速接入庞大的前端和优化生态。NVIDIA 的 NVPTX 后端、AMD 的 AMDGPU 后端、苹果的 ARM64 后端都是这么做的。3.2 LLVM IR整个体系的枢纽LLVM IR 是 LLVM 体系的核心也是很多新手的拦路虎。它有三种形态内存表示C 类、位码bitcode、文本.ll文件。三种形态可以无损转换方便阅读和交换。一个最小的 LLVM IR 程序大概长这样define i32 add(i32 %a, i32 %b) { %sum add i32 %a, %b ret i32 %sum }这个define就是定义一个函数i32是 32 位整数类型%a、%b是虚拟寄存器SSA 值add指令做加法。你可以把它保存为add.ll然后跑llvm-as add.ll -o add.bc # 文本转位码 opt -S -passesmem2reg add.ll -o add.opt.ll # 跑优化 llc add.ll -o add.s # 生成汇编 lli add.ll # 直接 JIT 执行很多刚接触 LLVM 的朋友会问为什么不直接用类 C 的 AST 做优化答案很简单AST 保留了太多源语言信息优化器必须要管语言越界的问题而 IR 是经过降级lowering的语义更简单、更贴近机器编译器优化就能在一个更干净、更底层的表示上做生成的代码也更可控。还有一点值得讲LLVM IR 是静态单赋值形式SSA的。SSA 的核心理念是每个变量只赋值一次这听起来很反直觉但正是这个设计让数据流分析变得简单高效。你可以在 IR 里看到大量%1 ...、%2 ...它们不是可变的寄存器而是不可变的值。遇到分支汇合需要更新变量时用phi指令来合并来自不同前驱的值。虽然写起来麻烦但分析起来极其顺畅。3.3 Pass 框架与优化管线优化器里的基本工作单元叫 Pass。一个 Pass 做一件小事情比如删除永远不会执行的代码、合并两个相同的加法、把小的结构体拆开存在寄存器里。LLVM 的优化器就是按顺序执行一串 Pass这串 Pass 的序列叫做 Pass Pipeline。Pass 的数量非常多光opt -print-passes列出来能刷半屏。常用的有mem2reg把alloca load/store的模式提升成 SSA 寄存器这是很多优化的前提。instcombine指令化简合并比如x 0换成x。gvn全局值编号消除冗余计算。loop-unroll循环展开减少循环控制开销。inline函数内联。在传统 LLVM 版本里Pass 是通过-O2 -O3这类优化级别隐式组织的在新版本LLVM 14 之后里新写的 Pass 要显式注册到 Pipeline这就是所谓的新 Pass Manager。新 PM 比老的好在哪它能更细粒度地控制每个函数、每个循环的优化流程也能更好地支持 JIT 场景下的增量编译。我刚开始写 Pass 的时候也踩过不少坑。提醒一句尽可能在新 Pass Manager 框架下写因为旧的 legacy PM 已经处于冻结状态新特性基本不会往里面加了。3.4 后端与指令选择没那么玄乎LLVM 的后端可能是新手最畏惧的部分看到一堆 TableGen 文件和几百个 Target 类就懵了。其实核心工作分三步指令选择把 LLVM IR 节点映射到目标指令集的指令比如 IR 里的add映射到 x86 的ADD。寄存器分配把虚拟寄存器的无限集合映射到物理寄存器比如 x86-64 的 RAX、RBX 等的有限集合。指令调度调整指令顺序利用 CPU 流水线、减少停顿。LLVM 的指令选择有多个路径默认用的是 SelectionDAG 框架新一代的 GlobalISel 也在逐渐成熟。用 TableGen 写指令模式的时候比如定义一个加法指令可以写成类似def ADD64rr : I0x01, MRMDestReg, (outs GR64:$dst), (ins GR64:$src1, GR64:$src2), add{q} {$src2, $dst|$dst, $src2}, [(set GR64:$dst, (add GR64:$src1, GR64:$src2))];这段代码的意思是说这是一个叫 ADD64rr 的指令编码是 0x01输出是一个 64 位通用寄存器输入是两个汇编上是addqSelectionDAG 模式是把 IR 的 add 匹配到这个指令。TableGen 会把这些声明编译成本地数据结构供指令选择器使用。这部分内容展开又是一篇长文这里不过多深入。但对于想搞懂LLVM 怎么生成代码的人来说抓大放小先理解这三步的主线再逐个击破是最好的策略。4. 新手实操从源码构建一个能用的 LLVM4.1 构建前准备与版本选择如果你想真正接触 LLVM建议别只用系统自带包尝试从源码编译一次。虽然有点费时间但你会对项目的构建方式、模块依赖、组件划分有非常直观的认识。先说前置条件。构建 LLVM 需要一台内存至少 8GB推荐 16GB的机器磁盘剩余空间 30GB 以上。CMake 3.20 以上版本不同会有不同要求老版本 LLVM 可能要求低些。一个可用的 C 编译器GCC 7.1 或 Clang 5 都行。Ninja 构建系统强烈推荐比 make 快得多还支持并行任务。版本选择上如果想稳定复现下面的操作可以选 LLVM 15.0.7因为不少 Linux 发行版和 Mesa 默认带的就是这套。拉取源码有两种方式# 方式一浅克隆指定 tag git clone --depth 1 --branch llvmorg-15.0.7 https://github.com/llvm/llvm-project.git # 方式二直接下载 release 源码包 # https://github.com/llvm/llvm-project/releases/tag/llvmorg-15.0.7我不建议直接git clone全量仓库几十个 GB 下载完还没编译就想删了。浅克隆加指定 tag 是最节省带宽和时间的方式。4.2 CMake 配置与构建命令进入源码目录后创建一个 build 目录在里面跑 CMake。下面是我个人比较推荐的配置mkdir build cd build cmake -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang;lld \ -DLLVM_TARGETS_TO_BUILDX86 \ -DLLVM_ENABLE_ASSERTIONSON \ -DCMAKE_INSTALL_PREFIX$HOME/llvm-15 \ ../llvm几个关键变量解释一下-DLLVM_ENABLE_PROJECTSclang;lld指定要额外构建的子项目。如果不加这个只编译 LLVM 核心。构建 clang 会多花不少时间但基本是必选项。-DLLVM_TARGETS_TO_BUILDX86只生成 X86 后端。默认情况下 LLVM 会为所有目标平台生成后端包括 AArch64、ARM、PowerPC、RISCV 等构建时间爆炸。如果你不需要交叉编译指定一个目标能省一半时间。-DLLVM_ENABLE_ASSERTIONSON开启断言。发布版建议关掉以提升性能但开发调试时强烈建议打开很多内存和逻辑错误会在断言里直接炸出来比事后排查强太多了。-DCMAKE_BUILD_TYPERelease编译优化模式。Release 模式生成的 LLVM 库和工具性能高、体积小Debug 模式适合调试 LLVM 自身代码但体积和速度都是灾难级别的。配置完成后构建ninja # 全量构建首次可能要 10-30 分钟 ninja install # 安装到 $HOME/llvm-15如果只装了clang;lld编译完在build/bin/下面可以看到clang、clang、lld、llvm-config等一系列工具。4.3 我踩过的构建坑一次给你排掉构建 LLVM 有不少经典坑我挑几个最常见的坑一内存不足。链接阶段每个人的编译内存占用很大特别是libLLVM.so。如果链接时 OOM可以减小并行度ninja -j2。如果还不行加参数-DLLVM_LINK_LLVM_DYLIBON把多个静态库合成一个动态库减少链接压力或者直接给机器加 swap不推荐但应急能用。坑二ninja 文件过多导致 inotify 报错。在某些 Linux 发行版下CMake 生成 ninja 文件后文件监听器会发现文件数超过系统限制。解决办法关掉-DLLVM_USE_SPLIT_DWARFON或者在命令行加-DCMAKE_EXPORT_COMPILE_COMMANDSOFF。最直接的办法就是把系统fs.inotify.max_user_watches调大。坑三系统 GCC 版本太老。LLVM 15 要求 GCC 7.1 以上CentOS 7 默认 GCC 4.8 会直接报错。建议用较新的发行版或者装一个 Clang 作为 bootstrap 编译器。还有个小技巧在 CMake 时可以显式指定编译器cmake -G Ninja \ -DCMAKE_C_COMPILERclang \ -DCMAKE_CXX_COMPILERclang \ ...用 clang 编译 LLVM 是很多 LLVM 开发者的习惯操作速度往往还快一点。坑四构建时间太久。如果你只需要 IR 层面的实验可以不构建 clang只构建核心和 opt/llc/lli 这几个工具。方法还是LLVM_ENABLE_PROJECTS然后在构建时只指定工具ninja opt llc lli llvm-as llvm-dis这样会快非常多几分钟到十几分钟就能完成。千万别没事全量构建体验编译人生。构建完别忘了验证一下$HOME/llvm-15/bin/clang --version $HOME/llvm-15/bin/llvm-as --version如果输出正常说明你的 LLVM 环境已经可用了。接下来就可以尝试写个 C 文件用clang -S -emit-llvm编译出来看看 IRecho int add(int a, int b) { return a b; } test.c $HOME/llvm-15/bin/clang -S -emit-llvm test.c -o test.ll cat test.ll看到define dso_local i32 add(i32 noundef %0, i32 noundef %1)这种输出的时候你就正式踏入 LLVM 的世界了。5. 延伸解读llvmpipe 和 LLVM 15.0.7 中的 256 bits5.1 llvmpipe用 LLVM 写成的软件渲染器很多接触图形栈的人会在终端里看到过类似 llvmpipe (LLVM 15.0.7, 256 bits) 这样的字符串。这通常出现在 Mesa 的软件渲染环境里比如在虚拟机、无 GPU 的云主机、或者是通过LIBGL_ALWAYS_SOFTWARE1强制开启软件渲染的场合。llvmpipe 是 Mesa 项目中的一个 Gallium 驱动它没有真正意义上的固定功能单元而是把 OpenGL 的渲染管线在 CPU 上实现。问题在于纯 CPU 渲染性能不够于是 llvmpipe 引入了 LLVM 的 JIT 能力在运行时把渲染所需的 vertex 处理、像素着色器代码动态编译成当前 CPU 的机器码大幅提升执行效率。具体工作流程可以简化成着色器源码GLSL→ TGSI/NIR 中间表示 → LLVM IR → 机器码。LLVM 在这里扮演的相当于一个高性能运行时编译器。那 256 bits 是什么意思它跟 LLVM 后端选择的矢量位宽相关。软件渲染在处理像素时天然适合 SIMD把 8 个像素的同一个颜色运算打包在一起处理一次性执行更高效。256 bits 通常表示 llvmpipe 可以同时处理 256 位宽的矢量数据对应的是 AVX2 或 AVX512 的 256 位寄存器YMM。当年在支持 AVX2 的 CPU 上llvmpipe 会启用这个位宽要是 CPU 只支持 SSE4.2那就只能走 128 位路径性能差距还是能感知到的。5.2 为什么 LLVM 的版本会影响 llvmpipe 的表现你看到的 LLVM 15.0.7 并不是说 llvmpipe 内部某段代码用的就是 15.0.7而是说这个 Mesa 构建里链接的 LLVM 运行时库来自 LLVM 15.0.7。Linux 发行版在构建 Mesa 包时指定了-DLLVM_CONFIG/usr/bin/llvm-config之类的参数把 Mesa 的 llvmpipe 和系统的 LLVM 动态库链接起来。这带来一个有意思的影响llvmpipe 能用到哪些 LLVM 优化和新指令集支持取决于系统里 LLVM 的版本。比如老版本 LLVM 对某类浮点指令的代码生成不够激进新版本就顺手优化了又或者某个补丁版本修了一个向量重排生成的性能回退直接反映到软件渲染帧率上。所以看到一个较新的 LLVM 版本号出现在 llvmpipe 里一般能推断显卡驱动未检测到硬件加速时系统软件渲染的性能会更好一点。如果你在排查 llvmpipe 性能问题可以用glxinfo确认glxinfo | grep OpenGL renderer如果输出 llvmpipe (LLVM 15.0.7, 256 bits)说明系统正在走软件渲染。如果你想看它到底用了哪些指令集扩展可以加环境变量LP_NUM_THREADS4 LIBGL_ALWAYS_SOFTWAREtrue glxinfoLP_NUM_THREADS控制 llvmpipe 的线程数核多的情况下调高还是能补一部分帧率的。当然这只是软件渲染场景下的一点性能优化手段与 LLVM 编译优化相比只是很小的一块应用。5.3 对编译器开发者的启发llvmpipe 这种用 LLVM JIT 做实时代码生成的项目对想深入 LLVM 的人来说其实是个很好的学习样本。它的代码里用了不少 LLVM 的 Pass、Target 和 MC 层 API你可以从源码里看到在一个真实项目里怎么集成 LLVM 做 JIT这比单纯看文档学到的东西多得多。Mesa 源码里的src/gallium/drivers/llvmpipe/目录是完整可读的重点看它怎么初始化 ExecutionEngine、怎么把着色器编译结果缓存起来、怎么处理多个线程同时调用 JIT 等等。说实话我第一次读完 llvmpipe 的 JIT 路径之后再去自己的项目里写 LLVM 集成思路清楚多了。6. 新手如何规划 LLVM 学习路线6.1 按阶段推进别一上来就啃 TableGenLLVM 的学习曲线确实陡但我觉得只要方法对两个月内完全能上手。我的建议是分四个阶段走阶段一会用 clang 产 IR看得懂 IR。先用clang -S -emit-llvm编译各种 C 片段读.ll文件认识define、call、br、phi、alloca、load、store这些基本指令。把opt -S -passesmem2reg跑一遍再看 IR 的变化基本就能理解优化器在干嘛。阶段二会用 opt/llc 操作 IR。把上一阶段的 IR 保存下来自己写一个简单的 Pass先在新 PM 框架下用llvm::FunctionPass练手比如统计函数内指令数量、把某个函数名打印出来。能跑通 pass 的注册、加载、输出你就跨过了最难的坎。阶段三理解 Pass 优化流程。把opt -O2在不同 IR 上跑看每道 Pass 细节。可以用--debug-onlyloop-unroll之类的参数看优化日志这比看抽象文档直观得多。阶段四目标后端和 CodeGen。这部分知识不需要所有人都掌握但至少要知道llc是怎么把 IR 编译成目标汇编的。试着看某个简单函数的汇编再反过来想它对应 IR 的哪几步操作。6.2 工具和资料推荐官方文档llvm.org/docs/里的 Tutorial 和 LangRef 是首选。LangRef 虽然长但当字典用非常好。Kaleidoscope 教程官方自带的用 LLVM 写一个语言的教程一步一步带你实现词法、语法、IR 生成、JIT。我建议无论如何都跟着写一遍哪怕只是照抄也要跑通。编译器工程书籍老牌的有《Engineering a Compiler》讲通用的编译原理如果只针对 LLVM可以看《LLVM Techniques, Tips, and Best Practices》。后者有不少工程落地的建议。源码本身遇到不懂的 API直接去llvm/include/llvm/下面翻头文件LLVM 的头文件注释写得非常详尽很多时候比外部博客还要准确。社区讨论LLVM Discoursediscourse.llvm.org和 GitHub Issue 区搜别人报错和处理方法效率远比来问我要高。Stack Overflow 的llvm标签下也有不少高质量回答。说个小技巧在源码树里用grep -r XXX找 API 定义和用法示例效率极高。比如搜createXXXPass、TargetMachine::createDataLayout这种关键类名直接在真实代码里学用法比查过时的博客靠谱太多。6.3 哪些坑值得提前规避新手学 LLVM 最常见的几个问题我觉得值得单独拎出来说用 debug 版本跑全量构建。这是最大误区。Debug 构建的 LLVM 工具速度极慢编译时间又长吓退了不少新手。建议用 Release Assertions 模式即-DCMAKE_BUILD_TYPERelWithDebInfo -DLLVM_ENABLE_ASSERTIONSON既有可调式信息性能也能接受。忽略新老 Pass Manager 的差异。网上很多教程还在讲 legacy PM比如opt -mem2reg这种写法。新版15更推荐opt -passesmem2reg写 Pass 时也有新旧两套接口一定确认你参考的资料针对的是哪个版本。没有处理好 include 路径。编译自己的 Pass 时链接和编译参数都得靠llvm-config来拿llvm-config --cxxflags --ldflags --libs core support直接用llvm-config --cxxflags输出的参数再链接--libs core一般能解决大多数新手找不到头文件或Undefined symbol的问题。7. 从 llvm-project 开始我的一些实战体会文章写到这内容已经覆盖了 llvm-project 是什么、仓库结构、核心架构、从源码构建、llvmpipe 关联、学习路线这几个层面。最后分享一点我个人在这条路上摸索出来的心得。如果你问我要不要追新版本我的回答是不必急。LLVM 版本迭代非常快新特性大多跟 AI 编译、硬件加速、新语言支持相关对普通开发者来说把一个版本学好学透比追逐新版本更有价值。我现在自己还在用 LLVM 15/16 的某个子集做代码分析很多东西完全没有过时。还有一点学 LLVM 一定要动手写光看文档和博客是不够的。哪怕只是写个只有十行逻辑的 Pass也会逼你去理解 IR 的数据结构、Pass 的注册流程、怎么编译链接、怎么调试。等到你能自由地在 IR 上做出自己想要的分析和变换再回头读那些高大上的编译器论文会觉得顺畅得多。llvm-project 就像一个庞大的工具箱你不需要一下子把每个工具都用熟先拿一把改锥把最简单的零件拧上再慢慢扩展。希望这篇从实践出发的拆解能帮你找到那把改锥。