[Mlir-commits] [mlir] [mlir][affine] Treat failed dependence checks conservatively (PR #211014)

Takayuki Todokoro llvmlistbot at llvm.org
Sat Jul 25 21:10:12 PDT 2026


https://github.com/takatodo updated https://github.com/llvm/llvm-project/pull/211014

>From 58adfdcb1eb0542803ada861317124ecbf9fbaa8 Mon Sep 17 00:00:00 2001
From: takatodo <takatodo1227 at gmail.com>
Date: Sun, 19 Jul 2026 17:57:36 +0900
Subject: [PATCH 1/2] [mlir][affine] Treat failed dependence checks
 conservatively

Reject tiling and loop permutation when Affine dependence analysis cannot construct a complete relation, and preserve the original loop nest when sequential-loop sinking encounters the same failure. Add regressions for a semi-affine same-memref dependence.

Fixes #210585

Assisted-by: OpenAI Codex
---
 .../Dialect/Affine/Analysis/AffineAnalysis.h  |  4 +--
 .../Affine/Analysis/AffineAnalysis.cpp        |  5 +++-
 .../Dialect/Affine/Analysis/LoopAnalysis.cpp  |  5 +++-
 mlir/lib/Dialect/Affine/Utils/LoopUtils.cpp   |  6 +++--
 .../Affine/loop-transformation-validity.mlir  | 25 +++++++++++++++++++
 5 files changed, 39 insertions(+), 6 deletions(-)
 create mode 100644 mlir/test/Dialect/Affine/loop-transformation-validity.mlir

diff --git a/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h b/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h
index 3e4b8648061ff..6bfa887e95fd1 100644
--- a/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h
+++ b/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h
@@ -190,8 +190,8 @@ inline bool noDependence(DependenceResult result) {
 
 /// Returns in 'depCompsVec', dependence components for dependences between all
 /// load and store ops in loop nest rooted at 'forOp', at loop depths in range
-/// [1, maxLoopDepth].
-void getDependenceComponents(
+/// [1, maxLoopDepth]. Returns failure if any dependence cannot be analyzed.
+LogicalResult getDependenceComponents(
     AffineForOp forOp, unsigned maxLoopDepth,
     std::vector<SmallVector<DependenceComponent, 2>> *depCompsVec);
 
diff --git a/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp b/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp
index 3d1a73417d1ea..aef5ec85858a7 100644
--- a/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp
@@ -694,7 +694,7 @@ DependenceResult mlir::affine::checkMemrefAccessDependence(
 
 /// Gathers dependence components for dependences between all ops in loop nest
 /// rooted at 'forOp' at loop depths in range [1, maxLoopDepth].
-void mlir::affine::getDependenceComponents(
+LogicalResult mlir::affine::getDependenceComponents(
     AffineForOp forOp, unsigned maxLoopDepth,
     std::vector<SmallVector<DependenceComponent, 2>> *depCompsVec) {
   // Collect all load and store ops in loop nest rooted at 'forOp'.
@@ -719,9 +719,12 @@ void mlir::affine::getDependenceComponents(
         DependenceResult result = checkMemrefAccessDependence(
             srcAccess, dstAccess, d, /*dependenceConstraints=*/nullptr,
             &depComps);
+        if (result.value == DependenceResult::Failure)
+          return failure();
         if (hasDependence(result))
           depCompsVec->push_back(depComps);
       }
     }
   }
+  return success();
 }
diff --git a/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp b/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp
index 40802cc6e85e5..2dbc1baa02236 100644
--- a/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp
@@ -524,8 +524,11 @@ bool mlir::affine::isTilingValid(ArrayRef<AffineForOp> loops) {
             srcAccess, dstAccess, d, /*dependenceConstraints=*/nullptr,
             &depComps);
 
+        if (result.value == DependenceResult::Failure)
+          return false;
+
         // Skip if there is no dependence in this case.
-        if (!hasDependence(result))
+        if (noDependence(result))
           continue;
 
         // Check whether there is any negative direction vector in the
diff --git a/mlir/lib/Dialect/Affine/Utils/LoopUtils.cpp b/mlir/lib/Dialect/Affine/Utils/LoopUtils.cpp
index 90bc57e950cf1..bb002229d7b50 100644
--- a/mlir/lib/Dialect/Affine/Utils/LoopUtils.cpp
+++ b/mlir/lib/Dialect/Affine/Utils/LoopUtils.cpp
@@ -1357,7 +1357,8 @@ bool mlir::affine::isValidLoopInterchangePermutation(
   // Gather dependence components for dependences between all ops in loop nest
   // rooted at 'loops[0]', at loop depths in range [1, maxLoopDepth].
   std::vector<SmallVector<DependenceComponent, 2>> depCompsVec;
-  getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec);
+  if (failed(getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec)))
+    return false;
   return checkLoopInterchangeDependences(depCompsVec, loops, loopPermMap);
 }
 
@@ -1466,7 +1467,8 @@ AffineForOp mlir::affine::sinkSequentialLoops(AffineForOp forOp) {
   // rooted at 'loops[0]', at loop depths in range [1, maxLoopDepth].
   unsigned maxLoopDepth = loops.size();
   std::vector<SmallVector<DependenceComponent, 2>> depCompsVec;
-  getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec);
+  if (failed(getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec)))
+    return forOp;
 
   // Mark loops as either parallel or sequential.
   SmallVector<bool, 8> isParallelLoop(maxLoopDepth, true);
diff --git a/mlir/test/Dialect/Affine/loop-transformation-validity.mlir b/mlir/test/Dialect/Affine/loop-transformation-validity.mlir
new file mode 100644
index 0000000000000..b57e9ddcc357b
--- /dev/null
+++ b/mlir/test/Dialect/Affine/loop-transformation-validity.mlir
@@ -0,0 +1,25 @@
+// RUN: mlir-opt %s -test-loop-permutation="permutation-map=1,0 check-validity=1" | FileCheck %s
+// RUN: mlir-opt %s -affine-loop-tile="tile-size=4" | FileCheck %s
+
+#dynamic_index = affine_map<()[s0, s1] -> (s0 * s1)>
+
+// Dependence analysis cannot represent the common semi-affine index. The
+// remaining indices carry a (1, -1) dependence, so both transforms must fail.
+// CHECK-LABEL: func.func @unknown_dependence
+func.func @unknown_dependence(
+    %A: memref<?x9x9xi32>, %B: memref<9x9xi32>,
+    %p: index, %q: index, %value: i32) {
+  // CHECK:      affine.for %[[I:.*]] = 1 to 8 {
+  // CHECK-NEXT:   affine.for %[[J:.*]] = 1 to 8 {
+  affine.for %i = 1 to 8 {
+    affine.for %j = 1 to 8 {
+      %z = affine.apply #dynamic_index()[%p, %q]
+      // CHECK: affine.store %{{.*}}, %{{.*}}[%{{.*}}, %[[I]], %[[J]]]
+      affine.store %value, %A[%z, %i, %j] : memref<?x9x9xi32>
+      %loaded = affine.load %A[%z, %i - 1, %j + 1]
+          : memref<?x9x9xi32>
+      affine.store %loaded, %B[%i, %j] : memref<9x9xi32>
+    }
+  }
+  return
+}

>From 5789f7fdc90c045761a5d96d643633394bc1e6e3 Mon Sep 17 00:00:00 2001
From: takatodo <takatodo1227 at gmail.com>
Date: Sun, 26 Jul 2026 11:26:07 +0900
Subject: [PATCH 2/2] [mlir][affine] Distinguish possible from proven
 dependences

---
 .../mlir/Dialect/Affine/Analysis/AffineAnalysis.h    | 12 +++++++++---
 mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp  |  2 +-
 mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp    |  2 +-
 mlir/lib/Dialect/Affine/Utils/Utils.cpp              |  2 +-
 mlir/test/lib/Analysis/TestMemRefDependenceCheck.cpp |  2 +-
 5 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h b/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h
index 6bfa887e95fd1..a001be9c42346 100644
--- a/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h
+++ b/mlir/include/mlir/Dialect/Affine/Analysis/AffineAnalysis.h
@@ -176,12 +176,18 @@ DependenceResult checkMemrefAccessDependence(
     SmallVector<DependenceComponent, 2> *dependenceComponents = nullptr,
     bool allowRAR = false);
 
-/// Utility function that returns true if the provided DependenceResult
-/// corresponds to a dependence result.
-inline bool hasDependence(DependenceResult result) {
+/// Returns true if the provided DependenceResult proves that a dependence
+/// exists.
+inline bool mustHaveDependence(DependenceResult result) {
   return result.value == DependenceResult::HasDependence;
 }
 
+/// Returns true unless the provided DependenceResult proves that no dependence
+/// exists.
+inline bool mayHaveDependence(DependenceResult result) {
+  return result.value != DependenceResult::NoDependence;
+}
+
 /// Returns true if the provided DependenceResult corresponds to the absence of
 /// a dependence.
 inline bool noDependence(DependenceResult result) {
diff --git a/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp b/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp
index aef5ec85858a7..f86c5e0e8421e 100644
--- a/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/AffineAnalysis.cpp
@@ -721,7 +721,7 @@ LogicalResult mlir::affine::getDependenceComponents(
             &depComps);
         if (result.value == DependenceResult::Failure)
           return failure();
-        if (hasDependence(result))
+        if (mustHaveDependence(result))
           depCompsVec->push_back(depComps);
       }
     }
diff --git a/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp b/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp
index 68296ea3368a1..984015943251e 100644
--- a/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp
+++ b/mlir/lib/Dialect/Affine/Utils/LoopFusionUtils.cpp
@@ -229,7 +229,7 @@ static unsigned getMaxLoopDepth(ArrayRef<Operation *> srcOps,
         // TODO: Cache dependence analysis results, check cache here.
         DependenceResult result =
             checkMemrefAccessDependence(srcAccess, dstAccess, d);
-        if (hasDependence(result)) {
+        if (mayHaveDependence(result)) {
           // Store minimum loop depth and break because we want the min 'd' at
           // which there is a dependence.
           loopDepth = std::min(loopDepth, d - 1);
diff --git a/mlir/lib/Dialect/Affine/Utils/Utils.cpp b/mlir/lib/Dialect/Affine/Utils/Utils.cpp
index 7043083298615..ec69aba8619f5 100644
--- a/mlir/lib/Dialect/Affine/Utils/Utils.cpp
+++ b/mlir/lib/Dialect/Affine/Utils/Utils.cpp
@@ -646,7 +646,7 @@ static bool mustReachAtInnermost(const MemRefAccess &srcAccess,
       getNumCommonSurroundingLoops(*srcAccess.opInst, *destAccess.opInst);
   DependenceResult result =
       checkMemrefAccessDependence(srcAccess, destAccess, nsLoops + 1);
-  return hasDependence(result);
+  return mustHaveDependence(result);
 }
 
 /// Returns true if `srcMemOp` may have an effect on `destMemOp` within the
diff --git a/mlir/test/lib/Analysis/TestMemRefDependenceCheck.cpp b/mlir/test/lib/Analysis/TestMemRefDependenceCheck.cpp
index b3b9a590773b0..b08e1e7cd1df0 100644
--- a/mlir/test/lib/Analysis/TestMemRefDependenceCheck.cpp
+++ b/mlir/test/lib/Analysis/TestMemRefDependenceCheck.cpp
@@ -89,7 +89,7 @@ static void checkDependences(ArrayRef<Operation *> loadsAndStores) {
         if (result.value == DependenceResult::Failure) {
           srcOpInst->emitError("dependence check failed");
         } else {
-          bool ret = hasDependence(result);
+          bool ret = mustHaveDependence(result);
           // TODO: Print dependence type (i.e. RAW, etc) and print
           // distance vectors as: ([2, 3], [0, 10]). Also, shorten distance
           // vectors from ([1, 1], [3, 3]) to (1, 3).



More information about the Mlir-commits mailing list