[Mlir-commits] [mlir] [mlir][memref]: Added OOB value bound check to ViewOp (PR #183985)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Feb 28 23:41:00 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-memref

Author: Aviad Cohen (AviadCo)

<details>
<summary>Changes</summary>



---
Full diff: https://github.com/llvm/llvm-project/pull/183985.diff


2 Files Affected:

- (modified) mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp (+34) 
- (modified) mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir (+211) 


``````````diff
diff --git a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
index 69afbcadb0b07..6d4c2e474013a 100644
--- a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -142,6 +142,39 @@ struct SubViewOpInterface
   }
 };
 
+struct ViewOpInterface
+    : public ValueBoundsOpInterface::ExternalModel<ViewOpInterface, ViewOp> {
+  void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
+                                       ValueBoundsConstraintSet &cstr) const {
+    auto viewOp = cast<ViewOp>(op);
+    assert(value == viewOp.getResult() && "invalid value");
+
+    // Each result dimension equals the corresponding mixed size (static or
+    // dynamic operand).
+    cstr.bound(value)[dim] == viewOp.getMixedSizes()[dim];
+
+    // Out-of-bounds constraint in bytes.
+    // The total number of bytes in the result should be the product of all result dimensions according to the element type size.
+    // Therefore, the out-of-bounds constraint in bytes is:
+    // (src_bytes - byte_shift) / dim_size == total_result_bytes_without_dim
+    // meaning that:
+    //  dim_size == (src_bytes - byte_shift) / total_result_bytes_without_dim
+    AffineExpr sourceBytes = cstr.getExpr(viewOp.getSource(), 0);
+    AffineExpr byteShift = cstr.getExpr(viewOp.getByteShift());
+    AffineExpr availableBytes = sourceBytes - byteShift;
+    // Element size in bytes (getElementTypeBitWidth() returns bits).
+    int64_t elementSizeBytes = viewOp.getType().getElementTypeBitWidth() / 8;
+    if (elementSizeBytes == 0)
+      return;
+    AffineExpr totalResultBytesWithoutDim = cstr.getExpr(elementSizeBytes);
+    for (unsigned i = 0; i < viewOp.getType().getRank(); ++i)
+      if (i != dim)
+        totalResultBytesWithoutDim = totalResultBytesWithoutDim * cstr.getExpr(value, i);
+
+    cstr.bound(value)[dim] == availableBytes.floorDiv(totalResultBytesWithoutDim);
+  }
+};
+
 } // namespace
 } // namespace memref
 } // namespace mlir
@@ -162,5 +195,6 @@ void mlir::memref::registerValueBoundsOpInterfaceExternalModels(
     memref::GetGlobalOp::attachInterface<memref::GetGlobalOpInterface>(*ctx);
     memref::RankOp::attachInterface<memref::RankOpInterface>(*ctx);
     memref::SubViewOp::attachInterface<memref::SubViewOpInterface>(*ctx);
+    memref::ViewOp::attachInterface<memref::ViewOpInterface>(*ctx);
   });
 }
diff --git a/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir
index d0aec68d54988..a29a356d01f85 100644
--- a/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir
@@ -127,3 +127,214 @@ func.func @memref_subview(%m: memref<?xf32>, %sz: index) -> index {
   %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?xf32, strided<[1], offset: 2>>) -> (index)
   return %1 : index
 }
+
+// -----
+
+// CHECK-LABEL: func @memref_view_static_sizes(
+//       CHECK:   %[[c64:.*]] = arith.constant 64 : index
+//       CHECK:   %[[c4:.*]] = arith.constant 4 : index
+//       CHECK:   return %[[c64]], %[[c4]]
+func.func @memref_view_static_sizes(%raw: memref<2048xi8>, %shift: index) -> (index, index) {
+  %0 = memref.view %raw[%shift][] : memref<2048xi8> to memref<64x4xf32>
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64x4xf32>) -> (index)
+  %2 = "test.reify_bound"(%0) {dim = 1} : (memref<64x4xf32>) -> (index)
+  return %1, %2 : index, index
+}
+
+// -----
+
+// CHECK-LABEL: func @memref_view_dynamic_sizes(
+//       CHECK:   %[[view:.*]] = memref.view
+//       CHECK:   return {{.*}}, {{.*}} : index, index
+func.func @memref_view_dynamic_sizes(%raw: memref<?xi8>, %shift: index, %sz0: index, %sz1: index) -> (index, index) {
+  %0 = memref.view %raw[%shift][%sz0, %sz1] : memref<?xi8> to memref<?x?xf32>
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?x?xf32>) -> (index)
+  %2 = "test.reify_bound"(%0) {dim = 1} : (memref<?x?xf32>) -> (index)
+  return %1, %2 : index, index
+}
+
+// -----
+
+// ViewOp OOB (static): 1D view in-bounds. Source 256 bytes, view 64xf32 (64*4=256).
+// test.compare checks OOB on dim 0 (dim == 64).
+// CHECK-LABEL: func @memref_view_oob_static_1d_in_bounds(
+//       CHECK:   %[[c64:.*]] = arith.constant 64 : index
+//       CHECK:   return %[[c64]]
+func.func @memref_view_oob_static_1d_in_bounds(%raw: memref<256xi8>, %shift: index) -> index {
+  %c0 = arith.constant 0 : index
+  %c64 = arith.constant 64 : index
+  %0 = memref.view %raw[%shift][] : memref<256xi8> to memref<64xf32>
+  %dim0 = memref.dim %0, %c0 : memref<64xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64xf32>) -> (index)
+  return %1 : index
+}
+
+// -----
+
+// ViewOp OOB: verify shift_bytes + view_size_bytes == src_size (1D f32: view_size = dim0*4).
+// Source 256 bytes, shift 0, view 64xf32 -> 0 + 64*4 == 256.
+func.func @memref_view_oob_shift_plus_size_eq_src_1d() {
+  %c0 = arith.constant 0 : index
+  %raw = memref.alloc() : memref<256xi8>
+  %0 = memref.view %raw[%c0][] : memref<256xi8> to memref<64xf32>
+  %src_size = memref.dim %raw, %c0 : memref<256xi8>
+  %dim0 = memref.dim %0, %c0 : memref<64xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%c0, %dim0, %src_size)
+      {cmp = "EQ", lhs_map = affine_map<(d0, d1) -> (d0 + d1 * 4)>,
+       rhs_map = affine_map<(d0) -> (d0)>}
+      : (index, index, index) -> ()
+  return
+}
+
+// -----
+
+// ViewOp OOB: shift_bytes + view_size_bytes == src_size with non-zero shift.
+// Source 256 bytes, shift 128, view 32xf32 -> 128 + 32*4 == 256.
+func.func @memref_view_oob_shift_plus_size_eq_src_with_shift() {
+  %c0 = arith.constant 0 : index
+  %c128 = arith.constant 128 : index
+  %raw = memref.alloc() : memref<256xi8>
+  %0 = memref.view %raw[%c128][] : memref<256xi8> to memref<32xf32>
+  %src_size = memref.dim %raw, %c0 : memref<256xi8>
+  %dim0 = memref.dim %0, %c0 : memref<32xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%c128, %dim0, %src_size)
+      {cmp = "EQ", lhs_map = affine_map<(d0, d1) -> (d0 + d1 * 4)>,
+       rhs_map = affine_map<(d0) -> (d0)>}
+      : (index, index, index) -> ()
+  return
+}
+
+// -----
+
+// ViewOp OOB (static): 2D view in-bounds. Source 1024 bytes, shift 0, view 64x4xf32 (1024 bytes).
+// test.compare checks OOB on both dims: dim0 == 64, dim1 == 4.
+// CHECK-LABEL: func @memref_view_oob_static_2d_in_bounds(
+//       CHECK:   arith.constant 64 : index
+//       CHECK:   arith.constant 4 : index
+//       CHECK:   return {{.*}}, {{.*}} : index, index
+func.func @memref_view_oob_static_2d_in_bounds(%raw: memref<1024xi8>, %shift: index) -> (index, index) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c64 = arith.constant 64 : index
+  %c4 = arith.constant 4 : index
+  %0 = memref.view %raw[%shift][] : memref<1024xi8> to memref<64x4xf32>
+  %dim0 = memref.dim %0, %c0 : memref<64x4xf32>
+  %dim1 = memref.dim %0, %c1 : memref<64x4xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+  // expected-remark @below{{true}}
+  "test.compare"(%dim1, %c4) {cmp = "EQ"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64x4xf32>) -> (index)
+  %2 = "test.reify_bound"(%0) {dim = 1} : (memref<64x4xf32>) -> (index)
+  return %1, %2 : index, index
+}
+
+// -----
+
+// ViewOp OOB (static) with byte_shift: source 256 bytes, shift 128, view 32xf32 (128 bytes).
+// test.compare checks OOB on dim 0 (dim == 32).
+// CHECK-LABEL: func @memref_view_oob_static_with_shift(
+//       CHECK:   %[[c32:.*]] = arith.constant 32 : index
+//       CHECK:   return %[[c32]]
+func.func @memref_view_oob_static_with_shift(%raw: memref<256xi8>, %shift: index) -> index {
+  %c0 = arith.constant 0 : index
+  %c32 = arith.constant 32 : index
+  %0 = memref.view %raw[%shift][] : memref<256xi8> to memref<32xf32>
+  %dim0 = memref.dim %0, %c0 : memref<32xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c32) {cmp = "EQ"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<32xf32>) -> (index)
+  return %1 : index
+}
+
+// -----
+
+// ViewOp OOB (static) different element type: f16 = 2 bytes. Source 128 bytes, view 64xf16.
+// test.compare checks OOB on dim 0 (dim == 64).
+// CHECK-LABEL: func @memref_view_oob_static_f16(
+//       CHECK:   %[[c64:.*]] = arith.constant 64 : index
+//       CHECK:   return %[[c64]]
+func.func @memref_view_oob_static_f16(%raw: memref<128xi8>, %shift: index) -> index {
+  %c0 = arith.constant 0 : index
+  %c64 = arith.constant 64 : index
+  %0 = memref.view %raw[%shift][] : memref<128xi8> to memref<64xf16>
+  %dim0 = memref.dim %0, %c0 : memref<64xf16>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64xf16>) -> (index)
+  return %1 : index
+}
+
+// -----
+
+// ViewOp OOB (dynamic): 1D view with dynamic size. Reify returns the size operand.
+// test.compare checks that dim 0 is non-negative (OOB + mixed size both imply >= 0).
+// CHECK-LABEL: func @memref_view_oob_dynamic_1d(
+//  CHECK-SAME:     %[[raw:.*]]: memref<?xi8>, %[[shift:.*]]: index, %[[n:.*]]: index
+//       CHECK:   %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[n]]]
+//       CHECK:   return %[[n]]
+func.func @memref_view_oob_dynamic_1d(%raw: memref<?xi8>, %shift: index, %n: index) -> index {
+  %c0 = arith.constant 0 : index
+  %0 = memref.view %raw[%shift][%n] : memref<?xi8> to memref<?xf32>
+  %dim0 = memref.dim %0, %c0 : memref<?xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c0) {cmp = "GE"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?xf32>) -> (index)
+  return %1 : index
+}
+
+// -----
+
+// ViewOp OOB (dynamic): 2D view with dynamic sizes. Reify returns size operands.
+// test.compare checks OOB on both dims: dim0 >= 0, dim1 >= 0.
+// CHECK-LABEL: func @memref_view_oob_dynamic_2d(
+//       CHECK:   %[[view:.*]] = memref.view
+//       CHECK:   return {{.*}}, {{.*}} : index, index
+func.func @memref_view_oob_dynamic_2d(%raw: memref<?xi8>, %shift: index, %a: index, %b: index) -> (index, index) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %0 = memref.view %raw[%shift][%a, %b] : memref<?xi8> to memref<?x?xf32>
+  %dim0 = memref.dim %0, %c0 : memref<?x?xf32>
+  %dim1 = memref.dim %0, %c1 : memref<?x?xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c0) {cmp = "GE"} : (index, index) -> ()
+  // expected-remark @below{{true}}
+  "test.compare"(%dim1, %c0) {cmp = "GE"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?x?xf32>) -> (index)
+  %2 = "test.reify_bound"(%0) {dim = 1} : (memref<?x?xf32>) -> (index)
+  return %1, %2 : index, index
+}
+
+// -----
+
+// ViewOp OOB (dynamic sizes, constant source): alloc 256 bytes, view with dynamic size %n.
+// OOB constraint forces dim0 == (256-0)/4 == 64; test.compare proves dim0 == 64.
+// CHECK-LABEL: func @memref_view_oob_dynamic_1d_constant_source(
+//       CHECK:   %[[c64:.*]] = arith.constant 64 : index
+//       CHECK:   return %[[c64]]
+func.func @memref_view_oob_dynamic_1d_constant_source(%n: index) -> index {
+  %c0 = arith.constant 0 : index
+  %c64 = arith.constant 64 : index
+  %raw = memref.alloc() : memref<256xi8>
+  %0 = memref.view %raw[%c0][%n] : memref<256xi8> to memref<?xf32>
+  %dim0 = memref.dim %0, %c0 : memref<?xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?xf32>) -> (index)
+  return %1 : index
+}
+
+// -----
+
+// ViewOp value bound failure: constant bound requested but view dim is dynamic (from block arg).
+// Only constraint is bound == %size; no constant available.
+func.func @memref_view_reify_constant_fails(%raw: memref<?xi8>, %shift: index, %size: index) -> index {
+  %0 = memref.view %raw[%shift][%size] : memref<?xi8> to memref<?xf32>
+  // expected-error @below {{could not reify bound}}
+  %1 = "test.reify_bound"(%0) {dim = 0, constant} : (memref<?xf32>) -> (index)
+  return %1 : index
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/183985


More information about the Mlir-commits mailing list