[Mlir-commits] [mlir] [MLIR][Affine] Add MDG edges between accesses to aliasing memref views (PR #237726)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sun Nov 1 05:07:12 PST 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Sajid Zubair (Sajid-Zubair)
<details>
<summary>Changes</summary>
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
---
Full diff: https://github.com/llvm/llvm-project/pull/237726.diff
2 Files Affected:
- (modified) mlir/lib/Dialect/Affine/Analysis/Utils.cpp (+48)
- (added) mlir/test/Dialect/Affine/loop-fusion-views.mlir (+100)
``````````diff
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
``````````
</details>
https://github.com/llvm/llvm-project/pull/237726
More information about the Mlir-commits
mailing list