Skip to content

[mlir][memref] Allow out-of-bounds semantics for memref.subview #152164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2047,11 +2047,9 @@ def SubViewOp : MemRef_OpWithOffsetSizesAndStrides<"subview", [
result_offset = src_offset + dot_product(offset_operands, src_strides)
```

The offset, size and stride operands must be in-bounds with respect to the
source memref. When possible, the static operation verifier will detect
out-of-bounds subviews. Subviews that cannot be confirmed to be in-bounds
or out-of-bounds based on compile-time information are valid. However,
performing an out-of-bounds subview at runtime is undefined behavior.
The operation does not guarantee if the created subview is in-bounds. It is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we clarify what exactly out-of-bounds means? Something along the lines of:

Subviews are allowed to run out-of-bounds. Both the offset and "offset + size" are allowed to be larger than the respective source dimension size. They cannot be negative.

the responsibility of the user to guarantee there are no out-of-bounds
accesses into the subview.
Comment on lines +2050 to +2052
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use poison?

Suggested change
The operation does not guarantee if the created subview is in-bounds. It is
the responsibility of the user to guarantee there are no out-of-bounds
accesses into the subview.
It is legal to use this operation to create a subview that is out-of-bounds, however
the returned value is poison and access to the subview will trigger undefined-behavior.

Copy link
Member

@matthias-springer matthias-springer Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Groverkss Can you describe the use case that you have in mind? If the result is poisoned (which would make sense to me), loading from it will also be poison undefined behavior (even if the out-of-bounds element will be masked out). I suspect you want to use memref.subview just as an op that does index computations

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned value is partially poisoned. The entire memref is not poisoned. It is not undefined behavior to access part of the subview that is in bounds.


Example 1:

Expand Down
17 changes: 10 additions & 7 deletions mlir/include/mlir/Interfaces/ViewLikeInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ SliceBoundsVerificationResult verifyInBoundsSlice(
/// returns the new result type of the op, based on the new offsets, sizes and
/// strides. `CastOpFunc` is used to generate a cast op if the result type of
/// the op has changed.
template <typename OpType, typename ResultTypeFn, typename CastOpFunc>
template <typename OpType, typename ResultTypeFn, typename CastOpFunc,
bool CheckInBounds = true>
class OpWithOffsetSizesAndStridesConstantArgumentFolder final
: public OpRewritePattern<OpType> {
public:
Expand All @@ -94,12 +95,14 @@ class OpWithOffsetSizesAndStridesConstantArgumentFolder final
failed(foldDynamicIndexList(mixedStrides)))
return failure();

// Pattern does not apply if the produced op would not verify.
SliceBoundsVerificationResult sliceResult = verifyInBoundsSlice(
cast<ShapedType>(op.getSource().getType()).getShape(), mixedOffsets,
mixedSizes, mixedStrides);
if (!sliceResult.isValid)
return failure();
if (CheckInBounds) {
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional bounds checking introduces complexity where the template parameter controls verification behavior. Consider documenting why bounds checking is optional and when each mode should be used to improve code maintainability.

Copilot uses AI. Check for mistakes.

// Pattern does not apply if the produced op would not verify.
SliceBoundsVerificationResult sliceResult = verifyInBoundsSlice(
cast<ShapedType>(op.getSource().getType()).getShape(), mixedOffsets,
mixedSizes, mixedStrides);
if (!sliceResult.isValid)
return failure();
}

// Compute the new result type.
auto resultType =
Expand Down
16 changes: 4 additions & 12 deletions mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2977,14 +2977,6 @@ LogicalResult SubViewOp::verify() {
return produceSubViewErrorMsg(SliceVerificationResult::LayoutMismatch,
*this, expectedType);

// Verify that offsets, sizes, strides do not run out-of-bounds with respect
// to the base memref.
SliceBoundsVerificationResult boundsResult =
verifyInBoundsSlice(baseType.getShape(), staticOffsets, staticSizes,
staticStrides, /*generateErrorMessage=*/true);
if (!boundsResult.isValid)
return getOperation()->emitError(boundsResult.errorMessage);

return success();
}

Expand Down Expand Up @@ -3253,10 +3245,10 @@ struct SubViewCanonicalizer {

void SubViewOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
results
.add<OpWithOffsetSizesAndStridesConstantArgumentFolder<
SubViewOp, SubViewReturnTypeCanonicalizer, SubViewCanonicalizer>,
SubViewOpMemRefCastFolder, TrivialSubViewOpFolder>(context);
results.add<OpWithOffsetSizesAndStridesConstantArgumentFolder<
SubViewOp, SubViewReturnTypeCanonicalizer,
SubViewCanonicalizer, /*CheckInBounds=*/false>,
SubViewOpMemRefCastFolder, TrivialSubViewOpFolder>(context);
}

OpFoldResult SubViewOp::fold(FoldAdaptor adaptor) {
Expand Down
16 changes: 0 additions & 16 deletions mlir/test/Dialect/MemRef/invalid.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -745,22 +745,6 @@ func.func @invalid_subview(%arg0 : index, %arg1 : index, %arg2 : index) {

// -----

func.func @invalid_subview(%arg0: memref<10xf32>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move these to ops.mlir?

// expected-error@+1 {{offset 0 is out-of-bounds: 10 >= 10}}
%0 = memref.subview %arg0 [10][1][1] : memref<10xf32> to memref<1xf32, strided<[1], offset: 10>>
return
}

// -----

func.func @invalid_subview(%arg0: memref<9xf32>) {
// expected-error@+1 {{slice along dimension 0 runs out-of-bounds: 9 >= 9}}
%0 = memref.subview %arg0 [3][4][2] : memref<9xf32> to memref<4xf32, strided<[2], offset: 3>>
return
}

// -----

func.func @invalid_rank_reducing_subview(%arg0 : index, %arg1 : index, %arg2 : index) {
%0 = memref.alloc() : memref<8x16x4xf32>
// expected-error@+1 {{expected result type to be 'memref<8x16x4xf32, strided<[64, 4, 1]>>' or a rank-reduced version. (mismatch of result sizes)}}
Expand Down
Loading