[Mlir-commits] [mlir] [mlir][Affine] Account for eliminated stores in fusion cost (PR #220711)

Mehdi Amini llvmlistbot at llvm.org
Tue Sep 22 03:22:52 PDT 2026


https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/220711

>From 4af0cb11bf27aa2cdc8421dfaa4a4270b5eb4397 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Wed, 2 Sep 2026 07:20:13 -0700
Subject: [PATCH] [mlir][Affine] Account for eliminated stores in fusion cost

Charge eliminated stores to their enclosing source loops when computing a
single-iteration fusion slice. The extracted cost utility instead charged
the destination insertion loop, where the adjustment was overwritten before
use.

Pre-fix regression:

  error: unexpected remark: fusion compute cost: 2
  error: expected remark "fusion compute cost: 1" was not produced

Found by Coverity.

Assisted-by: Codex
---
 .../Dialect/Affine/Utils/LoopFusionUtils.cpp  | 15 ++++----
 mlir/test/Dialect/Affine/loop-fusion-3.mlir   | 10 +++---
 .../Affine/loop-fusion-compute-cost.mlir      | 16 +++++++++
 .../lib/Dialect/Affine/TestLoopFusion.cpp     | 35 +++++++++++++++++++
 4 files changed, 64 insertions(+), 12 deletions(-)
 create mode 100644 mlir/test/Dialect/Affine/loop-fusion-compute-cost.mlir

diff --git a/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp b/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp
index 68296ea3368a1f..fc0bb23138f7bc 100644
--- a/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp
+++ b/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp
@@ -596,15 +596,17 @@ bool mlir::affine::getFusionComputeCost(AffineForOp srcForOp,
   if (storeLoadFwdGuaranteed) {
     // Subtract from operation count the loads/store we expect load/store
     // forwarding to remove.
-    unsigned storeCount = 0;
+    auto subtractFromEnclosingLoop = [&](Operation &op) {
+      SmallVector<AffineForOp, 4> loops;
+      getAffineForIVs(op, &loops);
+      if (!loops.empty())
+        --computeCostMap[loops.back()];
+    };
     llvm::SmallDenseSet<Value, 4> storeMemrefs;
     srcForOp.walk([&](AffineWriteOpInterface storeOp) {
       storeMemrefs.insert(storeOp.getMemRef());
-      ++storeCount;
+      subtractFromEnclosingLoop(*storeOp);
     });
-    // Subtract out any store ops in single-iteration src slice loop nest.
-    if (storeCount > 0)
-      computeCostMap[insertPointParent] = -storeCount;
     // Subtract out any load users of 'storeMemrefs' nested below
     // 'insertPointParent'.
     for (Value memref : storeMemrefs) {
@@ -616,8 +618,7 @@ bool mlir::affine::getFusionComputeCost(AffineForOp srcForOp,
         // 'insertPointParent'.
         getAffineForIVs(*user, &loops);
         if (llvm::is_contained(loops, cast<AffineForOp>(insertPointParent))) {
-          if (auto forOp = dyn_cast_or_null<AffineForOp>(user->getParentOp()))
-            --computeCostMap[forOp];
+          subtractFromEnclosingLoop(*user);
         }
       }
     }
diff --git a/mlir/test/Dialect/Affine/loop-fusion-3.mlir b/mlir/test/Dialect/Affine/loop-fusion-3.mlir
index 70d6c82105543c..6f9bde51ee022a 100644
--- a/mlir/test/Dialect/Affine/loop-fusion-3.mlir
+++ b/mlir/test/Dialect/Affine/loop-fusion-3.mlir
@@ -1056,11 +1056,11 @@ func.func @reduce_add_non_innermost(%arg0: memref<64x64xf32, 1>, %arg1: memref<1
   }
   return
 }
-// Test checks the loop structure is preserved after sibling fusion.
-// CHECK:         affine.for
-// CHECK-NEXT:      affine.for
-// CHECK-NEXT:        affine.for
-// CHECK:            affine.for
+// The cost model accounts for eliminated stores and chooses maximal sibling
+// fusion.
+// CHECK-COUNT-3: affine.for
+// CHECK-NOT:     affine.for
+// CHECK:         return
 
 
 
diff --git a/mlir/test/Dialect/Affine/loop-fusion-compute-cost.mlir b/mlir/test/Dialect/Affine/loop-fusion-compute-cost.mlir
new file mode 100644
index 00000000000000..b99fd9a1ec697e
--- /dev/null
+++ b/mlir/test/Dialect/Affine/loop-fusion-compute-cost.mlir
@@ -0,0 +1,16 @@
+// RUN: mlir-opt %s -test-loop-fusion=test-fusion-compute-cost \
+// RUN:   -verify-diagnostics
+
+func.func @single_iteration_store_load(%input: memref<1xf32>, %value: f32) {
+  affine.for %i = 0 to 1 {
+    affine.store %value, %input[%i] : memref<1xf32>
+  }
+  // expected-remark at below {{fusion compute cost: 1}}
+  affine.for %j = 0 to 1 {
+    affine.for %k = 0 to 1 {
+      %loaded = affine.load %input[%k] : memref<1xf32>
+      %unused = arith.addf %loaded, %loaded : f32
+    }
+  }
+  return
+}
diff --git a/mlir/test/lib/Dialect/Affine/TestLoopFusion.cpp b/mlir/test/lib/Dialect/Affine/TestLoopFusion.cpp
index bf11d94596fa7f..59e8a64801fa2a 100644
--- a/mlir/test/lib/Dialect/Affine/TestLoopFusion.cpp
+++ b/mlir/test/lib/Dialect/Affine/TestLoopFusion.cpp
@@ -52,6 +52,11 @@ struct TestLoopFusion
       *this, "test-loop-fusion-utilities",
       llvm::cl::desc("Enable testing of loop fusion transformation utilities"),
       llvm::cl::init(false)};
+
+  Option<bool> clTestFusionComputeCost{
+      *this, "test-fusion-compute-cost",
+      llvm::cl::desc("Enable testing of the fusion compute cost"),
+      llvm::cl::init(false)};
 };
 
 } // namespace
@@ -154,6 +159,28 @@ static bool testLoopFusionUtilities(AffineForOp forOpA, AffineForOp forOpB,
   return false;
 }
 
+/// Computes and emits the cost of fusing `srcForOp` into `dstForOp`.
+static LogicalResult testFusionComputeCost(AffineForOp srcForOp,
+                                           AffineForOp dstForOp) {
+  LoopNestStats srcStats;
+  LoopNestStats dstStats;
+  if (!getLoopNestStats(srcForOp, &srcStats) ||
+      !getLoopNestStats(dstForOp, &dstStats))
+    return failure();
+
+  ComputationSliceState slice;
+  if (canFuseLoops(srcForOp, dstForOp, /*dstLoopDepth=*/1, &slice).value !=
+      FusionResult::Success)
+    return failure();
+
+  int64_t computeCost;
+  if (!getFusionComputeCost(srcForOp, srcStats, dstForOp, dstStats, slice,
+                            &computeCost))
+    return failure();
+  dstForOp->emitRemark() << "fusion compute cost: " << computeCost;
+  return success();
+}
+
 using LoopFunc = function_ref<bool(AffineForOp, AffineForOp, unsigned, unsigned,
                                    unsigned, unsigned)>;
 
@@ -198,6 +225,14 @@ void TestLoopFusion::runOnOperation() {
   // Gather all AffineForOps by loop depth.
   gatherLoops(getOperation(), depthToLoops);
 
+  if (clTestFusionComputeCost) {
+    if (depthToLoops.empty() || depthToLoops.front().size() != 2 ||
+        failed(testFusionComputeCost(depthToLoops.front()[0],
+                                     depthToLoops.front()[1])))
+      return signalPassFailure();
+    return;
+  }
+
   // Run tests on all combinations of src/dst loop nests in 'depthToLoops'.
   if (clTestDependenceCheck)
     iterateLoops(depthToLoops, testDependenceCheck);



More information about the Mlir-commits mailing list