llvm-project 入门:核心结构解析与高效构建指南
2026/9/19 8:49:35 网站建设 项目流程

第一次拿到llvm-project这个仓库时,我一度有点恍惚:几百 MB 的源码压缩包、四五十个顶级目录、每个子目录都能单独开一场发布会,这就是那个传说中支撑起无数编程语言和芯片架构的编译器基础设施。很多刚接触 LLVM 生态的人都会问同一个问题:我到底该从哪一行代码开始看?

这篇文章我会从一个“踩过坑、重装过无数遍”的从业者视角,把llvm-project这个 monorepo 的内部结构、构建流程、常见坑位一次讲清楚。不管你是想做自定义编译器 pass、想给一种新语言写前端,还是只想搞清楚 Clang 和 LLVM 究竟是什么关系,这篇文章都能给你一个落地的参考路径。

1. 为什么 llvm-project 要把几十个项目打包进同一个仓库

llvm-project不是一个普通的开源项目仓库,它是 LLVM 官方把核心编译器、工具链、运行时库全塞进一个 Git 仓库后的“全家桶”。现在打开https://github.com/llvm/llvm-project,你能看到llvm/clang/lld/clang-tools-extra/等一串目录,这在十几年前并不是这样。

1.1 从 SVN 到 GitHub:一次“搬家式”的结构调整

早期 LLVM 使用的是 SVN 管理,而且核心的llvm和前端clang分属不同的仓库,各自维护各自的版本号,发布时要靠脚本去对齐。听起来是件小事,但对于每天都要跨项目改代码的开发者来说,这简直是折磨——你改了 LLVM IR 的一个接口,就得到另一个仓库里去同步修改 Clang 的调用点,改完还得找版本节点对齐。

2019 年 LLVM 团队把项目整体迁移到 GitHub,并正式采用 monorepo 结构。“monorepo”这个词听起来高大上,说白了就是所有子项目共用一个 Git 仓库、一份 issue 系统、一个 PR 流程。迁移之后,仓库的体积肉眼可见地膨胀,一个完整带历史信息的 clone 可能拉到几个 GB 的数据,但它换来的代码协作体验提升是非常明显的。

我印象最深的是,这次迁移解决了“原子提交”的问题。以前改一个 LLVM 接口可能要拆成两个项目分别提交,跨仓库的波纹处理非常痛苦;现在一个 commit 里可以同时看到llvm/clang/clang-tools-extra/里的配套变更,代码评审的人也能一眼看懂“你改了什么、为什么配套改这些”。

1.2 monorepo 方案的三个核心收益

如果你只是用户而不是维护者,可能觉得“仓库大不大跟我有什么关系”。但 monorepo 的设计对下游开发者其实也有非常实际的好处。

第一是版本一致性。整个工具链使用同一个 commit 号,你编译出的 Clang、LLVM 库、lld 链接器、libc++ 标准库天然就是同一时刻的代码状态,不会再出现“Clang 太新而 LLVM 库太旧”导致的灵异问题。

第二是跨项目改代码成本骤降。评测一个 pass 效果时,你常常需要同时修改 LLVM 核心、Clang 前端,甚至 lld 的链接逻辑。在单仓库里,这种改动可以在一次构建中完成,不需要到处维护分支。

第三是发布链路简化。LLVM 的发布流程基本就是打 tag、出 tar 包、跑测试。因为代码在一个仓库里,release/17.x这样的分支可以直接覆盖全部子项目,测试矩阵也更好管理。

当然,坏处同样肉眼可见:磁盘占用高、首次 clone 等待时间长、对只想用某个子模块的人来说有额外的学习成本。后面我会专门讲 clone 和构建时怎么把这些成本降到最低。

2. 目录结构:几十个子项目,先认清谁是谁

说实话,llvm-project刚 clone 下来时,很多人的第一反应是找“主程序”,结果发现没有任何一个目录叫“main.cpp”或“src”。整个仓库的顶层目录不是按模块分的,而是按“工具链的组成部分”分的。把每个目录的名字和定位搞清楚,后面看代码、写 pass、装环境都会顺很多。

2.1 链路的主心骨:LLVM 核心与 Clang

最核心的目录自然是llvm/。LLVM 这个名字本身就表示“Low Level Virtual Machine”,但今天你完全可以把“Virtual Machine”这个词忘掉,它就是一套编译器基础设施:包含中间表示(IR)的定义、优化 pass、指令选择、寄存器分配、目标代码生成等全套后端能力。平时大家说的“写一个 LLVM pass”,基本就是在这个目录下的lib/Transforms/llvm/lib/Passes/里做文章。

clang/则是 C/C++/Objective-C 的前端。它的任务是做词法分析、语法分析、语义分析,把源码变成 AST,再降级到 LLVM IR,交给llvm/去优化和生成目标机器码。很多人分不清 LLVM 和 Clang,可以这样记:Clang 是“翻译官”,把人类源代码变成 IR;LLVM 是“优化师+代码生成器”,负责把 IR 变成高效的机器码。两者在 monorepo 里各占一席,缺一不可。

clang-tools-extra/这个目录也值得一提,里面都是日常开发高频工具:clang-tidy(静态检查)、clang-format(代码格式化)、clangd(IDE 语言服务器)都在这里。早年间这些工具独立发版,后来一起搬进了 monorepo。

2.2 容易被忽略但天天在用:lld、compiler-rt、libc++、mlir

如果说llvm/clang/是聚光灯下的主角,下面这些目录就是幕后功臣,大部分工具链使用者每天都在用它们,却未必知道它们在llvm-project里。

lld/是 LLVM 生态的链接器。它支持 ELF、Mach-O、COFF 等多种格式,速度比传统 GNU ld 快出一个量级。我自己在大型 C++ 项目里实测过,链接耗时从几十秒降到几秒是很正常的体验。这也是为什么很多构建系统优先选择 lld 作为默认链接器。

compiler-rt/是 LLVM 的运行时库集合,涵盖了很多底层能力,比如 sanitizer 系列(ASan/LSan/UBSan)、profile 统计、内存内建函数实现。你在编译选项里加-fsanitize=address时,真正干活的代码就在这个目录里。

libc++/libc++abi/libunwind/组合起来就是一套完整的 C++ 标准库实现。想尝试在 Linux 上切换默认标准库,或者研究 C++ 标准库内部实现,这几个目录是绕不开的素材来源。

mlir/是近年最热门的子项目之一。它提供了一套可扩展的多级 IR 框架,很多 AI 编译器(包括各类深度学习加速器工具链)都基于它做算子调度和内存优化。国内很多芯片公司和互联网大厂的编译团队,招人 JD 里写的 “熟悉 MLIR” 指的就是这个目录。

至于flang/(Fortran 前端)、polly/(循环变换优化)、openmp/(OpenMP 运行时),属于特定场景才会深挖的方向。初次接触时,看一眼顶层目录名知道它们是干什么的就好,不必急于逐个精读。

3. 从 clone 到跑起来:完整构建流程

很多人在这一步倒下的原因不是代码难,而是构建姿势不对。这里我给出的是一套经过反复验证、适合 x86_64 Linux 环境的最小化流程。你不需要先通读任何文档,跟着做就能得到一个可运行的 Clang 和 LLVM 工具链。

3.1 动手前的三个准备:磁盘、依赖、心态

先说磁盘。构建llvm-project会吃大空间,我建议至少准备 60GB 可用磁盘,其中源码占 2~3GB,构建产物通常会有 20~40GB。如果你是全量 Debug 模式构建,那 100GB 都可能紧张。构建前先跑df -h看磁盘剩余空间,别等编译到一半才被报错打断,那感觉实在酸爽。

然后是系统依赖。以 Ubuntu 22.04 为例,构建前需要确保已经安装了这些包:

sudo apt update sudo apt install build-essential cmake ninja-build python3 \ libz-dev libncurses-dev libxml2-dev

如果你打算用 Clang 自身来编译 Clang(也就是自举构建),还需要先准备一个系统自带的 GCC 或 Clang 作为 bootstrap 编译器。CMake 版本建议 3.20 以上,Ninja 是强烈推荐的构建系统,比 Make 快且支持并行粒度更细。

最后是心态准备:不要第一次就追求全量构建。LLVM_TARGETS_TO_BUILD 如果默认全开(所有架构后端都编译),耗时和磁盘占用都会非常高。作为实验环境,构建两三个你关心的后端架构就足够了。

3.2 三条命令完成首次构建

第一步,拿到源码。为了节省时间和磁盘,做浅克隆:

git clone --depth=1 https://github.com/llvm/llvm-project.git

想用某个稳定版本,可以指定 branch,比如git clone --depth=1 -b llvmorg-17.0.1 https://github.com/llvm/llvm-project.git。浅克隆拿不到完整历史,但对大多数人来说根本不需要完整历史。

第二步,创建构建目录并进入:

cd llvm-project && mkdir build && cd build

第三步,用 CMake 生成构建配置。下面这组参数是我常用的“初版方案”:

cmake -G Ninja ../llvm \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_ENABLE_PROJECTS="clang;lld;clang-tools-extra" \ -DLLVM_TARGETS_TO_BUILD="X86" \ -DLLVM_ENABLE_ASSERTIONS=OFF

然后编译:

ninja -j4

这里的-j4表示并行任务数。机器核多内存足就调大到-j8-j16,内存紧张就保持 4。大约等待 30 分钟到 1 小时(取决于机器性能),编译完成后,build/bin/clangbuild/bin/llcbuild/bin/opt这些可执行文件就都出来了。

3.3 CMake 配置参数背后的权衡

构建参数不是随便填的,每一个都有它的取舍逻辑。

-DCMAKE_BUILD_TYPE=Release。这是最容易忽略但最关键的一项。默认的构建类型可能是 Debug,这种模式下整个 LLVM 没有做优化,编译速度极慢,运行也慢,且生成的二进制巨大。对于第一次跑通全流程的场景,Release 是绝对正确的选择。Debug 模式更多用于你想断点跟踪编译器自身逻辑的场景,那属于进阶玩法,需要更多内存和耐心。

-DLLVM_ENABLE_PROJECTS。这里用分号分隔你想额外构建的子项目。必选至少有clang,因为你最终要的是一个能编译 C/C++ 的工具链。lld强烈建议加上,构建它花不了多少时间,却能让后续链接阶段快非常多。clang-tools-extra则看你是否需要clang-tidyclangd,不需要可以不加,能省一些编译时间。

-DLLVM_TARGETS_TO_BUILD="X86"。很多人第一次构建时忽略这个参数,结果默认把 X86、ARM、AArch64、RISC-V、Mips、PowerPC 等十几套后端全部生成,编译时间直接翻几倍。我只写X86,是告诉 CMake 只需要为当前平台生成后端。这个参数起到的效果是“让构建集中在你真正用的架构上”,而不是“让所有架构都能被支持”。

-DLLVM_ENABLE_ASSERTIONS=OFF。关闭断言能大幅提高编译产物运行速度。但要注意,如果你后续打算自己开发 pass 或修改 LLVM 源码,断言其实是非常友好的“帮你及早发现错误”的机制。实验阶段我一般开着,正式跑性能基准测试再关掉。

-DLLVM_PARALLEL_LINK_JOBS这个参数在实际构建时也很有用。链接是最吃内存的环节,如果内存不太够但 CPU 核数很多,可以限制它,比如-DLLVM_PARALLEL_LINK_JOBS=2,避免多个大二进制同时链接直接 OOM。

4. 常见问题与排查技巧实录

任何高门槛的开源项目,九十步都容易卡在最后十步。构建llvm-project也同样,报错信息五花八门,但根因往往是几个固定的坑。下面这份速查表来自我自己几十次构建经验的浓缩。

4.1 高频事故速查表

问题表现常见根因解决办法
clone 到中途卡住或失败仓库体积大、网络波动使用--depth=1浅克隆;必要时切换镜像源
CMake 报错 “A compatible version of cmake is required”系统 CMake 太老apt 安装新版本,或用 pip 安装 cmake 到用户目录
编译早期大量 “cc1plus: out of memory”并行任务数过大、内存不够-j数值;同时设置LLVM_PARALLEL_LINK_JOBS=2
链接阶段 “nvalid memory pointer” 或 “undefined reference”不同编译器 ABI 冲突尽量使用同一套编译器构建;自举构建时保证 C/C++ 编译器一致
构建到 90% 时磁盘满build 目录巨大清理build/CMakeFiles缓存;为构建目录单独分配足量磁盘
运行clang --version提示 GLIBC 找不到构建机器比运行机器系统新在目标环境上重新构建,或用更老的系统镜像构建
opt加载自定义 pass 报错Pass is not registered没有使用新 PM 接口参考 4.2 改用 PassBuilder 注册方式

这里我想特别强调一下并行度和内存的关系。很多新手用ninja -j32在 16 核机器上构建,结果 32 个编译器同时启动,每个吃 1~2GB 内存,机器直接卡死或 OOM。把-j设成 CPU 核心数的一半或者保持默认,反而能稳定跑完。“只要编译快”的心态在这里不适用,稳定性优先。

另一个常见的隐藏问题,是你系统里可能存在多个 CMake 或 Ninja 版本,用了某些老版本时配置会产生诡异行为。建议构建前先跑cmake --versionninja --version确认版本,再决定要不要升级。

4.2 想改 Pass 做实验:看懂最小行动路径

很多人 clonellvm-project不是只是为了编译 Clang,而是想在 LLVM IR 上写一个自定义优化 pass 来实验。对于这类需求,我建议不要一开始就在llvm/源码树里新建文件,而是用动态库方式写一个独立的 pass 插件。这样修改后只需重新编译这个小插件,不用重编整个工程。

具体来说,新建一个目录,里面放一个非常简单的源码文件:

#include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; namespace { class HelloPass : public PassInfoMixin<HelloPass> { public: PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { errs() << "Hello from function: " << F.getName() << "\n"; return PreservedAnalyses::all(); } }; } // namespace llvm::PassPluginLibraryInfo getHelloPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "HelloPass", LLVM_VERSION_STRING, [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "hello-pass") { FPM.addPass(HelloPass()); return true; } return false; }); }}; } extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getHelloPassPluginInfo(); }

这是标准的 New PM 写法。编译这个插件时,需要让编译器找到 LLVM 头文件和库的路径。假设你已经构建完上面 3.2 节的工程,头文件在llvm-project/llvm/include,库在llvm-project/build/lib,那么命令大致长这样:

clang++ -shared -fPIC -std=c++17 hello_pass.cpp \ -I/path/to/llvm-project/llvm/include \ -I/path/to/llvm-project/build/include \ -L/path/to/llvm-project/build/lib \ -lLLVM-17 \ -o hello_pass.so

然后用编译好的opt加载它:

/path/to/llvm-project/build/bin/opt -load-pass-plugin=./hello_pass.so \ -passes="hello-pass" /path/to/some.ll -o /dev/null

如果一切正常,你会看到每个函数名都从终端打印出来。这就是一次完整的自定义 pass 开发闭环。后续想深入研究,可以把断点打到 PassBuilder 的回调里,或者用-print-after-all看优化每一步的变化。

最后再说两句挨过揍才明白的话

我个人在实际构建和使用llvm-project的过程中,最大的体会是:不要一上来就贪多贪全。第一次构建就想着 “把 RISC-V 后端也编进去”“顺便把 MLIR 也开了”,结果往往是构建时间翻倍、内存不够、报错一片。正常的学习曲线应该是先建立最小可用闭环,再一步步往里面加东西。

如果你真的想在这个领域长期深耕,我建议你专门找一块大硬盘,把完整历史和所有子模块都拉下来,没事翻一翻llvm/lib/Transforms/下面的代码,再对应着看clang/lib/CodeGen是怎么生成 IR 的。这比在网上看任何二手教程都来得直观、扎实。编译器这条路没有捷径,但llvm-project这个仓库本身,就是最好的那条路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询