llvm / llvm/llvm-project

[mlir][vector] Negative transfer starts are treated as in-bounds: canonicalization sets in_bounds=true and VectorToSCF guards only the upper bound

Open
#223,258 3 comments 0 reactions 0 assignees View on GitHub
mlir
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

`vector.transfer_read`/`transfer_write` document that with `in_bounds = false`
the accesses, **including the starting point**, may run out of bounds. Two
independent paths assume only the *upper* end can overrun, so a negative
starting index is treated as in-bounds.

**1. The canonicalizer asserts a safety attribute that is false.**
`isInBounds` (`mlir/lib/Dialect/Vector/IR/VectorOps.cpp:5391`) ends with:

```c++
int64_t sourceSize = op.getShapedType().getDimSize(indicesIdx);
int64_t vectorSize = op.getVectorType().getDimSize(resultIdx);

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

There is no `cstOp.value() >= 0` conjunct. For a start of `-1` into a
`memref<4xf32>` with `vector<3xf32>`, this computes `-1 + 3 = 2 <= 4` and
returns true, so `foldTransferInBoundsAttribute` rewrites `in_bounds = [false]`
to `[true]` under plain `-canonicalize`.

**2. `VectorToSCF` guards only the upper bound.** `generateInBoundsCheck`
(`mlir/lib/Conversion/VectorToSCF/VectorToSCF.cpp`) emits

```mlir
%3 = arith.cmpi slt, %2, %c4 : index
scf.if %3 { ... } else { ... }
```

with no `%2 >= 0` conjunct, so a negative index enters the `then` branch and
performs a scalar access below the allocation instead of padding the read or
suppressing the write.

Both reproduce at `31a1c03fe67c`.

## Reproducers

### Canonicalization flips the attribute

```mlir
func.func @negative_start(%a: memref<4xf32>) -> vector<3xf32> {
%cm1 = arith.constant -1 : index
%pad = arith.constant -42.0 : f32
%v = vector.transfer_read %a[%cm1], %pad {in_bounds = [false]}
: memref<4xf32>, vector<3xf32>
return %v : vector<3xf32>
}
```

```
mlir-opt -canonicalize repro.mlir
```

```mlir
%0 = vector.transfer_read %arg0[%c-1], %cst {in_bounds = [true]} : memref<4xf32>, vector<3xf32>
```

The transfer starts one element *below* the buffer, and is now marked as
guaranteed in-bounds. Any consumer that trusts the attribute will skip the
bounds handling this transfer requires.

### The lowered guard misses the lower bound

```mlir
func.func @neg2d(%a: memref<4x8xf32>, %i: index) -> vector<2x8xf32> {
%pad = arith.constant -42.0 : f32
%v = vector.transfer_read %a[%i, %i], %pad {in_bounds = [false, true]}
: memref<4x8xf32>, vector<2x8xf32>
return %v : vector<2x8xf32>
}
```

```
mlir-opt -convert-vector-to-scf repro2.mlir
```

```mlir
scf.for %arg2 = %c0 to %c2 step %c1 {
%2 = affine.apply #map(%arg2)[%arg1]
%3 = arith.cmpi slt, %2, %c4 : index // upper bound only
scf.if %3 {
%5 = vector.transfer_read %arg0[%4, %arg1], %cst_0 {in_bounds = [true]} ...
```

For a negative `%i` the condition holds and the guarded access runs below the
source.

## Why this is a bug and not intended behaviour

The op documentation is explicit that the starting point is covered:

> For every vector dimension, the boolean array attribute `in_bounds` specifies
> if the transfer is guaranteed to be within the source bounds. If set to
> "false", accesses (**including the starting point**) may run out-of-bounds
> along the respective vector dimension as the index increases.

So a negative start is a legal input for `in_bounds = false`, and both the
`in_bounds` inference and the lowered guard must establish `start >= 0` before
treating the access as safe.

## Environment

* llvm-project at `31a1c03fe67c`
* Release build with assertions, Linux x86-64

Contributor guide

Open the contributing guide

Research direction

Start with isInBounds and foldTransferInBoundsAttribute in mlir/lib/Dialect/Vector/IR/VectorOps.cpp, then inspect generateInBoundsCheck in mlir/lib/Conversion/VectorToSCF/VectorToSCF.cpp. Run the two supplied mlir-opt reproducers first. Done means negative transfer starts are not marked in-bounds and lowered reads or writes do not access below the source bounds.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.