[llvm] [DirectX] Resolve unreachable default branches in switch statements (PR #193592)
Finn Plummer via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 22 13:55:59 PDT 2026
https://github.com/inbelic created https://github.com/llvm/llvm-project/pull/193592
Add a legalization to resolve unreachable default branches in switch statements. These are introduced when the `CorrelatedValuePropagationPass` proves all cases of a switch are covered, leaving the default as an unreachable block.
The default destination is replaced with either the common successor block or the first switch case destination.
Assisted by: Claude Opus 4.6
>From 6b6ce7f580db44fa6e60df16960a9d33595c5b08 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 22 Apr 2026 17:44:49 +0000
Subject: [PATCH] [DirectX] Resolve unreachable default branches in switch
statements
Add a legalization to resolve unreachable default branches in switch
statements. These are introduced when the CorrelatedValuePropagationPass
proves all cases of a switch are covered, leaving the default as an
unreachable block.
The default destination is replaced with either the common successor
block or the first switch case destination.
---
llvm/lib/Target/DirectX/DXILLegalizePass.cpp | 52 +++++++
.../DirectX/legalize-switch-unreachable.ll | 143 ++++++++++++++++++
2 files changed, 195 insertions(+)
create mode 100644 llvm/test/CodeGen/DirectX/legalize-switch-unreachable.ll
diff --git a/llvm/lib/Target/DirectX/DXILLegalizePass.cpp b/llvm/lib/Target/DirectX/DXILLegalizePass.cpp
index 61b10574cb506..432197427733e 100644
--- a/llvm/lib/Target/DirectX/DXILLegalizePass.cpp
+++ b/llvm/lib/Target/DirectX/DXILLegalizePass.cpp
@@ -406,6 +406,57 @@ legalizeGetHighLowi64Bytes(Instruction &I,
return false;
}
+static bool resolveUnreachableSwitchDefault(
+ Instruction &I, SmallVectorImpl<Instruction *> &ToRemove,
+ DenseMap<Value *, Value *> &) {
+ auto *SI = dyn_cast<SwitchInst>(&I);
+ if (!SI || SI->getNumCases() == 0)
+ return false;
+
+ BasicBlock *DefaultBB = SI->getDefaultDest();
+
+ // Check if the default destination only contains an unreachable instruction.
+ if (DefaultBB->size() != 1 ||
+ !isa<UnreachableInst>(DefaultBB->getTerminator()))
+ return false;
+
+ // Try to find a common successor of all case destinations. If all case
+ // blocks unconditionally branch to the same block, that is the common
+ // successor. This is just a best effort, and is done as the original form of
+ // the switch statement was likely in this form before being transformed to
+ // an unreachable branch.
+ BasicBlock *CommonSuccessor = nullptr;
+ for (auto &Case : SI->cases()) {
+ BasicBlock *CaseBB = Case.getCaseSuccessor();
+ auto *BI = dyn_cast<BranchInst>(CaseBB->getTerminator());
+ if (!BI || !BI->isUnconditional()) {
+ CommonSuccessor = nullptr;
+ break;
+ }
+ BasicBlock *Succ = BI->getSuccessor(0);
+ if (!CommonSuccessor)
+ CommonSuccessor = Succ;
+ else if (CommonSuccessor != Succ) {
+ CommonSuccessor = nullptr;
+ break;
+ }
+ }
+
+ BasicBlock *NewDefault =
+ CommonSuccessor ? CommonSuccessor
+ : SI->case_begin()->getCaseSuccessor();
+
+ BasicBlock *SwitchBB = SI->getParent();
+ SI->setDefaultDest(NewDefault);
+
+ // Ensure all phi nodes are legal by adding an incoming poison value from the
+ // unreachable branch.
+ for (PHINode &Phi : NewDefault->phis())
+ Phi.addIncoming(PoisonValue::get(Phi.getType()), SwitchBB);
+
+ return true;
+}
+
static bool
legalizeScalarLoadStoreOnArrays(Instruction &I,
SmallVectorImpl<Instruction *> &ToRemove,
@@ -501,6 +552,7 @@ class DXILLegalizationPipeline {
LegalizationPipeline[Stage2].push_back(
downcastI64toI32InsertExtractElements);
LegalizationPipeline[Stage2].push_back(legalizeScalarLoadStoreOnArrays);
+ LegalizationPipeline[Stage2].push_back(resolveUnreachableSwitchDefault);
}
};
diff --git a/llvm/test/CodeGen/DirectX/legalize-switch-unreachable.ll b/llvm/test/CodeGen/DirectX/legalize-switch-unreachable.ll
new file mode 100644
index 0000000000000..3e23e3dd0bdb2
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/legalize-switch-unreachable.ll
@@ -0,0 +1,143 @@
+; RUN: opt -S -passes='dxil-legalize' -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+; Test that a switch with an unreachable default and a common successor across
+; all case blocks has its default redirected to the common successor.
+
+define i32 @test_common_successor(i32 %val) {
+; CHECK-LABEL: define i32 @test_common_successor(
+; CHECK: entry:
+; CHECK-NEXT: switch i32 %val, label %merge [
+; CHECK-NEXT: i32 0, label %case0
+; CHECK-NEXT: i32 1, label %case1
+; CHECK-NEXT: i32 2, label %case2
+; CHECK-NEXT: ]
+; CHECK: case0:
+; CHECK-NEXT: br label %merge
+; CHECK: case1:
+; CHECK-NEXT: br label %merge
+; CHECK: case2:
+; CHECK-NEXT: br label %merge
+; CHECK: merge:
+; CHECK-NEXT: %result = phi i32 [ 10, %case0 ], [ 20, %case1 ], [ 30, %case2 ], [ poison, %entry ]
+; CHECK-NEXT: ret i32 %result
+;
+entry:
+ switch i32 %val, label %default [
+ i32 0, label %case0
+ i32 1, label %case1
+ i32 2, label %case2
+ ]
+
+default:
+ unreachable
+
+case0:
+ br label %merge
+
+case1:
+ br label %merge
+
+case2:
+ br label %merge
+
+merge:
+ %result = phi i32 [ 10, %case0 ], [ 20, %case1 ], [ 30, %case2 ]
+ ret i32 %result
+}
+
+; Test that a switch with an unreachable default and no common successor
+; has its default redirected to the first case block.
+
+define i32 @test_no_common_successor(i32 %val) {
+; CHECK-LABEL: define i32 @test_no_common_successor(
+; CHECK: entry:
+; CHECK-NEXT: switch i32 %val, label %case0 [
+; CHECK-NEXT: i32 0, label %case0
+; CHECK-NEXT: i32 1, label %case1
+; CHECK-NEXT: ]
+; CHECK: case0:
+; CHECK-NEXT: ret i32 10
+; CHECK: case1:
+; CHECK-NEXT: ret i32 20
+;
+entry:
+ switch i32 %val, label %default [
+ i32 0, label %case0
+ i32 1, label %case1
+ ]
+
+default:
+ unreachable
+
+case0:
+ ret i32 10
+
+case1:
+ ret i32 20
+}
+
+; Test that a switch with a reachable default is not modified.
+
+define i32 @test_reachable_default(i32 %val) {
+; CHECK-LABEL: define i32 @test_reachable_default(
+; CHECK: entry:
+; CHECK-NEXT: switch i32 %val, label %default [
+; CHECK-NEXT: i32 0, label %case0
+; CHECK-NEXT: ]
+; CHECK: default:
+; CHECK-NEXT: ret i32 -1
+; CHECK: case0:
+; CHECK-NEXT: ret i32 10
+;
+entry:
+ switch i32 %val, label %default [
+ i32 0, label %case0
+ ]
+
+default:
+ ret i32 -1
+
+case0:
+ ret i32 10
+}
+
+; Test with conditional branches in case blocks (no common successor) and
+; the default falling back to first case.
+
+define i32 @test_conditional_case_blocks(i32 %val, i1 %cond) {
+; CHECK-LABEL: define i32 @test_conditional_case_blocks(
+; CHECK: entry:
+; CHECK-NEXT: switch i32 %val, label %case0 [
+; CHECK-NEXT: i32 0, label %case0
+; CHECK-NEXT: i32 1, label %case1
+; CHECK-NEXT: ]
+; CHECK: case0:
+; CHECK-NEXT: br i1 %cond, label %merge_a, label %merge_b
+; CHECK: case1:
+; CHECK-NEXT: br label %merge_a
+; CHECK: merge_a:
+; CHECK-NEXT: ret i32 1
+; CHECK: merge_b:
+; CHECK-NEXT: ret i32 2
+;
+entry:
+ switch i32 %val, label %default [
+ i32 0, label %case0
+ i32 1, label %case1
+ ]
+
+default:
+ unreachable
+
+case0:
+ br i1 %cond, label %merge_a, label %merge_b
+
+case1:
+ br label %merge_a
+
+merge_a:
+ ret i32 1
+
+merge_b:
+ ret i32 2
+}
More information about the llvm-commits
mailing list