ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LLVM编译器基础设施全解析:架构、IR与源码构建实战

LLVM编译器基础设施全解析:架构、IR与源码构建实战 我最早接触llvm-project这个仓库是被 GitHub 上那个常年霸榜的 star 数震住了。当时心想一个编译器项目怎么比好多明星 Web 框架还火后来自己在 Clang、优化器、甚至图形渲染这几个方向都踩过一遍才慢慢理解它为什么能吸引这么多人——它压根不是一个“编译器”那么简单而是一套完整的编译器基础设施是今天无数编程语言、GPU 栈、芯片工具链的地基。这篇文章我就把自己折腾llvm-project的完整经验整理出来从一个“会用编译器的开发者”视角带你拆开它的架构、搞懂它的核心机制再看清楚 llvmpipe 这类依赖 LLVM 的软件栈到底在做什么。文章最后会附上从源码构建llvm-project的实操记录和踩坑清单适合任何打算真正用起来、或者想深入源码的人。1. 先搞清楚llvm-project 到底是个什么项目1.1 一个 GitHub 仓库装着半套编译器生态很多人第一次打开llvm-project仓库会被里面的目录结构搞懵。它不是一个单一体项目而是把 LLVM 生态里多个核心组件统一放在一个 monorepo 里管理。其中最重要的几个llvm目录是基础设施本体包含优化器、代码生成器、所有后端clang是 C/C/Objective-C 前端lld是一个链接器libc是 C 标准库实现compiler-rt是运行时库还有mlir、flang、polly这些针对特定场景的子项目。我第一次看到这么多东西放一起本能反应是“组个团就想叫全家桶”但后来真去按文档编译才懂为什么必须放一起。因为 LLVM 的前端、优化层、后端不是三个独立程序而是共享同一套核心库和数据结构。Clang 和优化器之间靠 LLVM IR 通信LLD 和 Clang 又要共用.a和.o的格式处理代码。分开管理版本容易不一致monorepo 同步变更成本最低。所以现在苹果、Google、ARM、Qualcomm 这些重度参与方都默认在 monorepo 上协作。从使用角度看它的核心价值可以概括成一句话你想做一门新语言那不用从零写编译器。你只需要写一个能产出 LLVM IR 的前端后面优化和生成机器码的活全交给 LLVM。你想给芯片做工具链你只要为这个芯片写一个后端所有上游语言都能通过 LLVM 直接编译到你新芯片上。这也是为什么 Rust、Swift、Julia 都选择 LLVM而国内做 RISC-V 工具链的团队也基本绕不开llvm-project。1.2 从“虚拟机”到编译器基础设施一个名字引发的误解LLVM 全称是 Low Level Virtual Machine但你要是按“虚拟机”去理解它会走很大弯路。它其实没有传统意义的虚拟机运行时就算后来有 ExecutionEngine 这类 JIT 能力那也是给编译器做动态编译用的跟 JVM 那种跑字节码的虚拟机是两码事。这个误会很有意思。LLVM 是 2000 年 Chris Lattner 在伊利诺伊大学香槟分校的博士课题当时确实想做的是给动态语言用的虚拟机。后来研究过程中他们发现其实语言无关的核心不是虚拟机而是程序表示本身。于是重心转到了“一套可以跨语言、跨平台复用的编译基础设施”上。虽然名字没改但项目定位完全变了。今天你提“LLVM”行业默认指的是整套编译器生态而不是某个虚拟机。理解这一点很重要因为它决定了你后续看代码时的心理预期。你打开llvm/lib/CodeGen看到的是一堆指令选择、寄存器分配、指令调度的逻辑打开llvm/lib/IR看到的是定义 IR 数据结构的代码。整个仓库没有一个“虚拟机主程序”在等你找它更像是一个乐高积木库你按需组合出 Clang、opt、llc 这些工具。1.3 为什么我劝你先别急着编译先用起来比先造轮子重要llvm-project这个仓库对大部分开发者日常来说其实不必从源码构建。Ubuntu 上直接apt install clang lld llvmmacOS 上 Homebrew 装llvmWindows 上官方有二进制发布包这些都能让你快速拿到 Clang、opt、llvm-dis 这些工具。源码构建适合下面这几类人你想改 LLVM 源码你要给新架构加后端你要深度调试优化器或者你需要某个特定版本且不信任发行版打包。我自己的建议是先用系统包管理装上 LLVM把clang、opt、llc跑熟。等你真遇到“官方二进制不满足我我要改源码”的需求再回来 clone 仓库编译。别一开始就扎进源码构建的深水区否则光是等待编译时间就足够消磨掉兴趣。但作为一篇讲透llvm-project的文章源码构建那部分实操我后面还是完整给出来因为这确实是最能理解 LLVM 构造的方式之一。2. 解开 LLVM 的经典三阶段架构2.1 前端、中端、后端它是怎么把源码变成机器码的传统编译器往往是一套纵向的大流程词法分析、语法分析、语义分析、生成中间代码、优化、生成目标汇编。GCC 走的就是这个路子整个编译器围绕一门语言来设计。LLVM 把这条流水线劈成了三段前端负责把源代码转成中间表示IR中端只对 IR 做优化不关心这是 C 还是 Rust后端把优化后的 IR 变成目标平台机器码。这个拆分看着简单却是整个 LLVM 生态的命门。因为语言差异被前端吸收了平台差异被后端吸收了中间留下的 IR 是一块极其稳定的“通用语言”。C 语言写完的优化器能直接给 Scala 用x86 写好的后端能直接服务 COBOL。你可以把clang想象的翻译官把各种人类友好语言翻译成 IR 这种“编译器界通用语”后面opt和llc就只跟通用语打交道不需要再理会原始语言长什么样。我实际用起来觉得最爽的一点是当 Clang 报错的时候调试可以往前端找当生成代码性能不对的时候可以用opt的 pass 单独跑 IR 观察当怀疑是后端指令选择问题时又可以直接看llc输出。每一个阶段都可观测、可单独运行这种模块化太适合排查问题了跟传统编译器黑盒式体验完全不同。2.2 Pass 机制优化器为什么能像流水线一样组装LLVM 中端优化的核心是 pass。一个 pass 就是对 IR 做一次遍历和变换比如删冗余计算、做循环展开、内联小函数。opt命令行工具可以让你选择一个 pass 列表按顺序执行这就像一条加工流水线每个工位只干一件小事组合起来却能产生很高的优化效果。我举个直观例子一个最简单的死代码删除DCEpass它会分析代码里哪些指令的结果没被后续使用然后把它们清除。单个 pass 效果可能不明显但内联 pass 跑完原来可以内联的函数被摊平很多变量传输变成了死代码DCE 再跟上就能把膨胀部分剪掉。LLVM 的优化序列设计就是按这种“先放大、再清理”的节奏来安排 pass 顺序的。这个机制也带来调试上的福利。你想研究某个优化点可以只跑相关 pass不用每次都完整编译整个程序。我在验证循环优化效果时经常是clang -O0 -S -emit-llvm生成未优化 IR然后手动指定opt -passesloop-unroll,licm对比 pass 前后的 IR 变化。比直接看编译后汇编直观太多了。2.3 后端 Target一套 IR多个平台后端的核心是把优化后的 IR 转成目标平台的指令序列。传统思路是每个平台一套独立后端逻辑但 LLVM 抽象出了一个 Target 接口把寄存器描述、指令选择、调用约定等部分标准化。你为 AArch64 写了一个后端理论上 x86、RISC-V、WebAssembly 都能共享同一套中端优化只是最后指令生成不同。理解 target 关键词时你得知道 LLVM 里说的 Target 未必是一个 CPU 架构它可以是 WebAssemblywasm这种虚拟指令集也可以是 CUDA 这种 GPU 编程模型甚至可以是一个自定义 DSP。这就让 LLVM 成了各种特殊芯片“快速获得工具链”的首选路径。一个芯片公司要推新处理器最省事的方案往往是加一个 LLVM 后端然后 Clang、Rust、Swift 全都“白捡”了支持。我在 RISC-V 模拟器上调过交叉编译感受很直接clang --targetriscv64-unknown-elf -marchrv64gc一行命令完成从 C 源码到 RISC-V 汇编的跨越。中间的优化、寄存器分配、指令选择全部建立在这套 target 抽象之上。如果没有 LLVM搞一个新架构工具链的成本可能是几十人年。3. LLVM IR整套系统的灵魂与核心抽象3.1 三种形态一个真相LLVM IR 有三种存在形态新手经常搞混。第一种是内存中的数据结构编译过程中 pass 操作的就是这种 C 对象第二种是 bitcode一种紧凑的二进制格式后缀通常为.bc用于跨阶段传输第三种是人类可读的文本格式后缀通常为.ll方便调试和分析。一个实际的编译流程中Clang 前端生成 bitcode 或者内存 IRopt加载后进行各种 pass 变换llc再把最终 IR 变成汇编。你可以用clang -S -emit-llvm hello.c -o hello.ll把 C 源码转成文本 IR看看 LLVM 眼中的程序长什么样。这是学习 LLVM 最直观的入口跟我第一次看懂 IR 时的感觉一样原来编译中间产物不是黑箱而是一种可以阅读、修改、调试的代码形态。3.2 SSA 形式和三地址码IR 为什么长这样LLVM IR 最显著的特点是静态单赋值SSA。意思是每个变量只被赋值一次。听起来很奇怪正常程序里变量不都要反复改吗比如x x 1这种常见操作SSA 怎么表示答案是引入新变量名把代码改成x1 x0 1。后续再使用 x1 而不是 x0。去查这种设计是编译器领域多年的经验结晶变量只赋一次值数据流关系在代码里就是天然的很多优化算法瞬间变得简单可靠。另一个特点三地址码指每条指令大概相当于a b op c这种形式操作数最多三个。它不像汇编那么底层也不像 AST 那么高层非常适合做分析和变换。你在.ll文件里看到%1 add i32 %a, %b就是一个典型的三地址指令把%a和%b加起来得到%1。SSA 三地址码组合在一起使得 IR 上的每种分析都有清晰的数学基础。写优化 pass 时不用老想着“这个变量在别处被改过没”你看到的就是定死的值。搞清楚了这一点你去读 LLVM 的优化代码时会轻松很多。3.3 亲手编译一段 C 代码到 LLVM IR纸上谈兵没意思我直接给你看一个真实例子。假设有这样一个 C 文件int add_and_mul(int a, int b, int c) { int t a b; return t * c; }用clang -O0 -S -emit-llvm add.c -o add.ll生成文本 IR核心函数长这样define i32 add_and_mul(i32 %a, i32 %b, i32 %c) { entry: %t add i32 %a, %b %mul mul i32 %t, %c ret i32 %mul }i32表示 32 位整数%a、%b、%c是入口参数每条指令都有独立目标。你会注意到没有复杂嵌套表达式a b和t * c被拆成两条独立指令了这就是三地址码的直观体现。再看启用优化后的效果clang -O2 -S -emit-llvm add.c -o add.lldefine i32 add_and_mul(i32 %a, i32 %b, i32 %c) { %t add i32 %a, %b %mul mul i32 %t, %c ret i32 %mul }在 O0 和 O2 下 IR 看起来差不多这种情况在简单函数里很常见因为没有可优化的冗余。你换一个带循环或常量表达式的函数差别就很明显了。建议你自己拿个实际工程试一下把 O0 和 O3 的 IR 放一起 diff能直观看到优化器的威力。4. llvmpipeLLVM 在图形渲染里的一个“隐藏跨界应用”4.1 “llvmpipe (LLVM 15.0.7, 256 bits)” 到底在说什么在你使用glxinfo或Mesa驱动的 Linux 系统上偶尔会看到输出OpenGL renderer string: llvmpipe (LLVM 15.0.7, 256 bits)。很多人问这是不是显卡驱动出问题了。我明确说不是。这是 Mesa 在告诉你“当前 3D 渲染不靠 GPU而是靠 CPU 上的软件渲染器其中着色器编译和执行的底层运行时是 LLVM 15.0.7SIMD 向量宽度是 256 bits”。llvmpipe 本质上是 Mesa 里的一个软件渲染器它通过 LLVM 的 JIT 能力把 OpenGL 的 shader比如顶点着色器、片段着色器编译成当前 CPU 能跑的机器码。因为 LLVM 的优化器足够猛llvmpipe 做出来的软件渲染性能是传统纯解释器远远比不上的。而 “256 bits” 指的是生成的代码用的是 256 位 SIMD 指令也就是 AVX2/AVX-512 这类宽向量指令让 CPU 一条指令能同时处理多组数据大幅提升软件渲染速度。4.2 什么场景会碰到 llvmpipe无 GPU 服务器、云主机、虚拟机里最常出现 llvmpipe。比如你在一台只装了 CPU 的服务器上跑 OpenGL 程序Mesa 找不到硬件加速就自动 fallback 到 llvmpipe。容器和 CI 环境里没有 GPU 设备要做离屏渲染测试也基本靠它。还有一个典型场景是故障排查。你怀疑 GPU 驱动有问题时如果驱动加载失败Mesa 会自动切到软件渲染兜底这时候glxinfo的输出就是 llvmpipe。看到这个字符串并不意味着系统坏了但它确实暗示“当前没有可用的硬件加速”。对有经验的运维或开发来说这是判断环境的一个很直观信号。我记得有一次在 CI 里跑基于 OpenGL 的图像处理测试在没有 GPU 的 Runner 上测试一直卡到超时。排查了半天发现 Mesa 调用了 llvmpipe 软件渲染虽然能出正确结果但性能远不如硬件。后来我在测试入口加上了判断无 GPU 环境就直接跳过 GPU 相关用例彻底解决 CI 超时问题。这就是典型的不看 logos 就挨打的场景。4.3 llvmpipe 为什么选择了 LLVM而不是自己写一个编译器如果让你给 shader 做 JIT你会怎么做最省事的方案是逐条解释执行性能极差最复杂的方案是像正常编译器一样全家桶自己抄一遍工作量爆炸。llvmpipe 选了中间路线用 LLVM 做 JIT。它把 shader 先翻译成 LLVM IR然后调 LLVM 的优化器和代码生成器最后得到针对当前 CPU 的机器码。这个决定的核心考量是 LLVM 的“一次编写到处受益”。Mesa 团队不需要为 x86 和 ARM 分别维护一套代码生成逻辑这些东西 LLVM 后端已经覆盖。LLVM 版本升级带来的优化器改进llvmpipe 直接白嫖。现代 CPU 的 SIMD 指令集越来越宽LLVM 能自动生成矢量化的代码llvmpipe 也跟着受益。也正是因为 llvmpipe 与 LLVM 深度绑定你在构建 Mesa 时经常要指定 LLVM 的版本。比如-DLLVM_CONFIG/usr/bin/llvm-config-15告诉 Mesa 用 LLVM 15 来编译 llvmpipe 插件。版本不匹配时Mesa 的编译过程就会报一堆找不到 LLVM 组件的错误这类问题在后面常见问题里我再细说。5. 实操记录从零源码构建 llvm-project5.1 环境准备与依赖安装我这次构建环境是 Ubuntu 22.04x86_64 架构16 核 CPU32GB 内存磁盘预留了 80GB 空间。构建 LLVM 是重活依赖必须提前装齐。需要的基础包包括build-essential、cmake、ninja-build、python3、zlib1g-dev。sudo apt update sudo apt install -y build-essential cmake ninja-build python3 zlib1g-dev gitCMake 版本有要求。LLVM 15 通常在cmake_minimum_required(VERSION 3.20)左右如果你系统自带的 cmake 太老建议从源码装新版或者用 pip 装的 cmake。Ninja 版本一般 1.10 以上就行。Python 主要用于 LLVM 的脚本和测试框架版本 3.6 甚至以上即可。这里有个很现实的建议磁盘至少留 50GB如果是 Debug 构建可能要 80GB 以上。我见过有人编译到一半用尽磁盘所有构建缓存全部作废极其尴尬。时间上Release 全量构建 16 核大约 20-40 分钟Debug 可能要一两个小时。准备好耐心。5.2 获取代码浅克隆还是全克隆llvm-project的 git 历史非常深动辄几个 GB。如果你只是想要某个版本编译使用用浅克隆是理智的选择。比如构建 LLVM 15.0.7我会这样git clone --depth 1 --branch llvmorg-15.0.7 https://github.com/llvm/llvm-project.git这样只拉取该版本的一次快照体积小很多。如果你要参与开发、要看历史提交那再考虑全量git clone。我建议绝大多数读者先浅克隆省下带宽和磁盘。需要注意分支名是llvmorg-15.0.7不是llvm-15.0.7。LLVM 官方用 tag 管理版本这种 tag 的命名方式带llvmorg-前缀。克隆完成后先把目录结构看一眼llvm-project/llvm是主项目llvm-project/clang是 C/C 前端llvm-project/llvm里又有lib、tools、include等子目录。构建时 CMake 的源码目录要指向llvm-project/llvm不是仓库根目录。5.3 CMake 配置每个参数都是为什么我使用的构建配置如下cmake -G Ninja -S llvm-project/llvm -B build \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang;lld \ -DLLVM_TARGETS_TO_BUILDX86 \ -DLLVM_ENABLE_ASSERTIONSON \ -DLLVM_CCACHE_BUILDON逐行解释一下。-G Ninja指定用 Ninja 做构建系统比 Make 快很多并行度高。-S llvm-project/llvm让 CMake 定位源码目录注意不是仓库根目录。-B build是输出目录后面构建产物都放这里。-DCMAKE_BUILD_TYPERelease决定编译 LLVM 自身时用优化。它能极大缩短构建时间也让生成的 LLVM 工具跑得更快。缺点是没有调试信息如果你想断点调试 LLVM 内部代码应该用RelWithDebInfo或Debug。-DLLVM_ENABLE_PROJECTSclang;lld决定除了核心之外还要编哪些子项目。很多新手不解为什么不默认编 Clang。因为 LLVM 核心本身不依赖 Clang只编核心能大幅减少构建量。你如果要全部也可以clang;lld;libcxx;compiler-rt但要接受更长构建时间。-DLLVM_TARGETS_TO_BUILDX86指定只生成 X86 后端。默认是全平台后端构建时间和产物体积都成倍增加。如果你只需要在本机生成 x86 汇编这样设置最省。要交叉编译 RISC-V 或 AArch64再改成X86;RISC-V;AArch64。-DLLVM_ENABLE_ASSERTIONSON打开断言虽然会略增运行开销但能及早暴露 IR 不一致等问题对开发调试是强烈建议的。-DLLVM_CCACHE_BUILDON启用 ccache 缓存编译产物后续反复调整配置时重编速度有质变。没有 ccache 的机器先sudo apt install ccache。5.4 正式编译如何选目标为什么不是全量 ninja配置完成后我通常不会直接ninja -C build全量构建因为那会把所有工具和库都编一遍。按需构建更快。如果你只想要 C 语言编译器可以指定ninja -C build clang lld这个命令会编出build/bin/clang和build/bin/ld.lld。如果你想跑 IR 优化实验再补一个optninja -C build clang lld opt llc在 16 核机器上只编 ClangLLD 大概几分钟到十几分钟比全量友好太多了。构建完成后先验证版本./build/bin/clang --version如果输出包含clang version 15.0.7字样说明构建成功。我自己踩过的坑是忘了指定LLVM_TARGETS_TO_BUILD默认全平台后端构建时间和磁盘占用直接爆炸。还有一次忘了装 ccache改一版参数就要重新编译几十分钟心态崩了。所以这些参数在你第一次配置时就要确认清楚后续再加项目基本只是重编新增部分不会全量重来。5.5 验证 llvmpipe 与 LLVM 的联动构建完 LLVM 工具链后你还能顺手验证 llvmpipe 的场景。如果你的系统 Mesa 用的是系统 LLVM那glxinfo | grep OpenGL renderer看到的可能就是llvmpipe (LLVM 15.0.7, 256 bits)。这个信息在开发图形驱动时很重要。不过有些发行版没有把 llvmpipe 编进默认 Mesa需要安装对应的mesa-utils和libgl1-mesa-dri软件包。装好后在无 GPU 环境执行glxinfo -B能看到Device: llvmpipe (LLVM ...)。如果 CUDA 或 OpenCL 相关程序找设备时只能看到 CPU 设备多半也是因为 fallback 到了 llvmpipe。理解了这条链路你再看到相关日志就不会慌。6. 构建与使用中的高频问题排查6.1 内存不够导致编译 OOM这是最常遇到的第一道坎。LLVM 源码中某些文件特别大比如SelectionDAGISel.cpp、CodeGenPrepare.cpp编译时单个文件就能吃几个 GB 内存。小内存机器上 Ninja 并发一高直接 OOM 被杀。解决思路很简单调低并行数。Ninja 可以通过-j参数控制并行任务数。16 核机器改成-j4甚至-j2虽然慢不少但起码不会崩。另一种做法是使用lld作为链接器内存占用比 GNUld低。配置时可加-DLLVM_USE_LINKERlld让 LLVM 自己用 lld 链接。我建议构建前先用free -h看下内存如果只有 8GB并行任务数老老实实设成 2。别贪快等它 OOM 重来更浪费生命。6.2 Clang 前端构建不出来的排查有些用户以为LLVM_ENABLE_PROJECTS填完就会编 Clang结果构建完发现build/bin下没有clang。这通常是因为没指定 ninja target前面提到你执行的是ninja -C build而不是ninja -C build clang。但如果你确认执行了ninja clang还是报找不到 target那就检查 CMake 配置里LLVM_ENABLE_PROJECTS是否真的包含了clang。另一个常见问题是构建机器没有足够内存Clang 编译到一半被 kill但看日志又没报具体错误。这时候用dmesg | tail -n 30查内核的 OOM kill 记录通常能看到线索。先不说别的看到Killed process就基本锁定是内存不够了。6.3 Mesa/llvmpipe 版本对不上 LLVM 时报错Mesa 在链接 llvmpipe 时需要读取 LLVM 的配置信息常见一个报错是Could not find a suitable libLLVM或者Wrong LLVM version。这通常说明 Mesa 编译时找到了不兼容的 LLVM 版本。解决办法是显式指定 mesa 构建时用的llvm-config。比如 Ubuntu 上有多个 LLVM 版本时cmake -S mesa -B build_mesa \ -DLLVM_CONFIG$(which llvm-config-15) \ ...要确保llvm-config-15 --version输出是 15 开头的。如果你的自定义 LLVM 装在非标准路径可能还得把libLLVM.so所在目录加进LD_LIBRARY_PATH。这类问题的核心思路就是“版本对齐”。6.4 常见问题速查表症状可能原因排查与解决编译过程中Killed或退出码 137内存不足编译器被 OOM killer 杀掉调低-j启用 zram或减少并发链接任务CMake 报找不到 Python 或 zlib依赖缺失安装python3-dev、zlib1g-dev后重新配置clang不生成ninja target 没指定执行ninja -C build clang构建时间异常长Debug 构建或全量 target改用 ReleaseLLVM_TARGETS_TO_BUILD收紧glxinfo 显示 llvmpipe没有可用 GPU 硬件加速确认驱动是否加载无 GPU 环境属正常Mesa 报 LLVM 版本不匹配多个 LLVM 环境干扰显式指定LLVM_CONFIG理清 PATH磁盘写满全量 Debug 构建清理构建目录改用浅克隆和 Release这类表是给读者一个快速定位入口的。真实排查时我不会机械照表搬而是先看报错在哪个阶段再对症处理。比如链接阶段报错大都是缺依赖或内存问题优化阶段崩溃多半是 IR 不一致或某个 pass 有 bug。确定阶段后排查范围就小很多。7. 给新手的 LLVM 学习路线与个人体会如果你是刚接触llvm-project我给的建议是先玩工具再读源码最后才谈修改。第一步把 Clang、opt、llc 用熟知道一个 C 程序怎么从源码变成 IR 再变成汇编。第二步选定一个小目标比如“给 IR 加一个最简单 pass”写到能在opt里跑起来。第三步再去理解 SelectionDAG 或 GlobalISel 这些后端细节。按这个路径走挫折感会小很多你每次碰到的报错也都能落在明确的知识点上。文档方面《LLVM Language Reference Manual》值得精读它把 IR 的每一条指令都解释得很清楚。官方教程llvm/docs/MyFirstPass.rst很适合做 pass 开发的起点。遇到问题不要只靠搜索引擎邮件列表和 Discourse 上很多高质量讨论GitHub issue 里的技术细节也远比一般资料深。我很多 pass 相关的困惑都是在这些人讨论记录里找到答案的。最后分享一个我做 LLVM 实验时的必用小技巧准备一份很小的测试文件比如几十行的数组循环。每次改一个 pass 或后端参数就用它编译观察生成的 IR 或者汇编变化。越小的测试越容易确认行为等代码在“最小样例”上跑通了再上真实工程。这个习惯帮我节省了大量排查时间。编译器的世界很深但llvm-project是为数不多让你能“一边读一边跑”的现代大型项目。我这几年在它上面花的时间绝大多数都变成了对语言和硬件关系的更深理解。如果你也想成为那种“改一行编译器代码看整个程序行为变化”的人这个仓库值得你慢慢啃。
返回列表