pass
概述
- 一个
Pass就是对IR做一次完整的遍历和处理 - 每个
Pass专注于做一件事——分析代码的某个属性,或者对代码做某一种变换 - 每个
Pass读取当前的LLVM IR,做一种特定的处理,输出修改后的IR- 下一个
Pass在上一个Pass的输出基础上继续处理
- 下一个
|
1 2 3 4 5 6 |
Pass 1:mem2reg 把栈变量提升为 SSA 寄存器 Pass 2:instcombine 把多条指令合并为更简单的指令 Pass 3:inline 把小函数内联展开 Pass 4:dce 删除死代码 Pass 5:loop-vectorize 把标量循环转为 SIMD 向量操作 ... |
两大类型
- 分析
Pass(Analysis Pass)
|
1 2 3 4 5 6 7 8 9 10 11 |
只读取 IR,不修改 IR 输出:分析结果(其他 Pass 可以查询) 例子: - DominatorTree:计算支配树(谁支配谁) - AliasAnalysis:分析两个指针是否可能指向同一内存 - LoopInfo:识别代码中的循环结构 - ScalarEvolution:分析循环变量的演变规律 类比:体检医生只检查,不动手术 结果可以被多个变换 Pass 共享使用 |
- 变换
Pass(Transform Pass)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
读取 IR,修改 IR,输出修改后的 IR 可能需要查询分析 Pass 的结果 例子: - mem2reg:消除冗余的 alloca/load/store - instcombine:合并/简化指令 - inline:函数内联 - dce:死代码消除 - licm:循环不变量外提 - loop-vectorize:自动向量化 类比:外科医生根据体检结果做手术 |
作用范围
Pass按照它处理的IR粒度分为不同级别:- 对应四种
Pass粒度: Module PassFunction PassLoop PassBasicBlock Pass / Instruction Pass
- 对应四种
|
1 2 3 4 5 |
LLVM IR 的层次结构: Module(一个编译单元,包含所有函数和全局变量) └── Function(一个函数) └── BasicBlock(一个基本块,线性指令序列) └── Instruction(一条指令) |
Module Pass
|
1 2 3 4 5 6 7 |
Module Pass: 一次处理整个 Module 能看到所有函数,可以做跨函数的分析和优化 例子: - GlobalDCE:删除从未被调用的函数(需要看所有函数才能判断) - InlinePass:决定哪些函数值得内联(需要看调用关系) - MergeFunctions:合并代码完全相同的函数 |
Function Pass
|
1 2 3 4 5 6 7 8 9 |
Function Pass: 一次处理一个 Function 不能修改其他函数,只能看当前函数 大部分优化 Pass 属于这一类 例子: - mem2reg:在单个函数内消除冗余内存操作 - InstCombine:在单个函数内简化指令 - DCE:在单个函数内删除死代码 - LICM:在单个函数内的循环中外提不变量 |
Loop Pass
|
1 2 3 4 5 6 |
Loop Pass: 一次处理一个循环 例子: - LoopUnroll:循环展开 - LoopVectorize:循环向量化 - LoopRotate:循环旋转(把循环条件移到末尾) |
BasicBlock Pass / Instruction Pass
|
1 2 3 4 |
BasicBlock Pass / Instruction Pass: 更细粒度的处理 例子: - SimplifyCFG:简化控制流图中的单个基本块 |
Pass 在代码中
- 一个
LLVM Pass就是一个C++类,实现一个run方法:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 一个最简单的 Function Pass #include "llvm/IR/Function.h" #include "llvm/IR/PassManager.h" using namespace llvm; // 继承 PassInfoMixin,这是新 Pass Manager 的方式 struct MyPass : PassInfoMixin<MyPass> { // run 方法:对每个函数调用一次 PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { // F 就是当前函数的 IR // 你可以遍历它的所有基本块和指令 for (BasicBlock &BB : F) { // 遍历基本块 for (Instruction &I : BB) { // 遍历指令 // 对每条指令做分析或变换 if (auto *AddInst = dyn_cast<BinaryOperator>(&I)) { if (AddInst->getOpcode() == Instruction::Add) { // 找到了一条 add 指令 // 可以分析它、替换它、删除它... } } } } // 返回值告诉 Pass Manager 这个 Pass 修改了什么 return PreservedAnalyses::all(); // 没修改任何东西(分析 Pass) // 或者 return PreservedAnalyses::none(); // 修改了 IR(变换 Pass) // → Pass Manager 会知道之前的分析结果失效了,需要重新计算 } }; |
PreservedAnalyses——Pass 之间的协调
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
问题: 分析 Pass A 计算了支配树(耗时) 变换 Pass B 修改了 IR 分析 Pass A 的结果还有效吗? 如果 Pass B 说"我没改控制流,只替换了指令" → 支配树还是有效的,不需要重新计算 如果 Pass B 说"我删除了一个基本块" → 支配树失效了,下次需要时必须重新计算 PreservedAnalyses 就是这个协调机制: PreservedAnalyses::all() "我什么都没改" → 所有分析结果都还有效 PreservedAnalyses::none() "我改了很多" → 所有分析结果都失效 PA.preserve<DominatorTreeAnalysis>() "我改了代码但没改控制流" → 支配树还有效 Pass Manager 根据这个信息决定: - 缓存哪些分析结果 - 什么时候需要重新计算 - 避免不必要的重复计算 |
Pass 之间的依赖关系
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// 变换 Pass 可以向 Pass Manager 请求分析结果 struct LICM : PassInfoMixin<LICM> { // 循环不变量外提 PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { // 请求循环信息(分析 Pass 的结果) auto &LI = AM.getResult<LoopAnalysis>(F); // 如果之前已经计算过且没有失效,直接返回缓存结果 // 否则自动运行 LoopAnalysis 计算 // 请求支配树(另一个分析 Pass 的结果) auto &DT = AM.getResult<DominatorTreeAnalysis>(F); // 请求别名分析 auto &AA = AM.getResult<AAManager>(F); // 用这些分析结果来做优化 for (Loop *L : LI) { for (Instruction &I : ...) { // 检查指令是否循环不变: // 1. 用 DT 确认指令所在块支配循环出口 // 2. 用 AA 确认指令没有可能冲突的内存访问 // 3. 如果都满足,把指令移到循环外面 } } // 我修改了代码但没改控制流结构 PreservedAnalyses PA; PA.preserve<DominatorTreeAnalysis>(); // 支配树还有效 // LoopAnalysis 可能被改了(移动了指令位置) return PA; } }; |
重要 Pass
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Pass 名称 做什么 你的代码中的体现 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ mem2reg alloca+load+store → SSA 寄存器 消除了参数的栈存取 instcombine 多条指令 → 更少/更简单的指令 add i32 3, 4 → 7 simplifycfg 消除无用的跳转、合并基本块 空的 if 分支被删除 inline 把被调用函数的代码复制到调用点 add 被内联进 main dce 删除结果没人用的指令 计算了但没用的值被删 sccp 稀疏条件常量传播 跨基本块的常量传播 gvn 全局值编号(更强的公共子表达式消除) 重复计算被消除 licm 循环不变量外提 循环内不变的计算移到循环外 loop-unroll 循环展开 短循环展开为线性代码 loop-vectorize 标量循环 → SIMD 向量操作 for 循环用 SSE/AVX 加速 globaldce 删除没有被调用的全局函数 内联后的 add 函数被删除 tailcallelim 尾调用优化 递归变成循环 reassociate 重新结合运算顺序 (a+b)+c → a+(b+c)(如果更优) |
结合代码
示例
|
1 2 3 4 5 6 7 |
``` ### 未优化的 `IR` ```C++ # 生成未优化的 IR clang++ -emit-llvm -S -O0 test.cpp -o step0.ll |
|
1 |
mem2reg
- 消除冗余的栈操作,构造
SSA
|
1 2 |
# Pass 1:mem2reg(消除冗余的栈操作,构造 SSA) opt --passes=mem2reg step0.ll -S -o step1.ll |
|
1 |
inline
- 函数内联
|
1 2 |
# Pass 2:inline(函数内联) opt --passes=inline step1.ll -S -o step2.ll |
|
1 |
instcombine
- 指令组合/常量折叠
|
1 2 |
# Pass 3:instcombine(指令组合/常量折叠) opt --passes=instcombine step2.ll -S -o step3.ll |
|
1 |
运行单个 Pass
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# 这是你学习 Pass 最有效的方式 # 1. 生成未优化的 IR clang++ -emit-llvm -S -O0 test.cpp -o input.ll # 2. 运行单个 Pass opt --passes=mem2reg input.ll -S -o output.ll # 3. 对比差异 diff input.ll output.ll # 常用的单独运行方式: opt --passes=mem2reg input.ll -S -o out.ll # SSA 构造 opt --passes=instcombine input.ll -S -o out.ll # 指令简化 opt --passes=simplifycfg input.ll -S -o out.ll # 控制流简化 opt --passes=inline input.ll -S -o out.ll # 函数内联 opt --passes=dce input.ll -S -o out.ll # 死代码消除 opt --passes=gvn input.ll -S -o out.ll # 全局值编号 opt --passes=licm input.ll -S -o out.ll # 循环不变量外提 opt --passes=loop-vectorize input.ll -S -o out.ll # 循环向量化 # 组合多个 Pass(按顺序执行) opt --passes="mem2reg,instcombine,dce" input.ll -S -o out.ll # 查看 Pass 运行前后的 IR 变化 opt --passes=instcombine --print-before-all --print-after-all input.ll -S -o out.ll 2>log.txt # log.txt 中会显示每个 Pass 执行前后的完整 IR # 查看 Pass 的运行统计 opt --passes=instcombine --stats input.ll -S -o out.ll 2>&1 | grep -i "instcombine" # 显示这个 Pass 做了多少次替换、删除了多少条指令等 |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Linux 高性能服务器编程:HTTP相关11/27
- ♥ Linux_命令大全 压缩备份03/16
- ♥ Linux » Linux 高性能服务器编程:信号12/12
- ♥ 各平台调试方法总结记述一09/25
- ♥ Linux 线程等待&&取消&&终止03/31
- ♥ Linux 高性能服务器编程:网络基础编程二11/28