[Mlir-commits] [mlir] [mlir][cf] Fix crash in simplifyBrToBlockWithSinglePred when branch operand is a block argument of its successor (PR #183797)
Mehdi Amini
llvmlistbot at llvm.org
Fri Feb 27 10:47:04 PST 2026
https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/183797
When `simplifyBrToBlockWithSinglePred` merges a block into its sole predecessor, it calls `inlineBlockBefore` which replaces each block argument with the corresponding value passed by the branch. If one of those values is itself a block argument of the successor block, the call `replaceAllUsesWith(arg, arg)` is a no-op. Any uses of that argument outside the block (e.g. in a downstream block) are therefore not replaced, and when the successor block is erased the argument is destroyed while those uses are still live, triggering the assertion `use_empty() && "Cannot destroy a value that still has uses\!"` in `IRObjectWithUseList::~IRObjectWithUseList`.
Guard against this by returning early when any branch operand is a block argument owned by the destination block.
Fixes #126213
>From a2e7eeaf4d0deee56ea1b2e518dfc229ab327260 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Fri, 27 Feb 2026 10:32:43 -0800
Subject: [PATCH] [mlir][cf] Fix crash in simplifyBrToBlockWithSinglePred when
branch operand is a block argument of its successor
When `simplifyBrToBlockWithSinglePred` merges a block into its sole
predecessor, it calls `inlineBlockBefore` which replaces each block
argument with the corresponding value passed by the branch. If one of
those values is itself a block argument of the successor block, the
call `replaceAllUsesWith(arg, arg)` is a no-op. Any uses of that
argument outside the block (e.g. in a downstream block) are therefore
not replaced, and when the successor block is erased the argument is
destroyed while those uses are still live, triggering the assertion
`use_empty() && "Cannot destroy a value that still has uses\!"` in
`IRObjectWithUseList::~IRObjectWithUseList`.
Guard against this by returning early when any branch operand is a
block argument owned by the destination block.
Fixes #126213
---
.../Dialect/ControlFlow/IR/ControlFlowOps.cpp | 8 ++++
.../Dialect/ControlFlow/canonicalize.mlir | 39 +++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp b/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp
index d2078d8ab5ca5..0ce0d55f4397c 100644
--- a/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp
+++ b/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp
@@ -166,6 +166,14 @@ simplifyBrToBlockWithSinglePred(BranchOp op, PatternRewriter &rewriter) {
if (succ == opParent || !llvm::hasSingleElement(succ->getPredecessors()))
return failure();
+ // If any branch operand is itself a block argument of the successor, merging
+ // would call replaceAllUsesWith(arg, arg) — a no-op — leaving dangling uses
+ // of that argument after the successor block is erased.
+ for (Value operand : op.getOperands())
+ if (auto ba = dyn_cast<BlockArgument>(operand))
+ if (ba.getOwner() == succ)
+ return failure();
+
// Merge the successor into the current block and erase the branch.
SmallVector<Value> brOperands(op.getOperands());
rewriter.eraseOp(op);
diff --git a/mlir/test/Dialect/ControlFlow/canonicalize.mlir b/mlir/test/Dialect/ControlFlow/canonicalize.mlir
index 21a16784b81b2..3bf4ca006f34c 100644
--- a/mlir/test/Dialect/ControlFlow/canonicalize.mlir
+++ b/mlir/test/Dialect/ControlFlow/canonicalize.mlir
@@ -656,3 +656,42 @@ func.func @drop_unreachable_branch_2(%c: i1) {
^bb2:
ub.unreachable
}
+
+// -----
+
+// Regression test for https://github.com/llvm/llvm-project/issues/126213:
+// simplifyBrToBlockWithSinglePred must not merge a block into its sole
+// predecessor when a branch operand is itself a block argument of the
+// destination — replaceAllUsesWith would be a no-op for that argument,
+// leaving the block argument live after the block is erased and crashing.
+
+// CHECK-LABEL: @no_merge_self_arg_loop
+func.func @no_merge_self_arg_loop() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %true = arith.constant true
+ cf.cond_br %true, ^bb1, ^bb2
+^bb1:
+ cf.br ^bb3(%true : i1)
+^bb2:
+ cf.br ^bb3(%true : i1)
+^bb3(%0: i1):
+ cf.br ^bb4
+^bb4:
+ cf.br ^bb5
+^bb5:
+ cf.cond_br %0, ^bb6, ^bb7
+^bb6:
+ cf.br ^bb5
+^bb7:
+ cf.br ^bb8(%c0, %true : index, i1)
+^bb8(%1: index, %2: i1):
+ %3 = arith.cmpi slt, %1, %c1 : index
+ cf.cond_br %3, ^bb9, ^bb10
+^bb9:
+ %4 = arith.addi %1, %c1 : index
+ cf.br ^bb8(%4, %2 : index, i1)
+^bb10:
+ vector.print %2 : i1
+ return
+}
More information about the Mlir-commits
mailing list