[llvm] [SimplifyCFG] Simplify switch default branch when branch proves operand value (PR #206597)

Sayan Sivakumaran via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 07:47:06 PDT 2026


https://github.com/sivakusayan updated https://github.com/llvm/llvm-project/pull/206597

>From 3e21d6fdf74f6062d1ca49d19ccafd769d896b9a Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Wed, 1 Jul 2026 10:10:32 -0600
Subject: [PATCH 1/7] Pre-commit test cases

---
 .../SimplifyCFG/switch-simplify-default.ll    | 120 ++++++++++++++++++
 1 file changed, 120 insertions(+)
 create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll

diff --git a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
new file mode 100644
index 0000000000000..d32fbe4390863
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
@@ -0,0 +1,120 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+
+define i32 @default_branch_proves_operand_value(i8 %x) {
+; CHECK-LABEL: @default_branch_proves_operand_value(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       default:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[ENTRY_UNREACHABLEDEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  ]
+default:
+  %cmp = icmp eq i8 %x, 1
+  call void @llvm.assume(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  ret i32 %retval
+}
+
+define i32 @default_branch_with_weight_proves_operand_value(i8 %x) {
+; CHECK-LABEL: @default_branch_with_weight_proves_operand_value(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:    ], !prof [[PROF0:![0-9]+]]
+; CHECK:       default:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[ENTRY_UNREACHABLEDEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  ], !prof !0
+
+default:
+  %cmp = icmp eq i8 %x, 1
+  call void @llvm.assume(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  ret i32 %retval
+}
+
+define i32 @default_branch_cant_prove_operand_value(i8 %x) {
+; CHECK-LABEL: @default_branch_cant_prove_operand_value(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[DEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       default:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i8 [[X]], 10
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  ]
+
+default:
+  %cmp = icmp ule i8 %x, 10
+  call void @llvm.assume(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  ret i32 %retval
+}
+
+!0 = !{!"branch_weights", i32 8, i32 4, i32 2}

>From f378153a18c81601f1415ebdbef97e8d800355b2 Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Wed, 1 Jul 2026 10:31:30 -0600
Subject: [PATCH 2/7] Simplify switch default branch when branch proves operand
 value

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 38 +++++++++++++++++++
 .../SimplifyCFG/switch-simplify-default.ll    | 11 +++++-
 2 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 1e9c66c5a660c..4c80672088070 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7813,6 +7813,41 @@ static bool simplifySwitchWhenUMin(SwitchInst *SI, DomTreeUpdater *DTU) {
   return true;
 }
 
+static bool simplifySwitchDefaultBranch(SwitchInst *SI, DomTreeUpdater *DTU,
+                                        const DataLayout &DL,
+                                        AssumptionCache *AC) {
+  if (SI->defaultDestUnreachable())
+    return false;
+
+  // If the switch condition is guaranteed to take some concrete value
+  // in the default block, we can create a new case based on that concrete
+  // value and make the default block unreachable. For example, we can
+  // move the default branch into an explicit case here:
+  //
+  // switch_bb:
+  //   switch i8 %x, label %default_bb [...]
+  //
+  // default_bb:
+  //   %cmp = icmp eq i8 %x, 1
+  //   call void llvm.assume(i1 %cmp) ; Implies %x must be 1
+  //   ...
+  //
+  BasicBlock *Default = SI->getDefaultDest();
+  const Instruction *CxtI = Default->getTerminator();
+  const KnownBits Known = computeKnownBits(SI->getOperand(0), DL, AC, CxtI);
+  if (!Known.isConstant())
+    return false;
+
+  SwitchInstProfUpdateWrapper SIW(*SI);
+  ConstantInt *CaseVal =
+      ConstantInt::get(SI->getContext(), Known.getConstant());
+  SIW.addCase(CaseVal, Default, SIW.getSuccessorWeight(0));
+  SIW.setSuccessorWeight(0, 0);
+  createUnreachableSwitchDefault(SI, DTU,
+                                 /*RemoveOrigDefaultBlock*/ false);
+  return true;
+}
+
 /// Tries to transform switch of powers of two to reduce switch range.
 /// For example, switch like:
 /// switch (C) { case 1: case 2: case 64: case 128: }
@@ -8353,6 +8388,9 @@ bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
   if (simplifySwitchWhenUMin(SI, DTU))
     return requestResimplify();
 
+  if (simplifySwitchDefaultBranch(SI, DTU, DL, Options.AC))
+    return requestResimplify();
+
   return false;
 }
 
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
index d32fbe4390863..f1c8b4b136f3a 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
@@ -7,7 +7,10 @@ define i32 @default_branch_proves_operand_value(i8 %x) {
 ; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
 ; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
 ; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:      i8 1, label [[DEFAULT:%.*]]
 ; CHECK-NEXT:    ]
+; CHECK:       entry.unreachabledefault:
+; CHECK-NEXT:    unreachable
 ; CHECK:       default:
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
@@ -15,7 +18,7 @@ define i32 @default_branch_proves_operand_value(i8 %x) {
 ; CHECK:       case_zero:
 ; CHECK-NEXT:    br label [[RETURN]]
 ; CHECK:       return:
-; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[ENTRY_UNREACHABLEDEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
 ; CHECK-NEXT:    ret i32 [[RETVAL]]
 ;
 entry:
@@ -45,7 +48,10 @@ define i32 @default_branch_with_weight_proves_operand_value(i8 %x) {
 ; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
 ; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
 ; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:      i8 1, label [[DEFAULT:%.*]]
 ; CHECK-NEXT:    ], !prof [[PROF0:![0-9]+]]
+; CHECK:       entry.unreachabledefault:
+; CHECK-NEXT:    unreachable
 ; CHECK:       default:
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
@@ -53,7 +59,7 @@ define i32 @default_branch_with_weight_proves_operand_value(i8 %x) {
 ; CHECK:       case_zero:
 ; CHECK-NEXT:    br label [[RETURN]]
 ; CHECK:       return:
-; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[ENTRY_UNREACHABLEDEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
 ; CHECK-NEXT:    ret i32 [[RETVAL]]
 ;
 entry:
@@ -117,4 +123,5 @@ return:
   ret i32 %retval
 }
 
+; CHECK: [[PROF0]] = !{!"branch_weights", i32 0, i32 4, i32 2, i32 8}
 !0 = !{!"branch_weights", i32 8, i32 4, i32 2}

>From e21abc6065929e52c02cbcf8d6f39d2b48dab3bb Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Wed, 1 Jul 2026 13:31:16 -0600
Subject: [PATCH 3/7] Don't emit invalid switch IR

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     |  8 +++-
 .../SimplifyCFG/switch-simplify-default.ll    | 46 +++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 4c80672088070..4356c95a6b363 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7838,9 +7838,15 @@ static bool simplifySwitchDefaultBranch(SwitchInst *SI, DomTreeUpdater *DTU,
   if (!Known.isConstant())
     return false;
 
-  SwitchInstProfUpdateWrapper SIW(*SI);
+  // Make sure we don't create invalid IR if the switch already
+  // has an explicit case for this constant.
   ConstantInt *CaseVal =
       ConstantInt::get(SI->getContext(), Known.getConstant());
+  const llvm::SwitchInst::CaseIt CaseIt = SI->findCaseValue(CaseVal);
+  if (CaseIt != SI->case_default())
+    return false;
+
+  SwitchInstProfUpdateWrapper SIW(*SI);
   SIW.addCase(CaseVal, Default, SIW.getSuccessorWeight(0));
   SIW.setSuccessorWeight(0, 0);
   createUnreachableSwitchDefault(SI, DTU,
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
index f1c8b4b136f3a..b3ef9005c48cb 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
@@ -123,5 +123,51 @@ return:
   ret i32 %retval
 }
 
+define i32 @default_branch_proves_operand_value_but_has_existing_case(i8 %x) {
+; CHECK-LABEL: @default_branch_proves_operand_value_but_has_existing_case(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[DEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:      i8 1, label [[CASE_ONE:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       default:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i8 [[X]], 10
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_one:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 1, [[CASE_ONE]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  i8 1, label %case_one
+  ]
+
+default:
+  %cmp = icmp ule i8 %x, 10
+  call void @llvm.assume(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+case_one:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ], [1, %case_one ]
+  ret i32 %retval
+}
+
 ; CHECK: [[PROF0]] = !{!"branch_weights", i32 0, i32 4, i32 2, i32 8}
 !0 = !{!"branch_weights", i32 8, i32 4, i32 2}

>From b95f5c1add9c1ed7543492e4f0b1b7eb6b0f0de6 Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Wed, 1 Jul 2026 14:20:12 -0600
Subject: [PATCH 4/7] Mark default branch in crashing case as dead code

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 26 +++++-------
 .../SimplifyCFG/switch-simplify-default.ll    | 40 +++++++++----------
 2 files changed, 29 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 4356c95a6b363..25d3e627d3b9f 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7820,32 +7820,26 @@ static bool simplifySwitchDefaultBranch(SwitchInst *SI, DomTreeUpdater *DTU,
     return false;
 
   // If the switch condition is guaranteed to take some concrete value
-  // in the default block, we can create a new case based on that concrete
-  // value and make the default block unreachable. For example, we can
-  // move the default branch into an explicit case here:
-  //
-  // switch_bb:
-  //   switch i8 %x, label %default_bb [...]
-  //
-  // default_bb:
-  //   %cmp = icmp eq i8 %x, 1
-  //   call void llvm.assume(i1 %cmp) ; Implies %x must be 1
-  //   ...
-  //
+  // in the default block, we can make some nice simplifications to the
+  // switch.
   BasicBlock *Default = SI->getDefaultDest();
   const Instruction *CxtI = Default->getTerminator();
   const KnownBits Known = computeKnownBits(SI->getOperand(0), DL, AC, CxtI);
   if (!Known.isConstant())
     return false;
 
-  // Make sure we don't create invalid IR if the switch already
-  // has an explicit case for this constant.
+  // If the known value for the switch operand already has a case,
+  // the default branch must be dead - mark it as unreachable.
   ConstantInt *CaseVal =
       ConstantInt::get(SI->getContext(), Known.getConstant());
   const llvm::SwitchInst::CaseIt CaseIt = SI->findCaseValue(CaseVal);
-  if (CaseIt != SI->case_default())
-    return false;
+  if (CaseIt != SI->case_default()) {
+   createUnreachableSwitchDefault(SI, DTU,
+                                 /*RemoveOrigDefaultBlock*/ false);
+   return true;
+  }
 
+  // Otherwise, we can move the default branch into an explicit case branch.
   SwitchInstProfUpdateWrapper SIW(*SI);
   SIW.addCase(CaseVal, Default, SIW.getSuccessorWeight(0));
   SIW.setSuccessorWeight(0, 0);
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
index b3ef9005c48cb..5f8f5df1ba6cd 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
@@ -84,31 +84,33 @@ return:
   ret i32 %retval
 }
 
-define i32 @default_branch_cant_prove_operand_value(i8 %x) {
-; CHECK-LABEL: @default_branch_cant_prove_operand_value(
+define i32 @default_branch_proves_operand_value_but_has_existing_case(i8 %x) {
+; CHECK-LABEL: @default_branch_proves_operand_value_but_has_existing_case(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    switch i8 [[X:%.*]], label [[DEFAULT:%.*]] [
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
 ; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
 ; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:      i8 1, label [[CASE_ONE:%.*]]
 ; CHECK-NEXT:    ]
-; CHECK:       default:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i8 [[X]], 10
-; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
-; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       entry.unreachabledefault:
+; CHECK-NEXT:    unreachable
 ; CHECK:       case_zero:
 ; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_one:
+; CHECK-NEXT:    br label [[RETURN]]
 ; CHECK:       return:
-; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[CASE_ONE]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
 ; CHECK-NEXT:    ret i32 [[RETVAL]]
 ;
 entry:
   switch i8 %x, label %default [
   i8 -1, label %case_neg_one
   i8 0, label %case_zero
+  i8 1, label %case_one
   ]
 
 default:
-  %cmp = icmp ule i8 %x, 10
+  %cmp = icmp eq i8 %x, 1
   call void @llvm.assume(i1 %cmp)
   br label %return
 
@@ -118,18 +120,20 @@ case_neg_one:
 case_zero:
   br label %return
 
+case_one:
+  br label %return
+
 return:
-  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ], [1, %case_one ]
   ret i32 %retval
 }
 
-define i32 @default_branch_proves_operand_value_but_has_existing_case(i8 %x) {
-; CHECK-LABEL: @default_branch_proves_operand_value_but_has_existing_case(
+define i32 @default_branch_cant_prove_operand_value(i8 %x) {
+; CHECK-LABEL: @default_branch_cant_prove_operand_value(
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    switch i8 [[X:%.*]], label [[DEFAULT:%.*]] [
 ; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
 ; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
-; CHECK-NEXT:      i8 1, label [[CASE_ONE:%.*]]
 ; CHECK-NEXT:    ]
 ; CHECK:       default:
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i8 [[X]], 10
@@ -137,17 +141,14 @@ define i32 @default_branch_proves_operand_value_but_has_existing_case(i8 %x) {
 ; CHECK-NEXT:    br label [[RETURN]]
 ; CHECK:       case_zero:
 ; CHECK-NEXT:    br label [[RETURN]]
-; CHECK:       case_one:
-; CHECK-NEXT:    br label [[RETURN]]
 ; CHECK:       return:
-; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 1, [[CASE_ONE]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
 ; CHECK-NEXT:    ret i32 [[RETVAL]]
 ;
 entry:
   switch i8 %x, label %default [
   i8 -1, label %case_neg_one
   i8 0, label %case_zero
-  i8 1, label %case_one
   ]
 
 default:
@@ -161,11 +162,8 @@ case_neg_one:
 case_zero:
   br label %return
 
-case_one:
-  br label %return
-
 return:
-  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ], [1, %case_one ]
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
   ret i32 %retval
 }
 

>From de6726a84c07e280661ade44513e0e0799c61c4c Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Wed, 1 Jul 2026 14:27:18 -0600
Subject: [PATCH 5/7] Fix format issues

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 25d3e627d3b9f..a860113b89655 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7834,9 +7834,8 @@ static bool simplifySwitchDefaultBranch(SwitchInst *SI, DomTreeUpdater *DTU,
       ConstantInt::get(SI->getContext(), Known.getConstant());
   const llvm::SwitchInst::CaseIt CaseIt = SI->findCaseValue(CaseVal);
   if (CaseIt != SI->case_default()) {
-   createUnreachableSwitchDefault(SI, DTU,
-                                 /*RemoveOrigDefaultBlock*/ false);
-   return true;
+    createUnreachableSwitchDefault(SI, DTU, /*RemoveOrigDefaultBlock*/ false);
+    return true;
   }
 
   // Otherwise, we can move the default branch into an explicit case branch.

>From 43740d6b1668e0209234987980aff7305257bde7 Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Wed, 1 Jul 2026 18:44:11 -0600
Subject: [PATCH 6/7] Add some asserts

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 28 ++++++++++++-----------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index a860113b89655..bb9bbabe381d4 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7816,34 +7816,36 @@ static bool simplifySwitchWhenUMin(SwitchInst *SI, DomTreeUpdater *DTU) {
 static bool simplifySwitchDefaultBranch(SwitchInst *SI, DomTreeUpdater *DTU,
                                         const DataLayout &DL,
                                         AssumptionCache *AC) {
+  assert(SI);
   if (SI->defaultDestUnreachable())
     return false;
 
-  // If the switch condition is guaranteed to take some concrete value
+  // If it can be proved that the switch condition takes some concrete value
   // in the default block, we can make some nice simplifications to the
   // switch.
   BasicBlock *Default = SI->getDefaultDest();
   const Instruction *CxtI = Default->getTerminator();
-  const KnownBits Known = computeKnownBits(SI->getOperand(0), DL, AC, CxtI);
+  const KnownBits Known = computeKnownBits(SI->getCondition(), DL, AC, CxtI);
   if (!Known.isConstant())
     return false;
 
-  // If the known value for the switch operand already has a case,
-  // the default branch must be dead - mark it as unreachable.
+  // At this point, we know that only one value can be mapped to the
+  // default block. So, if a case doesn't exist for it already, we
+  // can create one pointing to the default block.
   ConstantInt *CaseVal =
       ConstantInt::get(SI->getContext(), Known.getConstant());
   const llvm::SwitchInst::CaseIt CaseIt = SI->findCaseValue(CaseVal);
-  if (CaseIt != SI->case_default()) {
-    createUnreachableSwitchDefault(SI, DTU, /*RemoveOrigDefaultBlock*/ false);
-    return true;
+  if (CaseIt == SI->case_default()) {
+    SwitchInstProfUpdateWrapper SIW(*SI);
+    SIW.addCase(CaseVal, Default, SIW.getSuccessorWeight(0));
+    SIW.setSuccessorWeight(0, 0);
   }
+  createUnreachableSwitchDefault(SI, DTU, /*RemoveOrigDefaultBlock*/ false);
 
-  // Otherwise, we can move the default branch into an explicit case branch.
-  SwitchInstProfUpdateWrapper SIW(*SI);
-  SIW.addCase(CaseVal, Default, SIW.getSuccessorWeight(0));
-  SIW.setSuccessorWeight(0, 0);
-  createUnreachableSwitchDefault(SI, DTU,
-                                 /*RemoveOrigDefaultBlock*/ false);
+  assert(SI->getNumCases() > 0 && "Switch should have at least one case");
+  assert(SI->findCaseValue(CaseVal) != SI->case_default() &&
+         "Proven value should have a dedicated case");
+  assert(SI->defaultDestUnreachable());
   return true;
 }
 

>From bdc4ec7dc521b0deb842ff818e0e0b4d160888f7 Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Mon, 6 Jul 2026 19:50:29 -0600
Subject: [PATCH 7/7] Add check for valid assumptions

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     |   7 +-
 .../SimplifyCFG/switch-simplify-default.ll    | 130 +++++++++++++++++-
 2 files changed, 133 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index bb9bbabe381d4..5deb7efbb610c 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7824,8 +7824,11 @@ static bool simplifySwitchDefaultBranch(SwitchInst *SI, DomTreeUpdater *DTU,
   // in the default block, we can make some nice simplifications to the
   // switch.
   BasicBlock *Default = SI->getDefaultDest();
-  const Instruction *CxtI = Default->getTerminator();
-  const KnownBits Known = computeKnownBits(SI->getCondition(), DL, AC, CxtI);
+  const Instruction *CxtI = &*Default->getFirstNonPHIIt();
+  DominatorTree *DT = DTU ? &DTU->getDomTree() : nullptr;
+  const KnownBits Known =
+      computeKnownBits(SI->getCondition(),
+                       SimplifyQuery(DL, DT, AC, CxtI).allowEphemerals(true));
   if (!Known.isConstant())
     return false;
 
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
index 5f8f5df1ba6cd..e81911c9064e5 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-simplify-default.ll
@@ -84,6 +84,91 @@ return:
   ret i32 %retval
 }
 
+define i32 @default_branch_proves_operand_value_different_blocks(i8 %x) {
+; CHECK-LABEL: @default_branch_proves_operand_value_different_blocks(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X:%.*]], 1
+; CHECK-NEXT:    switch i8 [[X]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:      i8 1, label [[DEFAULT:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       entry.unreachabledefault:
+; CHECK-NEXT:    unreachable
+; CHECK:       default:
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  %cmp = icmp eq i8 %x, 1
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  ]
+default:
+  call void @llvm.assume(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  ret i32 %retval
+}
+
+declare void @foo(i8 %bar) nounwind
+define i32 @default_branch_proves_operand_value_assume_operand_not_ephemeral(i8 %x) {
+; CHECK-LABEL: @default_branch_proves_operand_value_assume_operand_not_ephemeral(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:      i8 1, label [[DEFAULT:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       entry.unreachabledefault:
+; CHECK-NEXT:    unreachable
+; CHECK:       default:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    call void @foo(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[DEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  ]
+default:
+  %cmp = icmp eq i8 %x, 1
+  call void @llvm.assume(i1 %cmp)
+  call void @foo(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  ret i32 %retval
+}
+
 define i32 @default_branch_proves_operand_value_but_has_existing_case(i8 %x) {
 ; CHECK-LABEL: @default_branch_proves_operand_value_but_has_existing_case(
 ; CHECK-NEXT:  entry:
@@ -128,8 +213,8 @@ return:
   ret i32 %retval
 }
 
-define i32 @default_branch_cant_prove_operand_value(i8 %x) {
-; CHECK-LABEL: @default_branch_cant_prove_operand_value(
+define i32 @default_branch_assume_doesnt_prove_all_bits(i8 %x) {
+; CHECK-LABEL: @default_branch_assume_doesnt_prove_all_bits(
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    switch i8 [[X:%.*]], label [[DEFAULT:%.*]] [
 ; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
@@ -167,5 +252,46 @@ return:
   ret i32 %retval
 }
 
+declare void @foo_may_unwind()
+define i32 @default_branch_may_throw_before_assume(i8 %x) {
+; CHECK-LABEL: @default_branch_may_throw_before_assume(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    switch i8 [[X:%.*]], label [[ENTRY_UNREACHABLEDEFAULT:%.*]] [
+; CHECK-NEXT:      i8 -1, label [[RETURN:%.*]]
+; CHECK-NEXT:      i8 0, label [[CASE_ZERO:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       default:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
+; CHECK-NEXT:    call void @foo_may_unwind()
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       case_zero:
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RETVAL:%.*]] = phi i32 [ 1, [[ENTRY_UNREACHABLEDEFAULT]] ], [ 0, [[CASE_ZERO]] ], [ -1, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i32 [[RETVAL]]
+;
+entry:
+  switch i8 %x, label %default [
+  i8 -1, label %case_neg_one
+  i8 0, label %case_zero
+  ]
+default:
+  %cmp = icmp eq i8 %x, 1
+  call void @foo_may_unwind()
+  call void @llvm.assume(i1 %cmp)
+  br label %return
+
+case_neg_one:
+  br label %return
+
+case_zero:
+  br label %return
+
+return:
+  %retval = phi i32 [ 1, %default ], [ -1, %case_neg_one ], [ 0, %case_zero ]
+  ret i32 %retval
+}
+
 ; CHECK: [[PROF0]] = !{!"branch_weights", i32 0, i32 4, i32 2, i32 8}
 !0 = !{!"branch_weights", i32 8, i32 4, i32 2}



More information about the llvm-commits mailing list