最直接方式是用 mlir-opt 配合 -print-ir-before/-print-ir-after 指定 Pass 名(需双引号、大小写敏感),并注意作用域仅限紧邻的 Pass;多 Pass 需分别标注,配合 -debug-only=pass 可追踪执行顺序与层级。

用 mlir-opt 加 -pass-pipeline 看单个 Pass 的输入输出
最直接的方式是把 MLIR 文件喂给 mlir-opt,显式指定要跑的 Pass,并用 -print-ir-before 和 -print-ir-after 控制打印时机。比如你想看 canonicalizer 对某个 .mlir 文件做了什么:
mlir-opt -print-ir-before="canonicalizer" -print-ir-after="canonicalizer" -canonicalizer input.mlir
注意两点:一是 -print-ir-before 和 -print-ir-after 后面必须跟双引号括起来的 Pass 名(大小写敏感,和源码里注册的名称一致);二是它们只对紧随其后、在命令行中实际执行的那个 Pass 生效——如果你写了多个 Pass,得分别加标记。
常见错误现象:
- 漏掉引号,比如写成
-print-ir-before=canonicalizer,会报错unknown option - 把 Pass 名写成
CanonicalizerPass或createCanonicalizerPass(),实际应该用canonicalizer - 用了
-verify-each却没配-allow-unregistered-dialect,遇到自定义 Dialect 就直接 abort
用 -debug-only=pass 查看整个 Pipeline 的执行顺序和 IR 变化点
当你跑一整条 Pipeline(比如 iree-opt 或自定义的 mlir-opt -pass-pipeline),光看首尾不够,需要知道每个 Pass 是不是真被调用了、在哪个层级生效。这时启用调试日志更有效:
mlir-opt -debug-only=pass -pass-pipeline="func.func(canonicalizer, cse)" input.mlir 2>&1 | grep -E "(Running|after)"
输出里会出现类似 Running canonicalizer on func.func 和 after canonicalizer 的行,配合 -print-ir-before-all / -print-ir-after-all 就能定位到具体哪一步 IR 开始变。
关键细节:
-
-debug-only=pass输出默认走 stderr,记得重定向或用2>&1合并到 stdout 才能管道过滤 - 它不打印 IR 内容,只打“正在进/出哪个 Pass”,真正看 IR 还得靠
-print-ir-*系列 - 如果 Pipeline 里有嵌套(比如
func.func(...)),-debug-only=pass会显示层级,但-print-ir-before默认只作用于外层,要作用到内层需显式写func.func(canonicalizer)
手写 Pass 时在 runOnOperation 里插 getOperation().print(llvm::errs())
调试自己写的 C++ Pass 时,最可控的方式是在 runOnOperation 开头和结尾手动打印当前 Operation。比如你写了一个处理 func::FuncOp 的 Pass:
void MyPass::runOnOperation() {
getOperation().print(llvm::errs() << "Before MyPass:\n");
// ... your rewrite logic
getOperation().print(llvm::errs() << "After MyPass:\n");
}
这样能绕过 mlir-opt 的 IR 打印机制,确保看到的是内存中真实状态,尤其适合验证 Pattern Rewrite 是否真的修改了 Op 树结构。
容易踩的坑:
- 忘记加
\n导致多行输出挤在一起,建议统一用llvm::errs() << "label:\n"开头 - 在
PatternRewriter的matchAndRewrite里直接 print,可能因多次匹配导致重复输出,优先放在runOnOperation级别 - 如果 IR 很大,用
getOperation().print(llvm::errs(), mlir::OpPrintingFlags().useLocalScope())缩短变量名,避免刷屏
为什么 -print-ir-before-all 有时不生效
这个 flag 看似方便,但实际经常“静默失败”。根本原因是:它只对当前 Pass Manager 中显式添加的 Pass 生效,而很多工具(如 iree-opt)内部用的是分阶段构建的 Pass Manager,有些 Pass 是在子 PM 里 add 的,-print-ir-before-all 压根触达不到。
解决方案很实在:
- 先用
--mlir-print-op-on-failure确认 IR 是否合法,排除语法错误干扰 - 改用
-pass-pipeline显式写出你要观察的 Pipeline,再配-print-ir-before/-print-ir-after - 查工具源码里
buildXXXPassPipeline函数,确认目标 Pass 的确切名称和嵌套层级(比如 IREE 里Stream阶段的 Pass 常带stream.前缀)
最常被忽略的一点:MLIR 的 Pass 执行是 lazy 的,-print-ir-before 不会强制触发未被依赖的 Pass,所以一定要确保你写的 Pass 真正在 Pipeline 中被执行——哪怕只是加个 verify 或 canonicalize 在前面占位。

















