news 2026/9/20 18:00:53

LLVM不是编译器?一文搞懂编译器基础设施与自定义Pass

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLVM不是编译器?一文搞懂编译器基础设施与自定义Pass

LLVM 是我这些年见过被误解最深的开源项目之一。很多人一听到“llvm-project”,第一反应是“哦,就是那个 C/C++ 编译器”,也有人觉得它和 Linux 内核一样属于“听说过但不敢碰”的东西。实际上 LLVM 既不是某个具体的编译器,也不是一台虚拟机,它是一整套编译器基础设施;编译器只是一个侧面,IDE、代码分析工具、GPU 驱动、解释器、硬件设计工具,全都在这套基础设施上盖楼。这篇文章会围绕 llvm-project 仓库,把我从“能编译”到“能改 LLVM 源码”这个过程中真正关键的知识点、操作步骤和踩过的坑整理一遍,适合想入门编译器、想给自己的语言接后端、或者单纯想在大型 C++ 项目里找一份优质代码来读的工程师。

1. 先搞清楚:LLVM 到底是一个“项目”还是“编译器”

第一次git clone https://github.com/llvm/llvm-project.git的人多半会被仓库结构吓一跳:里面躺着 clang、lld、libcxx、compiler-rt、mlir、flang、polly、openmp 这么多子项目。这很容易让人以为 LLVM 是“另一个编译器全家桶”,但它和 GCC 在根本设计上是两回事。

1.1 从名字说起:Low Level Virtual Machine 的历史遗留

LLVM 这个缩写最早来自 2000 年前后 Chris Lattner 在伊利诺伊大学的一个研究项目,全称是 Low Level Virtual Machine。名字里的“Virtual Machine”不是 Java 虚拟机那种运行时,而是指一种“抽象的指令集”——也就是我们常说的 LLVM IR。这个 IR 不绑定具体 CPU,也不绑定具体语言,它更像一套中间表达语言,让编译器前段和后段可以解耦。

项目后来发展成了整个生态,名字却保留了下来。现在大家说“LLVM”时,可能指三样东西:

  • 一个开源的编译器基础设施项目,也就是 llvm-project 仓库里的大部分代码。
  • 以 Clang 为代表的 C/C++/Objective-C 编译器前端,加上优化器和各架构后端的完整工具链。
  • 那个核心中间表示(IR)本身,比如“把源码转成 LLVM IR”这句话里的 LLVM。

这三层含义经常混着用,导致很多人聊天时对不上号。如果你刚接触,建议先记住一句话:LLVM 是“编译器的基础设施”,而不是“一个编译器”。这就好比 LLVM 提供的是水、电、道路和预制板,而不是交付一栋精装房。

1.2 和 GCC 的最大区别:模块化带来的自由度

GCC 的成功在于它是一条非常完整的编译流水线:前端把 C/C++ 解析成 GCC 自己的中间表示,优化器做优化,后端生成目标机器码。整条链路成熟、稳定、性能好,但有一个结构性问题——前段、优化器、后端耦合得比较深。如果你想支持一个新语言,或者一个新 CPU 架构,改动成本很高,而且很难只复用其中一部分。

LLVM 打破了这一点。它把编译过程拆成清晰的阶段,阶段之间用统一的 IR 通信:

  • 你想造一门新语言,只需要写一个前端,把源码翻译成 LLVM IR,剩下几十个优化 pass、调试信息生成、链接期优化、多平台后端,全部可以白嫖。
  • 你想支持一个新 CPU,只需要写一个后端,把 LLVM IR 翻译成这个 CPU 的机器码。中间能享受所有前端和所有优化器带来的增益。
  • 你想在自己的工具里做静态分析,不必真的生成可执行文件,可以直接用 Clang 解析代码、生成 AST,再用 LLVM IR 做数据流分析。

这个模块化自由度是 LLVM 成为现代编译器事实标准的核心原因。Rust、Swift、Kotlin/Native、Zig 都用 LLVM 做后端,不是因为他们偷懒,而是因为重新造一套“优化器+全平台后端”的轮子是巨大的工程。

1.3 苹果的押注与 Clang 的诞生

这里还有一个绕不开的历史:苹果在 2005 年左右把 Chris Lattner 招进去,后来又基于 LLVM 开发了 Clang。Clang 的主要任务是替代 GCC 作为 macOS/iOS 的 C/C++/Objective-C 编译器。苹果需要更好的 IDE 集成、更快的编译速度和更友好的错误信息。Clang 的架构非常适合这种需求,因为它的 AST 是显式暴露的,IDE 可以从里面拿到非常丰富的语义信息。

后来 Clang 基本成了 macOS 上的默认编译器,也带动了整个 LLVM 生态的爆发。现在 Android NDK、Chromium、Windows 上的某些工具链、PlayStation 和 Xbox 这类游戏主机的 SDK,全都和 LLVM 有密切关系。你会发现,现代编译器的边界早就不只是“把 C 代码变成二进制”这么简单了。

2. 架构拆解:前端、IR、优化器、后端各管哪一段

理解 LLVM 最好的方式是把编译过程拆成四个部分:前端(Frontend)、中间表示(IR)、优化器(Optimizer)、后端(Backend)。它们在 llvm-project 里的代码分布也基本对应这个分层。

2.1 前端:从源代码到 AST,再到 IR

前端做的是词法分析、语法分析、语义分析。Clang 是这个阶段最著名的实现,支持的编程语言包括 C、C++、Objective-C 和 OpenMP/CUDA/HIP 等扩展。前端输出不是直接变成机器码,而是先把代码解析成 AST(抽象语法树),再做一系列语义检查,最后生成 LLVM IR。

比如这段 C 代码:

int add(int a, int b) { return a + b; }

经过 Clang 转成 LLVM IR 之后大概是这样的:

define i32 @add(i32 %a, i32 %b) { entry: %add = add nsw i32 %a, %b ret i32 %add }

我用一条最简单命令就能看到这个过程:

clang -S -emit-llvm add.c -o add.ll

看到 IR 文件时,你会直观感受到它和汇编很像,但又不完全一样。它有明确的类型系统,i32表示 32 位整数;它有显式的基本块,比如entry;它的指令数比真实汇编少很多,某些复杂指令还是高层语义。这套 IR 设计得好,是 LLVM 能统一各种语言和架构的关键。

2.2 IR 的核心机制:静态单赋值(SSA)

LLVM IR 最重要的底层机制是 SSA,即 Static Single Assignment:每个变量只能在代码中赋值一次。上面的%addadd指令定义后,后续指令就只做读取,不会再写入同一个变量。

为什么要这么设计?因为 SSA 让数据流分析变得极其简单。编译器要判断一个值在哪被定义、哪被使用,如果变量可以随意改写,就需要做大量的活跃变量分析;SSA 形式下,值和定义点天然绑定,很多优化算法直接遍历 use-def 链就能工作。你可以把 SSA 理解为整理房间时先给每个物品贴上唯一编号,后续找东西就不用翻箱倒柜,照着编号去取就行。

2.3 优化器:pass 框架是 LLVM 的灵魂

优化器是 LLVM 中最迷人的部分,核心就是 pass 框架。所谓 pass,可以理解成一个模块化的优化或分析算法。

  • 分析类 pass:只读 IR,收集信息。比如计算函数调用图、循环深度、指针别名关系。
  • 变换类 pass:改写 IR。比如函数内联、常量传播、死代码消除、循环展开。

LLVM 的优化器会按照特定顺序把这些 pass 串成一个 pipeline。比如你在命令行里写-O2,Clang 会启用一整条默认流水线:先做简单的局部优化,再做函数内联,再做循环优化,最后做代码布局等后端前优化。每个 pass 都尽量保持 IR 的合法性和类型的严谨性。

底层实现中,新 Pass Manager 已经是绝对主流。从 LLVM 14 左右开始,新 Pass Manager 默认启用,老的 legacy Pass Manager 正在被逐步清理。新 Pass Manager 最大的优势是更精确地管理 pass 之间的依赖,分析结果是缓存还是失效,可以被显式追踪。这一点在你写自定义 pass 时会深有体会。

2.4 后端:从 IR 到机器码的漫长旅程

如果你打开 llvm-project/llvm/lib/Target 目录,就会看到 X86、AArch64、ARM、RISCV、PowerPC、SystemZ、WebAssembly、NVPTX、AMDGPU 等一堆子目录。每个目录就是一套后端。

后端的工作是把 LLVM IR 一步步降低到目标机器指令。虽然细节极其复杂,但几个大阶段是固定的:

  • 先做指令选择,把 IR 指令映射到目标机器的指令候选。
  • 再做寄存器分配,决定哪些变量放寄存器、哪些放内存,寄存器不够时还要插入溢出代码。
  • 再做指令调度,尽量让流水线利用率更高。
  • 最后做汇编输出,把指令编码成文本汇编或二进制机器码。

后端依赖一张用 TableGen 语言写的目标描述文件(.td)。这张表要描述指令的编码、操作数类型、合法组合、寄存器类别等信息。LLVM 会读这张表生成大量 C++ 代码,用来做指令匹配和汇编反汇编。所以如果你要给新 CPU 做后端,很大一部分工作在写.td文件,而不是手写十万行匹配逻辑。

一个容易理解但不完全严谨的类比:整个编译器像是一家餐厅。前端是采购员,把各地食材(源码)统一加工成标准净菜(IR)。优化器是大厨,研究怎么用最短时间、最少的锅做出最好吃的菜。后端是摆盘师傅,一个人负责一种盘子(CPU 架构),把大厨的标准步骤转成符合自己餐具形制的最终摆盘。

3. 实操:十分钟写一个自定义 LLVM Pass 并跑通

光看架构概念还是不够,我建议你亲自写一个 Pass 跑一遍。这里我用一个最简单的例子:统计一个 IR Module 里有多少个函数。代码量很小,但能帮你把“改动 LLVM 源码或在 llvm-project 上做二次开发”这件事变成可触摸的经验。

3.1 准备开发环境:不是所有场景都要全量编译

如果你只是想用 Clang 的日常编译功能,直接用 Homebrew、apt 或官网下载的预编译二进制就够了,没有必要从源码构建。但如果你想写自定义 Pass,尤其是一个需要调试的 LLVM Pass,最好有一个对应版本的 LLVM 开发环境。

我的建议是这样的:

  • Linux 上可以用 apt 装llvm-devclang和配套的lld
  • macOS 上可以用 Homebrew 的llvm,它会同时安装llvm-config和头文件。
  • 如果你确定要长期折腾 LLVM,可以考虑自己从 llvm-project 构建。在 16GB 内存、50GB 空闲磁盘的机器上只构建 X86 后端,时间是可控的。

自己构建时,CMake 命令大致是:

cmake -G Ninja -S llvm-project/llvm -B build \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_ENABLE_PROJECTS="clang" \ -DLLVM_TARGETS_TO_BUILD="X86" ninja -C build

这里只开 X86 目标,能明显加速构建。如果你临时想看 AArch64,后续再重新配置也没关系。

3.2 用 New Pass Manager 写一个插件

从 LLVM 17 开始,Legacy Pass Manager 已经在逐步淡出。新代码直接按 New Pass Manager 的风格写,长期维护成本最低。

首先创建FunctionCounter.cpp

#include "llvm/IR/Function.h" #include "llvm/IR/Module.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; namespace { struct FunctionCounter : public PassInfoMixin<FunctionCounter> { PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) { unsigned funcCount = 0; for (auto &F : M) { if (!F.isDeclaration()) { ++funcCount; } } errs() << "Function count in module: " << funcCount << "\n"; return PreservedAnalyses::all(); } }; } // namespace llvm::PassPluginLibraryInfo getLLVMPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, "FunctionCounter", LLVM_VERSION_STRING, [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, ModulePassManager &PM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "function-counter") { PM.addPass(FunctionCounter()); return true; } return false; }); }}; } extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getLLVMPassPluginInfo(); }

再创建CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(FunctionCounter) find_package(LLVM REQUIRED CONFIG) include_directories(${LLVM_INCLUDE_DIRS}) add_definitions(${LLVM_DEFINITIONS}) add_library(FunctionCounter MODULE FunctionCounter.cpp) set(LLVM_LINK_COMPONENTS Support Core IRReader Passes) llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS}) target_link_libraries(FunctionCounter PRIVATE ${llvm_libs})

这个插件里最核心的几点:

  • PassInfoMixin<FunctionCounter>是 New Pass Manager 的接口要求。
  • run函数是 Pass 的入口,参数是 Module,所以能遍历所有函数。
  • errs()是 LLVM 自己的输出流,不要混用std::cerr,在 JIT 和插件场景里容易出问题。
  • 我们的 Pass 只读不改,所以返回PreservedAnalyses::all(),表示所有分析结果都不需要重新计算。
  • llvmGetPassPluginInfo是插件被opt加载时的入口,相当于告诉 LLVM“我这个插件能提供哪些 pass 管道命令”。

这里有个细节会坑到很多人:如果你通过LLVM_ATTRIBUTE_WEAK声明导出符号时漏掉了extern "C",插件能编译成功,但opt加载时永远提示找不到入口,非常隐蔽。

3.3 编译、运行、看结果

在项目目录下执行:

cmake -B build cmake --build build

然后用 Clang 生成一个测试 IR:

cat > test.c <<'EOF' int foo() { return 1; } int bar() { return 2; } EOF clang -S -emit-llvm test.c -o test.ll

最后用opt加载我们的插件:

opt -load-pass-plugin build/FunctionCounter.so -passes=function-counter -S test.ll -o test.opt.ll

正常会看到一行:

Function count in module: 2

-S表示以文本 IR 形式输出,-o指定输出文件。如果你把-passes=function-counter拼错,它会报错说不认识这个 pass。有了这个最小链路,你就能在这个 Pass 里加各种 IR 分析,或者对 IR 做变换了。

4. 工程里的真实使用姿势:Clang、opt、llc 的分工和流水线

Pass 写完之后,很多人会问:这些东西和我日常编译有什么关系?说实话,大部分业务开发不会直接调opt,但理解整条流水线能让你在遇到“优化后的程序跑出奇怪结果”这类问题时,知道从哪里下手。

4.1 从 C/C++ 源码到可执行文件的完整链路

我用一组命令把每个阶段拆开:

  1. 前端:C/C++ 到 LLVM IR
clang -O1 -S -emit-llvm foo.c -o foo.ll
  1. 优化器:对 IR 做优化
opt -passes=default<O2> foo.ll -S -o foo.opt.ll
  1. 后端:IR 到汇编
llc foo.opt.ll -o foo.s
  1. 汇编器+链接器:汇编到目标文件,再链接成可执行文件
clang foo.s -o foo

日常使用中,你直接执行clang -O2 foo.c -o foo,Clang 会在内部走完这几个阶段。但你手动拆开后就掌握了一个重要的调试技巧:如果怀疑优化器产生了问题,可以先把中间 IR 抓出来,再用opt只跑某个 pass 验证。

除了这条大家都熟悉的路径,llvm-project 还提供了几个非常实用的配套工具:

  • lli:直接解释执行或者用 JIT 执行 bitcode,适合验证 IR 逻辑而不生成最终二进制。
  • llvm-as/llvm-dis:在文本 IR 和二进制 bitcode 之间互转。
  • llvm-link:链接多个 bitcode 文件,是 LTO 的基础。
  • llvm-nm/llvm-objdump:查看目标文件符号和反汇编,比 GNU binutils 的对应工具更懂 LLVM 生成的内容。

4.2 LTO 和 ThinLTO:链接期优化为什么值得做

只做单文件优化时,编译器看不到其他编译单元里的信息,函数内联、常量传播都受限制。LTO(Link Time Optimization)的核心就是把编译阶段的 IR 保留到链接期,等所有编译单元都拿到手后再一起优化。

ThinLTO 是对全量 LTO 的一次妥协。全量 LTO 的问题在于内存开销巨大,大型项目根本吃不住。ThinLTO 的思路是为每个编译单元生成索引,链接时只加载需要的 IR 模块,并利用跨模块信息做关键优化。它没有全量 LTO 那么疯狂,但收益依然明显。

在 CMake 里开 ThinLTO 通常只要:

set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)

或者编译时:

clang -flto=thin -O2 main.c helper.c -o app

我自己的经验是:不要把 LTO 当成默认选项,尤其是大型 C++ 项目里,它可能增加大量内存使用和编译时间,收益未必可观。更好的玩法是先做 PGO(Profile-Guided Optimization),用真实流量 profile 指导优化,再考虑是否叠加 ThinLTO。

4.3 优化报告:让编译器告诉你为什么没优化好

很多时候你想知道某个循环为什么没被向量化,某个函数为什么没被内联。与其猜,不如让编译器直接告诉你。Clang/LLVM 提供了一系列优化报告开关:

clang -O2 -Rpass=inline foo.c clang -O2 -Rpass-missed=loop-vectorize foo.c clang -O2 -Rpass-analysis=loop-vectorize foo.c

-Rpass显示成功做的优化,-Rpass-missed显示想做但没做到的事,-Rpass-analysis显示分析过程和原因。这套机制在排查性能问题时非常有用,能帮你快速定位是代码写法有依赖、还是编译器没有足够信息。

另一种更底层的方式是让opt打印每个 pass 运行后的 IR:

opt -passes=default<O2> -print-after-all foo.ll -o /dev/null

这会输出大量信息,但当你怀疑某个 pass 改坏了 IR 时,-print-after-all是定位最直接的手段。

5. 踩坑记录:从“能编译”到“敢改 LLVM”之间的三道坎

我见过太多人卡在“看懂了理论”和“敢动手改”之间。真正阻碍他们的往往不是智商,而是几个看似玄学的坑。我把亲身踩过的几个典型问题列出来,你遇到类似症状时可以直接按这个思路查。

5.1 第一道坎:IR 类型不一致导致的“后端崩溃”

症状:你写了一个 transform pass,修改完 IR 后一跑llc,直接崩在指令选择阶段,报错信息极其难看,甚至只剩一个assert

原因:你的 pass 可能生成了一条把i32当作i64使用的指令,或者在不该插入phi的路径上插了一个有问题的phi。IR 本身是非常类型敏感的结构,前端生成 IR 时会保证类型匹配,但你手动变换时很容易打破平衡。

排查思路是:先用opt -verify验证 IR 是否合法:

opt -passes=function-counter -S test.ll -o test.out.ll

如果愿意,在写完 pass 之后主动调用verifyModule

#include "llvm/IR/Verifier.h" bool broken = verifyModule(M, &errs());

这能帮你第一时间抓出类型或 CFG 结构问题,而不是让错误一路传播到后端再爆雷。

5.2 第二道坎:PreservedAnalyses 撒谎后产生的“幽灵错误”

症状:你的 pass 正常改写了 IR,但后续其他分析 pass 拿到的是旧数据,导致优化结果混乱,甚至产生一个“此次编译没问题,下次编译就崩”的诡异错误。

原因:New Pass Manager 非常依赖 pass 对修改状态的声明。如果你实际修改了函数体,却返回PreservedAnalyses::all(),PM 就会认为所有分析结果都还新鲜,于是跳过重新计算,后面某个依赖旧分析的 pass 就“吃坏肚子”了。

正确做法是:如果修改了 IR,返回PreservedAnalyses::none()是最安全的;如果知道自己只改了一部分,可以用getLoopUpdater等方法做更细粒度的维护。新手阶段不要为了“性能”而手写精确保留,老老实实声明none(),把正确性放在第一位。

5.3 第三道坎:构建系统和版本错配

症状:你在网上找到一个基于 LLVM 15 的插件示例,代码看着也没问题,但在本地 LLVM 17/18/19 环境下编译,要么符号找不到,要么opt加载时直接段错误。

原因:LLVM 的 API 变动远比一般开源项目激进,尤其是 Pass Manager 和插件注册相关的代码,几乎每个大版本都会改一轮。很多老博客写的用法是基于 legacy pass manager 的,现在已经不推荐了。

我的建议:

  • 写插件前,先确认本地llvm-config --version
  • 以官方文档和当前源码的llvm/examples/Bye目录为准,那里会跟着版本维护,是活教材。
  • 不要盲目相信网上两三年前的文章,包括我现在写的这篇,动手前也要对照你本地版本的 API。

5.4 第四道坎:优化等级和未定义行为的叠加效应

症状:程序在-O0下一切正常,在-O2下崩溃或输出错误结果。你怀疑是编译器 bug,拿去再编一次又好了。

原因:很多情况下这是代码未定义行为导致的结果。有符号整数溢出、未初始化变量、违反 strict aliasing 规则,这些 UB 在指令调优或重排后表现出完全不同的效果,不是编译器“故意害你”。

排查手段是:

clang -O2 -fsanitize=address,undefined foo.c -o foo

对比加入 sanitizer 前后的运行结果。绝大多数“编译器 bug”最后都证明是业务代码的问题。真正怀疑 LLVM 生成代码有 bug 时,再结合-print-after-all和最小复现用例去 LLVM 社区提问,问的时候附上clang --version和原始终端输出,这会节省自己和大家的时间。

下面我整理一个快速排查表:

症状常见根因第一步排查
插件加载失败版本不一致或入口符号缺失检查llvmGetPassPluginInfo符号和 LLVM 版本
Pass 改动后代码生成崩溃IR 类型不匹配或 CFG 不合法调用verifyModule
优化结果不对但多次编译不一致PreservedAnalyses 声明错误返回PreservedAnalyses::none()
O0 正常 O2 出错源码存在未定义行为开 UBSan/ASan
想知道为什么没做某优化缺少 profile 或循环依赖复杂使用-Rpass-missed

6. 深入源码的阅读顺序和扩展生态

如果你读完前面这些还是觉得不过瘾,想进 llvm-project 源码里逛一逛,我给你一份比较顺手的阅读路径。

6.1 从哪里开始读源码

读 LLVM 源码最忌讳从大到小硬啃。我建议先看 IR 层的数据结构,再看一个具体 pass,最后看后端。

第一步,看llvm/include/llvm/IR/下的头文件,按这个顺序:

  • Value.h:理解 Value 是所有值类型的基类,随后才知道 User、Use、Instruction 之间的关系。
  • Function.hBasicBlock.h:理解函数由基本块组成,基本块由指令组成。
  • Instruction.h:理解指令如何分类和表示。

第二步,去llvm/lib/Transforms/下挑一个简单 pass。比如DeadCodeElimination.cpp或者GVN.cpp。你不需要看全部,只需要跟着一个 pass 看它如何遍历函数、结果如何被后续 pass 使用。

第三步,去llvm/lib/Target/X86/下大致看一遍 TableGen 文件和指令选择相关代码。这里不需要全懂,重点是理解.td文件和 generated code 之间的关系。

这套路径不会让你一夜变成编译器专家,但能帮你建立“源码不那么高不可攀”的直观感受。

6.2 几个值得关注的子项目

llvm-project 不只是编译器。以下子项目现在的重要性越来越大:

  • MLIR:一套新一代可扩展中间表示框架,用来构建编译器和领域专用框架。它把“高层抽象”和“低层机器码”的鸿沟用多级 IR 的方式填平。
  • Clang-Tidy 和 clangd:前者是静态分析工具,后者是 IDE 的语言服务器,这两者都是 Clang 前端能力在编译工具链之外的落地。
  • Flang:Fortran 前端,主要服务科学计算与高性能计算项目。
  • compiler-rt:内置函数库和 sanitizer 运行时,比如 AddressSanitizer、UndefinedBehaviorSanitizer 都是这里的产物。
  • ORC JIT:如果想做即时编译运行时,ORC 是当下最值得研究的 JIT 框架。

你会发现,LLVM 的边界已经远远超出了“编译器”这个词本身。它现在更像是一整套“语言实现和代码生成的基础设施”,有人说它是编译界的 Linux 内核,也不夸张。

6.3 我的个人学习路线建议

最后分享一点我自己的经验:不要抱着 LLVM 文档从头读到尾。这种项目的信息密度太高,直接啃文档,很快就看不懂、也记不住。更好的做法是先给自己定一个真实任务,比如“给一门玩具语言增加一个函数内联优化”,然后带着问题在源码里搜索。

我当年的做法是找一个非常小的优化 pass 读明白,然后在它基础上加了打印功能,观察经过不同优化流水线后 IR 的变化。这个过程比任何教程都有效。等你发现“哦,原来 pass 就是改动 IR 的一段代码”之后,障碍就消失了一大半,剩下的只是经验积累问题了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 17:58:05

大语言模型能力边界与AGI发展路径解析

1. 大语言模型的能力边界与潜力评估大语言模型&#xff08;LLM&#xff09;在文本生成、代码补全等任务上展现出的能力确实令人印象深刻。但当我们讨论其潜力时&#xff0c;需要先明确一个基本事实&#xff1a;当前LLM的核心能力本质上是对海量文本数据的统计建模与模式匹配。这…

作者头像 李华
网站建设 2026/9/20 17:55:22

手机EMC测试实战:辐射骚扰、desense与ESD整改思路全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 17:54:48

PyTorch实现AlexNet花卉图像分类:从数据准备到模型训练部署全流程

简介&#xff1a;以AlexNet模型为核心的花卉分类实战项目&#xff0c;面向深度学习初学者及图像分类开发者&#xff0c;解决从数据准备、模型训练到结果预测的全流程实践难题&#xff0c;并支持通过替换数据集快速迁移到其他分类任务。压缩包共2000个文件&#xff0c;整体约270…

作者头像 李华
网站建设 2026/9/20 17:53:40

Bertalign句对齐原理与工业级调优实战指南

1. 为什么句对齐不是“把两段文字按行切开”那么简单&#xff1f;很多人第一次接触多语言句对齐&#xff0c;第一反应是&#xff1a;“不就是把中文和英文各切成一行一行&#xff0c;然后挨个配对吗&#xff1f;”我三年前也是这么想的——直到在处理一份德语技术文档的中译本时…

作者头像 李华