[Mlir-commits] [mlir] [MLIR][Affine] Add MDG edges between accesses to aliasing memref views (PR #237726)

Sajid Zubair llvmlistbot at llvm.org
Sun Nov 1 05:06:33 PST 2026


https://github.com/Sajid-Zubair created https://github.com/llvm/llvm-project/pull/237726

MemRefDependenceGraph::init only adds memory dependence edges between nodes that access the same memref SSA value. Accesses through a view of a memref (e.g., memref.subview, memref.cast) were therefore never related to accesses of the underlying memref. As a result, affine-loop-fusion could move a loop nest that writes a memref past a load or store through a view of it, silently changing program results.

Conservatively add an edge between any two nodes that access different views of the same base memref (found by looking through ViewLikeOpInterface ops) when either of them may write to or free its view. Existing edges are left unchanged, so code without views is unaffected.

Fixes: #236555

Assisted by : Opus 5.5

>From 0f784391b92bbc3be55f4e214ca2bcedeb54e45e Mon Sep 17 00:00:00 2001
From: Sajid-Zubair <sajid10zubair at gmail.com>
Date: Sun, 1 Nov 2026 18:35:13 +0530
Subject: [PATCH] [MLIR][Affine] Add MDG edges between accesses to aliasing
 memref views

MemRefDependenceGraph::init only adds memory dependence edges between
nodes that access the same memref SSA value. Accesses through a view of a
memref (e.g., memref.subview, memref.cast) were therefore never related to
accesses of the underlying memref. As a result, affine-loop-fusion could
move a loop nest that writes a memref past a load or store through a view
of it, silently changing program results.

Conservatively add an edge between any two nodes that access different
views of the same base memref (found by looking through
ViewLikeOpInterface ops) when either of them may write to or free its
view. Existing edges are left unchanged, so code without views is
unaffected.

Fixes: #236555
---
 mlir/lib/Dialect/Affine/Analysis/Utils.cpp    |  48 +++++++++
 .../Dialect/Affine/loop-fusion-views.mlir     | 100 ++++++++++++++++++
 2 files changed, 148 insertions(+)
 create mode 100644 mlir/test/Dialect/Affine/loop-fusion-views.mlir

diff --git a/mlir/lib/Dialect/Affine/Analysis/Utils.cpp b/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
index ebe932a14694ab..5dd82917c91ed7 100644
--- a/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
@@ -21,6 +21,7 @@
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
 #include "mlir/Dialect/Utils/StaticValueUtils.h"
 #include "mlir/IR/IntegerSet.h"
+#include "mlir/Interfaces/ViewLikeInterface.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/Support/Debug.h"
@@ -186,6 +187,18 @@ static void getEffectedValues(Operation *op, SmallVectorImpl<Value> &values) {
   };
 }
 
+/// Returns the memref that `memref` is a view of, looking through any chain of
+/// view-like ops (e.g., memref.subview, memref.cast). Returns `memref` itself
+/// if it isn't defined by a view-like op.
+static Value getBaseMemRef(Value memref) {
+  while (auto viewOp = memref.getDefiningOp<ViewLikeOpInterface>()) {
+    if (memref != viewOp.getViewDest())
+      break;
+    memref = viewOp.getViewSource();
+  }
+  return memref;
+}
+
 /// Add `op` to MDG creating a new node and adding its memory accesses (affine
 /// or non-affine to memrefAccesses (memref -> list of nodes with accesses) map.
 static Node *
@@ -446,6 +459,41 @@ bool MemRefDependenceGraph::init(bool fullAffineDependences) {
       }
     }
   }
+  // Different memref values can be views of the same underlying memref (e.g.,
+  // via memref.subview or memref.cast), but the edges added above only relate
+  // accesses on the same memref value. Conservatively add an edge between any
+  // two nodes accessing different views of the same base memref if either of
+  // them may write to or free its view.
+  DenseMap<Value, SmallVector<Value>> baseToMemRefs;
+  for (auto &memrefAndList : memrefAccesses)
+    baseToMemRefs[getBaseMemRef(memrefAndList.first)].push_back(
+        memrefAndList.first);
+  for (auto &baseAndMemRefs : baseToMemRefs) {
+    ArrayRef<Value> memrefs = baseAndMemRefs.second;
+    for (unsigned a = 0, e = memrefs.size(); a < e; ++a) {
+      for (unsigned b = a + 1; b < e; ++b) {
+        for (unsigned idA : memrefAccesses[memrefs[a]]) {
+          Node *nodeA = getNode(idA);
+          bool aWrites =
+              nodeA->hasStore(memrefs[a]) || nodeA->hasFree(memrefs[a]);
+          for (unsigned idB : memrefAccesses[memrefs[b]]) {
+            if (idA == idB)
+              continue;
+            Node *nodeB = getNode(idB);
+            bool bWrites =
+                nodeB->hasStore(memrefs[b]) || nodeB->hasFree(memrefs[b]);
+            if (!aWrites && !bWrites)
+              continue;
+            // Edges go from the node earlier in the block to the later one.
+            if (nodeA->op->isBeforeInBlock(nodeB->op))
+              addEdge(idA, idB, memrefs[a]);
+            else
+              addEdge(idB, idA, memrefs[b]);
+          }
+        }
+      }
+    }
+  }
   return true;
 }
 
diff --git a/mlir/test/Dialect/Affine/loop-fusion-views.mlir b/mlir/test/Dialect/Affine/loop-fusion-views.mlir
new file mode 100644
index 00000000000000..ff0d91b521d08b
--- /dev/null
+++ b/mlir/test/Dialect/Affine/loop-fusion-views.mlir
@@ -0,0 +1,100 @@
+// RUN: mlir-opt %s -affine-loop-fusion -split-input-file | FileCheck %s
+
+// Accesses through views (memref.subview, memref.cast, ...) of a memref written
+// by a loop nest must keep their order relative to that loop nest.
+
+// CHECK-LABEL: func @load_through_subview_between_loops
+func.func @load_through_subview_between_loops(%in: memref<4xf32>, %buf: memref<4xf32>, %out: memref<4xf32>) -> f32 {
+  affine.for %i = 0 to 4 {
+    %v = affine.load %in[%i] : memref<4xf32>
+    affine.store %v, %buf[%i] : memref<4xf32>
+  }
+  %c0 = arith.constant 0 : index
+  %sv = memref.subview %buf[1] [2] [1] : memref<4xf32> to memref<2xf32, strided<[1], offset: 1>>
+  %x = memref.load %sv[%c0] : memref<2xf32, strided<[1], offset: 1>>
+  affine.for %i = 0 to 4 {
+    %v = affine.load %buf[%i] : memref<4xf32>
+    %w = arith.addf %v, %v : f32
+    affine.store %w, %out[%i] : memref<4xf32>
+  }
+  return %x : f32
+}
+// The fused loop nest must be placed before the load through the subview.
+// CHECK:       affine.for
+// CHECK:       }
+// CHECK-NOT:   affine.for
+// CHECK:       memref.load
+// CHECK-NOT:   affine.for
+// CHECK:       return
+
+// -----
+
+// CHECK-LABEL: func @store_through_subview_between_loops
+func.func @store_through_subview_between_loops(%in: memref<4xf32>, %buf: memref<4xf32>, %out: memref<4xf32>, %x: f32) {
+  affine.for %i = 0 to 4 {
+    %v = affine.load %in[%i] : memref<4xf32>
+    affine.store %v, %buf[%i] : memref<4xf32>
+  }
+  %c0 = arith.constant 0 : index
+  %sv = memref.subview %buf[1] [2] [1] : memref<4xf32> to memref<2xf32, strided<[1], offset: 1>>
+  memref.store %x, %sv[%c0] : memref<2xf32, strided<[1], offset: 1>>
+  affine.for %i = 0 to 4 {
+    %v = affine.load %buf[%i] : memref<4xf32>
+    affine.store %v, %out[%i] : memref<4xf32>
+  }
+  return
+}
+// The store through the subview must stay between the two loop nests.
+// CHECK:       affine.for
+// CHECK:       memref.store
+// CHECK:       affine.for
+
+// -----
+
+// CHECK-LABEL: func @load_through_cast_and_subview_between_loops
+func.func @load_through_cast_and_subview_between_loops(%in: memref<4xf32>, %buf: memref<4xf32>, %out: memref<4xf32>) -> f32 {
+  affine.for %i = 0 to 4 {
+    %v = affine.load %in[%i] : memref<4xf32>
+    affine.store %v, %buf[%i] : memref<4xf32>
+  }
+  %c0 = arith.constant 0 : index
+  %cast = memref.cast %buf : memref<4xf32> to memref<?xf32>
+  %sv = memref.subview %cast[1] [2] [1] : memref<?xf32> to memref<2xf32, strided<[1], offset: 1>>
+  %x = memref.load %sv[%c0] : memref<2xf32, strided<[1], offset: 1>>
+  affine.for %i = 0 to 4 {
+    %v = affine.load %buf[%i] : memref<4xf32>
+    %w = arith.addf %v, %v : f32
+    affine.store %w, %out[%i] : memref<4xf32>
+  }
+  return %x : f32
+}
+// Chained views are traced back to the base memref.
+// CHECK:       affine.for
+// CHECK:       }
+// CHECK-NOT:   affine.for
+// CHECK:       memref.load
+// CHECK-NOT:   affine.for
+// CHECK:       return
+
+// -----
+
+// Sibling loops reading %buf must not be fused across a store through a view
+// of %buf.
+// CHECK-LABEL: func @sibling_loops_across_store_through_subview
+func.func @sibling_loops_across_store_through_subview(%buf: memref<4xf32>, %out0: memref<4xf32>, %out1: memref<4xf32>, %x: f32) {
+  affine.for %i = 0 to 4 {
+    %v = affine.load %buf[%i] : memref<4xf32>
+    affine.store %v, %out0[%i] : memref<4xf32>
+  }
+  %c0 = arith.constant 0 : index
+  %sv = memref.subview %buf[1] [2] [1] : memref<4xf32> to memref<2xf32, strided<[1], offset: 1>>
+  memref.store %x, %sv[%c0] : memref<2xf32, strided<[1], offset: 1>>
+  affine.for %i = 0 to 4 {
+    %v = affine.load %buf[%i] : memref<4xf32>
+    affine.store %v, %out1[%i] : memref<4xf32>
+  }
+  return
+}
+// CHECK:       affine.for
+// CHECK:       memref.store
+// CHECK:       affine.for
\ No newline at end of file



More information about the Mlir-commits mailing list