Pass 管理与分析
前面的第二部分覆盖了 IR 的数据结构。从这一篇开始进入变换层——如何写 Pass 来分析和变换 IR。
MLIR 的 Pass 基础设施复用了 LLVM 的成熟经验(PassManager、AnalysisManager、PassInstrumentation),但针对多方言和 Region 嵌套做了关键扩展。
一、MLIR Pass 与 LLVM Pass 的模型差异
从 LLVM Pass 转到 MLIR Pass,核心差异有三个:
1. 没有 FunctionPass / ModulePass 的多级继承
LLVM 有
FunctionPass、ModulePass、LoopPass
等按作用域分级的 Pass 类型。MLIR 统一使用
OperationPass<OpT>——你只需要声明 Pass
操作的 Op 类型。mlir-opt 在执行时会自动遍历 IR
找到所有目标 Op 并依次运行 Pass:
OperationPass<func::FuncOp>:对每个func.func运行一次——等价于 LLVM 的 FunctionPass。OperationPass<ModuleOp>:对整个 Module 运行一次——等价于 LLVM 的 ModulePass。
这种统一性来自 MLIR 的”一切皆 Op”设计。循环体是 Op、函数是 Op、Module 也是 Op——不存在需要特殊分类的 IR 单元。
2. Pass 是局部作用域的
OperationPass<arith::AddIOp>
只能访问和修改 arith::AddIOp 的 Region 内部的
IR。它看不到 sibling Op 或外层
Module。这种局部性既是限制也是保证——它使得 Pass
的并行执行成为可能,且 Pass 之间的耦合最小。
3. AnalysisManager 是嵌套的
每个 Pass 运行时获得一个
AnalysisManager,它与 Pass 的作用域对应。对
func::FuncOp 的 Pass 分析函数内部的数据流;对
ModuleOp 的 Pass 做跨函数分析。
二、编写一个 OperationPass
以一个简单的 Pass 为例——统计 arith::AddIOp
的数量:
// include/MyPass/CountAddOps.h
#include "mlir/Pass/Pass.h"
namespace mlir {
namespace my {
class CountAddOpsPass : public PassWrapper<CountAddOpsPass,
OperationPass<func::FuncOp>> {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CountAddOpsPass)
void runOnOperation() override;
// Pass 选项(可通过命令行传递)
// Option<bool> verbose{*this, "verbose",
// llvm::cl::desc("Enable verbose output"), llvm::cl::init(false)};
};
} // namespace my
} // namespace mlir// lib/MyPass/CountAddOps.cpp
#include "CountAddOps.h"
void CountAddOpsPass::runOnOperation() {
func::FuncOp func = getOperation(); // 获取当前 Pass 作用的 Op
int count = 0;
func.walk([&](arith::AddIOp addOp) {
count++;
});
llvm::outs() << "Function '" << func.getName()
<< "' has " << count << " arith.addi ops\n";
}
// 注册 Pass
std::unique_ptr<Pass> mlir::my::createCountAddOpsPass() {
return std::make_unique<CountAddOpsPass>();
}关键点:
PassWrapper<CountAddOpsPass, OperationPass<func::FuncOp>>:CRTP 包装——MLIR Pass 基础设施要求这一层模板包装,用于类型擦除和多态。MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID:LLVM 的 RTTI 替代方案生成的类型 ID。每个 Pass 类必须有这一行。runOnOperation():Pass 的入口。getOperation()返回目标的func::FuncOp。walk:深度优先遍历 IR 树,对每个 Op 调用 lambda。
2.1 注册 Pass 到命令行
// 在 Pass 注册文件中
namespace {
// 将 Pass 注册到 mlir-opt 的命令行
struct CountAddOpsPassRegistration
: public PassRegistration<CountAddOpsPass> {
// Pass 的文本参数:mlir-opt --count-add-ops
// 参数名会自动从 CamelCase 转为 kebab-case
};
} // namespace注册后即可通过命令行运行:
mlir-opt input.mlir --count-add-ops三、Pass 的依赖管理与流水线
3.1 分析依赖
一个 Pass 可以声明它依赖某个分析(Analysis)。MLIR 的
AnalysisManager 保证:
- 当 Pass 请求一个分析时,如果尚未计算则创建并缓存它;
- 如果一个 Pass 修改了 IR(通过
markAnalysesPreserved告知),缓存的分析被正确失效。
// 声明依赖的分析
void CountAddOpsPass::runOnOperation() {
// 请求分析——如果之前被计算过且仍然有效,返回缓存的
auto &domInfo = getAnalysis<DominanceInfo>();
// 使用 domInfo ...
}
// 告知哪些分析不会被此 Pass 破坏
// (默认情况下,修改 IR 的 Pass 会使所有分析失效)
void runOnOperation() override {
// 做不可变的操作——不需要修改任何分析
markAllAnalysesPreserved(); // 保留所有分析
// 或
// markAnalysesPreserved<DominanceInfo>(); // 只保留特定分析
}3.2 Pass 流水线
mlir-opt 允许串联多个 Pass:
mlir-opt input.mlir \
--canonicalize \
--cse \
--convert-scf-to-cf \
--convert-cf-to-llvm \
--convert-func-to-llvmPassManager 也可以在 C++ 中程序化构建:
PassManager pm(&ctx);
pm.addPass(createCanonicalizerPass());
pm.addPass(createCSEPass());
pm.addNestedPass<func::FuncOp>(createConvertSCFToCFPass());
if (failed(pm.run(moduleOp))) {
// 处理失败
}OpPassManager 支持嵌套——你可以指定一个 Pass
只对特定类型的 Op 内部的 Region 运行:
OpPassManager &funcPM = pm.nest<func::FuncOp>();
funcPM.addPass(createMyFunctionLevelPass());3.3 多线程 Pass 执行
MLIR 的 PassManager 支持多线程执行。对于
OperationPass<func::FuncOp>,如果 IR
中有多个独立的
func.func,它们可以在不同线程上并行运行——因为每个
Pass 实例只能看到自己的 func::FuncOp。
PassManager pm(&ctx);
pm.enableMultithreading(); // 启用多线程
// 默认由环境变量或硬件并发度决定线程数
// 也可手动指定:pm.getContext()->setNumThreads(4);多线程的安全性由 OperationPass 的作用域隔离保证——不依赖全局锁。
四、Pass 的 IR 修改通知与 Analysis 保全
当一个 Pass 修改 IR 时,它需要告诉框架哪些修改是”安全的”——即哪些分析仍然有效:
void runOnOperation() override {
// 移除了一个操作
op->erase();
// 告诉框架:支配树和循环信息已经失效
// 但其他分析可以保留
markAnalysesPreserved<CallGraph>(); // CallGraph 未被影响
// 如果没有显式标记,框架假设所有分析都已失效
}这个机制在 LLVM 中也存在但在 MLIR 中更为敏感——因为 MLIR
的 Pass 可以嵌套运行,一个内层 Pass 修改了
scf.for 的内部
Region,不确定是否影响外层的分析。
五、调试 Pass 流水线
Pass 调试的常用 mlir-opt
标志(--mlir-print-ir-before-all、--mlir-print-ir-after-change、--mlir-timing、--crash-reproducer
等)在 第
16 章 有完整操作指南和诊断流程表。本节只保留与 Pass
机制直接相关的两点:
- 定位变换来源:用
--mlir-print-ir-after=<pass-name>确认是哪个 Pass 引入了非法 IR。 - 崩溃复现:用
--crash-reproducer生成最小.mlir,便于提交 Issue 或二分定位。
六、Pass 的注册与发现
在 CMake 中注册 Pass 库:
add_mlir_library(MLIRMyPass
CountAddOps.cpp
LINK_LIBS PUBLIC
MLIRPass
MLIRFuncDialect
MLIRArithDialect
)这样 mlir-opt
在链接到这个库后,就能自动发现并注册
--count-add-ops 选项。
七、本篇后续
Pass 管理是编译流水线的骨架。下一章进模式重写(Pattern Rewrite)——Pass 内部最重要的变换机制:如何匹配 IR 模式并替换为等价的更优表示。
参考资料
官方文档(A 级)
- MLIR Pass Infrastructure — https://mlir.llvm.org/docs/PassManagement/
- MLIR Passes — https://mlir.llvm.org/docs/Passes/
源码(A 级)
mlir/include/mlir/Pass/Pass.hmlir/include/mlir/Pass/PassManager.hmlir/include/mlir/Pass/AnalysisManager.h
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【编译器与 MLIR】环境搭建与第一个 MLIR 程序
从零构建 LLVM/MLIR 工程,用 mlir-opt 理解 .mlir 文本表示,运行规范化 Pass 并逐行解读转换结果,建立从命令行到 IR 变换的直觉。
【编译器与 MLIR】模式重写与规范化框架
深入 MLIR 的模式重写引擎:RewritePattern 的驱动模型、matchAndRewrite 的匹配与替换协议、规范化(Canonicalization)规则的编写、折叠与代数简化的最佳实践,以及 GreedyPatternRewriteDriver 的工作机制。
【编译器与 MLIR】AI 时代的编译器基础设施
从三阶段编译器局限出发,系统讲解 MLIR 方言、渐进降阶与 Pass 基础设施,覆盖 Tensor/Linalg/Affine/GPU 到框架桥接的完整编译链。
【编译器与 MLIR】编译器的挑战与 IR 的裂变
从三阶段编译器局限出发,串联 Halide、XLA、TVM 的 IR 裂变,说明 DSA 与 AI 编译器为何需要 MLIR 这类可组合的多层 IR 框架。