[Mlir-commits] [mlir] [mlir][bufferization] Trace write-side subset provenance (PR #228995)
Ziliang Zhang
llvmlistbot at llvm.org
Sat Oct 10 08:39:17 PDT 2026
https://github.com/ziliangzl updated https://github.com/llvm/llvm-project/pull/228995
>From 5c49e9514ab6cec6644ed4578e249af6813d7f4a Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Fri, 2 Oct 2026 23:16:21 +0800
Subject: [PATCH 1/3] [mlir][bufferization] Trace write-side subset provenance
Trace write operands back to enclosing subset extractions to rule out conflicts with disjoint reads. Follow only equivalent alias edges and require every possible read/write origin pair to be disjoint.
Add coverage for fill and vector writes through extracted views, equivalent casts, and multiple write origins.
---
.../Transforms/OneShotAnalysis.cpp | 147 ++++++++++++------
.../Dialect/Tensor/one-shot-bufferize.mlir | 77 +++++++++
.../Dialect/Vector/one-shot-bufferize.mlir | 127 +++++++++++++++
3 files changed, 304 insertions(+), 47 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index e8b3404843217..bb9e4417c00dc 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -719,6 +719,99 @@ static bool matchesInsertDestination(const AnalysisState &state,
return llvm::all_of(backwardSlice, matchingSubset);
}
+/// Find the nearest subset extractions from which `opOperand` may originate.
+/// Only equivalent alias edges may be crossed: an unknown alias does not
+/// guarantee that an access through the alias stays within the extracted
+/// subset. Return "false" if any origin is not a subset extraction.
+static bool
+findSubsetExtractionOrigins(const AnalysisState &state, OpOperand *opOperand,
+ SmallVectorImpl<SubsetOpInterface> &subsetOrigins) {
+ auto isSubsetExtraction = [](Value value) {
+ return !!value.getDefiningOp<SubsetExtractionOpInterface>();
+ };
+ TraversalConfig config;
+ config.followEquivalentOnly = true;
+ SetVector<Value> origins = state.findValueInReverseUseDefChain(
+ opOperand, isSubsetExtraction, config);
+ if (origins.empty())
+ return false;
+
+ for (Value origin : origins) {
+ auto extraction = origin.getDefiningOp<SubsetExtractionOpInterface>();
+ if (!extraction)
+ return false;
+ subsetOrigins.push_back(cast<SubsetOpInterface>(extraction.getOperation()));
+ }
+ return true;
+}
+
+/// Return "true" if every read subset is disjoint from every written subset.
+static bool areAllDisjointSubsets(ArrayRef<SubsetOpInterface> readSubsets,
+ ArrayRef<SubsetOpInterface> writtenSubsets,
+ const AnalysisState &state) {
+ return llvm::all_of(readSubsets, [&](SubsetOpInterface readSubset) {
+ return llvm::all_of(writtenSubsets, [&](SubsetOpInterface writtenSubset) {
+ return readSubset.operatesOnDisjointSubset(
+ writtenSubset, [&](Value v1, Value v2) {
+ return state.areEquivalentBufferizedValues(v1, v2);
+ });
+ });
+ });
+}
+
+/// Return "true" if the read and write are confined to provably disjoint
+/// subsets. Prefer the exact subsets of extraction/insertion ops. If those
+/// cannot be compared, use enclosing subset extractions as conservative access
+/// bounds.
+static bool areDisjointSubsetAccesses(OpOperand *uRead,
+ OpOperand *uConflictingWrite,
+ const AnalysisState &state) {
+ Operation *readingOp = uRead->getOwner();
+ Operation *conflictingWritingOp = uConflictingWrite->getOwner();
+
+ SmallVector<SubsetOpInterface, 1> directReadSubsets;
+ if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp))
+ if (uRead == &extraction.getSourceOperand())
+ directReadSubsets.push_back(cast<SubsetOpInterface>(readingOp));
+
+ SmallVector<SubsetOpInterface> readOrigins;
+ bool readOriginsInitialized = false;
+ bool hasValidReadOrigins = false;
+ auto readsAreDisjoint = [&](ArrayRef<SubsetOpInterface> writtenSubsets) {
+ if (!directReadSubsets.empty() &&
+ areAllDisjointSubsets(directReadSubsets, writtenSubsets, state))
+ return true;
+
+ if (!readOriginsInitialized) {
+ hasValidReadOrigins =
+ findSubsetExtractionOrigins(state, uRead, readOrigins);
+ readOriginsInitialized = true;
+ }
+ return hasValidReadOrigins &&
+ areAllDisjointSubsets(readOrigins, writtenSubsets, state);
+ };
+
+ // First try the exact subset written by an insertion op.
+ if (auto insertion =
+ dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp)) {
+ if (uConflictingWrite == &insertion.getDestinationOperand()) {
+ SmallVector<SubsetOpInterface, 1> writtenSubset = {
+ cast<SubsetOpInterface>(conflictingWritingOp)};
+ if (readsAreDisjoint(writtenSubset))
+ return true;
+ }
+ }
+
+ // The write may happen through a view that was produced by a subset
+ // extraction. The extraction is a safe (possibly imprecise) bound for every
+ // access through that view. There may be multiple possible origins, e.g.,
+ // after an arith.select; all of them must be disjoint from every read origin.
+ SmallVector<SubsetOpInterface> writtenOrigins;
+ if (!findSubsetExtractionOrigins(state, uConflictingWrite, writtenOrigins))
+ return false;
+ return readsAreDisjoint(writtenOrigins);
+}
+
/// Return "true" if the given "read" and potentially conflicting "write" are
/// not conflicting due to their subset relationship. The comments in this
/// function are expressed in terms of tensor.extract_slice/tensor.insert_slice
@@ -770,56 +863,16 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
return true;
}
+ // A read from a subset does not conflict with a write to a disjoint subset
+ // of an equivalent tensor. The actual read or write may be further down an
+ // aliasing use-def chain. Trace such accesses back to subset extractions,
+ // crossing only equivalent alias edges.
+ if (areDisjointSubsetAccesses(uRead, uConflictingWrite, state))
+ return true;
+
// If uConflictingWrite is an InsertSliceOp...
if (auto subsetOp =
dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp)) {
- if (uConflictingWrite == &subsetOp.getDestinationOperand()) {
- auto writtenSubset = cast<SubsetOpInterface>(conflictingWritingOp);
- auto isDisjointSubset = [&](SubsetOpInterface readSubset) {
- return readSubset.operatesOnDisjointSubset(
- writtenSubset, [&](Value v1, Value v2) {
- return state.areEquivalentBufferizedValues(v1, v2);
- });
- };
- auto isDisjointExtraction = [&](Value value) {
- auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
- return extraction && isDisjointSubset(cast<SubsetOpInterface>(
- extraction.getOperation()));
- };
-
- // Example:
- //
- // %0 = tensor.insert_slice %s into %t[0][4][1]
- // %1 = vector.transfer_read %t[%c4], %cst
- //
- // A read from a subset does not conflict with a write to a disjoint
- // subset of an equivalent tensor. Check the operand roles explicitly
- // because not every subset extraction bufferizes to a memory read.
- if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp)) {
- if (uRead == &extraction.getSourceOperand() &&
- isDisjointSubset(cast<SubsetOpInterface>(readingOp)))
- return true;
- }
-
- // Example:
- //
- // %0 = tensor.insert_slice %s into %t[0][4][1]
- // %1 = tensor.extract_slice %t[4][4][1]
- // return %0, %1
- //
- // The actual read may be further down the aliasing use-def chain. E.g.,
- // tensor.extract_slice is an alias-only op and the read is attributed to
- // a return or another consumer of its result. Trace such reads back to
- // their subset extractions. Every origin must be a disjoint subset; a
- // non-subset leaf or an extraction that may overlap keeps the analysis
- // conservative.
- SetVector<Value> readOrigins =
- state.findValueInReverseUseDefChain(uRead, isDisjointExtraction);
- if (!readOrigins.empty() &&
- llvm::all_of(readOrigins, isDisjointExtraction))
- return true;
- }
-
// As an example, consider the following IR.
//
// %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] }
diff --git a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
index 272e6d12e7e75..fa970d008c109 100644
--- a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
@@ -119,6 +119,83 @@ func.func @unknown_insert_extract(
// -----
+// A write through an extracted view is confined to that subset. It does not
+// conflict with a read through a disjoint view of the same tensor.
+
+// CHECK-LABEL: func @disjoint_fill_through_extract(
+// CHECK-SAME: %[[T:.*]]: memref<8xf32, strided<[?], offset: ?>>
+// CHECK-NOT: memref.alloc
+// CHECK: %[[LHS:.*]] = memref.subview %[[T]][0] [4] [1]
+// CHECK: %[[RHS:.*]] = memref.subview %[[T]][4] [4] [1]
+// CHECK: linalg.fill {{.*}} outs(%[[LHS]]
+// CHECK: return %[[LHS]], %[[RHS]]
+
+// CHECK-ANALYSIS-LABEL: func @disjoint_fill_through_extract(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: linalg.fill
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "true"]
+func.func @disjoint_fill_through_extract(
+ %t: tensor<8xf32> {bufferization.writable = true}, %value: f32)
+ -> (tensor<4xf32>, tensor<4xf32>) {
+ %lhs = tensor.extract_slice %t[0][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = linalg.fill ins(%value : f32) outs(%lhs : tensor<4xf32>)
+ -> tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
+
+// -----
+
+// A write through [0, 4) conflicts with a read through [2, 6).
+
+// CHECK-LABEL: func @overlapping_fill_through_extract(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+
+// CHECK-ANALYSIS-LABEL: func @overlapping_fill_through_extract(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["false"]
+func.func @overlapping_fill_through_extract(
+ %t: tensor<8xf32> {bufferization.writable = true}, %value: f32)
+ -> (tensor<4xf32>, tensor<4xf32>) {
+ %lhs = tensor.extract_slice %t[0][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[2][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = linalg.fill ins(%value : f32) outs(%lhs : tensor<4xf32>)
+ -> tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
+
+// -----
+
+// Dynamically positioned views remain conservative.
+
+// CHECK-LABEL: func @unknown_fill_through_extract(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+
+// CHECK-ANALYSIS-LABEL: func @unknown_fill_through_extract(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["false", "none"]
+func.func @unknown_fill_through_extract(
+ %t: tensor<8xf32> {bufferization.writable = true}, %value: f32,
+ %write_idx: index, %read_idx: index)
+ -> (tensor<4xf32>, tensor<4xf32>) {
+ %lhs = tensor.extract_slice %t[%write_idx][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[%read_idx][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = linalg.fill ins(%value : f32) outs(%lhs : tensor<4xf32>)
+ -> tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
+
+// -----
+
// CHECK-LABEL: func private @insert_slice_fun
// CHECK-SAME: %[[A0:[a-zA-Z0-9]*]]: memref<?xf32, strided<[?], offset: ?>>,
// CHECK-SAME: %[[A1:[a-zA-Z0-9]*]]: memref<?xf32, strided<[?], offset: ?>>,
diff --git a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
index a190c94753008..971a2ae20dcd7 100644
--- a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
@@ -125,3 +125,130 @@ func.func @unknown_transfer_read_write(
: tensor<8xf32>, vector<4xf32>
return %written, %read : tensor<8xf32>, vector<4xf32>
}
+
+// -----
+
+// A partial vector write through [0, 4) does not conflict with a read through
+// [4, 8). Both the allocation and the copy for the extracted write view can be
+// avoided.
+
+// CHECK-LABEL: func @disjoint_transfer_write_through_extract(
+// CHECK-SAME: %[[T:.*]]: memref<8xf32, strided<[?], offset: ?>>
+// CHECK-NOT: memref.alloc
+// CHECK-NOT: memref.copy
+// CHECK: %[[LHS:.*]] = memref.subview %[[T]][0] [4] [1]
+// CHECK: %[[RHS:.*]] = memref.subview %[[T]][4] [4] [1]
+// CHECK: vector.transfer_write %{{.*}}, %[[LHS]]
+// CHECK: return %[[LHS]], %[[RHS]]
+
+// CHECK-ANALYSIS-LABEL: func @disjoint_transfer_write_through_extract(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: vector.transfer_write
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "true", "none"]
+func.func @disjoint_transfer_write_through_extract(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %v: vector<2xf32>) -> (tensor<4xf32>, tensor<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %lhs = tensor.extract_slice %t[0][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = vector.transfer_write %v, %lhs[%c0]
+ : vector<2xf32>, tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
+
+// -----
+
+// Equivalent aliases between the extraction and the write preserve subset
+// containment.
+
+// CHECK-LABEL: func @disjoint_transfer_write_through_extract_and_cast(
+// CHECK-NOT: memref.alloc
+// CHECK-NOT: memref.copy
+// CHECK: vector.transfer_write
+
+// CHECK-ANALYSIS-LABEL: func @disjoint_transfer_write_through_extract_and_cast(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.cast
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.cast
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+func.func @disjoint_transfer_write_through_extract_and_cast(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %v: vector<2xf32>) -> (tensor<4xf32>, tensor<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %lhs = tensor.extract_slice %t[0][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %cast0 = tensor.cast %lhs : tensor<4xf32> to tensor<?xf32>
+ %cast1 = tensor.cast %cast0 : tensor<?xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = vector.transfer_write %v, %cast1[%c0]
+ : vector<2xf32>, tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
+
+// -----
+
+// Every possible write origin is disjoint from the read.
+
+// CHECK-LABEL: func @multiple_disjoint_write_origins(
+// CHECK-NOT: memref.alloc
+// CHECK-NOT: memref.copy
+// CHECK: vector.transfer_write
+
+// CHECK-ANALYSIS-LABEL: func @multiple_disjoint_write_origins(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+func.func @multiple_disjoint_write_origins(
+ %t: tensor<12xf32> {bufferization.writable = true},
+ %v: vector<2xf32>, %cond: i1) -> (tensor<4xf32>, tensor<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %lhs0 = tensor.extract_slice %t[0][4][1]
+ : tensor<12xf32> to tensor<4xf32>
+ %lhs1 = tensor.extract_slice %t[4][4][1]
+ : tensor<12xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[8][4][1]
+ : tensor<12xf32> to tensor<4xf32>
+ %selected = arith.select %cond, %lhs0, %lhs1 : tensor<4xf32>
+ %written = vector.transfer_write %v, %selected[%c0]
+ : vector<2xf32>, tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
+
+// -----
+
+// One possible write origin overlaps the read, so the proof must fail.
+
+// CHECK-LABEL: func @one_overlapping_write_origin(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+
+// CHECK-ANALYSIS-LABEL: func @one_overlapping_write_origin(
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["false"]
+func.func @one_overlapping_write_origin(
+ %t: tensor<12xf32> {bufferization.writable = true},
+ %v: vector<2xf32>, %cond: i1) -> (tensor<4xf32>, tensor<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %lhs0 = tensor.extract_slice %t[0][4][1]
+ : tensor<12xf32> to tensor<4xf32>
+ %lhs1 = tensor.extract_slice %t[8][4][1]
+ : tensor<12xf32> to tensor<4xf32>
+ %rhs = tensor.extract_slice %t[8][4][1]
+ : tensor<12xf32> to tensor<4xf32>
+ %selected = arith.select %cond, %lhs0, %lhs1 : tensor<4xf32>
+ %written = vector.transfer_write %v, %selected[%c0]
+ : vector<2xf32>, tensor<4xf32>
+ return %written, %rhs : tensor<4xf32>, tensor<4xf32>
+}
>From 758a23540698806c0f36ffbd7a979d312ceb7aa1 Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Sat, 10 Oct 2026 23:02:32 +0800
Subject: [PATCH 2/3] [mlir][bufferization] Fix subset provenance across
iterations
Guard disjoint subset proofs against cross-iteration interference and trace nested extractions to enclosing access bounds. Add SCF, CFG, and nested-subset regression tests.
---
.../Transforms/OneShotAnalysis.cpp | 131 +++++-----
.../one-shot-bufferize-disjoint-subsets.mlir | 238 ++++++++++++++++++
2 files changed, 304 insertions(+), 65 deletions(-)
create mode 100644 mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-disjoint-subsets.mlir
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index bb9e4417c00dc..858fbc3bb2b99 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -719,46 +719,47 @@ static bool matchesInsertDestination(const AnalysisState &state,
return llvm::all_of(backwardSlice, matchingSubset);
}
-/// Find the nearest subset extractions from which `opOperand` may originate.
-/// Only equivalent alias edges may be crossed: an unknown alias does not
-/// guarantee that an access through the alias stays within the extracted
-/// subset. Return "false" if any origin is not a subset extraction.
+/// Return "true" if every reverse use-def path reaches an extraction satisfying
+/// `isDisjoint`. An extraction bounds all accesses through its result. If its
+/// subset cannot be compared, continue through its source to try a larger
+/// bound. Other alias edges must be equivalent: an unknown alias does not
+/// guarantee containment. Unproven leaves and cycles conservatively fail the
+/// proof.
static bool
-findSubsetExtractionOrigins(const AnalysisState &state, OpOperand *opOperand,
- SmallVectorImpl<SubsetOpInterface> &subsetOrigins) {
- auto isSubsetExtraction = [](Value value) {
- return !!value.getDefiningOp<SubsetExtractionOpInterface>();
- };
- TraversalConfig config;
- config.followEquivalentOnly = true;
- SetVector<Value> origins = state.findValueInReverseUseDefChain(
- opOperand, isSubsetExtraction, config);
- if (origins.empty())
- return false;
+hasDisjointSubsetOrigins(const AnalysisState &state, OpOperand *opOperand,
+ function_ref<bool(SubsetOpInterface)> isDisjoint) {
+ SetVector<Value> worklist;
+ DenseSet<Value> visited;
+ worklist.insert(opOperand->get());
+ while (!worklist.empty()) {
+ Value value = worklist.pop_back_val();
+ auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
+ if (extraction &&
+ isDisjoint(cast<SubsetOpInterface>(extraction.getOperation())))
+ continue;
+
+ if (!visited.insert(value).second)
+ return false;
- for (Value origin : origins) {
- auto extraction = origin.getDefiningOp<SubsetExtractionOpInterface>();
- if (!extraction)
+ if (!state.getOptions().dynCastBufferizableOp(value))
return false;
- subsetOrigins.push_back(cast<SubsetOpInterface>(extraction.getOperation()));
+ if (extraction) {
+ worklist.insert(extraction.getSourceOperand().get());
+ continue;
+ }
+
+ AliasingOpOperandList aliases = state.getAliasingOpOperands(value);
+ if (aliases.getNumAliases() == 0)
+ return false;
+ for (AliasingOpOperand alias : aliases) {
+ if (alias.relation != BufferRelation::Equivalent)
+ return false;
+ worklist.insert(alias.opOperand->get());
+ }
}
return true;
}
-/// Return "true" if every read subset is disjoint from every written subset.
-static bool areAllDisjointSubsets(ArrayRef<SubsetOpInterface> readSubsets,
- ArrayRef<SubsetOpInterface> writtenSubsets,
- const AnalysisState &state) {
- return llvm::all_of(readSubsets, [&](SubsetOpInterface readSubset) {
- return llvm::all_of(writtenSubsets, [&](SubsetOpInterface writtenSubset) {
- return readSubset.operatesOnDisjointSubset(
- writtenSubset, [&](Value v1, Value v2) {
- return state.areEquivalentBufferizedValues(v1, v2);
- });
- });
- });
-}
-
/// Return "true" if the read and write are confined to provably disjoint
/// subsets. Prefer the exact subsets of extraction/insertion ops. If those
/// cannot be compared, use enclosing subset extractions as conservative access
@@ -769,35 +770,25 @@ static bool areDisjointSubsetAccesses(OpOperand *uRead,
Operation *readingOp = uRead->getOwner();
Operation *conflictingWritingOp = uConflictingWrite->getOwner();
- SmallVector<SubsetOpInterface, 1> directReadSubsets;
- if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp))
- if (uRead == &extraction.getSourceOperand())
- directReadSubsets.push_back(cast<SubsetOpInterface>(readingOp));
-
- SmallVector<SubsetOpInterface> readOrigins;
- bool readOriginsInitialized = false;
- bool hasValidReadOrigins = false;
- auto readsAreDisjoint = [&](ArrayRef<SubsetOpInterface> writtenSubsets) {
- if (!directReadSubsets.empty() &&
- areAllDisjointSubsets(directReadSubsets, writtenSubsets, state))
- return true;
-
- if (!readOriginsInitialized) {
- hasValidReadOrigins =
- findSubsetExtractionOrigins(state, uRead, readOrigins);
- readOriginsInitialized = true;
- }
- return hasValidReadOrigins &&
- areAllDisjointSubsets(readOrigins, writtenSubsets, state);
+ auto readsAreDisjoint = [&](SubsetOpInterface writtenSubset) {
+ auto isDisjoint = [&](SubsetOpInterface readSubset) {
+ return readSubset.operatesOnDisjointSubset(
+ writtenSubset, [&](Value v1, Value v2) {
+ return state.areEquivalentBufferizedValues(v1, v2);
+ });
+ };
+ if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp))
+ if (uRead == &extraction.getSourceOperand() &&
+ isDisjoint(cast<SubsetOpInterface>(readingOp)))
+ return true;
+ return hasDisjointSubsetOrigins(state, uRead, isDisjoint);
};
// First try the exact subset written by an insertion op.
if (auto insertion =
dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp)) {
if (uConflictingWrite == &insertion.getDestinationOperand()) {
- SmallVector<SubsetOpInterface, 1> writtenSubset = {
- cast<SubsetOpInterface>(conflictingWritingOp)};
- if (readsAreDisjoint(writtenSubset))
+ if (readsAreDisjoint(cast<SubsetOpInterface>(conflictingWritingOp)))
return true;
}
}
@@ -806,20 +797,16 @@ static bool areDisjointSubsetAccesses(OpOperand *uRead,
// extraction. The extraction is a safe (possibly imprecise) bound for every
// access through that view. There may be multiple possible origins, e.g.,
// after an arith.select; all of them must be disjoint from every read origin.
- SmallVector<SubsetOpInterface> writtenOrigins;
- if (!findSubsetExtractionOrigins(state, uConflictingWrite, writtenOrigins))
- return false;
- return readsAreDisjoint(writtenOrigins);
+ return hasDisjointSubsetOrigins(state, uConflictingWrite, readsAreDisjoint);
}
/// Return "true" if the given "read" and potentially conflicting "write" are
/// not conflicting due to their subset relationship. The comments in this
/// function are expressed in terms of tensor.extract_slice/tensor.insert_slice
-/// pairs, but apply to any subset ops that implement the
-/// `SubsetInsertionOpInterface`.
+/// pairs, but apply to any ops implementing the subset interfaces.
static bool areNonConflictingSubsets(OpOperand *uRead,
OpOperand *uConflictingWrite,
- const AnalysisState &state) {
+ OneShotAnalysisState &state) {
Operation *readingOp = uRead->getOwner();
Operation *conflictingWritingOp = uConflictingWrite->getOwner();
@@ -866,8 +853,22 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
// A read from a subset does not conflict with a write to a disjoint subset
// of an equivalent tensor. The actual read or write may be further down an
// aliasing use-def chain. Trace such accesses back to subset extractions,
- // crossing only equivalent alias edges.
- if (areDisjointSubsetAccesses(uRead, uConflictingWrite, state))
+ // crossing only equivalent alias edges or extraction source edges.
+ //
+ // %lhs = tensor.extract_slice %t[0][4][1]
+ // %rhs = tensor.extract_slice %t[4][4][1]
+ // %written = linalg.fill ins(%cst) outs(%lhs)
+ // return %written, %rhs
+ //
+ // Disjointness describes a single execution of the subset ops. If a read
+ // can observe writes from another iteration, the subset coordinates may
+ // differ across executions. Reuse the dominance checks to conservatively
+ // exclude such repetitive-region and CFG cases. Matching subset rules above
+ // and below do not rely on a disjointness proof and remain applicable.
+ const SetVector<Value> &definitions = state.findDefinitionsCached(uRead);
+ if ((definitions.empty() ||
+ canUseOpDominance(uRead, uConflictingWrite, definitions, state)) &&
+ areDisjointSubsetAccesses(uRead, uConflictingWrite, state))
return true;
// If uConflictingWrite is an InsertSliceOp...
diff --git a/mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-disjoint-subsets.mlir b/mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-disjoint-subsets.mlir
new file mode 100644
index 0000000000000..2cece8b4ffe89
--- /dev/null
+++ b/mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-disjoint-subsets.mlir
@@ -0,0 +1,238 @@
+// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries" -split-input-file | FileCheck %s
+// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only bufferize-function-boundaries" -split-input-file | FileCheck %s --check-prefix=ANALYSIS
+// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23 bufferize-function-boundaries" -split-input-file -o /dev/null
+// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59 bufferize-function-boundaries" -split-input-file -o /dev/null
+// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91 bufferize-function-boundaries" -split-input-file -o /dev/null
+
+// The inner extraction cannot be compared directly with the insertion. The
+// outer extraction still bounds the read and is disjoint from the insertion.
+
+// CHECK-LABEL: func @nested_read(
+// CHECK-NOT: memref.alloc
+// CHECK: memref.copy
+// CHECK: memref.subview
+// CHECK: memref.subview
+// CHECK-NOT: memref.alloc
+// CHECK: return
+// ANALYSIS-LABEL: func @nested_read(
+// ANALYSIS: tensor.insert_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "true"]
+func.func @nested_read(
+ %t: tensor<8xf32> {bufferization.writable = true}, %s: tensor<4xf32>)
+ -> (tensor<8xf32>, tensor<2xf32>) {
+ %written = tensor.insert_slice %s into %t[0][4][1]
+ : tensor<4xf32> into tensor<8xf32>
+ %outer = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %inner = tensor.extract_slice %outer[0][2][1]
+ : tensor<4xf32> to tensor<2xf32>
+ return %written, %inner : tensor<8xf32>, tensor<2xf32>
+}
+
+// -----
+
+// The same containment reasoning applies on the write side.
+
+// CHECK-LABEL: func @nested_write(
+// CHECK-NOT: memref.alloc
+// CHECK: linalg.fill
+// CHECK-NOT: memref.alloc
+// CHECK: return
+// ANALYSIS-LABEL: func @nested_write(
+// ANALYSIS: linalg.fill
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "true"]
+func.func @nested_write(
+ %t: tensor<8xf32> {bufferization.writable = true}, %value: f32)
+ -> (tensor<2xf32>, tensor<4xf32>) {
+ %outer = tensor.extract_slice %t[0][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %inner = tensor.extract_slice %outer[0][2][1]
+ : tensor<4xf32> to tensor<2xf32>
+ %rhs = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = linalg.fill ins(%value : f32) outs(%inner : tensor<2xf32>)
+ -> tensor<2xf32>
+ return %written, %rhs : tensor<2xf32>, tensor<4xf32>
+}
+
+// -----
+
+// Neither the inner nor outer bound proves disjointness in this case.
+
+// CHECK-LABEL: func @nested_overlap(
+// CHECK: memref.alloc
+// CHECK: linalg.fill
+// ANALYSIS-LABEL: func @nested_overlap(
+// ANALYSIS: tensor.extract_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["false"]
+func.func @nested_overlap(
+ %t: tensor<8xf32> {bufferization.writable = true}, %value: f32)
+ -> (tensor<2xf32>, tensor<4xf32>) {
+ %outer = tensor.extract_slice %t[0][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %inner = tensor.extract_slice %outer[0][2][1]
+ : tensor<4xf32> to tensor<2xf32>
+ %rhs = tensor.extract_slice %t[1][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %written = linalg.fill ins(%value : f32) outs(%inner : tensor<2xf32>)
+ -> tensor<2xf32>
+ return %written, %rhs : tensor<2xf32>, tensor<4xf32>
+}
+
+// -----
+
+// In iteration i the write to t[i] and read from t[i-1] are disjoint, but the
+// read must not observe the write from iteration i-1. The fill needs a private
+// buffer; tensor SSA semantics keep the original t unchanged across iterations.
+
+// CHECK-LABEL: func @cross_iteration_fill(
+// CHECK-SAME: %[[T:.*]]: memref<4xf32,
+// CHECK: scf.for
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: %[[RHS:.*]] = memref.subview %[[T]]
+// CHECK: linalg.fill {{.*}} outs(%[[ALLOC]]
+// CHECK: memref.load %[[RHS]]
+// ANALYSIS-LABEL: func @cross_iteration_fill(
+// ANALYSIS: tensor.extract_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["false", "none"]
+func.func @cross_iteration_fill(
+ %t: tensor<4xf32> {bufferization.writable = true}) -> f32 {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %zero = arith.constant 0.0 : f32
+ %value = arith.constant 100.0 : f32
+ %result = scf.for %i = %c1 to %c4 step %c1
+ iter_args(%sum = %zero) -> f32 {
+ %prev = arith.subi %i, %c1 : index
+ %lhs = tensor.extract_slice %t[%i][1][1]
+ : tensor<4xf32> to tensor<1xf32>
+ %rhs = tensor.extract_slice %t[%prev][1][1]
+ : tensor<4xf32> to tensor<1xf32>
+ %written = linalg.fill ins(%value : f32) outs(%lhs : tensor<1xf32>)
+ -> tensor<1xf32>
+ %new = tensor.extract %written[%c0] : tensor<1xf32>
+ %old = tensor.extract %rhs[%c0] : tensor<1xf32>
+ %pair = arith.addf %new, %old : f32
+ %next = arith.addf %sum, %pair : f32
+ scf.yield %next : f32
+ }
+ return %result : f32
+}
+
+// -----
+
+// Exact insertion subsets must obey the same cross-iteration restriction.
+
+// CHECK-LABEL: func @cross_iteration_insert(
+// CHECK-SAME: %[[T:.*]]: memref<4xf32,
+// CHECK: scf.for
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: memref.copy %[[T]], %[[ALLOC]]
+// ANALYSIS-LABEL: func @cross_iteration_insert(
+// ANALYSIS: tensor.insert_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "false", "none"]
+func.func @cross_iteration_insert(
+ %t: tensor<4xf32> {bufferization.writable = true}, %s: tensor<1xf32>)
+ -> f32 {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %zero = arith.constant 0.0 : f32
+ %result = scf.for %i = %c1 to %c4 step %c1
+ iter_args(%sum = %zero) -> f32 {
+ %prev = arith.subi %i, %c1 : index
+ %written = tensor.insert_slice %s into %t[%i][1][1]
+ : tensor<1xf32> into tensor<4xf32>
+ %rhs = tensor.extract_slice %t[%prev][1][1]
+ : tensor<4xf32> to tensor<1xf32>
+ %new = tensor.extract %written[%i] : tensor<4xf32>
+ %old = tensor.extract %rhs[%c0] : tensor<1xf32>
+ %pair = arith.addf %new, %old : f32
+ %next = arith.addf %sum, %pair : f32
+ scf.yield %next : f32
+ }
+ return %result : f32
+}
+
+// -----
+
+// Unstructured loops have the same cross-iteration hazard.
+
+// CHECK-LABEL: func @cross_iteration_cfg(
+// CHECK-SAME: %[[T:.*]]: memref<4xf32,
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: %[[RHS:.*]] = memref.subview %[[T]]
+// CHECK: linalg.fill {{.*}} outs(%[[ALLOC]]
+// CHECK: memref.load %[[RHS]]
+// ANALYSIS-LABEL: func @cross_iteration_cfg(
+// ANALYSIS: tensor.extract_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["false", "none"]
+func.func @cross_iteration_cfg(
+ %t: tensor<4xf32> {bufferization.writable = true}) -> f32 {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %zero = arith.constant 0.0 : f32
+ %value = arith.constant 100.0 : f32
+ cf.br ^loop(%c1, %zero : index, f32)
+^loop(%i: index, %sum: f32):
+ %prev = arith.subi %i, %c1 : index
+ %lhs = tensor.extract_slice %t[%i][1][1]
+ : tensor<4xf32> to tensor<1xf32>
+ %rhs = tensor.extract_slice %t[%prev][1][1]
+ : tensor<4xf32> to tensor<1xf32>
+ %written = linalg.fill ins(%value : f32) outs(%lhs : tensor<1xf32>)
+ -> tensor<1xf32>
+ %new = tensor.extract %written[%c0] : tensor<1xf32>
+ %old = tensor.extract %rhs[%c0] : tensor<1xf32>
+ %pair = arith.addf %new, %old : f32
+ %next = arith.addf %sum, %pair : f32
+ %next_i = arith.addi %i, %c1 : index
+ %continue = arith.cmpi ult, %next_i, %c4 : index
+ cf.cond_br %continue, ^loop(%next_i, %next : index, f32), ^exit(%next : f32)
+^exit(%result: f32):
+ return %result : f32
+}
+
+// -----
+
+// A fresh definition in each iteration resets the read/write sequence. Merely
+// being inside a loop must not disable the disjoint-subset optimization.
+
+// CHECK-LABEL: func @fresh_definition_in_loop(
+// CHECK: scf.for
+// CHECK: memref.alloc
+// CHECK-NOT: memref.alloc
+// CHECK: linalg.fill
+// CHECK-NOT: memref.alloc
+// CHECK: scf.yield
+// ANALYSIS-LABEL: func @fresh_definition_in_loop(
+// ANALYSIS: tensor.extract_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// ANALYSIS: tensor.extract_slice
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+// ANALYSIS: linalg.fill
+// ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "true"]
+func.func @fresh_definition_in_loop(%value: f32) -> f32 {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %zero = arith.constant 0.0 : f32
+ %result = scf.for %i = %c1 to %c4 step %c1
+ iter_args(%sum = %zero) -> f32 {
+ %t = tensor.from_elements %value, %value, %value, %value : tensor<4xf32>
+ %lhs = tensor.extract_slice %t[0][2][1]
+ : tensor<4xf32> to tensor<2xf32>
+ %rhs = tensor.extract_slice %t[2][2][1]
+ : tensor<4xf32> to tensor<2xf32>
+ %written = linalg.fill ins(%zero : f32) outs(%lhs : tensor<2xf32>)
+ -> tensor<2xf32>
+ %new = tensor.extract %written[%c0] : tensor<2xf32>
+ %old = tensor.extract %rhs[%c0] : tensor<2xf32>
+ %pair = arith.addf %new, %old : f32
+ %next = arith.addf %sum, %pair : f32
+ scf.yield %next : f32
+ }
+ return %result : f32
+}
>From 4eaa406b20553367aec9f9a7217789da168c315e Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Sat, 10 Oct 2026 23:39:03 +0800
Subject: [PATCH 3/3] [mlir][bufferization] Avoid redundant subset disjointness
proofs
Reject identical subset bounds before invoking ValueBounds and stop origin traversal at known overlapping bounds. Try matching-subset rules before the disjointness fallback.
---
.../Transforms/OneShotAnalysis.cpp | 59 +++++++++++--------
1 file changed, 34 insertions(+), 25 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index 858fbc3bb2b99..3ab4771d6ced7 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -724,16 +724,23 @@ static bool matchesInsertDestination(const AnalysisState &state,
/// subset cannot be compared, continue through its source to try a larger
/// bound. Other alias edges must be equivalent: an unknown alias does not
/// guarantee containment. Unproven leaves and cycles conservatively fail the
-/// proof.
+/// proof. An origin equal to `overlappingSubset` or its container fails
+/// immediately: a larger enclosing bound cannot prove disjointness either.
static bool
hasDisjointSubsetOrigins(const AnalysisState &state, OpOperand *opOperand,
- function_ref<bool(SubsetOpInterface)> isDisjoint) {
+ function_ref<bool(SubsetOpInterface)> isDisjoint,
+ SubsetOpInterface overlappingSubset = {}) {
SetVector<Value> worklist;
DenseSet<Value> visited;
worklist.insert(opOperand->get());
while (!worklist.empty()) {
Value value = worklist.pop_back_val();
+ if (overlappingSubset && value == overlappingSubset.getTensorContainer())
+ return false;
auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
+ if (extraction && overlappingSubset &&
+ extraction.getOperation() == overlappingSubset.getOperation())
+ return false;
if (extraction &&
isDisjoint(cast<SubsetOpInterface>(extraction.getOperation())))
continue;
@@ -772,6 +779,10 @@ static bool areDisjointSubsetAccesses(OpOperand *uRead,
auto readsAreDisjoint = [&](SubsetOpInterface writtenSubset) {
auto isDisjoint = [&](SubsetOpInterface readSubset) {
+ // Conservatively treat identical access bounds as overlapping. In
+ // particular, do not invoke ValueBounds on a dynamic slice with itself.
+ if (readSubset == writtenSubset)
+ return false;
return readSubset.operatesOnDisjointSubset(
writtenSubset, [&](Value v1, Value v2) {
return state.areEquivalentBufferizedValues(v1, v2);
@@ -781,7 +792,7 @@ static bool areDisjointSubsetAccesses(OpOperand *uRead,
if (uRead == &extraction.getSourceOperand() &&
isDisjoint(cast<SubsetOpInterface>(readingOp)))
return true;
- return hasDisjointSubsetOrigins(state, uRead, isDisjoint);
+ return hasDisjointSubsetOrigins(state, uRead, isDisjoint, writtenSubset);
};
// First try the exact subset written by an insertion op.
@@ -850,27 +861,6 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
return true;
}
- // A read from a subset does not conflict with a write to a disjoint subset
- // of an equivalent tensor. The actual read or write may be further down an
- // aliasing use-def chain. Trace such accesses back to subset extractions,
- // crossing only equivalent alias edges or extraction source edges.
- //
- // %lhs = tensor.extract_slice %t[0][4][1]
- // %rhs = tensor.extract_slice %t[4][4][1]
- // %written = linalg.fill ins(%cst) outs(%lhs)
- // return %written, %rhs
- //
- // Disjointness describes a single execution of the subset ops. If a read
- // can observe writes from another iteration, the subset coordinates may
- // differ across executions. Reuse the dominance checks to conservatively
- // exclude such repetitive-region and CFG cases. Matching subset rules above
- // and below do not rely on a disjointness proof and remain applicable.
- const SetVector<Value> &definitions = state.findDefinitionsCached(uRead);
- if ((definitions.empty() ||
- canUseOpDominance(uRead, uConflictingWrite, definitions, state)) &&
- areDisjointSubsetAccesses(uRead, uConflictingWrite, state))
- return true;
-
// If uConflictingWrite is an InsertSliceOp...
if (auto subsetOp =
dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp)) {
@@ -897,7 +887,26 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
return true;
}
- return false;
+ // A read from a subset does not conflict with a write to a disjoint subset
+ // of an equivalent tensor. The actual read or write may be further down an
+ // aliasing use-def chain. Trace such accesses back to subset extractions,
+ // crossing only equivalent alias edges or extraction source edges. Try the
+ // matching-subset rules above first to avoid unnecessary disjointness proofs.
+ //
+ // %lhs = tensor.extract_slice %t[0][4][1]
+ // %rhs = tensor.extract_slice %t[4][4][1]
+ // %written = linalg.fill ins(%cst) outs(%lhs)
+ // return %written, %rhs
+ //
+ // Disjointness describes a single execution of the subset ops. If a read
+ // can observe writes from another iteration, the subset coordinates may
+ // differ across executions. Reuse the dominance checks to conservatively
+ // exclude such repetitive-region and CFG cases. Matching subset rules above
+ // do not rely on a disjointness proof and remain applicable.
+ const SetVector<Value> &definitions = state.findDefinitionsCached(uRead);
+ return (definitions.empty() ||
+ canUseOpDominance(uRead, uConflictingWrite, definitions, state)) &&
+ areDisjointSubsetAccesses(uRead, uConflictingWrite, state);
}
/// Given sets of uses and writes, return true if there is a RaW conflict under
More information about the Mlir-commits
mailing list