[Mlir-commits] [mlir] [mlir][vector] Don't fold in_bounds for negative constant indices (PR #219681)

Dhairyashil R G llvmlistbot at llvm.org
Sat Aug 29 06:35:49 PDT 2026


https://github.com/dhairyashilRG created https://github.com/llvm/llvm-project/pull/219681

`isInBounds` checked only that the transfer *ends* inside the source:

    return cstOp.value() + vectorSize <= sourceSize;

The `in_bounds` attribute promises more than that. Its definition in VectorOps.td says accesses "(including the starting point)" may run out-of-bounds when it is "false", so setting it to "true" is a claim about the start of the transfer as well as its end.

With a negative constant index the two disagree. For

    vector.transfer_read %m[-1] : memref<8xf32>, vector<4xf32>

`-1 + 4 <= 8` holds, so the fold set `in_bounds = [true]`, even though element -1 is read from outside `%m`.

The same expression also overflows for a large enough index: at `index = 2^63 - 1` the addition wraps and the fold again reports `in_bounds = [true]`. Computing the bound as `sourceSize - vectorSize` instead avoids the addition entirely; both operands are static, non-negative dimension sizes, so the subtraction cannot overflow.

This is a spec-conformance fix, not a live miscompile report. A negative index into a memref is already undefined behaviour, and the masked lowering would not have caught it either: the guard `generateInBoundsCheck` emits in VectorToSCF is `memrefDim > index + iv`, an upper-bound check only.

Adds three regression tests, each of which fails without the change.

Assisted-by: Claude

>From 31051fa7d846fa52e9c9519fd3852172acf3b755 Mon Sep 17 00:00:00 2001
From: Dhairyashil R G <dhairyashil25 at gmail.com>
Date: Fri, 28 Aug 2026 21:48:29 +0530
Subject: [PATCH] [mlir][vector] Don't fold in_bounds for negative constant
 indices

`isInBounds` checked only that the transfer *ends* inside the source:

    return cstOp.value() + vectorSize <= sourceSize;

The `in_bounds` attribute promises more than that. Its definition in
VectorOps.td says accesses "(including the starting point)" may run
out-of-bounds when it is "false", so setting it to "true" is a claim about
the start of the transfer as well as its end.

With a negative constant index the two disagree. For

    vector.transfer_read %m[-1] : memref<8xf32>, vector<4xf32>

`-1 + 4 <= 8` holds, so the fold set `in_bounds = [true]`, even though
element -1 is read from outside `%m`.

The same expression also overflows for a large enough index: at
`index = 2^63 - 1` the addition wraps and the fold again reports
`in_bounds = [true]`. Computing the bound as `sourceSize - vectorSize`
instead avoids the addition entirely; both operands are static, non-negative
dimension sizes, so the subtraction cannot overflow.

This is a spec-conformance fix, not a live miscompile report. A negative
index into a memref is already undefined behaviour, and the masked lowering
would not have caught it either: the guard `generateInBoundsCheck` emits in
VectorToSCF is `memrefDim > index + iv`, an upper-bound check only.

Adds three regression tests, each of which fails without the change.

Assisted-by: Claude
---
 mlir/lib/Dialect/Vector/IR/VectorOps.cpp   |  7 +++-
 mlir/test/Dialect/Vector/canonicalize.mlir | 47 ++++++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index f8f3deb2e4789..88de4ac0e0c0b 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -5396,8 +5396,13 @@ static bool isInBounds(TransferOp op, int64_t resultIdx, int64_t indicesIdx) {
 
   int64_t sourceSize = op.getShapedType().getDimSize(indicesIdx);
   int64_t vectorSize = op.getVectorType().getDimSize(resultIdx);
+  // Largest index at which a full vector still fits. Computed as a subtraction
+  // rather than adding to the index, which could overflow.
+  int64_t maxStart = sourceSize - vectorSize;
 
-  return cstOp.value() + vectorSize <= sourceSize;
+  // `in_bounds` guarantees that the transfer stays within the source *including
+  // its starting point*, so a negative index is not in bounds.
+  return *cstOp >= 0 && *cstOp <= maxStart;
 }
 
 template <typename TransferOp>
diff --git a/mlir/test/Dialect/Vector/canonicalize.mlir b/mlir/test/Dialect/Vector/canonicalize.mlir
index 7ad6eda6ec1ba..5420d9a017530 100644
--- a/mlir/test/Dialect/Vector/canonicalize.mlir
+++ b/mlir/test/Dialect/Vector/canonicalize.mlir
@@ -1435,6 +1435,53 @@ func.func @no_fold_transfer_write_in_bounds_scalable(%m: memref<4xf32>, %v: vect
 
 // -----
 
+// `in_bounds` promises that the transfer stays within the source *including its
+// starting point*, so a negative index is not in bounds even when the end of
+// the transfer would land inside the source. Here `-1 + 4 <= 8` holds, but
+// element `-1` is still read from outside `%m`.
+
+// CHECK-LABEL: func @no_fold_transfer_read_in_bounds_negative_index
+//       CHECK:   vector.transfer_read
+//   CHECK-NOT:   in_bounds
+//       CHECK:   : memref<8xf32>, vector<4xf32>
+func.func @no_fold_transfer_read_in_bounds_negative_index(%m: memref<8xf32>, %p: f32) -> vector<4xf32> {
+  %c-1 = arith.constant -1 : index
+  %v = vector.transfer_read %m[%c-1], %p : memref<8xf32>, vector<4xf32>
+  return %v : vector<4xf32>
+}
+
+// -----
+
+// Same for the write path, where the fold would permit a store to element -1.
+
+// CHECK-LABEL: func @no_fold_transfer_write_in_bounds_negative_index
+//       CHECK:   vector.transfer_write
+//   CHECK-NOT:   in_bounds
+//       CHECK:   : vector<4xf32>, memref<8xf32>
+func.func @no_fold_transfer_write_in_bounds_negative_index(%m: memref<8xf32>, %v: vector<4xf32>) {
+  %c-1 = arith.constant -1 : index
+  vector.transfer_write %v, %m[%c-1] : vector<4xf32>, memref<8xf32>
+  return
+}
+
+// -----
+
+// The bound is computed as `sourceSize - vectorSize` rather than
+// `index + vectorSize`, so an index large enough to overflow the addition is
+// still rejected.
+
+// CHECK-LABEL: func @no_fold_transfer_read_in_bounds_huge_index
+//       CHECK:   vector.transfer_read
+//   CHECK-NOT:   in_bounds
+//       CHECK:   : memref<8xf32>, vector<4xf32>
+func.func @no_fold_transfer_read_in_bounds_huge_index(%m: memref<8xf32>, %p: f32) -> vector<4xf32> {
+  %c = arith.constant 9223372036854775807 : index
+  %v = vector.transfer_read %m[%c], %p : memref<8xf32>, vector<4xf32>
+  return %v : vector<4xf32>
+}
+
+// -----
+
 // CHECK-LABEL: fold_vector_transfers
 func.func @fold_vector_transfers(%A: memref<?x8xf32>) -> (vector<4x8xf32>, vector<4x9xf32>) {
   %c0 = arith.constant 0 : index



More information about the Mlir-commits mailing list