[Mlir-commits] [mlir] [mlir][bufferization] Handle disjoint subsets in One-Shot Analysis (PR #224184)
Ziliang Zhang
llvmlistbot at llvm.org
Tue Sep 22 19:58:39 PDT 2026
https://github.com/ziliangzl updated https://github.com/llvm/llvm-project/pull/224184
>From e651cfd72018150360e0492ecf0d30a100f3592d Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Thu, 17 Sep 2026 10:53:05 +0800
Subject: [PATCH 1/6] [mlir][bufferization] Handle disjoint subsets in One-Shot
Analysis
Teach One-Shot Analysis to ignore RaW conflicts between provably
disjoint subset reads and writes. Trace aliasing reads back to subset
extraction ops to handle alias-only operations such as
tensor.extract_slice.
Add tensor and vector tests for disjoint, overlapping, and unknown
subsets.
---
.../Transforms/OneShotAnalysis.cpp | 39 ++++++++++
.../Dialect/Tensor/one-shot-bufferize.mlir | 69 ++++++++++++++++
.../Dialect/Vector/one-shot-bufferize.mlir | 78 +++++++++++++++++++
3 files changed, 186 insertions(+)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index e948a02cc415dd..ffdefa79e419fa 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -518,6 +518,45 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
Operation *readingOp = uRead->getOwner();
Operation *conflictingWritingOp = uConflictingWrite->getOwner();
+ auto subsetInsertion =
+ dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp);
+ if (subsetInsertion &&
+ uConflictingWrite == &subsetInsertion.getDestinationOperand()) {
+ auto writtenSubset = cast<SubsetOpInterface>(conflictingWritingOp);
+ auto isDisjointExtraction = [&](Value value) {
+ auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
+ return extraction &&
+ cast<SubsetOpInterface>(extraction.getOperation())
+ .operatesOnDisjointSubset(
+ writtenSubset, [&](Value v1, Value v2) {
+ return state.areEquivalentBufferizedValues(v1, v2);
+ });
+ };
+
+ // 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 a
+ // subset extraction does not itself bufferize to a memory read.
+ if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp)) {
+ if (uRead == &extraction.getSourceOperand() &&
+ cast<SubsetOpInterface>(readingOp).operatesOnDisjointSubset(
+ writtenSubset, [&](Value v1, Value v2) {
+ return state.areEquivalentBufferizedValues(v1, v2);
+ }))
+ return true;
+ }
+
+ // 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;
+ }
+
// Special rules for matching ExtractSliceOp/InsertSliceOp pairs. If
// uRead is an InsertSliceOp...
if (auto subsetOp = dyn_cast<SubsetInsertionOpInterface>(readingOp)) {
diff --git a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
index bd7115cb3a9c5a..5b59176aed05a3 100644
--- a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
@@ -1,4 +1,5 @@
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries" -drop-equivalent-buffer-results -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=CHECK-ANALYSIS
// Run fuzzer with different seeds.
// 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
@@ -8,6 +9,74 @@
// Test bufferization using memref types that have no layout map.
// RUN: mlir-opt %s -one-shot-bufferize="unknown-type-conversion=identity-layout-map bufferize-function-boundaries" -split-input-file -o /dev/null
+// A write to [0, 4) does not conflict with a read from [4, 8), even though
+// tensor.extract_slice itself is alias-only and the actual read is the return.
+
+// CHECK-LABEL: func @disjoint_insert_extract(
+// CHECK-NOT: memref.alloc
+// CHECK: memref.subview
+// CHECK: memref.copy
+// CHECK: memref.subview
+// CHECK-NOT: memref.alloc
+
+// CHECK-ANALYSIS-LABEL: func @disjoint_insert_extract(
+// CHECK-ANALYSIS: tensor.insert_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "true"]
+func.func @disjoint_insert_extract(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %source: tensor<4xf32>) -> (tensor<8xf32>, tensor<4xf32>) {
+ %written = tensor.insert_slice %source into %t[0][4][1]
+ : tensor<4xf32> into tensor<8xf32>
+ %read = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ return %written, %read : tensor<8xf32>, tensor<4xf32>
+}
+
+// -----
+
+// A write to [0, 4) conflicts with a read from [2, 6).
+
+// CHECK-LABEL: func @overlapping_insert_extract(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: memref.copy %{{.*}}, %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @overlapping_insert_extract(
+// CHECK-ANALYSIS: tensor.insert_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "false"]
+func.func @overlapping_insert_extract(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %source: tensor<4xf32>) -> (tensor<8xf32>, tensor<4xf32>) {
+ %written = tensor.insert_slice %source into %t[0][4][1]
+ : tensor<4xf32> into tensor<8xf32>
+ %read = tensor.extract_slice %t[2][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ return %written, %read : tensor<8xf32>, tensor<4xf32>
+}
+
+// -----
+
+// Dynamic offsets that cannot be proven disjoint remain conservative.
+
+// CHECK-LABEL: func @unknown_insert_extract(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: memref.copy %{{.*}}, %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @unknown_insert_extract(
+// CHECK-ANALYSIS: tensor.insert_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "false", "none"]
+func.func @unknown_insert_extract(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %source: tensor<4xf32>, %write_idx: index,
+ %read_idx: index) -> (tensor<8xf32>, tensor<4xf32>) {
+ %written = tensor.insert_slice %source into %t[%write_idx][4][1]
+ : tensor<4xf32> into tensor<8xf32>
+ %read = tensor.extract_slice %t[%read_idx][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ return %written, %read : tensor<8xf32>, 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 c2d699b9b013a1..004762f939d0de 100644
--- a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
@@ -41,3 +41,81 @@ func.func @reading_xfer_write(%t: tensor<5x10xf32>, %v: vector<4x11xf32>) -> ten
%1 = vector.transfer_write %v, %t[%c0, %c0] : vector<4x11xf32>, tensor<5x10xf32>
return %1 : tensor<5x10xf32>
}
+
+// -----
+
+// A write to [0, 4) does not conflict with a read from [4, 8), so the write
+// can bufferize in-place.
+
+// CHECK-LABEL: func @disjoint_transfer_read_write(
+// CHECK-SAME: %[[T:.*]]: memref<8xf32, strided<[?], offset: ?>>
+// CHECK-NOT: memref.alloc
+// CHECK-NOT: memref.copy
+// CHECK: vector.transfer_write %{{.*}}, %[[T]][%{{.*}}]
+// CHECK: vector.transfer_read %[[T]][%{{.*}}]
+// CHECK: return %[[T]],
+
+// CHECK-ANALYSIS-LABEL: func @disjoint_transfer_read_write(
+// CHECK-ANALYSIS: vector.transfer_write
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "true", "none"]
+func.func @disjoint_transfer_read_write(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %v: vector<4xf32>) -> (tensor<8xf32>, vector<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %c4 = arith.constant 4 : index
+ %pad = arith.constant 0.0 : f32
+ %written = vector.transfer_write %v, %t[%c0]
+ : vector<4xf32>, tensor<8xf32>
+ %read = vector.transfer_read %t[%c4], %pad
+ : tensor<8xf32>, vector<4xf32>
+ return %written, %read : tensor<8xf32>, vector<4xf32>
+}
+
+// -----
+
+// A write to [0, 4) conflicts with a read from [2, 6), so the write must
+// bufferize out-of-place.
+
+// CHECK-LABEL: func @overlapping_transfer_read_write(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: memref.copy %{{.*}}, %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @overlapping_transfer_read_write(
+// CHECK-ANALYSIS: vector.transfer_write
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "false", "none"]
+func.func @overlapping_transfer_read_write(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %v: vector<4xf32>) -> (tensor<8xf32>, vector<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %pad = arith.constant 0.0 : f32
+ %written = vector.transfer_write %v, %t[%c0]
+ : vector<4xf32>, tensor<8xf32>
+ %read = vector.transfer_read %t[%c2], %pad
+ : tensor<8xf32>, vector<4xf32>
+ return %written, %read : tensor<8xf32>, vector<4xf32>
+}
+
+// -----
+
+// If the subsets cannot be proven disjoint, the write must bufferize
+// out-of-place.
+
+// CHECK-LABEL: func @unknown_transfer_read_write(
+// CHECK: %[[ALLOC:.*]] = memref.alloc
+// CHECK: memref.copy %{{.*}}, %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @unknown_transfer_read_write(
+// CHECK-ANALYSIS: vector.transfer_write
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "false", "none"]
+func.func @unknown_transfer_read_write(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %v: vector<4xf32>, %write_idx: index,
+ %read_idx: index) -> (tensor<8xf32>, vector<4xf32>) {
+ %pad = arith.constant 0.0 : f32
+ %written = vector.transfer_write %v, %t[%write_idx]
+ : vector<4xf32>, tensor<8xf32>
+ %read = vector.transfer_read %t[%read_idx], %pad
+ : tensor<8xf32>, vector<4xf32>
+ return %written, %read : tensor<8xf32>, vector<4xf32>
+}
>From 5d5f83d783456a9fe17403f7c9c863b58051be74 Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Thu, 17 Sep 2026 11:26:22 +0800
Subject: [PATCH 2/6] clang-format
---
mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index ffdefa79e419fa..c7e6acc7fa6ba0 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -552,8 +552,7 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
// 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))
+ if (!readOrigins.empty() && llvm::all_of(readOrigins, isDisjointExtraction))
return true;
}
>From 89865572feae424a3570885aa5390afd7a9d7dfe Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Fri, 18 Sep 2026 15:14:46 +0800
Subject: [PATCH 3/6] [mlir][bufferization] Address disjoint subset review
feedback
---
.../Transforms/OneShotAnalysis.cpp | 90 +++++++++++--------
.../Dialect/Tensor/one-shot-bufferize.mlir | 6 ++
2 files changed, 57 insertions(+), 39 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index c7e6acc7fa6ba0..06b70359d372cf 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -518,44 +518,6 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
Operation *readingOp = uRead->getOwner();
Operation *conflictingWritingOp = uConflictingWrite->getOwner();
- auto subsetInsertion =
- dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp);
- if (subsetInsertion &&
- uConflictingWrite == &subsetInsertion.getDestinationOperand()) {
- auto writtenSubset = cast<SubsetOpInterface>(conflictingWritingOp);
- auto isDisjointExtraction = [&](Value value) {
- auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
- return extraction &&
- cast<SubsetOpInterface>(extraction.getOperation())
- .operatesOnDisjointSubset(
- writtenSubset, [&](Value v1, Value v2) {
- return state.areEquivalentBufferizedValues(v1, v2);
- });
- };
-
- // 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 a
- // subset extraction does not itself bufferize to a memory read.
- if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp)) {
- if (uRead == &extraction.getSourceOperand() &&
- cast<SubsetOpInterface>(readingOp).operatesOnDisjointSubset(
- writtenSubset, [&](Value v1, Value v2) {
- return state.areEquivalentBufferizedValues(v1, v2);
- }))
- return true;
- }
-
- // 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;
- }
-
// Special rules for matching ExtractSliceOp/InsertSliceOp pairs. If
// uRead is an InsertSliceOp...
if (auto subsetOp = dyn_cast<SubsetInsertionOpInterface>(readingOp)) {
@@ -598,7 +560,56 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
// If uConflictingWrite is an InsertSliceOp...
if (auto subsetOp =
- dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp))
+ 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] }
@@ -620,6 +631,7 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
uRead->get(), subsetOp.getSourceOperand().get()) &&
matchesInsertDestination(state, &subsetOp.getSourceOperand(), subsetOp))
return true;
+ }
return false;
}
diff --git a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
index 5b59176aed05a3..1014c1da4db71f 100644
--- a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
@@ -22,6 +22,8 @@
// CHECK-ANALYSIS-LABEL: func @disjoint_insert_extract(
// CHECK-ANALYSIS: tensor.insert_slice
// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "true"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
func.func @disjoint_insert_extract(
%t: tensor<8xf32> {bufferization.writable = true},
%source: tensor<4xf32>) -> (tensor<8xf32>, tensor<4xf32>) {
@@ -43,6 +45,8 @@ func.func @disjoint_insert_extract(
// CHECK-ANALYSIS-LABEL: func @overlapping_insert_extract(
// CHECK-ANALYSIS: tensor.insert_slice
// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "false"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
func.func @overlapping_insert_extract(
%t: tensor<8xf32> {bufferization.writable = true},
%source: tensor<4xf32>) -> (tensor<8xf32>, tensor<4xf32>) {
@@ -64,6 +68,8 @@ func.func @overlapping_insert_extract(
// CHECK-ANALYSIS-LABEL: func @unknown_insert_extract(
// CHECK-ANALYSIS: tensor.insert_slice
// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "false", "none"]
+// CHECK-ANALYSIS: tensor.extract_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "none"]
func.func @unknown_insert_extract(
%t: tensor<8xf32> {bufferization.writable = true},
%source: tensor<4xf32>, %write_idx: index,
>From 22b1c17382b4daaed3af618aad78e6ccb549a14b Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Fri, 18 Sep 2026 15:20:16 +0800
Subject: [PATCH 4/6] fix vector analysis checks
---
mlir/test/Dialect/Vector/one-shot-bufferize.mlir | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
index 004762f939d0de..a190c947530086 100644
--- a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
@@ -58,6 +58,8 @@ func.func @reading_xfer_write(%t: tensor<5x10xf32>, %v: vector<4x11xf32>) -> ten
// CHECK-ANALYSIS-LABEL: func @disjoint_transfer_read_write(
// CHECK-ANALYSIS: vector.transfer_write
// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "true", "none"]
+// CHECK-ANALYSIS: vector.transfer_read
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "none", "none"]
func.func @disjoint_transfer_read_write(
%t: tensor<8xf32> {bufferization.writable = true},
%v: vector<4xf32>) -> (tensor<8xf32>, vector<4xf32>) {
@@ -83,6 +85,8 @@ func.func @disjoint_transfer_read_write(
// CHECK-ANALYSIS-LABEL: func @overlapping_transfer_read_write(
// CHECK-ANALYSIS: vector.transfer_write
// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "false", "none"]
+// CHECK-ANALYSIS: vector.transfer_read
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "none", "none"]
func.func @overlapping_transfer_read_write(
%t: tensor<8xf32> {bufferization.writable = true},
%v: vector<4xf32>) -> (tensor<8xf32>, vector<4xf32>) {
@@ -108,6 +112,8 @@ func.func @overlapping_transfer_read_write(
// CHECK-ANALYSIS-LABEL: func @unknown_transfer_read_write(
// CHECK-ANALYSIS: vector.transfer_write
// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["none", "false", "none"]
+// CHECK-ANALYSIS: vector.transfer_read
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "none", "none"]
func.func @unknown_transfer_read_write(
%t: tensor<8xf32> {bufferization.writable = true},
%v: vector<4xf32>, %write_idx: index,
>From 1e00eb4ed5c39f74bb751822bc4a7cd41be776d1 Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Fri, 18 Sep 2026 15:28:35 +0800
Subject: [PATCH 5/6] fix formatting
---
.../Dialect/Bufferization/Transforms/OneShotAnalysis.cpp | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index 06b70359d372cf..aef195fe84b354 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -571,9 +571,8 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
};
auto isDisjointExtraction = [&](Value value) {
auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
- return extraction &&
- isDisjointSubset(
- cast<SubsetOpInterface>(extraction.getOperation()));
+ return extraction && isDisjointSubset(cast<SubsetOpInterface>(
+ extraction.getOperation()));
};
// Example:
@@ -584,8 +583,7 @@ static bool areNonConflictingSubsets(OpOperand *uRead,
// 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 (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp)) {
if (uRead == &extraction.getSourceOperand() &&
isDisjointSubset(cast<SubsetOpInterface>(readingOp)))
return true;
>From bbfc5a3b251778b1208e899d75608d8f6be92369 Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Wed, 23 Sep 2026 10:58:22 +0800
Subject: [PATCH 6/6] fix alias chain test
---
.../Dialect/Tensor/one-shot-bufferize.mlir | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
index 1014c1da4db71f..272e6d12e7e75d 100644
--- a/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Tensor/one-shot-bufferize.mlir
@@ -36,6 +36,42 @@ func.func @disjoint_insert_extract(
// -----
+// A chain of alias-only ops between the extraction and the actual read is
+// traced back to the disjoint subset.
+
+// CHECK-LABEL: func @disjoint_insert_extract_alias_chain(
+// CHECK-NOT: memref.alloc
+// CHECK: memref.subview
+// CHECK: memref.copy
+// CHECK: memref.subview
+// CHECK-NOT: memref.alloc
+
+// CHECK-ANALYSIS-LABEL: func @disjoint_insert_extract_alias_chain(
+// CHECK-ANALYSIS: tensor.insert_slice
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true", "true"]
+// 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.cast
+// CHECK-ANALYSIS-SAME: __inplace_operands_attr__ = ["true"]
+func.func @disjoint_insert_extract_alias_chain(
+ %t: tensor<8xf32> {bufferization.writable = true},
+ %source: tensor<4xf32>) -> (tensor<8xf32>, tensor<?xf32>) {
+ %written = tensor.insert_slice %source into %t[0][4][1]
+ : tensor<4xf32> into tensor<8xf32>
+ %extracted = tensor.extract_slice %t[4][4][1]
+ : tensor<8xf32> to tensor<4xf32>
+ %cast0 = tensor.cast %extracted : tensor<4xf32> to tensor<?xf32>
+ %cast1 = tensor.cast %cast0 : tensor<?xf32> to tensor<4xf32>
+ %cast2 = tensor.cast %cast1 : tensor<4xf32> to tensor<?xf32>
+ return %written, %cast2 : tensor<8xf32>, tensor<?xf32>
+}
+
+// -----
+
// A write to [0, 4) conflicts with a read from [2, 6).
// CHECK-LABEL: func @overlapping_insert_extract(
More information about the Mlir-commits
mailing list