[llvm] [SimplifyCFG] Don't delete basic block if it is a partial cleanuppad (PR #157363)
Gábor Spaits via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 9 13:56:27 PDT 2025
https://github.com/spaits updated https://github.com/llvm/llvm-project/pull/157363
>From efade811fd44ee096ae102ab941394d1d3db7ca9 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Tue, 9 Sep 2025 12:51:46 +0200
Subject: [PATCH 1/2] Replace unreachable cleanupret and catchret instruction
with unreachable instruction
---
llvm/lib/Transforms/Utils/BasicBlockUtils.cpp | 20 ++-
.../unreachable-multi-basic-block-funclet.ll | 169 ++++++++++++++++++
2 files changed, 188 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Transforms/SimplifyCFG/unreachable-multi-basic-block-funclet.ll
diff --git a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
index cad0b4c12b54e..3515374a58ca1 100644
--- a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
+++ b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
@@ -75,7 +75,25 @@ 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.
+ unsigned OpCode = I.getOpcode();
+ if (OpCode == Instruction::CatchPad ||
+ OpCode == Instruction::CleanupPad) {
+ 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);
+ }
+ }
+ }
// 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
diff --git a/llvm/test/Transforms/SimplifyCFG/unreachable-multi-basic-block-funclet.ll b/llvm/test/Transforms/SimplifyCFG/unreachable-multi-basic-block-funclet.ll
new file mode 100644
index 0000000000000..d2fccae6770db
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/unreachable-multi-basic-block-funclet.ll
@@ -0,0 +1,169 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes=simplifycfg -S < %s | FileCheck %s
+
+; cleanuppad/cleanupret
+
+define void @unreachable_cleanuppad_linear(i64 %shapes.1) personality ptr null {
+; CHECK-LABEL: define void @unreachable_cleanuppad_linear(
+; CHECK-SAME: i64 [[SHAPES_1:%.*]]) personality ptr null {
+; CHECK-NEXT: [[START:.*:]]
+; CHECK-NEXT: [[_7:%.*]] = icmp ult i64 0, [[SHAPES_1]]
+; CHECK-NEXT: ret void
+;
+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 {
+; CHECK-LABEL: define void @unreachable_cleanuppad_multiple_predecessors(
+; CHECK-SAME: i64 [[SHAPES_1:%.*]]) personality ptr null {
+; CHECK-NEXT: [[START:.*:]]
+; CHECK-NEXT: [[_7:%.*]] = icmp ult i64 0, [[SHAPES_1]]
+; CHECK-NEXT: ret void
+;
+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 {
+; CHECK-LABEL: define void @unreachable_catchpad_linear(
+; CHECK-SAME: i64 [[SHAPES_1:%.*]]) personality ptr null {
+; CHECK-NEXT: [[START:.*:]]
+; CHECK-NEXT: [[_7:%.*]] = icmp ult i64 0, [[SHAPES_1]]
+; CHECK-NEXT: ret void
+;
+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 {
+; CHECK-LABEL: define void @unreachable_catchpad_multiple_predecessors(
+; CHECK-SAME: i64 [[SHAPES_1:%.*]]) personality ptr null {
+; CHECK-NEXT: [[START:.*:]]
+; CHECK-NEXT: [[_7:%.*]] = icmp ult i64 0, [[SHAPES_1]]
+; CHECK-NEXT: ret void
+;
+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 {
+; CHECK-LABEL: define void @gh148052(
+; CHECK-SAME: i64 [[SHAPES_1:%.*]]) personality ptr null {
+; CHECK-NEXT: [[START:.*:]]
+; CHECK-NEXT: [[_7:%.*]] = icmp ult i64 0, [[SHAPES_1]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[_7]])
+; CHECK-NEXT: ret void
+;
+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)
>From 142716b871016b942bb873e6d2ffb8993b7b5e51 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Tue, 9 Sep 2025 22:56:10 +0200
Subject: [PATCH 2/2] Address the case when catchswitch leaves poisoned pads
---
llvm/lib/Transforms/Utils/BasicBlockUtils.cpp | 31 ++++++++++++++-----
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
index 3515374a58ca1..53bd321a3e169 100644
--- a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
+++ b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
@@ -58,6 +58,21 @@ 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 replaceEHPadsRetWithUnreachable(Instruction &I) {
+ unsigned OpCode = I.getOpcode();
+ assert(OpCode == Instruction::CatchPad ||
+ OpCode == Instruction::CleanupPad);
+ 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,
@@ -84,14 +99,14 @@ void llvm::detachDeadBlocks(
unsigned OpCode = I.getOpcode();
if (OpCode == Instruction::CatchPad ||
OpCode == Instruction::CleanupPad) {
- 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);
- }
+ replaceEHPadsRetWithUnreachable(I);
+ }
+ if (OpCode == Instruction::CatchSwitch) {
+ CatchSwitchInst &CSI = cast<CatchSwitchInst>(I);
+ for (unsigned I = 0; I < CSI.getNumSuccessors(); I++) {
+ BasicBlock *SucBlock = CSI.getSuccessor(I);
+ Instruction &PadInstr = *(SucBlock->getFirstNonPHIIt());
+ replaceEHPadsRetWithUnreachable(PadInstr);
}
}
// Because control flow can't get here, we don't care what we replace the
More information about the llvm-commits
mailing list