Repository: llvm/llvm-project
Pull request #135498: [mlir][vector] Prevent folding of OOB values in insert/extract
Author: login: math-fehr
Labels: name: mlir:vectorops
name: mlir
name: mlir:vector
Created: 2025-04-12T18:23:43Z | Merged: 2025-04-18T03:53:27Z

Out of bound position values should not be folded in vector.extract and vector.insert operations, as only in bounds constants and -1 are valid.

Fixes #134516

Linked issue #134516: [mlir][vector] Error when running `--canonicalize` with out of bounds positions in `vector.extract`
The following code
```
func.func @extract_poison_idx(%arg0: vector<16xf32>) -> f32 {
  %x = arith.constant -2 : index
  %0 = vector.extract %arg0[%x]: f32 from vector<16xf32>
  return %0 : f32
}
```
raises the following error when running `--canonicalize`
```
<source>:3:8: error: 'vector.extract' op expected position attribute #1 to be a non-negative integer smaller than the corresponding vector dimension or poison (-1)
  %0 = vector.extract %arg0[%x]: f32 from vector<16xf32>
       ^
```

This is due to `-1` being the only accepted static position that is out of bounds in a `vector.extract` or `vector.insert`.

There should be two ways to fix this:
* Either we allow out of bounds static positions that are not `-1` (which are also poison).
* Or the `vector.extract` builders should map out of bounds static positions to `-1`.
* Or it is the role of the users to make sure they do not pass non `-1` out of bounds static positions.

I feel both options come with a cost, and would personally prefer solution 1, as it follows LLVM semantics (`extractelement <4 x i32> %vec, i32 -2` is poison as well), and is roughly more modular. I feel solution 3 is exactly why we have this bug in the first place, and would be quite error-prone overall, because it is really easy to miss. Solution 2 is *okay* for me, but I feel following LLVM would make more sense here.

What do you think? I can write the patch, I just want to make sure we agree on a solution first before going into it. It also applies for `vector.insert` and `vector.shuffle` from what I see.
Linked issue comments: user: login: math-fehr
created_at: 2025-04-06T00:59:47Z
body: @kuhar @banach-space @dcaballe @Groverkss, I'm pinging you explicitly as you were the ones reviewing/writing #123488 which introduced the `-1` constraint.
author_association: CONTRIBUTOR
user: login: kuhar
created_at: 2025-04-07T02:48:00Z
body: The combination of the verifier & canonicalizer is too strict -- we shouldn't be checking (non-attribute) operands like this.

With the current op specs, this is immediate UB, and the fix would be either:
* Teach canon patterns to leave invalid constant operands as-is.
* Turn invalid constant operands into valid attribute operands (-1 or any other valid index). This is fine because lowering can make things more defined (refinement).

I'd be leaning towards not for defining extract/insert with OOB indices as poison -- I'm not convinced yet it improves the overall consistency/ergonomic. I said 'leaning' because in terms of runtime overhead in the dynamic case, I don't think it will cause any extra overhead during lowering:
* SPIR-V does treat any OOB index (including -1) as immediate UB, and we already have to account for that in the lowering by clamping. Adding more OOB cases won't make the situation worse for SPIR-V.
* LLVM already treats OOB indices as poison: https://llvm.org/docs/LangRef.html#id184.

If others would prefer consistency with the LLVM instructions, I won't object. If we do go this way, I think this should be a separate PR from the one that makes the current semantics work properly.
author_association: MEMBER
user: login: math-fehr
created_at: 2025-04-07T14:47:45Z
body: That makes sense to me!
Technically here it is not that we check non-attribute operands, but it's the fact that the folder wouldn't map a wrong access to -1.

I think I have a more general question which is why do we require OOB accesses to be mapped to `-1`? Looking at the origin of it [#123488](https://github.com/llvm/llvm-project/pull/123488), it seems that there was a misunderstanding on how LLVM works, as `-1` is not a special case they are using AFAIK, and any OOB value is allowed (and canonicalize the operation to `poison`). I am wondering if we should not instead allow all values in the verifier, which should simplify a bit the semantics and avoid these kind of errors. Unless maybe there are other considerations I'm missing?

And yes, for chosing wether extract/insert should be OOB or poison, the only datapoint I have is that LLVM uses poison instead of UB so that the operations can be safely hoisted from loops (because of ConditionallySpeculatable), which is way harder to do when immediate UB is involved.
author_association: CONTRIBUTOR
user: login: kuhar
created_at: 2025-04-07T16:23:31Z
body: > I think I have a more general question which is why do we require OOB accesses to be mapped to `-1`? Looking at the origin of it [#123488](https://github.com/llvm/llvm-project/pull/123488), it seems that there was a misunderstanding on how LLVM works, as `-1` is not a special case they are using AFAIK, and any OOB value is allowed (and canonicalize the operation to `poison`)

I think this follows the shufflevector semantics, where there's dedicated poison index: https://llvm.org/docs/LangRef.html#id193. @dcaballe should know if this is intended or not.

> I am wondering if we should not instead allow all values in the verifier, which should simplify a bit the semantics and avoid these kind of errors. Unless maybe there are other considerations I'm missing?

I think this may make sense but I think it'd be better to stage this and first fix the current semantics before allowing more OOB indices.

If we canonicalize constant OOB indices towards `-1`, this should fix the error, and be compatible with allowing more OOB values later on.

author_association: MEMBER
user: login: banach-space
created_at: 2025-04-07T17:49:50Z
body: Thanks for raising this issue, @math-fehr !

> The combination of the verifier & canonicalizer is too strict -- we shouldn't be checking (non-attribute) operands like this.

Agreed. This violates the (undocumented?) rule that a verifier should only rely on the "local" info. This is not "local":

```mlir
  %x = arith.constant -2 : index
```

is that what you had in mind, Jakub?

> I think I have a more general question which is why do we require OOB accesses to be mapped to -1?

Just to clarify, `-1` was introduced to model "poison" values specifically. I don't recall discussing OOB.

> I think this follows the shufflevector semantics, where there's dedicated poison index: https://llvm.org/docs/LangRef.html#id193.

I actually don't see anything specific about `-1` 🤔 

> If others would prefer consistency with the LLVM instructions, I won't object.

I don't really have any data-points to refer to, so for now I'd keep `-1` as the only valid option and to use that to model `poison` specifically. Mostly to keep things simple. 
author_association: CONTRIBUTOR
user: login: kuhar
created_at: 2025-04-07T20:31:02Z
body: > is that what you had in mind, Jakub?

Yes, exactly. This is documented here: https://mlir.llvm.org/getting_started/DeveloperGuide/#ir-verifier
author_association: MEMBER
user: login: math-fehr
created_at: 2025-04-07T20:55:08Z
body: > Agreed. This violates the (undocumented?) rule that a verifier should only rely on the "local" info. This is not "local":

I think there is a misunderstanding on this, the error comes from the folder that folds an OOB access, which triggers a verifier error after the operation is folded.

> Just to clarify, -1 was introduced to model "poison" values specifically. I don't recall discussing OOB.

Ohhh, this was a misunderstanding on my end, my bad.

> I think this may make sense but I think it'd be better to stage this and first fix the current semantics before allowing more OOB indices.

Yes, I'll write a patch to fix the folder, so then at least this should now work.
author_association: CONTRIBUTOR
user: login: kuhar
created_at: 2025-04-07T22:03:40Z
body: > I think there is a misunderstanding on this, the error comes from the folder that folds an OOB access, which triggers a verifier error after the operation is folded.

I think we are on the same page: it's a combination of the folder and the verifier that's at fault. Folders must not turn valid IR into invalid IR. The fix is to stop folding such inputs.
author_association: MEMBER
user: login: dcaballe
created_at: 2025-04-09T13:22:18Z
body: Using `-1` to represent a poison index comes from the shuffle vector implementation in LLVM: https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/Instructions.h#L1901-L1909. We extended it to `vector.extract` and `vector.insert` ops in MLIR for consistency and simplicity since they are all operations indexing a position within a vector and their indices are often propagated (with some minor transformations) across these ops in canonicalizations and folders.

I don't see a problem with allowing more OOB indices. This was initially constrained to test the waters. I would canonicalize OOB to `-1`, though, to avoid having arbitrary odd numbers in the IR.
author_association: CONTRIBUTOR
user: login: joker-eph
created_at: 2025-04-24T10:26:03Z
body: > With the current op specs, this is immediate UB,

The op is marked as "Pure", either it isn't immediate UB or it's a bug in ODS. 
You can't speculate an operation that is "immediate UB" for example. 
author_association: COLLABORATOR

Diff:
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index 5a3983699d5a3..71077d4943aa5 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -1996,6 +1996,11 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
   std::vector<int64_t> staticPosition = op.getStaticPosition().vec();
   OperandRange dynamicPosition = op.getDynamicPosition();
   ArrayRef<Attribute> dynamicPositionAttr = adaptor.getDynamicPosition();
+  ArrayRef<int64_t> vectorShape;
+  if constexpr (std::is_same_v<OpType, ExtractOp>)
+    vectorShape = op.getSourceVectorType().getShape();
+  else
+    vectorShape = op.getDestVectorType().getShape();
 
   // If the dynamic operands is empty, it is returned directly.
   if (!dynamicPosition.size())
@@ -2012,9 +2017,13 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
     Attribute positionAttr = dynamicPositionAttr[index];
     Value position = dynamicPosition[index++];
     if (auto attr = mlir::dyn_cast_if_present<IntegerAttr>(positionAttr)) {
-      staticPosition[i] = attr.getInt();
-      opChange = true;
-      continue;
+      int64_t value = attr.getInt();
+      // Do not fold if the value is out of bounds.
+      if (value >= 0 && value < vectorShape[i]) {
+        staticPosition[i] = attr.getInt();
+        opChange = true;
+        continue;
+      }
     }
     operands.push_back(position);
   }
diff --git a/mlir/test/Dialect/Vector/canonicalize.mlir b/mlir/test/Dialect/Vector/canonicalize.mlir
index b7db8ec834be7..ec2f823f4c701 100644
--- a/mlir/test/Dialect/Vector/canonicalize.mlir
+++ b/mlir/test/Dialect/Vector/canonicalize.mlir
@@ -3233,3 +3233,41 @@ func.func @fold_insert_constant_indices(%arg : vector<4x1xi32>) -> vector<4x1xi3
   %res = vector.insert %1, %arg[%0, %0] : i32 into vector<4x1xi32>
   return %res : vector<4x1xi32>
 }
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.insert.
+
+// CHECK-LABEL: @fold_insert_oob
+//  CHECK-SAME:   %[[ARG:.*]]: vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+//       CHECK:   %[[OOB1:.*]] = arith.constant -2 : index
+//       CHECK:   %[[OOB2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[VAL:.*]] = arith.constant 1 : i32
+//       CHECK:   %[[RES:.*]] = vector.insert %[[VAL]], %[[ARG]] [0, %[[OOB1]], %[[OOB2]]] : i32 into vector<4x1x2xi32>
+//       CHECK:   return %[[RES]] : vector<4x1x2xi32>
+func.func @fold_insert_oob(%arg : vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+  %c0 = arith.constant 0 : index
+  %c-2 = arith.constant -2 : index
+  %c2 = arith.constant 2 : index
+  %c1 = arith.constant 1 : i32
+  %res = vector.insert %c1, %arg[%c0, %c-2, %c2] : i32 into vector<4x1x2xi32>
+  return %res : vector<4x1x2xi32>
+}
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.extract.
+
+// CHECK-LABEL: @fold_extract_oob
+//  CHECK-SAME:   %[[ARG:.*]]: vector<4x1x2xi32>) -> i32 {
+//       CHECK:   %[[OOB1:.*]] = arith.constant -2 : index
+//       CHECK:   %[[OOB2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[RES:.*]] = vector.extract %[[ARG]][0, %[[OOB1]], %[[OOB2]]] : i32 from vector<4x1x2xi32>
+//       CHECK:   return %[[RES]] : i32
+func.func @fold_extract_oob(%arg : vector<4x1x2xi32>) -> i32 {
+  %c0 = arith.constant 0 : index
+  %c-2 = arith.constant -2 : index
+  %c2 = arith.constant 2 : index
+  %res = vector.extract %arg[%c0, %c-2, %c2] : i32 from vector<4x1x2xi32>
+  return %res : i32
+}


Reviews:
user: login: kuhar
body: Nice, thanks for fixing this!
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-13T02:00:24Z
user: login: math-fehr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T03:46:21Z
user: login: kuhar
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-17T03:57:22Z
user: login: joker-eph
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T08:30:12Z

Review comments:
diff_hunk: @@ -1996,6 +1996,12 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
   std::vector<int64_t> staticPosition = op.getStaticPosition().vec();
   OperandRange dynamicPosition = op.getDynamicPosition();
   ArrayRef<Attribute> dynamicPositionAttr = adaptor.getDynamicPosition();
+  ArrayRef<int64_t> vectorShape;
+  if constexpr (std::is_same_v<OpType, ExtractOp>) {
+    vectorShape = op.getSourceVectorType().getShape();
+  } else if constexpr (std::is_same_v<OpType, InsertOp>) {
+    vectorShape = op.getDestVectorType().getShape();
+  }
path: mlir/lib/Dialect/Vector/IR/VectorOps.cpp
user: login: kuhar
body: What happens in the else case? Since this is only meant for these two ops, I don't think we need the second if. Alternatively, make this a helper function overloaded for these two types?
created_at: 2025-04-13T01:58:40Z
author_association: MEMBER
diff_hunk: @@ -3233,3 +3233,41 @@ func.func @fold_insert_constant_indices(%arg : vector<4x1xi32>) -> vector<4x1xi3
   %res = vector.insert %1, %arg[%0, %0] : i32 into vector<4x1xi32>
   return %res : vector<4x1xi32>
 }
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.insert
+
+// CHECK-LABEL: @fold_insert_oob
+//  CHECK-SAME:   %[[ARG:.*]]: vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+//       CHECK:   %[[OOB1:.*]] = arith.constant -2 : index
+//       CHECK:   %[[OOB2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[VAL:.*]] = arith.constant 1 : i32
+//       CHECK:   %[[RES:.*]] = vector.insert %[[VAL]], %[[ARG]] [0, %[[OOB1]], %[[OOB2]]] : i32 into vector<4x1x2xi32>
+//       CHECK:   return %[[RES]] : vector<4x1x2xi32>
+func.func @fold_insert_oob(%arg : vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+  %0 = arith.constant 0 : index
+  %-2 = arith.constant -2 : index
path: mlir/test/Dialect/Vector/canonicalize.mlir
user: login: kuhar
body: nit: I'm surprised that this syntax works... Maybe prefix all constants with `c` like in the test cases towards the top of the file?
created_at: 2025-04-13T02:00:02Z
author_association: MEMBER
diff_hunk: @@ -3233,3 +3233,41 @@ func.func @fold_insert_constant_indices(%arg : vector<4x1xi32>) -> vector<4x1xi3
   %res = vector.insert %1, %arg[%0, %0] : i32 into vector<4x1xi32>
   return %res : vector<4x1xi32>
 }
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.insert
path: mlir/test/Dialect/Vector/canonicalize.mlir
user: login: kuhar
body: ```suggestion
// Check that out of bounds indices are not folded for vector.insert.
```
created_at: 2025-04-13T02:00:08Z
author_association: MEMBER
diff_hunk: @@ -3233,3 +3233,41 @@ func.func @fold_insert_constant_indices(%arg : vector<4x1xi32>) -> vector<4x1xi3
   %res = vector.insert %1, %arg[%0, %0] : i32 into vector<4x1xi32>
   return %res : vector<4x1xi32>
 }
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.insert
+
+// CHECK-LABEL: @fold_insert_oob
+//  CHECK-SAME:   %[[ARG:.*]]: vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+//       CHECK:   %[[OOB1:.*]] = arith.constant -2 : index
+//       CHECK:   %[[OOB2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[VAL:.*]] = arith.constant 1 : i32
+//       CHECK:   %[[RES:.*]] = vector.insert %[[VAL]], %[[ARG]] [0, %[[OOB1]], %[[OOB2]]] : i32 into vector<4x1x2xi32>
+//       CHECK:   return %[[RES]] : vector<4x1x2xi32>
+func.func @fold_insert_oob(%arg : vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+  %0 = arith.constant 0 : index
+  %-2 = arith.constant -2 : index
+  %2 = arith.constant 2 : index
+  %1 = arith.constant 1 : i32
+  %res = vector.insert %1, %arg[%0, %-2, %2] : i32 into vector<4x1x2xi32>
+  return %res : vector<4x1x2xi32>
+}
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.extract
path: mlir/test/Dialect/Vector/canonicalize.mlir
user: login: kuhar
body: ```suggestion
// Check that out of bounds indices are not folded for vector.extract.
```
created_at: 2025-04-13T02:00:13Z
author_association: MEMBER
diff_hunk: @@ -1996,6 +1996,12 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
   std::vector<int64_t> staticPosition = op.getStaticPosition().vec();
   OperandRange dynamicPosition = op.getDynamicPosition();
   ArrayRef<Attribute> dynamicPositionAttr = adaptor.getDynamicPosition();
+  ArrayRef<int64_t> vectorShape;
+  if constexpr (std::is_same_v<OpType, ExtractOp>) {
+    vectorShape = op.getSourceVectorType().getShape();
+  } else if constexpr (std::is_same_v<OpType, InsertOp>) {
+    vectorShape = op.getDestVectorType().getShape();
+  }
path: mlir/lib/Dialect/Vector/IR/VectorOps.cpp
user: login: math-fehr
body: I removed the second if if that's fine for you!
created_at: 2025-04-17T03:46:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1996,6 +1996,12 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
   std::vector<int64_t> staticPosition = op.getStaticPosition().vec();
   OperandRange dynamicPosition = op.getDynamicPosition();
   ArrayRef<Attribute> dynamicPositionAttr = adaptor.getDynamicPosition();
+  ArrayRef<int64_t> vectorShape;
+  if constexpr (std::is_same_v<OpType, ExtractOp>) {
+    vectorShape = op.getSourceVectorType().getShape();
+  } else {
+    vectorShape = op.getDestVectorType().getShape();
+  }
path: mlir/lib/Dialect/Vector/IR/VectorOps.cpp
user: login: joker-eph
body: ```suggestion
  if constexpr (std::is_same_v<OpType, ExtractOp>)
    vectorShape = op.getSourceVectorType().getShape();
  else
    vectorShape = op.getDestVectorType().getShape();
```
created_at: 2025-04-17T08:30:12Z
author_association: COLLABORATOR

PR comments:
user: login: llvmbot
created_at: 2025-04-12T18:24:05Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-vector

@llvm/pr-subscribers-mlir

Author: Fehr Mathieu (math-fehr)

<details>
<summary>Changes</summary>

Out of bound position values should not be folded in vector.extract and vector.insert operations, as only in bounds constants and -1 are valid.

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


2 Files Affected:

- (modified) mlir/lib/Dialect/Vector/IR/VectorOps.cpp (+13-3) 
- (modified) mlir/test/Dialect/Vector/canonicalize.mlir (+38) 


``````````diff
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index 5a3983699d5a3..0031608e2c9d5 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -1996,6 +1996,12 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
   std::vector<int64_t> staticPosition = op.getStaticPosition().vec();
   OperandRange dynamicPosition = op.getDynamicPosition();
   ArrayRef<Attribute> dynamicPositionAttr = adaptor.getDynamicPosition();
+  ArrayRef<int64_t> vectorShape;
+  if constexpr (std::is_same_v<OpType, ExtractOp>) {
+    vectorShape = op.getSourceVectorType().getShape();
+  } else if constexpr (std::is_same_v<OpType, InsertOp>) {
+    vectorShape = op.getDestVectorType().getShape();
+  }
 
   // If the dynamic operands is empty, it is returned directly.
   if (!dynamicPosition.size())
@@ -2012,9 +2018,13 @@ static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
     Attribute positionAttr = dynamicPositionAttr[index];
     Value position = dynamicPosition[index++];
     if (auto attr = mlir::dyn_cast_if_present<IntegerAttr>(positionAttr)) {
-      staticPosition[i] = attr.getInt();
-      opChange = true;
-      continue;
+      int64_t value = attr.getInt();
+      // Do not fold if the value is out of bounds.
+      if (value >= 0 && value < vectorShape[i]) {
+        staticPosition[i] = attr.getInt();
+        opChange = true;
+        continue;
+      }
     }
     operands.push_back(position);
   }
diff --git a/mlir/test/Dialect/Vector/canonicalize.mlir b/mlir/test/Dialect/Vector/canonicalize.mlir
index b7db8ec834be7..b0f502a0b7c36 100644
--- a/mlir/test/Dialect/Vector/canonicalize.mlir
+++ b/mlir/test/Dialect/Vector/canonicalize.mlir
@@ -3233,3 +3233,41 @@ func.func @fold_insert_constant_indices(%arg : vector<4x1xi32>) -> vector<4x1xi3
   %res = vector.insert %1, %arg[%0, %0] : i32 into vector<4x1xi32>
   return %res : vector<4x1xi32>
 }
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.insert
+
+// CHECK-LABEL: @fold_insert_oob
+//  CHECK-SAME:   %[[ARG:.*]]: vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+//       CHECK:   %[[OOB1:.*]] = arith.constant -2 : index
+//       CHECK:   %[[OOB2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[VAL:.*]] = arith.constant 1 : i32
+//       CHECK:   %[[RES:.*]] = vector.insert %[[VAL]], %[[ARG]] [0, %[[OOB1]], %[[OOB2]]] : i32 into vector<4x1x2xi32>
+//       CHECK:   return %[[RES]] : vector<4x1x2xi32>
+func.func @fold_insert_oob(%arg : vector<4x1x2xi32>) -> vector<4x1x2xi32> {
+  %0 = arith.constant 0 : index
+  %-2 = arith.constant -2 : index
+  %2 = arith.constant 2 : index
+  %1 = arith.constant 1 : i32
+  %res = vector.insert %1, %arg[%0, %-2, %2] : i32 into vector<4x1x2xi32>
+  return %res : vector<4x1x2xi32>
+}
+
+// -----
+
+// Check that out of bounds indices are not folded for vector.extract
+
+// CHECK-LABEL: @fold_extract_oob
+//  CHECK-SAME:   %[[ARG:.*]]: vector<4x1x2xi32>) -> i32 {
+//       CHECK:   %[[OOB1:.*]] = arith.constant -2 : index
+//       CHECK:   %[[OOB2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[RES:.*]] = vector.extract %[[ARG]][0, %[[OOB1]], %[[OOB2]]] : i32 from vector<4x1x2xi32>
+//       CHECK:   return %[[RES]] : i32
+func.func @fold_extract_oob(%arg : vector<4x1x2xi32>) -> i32 {
+  %0 = arith.constant 0 : index
+  %-2 = arith.constant -2 : index
+  %2 = arith.constant 2 : index
+  %res = vector.extract %arg[%0, %-2, %2] : i32 from vector<4x1x2xi32>
+  return %res : i32
+}

``````````

</details>

author_association: MEMBER
user: login: kuhar
created_at: 2025-04-17T03:58:07Z
body: Can you also add the issue url to the PR description?
author_association: MEMBER