调试与分析工作流
前面十五章覆盖了从
MLIR
核心概念到动手实践的完整旅程。这一篇是调试工具箱——与
第
08 章(Pass 机制)分工:08 讲 Pass 怎么写,16 讲 Pass
出了问题怎么查。以下命令在 MLIR 19.x
上可用;标志名随版本可能略有差异,以
mlir-opt --help 为准。
一、mlir-opt 的诊断命令
1.1 逐步观察 IR 变换
# 在每个 Pass 前打印 IR 状态
mlir-opt input.mlir \
--mlir-print-ir-before-all \
--canonicalize --cse --convert-scf-to-cf
# 只在特定 Pass 前后打印
mlir-opt input.mlir \
--mlir-print-ir-before=canonicalize \
--mlir-print-ir-after=canonicalize \
--canonicalize
# 打印 Pass 变换的 diff(仅展示被修改的 Op)
mlir-opt input.mlir \
--mlir-print-ir-after-change \
--canonicalize --cse1.2 Pass 计时与统计
# 打印每个 Pass 的 wall-clock 执行时间
mlir-opt input.mlir --mlir-timing \
--canonicalize --cse --convert-scf-to-cf
# 打印 Pass 统计(次数、Op 创建/删除数)
mlir-opt input.mlir --mlir-pass-statistics \
--canonicalize1.3 验证 IR
# 只解析和验证——不运行任何 Pass
mlir-opt input.mlir --mlir-print-ir-after-all -o /dev/null
# 显式运行验证 Pass
mlir-opt input.mlir -verify-diagnostics1.4 崩溃最小化
# 生成可重现崩溃的最小 IR
mlir-opt input.mlir \
--mlir-print-ir-on-crash \
--crash-reproducer=crash_repro.mlir \
--my-crashing-pass
# 重现崩溃
mlir-opt crash_repro.mlir --my-crashing-pass崩溃 reproducer 包含引发崩溃的 IR 片段和 Pass 配置,是提交 MLIR Bug Report 的最佳附件。
二、诊断 Pass 内的错误
2.1 Op 级别诊断
在 Pass 代码中,用 MLIR 的诊断 API 替代
llvm::outs():
// 正确做法——用 emitError/emitWarning/emitRemark
op->emitError() << "unexpected operand type: " << op.getType();
op->emitWarning() << "suboptimal pattern detected";
op->emitRemark() << "applied fusion";
// 不要用 llvm::outs()——它的输出无法被框架控制,也无法集成到 Pass 统计中2.2 定位 IR 中的非法构造
如果某个 Op 或 IR 结构未通过验证器,MLIR 会报告精确的诊断信息:
error: 'arith.addi' op operand #0 must be signless-integer-like, but got 'f32'
%0 = arith.addi %a, %b : f32
^
note: see current operation: %0 = "arith.addi"(%a, %b) : (f32, f32) -> f32
这类错误信息包括:Op 在源文件中的位置、违反的约束、以及 Op 的当前内容(便于 copy-paste)。
2.3 Op 的 print/dump
// 打印单个 Op(含其子 Region 的完整 IR)
op->dump();
// 打印 Op 及其立即上下文
op->print(llvm::errs());
llvm::errs() << "\n";
// 在 gdb/lldb 中
(gdb) call op->dump()
(gdb) call op->getOperand(0).dump()三、可视化 IR
3.1 文本 dump 与格式化
# 带颜色的文本输出
mlir-opt input.mlir --canonicalize | bat -l mlir
# 紧凑格式化(单行 Op)
mlir-opt input.mlir --mlir-elide-elementsattrs-if-larger=83.2 Graphviz 可视化
MLIR 提供了将 IR 的控制流图输出为 Graphviz DOT 格式的功能:
// C++ API:导出整个 Module 的 CFG 视图
moduleOp->dump();
op->dump();
// 一些工具如 mlir-viewer 可以将 .mlir 文件可视化为交互式图目前 MLIR 社区的图形化工具仍在发展中。第三方工具(如社区维护的 MLIR viewer 脚本)和 VS Code 的 MLIR 扩展提供了一定程度的交互式浏览能力——均属 B 级辅助工具,非 MLIR 官方发布件。
四、Pass 级别的调试技巧
4.1 隔离问题 Pass
# 二分法定位——依次添加 Pass,看哪个引入问题
mlir-opt input.mlir --pass1 -o step1.mlir # ✓ 正常
mlir-opt step1.mlir --pass2 -o step2.mlir # ✓ 正常
mlir-opt step2.mlir --pass3 -o step3.mlir # ✗ 出错——问题在 pass3
# 查看 pass3 前后的差异
diff <(mlir-opt step2.mlir --mlir-print-ir-generic) \
<(mlir-opt step2.mlir --pass3 --mlir-print-ir-generic)4.2 在特定 Op 上断点调试
// 在 gdb 中设置条件断点:只在特定的 func.func 上停止
(gdb) b mlir::detail::PassExecutor::run
// 或在你的 Pass 的 runOnOperation 中
(gdb) b MyPass::runOnOperation
(gdb) condition 1 op->getName().getStringRef() == "my_target_func"4.3 禁用部分优化隔离问题
# 禁用规范化以查看原始 IR 的降阶效果
mlir-opt input.mlir \
--my-pass \
--mlir-print-ir-after=my-pass \
-o /dev/null
# 先不加 -canonicalize 看原始的降阶结果,再加看优化后
mlir-opt input.mlir --my-pass -o after_lower.mlir
mlir-opt input.mlir --my-pass --canonicalize -o after_lower_canon.mlir
diff after_lower.mlir after_lower_canon.mlir五、mlir-tblgen 的调试
5.1 查看生成的代码
# 查看 ODS 生成的 C++ 声明(检查 TableGen 定义是否正确)
mlir-tblgen -gen-op-decls MyOps.td -I$INCLUDE_PATH
# 查看生成的 dialect 声明
mlir-tblgen -gen-dialect-decls MyDialect.td -I$INCLUDE_PATH
# 查看生成的 op 实现(构建器、验证器、解析/打印器)
mlir-tblgen -gen-op-defs MyOps.td -I$INCLUDE_PATH5.2 TableGen 错误定位
TableGen 的错误信息可能不够清晰。常见问题:
Record not defined→ 拼写错误或未 include 基类定义。Type constraint failed→ 类型 predicate 语法错误或类型名称不匹配。Duplicate record→ 在 include 守卫之外定义了同名 Op。
遇到难以理解的 TableGen 错误时,在 TableGen 中打印中间值:
// 调试用——打印 Record 内容(会出现在 stderr 中)
def : Assert<CPred<"false">, "debug: " # myVar>;
六、性能剖析
6.1 编译时间剖析
# 启用详细计时
mlir-opt input.mlir --mlir-timing --mlir-timing-display=list \
--my-pipeline
# 输出示例:
# === Timing ===
# Total Execution Time: 1.234s
# ---Pass Execution Timing---
# 0.452s (36.6%) Canonicalizer
# 0.310s (25.1%) ConvertLinalgToLoops
# 0.089s ( 7.2%) CSE6.2 运行时性能剖析
生成 LLVM IR 后,用标准的 LLVM 性能工具链:
# 使用 llc 生成目标代码并进行性能分析
mlir-translate -mlir-to-llvmir input.mlir | llc -O3 -o output.s
perf record ./a.out
perf report七、常见问题的诊断流程
| 症状 | 第一步检查 |
|---|---|
| Pass 运行后 IR 不变 | 检查 Pattern 是否被正确添加(检查
patterns.add<MyPattern>()) |
| 降阶后 IR 验证失败 | 用 --mlir-print-ir-after-all 定位出问题的
Pass |
| JIT 执行结果错误 | 将降阶后的 LLVM IR dump 出来对比预期 |
| Pass 崩溃(segfault) | 启用 --crash-reproducer 生成最小化重现 |
| 编译时间过长 | 用 --mlir-timing 定位耗时 Pass;检查
worklist 收敛性 |
| 类型转换失败 | 检查 TypeConverter 是否覆盖所有可能的类型 |
八、工具链总结
开发阶段:
mlir-opt (--mlir-print-ir-*) ← 观察每个 Pass 前后的 IR 状态
mlir-tblgen -gen-* ← 检查 TableGen 生成代码
op->dump() / op->emitError() ← C++ 诊断
调试阶段:
--crash-reproducer ← 崩溃最小化
gdb/lldb + op->dump() ← 断点定位
diff between passes ← IR 差异定位
优化阶段:
--mlir-timing ← Pass 耗时分布
--mlir-pass-statistics ← Pass 效果量化
LLVM perf toolchain ← 运行时性能分析
参考资料
官方文档(A 级)
- MLIR Debugging Tips — https://mlir.llvm.org/getting_started/Debugging/
- MLIR Pass Infrastructure — https://mlir.llvm.org/docs/PassManagement/
源码(A 级)
mlir/lib/Pass/PassTiming.cpp— Pass 计时实现mlir/lib/Pass/IRPrinting.cpp— IR dump 实现
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【编译器与 MLIR】环境搭建与第一个 MLIR 程序
从零构建 LLVM/MLIR 工程,用 mlir-opt 理解 .mlir 文本表示,运行规范化 Pass 并逐行解读转换结果,建立从命令行到 IR 变换的直觉。
【编译器与 MLIR】编译器的挑战与 IR 的裂变
从三阶段编译器局限出发,串联 Halide、XLA、TVM 的 IR 裂变,说明 DSA 与 AI 编译器为何需要 MLIR 这类可组合的多层 IR 框架。
【编译器与 MLIR】MLIR 全景图与设计哲学
从 Module-Operation-Region-Block 四层结构出发,系统讲解 MLIR 的三条核心设计原则:渐进降阶、方言可组合性、基础设施复用,配合 IREE、CIRCT、Torch-MLIR 等实际案例建立心智模型。
【编译器与 MLIR】操作、方言与 IR 的 C++ 表示
深入 Operation、Op、Value、Block、Region 的 C++ 内存布局与继承体系:CRTP 模板包装、SSA 值的两种来源、Use 链表的遍历方法。这是后续所有 Pass 写作的基础。