[Mlir-commits] [mlir] 57227c7 - [mlir][vector] Don't fold in_bounds for scalable vector dimensions (#213506)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Aug 10 01:49:29 PDT 2026
Author: Dhairyashil R G
Date: 2026-08-10T09:49:23+01:00
New Revision: 57227c70be689b1d81354adadcc01bb292da939a
URL: https://github.com/llvm/llvm-project/commit/57227c70be689b1d81354adadcc01bb292da939a
DIFF: https://github.com/llvm/llvm-project/commit/57227c70be689b1d81354adadcc01bb292da939a.diff
LOG: [mlir][vector] Don't fold in_bounds for scalable vector dimensions (#213506)
`isInBounds` compares `index + getVectorType().getDimSize(resultIdx)`
against the static size of the corresponding source dimension. For a
scalable dimension the vector holds `vscale * getDimSize(resultIdx)`
elements, so the static size is only a lower bound and the comparison
can succeed for a transfer that is actually out of bounds.
For example, reading `vector<[4]xf32>` at index 0 of a `memref<4xf32>`
folds to `in_bounds = [true]` because `0 + 4 <= 4`. Lowering then turns
a predicated masked load into a plain full-width load:
```mlir
// Without the fold: predicated, lanes >= 4 are inactive.
%8 = llvm.intr.stepvector : vector<[4]xi32>
%16 = llvm.icmp "slt" %8, %15 : vector<[4]xi32>
%23 = llvm.intr.masked.load %22, %16, %20 {alignment = 4 : i32} :
(!llvm.ptr, vector<[4]xi1>, vector<[4]xf32>) -> vector<[4]xf32>
// With the fold: unpredicated, reads `4 * vscale` elements.
%9 = llvm.load %8 {alignment = 4 : i64} : !llvm.ptr -> vector<[4]xf32>
```
which reaches AArch64 as `ld1w { z1.s }, p1/z, [x1]` and `ldr z1, [x1]`
respectively. For every `vscale > 1` the folded form reads past the end
of the allocation. The same applies to `vector.transfer_write`, where
the fold turns a masked store into an out-of-bounds one.
Bail out for scalable result dimensions. Fixed-size dimensions of a
partly scalable vector are unaffected and still fold; the added
`vector<4x[4]xf32>` test covers that.
Confirmed on SVE hardware (AWS Graviton3, 256-bit vectors, vscale = 2)
with the 4-element buffer placed flush against a PROT_NONE guard page:
predicated ld1w -> returns 10.0, exit 0
unpredicated ldr -> SIGSEGV, exit 139
Assisted-by: Claude
Added:
Modified:
mlir/lib/Dialect/Vector/IR/VectorOps.cpp
mlir/test/Dialect/Vector/canonicalize.mlir
Removed:
################################################################################
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index e169ca4fe0c82..170c0070ff07f 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -5377,6 +5377,10 @@ static bool isInBounds(TransferOp op, int64_t resultIdx, int64_t indicesIdx) {
// op.getIndices()[indicesIdx] + vectorType < dim(op.getSource(), indicesIdx)
if (op.getShapedType().isDynamicDim(indicesIdx))
return false;
+ // Scalable dimensions are `vscale` times larger at runtime, so the static
+ // size is only a lower bound and cannot prove that the transfer fits.
+ if (op.getVectorType().getScalableDims()[resultIdx])
+ return false;
Value index = op.getIndices()[indicesIdx];
std::optional<int64_t> cstOp = getConstantIntValue(index);
if (!cstOp.has_value())
diff --git a/mlir/test/Dialect/Vector/canonicalize.mlir b/mlir/test/Dialect/Vector/canonicalize.mlir
index ed4c908c6e5f2..bb1729cd7c3bc 100644
--- a/mlir/test/Dialect/Vector/canonicalize.mlir
+++ b/mlir/test/Dialect/Vector/canonicalize.mlir
@@ -1404,6 +1404,37 @@ func.func @fold_vector_transfer_masks(%A: memref<?x?xf32>) -> (vector<4x8xf32>,
// -----
+// A scalable vector dimension holds `vscale * N` elements, so the static size N
+// is only a lower bound and cannot prove that the transfer is in bounds. Here
+// `vector<[4]xf32>` reads `4 * vscale` elements from a 4-element memref, which
+// is out of bounds for every `vscale > 1`.
+
+// CHECK-LABEL: func @no_fold_transfer_read_in_bounds_scalable
+// CHECK: vector.transfer_read
+// CHECK-NOT: in_bounds
+// CHECK: : memref<4xf32>, vector<[4]xf32>
+func.func @no_fold_transfer_read_in_bounds_scalable(%m: memref<4xf32>, %p: f32) -> vector<[4]xf32> {
+ %c0 = arith.constant 0 : index
+ %v = vector.transfer_read %m[%c0], %p : memref<4xf32>, vector<[4]xf32>
+ return %v : vector<[4]xf32>
+}
+
+// -----
+
+// Same for the write path, where an unsound fold is an out-of-bounds store.
+
+// CHECK-LABEL: func @no_fold_transfer_write_in_bounds_scalable
+// CHECK: vector.transfer_write
+// CHECK-NOT: in_bounds
+// CHECK: : vector<[4]xf32>, memref<4xf32>
+func.func @no_fold_transfer_write_in_bounds_scalable(%m: memref<4xf32>, %v: vector<[4]xf32>) {
+ %c0 = arith.constant 0 : index
+ vector.transfer_write %v, %m[%c0] : vector<[4]xf32>, memref<4xf32>
+ return
+}
+
+// -----
+
// 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