[llvm] [DSE] Refine mayThrowBetween for cross-BB dead store checks (PR #191210)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 9 07:46:52 PDT 2026
https://github.com/ParkHanbum created https://github.com/llvm/llvm-project/pull/191210
mayThrowBetween() currently bails out for cross-BB cases if any
unmodeled throwing block exists in the function, which is overly
conservative and can block valid DSE.
Handle same-BB cases by scanning only the relevant instruction range,
and handle cross-BB cases by walking backwards from the killing block
through blocks dominated by the dead block. Keep a conservative bailout
when the search exceeds a limit.
Proof: https://alive2.llvm.org/ce/z/vtgqNp
Fixed: https://github.com/llvm/llvm-project/issues/190583
>From ae66e77c5dbdd25ed19360ea39d6fafcd6eab1f5 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Wed, 8 Apr 2026 13:56:05 +0900
Subject: [PATCH] [DSE] Refine mayThrowBetween for cross-BB dead store checks
mayThrowBetween() currently bails out for cross-BB cases if any
unmodeled throwing block exists in the function, which is overly
conservative and can block valid DSE.
Handle same-BB cases by scanning only the relevant instruction range,
and handle cross-BB cases by walking backwards from the killing block
through blocks dominated by the dead block. Keep a conservative bailout
when the search exceeds a limit.
Proof: https://alive2.llvm.org/ce/z/vtgqNp
Fixed: #190583
---
.../Scalar/DeadStoreElimination.cpp | 82 +++++++++++++++++--
.../multiblock-multipath-throwing.ll | 11 ++-
2 files changed, 81 insertions(+), 12 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 2714074dddafc..2f54e648ce756 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -174,6 +174,13 @@ static cl::opt<bool> EnableInitializesImprovement(
"enable-dse-initializes-attr-improvement", cl::init(true), cl::Hidden,
cl::desc("Enable the initializes attr improvement in DSE"));
+
+static cl::opt<unsigned> MemorySSAThrowCheckLimit(
+ "dse-memoryssa-throw-check-limit", cl::init(50), cl::Hidden,
+ cl::desc("The maximum number of blocks to check for unmodeled may-throw "
+ "instructions between the dead and killing stores "
+ "(default = 50)"));
+
//===----------------------------------------------------------------------===//
// Helper functions
//===----------------------------------------------------------------------===//
@@ -2068,15 +2075,78 @@ void DSEState::deleteDeadInstruction(Instruction *SI,
bool DSEState::mayThrowBetween(Instruction *KillingI, Instruction *DeadI,
const Value *KillingUndObj) {
- // First see if we can ignore it by using the fact that KillingI is an
- // alloca/alloca like object that is not visible to the caller during
- // execution of the function.
+ // If the object is not visible to the caller on unwind, intervening throws
+ // do not block DSE for this location.
if (KillingUndObj && isInvisibleToCallerOnUnwind(KillingUndObj))
return false;
- if (KillingI->getParent() == DeadI->getParent())
- return ThrowingBlocks.count(KillingI->getParent());
- return !ThrowingBlocks.empty();
+ auto IsMemorySSAUntrackedThrow = [&](Instruction &I) {
+ // This function is only meant to catch "extra" may-throws that are not
+ // modeled as MemoryDefs in MemorySSA.
+ return I.mayThrow() && !MSSA.getMemoryAccess(&I);
+ };
+
+ auto ScanRange = [&](BasicBlock *BB, BasicBlock::iterator Begin,
+ BasicBlock::iterator End) {
+ for (auto It = Begin; It != End; ++It)
+ if (IsMemorySSAUntrackedThrow(*It))
+ return true;
+ return false;
+ };
+
+ BasicBlock *KillingBB = KillingI->getParent();
+ BasicBlock *DeadBB = DeadI->getParent();
+ // Same block: only scan the actual instruction interval.
+ if (KillingBB == DeadBB)
+ return ScanRange(DeadBB, std::next(DeadI->getIterator()),
+ KillingI->getIterator());
+
+ // If there are no blocks containing extra may-throws, we're done.
+ if (ThrowingBlocks.empty())
+ return false;
+
+ // Cross-BB case: walk backwards from the killing block, but stay within the
+ // region dominated by the dead block. Any block outside that region is not
+ // between DeadI and KillingI.
+ if (!DT.dominates(DeadBB, KillingBB))
+ return true;
+
+ SmallVector<BasicBlock *, 8> WorkList;
+ SmallPtrSet<BasicBlock *, 8> Visited;
+ WorkList.push_back(KillingBB);
+ Visited.insert(KillingBB);
+ unsigned NumChecked = 0;
+ while (!WorkList.empty()) {
+ BasicBlock *BB = WorkList.pop_back_val();
+ if (++NumChecked > MemorySSAThrowCheckLimit)
+ return true;
+
+ if (BB == KillingBB) {
+ // Only instructions before KillingI are relevant in the destination.
+ if (ThrowingBlocks.count(BB) &&
+ ScanRange(BB, BB->begin(), KillingI->getIterator()))
+ return true;
+ } else if (BB == DeadBB) {
+ // Only instructions after DeadI are relevant in the source.
+ if (ThrowingBlocks.count(BB) &&
+ ScanRange(BB, std::next(DeadI->getIterator()), BB->end()))
+ return true;
+ continue;
+ } else {
+ // Entire intermediate block is between DeadI and KillingI.
+ if (ThrowingBlocks.count(BB))
+ return true;
+ }
+
+ for (BasicBlock *Pred : predecessors(BB)) {
+ if (Pred != DeadBB && !DT.dominates(DeadBB, Pred))
+ continue;
+ if (Visited.insert(Pred).second)
+ WorkList.push_back(Pred);
+ }
+ }
+
+ return false;
}
bool DSEState::isDSEBarrier(const Value *KillingUndObj, Instruction *DeadI) {
diff --git a/llvm/test/Transforms/DeadStoreElimination/multiblock-multipath-throwing.ll b/llvm/test/Transforms/DeadStoreElimination/multiblock-multipath-throwing.ll
index a513c60ca265f..56463b9be24ef 100644
--- a/llvm/test/Transforms/DeadStoreElimination/multiblock-multipath-throwing.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/multiblock-multipath-throwing.ll
@@ -10,12 +10,12 @@ declare void @use(ptr)
; Tests where the pointer/object is accessible after the function returns.
; Cannot remove the store from the entry block, because the call in bb2 may throw.
+; The store in bb1 may still be eliminated by sinking/merging it into bb5.
define void @accessible_after_return_1(ptr noalias %P, i1 %c1) {
; CHECK-LABEL: @accessible_after_return_1(
-; CHECK-NEXT: store i32 1, ptr [[P:%.*]], align 4
; CHECK-NEXT: br i1 [[C1:%.*]], label [[BB1:%.*]], label [[BB2:%.*]]
; CHECK: bb1:
-; CHECK-NEXT: store i32 0, ptr [[P]], align 4
+; CHECK-NEXT: store i32 0, ptr [[P:%.*]], align 4
; CHECK-NEXT: br label [[BB5:%.*]]
; CHECK: bb2:
; CHECK-NEXT: call void @readnone_may_throw()
@@ -46,12 +46,11 @@ bb5:
define void @accessible_after_return6(ptr %P, i1 %c.1, i1 %c.2) {
; CHECK-LABEL: @accessible_after_return6(
; CHECK-NEXT: entry:
-; CHECK-NEXT: store i32 0, ptr [[P:%.*]], align 4
; CHECK-NEXT: br i1 [[C_1:%.*]], label [[BB1:%.*]], label [[BB2:%.*]]
; CHECK: bb1:
; CHECK-NEXT: br i1 [[C_2:%.*]], label [[BB3:%.*]], label [[BB4:%.*]]
; CHECK: bb2:
-; CHECK-NEXT: store i32 1, ptr [[P]], align 4
+; CHECK-NEXT: store i32 1, ptr [[P:%.*]], align 4
; CHECK-NEXT: ret void
; CHECK: bb3:
; CHECK-NEXT: call void @readnone_may_throw()
@@ -90,7 +89,7 @@ bb4:
define void @alloca_1(i1 %c1) {
; CHECK-LABEL: @alloca_1(
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[P:%.*]] = alloca i32
+; CHECK-NEXT: [[P:%.*]] = alloca i32, align 4
; CHECK-NEXT: br i1 [[C1:%.*]], label [[BB1:%.*]], label [[BB2:%.*]]
; CHECK: bb1:
; CHECK-NEXT: store i32 0, ptr [[P]], align 4
@@ -127,7 +126,7 @@ bb5:
; call in bb3 (which may throw) can be ignored.
define void @alloca_2(i1 %c.1, i1 %c.2) {
; CHECK-LABEL: @alloca_2(
-; CHECK-NEXT: [[P:%.*]] = alloca i32
+; CHECK-NEXT: [[P:%.*]] = alloca i32, align 4
; CHECK-NEXT: br i1 [[C_1:%.*]], label [[BB1:%.*]], label [[BB2:%.*]]
; CHECK: bb1:
; CHECK-NEXT: store i32 0, ptr [[P]], align 4
More information about the llvm-commits
mailing list