flowchart TD
subgraph Module["ModuleOp (顶层容器)"]
subgraph Func["func.func @kernel(%arg0: f32) -> f32"]
subgraph Region["Region 0"]
subgraph Block0["Block ^bb0 (入口块)"]
Op1["%0 = arith.addf %arg0, %arg0 : f32"]
Op2["%1 = arith.mulf %0, %cst : f32"]
Term["func.return %1 : f32"]
end
end
end
end
Op1 -->|SSA定值传值| Op2
Op2 -->|SSA定值传值| Term
第 1 课:IR 层级、SSA 与 Region
参考答案版:包含完整代码实现与问答题深度参考解析。建议先独立完成练习题后再展开核对。
本课目标与完成标准
学完后你应能:读懂 operation/result/operand/region/block,并手写可被 mlir-opt 解析的函数。
通过必须同时满足:
- 独立补齐本课唯一的代码填空题,并通过给定检查;
- 三个问答题均说明因果链,而不是只报术语;
- 能指出至少一个正确性边界和一个性能取舍;
- 总分不低于 8/10,且没有一票否决级概念错误。
前置关系
- 课程前置:编译原理基础、C++ 阅读能力
- 本课在路线中的作用:MLIR 的统一节点是 Operation;Module、函数、循环都通过 operation 嵌套 region/block 表达,多结果 SSA 连接数据流。
核心心智模型
1. 它是什么,解决什么问题
在现代异构编译基础设施中,传统编译器的中间表示(如 LLVM IR)以一维指令流与基本块(Basic Block)CFG 为中心,虽然极其适合底层标量优化与机器码生成,但丢失了高层张量算子结构、循环多面体多维嵌套以及硬件特定语义。 为了统一从高层图计算(PyTorch / TensorFlow / JAX)到底层 GPU / NPU 汇编的全栈表示,MLIR(Multi-Level Intermediate Representation)提出了彻底递归正交的层次化抽象: 整个编译世界由一个统一的核心原语构成——Operation(操作)。 顶层模块(builtin.module)、函数(func.func)、仿射循环(affine.for)、结构化条件控制(scf.if)以及基本算术计算(arith.addi),在 MLIR 统一对象模型中全部是 Operation! 本课解决的核心工程问题是:理解 Operation、Region、Block 与 SSA(静态单赋值)的递归嵌套树状结构,掌握基于 Block Argument 的现代无 节点控制流,以及手写符合 MLIR 验证器(Verifier)严密契约的语法树。
2. 它如何工作
MLIR 递归嵌套层次与内存验证流程如图 1-1 与图 1-2 所示:
图 1-1:MLIR 嵌套包含层次 (Operation → Region → Block → Operation) 与 SSA 定值-引用链
flowchart LR
Source["MLIR 文本源码 (.mlir)"] --> Lexer["词法/语法解析 (AsmParser)"]
Lexer --> OpBuild["Operation 实例构建 & 操作数类型推导"]
OpBuild --> Verify["Dialect & Trait 合法性校验 (verify())"]
Verify --> SSA["SSA 符号表解析与 Block 前向引用绑定"]
SSA --> InMem["内存 IR 树形数据结构 (MLIRContext)"]
图 1-2:MLIR Operation 解析验证与内存模型装载全流程
统一递归包含层次(Operation Tree):
- Operation:语义执行单元。它可以拥有 0 或多个操作数(Operands)、产生 0 或多个静态单赋值返回值(SSA Results)、绑定一组编译期属性(Attributes)、拥有特定类型(Types),并嵌套 0 或多个 Region。
- Region:封装一个局部的控制流与局部作用域容器。例如
func.func的函数体是一个 Region,scf.if的 then/else 分支各是一个 Region。 - Block:Region 内部的基本块(如
^bb0)。Block 包含线性排列的 Operation 列表,并强制以终止操作(Terminator Operation,如func.return、scf.yield或cf.br)结尾。
消灭 节点的 Block Arguments(块参数控制流): 在传统 LLVM IR 中,基本块汇聚控制流必须依赖 节点(Phi Node),这极易在优化 Pass 移动指令时产生隐蔽的悬空引用 Bug。 MLIR 革命性地废弃了 节点,转而采用类似函数调用的块参数(Block Arguments):每个 Block 可以声明参数列表(如
^bb1(%x: i32):),上游块通过跳转指令cf.br ^bb1(%val)显式传参。文本解析与语义验证(Verifier Pipeline): 如图 1-2 所示,文本源码经过
AsmParser词法/语法解析生成内存中的 Operation AST。随后进入verify()阶段:验证所有操作数的类型、属性的合法性、支配关系(Dominance)、以及终止操作与外层 Region 的类型签名匹配。
3. 正确性条件与常见误区
SSA 值的严格支配约束(Dominance Invariant): 在同一个 Block 或存在嵌套关系的 Region 中,SSA 值必须先定值(Definition),后使用(Use)。这不仅是文本的书写顺序,更是支配树(Dominance Tree)的物理拓扑约束。未定义的 SSA 值无法通过符号绑定,会直接触发
use of undeclared SSA value诊断错误。孤儿块与终止指令缺失(Missing Terminator): 每个基本块(Block)的最后一条操作必须是具备
IsTerminatorTrait 的终止操作。若遗漏func.return或scf.yield,Verifier 会抛出block must end with a terminator语法校验失败。Region 隔离与符号可见性(IsolatedFromAbove Trait): 拥有
IsolatedFromAbove特征的操作(如func.func、builtin.module)其内部 Region 是完全封闭的,绝对不能直接捕获外层作用域的 SSA 变量。任何外部数据必须显式通过参数传递进入。
4. 性能与工程取舍
结构化控制流(Structured Control Flow)的分析优势: MLIR 倡导在前端阶段保留结构化控制流(如
scf.if、scf.for、affine.for),其循环边界和条件分支作为 Operation 的属性或操作数显式存在。这使得循环展开、仿射多面体变换、并行化分析极为简明,避免了过早将循环打散成 CFG 跳转图后导致难以逆向分析控制流拓扑。内存树开销 vs 表达力: 相比于扁平线性的传统三地址码,MLIR 在内存中维护的是一颗深度嵌套的 C++ 指针树(每个 Operation 包含 Operand 链表、Result 数组与 Region 向量)。这带来了一定的内存常数开销,但为多层级 Dialect 逐步降级(Lowering)提供了无可替代的灵活性。
具体演示
以文本指令 %sum = arith.addi %a, %b : i32 为例:
- Operation 标识:
arith.addi(隶属于算术方言arith的整数加法操作)。 - Operands:
%a与%b,必须为合法的 SSA 值且类型均为i32。 - Result:产生唯一的 SSA 变量
%sum,类型为i32。 - Generic 展开形式: 该语句的语法糖底层完全等价于通用规范形式:
"arith.addi"(%a, %b) : (i32, i32) -> i32通用格式暴露了 MLIR 一切皆 Operation 的统一内存模型本质。
请在阅读后先合上这一节,用自己的语言复述“输入状态 → 中间状态 → 输出状态”,再做练习。
实践任务:唯一代码填空题
补齐整数加法 operation 和返回值。
规则:只能修改 TODO/______ 所在位置;不要删除断言或放宽误差。代码注释说明了每个边界条件。
Code
%%writefile /tmp/lesson01.mlir
module {
func.func @add(%a: i32, %b: i32) -> i32 {
%sum = ______ %a, %b : i32
func.return ______ : i32
}
}检查方法
有 MLIR 工具链时运行 mlir-opt /tmp/lesson01.mlir -verify-diagnostics;否则逐项检查 def-use 和类型。
提交时请给出:补齐后的代码、实际运行输出(环境不可用时注明“仅静态审查”)以及对失败用例的解释。
Q1
不要背定义:请从输入、状态变化和输出三个阶段解释“IR 层级、SSA 与 Region”的工作机制。
你的答案:
Q2
把 %sum 在定义之前使用,为什么不仅是文本顺序问题?
你的答案:
Q3
如何在 IR 中表达 if 的两个分支都产生一个值?
你的答案:
评分与通过规则
- 代码 4 分:正常输入 2 分,边界输入 1 分,解释实现 1 分;
- Q1~Q3 各 2 分;
- 一票否决:结果碰巧正确但核心因果链错误、删除边界检查、把未运行结果说成实测。
需要提示时按四级机制请求:概念区域 → 具体方向 → 关键局部 → 完整答案。