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

Aviad Cohen llvmlistbot at llvm.org
Sun Mar 1 11:52:21 PST 2026


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

>From 939e4827d30a4d21bea989a818357bdcd2bc4a8d Mon Sep 17 00:00:00 2001
From: Aviad Cohen <aviad.cohen2 at mobileye.com>
Date: Sun, 1 Mar 2026 08:45:06 +0200
Subject: [PATCH] [mlir][memref] Add ValueBoundsOpInterface for ViewOp with OOB
 constraint

Implement ValueBoundsOpInterface for memref.view so that value bounds and reification can use both the declared sizes and an out-of-bounds (OOB) constraint.
---
 .../MemRef/IR/ValueBoundsOpInterfaceImpl.cpp  |  39 ++++
 .../value-bounds-op-interface-impl.mlir       | 170 ++++++++++++++++++
 2 files changed, 209 insertions(+)

diff --git a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
index 69afbcadb0b07..ea2dcbf4227de 100644
--- a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -9,6 +9,7 @@
 #include "mlir/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.h"
 
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/IR/Builders.h"
 #include "mlir/Interfaces/ValueBoundsOpInterface.h"
 
 using namespace mlir;
@@ -142,6 +143,43 @@ 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;
+    MemRefType memrefType = viewOp.getType();
+    if (!memrefType.getElementType().isIntOrFloat())
+      return;
+    int64_t elementSizeBytes = memrefType.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 +200,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..34821c5c05303 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,173 @@ 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_dynamic_sizes(
+// CHECK-SAME:      %[[raw:.*]]: memref<?xi8>, %[[shift:.*]]: index, %[[sz0:.*]]: index, %[[sz1:.*]]: index) -> (index, index) {
+//       CHECK:   %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[sz0]], %[[sz1]]]
+//       CHECK:   return %[[sz0]], %[[sz1]] : 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: 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: dynamic shift and dynamic size. Reify returns the size operand.
+// Source 256 bytes (alloc), view ?xf32. test.compare checks dim0 >= 0 (OOB + size imply non-negative).
+// CHECK-LABEL: func @memref_view_oob_shift_plus_size_eq_src_with_shift(
+//  CHECK-SAME:     %[[shift:.*]]: index, %[[n:.*]]: index
+//       CHECK:   %[[raw:.*]] = memref.alloc()
+//       CHECK:   %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[n]]]
+//       CHECK:   return %[[n]]
+func.func @memref_view_oob_shift_plus_size_eq_src_with_shift(%shift: index, %n: index) -> index {
+  %c0 = arith.constant 0 : index
+  %raw = memref.alloc() : memref<256xi8>
+  %0 = memref.view %raw[%shift][%n] : memref<256xi8> 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 shift and sizes): 2D view. Source 1024 bytes, dynamic shift and sizes.
+// test.compare checks OOB on both dims: dim0 >= 0, dim1 >= 0. Reify returns size operands.
+// CHECK-LABEL: func @memref_view_oob_dynamic_2d_in_bounds(
+//  CHECK-SAME:     %[[raw:.*]]: memref<1024xi8>, %[[shift:.*]]: index, %[[sz0:.*]]: index, %[[sz1:.*]]: index
+//       CHECK:   %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[sz0]], %[[sz1]]]
+//       CHECK:   return %[[sz0]], %[[sz1]] : index, index
+func.func @memref_view_oob_dynamic_2d_in_bounds(%raw: memref<1024xi8>, %shift: index, %sz0: index, %sz1: index) -> (index, index) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %0 = memref.view %raw[%shift][%sz0, %sz1] : memref<1024xi8> 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): 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 OOB (2D, one dynamic dim, constant source): alloc 256 bytes, view with dynamic dim0 %n and static dim1=4.
+// OOB constraint forces dim0 == (256-0)/(4*4) == 16; test.compare proves dim0 == 16, dim1 == 4.
+// CHECK-LABEL: func @memref_view_oob_dynamic_2d_constant_source(
+//       CHECK:   arith.constant 16 : index
+//       CHECK:   arith.constant 4 : index
+//       CHECK:   return {{.*}}, {{.*}} : index, index
+func.func @memref_view_oob_dynamic_2d_constant_source(%n: index) -> (index, index) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c16 = arith.constant 16 : index
+  %c4 = arith.constant 4 : index
+  %raw = memref.alloc() : memref<256xi8>
+  %0 = memref.view %raw[%c0][%n] : memref<256xi8> to memref<?x4xf32>
+  %dim0 = memref.dim %0, %c0 : memref<?x4xf32>
+  %dim1 = memref.dim %0, %c1 : memref<?x4xf32>
+  // expected-remark @below{{true}}
+  "test.compare"(%dim0, %c16) {cmp = "EQ"} : (index, index) -> ()
+  // expected-remark @below{{true}}
+  "test.compare"(%dim1, %c4) {cmp = "EQ"} : (index, index) -> ()
+  %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?x4xf32>) -> (index)
+  %2 = "test.reify_bound"(%0) {dim = 1} : (memref<?x4xf32>) -> (index)
+  return %1, %2 : index, 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
+}



More information about the Mlir-commits mailing list