[llvm] [IR] Don't store switch case values as operands (PR #170984)

Alexis Engelke via llvm-commits llvm-commits at lists.llvm.org
Thu Dec 11 07:24:42 PST 2025


https://github.com/aengelke updated https://github.com/llvm/llvm-project/pull/170984

>From 4b1113a8a760373aa8867dbdb2000a63e93cacf5 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Sat, 6 Dec 2025 12:36:53 +0000
Subject: [PATCH 1/2] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
 =?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.5-bogner
---
 llvm/docs/ReleaseNotes.md                     |  2 +
 llvm/include/llvm-c/Core.h                    | 24 ++++++
 llvm/include/llvm/IR/Instructions.h           | 35 +++++----
 llvm/include/llvm/IR/User.h                   |  8 +-
 llvm/lib/Bitcode/Writer/ValueEnumerator.cpp   |  8 ++
 llvm/lib/CodeGen/TypePromotion.cpp            |  8 ++
 llvm/lib/IR/Core.cpp                          | 13 ++++
 llvm/lib/IR/Instruction.cpp                   |  8 ++
 llvm/lib/IR/Instructions.cpp                  | 33 ++++----
 llvm/lib/IR/User.cpp                          | 20 ++---
 llvm/lib/IR/Verifier.cpp                      |  2 +-
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 17 ++---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 15 +++-
 .../Transforms/SimplifyCFG/switch-dedup.ll    | 76 +++++++++++++++++++
 llvm/unittests/IR/InstructionsTest.cpp        | 23 ++++++
 llvm/unittests/IR/VerifierTest.cpp            |  3 -
 16 files changed, 236 insertions(+), 59 deletions(-)
 create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-dedup.ll

diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index dc0cec6122cdf..5ffad71abaf35 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -74,6 +74,7 @@ Changes to the LLVM IR
   format string function implementations from statically-linked libc's based on
   the requirements of each call. Currently only `float` is supported; this can
   keep floating point support out of printf if it can be proven unused.
+* Case values are no longer operands of `SwitchInst`.
 
 Changes to LLVM infrastructure
 ------------------------------
@@ -178,6 +179,7 @@ Changes to the C API
 * Add `LLVMGetOrInsertFunction` to get or insert a function, replacing the combination of `LLVMGetNamedFunction` and `LLVMAddFunction`.
 * Allow `LLVMGetVolatile` to work with any kind of Instruction.
 * Add `LLVMConstFPFromBits` to get a constant floating-point value from an array of 64 bit values.
+* Add `LLVMGetSwitchCaseValue` and `LLVMSetSwitchCaseValue` to get and set switch case values; switch case values are no longer operands of the instruction.
 
 Changes to the CodeGen infrastructure
 -------------------------------------
diff --git a/llvm/include/llvm-c/Core.h b/llvm/include/llvm-c/Core.h
index fc41b5835d6eb..0074f1aad5a3c 100644
--- a/llvm/include/llvm-c/Core.h
+++ b/llvm/include/llvm-c/Core.h
@@ -4213,6 +4213,30 @@ LLVM_C_ABI void LLVMSetCondition(LLVMValueRef Branch, LLVMValueRef Cond);
  */
 LLVM_C_ABI LLVMBasicBlockRef LLVMGetSwitchDefaultDest(LLVMValueRef SwitchInstr);
 
+/**
+ * Obtain the case value for a successor of a switch instruction. i corresponds
+ * to the successor index. The first successor is the default destination, so i
+ * must be greater than zero.
+ *
+ * This only works on llvm::SwitchInst instructions.
+ *
+ * @see llvm::SwitchInst::CaseHandle::getCaseValue()
+ */
+LLVM_C_ABI LLVMValueRef LLVMGetSwitchCaseValue(LLVMValueRef SwitchInstr,
+                                               unsigned i);
+
+/**
+ * Set the case value for a successor of a switch instruction. i corresponds to
+ * the successor index. The first successor is the default destination, so i
+ * must be greater than zero.
+ *
+ * This only works on llvm::SwitchInst instructions.
+ *
+ * @see llvm::SwitchInst::CaseHandle::setValue()
+ */
+LLVM_C_ABI void LLVMSetSwitchCaseValue(LLVMValueRef SwitchInstr, unsigned i,
+                                       LLVMValueRef CaseValue);
+
 /**
  * @}
  */
diff --git a/llvm/include/llvm/IR/Instructions.h b/llvm/include/llvm/IR/Instructions.h
index 8bd060ae8f485..80e9cb5bd806b 100644
--- a/llvm/include/llvm/IR/Instructions.h
+++ b/llvm/include/llvm/IR/Instructions.h
@@ -2665,7 +2665,7 @@ class PHINode : public Instruction {
   // User::allocHungoffUses, because we have to allocate Uses for the incoming
   // values and pointers to the incoming blocks, all in one allocation.
   void allocHungoffUses(unsigned N) {
-    User::allocHungoffUses(N, /* IsPhi */ true);
+    User::allocHungoffUses(N, /*WithExtraValues=*/true);
   }
 
 public:
@@ -3198,10 +3198,10 @@ class SwitchInst : public Instruction {
 
   unsigned ReservedSpace;
 
-  // Operand[0]    = Value to switch on
-  // Operand[1]    = Default basic block destination
-  // Operand[2n  ] = Value to match
-  // Operand[2n+1] = BasicBlock to go to on match
+  // Operand[0] = Value to switch on
+  // Operand[1] = Default basic block destination
+  // Operand[n] = BasicBlock to go to on match
+  // Values are stored after the Uses similar to PHINode's basic blocks.
   SwitchInst(const SwitchInst &SI);
 
   /// Create a new switch instruction, specifying a value to switch on and a
@@ -3223,6 +3223,17 @@ class SwitchInst : public Instruction {
 
   LLVM_ABI SwitchInst *cloneImpl() const;
 
+  void allocHungoffUses(unsigned N) {
+    User::allocHungoffUses(N, /*WithExtraValues=*/true);
+  }
+
+  ConstantInt *const *case_values() const {
+    return reinterpret_cast<ConstantInt *const *>(op_begin() + ReservedSpace);
+  }
+  ConstantInt **case_values() {
+    return reinterpret_cast<ConstantInt **>(op_begin() + ReservedSpace);
+  }
+
 public:
   void operator delete(void *Ptr) { User::operator delete(Ptr); }
 
@@ -3257,7 +3268,7 @@ class SwitchInst : public Instruction {
     ConstantIntT *getCaseValue() const {
       assert((unsigned)Index < SI->getNumCases() &&
              "Index out the number of cases.");
-      return reinterpret_cast<ConstantIntT *>(SI->getOperand(2 + Index * 2));
+      return SI->case_values()[Index];
     }
 
     /// Resolves successor for current case.
@@ -3299,7 +3310,7 @@ class SwitchInst : public Instruction {
     void setValue(ConstantInt *V) const {
       assert((unsigned)Index < SI->getNumCases() &&
              "Index out the number of cases.");
-      SI->setOperand(2 + Index*2, reinterpret_cast<Value*>(V));
+      SI->case_values()[Index] = V;
     }
 
     /// Sets the new successor for current case.
@@ -3406,9 +3417,7 @@ class SwitchInst : public Instruction {
 
   /// Return the number of 'cases' in this switch instruction, excluding the
   /// default case.
-  unsigned getNumCases() const {
-    return getNumOperands()/2 - 1;
-  }
+  unsigned getNumCases() const { return getNumOperands() - 2; }
 
   /// Returns a read/write iterator that points to the first case in the
   /// SwitchInst.
@@ -3510,14 +3519,14 @@ class SwitchInst : public Instruction {
   /// case.
   LLVM_ABI CaseIt removeCase(CaseIt I);
 
-  unsigned getNumSuccessors() const { return getNumOperands()/2; }
+  unsigned getNumSuccessors() const { return getNumOperands() - 1; }
   BasicBlock *getSuccessor(unsigned idx) const {
     assert(idx < getNumSuccessors() &&"Successor idx out of range for switch!");
-    return cast<BasicBlock>(getOperand(idx*2+1));
+    return cast<BasicBlock>(getOperand(idx + 1));
   }
   void setSuccessor(unsigned idx, BasicBlock *NewSucc) {
     assert(idx < getNumSuccessors() && "Successor # out of range for switch!");
-    setOperand(idx * 2 + 1, NewSucc);
+    setOperand(idx + 1, NewSucc);
   }
 
   // Methods for support type inquiry through isa, cast, and dyn_cast:
diff --git a/llvm/include/llvm/IR/User.h b/llvm/include/llvm/IR/User.h
index cbb4379b68c41..394ea70d6637e 100644
--- a/llvm/include/llvm/IR/User.h
+++ b/llvm/include/llvm/IR/User.h
@@ -132,13 +132,13 @@ class User : public Value {
 
   /// Allocate the array of Uses, followed by a pointer
   /// (with bottom bit set) to the User.
-  /// \param IsPhi identifies callers which are phi nodes and which need
-  /// N BasicBlock* allocated along with N
-  LLVM_ABI void allocHungoffUses(unsigned N, bool IsPhi = false);
+  /// \param WithExtraValues identifies callers which need N Value* allocated
+  /// along the N operands.
+  LLVM_ABI void allocHungoffUses(unsigned N, bool WithExtraValues = false);
 
   /// Grow the number of hung off uses.  Note that allocHungoffUses
   /// should be called if there are no uses.
-  LLVM_ABI void growHungoffUses(unsigned N, bool IsPhi = false);
+  LLVM_ABI void growHungoffUses(unsigned N, bool WithExtraValues = false);
 
 protected:
   ~User() = default; // Use deleteValue() to delete a generic Instruction.
diff --git a/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp b/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp
index 36d0d35d024cc..4d5188cf7a0ce 100644
--- a/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp
+++ b/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp
@@ -164,6 +164,10 @@ static OrderMap orderModule(const Module &M) {
           orderConstantValue(Op);
         if (auto *SVI = dyn_cast<ShuffleVectorInst>(&I))
           orderValue(SVI->getShuffleMaskForBitcode(), OM);
+        if (auto *SI = dyn_cast<SwitchInst>(&I)) {
+          for (const auto &Case : SI->cases())
+            orderValue(Case.getCaseValue(), OM);
+        }
         orderValue(&I, OM);
       }
   }
@@ -1092,6 +1096,10 @@ void ValueEnumerator::incorporateFunction(const Function &F) {
       }
       if (auto *SVI = dyn_cast<ShuffleVectorInst>(&I))
         EnumerateValue(SVI->getShuffleMaskForBitcode());
+      if (auto *SI = dyn_cast<SwitchInst>(&I)) {
+        for (const auto &Case : SI->cases())
+          EnumerateValue(Case.getCaseValue());
+      }
     }
     BasicBlocks.push_back(&BB);
     ValueMap[&BB] = BasicBlocks.size();
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index e9fa78eabff7c..0865597dadcd6 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -512,6 +512,14 @@ void IRPromoter::PromoteTree() {
         I->setOperand(i, ConstantInt::get(ExtTy, 0));
     }
 
+    // For switch, also mutate case values, which are not operands.
+    if (auto *SI = dyn_cast<SwitchInst>(I)) {
+      for (auto Case : SI->cases()) {
+        APInt NewConst = Case.getCaseValue()->getValue().zext(PromotedWidth);
+        Case.setValue(ConstantInt::get(SI->getContext(), NewConst));
+      }
+    }
+
     // Mutate the result type, unless this is an icmp or switch.
     if (!isa<ICmpInst>(I) && !isa<SwitchInst>(I)) {
       I->mutateType(ExtTy);
diff --git a/llvm/lib/IR/Core.cpp b/llvm/lib/IR/Core.cpp
index bea30649947c7..e0427e9c3d8b1 100644
--- a/llvm/lib/IR/Core.cpp
+++ b/llvm/lib/IR/Core.cpp
@@ -3257,6 +3257,19 @@ LLVMBasicBlockRef LLVMGetSwitchDefaultDest(LLVMValueRef Switch) {
   return wrap(unwrap<SwitchInst>(Switch)->getDefaultDest());
 }
 
+LLVMValueRef LLVMGetSwitchCaseValue(LLVMValueRef Switch, unsigned i) {
+  assert(i > 0 && i <= unwrap<SwitchInst>(Switch)->getNumCases());
+  auto It = unwrap<SwitchInst>(Switch)->case_begin() + (i - 1);
+  return wrap(It->getCaseValue());
+}
+
+void LLVMSetSwitchCaseValue(LLVMValueRef Switch, unsigned i,
+                            LLVMValueRef CaseValue) {
+  assert(i > 0 && i <= unwrap<SwitchInst>(Switch)->getNumCases());
+  auto It = unwrap<SwitchInst>(Switch)->case_begin() + (i - 1);
+  It->setValue(unwrap<ConstantInt>(CaseValue));
+}
+
 /*--.. Operations on alloca instructions (only) ............................--*/
 
 LLVMTypeRef LLVMGetAllocatedType(LLVMValueRef Alloca) {
diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index 33ca46ca1c2c6..b1fa86aff239e 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -975,6 +975,14 @@ bool Instruction::isIdenticalToWhenDefined(const Instruction *I,
     return equal(Phi->blocks(), OtherPhi->blocks());
   }
 
+  if (const SwitchInst *SI = dyn_cast<SwitchInst>(this)) {
+    const SwitchInst *OtherSI = cast<SwitchInst>(I);
+    for (auto [Case, OtherCase] : zip(SI->cases(), OtherSI->cases()))
+      if (Case.getCaseValue() != OtherCase.getCaseValue())
+        return false;
+    return true;
+  }
+
   return this->hasSameSpecialState(I, /*IgnoreAlignment=*/false,
                                    IntersectAttrs);
 }
diff --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp
index 85d3690dd8306..db0d5af83655f 100644
--- a/llvm/lib/IR/Instructions.cpp
+++ b/llvm/lib/IR/Instructions.cpp
@@ -202,7 +202,7 @@ void PHINode::growOperands() {
   if (NumOps < 2) NumOps = 2;      // 2 op PHI nodes are VERY common.
 
   ReservedSpace = NumOps;
-  growHungoffUses(ReservedSpace, /* IsPhi */ true);
+  growHungoffUses(ReservedSpace, /*WithExtraValues=*/true);
 }
 
 /// hasConstantValue - If the specified PHI node always merges together the same
@@ -4076,7 +4076,7 @@ SwitchInst::SwitchInst(Value *Value, BasicBlock *Default, unsigned NumCases,
                        InsertPosition InsertBefore)
     : Instruction(Type::getVoidTy(Value->getContext()), Instruction::Switch,
                   AllocMarker, InsertBefore) {
-  init(Value, Default, 2+NumCases*2);
+  init(Value, Default, 2 + NumCases);
 }
 
 SwitchInst::SwitchInst(const SwitchInst &SI)
@@ -4084,10 +4084,12 @@ SwitchInst::SwitchInst(const SwitchInst &SI)
   init(SI.getCondition(), SI.getDefaultDest(), SI.getNumOperands());
   setNumHungOffUseOperands(SI.getNumOperands());
   Use *OL = getOperandList();
+  ConstantInt **VL = case_values();
   const Use *InOL = SI.getOperandList();
-  for (unsigned i = 2, E = SI.getNumOperands(); i != E; i += 2) {
+  ConstantInt *const *InVL = SI.case_values();
+  for (unsigned i = 2, E = SI.getNumOperands(); i != E; ++i) {
     OL[i] = InOL[i];
-    OL[i+1] = InOL[i+1];
+    VL[i - 2] = InVL[i - 2];
   }
   SubclassOptionalData = SI.SubclassOptionalData;
 }
@@ -4097,11 +4099,11 @@ SwitchInst::SwitchInst(const SwitchInst &SI)
 void SwitchInst::addCase(ConstantInt *OnVal, BasicBlock *Dest) {
   unsigned NewCaseIdx = getNumCases();
   unsigned OpNo = getNumOperands();
-  if (OpNo+2 > ReservedSpace)
+  if (OpNo + 1 > ReservedSpace)
     growOperands();  // Get more space!
   // Initialize some new operands.
-  assert(OpNo+1 < ReservedSpace && "Growing didn't work!");
-  setNumHungOffUseOperands(OpNo+2);
+  assert(OpNo < ReservedSpace && "Growing didn't work!");
+  setNumHungOffUseOperands(OpNo + 1);
   CaseHandle Case(this, NewCaseIdx);
   Case.setValue(OnVal);
   Case.setSuccessor(Dest);
@@ -4112,21 +4114,22 @@ void SwitchInst::addCase(ConstantInt *OnVal, BasicBlock *Dest) {
 SwitchInst::CaseIt SwitchInst::removeCase(CaseIt I) {
   unsigned idx = I->getCaseIndex();
 
-  assert(2 + idx*2 < getNumOperands() && "Case index out of range!!!");
+  assert(2 + idx < getNumOperands() && "Case index out of range!!!");
 
   unsigned NumOps = getNumOperands();
   Use *OL = getOperandList();
+  ConstantInt **VL = case_values();
 
   // Overwrite this case with the end of the list.
-  if (2 + (idx + 1) * 2 != NumOps) {
-    OL[2 + idx * 2] = OL[NumOps - 2];
-    OL[2 + idx * 2 + 1] = OL[NumOps - 1];
+  if (2 + idx + 1 != NumOps) {
+    OL[2 + idx] = OL[NumOps - 1];
+    VL[idx] = VL[NumOps - 2 - 1];
   }
 
   // Nuke the last value.
-  OL[NumOps-2].set(nullptr);
-  OL[NumOps-2+1].set(nullptr);
-  setNumHungOffUseOperands(NumOps-2);
+  OL[NumOps - 1].set(nullptr);
+  VL[NumOps - 2 - 1] = nullptr;
+  setNumHungOffUseOperands(NumOps - 1);
 
   return CaseIt(this, idx);
 }
@@ -4139,7 +4142,7 @@ void SwitchInst::growOperands() {
   unsigned NumOps = e*3;
 
   ReservedSpace = NumOps;
-  growHungoffUses(ReservedSpace);
+  growHungoffUses(ReservedSpace, /*WithExtraValues=*/true);
 }
 
 void SwitchInstProfUpdateWrapper::init() {
diff --git a/llvm/lib/IR/User.cpp b/llvm/lib/IR/User.cpp
index 9bb7c1298593a..1847c29d9ea4f 100644
--- a/llvm/lib/IR/User.cpp
+++ b/llvm/lib/IR/User.cpp
@@ -50,16 +50,16 @@ bool User::replaceUsesOfWith(Value *From, Value *To) {
 //                         User allocHungoffUses Implementation
 //===----------------------------------------------------------------------===//
 
-void User::allocHungoffUses(unsigned N, bool IsPhi) {
+void User::allocHungoffUses(unsigned N, bool WithExtraValues) {
   assert(HasHungOffUses && "alloc must have hung off uses");
 
-  static_assert(alignof(Use) >= alignof(BasicBlock *),
+  static_assert(alignof(Use) >= alignof(Value *),
                 "Alignment is insufficient for 'hung-off-uses' pieces");
 
   // Allocate the array of Uses
   size_t size = N * sizeof(Use);
-  if (IsPhi)
-    size += N * sizeof(BasicBlock *);
+  if (WithExtraValues)
+    size += N * sizeof(Value *);
   Use *Begin = static_cast<Use*>(::operator new(size));
   Use *End = Begin + N;
   setOperandList(Begin);
@@ -67,7 +67,7 @@ void User::allocHungoffUses(unsigned N, bool IsPhi) {
     new (Begin) Use(this);
 }
 
-void User::growHungoffUses(unsigned NewNumUses, bool IsPhi) {
+void User::growHungoffUses(unsigned NewNumUses, bool WithExtraValues) {
   assert(HasHungOffUses && "realloc must have hung off uses");
 
   unsigned OldNumUses = getNumOperands();
@@ -77,22 +77,22 @@ void User::growHungoffUses(unsigned NewNumUses, bool IsPhi) {
   assert(NewNumUses > OldNumUses && "realloc must grow num uses");
 
   Use *OldOps = getOperandList();
-  allocHungoffUses(NewNumUses, IsPhi);
+  allocHungoffUses(NewNumUses, WithExtraValues);
   Use *NewOps = getOperandList();
 
   // Now copy from the old operands list to the new one.
   std::copy(OldOps, OldOps + OldNumUses, NewOps);
 
-  // If this is a Phi, then we need to copy the BB pointers too.
-  if (IsPhi) {
+  // If the User has extra values (phi basic blocks, switch case values), then
+  // we need to copy these, too.
+  if (WithExtraValues) {
     auto *OldPtr = reinterpret_cast<char *>(OldOps + OldNumUses);
     auto *NewPtr = reinterpret_cast<char *>(NewOps + NewNumUses);
-    std::copy(OldPtr, OldPtr + (OldNumUses * sizeof(BasicBlock *)), NewPtr);
+    std::copy(OldPtr, OldPtr + (OldNumUses * sizeof(Value *)), NewPtr);
   }
   Use::zap(OldOps, OldOps + OldNumUses, true);
 }
 
-
 // This is a private struct used by `User` to track the co-allocated descriptor
 // section.
 struct DescriptorInfo {
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 439b3859fd3ac..778feec6a2da0 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3424,7 +3424,7 @@ void Verifier::visitSwitchInst(SwitchInst &SI) {
   Type *SwitchTy = SI.getCondition()->getType();
   SmallPtrSet<ConstantInt*, 32> Constants;
   for (auto &Case : SI.cases()) {
-    Check(isa<ConstantInt>(SI.getOperand(Case.getCaseIndex() * 2 + 2)),
+    Check(isa<ConstantInt>(Case.getCaseValue()),
           "Case value is not a constant integer.", &SI);
     Check(Case.getCaseValue()->getType() == SwitchTy,
           "Switch constants must all be same type as switch value!", &SI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index eea49bfdaf04b..4081b6b526771 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1539,19 +1539,18 @@ void SPIRVEmitIntrinsics::useRoundingMode(ConstrainedFPIntrinsic *FPI,
 
 Instruction *SPIRVEmitIntrinsics::visitSwitchInst(SwitchInst &I) {
   BasicBlock *ParentBB = I.getParent();
+  Function *F = ParentBB->getParent();
   IRBuilder<> B(ParentBB);
   B.SetInsertPoint(&I);
   SmallVector<Value *, 4> Args;
   SmallVector<BasicBlock *> BBCases;
-  for (auto &Op : I.operands()) {
-    if (Op.get()->getType()->isSized()) {
-      Args.push_back(Op);
-    } else if (BasicBlock *BB = dyn_cast<BasicBlock>(Op.get())) {
-      BBCases.push_back(BB);
-      Args.push_back(BlockAddress::get(BB->getParent(), BB));
-    } else {
-      report_fatal_error("Unexpected switch operand");
-    }
+  Args.push_back(I.getCondition());
+  BBCases.push_back(I.getDefaultDest());
+  Args.push_back(BlockAddress::get(F, I.getDefaultDest()));
+  for (auto &Case : I.cases()) {
+    Args.push_back(Case.getCaseValue());
+    BBCases.push_back(Case.getCaseSuccessor());
+    Args.push_back(BlockAddress::get(F, Case.getCaseSuccessor()));
   }
   CallInst *NewI = B.CreateIntrinsic(Intrinsic::spv_switch,
                                      {I.getOperand(0)->getType()}, {Args});
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index ed2a5c292fa54..a91c2e2119e56 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1900,12 +1900,19 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
     // so does not add any new instructions.
     SmallVector<BasicBlock *> Succs = to_vector(successors(BB));
     // Check if sizes and terminators of all successors match.
-    bool AllSame = none_of(Succs, [&Succs](BasicBlock *Succ) {
+    bool AllSame = all_of(drop_begin(Succs), [&Succs](BasicBlock *Succ) {
       Instruction *Term0 = Succs[0]->getTerminator();
       Instruction *Term = Succ->getTerminator();
-      return !Term->isSameOperationAs(Term0) ||
-             !equal(Term->operands(), Term0->operands()) ||
-             Succs[0]->size() != Succ->size();
+      if (!Term->isSameOperationAs(Term0) || Succs[0]->size() != Succ->size())
+        return false;
+      if (auto *SI = dyn_cast<SwitchInst>(Term)) {
+        // Switch case values also need to be equal.
+        auto *SI0 = cast<SwitchInst>(Term0);
+        for (auto [Case, Case0] : zip(SI->cases(), SI0->cases()))
+          if (Case.getCaseValue() != Case0.getCaseValue())
+            return false;
+      }
+      return equal(Term->operands(), Term0->operands());
     });
     if (!AllSame)
       return false;
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-dedup.ll b/llvm/test/Transforms/SimplifyCFG/switch-dedup.ll
new file mode 100644
index 0000000000000..1a8115e3ca386
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-dedup.ll
@@ -0,0 +1,76 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=simplifycfg -S | FileCheck %s
+
+define i32 @nodedup(i1 %c, i16 %v) {
+; CHECK-LABEL: define i32 @nodedup(
+; CHECK-SAME: i1 [[C:%.*]], i16 [[V:%.*]]) {
+; CHECK-NEXT:    br i1 [[C]], label %[[B1:.*]], label %[[B2:.*]]
+; CHECK:       [[B1]]:
+; CHECK-NEXT:    switch i16 [[V]], label %[[RET2:.*]] [
+; CHECK-NEXT:      i16 1962, label %[[COMMON_RET:.*]]
+; CHECK-NEXT:      i16 2000, label %[[COMMON_RET]]
+; CHECK-NEXT:      i16 1963, label %[[COMMON_RET]]
+; CHECK-NEXT:    ]
+; CHECK:       [[B2]]:
+; CHECK-NEXT:    switch i16 [[V]], label %[[RET2]] [
+; CHECK-NEXT:      i16 2766, label %[[COMMON_RET]]
+; CHECK-NEXT:      i16 2798, label %[[COMMON_RET]]
+; CHECK-NEXT:      i16 2767, label %[[COMMON_RET]]
+; CHECK-NEXT:    ]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    [[COMMON_RET_OP:%.*]] = phi i32 [ 3, %[[RET2]] ], [ 1, %[[B2]] ], [ 1, %[[B2]] ], [ 1, %[[B2]] ], [ 1, %[[B1]] ], [ 1, %[[B1]] ], [ 1, %[[B1]] ]
+; CHECK-NEXT:    ret i32 [[COMMON_RET_OP]]
+; CHECK:       [[RET2]]:
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+;
+  br i1 %c, label %b1, label %b2
+b1:
+  switch i16 %v, label %ret2 [
+  i16 1962, label %ret1
+  i16 2000, label %ret1
+  i16 1963, label %ret1
+  ]
+b2:
+  switch i16 %v, label %ret2 [
+  i16 2766, label %ret1
+  i16 2798, label %ret1
+  i16 2767, label %ret1
+  ]
+ret1:
+  ret i32 1
+ret2:
+  ret i32 3
+}
+
+define i32 @dedup(i1 %c, i16 %v) {
+; CHECK-LABEL: define i32 @dedup(
+; CHECK-SAME: i1 [[C:%.*]], i16 [[V:%.*]]) {
+; CHECK-NEXT:    switch i16 [[V]], label %[[RET2:.*]] [
+; CHECK-NEXT:      i16 2766, label %[[COMMON_RET:.*]]
+; CHECK-NEXT:      i16 2798, label %[[COMMON_RET]]
+; CHECK-NEXT:      i16 2767, label %[[COMMON_RET]]
+; CHECK-NEXT:    ]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    [[COMMON_RET_OP:%.*]] = phi i32 [ 3, %[[RET2]] ], [ 1, [[TMP0:%.*]] ], [ 1, [[TMP0]] ], [ 1, [[TMP0]] ]
+; CHECK-NEXT:    ret i32 [[COMMON_RET_OP]]
+; CHECK:       [[RET2]]:
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+;
+  br i1 %c, label %b1, label %b2
+b1:
+  switch i16 %v, label %ret2 [
+  i16 2766, label %ret1
+  i16 2798, label %ret1
+  i16 2767, label %ret1
+  ]
+b2:
+  switch i16 %v, label %ret2 [
+  i16 2766, label %ret1
+  i16 2798, label %ret1
+  i16 2767, label %ret1
+  ]
+ret1:
+  ret i32 1
+ret2:
+  ret i32 3
+}
diff --git a/llvm/unittests/IR/InstructionsTest.cpp b/llvm/unittests/IR/InstructionsTest.cpp
index f4693bfb1a4d1..f7517a732e7f6 100644
--- a/llvm/unittests/IR/InstructionsTest.cpp
+++ b/llvm/unittests/IR/InstructionsTest.cpp
@@ -968,6 +968,29 @@ TEST(InstructionsTest, SwitchInst) {
   const auto &Handle = *CCI;
   EXPECT_EQ(1, Handle.getCaseValue()->getSExtValue());
   EXPECT_EQ(BB1.get(), Handle.getCaseSuccessor());
+
+  // C API tests.
+  EXPECT_EQ(BB0.get(), unwrap(LLVMGetSwitchDefaultDest(wrap(SI))));
+  EXPECT_EQ(BB0.get(), unwrap(LLVMGetSuccessor(wrap(SI), 0)));
+  EXPECT_EQ(BB1.get(), unwrap(LLVMGetSuccessor(wrap(SI), 1)));
+  EXPECT_EQ(
+      1,
+      unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 1))->getSExtValue());
+  EXPECT_EQ(BB2.get(), unwrap(LLVMGetSuccessor(wrap(SI), 2)));
+  EXPECT_EQ(
+      2,
+      unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 2))->getSExtValue());
+  EXPECT_EQ(BB3.get(), unwrap(LLVMGetSuccessor(wrap(SI), 3)));
+  EXPECT_EQ(
+      3,
+      unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 3))->getSExtValue());
+  // Test case value modification. The C API provides case value indices
+  // matching the successor indices.
+  LLVMSetSwitchCaseValue(wrap(SI), 2, wrap(ConstantInt::get(Int32Ty, 12)));
+  EXPECT_EQ(12, (SI->case_begin() + 1)->getCaseValue()->getSExtValue());
+  EXPECT_EQ(
+      12,
+      unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 2))->getSExtValue());
 }
 
 TEST(InstructionsTest, SwitchInstProfUpdateWrapper) {
diff --git a/llvm/unittests/IR/VerifierTest.cpp b/llvm/unittests/IR/VerifierTest.cpp
index 440db1216edc9..49fad197d12d7 100644
--- a/llvm/unittests/IR/VerifierTest.cpp
+++ b/llvm/unittests/IR/VerifierTest.cpp
@@ -329,9 +329,6 @@ TEST(VerifierTest, SwitchInst) {
   Switch->addCase(ConstantInt::get(Int32Ty, 2), OnTwo);
 
   EXPECT_FALSE(verifyFunction(*F));
-  // set one case value to function argument.
-  Switch->setOperand(2, F->getArg(1));
-  EXPECT_TRUE(verifyFunction(*F));
 }
 
 TEST(VerifierTest, CrossFunctionRef) {

>From 4f5b0f81333acf4c9ad637a3d207cf0e6d0f19e8 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Thu, 11 Dec 2025 15:24:27 +0000
Subject: [PATCH 2/2] move to hasSameSpecialState and adjust FunctionComparator

Created using spr 1.3.5-bogner
---
 llvm/lib/IR/Instruction.cpp                   | 16 ++---
 .../Transforms/Utils/FunctionComparator.cpp   |  6 ++
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 17 +----
 .../Transforms/IPO/MergeFunctionsTest.cpp     | 72 ++++++++++++++++++-
 4 files changed, 87 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index b1fa86aff239e..8f66c6c3a4fc8 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -865,7 +865,7 @@ const char *Instruction::getOpcodeName(unsigned OpCode) {
 }
 
 /// This must be kept in sync with FunctionComparator::cmpOperations in
-/// lib/Transforms/IPO/MergeFunctions.cpp.
+/// lib/Transforms/Utils/FunctionComparator.cpp.
 bool Instruction::hasSameSpecialState(const Instruction *I2,
                                       bool IgnoreAlignment,
                                       bool IntersectAttrs) const {
@@ -913,6 +913,12 @@ bool Instruction::hasSameSpecialState(const Instruction *I2,
     return CI->getCallingConv() == cast<CallBrInst>(I2)->getCallingConv() &&
            CheckAttrsSame(CI, cast<CallBrInst>(I2)) &&
            CI->hasIdenticalOperandBundleSchema(*cast<CallBrInst>(I2));
+  if (const SwitchInst *SI = dyn_cast<SwitchInst>(I1)) {
+    for (auto [Case1, Case2] : zip(SI->cases(), cast<SwitchInst>(I2)->cases()))
+      if (Case1.getCaseValue() != Case2.getCaseValue())
+        return false;
+    return true;
+  }
   if (const InsertValueInst *IVI = dyn_cast<InsertValueInst>(I1))
     return IVI->getIndices() == cast<InsertValueInst>(I2)->getIndices();
   if (const ExtractValueInst *EVI = dyn_cast<ExtractValueInst>(I1))
@@ -975,14 +981,6 @@ bool Instruction::isIdenticalToWhenDefined(const Instruction *I,
     return equal(Phi->blocks(), OtherPhi->blocks());
   }
 
-  if (const SwitchInst *SI = dyn_cast<SwitchInst>(this)) {
-    const SwitchInst *OtherSI = cast<SwitchInst>(I);
-    for (auto [Case, OtherCase] : zip(SI->cases(), OtherSI->cases()))
-      if (Case.getCaseValue() != OtherCase.getCaseValue())
-        return false;
-    return true;
-  }
-
   return this->hasSameSpecialState(I, /*IgnoreAlignment=*/false,
                                    IntersectAttrs);
 }
diff --git a/llvm/lib/Transforms/Utils/FunctionComparator.cpp b/llvm/lib/Transforms/Utils/FunctionComparator.cpp
index 6d4026e8209de..7cf19177db545 100644
--- a/llvm/lib/Transforms/Utils/FunctionComparator.cpp
+++ b/llvm/lib/Transforms/Utils/FunctionComparator.cpp
@@ -724,6 +724,12 @@ int FunctionComparator::cmpOperations(const Instruction *L,
     return cmpMDNode(L->getMetadata(LLVMContext::MD_range),
                      R->getMetadata(LLVMContext::MD_range));
   }
+  if (const SwitchInst *SI = dyn_cast<SwitchInst>(L)) {
+    for (auto [LCase, RCase] : zip(SI->cases(), cast<SwitchInst>(R)->cases()))
+      if (int Res = cmpConstants(LCase.getCaseValue(), RCase.getCaseValue()))
+        return Res;
+    return 0;
+  }
   if (const InsertValueInst *IVI = dyn_cast<InsertValueInst>(L)) {
     ArrayRef<unsigned> LIndices = IVI->getIndices();
     ArrayRef<unsigned> RIndices = cast<InsertValueInst>(R)->getIndices();
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 0389a0444a63c..ad707423f0810 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1904,21 +1904,10 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
     // so does not add any new instructions.
 
     // Check if sizes and terminators of all successors match.
+    Instruction *Term0 = UniqueSuccessors[0]->getTerminator();
     bool AllSame =
-        all_of(UniqueSuccessors, [&UniqueSuccessors](BasicBlock *Succ) {
-          Instruction *Term0 = UniqueSuccessors[0]->getTerminator();
-          Instruction *Term = Succ->getTerminator();
-          if (!Term->isSameOperationAs(Term0) ||
-              UniqueSuccessors[0]->size() != Succ->size())
-            return false;
-          if (auto *SI = dyn_cast<SwitchInst>(Term)) {
-            // Switch case values also need to be equal.
-            auto *SI0 = cast<SwitchInst>(Term0);
-            for (auto [Case, Case0] : zip(SI->cases(), SI0->cases()))
-              if (Case.getCaseValue() != Case0.getCaseValue())
-                return false;
-          }
-          return equal(Term->operands(), Term0->operands());
+        all_of(drop_begin(UniqueSuccessors), [Term0](BasicBlock *Succ) {
+          return Succ->getTerminator()->isIdenticalTo(Term0);
         });
     if (!AllSame)
       return false;
diff --git a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
index 56d119878a9ab..bce7d64fe8d3b 100644
--- a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
+++ b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
@@ -243,4 +243,74 @@ TEST(MergeFunctions, FalseOutputFunctionsTest) {
   EXPECT_EQ(MergeResult.size(), 0u);
 }
 
-} // namespace
\ No newline at end of file
+TEST(MergeFunctions, SwitchMergeTrue) {
+  LLVMContext Ctx;
+  SMDiagnostic Err;
+  std::unique_ptr<Module> M1(parseAssemblyString(R"invalid(
+        @exp = global [2 x ptr] [ptr @fn1, ptr @fn2]
+        define internal i32 @fn1(i32 %a) unnamed_addr {
+            switch i32 %a, label %b1 [
+                i32 1, label %b2
+                i32 2, label %b3
+            ]
+        b1:
+            ret i32 1
+        b2:
+            ret i32 2
+        b3:
+            ret i32 3
+        }
+        define internal i32 @fn2(i32 %a) unnamed_addr {
+            switch i32 %a, label %b1 [
+                i32 1, label %b2
+                i32 2, label %b3
+            ]
+        b1:
+            ret i32 1
+        b2:
+            ret i32 2
+        b3:
+            ret i32 3
+        }
+    )invalid",
+                                                 Err, Ctx));
+
+  EXPECT_TRUE(MergeFunctionsPass::runOnModule(*M1)); // Merged fn1 and fn2.
+}
+
+TEST(MergeFunctions, SwitchMergeFalse) {
+  LLVMContext Ctx;
+  SMDiagnostic Err;
+  std::unique_ptr<Module> M1(parseAssemblyString(R"invalid(
+        @exp = global [2 x ptr] [ptr @fn1, ptr @fn2]
+        define internal i32 @fn1(i32 %a) unnamed_addr {
+            switch i32 %a, label %b1 [
+                i32 1, label %b2
+                i32 2, label %b3
+            ]
+        b1:
+            ret i32 1
+        b2:
+            ret i32 2
+        b3:
+            ret i32 3
+        }
+        define internal i32 @fn2(i32 %a) unnamed_addr {
+            switch i32 %a, label %b1 [
+                i32 1000, label %b2
+                i32 2, label %b3
+            ]
+        b1:
+            ret i32 1
+        b2:
+            ret i32 2
+        b3:
+            ret i32 3
+        }
+    )invalid",
+                                                 Err, Ctx));
+
+  EXPECT_FALSE(MergeFunctionsPass::runOnModule(*M1)); // No merge.
+}
+
+} // namespace



More information about the llvm-commits mailing list