scf.for 是 mlir 中显式带范围、步长和归纳变量的 ssa 操作,非 c 风格语法糖;其基本结构为:scf.for %i = %lb to %ub step %step { ... },其中 %i 为只读归纳变量。

scf.for 的基本结构长什么样
MLIR 里的 scf.for 不是 C 风格的 for 循环语法糖,它是一个显式带范围、步长和归纳变量的 SSA 操作。你不能写 for (int i = 0; i 这种形式;必须拆成三部分:起始值、终止值、步长,全部作为操作数传入。
最简合法形式是:
scf.for %i = %lb to %ub step %step {
// 循环体
}
其中 %lb、%ub、%step 必须是 index 类型,且 %ub 是**不包含**的上界(即循环执行条件为 i )。
-
%i是归纳变量(induction variable),类型自动推导为index,只在循环体内可见 - 循环体必须是单个
scf.for区域(region),不能为空;哪怕只做一次迭代也要写个scf.yield - 如果需要返回值(比如累加结果),得用
scf.reduce或手动带出yield值,scf.for本身不返回值
为什么 scf.for 里不能直接用 arith.constant 当边界
常见错误是这么写:
scf.for %i = %c0 to %c10 step %c1 { ... }
然后报错:operand #0 of 'scf.for' must be of type 'index' —— 因为 %c0 默认是 i64 或 i32,不是 index。MLIR 对类型非常严格,尤其在控制流中。
正确做法是显式转成 index:
%c0_idx = arith.index_cast %c0 : i64 to index
%c10_idx = arith.index_cast %c10 : i64 to index
scf.for %i = %c0_idx to %c10_idx step %c1_idx { ... }
- 更推荐用
arith.constant直接声明index类型常量:%c0 = arith.constant 0 : index - 步长也必须是
index,%c1 = arith.constant 1 : index才合法 - 运行时动态边界(比如来自 memref.dim)也得是
index类型,否则编译器拒绝 lowering
嵌套 scf.for 怎么写才不踩多面体优化的坑
MLIR 的 affine 和 linalg 优化链(比如 tiling、fusion)依赖循环结构可分析。如果你手写 scf.for 嵌套但没对齐仿射约束,后续 pass 可能直接跳过或报 not a valid affine map。
关键点:
- 所有边界和步长必须是**静态仿射表达式**:只能含常量、
index参数、加减乘(乘仅限常量系数),不能有除法、取模、条件分支 - 避免用
memref.load结果当循环上界——那会变成运行时值,破坏仿射性 - 典型安全写法是把维度信息提前提取并 cast 成
index:%dim = memref.dim %A, %c0 : memref<?x ?xf32> -> index - 如果真要动态上界又想保留优化机会,改用
scf.while+ 显式条件判断,但会丢失多面体分析能力
scf.for 和 linalg.generic 的边界选择冲突怎么解
当你从 linalg.generic lower 到 scf.for 时,编译器自动生成的循环边界可能和你手写的不一致,尤其在分块(tiling)后。比如 linalg 要求 K 维按 16 分块,但你的 scf.for 步长写死 %c8,就会导致访存越界或计算漏项。
根本原因在于:linalg 的迭代空间是语义化的(比如 “遍历 A 的 M×K 和 B 的 K×N”),而 scf.for 是底层结构化的。二者衔接靠 linalg.tiled_loop 或 scf.forall 中间表示过渡。
- 别直接手写
scf.for替代 linalg lowering;优先用linalg.tile+linalg.fuse生成结构化循环 - 若必须介入,检查 lowering 后 IR 中
scf.for的lowerBound/upperBound属性是否被affine.min包裹——那是编译器在处理不整除情形(如 size=65, tile=32 → 最后一块 size=1) - 手动验证每层循环的步长是否与目标硬件分块参数对齐(AVX2 是 M=2/N=32,AMX 是 M=32/N=32);错配会导致向量化失败
真正难的不是写出合法的 scf.for,而是让它既满足类型和仿射约束,又能被后续 lowering pass 识别为可优化的结构——边界怎么算、步长怎么设、哪些值该提前 materialize,这些细节一旦错位,后面所有向量化、bufferization、codegen 都会连锁失效。











