pass添加顺序即执行顺序,因passmanager用std::vector存储并按插入序遍历;无隐式调度,须显式控制,如canonicalize必须在cse前,nest仅限作用域不改序。

Pass 添加顺序直接决定执行顺序
MLIR 的 Pass 流水线没有隐式调度逻辑,passManager.addPass() 的调用顺序就是实际执行顺序。这不是“建议”,而是底层实现决定的:PassManager 内部维护一个 std::vector<:unique_ptr>></:unique_ptr>,遍历时严格按插入顺序执行。
常见错误是误以为可以通过 Pass 名称、类型或依赖声明自动排序——MLIR 不提供类似 GCC 的 pass 插槽(如 “early-opt”、“late-opt”)机制,也没有全局拓扑排序。所有顺序控制必须显式编码在 C++ 构建逻辑里。
- 多个 Pass 作用于同一层级(比如都注册在
func::FuncOp上),它们之间无隐式依赖,谁先加谁先跑 - 若某 Pass 生成的 IR 是另一个 Pass 的输入前提(例如先
Canonicalizer再CSE),就必须手动保证前者在后者之前添加 - 跨层级 Pass(如 Module 级 Pass 和 Func 级 Pass)也遵循同样规则;Module 级 Pass 先执行完,才会递归进入其子 Region 调用 Func 级 Pass
如何在 iree-opt / mlir-opt 中复现特定顺序
命令行工具通过 -p 或 --passes 指定流水线,参数值是逗号分隔的 Pass 名字符串,顺序即执行顺序。例如:
iree-opt -p="canonicalize,cse,iree-stream-convert-to-stream" input.mlir
这等价于在 C++ 中依次调用:
passManager.addPass(mlir::createCanonicalizerPass());<br>passManager.addPass(mlir::createCSEPass());<br>passManager.addPass(IREE::Stream::createConvertToStreamPass());
注意:iree-opt 和 mlir-opt 的内置 Pass 名不一定完全一致(如 iree-stream-convert-to-stream 是 IREE 自定义 Pass,而 canonicalize 是 MLIR 标准 Pass),具体名称需查对应 dialect 的 registerPasses() 实现或文档。
- 不支持在命令行中嵌套层级(如 “func.func(cse)”),只能靠 Pass 自身的
nest行为控制作用域 - 若某个 Pass 需要配置参数(如
canonicalize的maxIterations),命令行需用冒号语法:canonicalize:max-iterations=3 - 调试时可用
--print-ir-before-all或--print-ir-after=<code>pass-name观察每步输出,验证顺序是否符合预期
嵌套 Pass(nest)和作用域控制的实际影响
nest<:funcop>()</:funcop> 这类调用不是“改变顺序”,而是限定作用域:它把后续添加的 Pass 包裹进一个作用域节点,使它们只对 func::FuncOp 及其子树生效。该嵌套节点本身在流水线中仍占一个位置,且其内部 Pass 按添加顺序执行。
例如这段 C++ 代码:
passManager.addPass(createCanonicalizerPass());<br>passManager.nest<:funcop>().addPass(createCSEPass());<br>passManager.addPass(createSymbolDCEPass());</:funcop>
执行顺序是:1. 全局 canonicalize → 2. 进入每个 func::FuncOp 并在其内部运行 cse → 3. 全局 symbol-dce。其中第 2 步的 cse 不会作用于 module-level 的 global op,也不会被提前到第 1 步之前。
-
nest不改变外层顺序,只插入一个“作用域容器”,容器内 Pass 仍受自身添加顺序约束 - 同一个
nest<t>()</t>块里可以添加多个 Pass,它们之间仍按添加顺序执行 - 不要试图用多层
nest来模拟条件分支——MLIR PassManager 不支持运行时跳过某段流水线;需要条件逻辑,得在 Pass 内部判断,或由上层构建器动态拼接passManager
容易被忽略的隐式顺序依赖点
有些 Pass 看似独立,实则强依赖前序 Pass 的副作用。最典型的是 SymbolDCEPass:它只删未被 symbol_table::lookup 引用的符号,但如果前面没运行 canonicalize 或 inline,很多本可被消除的引用仍残留,导致 DCE 失效。
另一个易错点是 Verify*Pass:它们不改 IR,但失败时直接 abort。如果放在流水线中间(如 canonicalize 后、cse 前),就能卡住非法 IR;但如果放最后,可能掩盖前面 Pass 引入的问题。
- IREE 中的
VerifyInitializationOrderPass必须在所有初始化相关 Pass(如FuseGlobals)之前,否则校验必然失败 -
ApplyPatterns类 Pass(如OptimizeIntArithmetic)通常需在canonicalize之后,否则模式匹配成功率低 - 自定义 Pass 若读取了
Operation::getAttr("some_key"),要确认前面已有 Pass 写入该属性,否则空指针或断言失败
顺序不是写完就完的事,得结合 IR 形态变化和 Pass 的契约来推演——多数崩溃或结果异常,根源不在 Pass 本身,而在它前面缺了一个该有的 canonicalize 或 symbol-dce。











