MLIR是独立于LLVM的编译器基础设施,核心在于Dialect(模块化单元)、Operation(带语义的SSA计算节点)和Progressive Lowering(多层语义精炼),而非类比LLVM的学习路径。

MLIR 不是 LLVM 的升级版,也不是“LLVM 2.0”,它是独立设计、与 LLVM 并存的编译器基础设施。入门时若照搬 LLVM 学习路径(比如先啃 LLVM IR、PassManager、BasicBlock),反而会卡在抽象 mismatch 上——因为 MLIR 的核心范式完全不同。
先搞懂 Dialect 是什么,而不是急着写 Pass
几乎所有初学者第一次看 toy.transpose 这类操作时都懵:这既不像 C 也不像 LLVM 指令,更不像 AST 节点。Dialect 就是破局点——它不是语法糖,而是 MLIR 的模块化单元,每个 Dialect 定义自己的一组 Operation、Type 和 Attribute。比如 arith 方言管基础算术,linalg 管张量运算,affine 管循环嵌套。不理解 Dialect,就等于没拿到 MLIR 的钥匙。
-
Dialect名字必须带点号(如toy.transpose),前缀即命名空间,避免跨方言冲突 - 一个
Operation可以属于不同Dialect,但不能跨Dialect直接复用语义(比如linalg.matmul和arith.mul语义完全不兼容) - 官方
toy教程里所有.ch1到.ch7的演进,本质就是逐步引入新Dialect并建立它们之间的 lowering 关系
把 Operation 当作“带语义的 SSA 值”,别当指令看
Operation 表面像 LLVM 的 Instruction,但行为差异极大:它可含 region(代码块嵌套)、可无返回值、可携带自定义属性字典(如 {inplace = true}),甚至能表达控制流结构(scf.for、scf.if)。把它当成“有类型、有属性、有作用域的计算节点”更准确。
- 每个
Operation的输入输出都是 SSA 值,但类型系统比 LLVM 更灵活(支持tensor<2x3xf64>这种带形状的类型) - 属性(
Attribute)是 compile-time 常量,不能被优化 pass 改写;而 operand(操作数)才是 runtime 可变的依赖项 - region 是关键:没有 region 的
Operation(如arith.addf)是纯函数式;带 region 的(如func.func)才真正承载控制流和作用域
放弃“一次降到 LLVM IR”的执念,接受 Progressive Lowering
LLVM 的流程是:前端 → LLVM IR → 一堆优化 → MachineInstr。MLIR 不同:Progressive Lowering 意味着你得主动设计多层 IR,并为每一层写对应的 lowering pass。比如 toy 教程中,toy.transpose 先降到 linalg 层,再降到 affine,最后到 llvm。这不是冗余步骤,而是保留语义做优化的必要代价。
- lowering 不是“翻译”,而是“语义精炼”:每降一层,丢弃一部分高层信息(如 shape 推导结果),换回更低层的表达能力(如内存访问模式)
- 工具链里
mlir-opt的--pass-pipeline参数本质就是在拼接这些 lowering 阶段,顺序错一点就可能失败(比如先跑convert-linalg-to-loops再跑convert-affine-to-std就会报错) - 调试时别只看最终 LLVM IR;用
mlir-opt --print-ir-after-all观察每层 IR 变化,比单看输入输出更容易定位 lowering 断点
Dialect 当配置、把 Operation 当指令、把 Lowering 当翻译——这三者一旦错位,后续所有 TableGen、PatternRewrite、Interface 都会变成黑盒。先花两小时把 toy.ch2 的 emitMlir 函数手敲一遍,比通读五篇文档更管用。

















