土法炼钢兴趣小组的算法知识备份

【编译器与 MLIR】调试与分析工作流

文章导航

分类入口
compilerarchitecture
标签入口
#mlir#llvm#compiler#debugging#mlir-opt#diagnostics#performance#visualization

目录

调试与分析工作流

前面十五章覆盖了从 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 --cse

1.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 \
  --canonicalize

1.3 验证 IR

# 只解析和验证——不运行任何 Pass
mlir-opt input.mlir --mlir-print-ir-after-all -o /dev/null

# 显式运行验证 Pass
mlir-opt input.mlir -verify-diagnostics

1.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=8

3.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_PATH

5.2 TableGen 错误定位

TableGen 的错误信息可能不够清晰。常见问题:

遇到难以理解的 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%) CSE

6.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 级)

源码(A 级)

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-09 · compiler / architecture

【编译器与 MLIR】MLIR 全景图与设计哲学

从 Module-Operation-Region-Block 四层结构出发,系统讲解 MLIR 的三条核心设计原则:渐进降阶、方言可组合性、基础设施复用,配合 IREE、CIRCT、Torch-MLIR 等实际案例建立心智模型。

2026-06-09 · compiler / architecture

【编译器与 MLIR】操作、方言与 IR 的 C++ 表示

深入 Operation、Op、Value、Block、Region 的 C++ 内存布局与继承体系:CRTP 模板包装、SSA 值的两种来源、Use 链表的遍历方法。这是后续所有 Pass 写作的基础。


By .