[Mlir-commits] [mlir] [mlir][SCF] Fix use-after-free in coalesceLoops when inner loop yields its induction var (PR #217510)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Aug 19 19:58:21 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-affine

Author: Aman Singh (amanyagami)

<details>
<summary>Changes</summary>

coalesceLoops rewrites yielded values that reference the inner loop's
iter_args (about to be inlined and destroyed) to their replacement
values, but never checked whether a yielded value was the inner
loop's induction variable itself — also a block argument about to be
destroyed. When the inner loop's `scf.yield` yields its own
induction variable, this left a dangling `Value` reference that
crashed `RewriterBase::replaceOp` with a use-after-free.

Fix by also rewriting a yielded induction variable to its delinearized
replacement value, mirroring the existing iter_arg case.

Verified: reverting this fix reproduces the reported segfault; with
the fix, `mlir-opt --affine-loop-coalescing` on the reported
reproducer succeeds, and `mlir/test/Dialect/Affine/loop-coalescing.mlir`
passes.

Fixes #<!-- -->216903

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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


2 Files Affected:

- (modified) mlir/lib/Dialect/SCF/Utils/Utils.cpp (+7) 
- (modified) mlir/test/Dialect/Affine/loop-coalescing.mlir (+27) 


``````````diff
diff --git a/mlir/lib/Dialect/SCF/Utils/Utils.cpp b/mlir/lib/Dialect/SCF/Utils/Utils.cpp
index c158e624002bd..36cddd5cf8bd7 100644
--- a/mlir/lib/Dialect/SCF/Utils/Utils.cpp
+++ b/mlir/lib/Dialect/SCF/Utils/Utils.cpp
@@ -1002,6 +1002,13 @@ LogicalResult mlir::coalesceLoops(RewriterBase &rewriter,
     auto yieldedVals = llvm::to_vector(innerTerminator->getOperands());
     assert(llvm::equal(outerLoop.getRegionIterArgs(), innerLoop.getInitArgs()));
     for (Value &yieldedVal : yieldedVals) {
+      // The yielded value may be the induction variable of the inner loop,
+      // which is about to be inlined and whose block argument is about to
+      // be destroyed. Use its replacement value instead.
+      if (yieldedVal == innerLoop.getInductionVar()) {
+        yieldedVal = delinearizeIvs[i];
+        continue;
+      }
       // The yielded value may be an iteration argument of the inner loop
       // which is about to be inlined.
       auto iter = llvm::find(innerLoop.getRegionIterArgs(), yieldedVal);
diff --git a/mlir/test/Dialect/Affine/loop-coalescing.mlir b/mlir/test/Dialect/Affine/loop-coalescing.mlir
index d08d2bf79c781..b8a68b9c5c1f9 100644
--- a/mlir/test/Dialect/Affine/loop-coalescing.mlir
+++ b/mlir/test/Dialect/Affine/loop-coalescing.mlir
@@ -462,3 +462,30 @@ func.func @no_coalesce_zero_step(%lb: index, %ub: index) {
   }
   return
 }
+
+// -----
+
+// Regression test for https://github.com/llvm/llvm-project/issues/216903:
+// coalescing must not crash when the inner loop yields its own induction
+// variable. The yielded induction variable has to be rewritten to its
+// linearized replacement before the inner loop's induction variable block
+// argument is destroyed by inlining.
+
+// CHECK-LABEL: @inner_loop_yields_induction_var
+func.func @inner_loop_yields_induction_var() -> index {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c4 = arith.constant 4 : index
+  // CHECK: scf.for %[[IV:.*]] = %{{.*}} to %{{.*}} step %{{.*}} iter_args(%{{.*}} = %{{.*}}) -> (index) {
+  // CHECK-NOT: scf.for
+  // CHECK: %[[DELIN:.+]]:2 = affine.delinearize_index %[[IV]]
+  // CHECK: scf.yield %[[DELIN]]#1 : index
+  // CHECK: }
+  %r = scf.for %i = %c0 to %c4 step %c1 iter_args(%a = %c0) -> (index) {
+    %s = scf.for %j = %c0 to %c4 step %c1 iter_args(%b = %a) -> (index) {
+      scf.yield %j : index
+    }
+    scf.yield %s : index
+  }
+  return %r : index
+}

``````````

</details>


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


More information about the Mlir-commits mailing list