[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