mlir文本格式是ir的结构化序列化表示,需先识别func/operation嵌套骨架、再抓ssa值与方言语义、最后结合类型和属性理解;tensor类型如tensor表2行3列张量,%开头为单赋值ssa名,.分隔方言命名空间,loc信息对调试至关重要。

mlir 文本格式不是“读一遍就懂”的线性代码,它本质是 IR 的**结构化序列化表示**,读它得先认出骨架、再抓关键字段、最后按 SSA 和方言语义串起来。
看懂 func 和 operation 的嵌套结构
MLIR 文本里没有缩进强制语义,但实际书写习惯会用缩进反映区域(Region)和块(Block)的层级。例如:
func.func @multiply_transpose(%arg0: tensor, %arg1: tensor) -> tensor {
%0 = toy.transpose(%arg0 : tensor) to tensor
%1 = toy.transpose(%arg1 : tensor) to tensor
%2 = toy.mul %0, %1 : tensor
toy.return %2 : tensor
}
这里 func.func 是一个操作(Operation),它的 body 是一个 Region;region 里只有一个 Block(隐式),block 以 toy.return 结尾。所有以 % 开头的是 SSA 值,每个只定义一次,后面直接用名字引用。
-
@multiply_transpose是函数名,属于func方言 -
toy.transpose和toy.mul属于toy方言,名称里的.是方言命名空间分隔符 -
to tensor这类不是类型声明,而是操作的属性或结果类型修饰,需查对应方言文档确认语义
识别 tensor 类型和维度写法
张量类型如 tensor 或 tensor 是 MLIR 中最常碰的类型,读法固定:
-
tensor:2 行 3 列的双精度浮点张量,顺序是行优先 -
tensor:动态形状张量,秩(dimension count)未知,运行时才确定 -
tensor<?x3xf64>:第一维动态(?),第二维固定为 3 - 注意:
tensor是一维张量(长度为 6),不是标量;标量是f64或i32
类型出现在冒号后,要么是操作数类型(如 (%arg0 : tensor)),要么是结果类型(如 -> tensor),也可能是操作内部显式标注(如 toy.transpose(%arg0 : tensor))。
区分 attribute、operand 和 result
一个 operation 的文本形式通常长这样:
%t_tensor = "toy.transpose" (%tensor) { inplace = true } : (tensor) -> tensor
拆开看:
-
%t_tensor:结果值名(SSA 名,仅用于阅读,不参与语义) -
"toy.transpose":操作名,带方言前缀,必须注册才能解析 -
(%tensor):操作数列表,必须是已定义的 SSA 值或 block 参数 -
{inplace = true}:属性字典,编译期常量,影响 lowering 行为(比如是否原地转置) -
: (…) -> …:类型签名,左边是输入类型元组,右边是输出类型;注意不是 C 风格函数声明,不绑定变量名
漏掉属性或类型不匹配,mlir-opt 或 toyc 会报类似 expected 'tensor' but got 'f64' 的错误,此时要回溯定义处查类型来源。
遇到 loc 和调试信息别跳过
真实生成的 MLIR 常带位置信息,形如:
loc("codegen.toy":5:3)
这不是装饰,是调试关键:
- 它指向原始源码位置,
codegen.toy第 5 行第 3 列 - 若用
-mlir-print-debuginfo编译,所有操作都会带loc;省略后就只剩 IR 结构,丢了溯源能力 - 在写自定义 pass 时,
op.getLoc()可用来生成带位置的错误提示,用户才看得懂哪行 toy 代码出了问题
没 loc 的 IR 看着干净,但一旦出错,你只能靠操作名和 SSA 值名去反推——这在复杂模块里几乎不可行。
toy.print 操作没被 lowering 到 LLVM,或者 tensor 在转换到 memref 时维度信息丢失。这些都得结合方言定义和 lowering 规则来读,而不是单看文本格式本身。











