为何-affine-scalrep无法消除MLIR内层循环中的冗余加载/存储?
一、scalrep Pass忽略该情况的核心原因
1. 内存分配位置的限制
Affine Scalar Replacement(-affine-scalrep)默认优先处理循环体内部分配的临时MemRef,而你的代码中%alloc是在函数最外层分配的全局临时内存,不属于循环内部分配的范畴,这会导致Pass跳过对它的标量替换逻辑。
2. 访问模式的识别限制
%alloc是3D MemRef,但在最内层%arg5循环中始终访问第二维为0的固定位置,这种“降维”的访问模式没有被scalrep的模式匹配逻辑覆盖。Pass需要明确识别出该内存位置在整个%arg5循环迭代中是唯一、无外部修改的候选,当前实现可能未处理这种多维度但固定某一维的访问场景。
3. 别名分析的隐含条件
scalrep需要确保目标MemRef的所有访问无别名、无跨循环的外部依赖。虽然你的代码中%alloc的访问逻辑满足这一点,但Pass的别名分析可能未显式确认该MemRef没有被其他代码路径访问,因此未触发替换。
二、消除冗余内存操作的替代方案
1. 调整Pass顺序并启用scalrep扩展模式
先运行结构简化Pass清理Affine IR,再启用scalrep的向量操作扩展:
mlir-opt -affine-simplify-structures -affine-scalar-replacement -scalar-replacement-for-vector-ops your_code.mlir
扩展模式会增强对非标准访问模式的识别,覆盖你这种固定维度的MemRef访问场景。
2. 使用MemRef数据流优化Pass
-memref-dataflow-opt专门针对MemRef的数据流冗余进行优化,能精准识别循环内的累积式load/store操作并替换为SSA值,非常适合你的类GEMM累加逻辑:
mlir-opt -memref-dataflow-opt your_code.mlir
该Pass无需依赖内存分配位置,直接基于数据流分析消除冗余内存操作。
3. 手动调整循环内部分配(可选)
将%alloc的分配移动到%arg4循环内部,并降为1D MemRef(因为每个%arg3+%arg4迭代仅使用单一位置),让scalrep更容易识别为循环内临时内存:
affine.for %arg3 = 0 to 8 { affine.for %arg4 = 0 to 64 { %tmp = memref.alloc() : memref<1xf32> %0 = affine.load %arg2[0, %arg4] : memref<1x64xf32> affine.store %0, %tmp[0] : memref<1xf32> affine.for %arg5 = 0 to 16 { %3 = affine.load %arg0[%arg3, %arg5] : memref<8x16xf32> %4 = affine.load %arg1[%arg5, %arg4] : memref<16x64xf32> %5 = affine.load %tmp[0] : memref<1xf32> %6 = arith.mulf %3, %4 : f32 %7 = arith.addf %5, %6 : f32 affine.store %7, %tmp[0] : memref<1xf32> } %1 = affine.load %tmp[0] : memref<1xf32> %2 = arith.maximumf %1, %cst : f32 affine.store %2, %alloc_0[%arg3, %arg4] : memref<8x64xf32> memref.dealloc %tmp : memref<1xf32> } }
调整后再运行-affine-scalrep即可顺利将内存操作替换为SSA值。
内容的提问来源于stack exchange,提问作者Adwaid Suresh

