块参数是只读入口绑定,非计算结果载体;真正传递结果靠操作的SSA型result,如scf.yield馈入下一次迭代参数、linalg.yield结束region执行,func.func返回值须由return显式声明。

MLIR 中块参数(Block Argument)不是用来“传递计算结果”的
这是个常见误解。块参数(BlockArgument)在 MLIR 中只在块(Block)入口处被**定义和绑定**,由父操作(如 func.func、scf.for、linalg.generic)在控制流进入时**一次性提供值**,之后不可修改,也不用于“返回”或“传出”。真正承载计算结果的是操作的 result,它们是 SSA 值,通过操作之间的数据依赖链流动。
哪些操作会用块参数?典型场景和绑定方式
块参数只出现在带区域(Region)的操作中,且必须由该操作显式声明并传入。常见情况包括:
-
func.func:函数参数直接映射为入口块(entry block)的块参数,例如@add(%arg0: i32, %arg1: i32)中的%arg0和%arg1是块参数,值来自函数调用方 -
scf.for:循环变量(%i)和迭代器初始值/步进值(如%init)作为块参数传入循环体块,其中%init的值来自上一次迭代的yield结果(注意:这不是“返回”,而是scf.yield将值**馈入下一次迭代的块参数**) -
linalg.generic:索引映射(indexing_maps)决定输入张量如何映射到块参数,例如逐元素加法中,两个输入张量的当前元素会分别绑定为块参数%a、%b,供 region 内部使用
想把计算结果交给下一个块?用 yield + 块参数链,而不是赋值
在有循环或条件结构的区域里,“传递结果”靠的是 scf.yield 或 cf.br 等终止操作显式指定下一跳块的参数值。例如:
scf.for %i = %c0 to %c4 step %c1 iter_args(%acc = %c0) -> (i32) {
%new_acc = arith.addi %acc, %i : i32
scf.yield %new_acc : i32 // 这行把 %new_acc 绑定给下一次迭代块的 %acc 参数
}
这里 %acc 是每次迭代体块的块参数,它的值完全由前一次 scf.yield 提供;你不能在块内写 %acc = ... 来修改它——它是只读绑定。
容易踩的坑:混淆块参数、操作结果和临时变量
初学者常犯的错误:
- 试图对块参数做
arith.addi后再存回同名变量(%x = arith.addi %x, %y)——语法上可能通过,但语义错误:%x是只读绑定,这实际创建了一个新 SSA 值,旧%x不再被使用,容易导致逻辑断裂 - 在
linalg.generic的 region 里用std.return想“返回值”——不合法,region 内只能用linalg.yield,且它不产生新值,只是结束 region 执行 - 以为
func.func的块参数可以被函数内部“改写后传出”,实际上函数返回值必须通过return操作显式声明,其操作数必须是某次计算产生的result
真正需要“传出结果”的地方,永远依赖操作的 result 字段,而不是块参数的重绑定。块参数只是入口契约,不是可变容器。

















