scf 到 cf 的转换必须显式注册 pattern,因 mlir 不提供自动降级;需调用 mlir::cf::populatescftocontrolflowconversionpatterns 并确保 cf dialect 合法,否则 scf 操作残留导致 legalization 失败。

scf 到 cf 的转换不是“自动发生”的,必须显式调用对应 pattern 集合,且需确保目标 dialect 合法性已配置。
为什么不能直接用 applyFullConversion 就完事
因为 applyFullConversion 本身不决定“怎么转”,它只执行你传入的 patterns 和 target。如果你没把 scf → cf 的 rewrite pattern 加进去,scf 操作就保持原样,转换会失败或卡住(报 Failed to legalize operation 'scf.if' 类错误)。
常见错误现象:scf.if、scf.for 等操作未被处理,lowering 中断;或 fallback 到非法状态,触发 assertion。
关键点在于:scf 是高级结构化控制流,cf 是底层跳转原语(br、cond_br),二者语义层级不同,MLIR 不提供默认“自动降级”——必须靠显式模式注册。
怎么注册 scf → cf 的转换 pattern
使用 MLIR 提供的标准 pattern 工厂函数:mlir::cf::populateSCFToControlFlowConversionPatterns。
实操要点:
- 该函数必须在构建
RewritePatternSet时调用,且要在applyFullConversion前完成 - 参数是
patterns和getContext()(不是module或rewriter) - 不需要手动写
scf.if→cf.cond_br的具体逻辑,pattern 内部已封装好展开规则(如 if 展开为 cond_br + br + block merge) - 注意依赖:该 pattern 集要求
cfdialect 在 target 中被标记为 legal,否则 legalization 阶段会拒绝
示例片段:
mlir::RewritePatternSet patterns(&getContext()); mlir::cf::populateSCFToControlFlowConversionPatterns(patterns, &getContext()); mlir::ConversionTarget target(getContext()); target.addLegalDialect<:cf::controlflowdialect>(); // ← 这行不能漏 target.addLegalOp<:moduleop>(); if (failed(applyFullConversion(module, target, std::move(patterns)))) return failure();</:moduleop></:cf::controlflowdialect>
scf → cf 转换后还剩什么?哪些操作不会动
这个转换只处理 scf 方言的操作,其他方言(如 arith、affine、toy)完全不受影响。它不碰类型、不改数据流、不优化循环结构——只是把 scf.for 拆成 cf.br + cf.cond_br + block 参数传递的显式跳转序列。
典型输出结构:
-
scf.if→ 生成两个新 block,用cf.cond_br分支,末尾用cf.br合并到共同后继 -
scf.for→ 生成初始化、条件判断、循环体、增量更新四个 block,用cf.br和cf.cond_br串起来 -
scf.while→ 类似,但条件判断 block 位于循环入口,支持提前退出
⚠️ 容易被忽略的点:转换后 block 的参数绑定方式变了。scf 的 region 参数会映射为 cf block 的 operand,若下游要继续 lowering 到 LLVM,需确保 LLVMTypeConverter 能正确处理这些 operand 类型(尤其是 memref 传参)。
能不能跳过 cf 直接 scf → LLVM?
能,而且更常见。MLIR 官方推荐路径是 scf → arith/std → LLVM,而不是先降到 cf 再到 LLVM。
原因:
-
mlir::cf::populateControlFlowToLLVMConversionPatterns也能处理cf.cond_br,但它生成的是 rawllvm.br,缺乏结构信息,不利于后续 LLVM 优化 -
scf自带 loop nest 结构,配合affine可做多面体优化;一旦降到cf,结构就扁平化了,优化机会丢失 - 多数实际 lowering 流程(如 Toy Tutorial)都绕过 cf,直接用
mlir::cf::populateSCFToControlFlowConversionPatterns是为了调试或对接自定义 backend,不是必须步骤
所以除非你在写一个需要精确控制跳转指令生成的硬件 backend,否则别主动引入 cf 层——它增加了中间表示复杂度,却没带来编译优势。











