老实说,第一次把llvm-project这个仓库 clone 下来的时候,大多数人都会被它的体积和复杂度吓一跳。这不是一个普通的开源项目,它是一整套编译器基础设施,涵盖了从底层 IR(中间表示)到目标代码生成、从 C/C++ 前端到链接器、从调试器到标准库实现的完整链路。很多人一开始只想搞懂“LLVM 到底是什么”,结果一头扎进去,几个月都出不来。这篇内容我就以自己的实战经验为主线,聊聊 llvm-project 的核心结构、源码怎么读、环境怎么搭、以及如何在这个项目上动手写点真正有用的东西。
这篇内容适合以下几类读者:刚接触编译原理、想在 LLVM 基础上做二次开发的研究生或工程师,工作中需要定制编译器工具链的底层开发者,以及想通过阅读 LLVM 源码提升自身内功、但苦于没有系统路线图的自学者。我会尽量把那些文档里不会细讲的“坑”和“捷径”都说清楚,希望能帮你少走弯路。
1. 项目整体认知:LLVM 不是一个编译器,而是一整套编译器工厂
在深入代码之前,有一件事必须先把观念掰过来:llvm-project并不是一个“编译器”,而是一套用来构建编译器的工具箱。你可以把它理解成一个“编译器工厂”——里面存放着各种标准化零件(IR 优化框架、目标描述语言、代码生成器、链接器、标准库模块等),你自己选择搭配,最终拼装出一款面向特定语言、特定硬件平台的编译器。
1.1 核心模块到底有哪些
目前官方仓库里主要包含这么几大块:
- LLVM 核心库:这是整个项目的心脏,包含 IR 的定义与操作、优化 Pass 框架、目标无关代码生成器、后端支持(X86、ARM、RISC-V、AArch64 等)、MC 层(机器码相关)、JIT 相关组件。
- Clang:C/C++/Objective-C 前端。它负责把源码解析成 AST,再降级成 LLVM IR。实际工作中我们经常把“Clang”和“LLVM”混着叫——严格说,Clang 只是 LLVM 生态里最知名的一个前端。
- LLD:一个高性能的原生链接器,在构建 LLVM 自身时经常用来替代系统默认的
ld,速度提升非常明显。 - libc++ / libc++abi:C++ 标准库和 ABI 层的实现。如果你在 macOS 或某些嵌入式环境上用 Clang 作为默认编译器,这两个模块就是幕后功臣。
- MLIR:一个用于构建可复用、可扩展编译器基础设施的多层 IR 框架。最近几年 AI 编译器领域特别火,很多芯片厂商的编译器工具链都是基于 MLIR 做的。
- LLDB:基于 LLVM 技术栈的调试器。
- compiler-rt:提供编译器运行时库,比如
asan(AddressSanitizer)、ubsan(UndefinedBehaviorSanitizer)等。 - OpenMP、polly、flang、libclc:分别对应 OpenMP 运行时、多面体优化、Fortran 前端和 OpenCL 库。
有一点要明确:git 仓库里的代码是“全家桶”形态,但实际使用时完全可以按需挑选。通过 CMake 的LLVM_ENABLE_PROJECTS选项,你可以只构建 Clang 和 LLD,或用LLVM_ENABLE_RUNTIMES只编译 libc++。
1.2 为什么 LLVM 能成为行业事实标准
原因归根到底在于它的 IR 设计:LLVM IR 是一种有静态单赋值(SSA)形式的、与具体语言和具体硬件解耦的中间表示。正因为解耦,前端和后端都可以独立演进——你写好一个基于 LLVM 的后端,就能自动支持所有接入了 LLVM 的前端语言。反过来,你开发一个新的前端语言,只要输出合法 LLVM IR,就能免费获得大量成熟的优化和数十个硬件后端支持。
这就解释了为什么 GPU 厂商、AI 芯片公司、甚至 FPGA 工具链都在基于 LLVM 做定制开发。它直接把“造编译器”的门槛从“从零到一”降到了“做加减法”,让你只需要专注自己最擅长的部分,其余的交给生态。理解了这一层,你再看这个项目庞大的代码量就不会觉得劝退,反而会心生敬畏——毕竟你是在和过去二十年世界顶级编译器工程师的集体智慧打交道。
2. 环境准备与源码构建:从 Clone 到跑通 Clang 的完整流程
获取和构建 LLVM 项目本身是接触这个项目的必备功课。很多新手在这里就卡住了:源码体量大、CMake 参数复杂、链接阶段的资源占用能直接把内存吃满。下面我按实际操作的顺序和你过一遍,并把我踩过的坑重点标出来。
2.1 拉取源码与版本选择
从 GitHub 上拉取源码的方式非常简单,但这里有一个关键决策:选择哪个版本。
git clone --depth 1 --branch llvmorg-15.0.7 https://github.com/llvm/llvm-project.git cd llvm-project以 LLVM 15.0.7 为例。为什么建议用 release 版本而不是直接拉主分支?如果你是第一次接触这个项目,主分支每天都在变,API 今天能编译明天就废弃,而你查到的技术博客和论坛回答大概率基于某个 release 版本,代码动不动就不是最新状态,排查成本会非常大。Release 版本稳定、资料多、生态成熟,适合作为切入点。
如果你只是学习而非对外发布工具链,浅克隆(--depth 1)就够了,可以省下大量下载时间。注意,如果你之后想跑git log查看历史提交信息,浅克隆会受限,可以后续按需git fetch --unshallow。
2.2 CMake 配置解析:这些参数到底在干什么
LLVM 用的是 CMake 构建系统,但直接cmake ..十有八九会失败或编译出残废版本。我经历过无数次失败后,总结出一套稳妥的配置:
mkdir build && cd build cmake ../llvm \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_ENABLE_PROJECTS="clang;lld" \ -DLLVM_TARGETS_TO_BUILD="X86;AArch64" \ -DLLVM_CCACHE_BUILD=ON \ -DLLVM_OPTIMIZED_TABLEGEN=ON \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -G Ninja逐个解释这些参数背后的逻辑:
CMAKE_BUILD_TYPE=Release:使用-O2优化编译自身的代码。如果你的机器内存足够,强烈建议 Release;Debug 版本的 LLVM 编译慢、链接内存占用高,但调试自己写的 Pass 会更友好。折中方案是RelWithDebInfo,带调试信息且优化的构建,日常开发我一般用这个。LLVM_ENABLE_PROJECTS="clang;lld":因为我要做编译器栈相关的开发,Clang 和 LLD 是刚需。如果你只研究优化 Pass,clang都可以不启用,构建时间能大幅缩短。LLVM_TARGETS_TO_BUILD="X86;AArch64":限制只生成 X86 和 ARM 的目标后端,避免把几百个后端都编译一遍。这个参数直接决定你只需要一种架构,就可以大幅缩减编译时间。LLVM_CCACHE_BUILD=ON:启用 ccache 编译缓存,反复修改源码重编时的提速效果极其明显。第一次构建会慢一点(缓存未命中),第二次起就是质的飞跃。LLVM_OPTIMIZED_TABLEGEN=ON:用优化后的 TableGen 工具生成代码,能显著缓解 TableGen 执行慢的问题,对调试 TableGen 定义文件尤其重要。
如果你是在 Linux 上构建且内存低于 16GB,我个人还建议加入-DLLVM_PARALLEL_LINK_JOBS=2来限制并行链接任务数。否则 LLVM 链接时会一下子起很多进程,内存瞬间爆掉,直接 OOM。
2.3 编译过程中的三个大坑
第一个坑:链接内存不足。这是最经典的问题。LLVM 自带的几个核心库非常庞大,链接阶段非常吃内存。我曾在 8GB 内存的机器上试过,链接libLLVM.so时直接触发 OOM 被内核杀死。除了上面提到的限制链接并发数,还可以切换链接器。用 lld 替代系统默认的 BFD ld 或者 GNU gold,内存和速度双双优化:
-DLLVM_ENABLE_LLD=ON如果你是用 Clang 编译,加这个参数配合 LLD,整个构建过程会顺畅很多。
第二个坑:磁盘空间不够。Release 构建 + Clang + LLD 完整编下来,磁盘占用轻松突破 30GB,加上源码和缓存,建议至少留出 50GB。如果空间紧张,可以不用 ccache,并且只构建必要的目标后端。
第三个坑:系统 GCC 环境不兼容。明明是 Ubuntu 20.04 之前的系统,用的 GCC 版本过老,编译 LLVM 新版时经常报一些 C++ 标准库相关的晦涩错误。解决办法很简单:装一个版本合适的 Clang 当编译器来编 LLVM,或者升级 GCC。我一般喜欢用clang + lld + ccache这个组合,实测下来稳。
构建完成后验证一下:
./bin/clang --version ./bin/llvm-as --version能正常输出版本信息,说明你的基础环境已经跑通了。
3. 核心源码结构与源码导航:读懂 LLVM 项目的高效路线
很多人拿到仓库后第一反应是想通读源码。坦白讲,通读是不可能的,也没必要。更高效的方式是按图索骥,搞清楚每个目录负责什么,然后针对你的问题定点深入。
3.1 顶层目录到底谁是谁
在llvm-project/llvm里按目录做一次快速地图梳理:
include/llvm和lib/:核心代码区,里面按功能又分了IR(IR 的定义与基本操作)、Passes(Pass 管理器与新 Pass 的注册)、Transforms(各类优化 Pass)、CodeGen(目标代码生成)、Target(各硬件平台的后端描述)、MC(机器码层)、Analysis(分析框架)等。tools/:各可执行工具,比如opt(优化 Pass 的试验场)、llc(把 IR 降级为汇编)、llvm-as(文本 IR 转 bitcode)、llvm-dis(bitcode 转文本 IR)等。unittests和test:单测与端到端测试,采用lit+FileCheck框架。你写的 Pass 如果要做完整测试,这两个目录就是你的归宿。utils/:辅助脚本,比如在git commit时自动运行clang-format的 hook。cmake/:构建相关的 CMake 模块。
在llvm-project/clang下,include/clang和lib/同理,包含AST(抽象语法树)、Sema(语义分析)、CodeGen(从前端 IR 生成 LLVM IR)、Driver(命令行驱动)等模块。首次接触不需要全部看完,抓住主干即可。
3.2 LLVM IR:整个项目的中枢神经
LLVM IR 的文本表示很容易上手,尝试用下面命令看看一个简单 C 函数到底变成了什么:
// test.c int add(int a, int b) { return a + b; }clang -S -emit-llvm test.c -o test.ll cat test.ll你会发现原来带类型的变量经过 IR 中间表示后,变成了无类型的虚拟寄存器(%0、%1),操作变成了类似add i32 %0, %1这样的三地址码形式。指令上能看到nsw这种 no-signed-wrap 的标志位,这是给优化 Pass 的提示——不要产生带符号整数溢出的行为。
理解和熟悉 IR 是后续写 Pass 的前提,相当于你要学会一门“中间语言”。如果一时觉得抽象,可以想象一台虚拟机,LLVM IR 就是这台虚拟机的汇编语言,但这种汇编语言是为优化和移植而设计的。
3.3 TableGen:写编译器也能生成代码
TableGen是 LLVM 里一个非常独特的子系统。如果你想给 LLVM 添加一个新的 CPU 指令或者定义一个新的目标特性,你不应该手写 C++ 代码,而是定义.td文件。然后用llvm-tblgen生成对应的 C++ 头文件、枚举、匹配器等。
一个最小的.td片段下面这样:
def HasAVX2 : SubtargetFeature<"avx2", "HasAVX2", "true", "Enable AVX2 instructions">;这段代码看似简单,但它生成的不仅仅是字符串常量,还会联动指令选择表、代码生成匹配器、汇编器/反汇编器等大量 C++ 代码。TableGen 的核心理念就是“声明式描述,自动生成实现”,让你不用维护几千行重复代码。这是 LLVM 可以轻松支持几十个后端架构的秘密武器之一。
3.4 一个值得关注的实际案例:llvmpipe 与 256 位向量
很多人知道llvmpipe,它属于 Mesa 3D 项目里的软件渲染器。但你没意识到的是,它内部大量使用 LLVM 作为 JIT 引擎:把图形着色器编译成当前 CPU 能够执行的高性能机器码。你过来时带的热搜里出现llvmpipe (llvm 15.0.7, 256 bits),就是在说它用 LLVM 后端把着色器转成了支持 AVX2 的 256 位 SIMD 指令。
这很能说明 LLVM 的价值:它不仅存在于离线编译工具链,还可以作为运行时 JIT 编译引擎嵌在图形栈、数据库(比如一些现代 OLAP 引擎做表达式 JIT)和 AI 推理引擎里。LLVM 的MCJIT、ORC和ORCv2模块就是专门为这类场景设计的。你在llvm-project里逛久了你就会发现,它绝不是一个“上古时代的编译代码库”,而是一个永不过时的编译器基础设施。
4. 动手实践:用 LLVM Pass 框架定制自己的编译优化
纸上得来终觉浅。我的建议是第一次动手,不要想那些宏大的目标,就写一个最简单的 Function Pass,然后通过opt在 IR 上运行它。这个过程能让你对 LLVM 的编译流程、IR 结构与 Pass 生命周期有一个立体认知。
4.1 环境准备与工程骨架
最简单的实验可以不新建独立项目,直接在 LLVM 源码树里加一个 Pass,但那样污染代码树,不利于版本管理。更推荐的方式是做一个独立的 out-of-tree Pass 插件。这里用新 Pass 管理器的PassPlugin方式演示。
创建如下目录结构:
demo-pass/ ├── CMakeLists.txt └── DemoPass.cppCMakeLists.txt内容如下:
cmake_minimum_required(VERSION 3.20) project(DemoPass) find_package(LLVM REQUIRED CONFIG) message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") include_directories(${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND "${LLVM_DEFINITIONS}") add_library(DemoPass MODULE DemoPass.cpp) target_link_libraries(DemoPass PRIVATE LLVM) target_compile_definitions(DemoPass PRIVATE ${LLVM_DEFINITIONS_LIST})这文件里有几个关键点:find_package(LLVM REQUIRED CONFIG)需要你的 LLVM 安装路径能被 CMake 找到;MODULE表示生成动态库,也就是插件形态;target_link_libraries(DemoPass PRIVATE LLVM)会链接整个 LLVM 库。
你可以先用上面那个 build 目录里的 LLVM 作为依赖,我这里把构建好的 LLVM 路径赋值给环境变量,后面编译插件直接用它。
4.2 一个统计函数指令数的 Pass
写一个最简单的 Pass,作用是在每次函数 visit 时统计该函数基本块和指令数量,并用errs()打印出来。
#include "llvm/IR/Function.h" #include "llvm/IR/Instructions.h" #include "llvm/Pass.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; namespace { class DemoPass : public PassInfoMixin<DemoPass> { public: PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { unsigned instCount = 0; for (BasicBlock &BB : F) { for (Instruction &I : BB) { instCount++; } } errs() << "Function " << F.getName() << " has " << instCount << " instructions\n"; return PreservedAnalyses::all(); } }; } // namespace几处容易产生疑问的点说明一下:
PassInfoMixin<DemoPass>是新 Pass 管理器(NewPM)的基类,它让 Pass 能被自动注册、序列化并复合进 Pass 管线。run返回PreservedAnalyses,表示这个 Pass 哪些分析结果被保持了。如果你没有修改任何 IR,就返回PreservedAnalyses::all(),告诉框架所有分析缓存都有效——这是优化性能的重要细节;如果修改了 IR,你就要想清楚哪些分析被破坏了。F.getName()拿到的是函数名,包括重载修饰后的形式,如果函数没有名字(比如匿名函数),getName()返回空字符串,此时可以用F.hasName()来判断。
4.3 新旧 Pass 管理器的注册差异
很多老教程里用的是旧的legacy::FunctionPass和RegisterPass<DemoPass> X("demo-pass", "...")。新 Pass 管理器的注册方式完全不同,我们需要导出插件入口函数:
extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, "DemoPass", LLVM_VERSION_STRING, [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "demo-pass") { FPM.addPass(DemoPass()); return true; } return false; }); }}; }这里有几点需要特别注意:
LLVM_ATTRIBUTE_WEAK是为了防止插件被同时静态链接进主程序时出现重复符号定义。- 注册回调
registerPipelineParsingCallback让opt可以通过-passes=demo-pass识别你的 Pass。如果注册失败或者 Pass 名字对不上,opt 就会报错“unknown pass name”。 - 如果你还想让这个 Pass 默认加进
-O2的标准管线,也可以注册registerPipelineStartEPCallback或registerOptimizerLastEPCallback,但第一次先把基本跑通就行。
在CMakeLists.txt旁边,写好源文件后,用如下方式编译插件:
export LLVM_DIR=/path/to/llvm-project/build/lib/cmake/llvm cmake -S . -B build -DLLVM_DIR=$LLVM_DIR cmake --build build编译成功会生成build/DemoPass.so或build/libDemoPass.so。
4.4 用 opt 和 clang 实际验证效果
先准备一个 C 文件:
// sample.c int add(int a, int b) { return a + b; } int main() { return add(1, 2); }然后用 clang 生成 IR:
clang -S -emit-llvm sample.c -o sample.ll用你编译好的插件跑一下:
opt -load-pass-plugin=./build/DemoPass.so -passes=demo-pass sample.ll -S你会看到输出里面穿插着类似:
Function add has 3 instructions Function main has 5 instructions如果你没有用-load-pass-plugin加载插件,而直接用-passes=demo-pass,就会报错。这个问题我遇到过很多次,所以建议先在命令行上确认opt -load-pass-plugin=./build/DemoPass.so -passes=demo-pass sample.ll能正常通过再往下做。
5. 常见问题与排查技巧:我踩过的那些坑和解决办法
写 Pass 和构建 LLVM 的过程绝非一帆风顺。我把高频问题整理成一份速查表,下面每一条都是实际踩过的。
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
opt加载插件后报unknown pass name 'demo-pass' | 插件注册 PipelineParsingCallback 名字不匹配,或插件没有正确导出llvmGetPassPluginInfo | 检查回调里比较的字符串,用opt -load-pass-plugin=... -passes=help查看已注册 Pass;用nm -D查看导出符号 |
插件编译报一堆undefined reference to ... | 没有链接 LLVM 库,或 LLVM 库版本和头文件版本不一致 | 检查target_link_libraries(DemoPass PRIVATE LLVM),确认LLVM_DIR指向的构建树和你用的 clang/opt 相同 |
llvm-config找不到 | 环境变量没配好 | 构建完 LLVM 后,将build/bin加入PATH,build/lib/cmake/llvm写入LLVM_DIR |
LLVM_ENABLE_PROJECTS里加clang后构建失败 | 项目间的依赖顺序或内存不足 | 建议先只构建clang;lld;控制并行任务数;使用-DLLVM_PARALLEL_LINK_JOBS=2 |
| Pass 运行后没有任何输出 | Pass 没执行或函数被优化掉了 | 确保-passes=demo-pass在正确优化层级之前运行;给函数加__attribute__((noinline))或降低优化级别 |
修改 IR 后返回PreservedAnalyses::all()导致后续优化误判 | 没有正确报告分析被破坏 | 如果修改了 CFG 或删除了指令,应该返回PreservedAnalyses::none()或具体说明哪些分析被破坏 |
5.1 链接内存不足的终极解法
链接阶段是资源杀手。在构建所有库的过程中,最“凶残”的场景是生成libLLVM-15.so这个共享库。如果你内存不足,可以放弃共享库构建,改为静态链接一切:
-DLLVM_BUILD_LLVM_DYLIB=OFF -DLLVM_LINK_LLVM_DYLIB=OFF这个配置会减少链接时峰值内存,但会增加每个二进制的大小。如果你只是做开发而不是发布一个完整的工具链,这样反而更省事。
5.2 调试 LLVM 问题的几个姿势
翻看 LLVM 源码自带的 Log 输出非常有价值。很多 Pass 本身支持 debug 输出,编译 LLVM 时加上:
-DLLVM_ENABLE_ASSERTIONS=ON -DCMAKE_BUILD_TYPE=Debug构建 Debug 版本后,使用:
opt -debug-only=pass-name ...可以输出极详细的内部调试信息。注意它要求-debug-only=...这个参数在opt之后,且插件也需要是 Debug 版本,否则字符串匹配可能失效。
如果你要跟踪一个 Pass 对 IR 的影响,最朴素但有效的手段是在 Pass 里打印 IR 前后快照:
errs() << "Before: " << F << "\n"; // do something errs() << "After: " << F << "\n";5.3 区分“你的 Pass 的问题”和“LLVM 的问题”
遇到异常行为,首先要确认 LLVM 本身的正确性不受影响。最简单的办法是:先用不带插件的opt对同样的 IR 跑一遍标准-O2,确认输出正常,再叠加自己的 Pass。如果加了 Pass 才出问题,那大概率就是你 Pass 的 IR 修改逻辑有误,这时要仔细检查 basic block 和 instruction 的迭代器是否在遍历中失效了。C++ 中向Instruction列表插入/删除指令会导致迭代器失效,这在 LLVM 中同样是高频地雷。
6. 学习路线与后续扩展:从能跑通到真正玩明白
写到这里,你已经在 LLVM 上跑通了自己的第一个 Pass,这就算正式入门了。接下来的路怎么走,我结合自己的经验给出几条建议。
6.1 从使用者到贡献者
LLVM 社区的代码评审文化非常强调“small patch”。第一次贡献不建议一上来就提交一个能进入标准优化管线的巨型 Pass,哪怕功能完整也可能因为缺少测试、缺少文档或格式问题被打回。可以从test/目录下的lit测试用例写起,或者修一个文档注释、处理一个简单 TODO。你会在评审过程中学到大量编码风格和设计取舍,这远比你自己闷头写代码收获大。
同时,善用llvm-dev邮件列表和 Discourse 论坛。这里提问时记住一个原则:同时附上尽量小的复现用例和你的 LLVM 版本信息,这个问题基本就成功了一半。技术圈很少有人愿意回答一句“我的 Pass 不工作”的提问,但几乎所有人都愿意讨论一段可复现的 IR 为什么会变坏。
6.2 我个人最推荐的学习路径
如果你的目标是成为编译器领域的专业开发者,我推荐按这个顺序进阶:
- 吃透 LLVM IR 的语义:读官方文档
LLVM Language Reference Manual,把add、load、store、br、phi等指令搞明白。可以做练习:手写 IR,然后用lli解释执行。 - 理解编译流程:一条 C 代码是如何一步步变成汇编的,深入研究 Clang 的前端到 IR 的降级过程、LLVM IR 的各种优化 Pass 分别做了什么。
- 选一个目标架构做后端:挑一个简单的后端,比如旧版 X86 或 RISC-V,跟一遍
SelectionDAG的指令选择流程。这块是最难啃的,但啃下来之后你对整个编译器生态的理解会完全不同。 - 研究 MLIR:如果你对 AI 编译器、DSL 编译器感兴趣,MLIR 几乎是绕不开的核心。它把 IR 分成多层抽象,极大简化了领域专用优化的开发流程。
- 关注 JIT 与运行时:如果你对数据库向量化执行、动态语言 JIT 感兴趣,那么
ORC相关接口是最好的学习材料,包括llvm-jitlink工具和Kaleidoscope教程。
我遇到过一个很有意思的项目:用 LLVM 的ORC在查询执行引擎里做表达式 JIT,把原本逐行解释执行的 SQL 表达式改用 JIT 编译成机器码,性能提升好几个量级。这类玩法恰恰是 llvm-project 能支撑的典型现代应用场景。
6.3 一个可以继续扩展的方向:自定义 IR 分析与优化
写 Pass 不局限于某个简单的打印统计。你可以尝试写一个真正的分析 Pass,比如统计循环层级、分析函数调用关系并输出调用图,或者做一个“把某个函数内所有add转换为sub”的 IR 改写 Pass。再进阶一点,尝试做跨函数分析,使用ModuleAnalysisManager并在多个函数之间共享分析结果。这会迫使你去了解AnalysisManager的缓存机制和分析依赖模型。
再往后,你可以研究一下 Polly 或 MLIR 的 Affine 框架,在多面体模型上做循环变换。这条路虽然在工业上落地成本较高,但学术价值和应用前景都可观,很多编译器优化论文里的实验就是在这个框架下做的。
7. 最后再说几句心里话
我在 llvm-project 上投入的时间越多,越觉得它是一个被严重低估的“宝藏仓库”。很多人一看到 C++ 模板和巨型 CMake 配置就畏难而退,但实际上只要找到正确的抓手,它的学习曲线完全可以被拉缓。从构建环境开始,到读懂 IR,再到写自己的第一个 Pass,每一步都会带来实实在在的成就感。这个过程也会让你对编译器、对程序语言背后发生的事情有完全不同的理解,而这种理解,在工程实践中是很大的加分项。
如果你想开始,建议今天就下决心把源码拉下来、把构建跑通,哪怕只编译llc并用它生成一句汇编,也算迈出了最难的一步。后面的事情,会越来越顺。