dialect conversion的核心三要素是conversiontarget、conversionpattern和typeconverter:前者声明合法终点,后者定义非法op到合法op的映射逻辑,typeconverter则处理类型映射以避免rewriter创建失败。

直接上手 Dialect Conversion 不需要先搞懂整个 MLIR 生态,但必须明确一点:它不是“自动降级”,而是你手动定义「哪些操作不合法」「用什么替代」「怎么替」——漏掉任一环,ConversionTarget 就会报错或静默失败。
什么是 Dialect Conversion 的核心三要素
它本质是一套约束驱动的重写机制,不是模板生成器。三个组件缺一不可:
-
ConversionTarget:声明哪些 dialect 或 operation 是“合法终点”,其余全是待转换的非法操作 -
ConversionPattern:每个 pattern 对应一个非法 op → 合法 op 的映射逻辑(比如toy.transpose→affine.for+memref.load/store) -
TypeConverter(可选但常用):当 op 的输入/输出类型要变(如TensorType→MemRefType),得靠它做类型映射,否则重写时rewriter.create会因类型不匹配崩溃
为什么 addLegalDialect 后还是报 “illegal operation”
常见错在只加了目标 dialect,却没处理“中间状态”。例如你设了 target.addLegalDialect<affinedialect standardopsdialect></affinedialect>,但实际 lowering 过程中产生了 scf.for —— 它属于 SCFDialect,而你没声明它合法,就会卡住。
- 检查报错里具体是哪个 op 被判为 illegal,用
op->getDialect()->getName()打印名字,再决定是否addLegalDialect或addLegalOp - 别迷信“标准 dialect 自动合法”:LLVM IR dialect 默认不合法,
LLVM::LLVMFuncOp必须显式addLegalOp - 函数签名、module-level op(如全局变量声明)容易被忽略,它们不属于任何 function body,需单独处理
写 ConversionPattern 时最常踩的坑
Pattern 不是自由发挥的 rewrite 函数,它受 PatternRewriter 生命周期和插入点严格约束:
- 不能在 pattern 里调用
rewriter.setInsertionPointToStart(module.getBody())—— module 不在当前 op 的 parent scope 内,会 crash;改用rewriter.getInsertionBlock()->getParentOp()向上找最近合法容器 -
rewriter.replaceOp必须确保新 op 的 result 数量、类型、use 链完全兼容旧 op,否则 verifier 直接 abort;建议先用rewriter.create构造,再用rewriter.replaceAllUsesWith拆解替换 - 涉及循环嵌套(如把
toy.matmul降成affine.for)时,别手动拼affine.apply,优先用buildAffineLoopNest辅助函数,避免 index 维度错位
调试 Dialect Conversion 的实际手段
官方文档不提,但真实开发中离不了:
- 加
target.markUnknownOpDynamicallyLegal([](Operation *op) { llvm::errs() getName() 看哪些 op 被扫到又拒绝 - 在 pattern 的
matchAndRewrite开头打日志:llvm::errs() ,确认是否命中 - 用
mlir-opt --pass-pipeline="builtin.module(func.func(toy-to-affine-lowering))"单步跑 pass,比整个 toyc 流程快十倍
真正难的从来不是写通第一个 pattern,而是当 toy.print 和 toy.transpose 共存时,如何让前者降成 printf 调用、后者降成内存循环,且两者类型系统不打架——这要求你在 TypeConverter 里对不同 op 做分支判断,而不是一刀切映射。











