[Mlir-commits] [mlir] [MLIR] Fix crash in ValueBoundsConstraintSet for non-entry block args (PR #185048)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Mar 6 09:00:56 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: Mehdi Amini (joker-eph)

<details>
<summary>Changes</summary>

When two vector transfer ops share a non-entry block argument as an index (e.g., in a loop with unstructured control flow), calling `ValueBoundsConstraintSet::areEqual` on those values caused a crash.

The first `populateConstraints` call would insert the block argument into the constraint set. The second call found it already mapped and called `getPos`, which hit an assert requiring the value to be either an OpResult or an entry-block argument.

Fix with two changes:
1. In `insert()`, suppress adding non-entry block arguments to the worklist. `ValueBoundsOpInterface` cannot derive bounds for such values, so the worklist push was a no-op and triggered the re-entrant `getPos` call.
2. Remove the overly conservative assert in `getPos`. Looking up a previously inserted non-entry block argument is valid; the assert was preventing legitimate use after the value had already been inserted.

Fixes #<!-- -->119861

Assisted-by: Claude Code

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


2 Files Affected:

- (modified) mlir/lib/Interfaces/ValueBoundsOpInterface.cpp (+10-4) 
- (modified) mlir/test/Dialect/Vector/vector-transferop-opt.mlir (+27) 


``````````diff
diff --git a/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp b/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp
index 85a156efd9474..35e3e92b91176 100644
--- a/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp
+++ b/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp
@@ -282,7 +282,16 @@ int64_t ValueBoundsConstraintSet::insert(Value value,
     if (positionToValueDim[i].has_value())
       valueDimToPosition[*positionToValueDim[i]] = i;
 
-  if (addToWorklist) {
+  // Do not add block arguments from non-entry blocks to the worklist. The
+  // ValueBoundsOpInterface cannot derive any bounds for such values (they
+  // arise from unstructured control flow), so putting them on the worklist
+  // would be a no-op. More importantly, suppressing the worklist push ensures
+  // that processWorklist never calls getExpr on such a value a second time,
+  // which would otherwise cause the same value to be looked up as already
+  // mapped (triggering an unintended bug path).
+  if (addToWorklist &&
+      (!isa<BlockArgument>(value) ||
+       cast<BlockArgument>(value).getOwner()->isEntryBlock())) {
     LDBG() << "Push to worklist: " << value
            << " (dim: " << dim.value_or(kIndexValue) << ")";
     worklist.push(pos);
@@ -334,9 +343,6 @@ int64_t ValueBoundsConstraintSet::getPos(Value value,
                                          std::optional<int64_t> dim) const {
 #ifndef NDEBUG
   assertValidValueDim(value, dim);
-  assert((isa<OpResult>(value) ||
-          cast<BlockArgument>(value).getOwner()->isEntryBlock()) &&
-         "unstructured control flow is not supported");
 #endif // NDEBUG
   LDBG() << "Getting pos for: " << value
          << " (dim: " << dim.value_or(kIndexValue)
diff --git a/mlir/test/Dialect/Vector/vector-transferop-opt.mlir b/mlir/test/Dialect/Vector/vector-transferop-opt.mlir
index f4f7fb1ba0304..eb07df525a6e5 100644
--- a/mlir/test/Dialect/Vector/vector-transferop-opt.mlir
+++ b/mlir/test/Dialect/Vector/vector-transferop-opt.mlir
@@ -1,5 +1,7 @@
 // RUN: mlir-opt %s -test-vector-transferop-opt | FileCheck %s
 
+#leading_dim_1d_map = affine_map<(d0, d1) -> (d0)>
+
 // CHECK-LABEL: func @forward_dead_store
 //   CHECK-NOT:   vector.transfer_write
 //   CHECK-NOT:   vector.transfer_read
@@ -607,3 +609,28 @@ func.func @forward_and_eliminate_stores_through_trivial_aliases(
   vector.transfer_write %23, %subview_of_cast[%c0, %c0] {in_bounds = [true, true]} : vector<[8]x[8]xf32>, memref<?x?xf32, strided<[?, 1]>>
   return
 }
+
+// Regression test for https://github.com/llvm/llvm-project/issues/119861:
+// When two transfer ops in unstructured control flow share the same block
+// argument as an index, ValueBoundsConstraintSet must not crash when
+// isDisjointTransferIndices calls areEqual on those values.
+
+// CHECK-LABEL: func @no_crash_unstructured_cf_shared_index
+func.func @no_crash_unstructured_cf_shared_index(%alloc: memref<10x10xf32>) {
+  %v = arith.constant dense<0.0> : vector<4xf32>
+  %c0 = arith.constant 0 : index
+  cf.br ^bb1(%c0 : index)
+^bb1(%i: index):
+  %limit = arith.constant 10 : index
+  %cond = arith.cmpi slt, %i, %limit : index
+  cf.cond_br %cond, ^bb2, ^bb3
+^bb2:
+  // CHECK: vector.transfer_write
+  vector.transfer_write %v, %alloc[%i, %c0] {permutation_map = #leading_dim_1d_map} : vector<4xf32>, memref<10x10xf32>
+  // CHECK: vector.transfer_write
+  vector.transfer_write %v, %alloc[%i, %limit] {permutation_map = #leading_dim_1d_map} : vector<4xf32>, memref<10x10xf32>
+  %next = arith.addi %i, %limit : index
+  cf.br ^bb1(%next : index)
+^bb3:
+  return
+}

``````````

</details>


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


More information about the Mlir-commits mailing list