region 包含 block,block 包含 operation;region 是逻辑作用域边界且可为空,block 是 ssa 基本单元且必须以 terminator 结尾。

Block 和 Region 在 MLIR 里不是同级概念,而是父子嵌套关系:Region 包含 Block,Block 包含 Operation。 它们常被混淆,是因为都涉及“代码容器”,但职责和约束完全不同——Region 是逻辑作用域边界,Block 是 SSA 控制流基本单元。
Region 必须显式创建,且可以为空
Region 是 Operation 的可选子结构,通过 getRegions() 获取,但它本身不直接存 Operation。你调用 op->getRegions()[0] 得到一个 Region 对象,但这个 Region 可能没有 Block(比如刚构造完 FuncOp 还没插入 body)。常见错误是直接对空 Region 调用 getBlocks().front(),触发段错误。
- Region 存在 ≠ 有 Block;必须用
region.empty()或region.getBlocks().empty()显式检查 - Region 没有“入口 Block”的强制语义(除非 dialect 特别约定,如
func.func要求第一个 Block 是入口) - 某些 Operation(如
arith.addi)根本不能带 Region;能否带由 operation 的 trait 决定
Block 必须属于某个 Region,且必须以 terminator 结尾
Block 是 Region 的子单位,不能独立存在。每个 Block 必须以 terminator operation(如 return、br、yield)结束,否则 IR 验证失败(verify 报错:block must terminate with a terminator operation)。这也是为什么你在写自定义 pass 时,往 Block 里插操作后忘了加 return,mlir-opt 会直接 abort。
- Block 参数(如
^bb0(%arg: i32))只在创建 Block 时声明,后续不能动态增减 - Block 内部的
Operation按顺序执行,但 SSA 值的作用域仅限于该 Block(除非被 phi-like operation 如yield传出) - 多个 Block 之间跳转靠 terminator 的 successor 列表,不是靠 label 名字硬编码
Region 和 Block 的实际构造顺序不能颠倒
构建 IR 时,顺序错一步就崩:先有 Operation,再给它分配 Region,再在 Region 里创建 Block,最后往 Block 里插 Operation。用 builder 写的话,典型流程是:
auto funcOp = builder.create<:funcop>(loc, "foo", type);
auto &bodyRegion = funcOp.getBody();
// 此时 bodyRegion 还是空的
auto block = builder.createBlock(&bodyRegion, {}, argTypes, argLocs); // 创建 Block 同时 append 到 Region
builder.setInsertionPointToStart(block);
builder.create<:constantop>(loc, value); // 插入 op
builder.create<:returnop>(loc, result); // terminator 不可少</:returnop></:constantop></:funcop>
- 漏掉
createBlock这步,bodyRegion就是空的,后续任何遍历都会 crash - 用
builder.createBlock时传错&bodyRegion(比如传成别的 Region 地址),Block 会被插到错误位置,IR 结构错乱 - Region 可以有多个 Block(如带条件分支的函数),但每个 Block 只能属于一个 Region
最易被忽略的一点:Region 的生命周期绑定在 Operation 上,而 Block 的生命周期绑定在 Region 上——这意味着你不能单独释放或移动 Block,也不能在 Region 被销毁后还持有它的 Block 指针。调试时若看到 use-after-free 或迭代器失效,八成是绕过了这层所有权链。











