LLVM:从虚拟机到编译器基础设施,一文读懂架构与生态

发布时间:2026/9/18 18:23:58
LLVM:从虚拟机到编译器基础设施,一文读懂架构与生态 只要你是写代码的肯定在各种工具链的角落里见过 LLVM 这个名字。但你可能和我最初一样有个困惑LLVM 全称 Low Level Virtual Machine听着像虚拟机怎么实际上干的全是编译器的活而且不光 C/C 用它Rust、Swift、Julia 这些语言也要靠它连 GPU 驱动、软件渲染器、SQL 数据库里都能看到它的影子。这里头其实有一条很有意思的演化线LLVM 诞生时确实用过“虚拟机”的概念可后来整个项目彻底转向编译器基础设施名字保留了下来内容早就不是原来的剧本了。这篇文章我想把这些年摸 llvm-project 的经验整理成一张“地图”。它的仓库里到底装了哪些子项目、版本号 15.0.7 这种补丁版本处在哪个历史坐标上、SIMD 优化里的 256 位到底指什么、Rust 和 llvmpipe 这些相差十万八千里的项目是怎么都挂在 LLVM 名下的以及最关键的——一个普通开发者从哪入手才能看懂这套庞大的代码库。全文会用工程师唠嗑的语气来写尽量不端着适合对编译原理有兴趣、但还没深入源码的读者。1. LLVM 是什么从“虚拟机”到编译器基础设施之王1.1 名字的由来以及它坑了多少人LLVM 项目始于 2000 年前后发起人是当时还在 UIUC 读博的 Chris Lattner。他早期的想法确实和“虚拟机”沾边设计一种可以在程序整个生命周期里反复使用的中间表示IR配合运行时进行持续优化。受 Java 虚拟机和动态编译思想的启发他给项目起名叫 Low Level Virtual Machine。但随着时间推移真正让 LLVM 赢得开发者认可的并不是某个具体的虚拟机实现而是那套“前端产出 IR、中端做优化、后端生成机器码”的编译器架构。于是虚拟机的定位逐渐被剥离名字却一直用到了今天随后演变为 llvm-project 这个巨型仓库。所以很多第一次接触的人都会犯同一个错误听到 LLVM 就想找它的 JVM 或者 V8 那种运行时。其实 LLVM 和这些东西完全是两条路线。它不执行代码它生成代码而且生成的是经过精心优化的本地机器码。可以把它想象成一家翻译公司英语、日语、德语之间互译需要六条路径但现在大家约定先翻译成一种万能中间文档再从中间文档翻译成目标语言两条路径就解决了所有组合。LLVM IR 就是那个万能中间文档Clang 是负责把 C/C 翻译成这份文档的前端后端则负责把文档再变成 x86、ARM、RISC-V 等不同的机器指令。1.2 三段式架构为什么能颠覆编译器行业在 LLVM 之前编译器的主流模式是 GCC 那种“整体式设计”。每种语言就是一个完整的前端、优化器、后端耦合在一起的编译器新增一门语言要从头写一整条工具链新增一个 CPU 架构也要在多个编译器里分别适配。语言和硬件的组合是乘积关系维护成本随着生态膨胀越来越不可控。LLVM 把这条路改成了三段式前端Frontend、中端Optimizer、后端Backend。前端负责把源代码翻译成 LLVM IR中端针对 IR 做无数遍优化pass后端再根据目标 CPU 把 IR 降级成机器码。这里最关键的是 IR 成了可复用的交换格式。前端不管后端有多少只需把语义准确翻译成 IR后端也不用了解 Swift 的 optional 或者 Rust 的所有权只需要把 IR 优化好后映射到指令集。前端和后端彻底解耦编译器从“一个黑盒二进制”变成了可以自由组合的零件。这种设计听起来简单做起来极其困难。LLVM IR 要足够底层才能承载 C、C、Rust、Swift 等各种语言的不同语义又要足够高层才能让优化器发现共同套路。它用 SSA静态单赋值形式表示所有变量只赋值一次配合显式的控制流图让数据流分析变得简单可靠。当前端把源码降级成这种 IR 后任何语言写的循环都能被同一套循环优化、向量化、内联、常量传播所覆盖这才是 LLVM 真正的护城河。1.3 llvm-project 仓库里到底装了什么东西很多人以为 llvm-project 只是一个编译器实际上它是一整套工具链生态。光看顶层目录就能吓一跳子项目一句话说明典型应用场景LLVM 核心库提供 IR、pass 优化框架、指令选择、寄存器分配、JIT 等基础能力想写编译器的任何项目ClangC/C/Objective-C 前端也是可复用的编译库替换 gcc重构工具、静态分析clang-tools-extra围绕 Clang 的小工具集合比如 clang-tidy、clang-format代码风格检查、自动化重构LLD高性能链接器比传统 GNU ld 快很多大规模 C/C 项目链接libc / libcabiC 标准库实现现代 C 项目运行时compiler-rt运行时支持库包括 sanitizer、profile、builtin内存检查、覆盖率、编译指令补全MLIR多层 IR 基础设施专为异构编译和 AI 编译器设计TensorFlow/PyTorch/IREE 等编译器FlangFortran 前端老科学计算代码迁移Polly基于多面体模型的循环优化高性能计算和自动并行化lldb调试器提供库化调试能力替代 gdb集成到 IDE平时大家口语里说“我装了 LLVM”多数时候装的就是这个仓库的一块或多块组合。官方发布时会统一打 tag比如 llvmorg-15.0.7指的就是某个时间点整个仓库的稳定快照。明白这一点你再看网上那些“LLVM 15.0.7 发布了”的消息就知道它不是单个工具的新版本而是整套编译器基础设置的一次整编。2. 版本演进与 LLVM 15.0.7 的坐标2.1 LLVM 的版本节奏每年一个大版本API 从不客气LLVM 的版本规划和很多开源项目不太一样。它保持每年一个主版本的节奏比如 14、15、16 一直到现在的 21时间点基本都在每年 9 月到 12 月之间。15.0.0 大约在 2022 年发布之后会有一串补丁版本15.0.1、15.0.2 一直到 15.0.7这类补丁版本集中修复前一个主版本周期内被发现的功能回退、崩溃问题和代码生成质量问题。更关键的是LLVM 官方在版本迭代时很少保证 API 兼容。你的第三方 pass 是用 LLVM 14 写的直接拿到 LLVM 17 下重新编译大概率会编译失败。这个策略和 GCC 那种“尽量保持稳定”的思路截然不同。对 LLVM 核心团队来说他们宁可牺牲向后兼容也要让内部接口保持演进速度。这就导致所有基于 LLVM 做二次开发的公司比如各种芯片厂商的工具链、编程语言项目、数据库 JIT 引擎都必须跟着上游节奏不断适配。从版本演进的大历史看有几个节点特别重要。早期 LLVM 3.x 稳定了 IR 和 pass 框架13 到 14 之间新 pass 管理器彻底上位14 到 15 之间 opaque pointer不透明指针逐渐成为默认17 左右彻底移除旧指针类型代码19 之后则迎来了 Orc JIT 的大规模重构和更灵活的 target 扩展机制。LLVM 15.0.7 正好处于“新 pass 管理器和 opaque pointer 全面切换后”的第一个稳定窗口对很多长期维护的系统项目来说是一个值得记录的坐标。2.2 为什么大家都在找 15.0.7 这个补丁版本主版本和补丁版本的价值差异只有被某个 bug 坑过的人才能真正理解。15.0.0 刚发布时编译器领域的热度都在新特性和新 API 上但真正在公司 CI 里跑上几天各种在特定 CPU 上生成错误代码、特定模式下链接失败、记录型 debug 信息错乱的问题就会浮出水面。从 15.0.1 到 15.0.7LLVM 团队逐个月修复这些回归15.0.7 是 15 系列的最后一个补丁版本。对普通用户而言如果你的项目因为某条老依赖链绑定在 LLVM 15 上千万不要停留在 15.0.0 或者 15.0.3直接上 15.0.7 能少踩很多莫名其妙的问题。而且很多预编译包、系统发行版、第三方工具链比如某些图形驱动的软件渲染模块也倾向于固定在一个补丁版本上做测试。你会在各种软件日志里看到“LLVM 15.0.7”这种字样它代表那个软件依赖的编译器组件是经过充分修整的稳定快照。2.3 日志里的“256 bits”到底是什么搜索词里出现的“llvmpipe (llvm 15.0.7, 256 bits”看起来像某个环境在启动软件渲染器时打印出来的信息。这里的 256 bits 通常指的是 SIMD单指令多数据向量宽度。现代 CPU 除了能一条指令处理一个数据还能用向量寄存器同时处理多个数据。x86 平台上的演进路线是SSE 提供了 128 位寄存器AVX 扩展到 256 位AVX2 进一步补齐整数向量指令AVX-512 则是 512 位寄存器加掩码等特性。256 位寄存器意味着一条指令可以同时操作 8 个 32 位整数、8 个 float、或者 4 个 double。对编译器来说它能否充分利用这一点直接决定了在图像处理、科学计算、音视频编码这些任务里的性能上限。llvmpipe 这类软件渲染器会在运行时检测 CPU 是否支持 AVX2支持的话就让 LLVM 生成使用 256 位 SIMD 路径的代码不支持就回退到 128 位 SSE 路径。别小看这 128 到 256 的差距像素填充和光栅化逻辑一旦向量化吞吐量往往能差出接近一倍。我这里稍微展开讲讲 LLVM 是怎么用上这些宽度的。你写一个简单的循环void add_vec(float *a, float *b, int n) { for (int i 0; i n; i) a[i] b[i]; }如果编译时开启-O2 -mavx2LLVM 的 LoopVectorizer 会分析这个循环是否有循环携带依赖是否适合向量化然后尝试把每次迭代处理一个 float 改成处理 8 个 float。生成的代码里会看到 ymm 寄存器和对应的向量加指令。编译器要做好数据对齐检查、循环尾处理还要在无法证明内存安全时生成运行时检查这背后就是几十个 pass 协同工作的结果。3. LLVM 如何改写编译器生态3.1 编程语言新贵们为什么首选 LLVM过去十几年里几乎所有新出的系统级编程语言都走了同一条路前端自己写后端交给 LLVM。Rust 的 rustc 编译器先把 Rust 源码转换成自己的 HIR 和 MIR再降级成 LLVM IR后续优化和代码生成直接复用 LLVMSwift 从诞生起就跟 LLVM 深度绑定甚至专门设计了 SIL 层来衔接 Swift 的高层语义和 LLVM IRJulia 这种动态语言更极端直接在运行时用 LLVM 的 JIT 把函数编译成机器码让动态脚本获得接近 C 的速度Kotlin/Native、Zig、Crystal 的前端也大多是围绕 LLVM 做对接。为什么大家不约而同选择 LLVM因为写一个高质量代码生成器的成本实在太高了。如果你想在一门新语言里实现对数十种 CPU 架构的支持同时还能拿到循环优化、向量化、内联、寄存器分配这些成熟能力自己从零写后端基本等于重新发明一遍编译器。用 LLVM 后一个几十人的语言团队可以把精力全部放在类型系统、语义分析、标准库这些真正有语言特色的部分编译产物质量直接继承 LLVM 十多年的工程沉淀。可以说没有 LLVMRust 和 Swift 想达到今天的成熟度至少得多花好几年。3.2 苹果、谷歌与整个系统级工具链的转向LLVM 对行业的改写不只是体现在小众语言圈。苹果从 Xcode 时代开始就逐步把默认编译器从 GCC 切换到 Clang/LLVM到了现代 macOS 和 iOS 上整个系统的构建都重度依赖 LLVM 工具链谷歌在 Android NDK 里也早已把默认编译器换成了 ClangFuchsia 的开发更是一步到位使用了大量 LLVM 组件Windows 之外的很多开源社区软件也越来越习惯用 Clang 而不是 GCC 来编译。可能在普通用户看不到的地方影响更大。GPU 编译器领域AMD 的 ROCm 编译栈、Intel 的 oneAPI 都大量采用 LLVM 技术路线RISC-V 芯片的软件生态起步时第一件事就是先把 LLVM 后端跑通很多商业数据库、数据分析引擎为了把 SQL 表达式编译成本地代码直接把 LLVM 当 JIT 引擎嵌在自己的执行引擎里。LLVM 已经从“编译器爱好者玩的库”变成了芯片厂商、互联网大厂、数据库公司共同押注的基础设施这种地位在开源历史上没有几个项目能比。3.3 图形学里的意外应用llvmpipe 软件渲染器聊到 llvmpipe其实很多人没意识到LLVM 能“编译”的东西远不止 C/C 和 Rust。llvmpipe 是 Mesa 项目里的一个纯软件实现图形渲染器在没有 GPU 的环境下也能跑 OpenGL 和部分 Vulkan。它的工作方式是把 GLSL/Vulkan shader 翻译成中间表示然后在运行时交给 LLVM 的 JIT 引擎动态生成针对当前 CPU 特性的机器码。你可以理解为它把“画三角形”和“算颜色”这件听起来很图形学的事变成了一次编译器 JIT 的过程。“llvmpipe (llvm 15.0.7, 256 bits”这行日志翻译成人话就是当前软件渲染器由 LLVM 15.0.7 驱动并使用 256 位 SIMD 路径做加速。它默认检测 CPU 对 AVX2 的支持情况支持就生成 ymm 寄存器的代码一条指令处理 8 个 32 位像素分量不支持就退回 128 位 SSE。为什么不能用 C 手写硬编码来实现这些 shader因为每个 shader 的状态、输入参数、输出格式千变万化手写模板覆盖不住所有组合。用 LLVM 的 JIT正好可以针对具体场景即时拼装机器码比解释器快好几个数量级又比预编译模板灵活得多。这个例子特别能说明 LLVM 的应用边界早就不在“编译器”这个标签下了。4. 从零看懂 llvm-project目录地图与动手实验4.1 先记住几个高频出现的目录直接扎进 llvm-project 的源码树会感觉像进了迷宫但核心模块其实就那么几个。顶层目录里最常打交道的是这几个llvm/ include/llvm/IR/ IR 定义Instruction、BasicBlock、Function、Module lib/Transforms/ 优化 pass 的实现 lib/Target/ 各种后端X86、AArch64、RISCV 等 lib/CodeGen/ 指令选择、寄存器分配、指令调度 tools/opt/ opt 命令行工具入口 clang/ lib/AST/ 抽象语法树的构建与操作 lib/Sema/ 语义分析 include/clang/ 给开发者的头文件 lld/ lib/ 链接器各阶段实现 mlir/ include/mlir/IR/ MLIR 的 IR 基类如果你只是想理解 LLVM 如何把源代码变成机器码最优阅读顺序是先看llvm/include/llvm/IR里的核心类定义再挑lib/Transforms/InstCombine这种典型 pass 读一读最后用opt和llc把实际代码过一遍看看优化前后 IR 的变化。刚开始不建议直接读后端代码那里头寄存器分配和指令调度的细节非常劝退。4.2 最容易上手的三个命令行实验没有比直接跑工具更适合理解 LLVM 的方式了。用 Clang 把 C 文件翻译成 IRclang -S -emit-llvm hello.c -o hello.ll生成的 .ll 文件是文本格式的 LLVM IR打开后能看到 define、load、store、call 这些指令。你可以看看一个简单的 main 函数经过优化后变成什么样再对比不同优化级别下 IR 的复杂度差异。如果想看控制流图用 opt 生成 dot 文件opt -passesdot-cfg hello.ll -o /dev/null这样会在目录下生成 .dot 文件再用 dot 命令转成 png 或者 pdf你就能非常直观地看到基本块之间的关系。还有一个办法能搞清楚某段 C 代码到底被哪些优化处理过clang -O2 -mllvm -debug-passArguments hello.c -c -o hello.o这串命令会把 -O2 对应的 pass 序列全打出来。看到那些 pass 名字以后再去源码里搜对应的实现比从头到尾通读源码高效得多。4.3 亲手写一个最小的 LLVM pass想真正入门 LLVM 开发绕不开写一个自定义 pass。下面是我在 LLVM 15 左右的版本上验证过的最小例子用新 pass 管理器插件的方式遍历所有函数里的指令并打印操作码#include llvm/IR/Function.h #include llvm/IR/Instructions.h #include llvm/Passes/PassBuilder.h #include llvm/Passes/PassPlugin.h #include llvm/Support/raw_ostream.h using namespace llvm; namespace { struct DemoPass : public PassInfoMixinDemoPass { PreservedAnalyses run(Function F, FunctionAnalysisManager ) { for (auto BB : F) { for (auto I : BB) { errs() opcode: I.getOpcodeName() \n; } } return PreservedAnalyses::all(); } }; } // namespace PassPluginLibraryInfo getPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, demo, LLVM_VERSION_STRING, [](PassBuilder PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager FPM, ArrayRefPassBuilder::PipelineElement) { if (Name demo) { FPM.addPass(DemoPass()); return true; } return false; }); }}; } extern C ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getPassPluginInfo(); }编译并加载的流程很简单先有插件动态库再用 opt 在 IR 上跑clang -fPIC -shared demo.cpp $(llvm-config --cxxflags --ldflags --libs) -o demo.so opt -load-pass-plugin ./demo.so -passesdemo hello.ll -o /dev/null第一次跑通这个例子的时候你大概会有一种“编译器也没那么神秘”的感觉。把代码改一改比如统计某个函数有多少条加法指令或者看看哪些函数没有调用任何外部函数这个小小的 pass 就是你进入 LLVM 内部世界的钥匙。4.4 给入门者的避坑建议我踩过的坑总结下来有下面几条。第一如果要开发 LLVM 插件或学习源码建议用 Debug Asserts 的构建方式。Release 版会把很多内部断言和调试工具关掉跑出来问题你也看不出来等到了机器码层面再排查就非常痛苦。构建命令大致是cmake -G Ninja -S llvm -B build -DCMAKE_BUILD_TYPEDebug -DLLVM_ENABLE_ASSERTIONSON ninja -C build第二千万别用系统全局安装的 libLLVM 来编插件。插件是用 opt 加载的要求插件链接的 LLVM 版本、API 宏跟 opt 完全一致混了就会报无法加载或者直接段错误。一定要用你自己编译出来的 llvm-config 去取值。第三网上很多教程还停留在旧版 pass 管理器Legacy PM的写法新版本默认用 New PM两者接口差非常多拷贝代码前先看清楚用的哪个版本。第四llvm-project 编译时间很长机器内存不够的话链接阶段容易爆尽量用 Ninja 并且控制并行任务数别把编译机器直接跑死。5. LLVM 对今后几年技术方向的影响5.1 多 CPU 架构时代一套 IR 通吃的价值过去十几年大家写代码默认面向 x86交叉编译是少数嵌入式工程师的事。但现在 ARM 服务器、RISC-V 开发板、各种专用 AI 芯片到处都是一个产品要同时支持 x86、ARM、RISC-V 成了常事。在这种多架构并存的背景下LLVM“前端-中端-后端”解耦的优势被放大到极致。因为 IR 是统一的语言团队只需对接 LLVM就能一键支持所有后端芯片厂商也可以只专注于实现 target 描述和指令选择不用重新发明一套完整的编译器前端。对开发者的直接影响是同一个 Rust 项目、同一个 C 工程用 clang 编译时加个--target参数就能交叉编译到另一个架构优化能力还保持一致。我没有夸张这套体验在十年前几乎不可想象LLVM 把“同一套工具链通吃所有硬件”变成了行业默认配置。5.2 MLIR 与 AI 编译器的爆发如果只能挑一个 LLVM 项目对未来的影响我会投 MLIR。AI 芯片这几年碎片化严重GPU、NPU、TPU、FPGA 各有各的指令集和内存层次传统编译器很难在这么高的抽象层级上做统一优化。MLIR 的思路是在 LLVM IR 之上再架一层“多层 IR 基础设施”让你可以按领域定义自己的中间表示从计算图级别一直降级到循环级别最后再接到 LLVM 后端。TensorFlow、PyTorch 的编译栈、IREE、各种大模型推理引擎几乎都在用 MLIR 搭建自己的编译管道。它让“为某个硬件写编译器”从一项博士级工作变成可以复用组件的工程任务。以后你看到某个新芯片说支持 PyTorch 模型部署背后很可能就是一条 MLIR 管道。这等于把 LLVM 的“三段式”思想再次延伸到 AI 领域编译器基础设施这个概念的范围又被撑大了一圈。5.3 软件渲染不再是“兜底方案”llvmpipe 这类软件渲染器的价值过去通常被理解为“没有 GPU 时凑合用”。但在云桌面、无头渲染、CI 测试、虚拟化环境这些场景里软件渲染往往是刚需。云上 CPU 资源便宜几十个核并行跑 CPU 渲染配合 LLVM JIT 动态生成的 SIMD 代码在很多工作负载上已经接近中低端 GPU 的效果。关键是 LLVM 的 JIT 能力给了软件渲染器一个非常难得的特性针对性。它在运行时才知道 CPU 具体支持哪些指令集才能为特定 shader 生成高度特化的代码。这种“你是什么 CPU我就给你生成什么代码”的动态优化是预编译二进制做不到的。我判断未来几年随着 AI 推理的 CPU 端加速、云游戏和虚拟桌面持续加码llvmpipe 这种“LLVM 驱动的软件侧门”还会出现在更多意想不到的地方。最后分享一个我自己的体会llvm-project 最让人敬畏的地方不是代码量大而是每段代码背后都压着几十年的工程决策。真正让我入门的是拿优化 pass 开刀写了一个打印指令的小插件然后对着-O2打出来的 pass 序列挨个去读源码。建议新手别一上来就啃 IR Builder 那套复杂 API先跑测试、再改几十行的 pass、再试着提交一个修文档或者补测试用例的小 pull request整个过程不需要你什么都会。你只需要在某个小角落先钻进去剩下的地图会随着时间慢慢打开。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询