Bug type: crash
Component: BasicBlockUtils
Bug location: llvm/lib/Transforms/Utils/BasicBlockUtils.cpp: llvm::detachDeadBlocks
Issue title: Bug in SimplifyCFG causes ICE
Labels: crash
llvm:transforms
Reported by: sivadeilra

This minimal Rust code causes a failure when the `simplifycfg` pass is enabled:

```rust
pub fn repro_func(shapes: &mut [ShapeInfo<ReproShape>]) {
    repro_generic::<ReproShape>(shapes);
}

#[derive(Clone)]
pub struct ShapeInfo<S> {
    pub shape: S,
}

#[derive(Default, Copy, Clone)]
pub enum ReproShape {
    #[default]
    A = 0,
    B = 1,
}

pub(crate) fn repro_generic<S: Default + Clone>(shapes: &mut [ShapeInfo<S>]) {
    shapes[0] = shapes[1].clone();
    shapes[1].shape = Default::default();
}
```

When this is compiled with these Rust args:

```
rustc \
--emit=llvm-ir \
--crate-name=repro \
--edition=2021 \
repro.rs \
--crate-type=lib \
--out-dir=out \
--target=x86_64-pc-windows-msvc \
-Cno-prepopulate-passes
```

it produces `repro.ll`.  Rust normally enables `simplifycfg` by default, so I've disabled all passes in this repro.  You can manually reproduce the problem by running `opt --passes=simplifycfg repro.ll`.

I tried to attach `repro.ll`, but GitHub rejects `*.ll` files.  I'll create a repro PR.


Comments: author: sivadeilra
body: I've created #148053 with an integration test that shows the bug.

Tagging @spaits, @andjo403 for help, since I see recent commits to this code.


author: spaits
body: Hi. Could you please tell what rustc version you use?
author: sivadeilra
body: I used 1.88.0 to generate the LLVM IR.  I haven't tested earlier versions, but I'm guessing it repros with earlier versions, as long as you're generating IR and then optimizing with with LLVM at HEAD.
author: sivadeilra
body: I just checked with Rust 1.82.0, and the same problem occurs.
author: efriedma-quic
body: Reduced using `llvm-reduce -skip-delta-passes=simplify-cfg,ir-passes`:

```
define void @_RINvCs3LQYlzOoal9_5repro13repro_genericNtB2_10ReproShapeEB2_(i64 %shapes.1) personality ptr null {
start:
  %_7 = icmp ult i64 0, %shapes.1
  br i1 %_7, label %bb1, label %panic

bb1:                                              ; preds = %start
  %_11 = icmp ult i64 0, %shapes.1
  br i1 %_11, label %bb3, label %panic1

panic:                                            ; preds = %start
  unreachable

bb3:                                              ; preds = %bb1
  ret void

panic1:                                           ; preds = %bb1
  invoke void @_RNvNtCshEuXqvZjEXJ_4core9panicking18panic_bounds_check(i64 0, i64 0, ptr null)
          to label %unreachable unwind label %funclet_bb14

funclet_bb14:                                     ; preds = %panic1
  %cleanuppad = cleanuppad within none []
  br label %bb13

unreachable:                                      ; preds = %panic1
  unreachable

bb10:                                             ; preds = %funclet_bb10
  cleanupret from %cleanuppad5 unwind to caller

funclet_bb10:                                     ; preds = %bb13
  %cleanuppad5 = cleanuppad within none []
  br label %bb10

bb13:                                             ; preds = %funclet_bb14
  cleanupret from %cleanuppad unwind label %funclet_bb10
}

declare void @_RNvNtCshEuXqvZjEXJ_4core9panicking18panic_bounds_check(i64, i64, ptr)
```
author: sivadeilra
body: Call stack:

```text
0:000> knL 20
 # Call Site
00 opt!HandleAbort+0xc
01 opt!raise+0x221
02 opt!abort+0x18
03 opt!common_assert_to_stderr_direct+0xd3
04 opt!common_assert_to_stderr<wchar_t>+0x18
05 opt!common_assert+0x56
06 opt!_wassert+0x72
07 opt!llvm::cast<llvm::CleanupPadInst,llvm::Use>+0x40
08 opt!llvm::CleanupReturnInst::getCleanupPad+0x1c
09 opt!llvm::removeUnwindEdge+0x118
0a opt!removeEmptyCleanup+0x6ac
0b opt!`anonymous namespace'::SimplifyCFGOpt::simplifyCleanupReturn+0x5d
0c opt!`anonymous namespace'::SimplifyCFGOpt::simplifyOnce+0x57d
0d opt!`anonymous namespace'::SimplifyCFGOpt::run+0x35
0e opt!llvm::simplifyCFG+0xbc
0f opt!iterativelySimplifyCFG+0x3a9
10 opt!simplifyFunctionCFGImpl+0x16a
11 opt!simplifyFunctionCFG+0x91
12 opt!llvm::SimplifyCFGPass::run+0xcf
13 opt!llvm::detail::PassModel<llvm::Function,llvm::SimplifyCFGPass,llvm::AnalysisManager<llvm::Function> >::run+0x40
14 opt!llvm::PassManager<llvm::Function,llvm::AnalysisManager<llvm::Function> >::run+0x1e2
15 opt!llvm::detail::PassModel<llvm::Function,llvm::PassManager<llvm::Function,llvm::AnalysisManager<llvm::Function> >,llvm::AnalysisManager<llvm::Function> >::run+0x40
16 opt!llvm::ModuleToFunctionPassAdaptor::run+0x1ae
17 opt!llvm::detail::PassModel<llvm::Module,llvm::ModuleToFunctionPassAdaptor,llvm::AnalysisManager<llvm::Module> >::run+0x40
18 opt!llvm::PassManager<llvm::Module,llvm::AnalysisManager<llvm::Module> >::run+0x1e2
19 opt!llvm::runPassPipeline+0x1ef5
```

Assertion failure:

```text
Assertion failed: isa<To>(Val) && "cast<Ty>() argument of incompatible type!", file D:\rust\src\llvm-project\llvm\include\llvm/Support/Casting.h, line 566
```

author: spaits
body: Here is the reproducer in godbolt:
https://godbolt.org/z/njT5ET3dj
author: hstk30-hw
body: the `%cleanuppad` is optimize to `posion`

```
cleanupret from posion unwind label %funclet_bb10`
```

Cast from `posion` to `CleanupPadInst` failed
author: spaits
body: It seems that the seond attempt causes crashes on the buildbot builds: https://lab.llvm.org/buildbot/#/builders/55/builds/17295

I have reverted the fix again. I will give it another go later this week.

Fix patch:
commit 43561ad204329667954d5ce1e632e09335f0ab12
Author: Gábor Spaits <gaborspaits1@gmail.com>
Date:   Thu Sep 11 22:52:16 2025 +0200

    [BasicBlockUtils] Handle funclets when detaching EH pad blocks (#157363)
    
    Fixes #148052 .
    
    When removing EH Pad blocks, the value defined by them becomes poison. These poison values are then used by `catchret` and `cleanupret`, which is invalid. This commit replaces those unreachable `catchret` and `cleanupret` instructions with `unreachable`.

diff --git a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
index cad0b4c12b54..d2391e166f94 100644
--- a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
+++ b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
@@ -58,6 +58,19 @@ static cl::opt<unsigned> MaxDeoptOrUnreachableSuccessorCheckDepth(
              "is followed by a block that either has a terminating "
              "deoptimizing call or is terminated with an unreachable"));
 
+static void replaceFuncletPadsRetWithUnreachable(Instruction &I) {
+  assert(isa<FuncletPadInst>(I) && "Instruction must be a funclet pad!");
+  for (User *User : make_early_inc_range(I.users())) {
+    Instruction *ReturnInstr = dyn_cast<Instruction>(User);
+    if (isa<CatchReturnInst>(ReturnInstr) ||
+        isa<CleanupReturnInst>(ReturnInstr)) {
+      BasicBlock *ReturnInstrBB = ReturnInstr->getParent();
+      ReturnInstr->eraseFromParent();
+      new UnreachableInst(ReturnInstrBB->getContext(), ReturnInstrBB);
+    }
+  }
+}
+
 void llvm::detachDeadBlocks(
     ArrayRef<BasicBlock *> BBs,
     SmallVectorImpl<DominatorTree::UpdateType> *Updates,
@@ -75,7 +88,36 @@ void llvm::detachDeadBlocks(
     // Zap all the instructions in the block.
     while (!BB->empty()) {
       Instruction &I = BB->back();
-      // If this instruction is used, replace uses with an arbitrary value.
+      // Exception handling funclets need to be explicitly addressed.
+      // These funclets must begin with cleanuppad or catchpad and end with
+      // cleanupred or catchret. The return instructions can be in different
+      // basic blocks than the pad instruction. If we would only delete the
+      // first block, the we would have possible cleanupret and catchret
+      // instructions with poison arguments, which wouldn't be valid.
+      if (isa<FuncletPadInst>(I))
+        replaceFuncletPadsRetWithUnreachable(I);
+
+      // Catchswitch instructions have handlers, that must be catchpads and
+      // an unwind label, that is either a catchpad or catchswitch.
+      if (CatchSwitchInst *CSI = dyn_cast<CatchSwitchInst>(&I)) {
+        // Iterating over the handlers and the unwind basic block and processing
+        // catchpads. If the unwind label is a catchswitch, we just replace the
+        // label with poison later on.
+        for (unsigned I = 0; I < CSI->getNumSuccessors(); I++) {
+          BasicBlock *SucBlock = CSI->getSuccessor(I);
+          Instruction &SucFstInst = *(SucBlock->getFirstNonPHIIt());
+          if (isa<FuncletPadInst>(SucFstInst)) {
+            replaceFuncletPadsRetWithUnreachable(SucFstInst);
+            // There may be catchswitch instructions using the catchpad.
+            // Just replace those with poison.
+            if (!SucFstInst.use_empty())
+              SucFstInst.replaceAllUsesWith(
+                  PoisonValue::get(SucFstInst.getType()));
+            SucFstInst.eraseFromParent();
+          }
+        }
+      }
+
       // Because control flow can't get here, we don't care what we replace the
       // value with.  Note that since this block is unreachable, and all values
       // contained within it must dominate their uses, that all uses will


Regression tests:
file: llvm/test/Transforms/SimplifyCFG/unreachable-multi-basic-block-funclet.ll
commands: opt -passes=simplifycfg -S < %s
tests: test_name: <module>
test_body: 
; cleanuppad/cleanupret

define void @unreachable_cleanuppad_linear(i64 %shapes.1) personality ptr null {
;
start:
  %_7 = icmp ult i64 0, %shapes.1
  ret void

funclet:
  %cleanuppad = cleanuppad within none []
  br label %funclet_end

funclet_end:
  cleanupret from %cleanuppad unwind to caller
}

define void @unreachable_cleanuppad_multiple_predecessors(i64 %shapes.1) personality ptr null {
;
start:
  %_7 = icmp ult i64 0, %shapes.1
  ret void

funclet:
  %cleanuppad = cleanuppad within none []
  switch i64 %shapes.1, label %otherwise [ i64 0, label %one
  i64 1, label %two
  i64 42, label %three ]
one:
  br label %funclet_end

two:
  br label %funclet_end

three:
  br label %funclet_end

otherwise:
  br label %funclet_end

funclet_end:
  cleanupret from %cleanuppad unwind to caller
}

; catchpad/catchret

define void @unreachable_catchpad_linear(i64 %shapes.1) personality ptr null {
;
start:
  %_7 = icmp ult i64 0, %shapes.1
  ret void

dispatch:
  %cs = catchswitch within none [label %funclet] unwind to caller

funclet:
  %cleanuppad = catchpad within %cs []
  br label %funclet_end


funclet_end:
  catchret from %cleanuppad to label %unreachable

unreachable:
  unreachable
}

define void @unreachable_catchpad_multiple_predecessors(i64 %shapes.1) personality ptr null {
;
start:
  %_7 = icmp ult i64 0, %shapes.1
  ret void

dispatch:
  %cs = catchswitch within none [label %funclet] unwind to caller

funclet:
  %cleanuppad = catchpad within %cs []
  switch i64 %shapes.1, label %otherwise [ i64 0, label %one
  i64 1, label %two
  i64 42, label %three ]
one:
  br label %funclet_end

two:
  br label %funclet_end

three:
  br label %funclet_end

otherwise:
  br label %funclet_end

funclet_end:
  catchret from %cleanuppad to label %unreachable

unreachable:
  unreachable
}

; Issue reproducer

define void @gh148052(i64 %shapes.1) personality ptr null {
;
start:
  %_7 = icmp ult i64 0, %shapes.1
  br i1 %_7, label %bb1, label %panic

bb1:
  %_11 = icmp ult i64 0, %shapes.1
  br i1 %_11, label %bb3, label %panic1

panic:
  unreachable

bb3:
  ret void

panic1:
  invoke void @func(i64 0, i64 0, ptr null)
  to label %unreachable unwind label %funclet_bb14

funclet_bb14:
  %cleanuppad = cleanuppad within none []
  br label %bb13

unreachable:
  unreachable

bb10:
  cleanupret from %cleanuppad5 unwind to caller

funclet_bb10:
  %cleanuppad5 = cleanuppad within none []
  br label %bb10

bb13:
  cleanupret from %cleanuppad unwind label %funclet_bb10
}

declare void @func(i64, i64, ptr)