[llvm] [ControlFlowHub] Add support for switch-based guards (PR #213549)

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 2 07:58:53 PDT 2026


https://github.com/hertelukas created https://github.com/llvm/llvm-project/pull/213549

Follow-up on #206567.

Implements switch guards for the `ControlFlowHub`. To route correctly, every outgoing edge is numbered and selected based on the predecessor through a `merged.bb.idx` phi. In the case the incoming BB has two incident edges, the conditional branch is replaced with a `select` instruction, which is then used in the routing phi.

Added switch generation as an option for `UnifyLoopExits` and `FixIrreducible`.

cc @ro-i
Assisted by: Gemini 3.1 Pro for code review

>From 7a22f4927581976ba7826b74419e93a0b026ac74 Mon Sep 17 00:00:00 2001
From: Lukas <dev at lukas-hertel.de>
Date: Sat, 1 Aug 2026 19:57:37 +0200
Subject: [PATCH] [ControlFlowHub] Add support for switch-based guards

Introduce `finalizeAsSwitch` in `ControlFlowHub` to allow generating a
single switch instruction with a PHI node instead of a cascaded branch
sled when routing control flow to multiple outgoing targets.

Expose a `switch-guards` option in the new pass manager for both
`FixIrreducible` and `UnifyLoopExits` passes (defaulting to false).

Assisted by: Gemini 3.1 Pro for code review
---
 .../llvm/Transforms/Utils/ControlFlowUtils.h  |  36 +-
 .../llvm/Transforms/Utils/FixIrreducible.h    |   4 +
 .../llvm/Transforms/Utils/UnifyLoopExits.h    |   3 +
 llvm/lib/Passes/PassBuilder.cpp               |  10 +
 llvm/lib/Passes/PassRegistry.def              |  10 +-
 .../lib/Transforms/Utils/ControlFlowUtils.cpp |  90 +++-
 llvm/lib/Transforms/Utils/FixIrreducible.cpp  |  12 +-
 llvm/lib/Transforms/Utils/UnifyLoopExits.cpp  |  15 +-
 .../FixIrreducible/switch_guards.ll           | 344 ++++++++++++++
 .../UnifyLoopExits/switch_guards.ll           | 428 ++++++++++++++++++
 10 files changed, 924 insertions(+), 28 deletions(-)
 create mode 100644 llvm/test/Transforms/FixIrreducible/switch_guards.ll
 create mode 100644 llvm/test/Transforms/UnifyLoopExits/switch_guards.ll

diff --git a/llvm/include/llvm/Transforms/Utils/ControlFlowUtils.h b/llvm/include/llvm/Transforms/Utils/ControlFlowUtils.h
index e4dab90031e81..93558a1351102 100644
--- a/llvm/include/llvm/Transforms/Utils/ControlFlowUtils.h
+++ b/llvm/include/llvm/Transforms/Utils/ControlFlowUtils.h
@@ -25,11 +25,19 @@ class DomTreeUpdater;
 
 /// Given a set of branch descriptors [BB, Succ0, Succ1], create a "hub" such
 /// that the control flow from each BB to a successor is now split into two
-/// edges, one from BB to the hub and another from the hub to the successor. The
-/// hub consists of a series of guard blocks, one for each outgoing block. Each
-/// guard block conditionally branches to the corresponding outgoing block, or
-/// the next guard block in the chain. These guard blocks are returned in the
-/// argument vector.
+/// edges, one from BB to the hub and another from the hub to the successor.
+/// There are two ways to configure the hub: either as a branch sled or as a
+/// single switch.
+///
+/// For the branch sled case, the hub consists of a series of guard blocks, one
+/// for each outgoing block. Each guard block conditionally branches to the
+/// corresponding outgoing block, or the next guard block in the chain. These
+/// guard blocks are returned in the argument vector `GuardBlocks`.
+///
+/// In the case of a switch guard, the hub consists only of a single guard block
+/// containing a switch instruction that branches to all outgoing blocks (with an
+/// unreachable default destination). `GuardBlocks` will contain this single
+/// guard block.
 ///
 /// This also updates any PHINodes in the successor. For each such PHINode, the
 /// operands corresponding to incoming blocks are moved to a new PHINode in the
@@ -113,14 +121,26 @@ struct ControlFlowHub {
     Branches.emplace_back(BB, Succ0, Succ1);
   }
 
-  /// Return the unified loop exit block and a flag indicating if the CFG was
-  /// changed at all.
+  /// Return the first guard block (or the single switch guard block) and a
+  /// flag indicating if the CFG was changed at all.
   LLVM_ABI std::pair<BasicBlock *, bool>
   finalize(DomTreeUpdater *DTU, SmallVectorImpl<BasicBlock *> &GuardBlocks,
            const StringRef Prefix,
-           std::optional<unsigned> MaxControlFlowBooleans = std::nullopt);
+           std::optional<unsigned> MaxControlFlowBooleans = std::nullopt,
+           bool SwitchGuards = false);
 
   SmallVector<BranchDescriptor> Branches;
+
+private:
+  BasicBlock *finalizeAsBrSled(DomTreeUpdater *DTU,
+                               SmallVectorImpl<BasicBlock *> &GuardBlocks,
+                               const StringRef Prefix,
+                               std::optional<unsigned> MaxControlFlowBooleans,
+                               SetVector<BasicBlock *> &Outgoing);
+  BasicBlock *finalizeAsSwitch(DomTreeUpdater *DTU,
+                               SmallVectorImpl<BasicBlock *> &GuardBlocks,
+                               const StringRef Prefix,
+                               SetVector<BasicBlock *> &Outgoing);
 };
 
 } // end namespace llvm
diff --git a/llvm/include/llvm/Transforms/Utils/FixIrreducible.h b/llvm/include/llvm/Transforms/Utils/FixIrreducible.h
index b10ca1a590a37..84358f00b94b6 100644
--- a/llvm/include/llvm/Transforms/Utils/FixIrreducible.h
+++ b/llvm/include/llvm/Transforms/Utils/FixIrreducible.h
@@ -13,7 +13,11 @@
 
 namespace llvm {
 struct FixIrreduciblePass : OptionalPassInfoMixin<FixIrreduciblePass> {
+
+  FixIrreduciblePass(bool SwitchGuards = false) : SwitchGuards(SwitchGuards) {}
   LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+  bool SwitchGuards;
 };
 } // namespace llvm
 
diff --git a/llvm/include/llvm/Transforms/Utils/UnifyLoopExits.h b/llvm/include/llvm/Transforms/Utils/UnifyLoopExits.h
index c304ff912bb5e..627ff1563d972 100644
--- a/llvm/include/llvm/Transforms/Utils/UnifyLoopExits.h
+++ b/llvm/include/llvm/Transforms/Utils/UnifyLoopExits.h
@@ -15,7 +15,10 @@ namespace llvm {
 
 class UnifyLoopExitsPass : public RequiredPassInfoMixin<UnifyLoopExitsPass> {
 public:
+  UnifyLoopExitsPass(bool SwitchGuards = false) : SwitchGuards(SwitchGuards) {}
   LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+  bool SwitchGuards;
 };
 } // namespace llvm
 
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index f758469dd8ebb..8a222bb1f7d5a 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -968,6 +968,11 @@ Expected<bool> parseDropUnnecessaryAssumesPassOptions(StringRef Params) {
                                             "DropUnnecessaryAssumes");
 }
 
+Expected<bool> parseFixIrreduciblePassOptions(StringRef Params) {
+  return PassBuilder::parseSinglePassOption(Params, "switch-guards",
+                                            "FixIrreducible");
+}
+
 Expected<bool> parseLoopExtractorPassOptions(StringRef Params) {
   return PassBuilder::parseSinglePassOption(Params, "single", "LoopExtractor");
 }
@@ -977,6 +982,11 @@ Expected<bool> parseLowerMatrixIntrinsicsPassOptions(StringRef Params) {
                                             "LowerMatrixIntrinsics");
 }
 
+Expected<bool> parseUnifyLoopExitsPassOptions(StringRef Params) {
+  return PassBuilder::parseSinglePassOption(Params, "switch-guards",
+                                            "UnifyLoopExits");
+}
+
 Expected<IRNormalizerOptions> parseIRNormalizerPassOptions(StringRef Params) {
   IRNormalizerOptions Result;
   while (!Params.empty()) {
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 0955bfdb3246c..b94316424313d 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -452,7 +452,6 @@ FUNCTION_PASS("expand-memcmp", ExpandMemCmpPass())
 FUNCTION_PASS("expand-reductions", ExpandReductionsPass())
 FUNCTION_PASS("extra-vector-passes",
               ExtraFunctionPassManager<ShouldRunExtraVectorPasses>())
-FUNCTION_PASS("fix-irreducible", FixIrreduciblePass())
 FUNCTION_PASS("flatten-cfg", FlattenCFGPass())
 FUNCTION_PASS("float2int", Float2IntPass())
 FUNCTION_PASS("free-machine-function", FreeMachineFunctionPass())
@@ -565,7 +564,6 @@ FUNCTION_PASS("trigger-crash-function", TriggerCrashFunctionPass())
 FUNCTION_PASS("trigger-verifier-error", TriggerVerifierErrorPass())
 FUNCTION_PASS("tsan", ThreadSanitizerPass())
 FUNCTION_PASS("typepromotion", TypePromotionPass(*TM))
-FUNCTION_PASS("unify-loop-exits", UnifyLoopExitsPass())
 FUNCTION_PASS("unreachableblockelim", UnreachableBlockElimPass())
 FUNCTION_PASS("vector-combine", VectorCombinePass())
 FUNCTION_PASS("verify", VerifierPass())
@@ -600,6 +598,10 @@ FUNCTION_PASS_WITH_PARAMS(
     "ee-instrument", "EntryExitInstrumenterPass",
     [](bool PostInlining) { return EntryExitInstrumenterPass(PostInlining); },
     parseEntryExitInstrumenterPassOptions, "post-inline")
+FUNCTION_PASS_WITH_PARAMS(
+    "fix-irreducible", "FixIrreduciblePass",
+    [](bool SwitchGuards) { return FixIrreduciblePass(SwitchGuards); },
+    parseFixIrreduciblePassOptions, "switch-guards")
 FUNCTION_PASS_WITH_PARAMS(
     "func-properties-stats", "FunctionPropertiesStatisticsPass",
     [](bool IsPreOptimizations) {
@@ -737,6 +739,10 @@ FUNCTION_PASS_WITH_PARAMS(
       return StructurizeCFGPass(SkipUniformRegions);
     },
     parseStructurizeCFGPassOptions, "skip-uniform-regions")
+FUNCTION_PASS_WITH_PARAMS(
+    "unify-loop-exits", "UnifyLoopExitsPass",
+    [](bool SwitchGuards) { return UnifyLoopExitsPass(SwitchGuards); },
+    parseUnifyLoopExitsPassOptions, "switch-guards")
 FUNCTION_PASS_WITH_PARAMS(
     "win-eh-prepare", "WinEHPreparePass",
     [](bool DemoteCatchSwitchPHIOnly) {
diff --git a/llvm/lib/Transforms/Utils/ControlFlowUtils.cpp b/llvm/lib/Transforms/Utils/ControlFlowUtils.cpp
index cf3bacc08f5a5..b458c3af44e93 100644
--- a/llvm/lib/Transforms/Utils/ControlFlowUtils.cpp
+++ b/llvm/lib/Transforms/Utils/ControlFlowUtils.cpp
@@ -15,6 +15,7 @@
 #include "llvm/Analysis/DomTreeUpdater.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/Dominators.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/ValueHandle.h"
 #include "llvm/Transforms/Utils/Local.h"
@@ -279,7 +280,8 @@ static void reconnectPhis(BasicBlock *Out, BasicBlock *GuardBlock,
 
 std::pair<BasicBlock *, bool> ControlFlowHub::finalize(
     DomTreeUpdater *DTU, SmallVectorImpl<BasicBlock *> &GuardBlocks,
-    const StringRef Prefix, std::optional<unsigned> MaxControlFlowBooleans) {
+    const StringRef Prefix, std::optional<unsigned> MaxControlFlowBooleans,
+    bool SwitchGuards) {
 #ifndef NDEBUG
   SmallPtrSet<BasicBlock *, 8> Incoming;
 #endif
@@ -287,9 +289,10 @@ std::pair<BasicBlock *, bool> ControlFlowHub::finalize(
 
   for (auto [BB, Succ0, Succ1] : Branches) {
 #ifndef NDEBUG
-    assert(
-        (Incoming.insert(BB).second || isa<CallBrInst>(BB->getTerminator())) &&
-        "Duplicate entry for incoming block.");
+    assert((Incoming.insert(BB).second ||
+            isa<CallBrInst>(BB->getTerminator()) ||
+            isa<SwitchInst>(BB->getTerminator())) &&
+           "Duplicate entry for incoming block.");
 #endif
     if (Succ0)
       Outgoing.insert(Succ0);
@@ -302,6 +305,18 @@ std::pair<BasicBlock *, bool> ControlFlowHub::finalize(
   if (Outgoing.size() < 2)
     return {Outgoing.front(), false};
 
+  if (SwitchGuards && Outgoing.size() > 2)
+    return {finalizeAsSwitch(DTU, GuardBlocks, Prefix, Outgoing), true};
+
+  return {finalizeAsBrSled(DTU, GuardBlocks, Prefix, MaxControlFlowBooleans,
+                           Outgoing),
+          true};
+}
+
+BasicBlock *ControlFlowHub::finalizeAsBrSled(
+    DomTreeUpdater *DTU, SmallVectorImpl<BasicBlock *> &GuardBlocks,
+    const StringRef Prefix, std::optional<unsigned> MaxControlFlowBooleans,
+    SetVector<BasicBlock *> &Outgoing) {
   SmallVector<DominatorTree::UpdateType, 16> Updates;
   if (DTU) {
     for (auto [BB, Succ0, Succ1] : Branches) {
@@ -350,5 +365,70 @@ std::pair<BasicBlock *, bool> ControlFlowHub::finalize(
         Inst->eraseFromParent();
   }
 
-  return {FirstGuardBlock, true};
+  return FirstGuardBlock;
+}
+
+BasicBlock *ControlFlowHub::finalizeAsSwitch(
+    DomTreeUpdater *DTU, SmallVectorImpl<BasicBlock *> &GuardBlocks,
+    const StringRef Prefix, SetVector<BasicBlock *> &Outgoing) {
+
+  SmallVector<DominatorTree::UpdateType, 16> Updates;
+  if (DTU) {
+    for (auto [BB, Succ0, Succ1] : Branches) {
+      if (Succ0)
+        Updates.push_back({DominatorTree::Delete, BB, Succ0});
+      // Only add Succ1 if it's different from Succ0 to avoid duplicate updates
+      if (Succ1 && Succ1 != Succ0)
+        Updates.push_back({DominatorTree::Delete, BB, Succ1});
+    }
+  }
+
+  Function *F = Outgoing.front()->getParent();
+  BasicBlock *Guard = BasicBlock::Create(F->getContext(), Prefix + ".guard", F);
+  GuardBlocks.push_back(Guard);
+  Type *Int32Ty = Type::getInt32Ty(F->getContext());
+  IRBuilder<> Builder(Guard);
+  auto *Phi = Builder.CreatePHI(Int32Ty, Branches.size(), "merged.bb.idx");
+  for (auto [BB, Succ0, Succ1] : Branches) {
+    Value *Condition = redirectToHub(BB, Succ0, Succ1, Guard);
+    Value *IncomingId = nullptr;
+
+    if (Succ0 && Succ1) {
+      Value *Id0 = ConstantInt::get(
+          Int32Ty, std::distance(Outgoing.begin(), find(Outgoing, Succ0)));
+      Value *Id1 = ConstantInt::get(
+          Int32Ty, std::distance(Outgoing.begin(), find(Outgoing, Succ1)));
+      IncomingId = SelectInst::Create(Condition, Id0, Id1, "target.bb.idx",
+                                      BB->getTerminator()->getIterator());
+    } else {
+      auto *Succ = Succ0 ? Succ0 : Succ1;
+      uint64_t Idx = std::distance(Outgoing.begin(), find(Outgoing, Succ));
+      IncomingId = ConstantInt::get(Int32Ty, Idx);
+    }
+
+    Phi->addIncoming(IncomingId, BB);
+  }
+
+  BasicBlock *DefaultDest =
+      BasicBlock::Create(F->getContext(), Prefix + ".guard.default", F);
+  IRBuilder<>(DefaultDest).CreateUnreachable();
+  SwitchInst *SI = Builder.CreateSwitch(Phi, DefaultDest, Outgoing.size());
+  for (int I = 0, E = Outgoing.size(); I != E; ++I) {
+    SI->addCase(Builder.getInt32(I), Outgoing[I]);
+  }
+
+  for (BasicBlock *Out : Outgoing) {
+    reconnectPhis(Out, Guard, Branches, Guard);
+  }
+
+  if (DTU) {
+    for (auto [BB, Succ0, Succ1] : Branches)
+      Updates.push_back({DominatorTree::Insert, BB, Guard});
+
+    for (auto *Outgoing : Outgoing)
+      Updates.push_back({DominatorTree::Insert, Guard, Outgoing});
+    Updates.push_back({DominatorTree::Insert, Guard, DefaultDest});
+    DTU->applyUpdates(Updates);
+  }
+  return Guard;
 }
diff --git a/llvm/lib/Transforms/Utils/FixIrreducible.cpp b/llvm/lib/Transforms/Utils/FixIrreducible.cpp
index 09e9f426dced6..49a8247224e18 100644
--- a/llvm/lib/Transforms/Utils/FixIrreducible.cpp
+++ b/llvm/lib/Transforms/Utils/FixIrreducible.cpp
@@ -272,7 +272,7 @@ static void updateLoopInfo(CycleInfo &CI, LoopInfo &LI, CycleRef C,
 // natural loop. Also insert this new loop at its appropriate place in the
 // hierarchy of loops.
 static bool fixIrreducible(CycleRef C, CycleInfo &CI, DominatorTree &DT,
-                           LoopInfo *LI) {
+                           LoopInfo *LI, bool SwitchGuards) {
   if (CI.isReducible(C))
     return false;
   LLVM_DEBUG(dbgs() << "Processing cycle:\n" << CI.print(C) << "\n";);
@@ -395,7 +395,7 @@ static bool fixIrreducible(CycleRef C, CycleInfo &CI, DominatorTree &DT,
   SetVector<BasicBlock *> Entries;
   Entries.insert(CI.getEntries(C).rbegin(), CI.getEntries(C).rend());
 
-  CHub.finalize(&DTU, GuardBlocks, "irr");
+  CHub.finalize(&DTU, GuardBlocks, "irr", std::nullopt, SwitchGuards);
 #if defined(EXPENSIVE_CHECKS)
   assert(DT.verify(DominatorTree::VerificationLevel::Full));
 #else
@@ -423,13 +423,13 @@ static bool fixIrreducible(CycleRef C, CycleInfo &CI, DominatorTree &DT,
 }
 
 static bool FixIrreducibleImpl(Function &F, CycleInfo &CI, DominatorTree &DT,
-                               LoopInfo *LI) {
+                               LoopInfo *LI, bool SwitchGuards) {
   LLVM_DEBUG(dbgs() << "===== Fix irreducible control-flow in function: "
                     << F.getName() << "\n");
 
   bool Changed = false;
   for (auto C : CI.cycles())
-    Changed |= fixIrreducible(C, CI, DT, LI);
+    Changed |= fixIrreducible(C, CI, DT, LI, SwitchGuards);
 
   if (!Changed)
     return false;
@@ -448,7 +448,7 @@ bool FixIrreducible::runOnFunction(Function &F) {
   LoopInfo *LI = LIWP ? &LIWP->getLoopInfo() : nullptr;
   auto &CI = getAnalysis<CycleInfoWrapperPass>().getResult();
   auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
-  return FixIrreducibleImpl(F, CI, DT, LI);
+  return FixIrreducibleImpl(F, CI, DT, LI, false);
 }
 
 PreservedAnalyses FixIrreduciblePass::run(Function &F,
@@ -457,7 +457,7 @@ PreservedAnalyses FixIrreduciblePass::run(Function &F,
   auto &CI = AM.getResult<CycleAnalysis>(F);
   auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
 
-  if (!FixIrreducibleImpl(F, CI, DT, LI))
+  if (!FixIrreducibleImpl(F, CI, DT, LI, SwitchGuards))
     return PreservedAnalyses::all();
 
   PreservedAnalyses PA;
diff --git a/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp b/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
index b8aba85130368..a0f78a99bc7f7 100644
--- a/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
+++ b/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
@@ -147,7 +147,7 @@ static void restoreSSA(const DominatorTree &DT, const Loop *L,
   }
 }
 
-static bool unifyLoopExits(DominatorTree &DT, LoopInfo &LI, Loop *L) {
+static bool unifyLoopExits(DominatorTree &DT, LoopInfo &LI, Loop *L, bool SwitchGuards) {
   // To unify the loop exits, we need a list of the exiting blocks as
   // well as exit blocks. The functions for locating these lists both
   // traverse the entire loop body. It is more efficient to first
@@ -241,8 +241,9 @@ static bool unifyLoopExits(DominatorTree &DT, LoopInfo &LI, Loop *L) {
   SmallVector<BasicBlock *, 8> GuardBlocks;
   BasicBlock *LoopExitBlock;
   bool ChangedCFG;
-  std::tie(LoopExitBlock, ChangedCFG) = CHub.finalize(
-      &DTU, GuardBlocks, "loop.exit", MaxBooleansInControlFlowHub.getValue());
+  std::tie(LoopExitBlock, ChangedCFG) =
+      CHub.finalize(&DTU, GuardBlocks, "loop.exit",
+                    MaxBooleansInControlFlowHub.getValue(), SwitchGuards);
   ChangedCFG |= Changed;
   if (!ChangedCFG)
     return false;
@@ -281,13 +282,13 @@ static bool unifyLoopExits(DominatorTree &DT, LoopInfo &LI, Loop *L) {
   return true;
 }
 
-static bool runImpl(LoopInfo &LI, DominatorTree &DT) {
+static bool runImpl(LoopInfo &LI, DominatorTree &DT, bool SwitchGuards) {
 
   bool Changed = false;
   auto Loops = LI.getLoopsInPreorder();
   for (auto *L : Loops) {
     LLVM_DEBUG(dbgs() << "Processing loop:\n"; L->print(dbgs()));
-    Changed |= unifyLoopExits(DT, LI, L);
+    Changed |= unifyLoopExits(DT, LI, L, SwitchGuards);
   }
   return Changed;
 }
@@ -298,7 +299,7 @@ bool UnifyLoopExitsLegacyPass::runOnFunction(Function &F) {
   auto &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
   auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
 
-  return runImpl(LI, DT);
+  return runImpl(LI, DT, false);
 }
 
 namespace llvm {
@@ -310,7 +311,7 @@ PreservedAnalyses UnifyLoopExitsPass::run(Function &F,
   auto &LI = AM.getResult<LoopAnalysis>(F);
   auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
 
-  if (!runImpl(LI, DT))
+  if (!runImpl(LI, DT, SwitchGuards))
     return PreservedAnalyses::all();
   PreservedAnalyses PA;
   PA.preserve<LoopAnalysis>();
diff --git a/llvm/test/Transforms/FixIrreducible/switch_guards.ll b/llvm/test/Transforms/FixIrreducible/switch_guards.ll
new file mode 100644
index 0000000000000..836aedfd0f119
--- /dev/null
+++ b/llvm/test/Transforms/FixIrreducible/switch_guards.ll
@@ -0,0 +1,344 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='fix-irreducible<switch-guards>' -S | FileCheck %s --check-prefixes=CHECK,SWITCH
+; RUN: opt < %s -passes='fix-irreducible' -S | FileCheck %s --check-prefixes=CHECK,BOOLEAN
+; RUN: opt < %s -passes='verify<loops>,fix-irreducible<switch-guards>,verify<loops>' -S | FileCheck %s --check-prefixes=CHECK,SWITCH
+
+declare void @use(i32)
+
+define i32 @irreducible_three_entries(i1 %PredEntry, i1 %PredEntry2, i1 %PredA, i1 %PredB, i1 %PredC, i32 %X, i32 %Y, i32 %Z) {
+; SWITCH-LABEL: define i32 @irreducible_three_entries(
+; SWITCH-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDENTRY2:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*]]:
+; SWITCH-NEXT:    br i1 [[PREDENTRY]], label %[[IRR_GUARD:.*]], label %[[ENTRY2:.*]]
+; SWITCH:       [[ENTRY2]]:
+; SWITCH-NEXT:    [[TARGET_BB_IDX:%.*]] = select i1 [[PREDENTRY2]], i32 1, i32 0
+; SWITCH-NEXT:    br label %[[IRR_GUARD]]
+; SWITCH:       [[A:.*]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ [[VALC_MOVED:%.*]], %[[C:.*]] ], [ [[VALA_MOVED:%.*]], %[[IRR_GUARD]] ]
+; SWITCH-NEXT:    [[INCA:%.*]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDA]], label %[[B:.*]], label %[[EXIT:.*]]
+; SWITCH:       [[B]]:
+; SWITCH-NEXT:    [[VALB:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[VALB_MOVED:%.*]], %[[IRR_GUARD]] ]
+; SWITCH-NEXT:    [[INCB:%.*]] = add i32 [[VALB]], 2
+; SWITCH-NEXT:    br i1 [[PREDB]], label %[[IRR_GUARD]], label %[[EXIT]]
+; SWITCH:       [[C]]:
+; SWITCH-NEXT:    [[INCC:%.*]] = add i32 [[VALC_MOVED]], 3
+; SWITCH-NEXT:    br i1 [[PREDC]], label %[[A]], label %[[EXIT]]
+; SWITCH:       [[EXIT]]:
+; SWITCH-NEXT:    [[RES:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[INCB]], %[[B]] ], [ [[INCC]], %[[C]] ]
+; SWITCH-NEXT:    ret i32 [[RES]]
+; SWITCH:       [[IRR_GUARD]]:
+; SWITCH-NEXT:    [[VALA_MOVED]] = phi i32 [ poison, %[[B]] ], [ poison, %[[ENTRY2]] ], [ [[X]], %[[ENTRY]] ]
+; SWITCH-NEXT:    [[VALB_MOVED]] = phi i32 [ poison, %[[B]] ], [ [[Y]], %[[ENTRY2]] ], [ poison, %[[ENTRY]] ]
+; SWITCH-NEXT:    [[VALC_MOVED]] = phi i32 [ [[INCB]], %[[B]] ], [ [[Z]], %[[ENTRY2]] ], [ poison, %[[ENTRY]] ]
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[B]] ], [ [[TARGET_BB_IDX]], %[[ENTRY2]] ], [ 2, %[[ENTRY]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[IRR_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[C]]
+; SWITCH-NEXT:      i32 1, label %[[B]]
+; SWITCH-NEXT:      i32 2, label %[[A]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[IRR_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define i32 @irreducible_three_entries(
+; BOOLEAN-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDENTRY2:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*]]:
+; BOOLEAN-NEXT:    [[PREDENTRY2_INV:%.*]] = xor i1 [[PREDENTRY2]], true
+; BOOLEAN-NEXT:    br i1 [[PREDENTRY]], label %[[IRR_GUARD:.*]], label %[[ENTRY2:.*]]
+; BOOLEAN:       [[ENTRY2]]:
+; BOOLEAN-NEXT:    br label %[[IRR_GUARD]]
+; BOOLEAN:       [[A:.*]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ [[VALC_MOVED:%.*]], %[[C:.*]] ], [ [[VALA_MOVED:%.*]], %[[IRR_GUARD1:.*]] ]
+; BOOLEAN-NEXT:    [[INCA:%.*]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDA]], label %[[B:.*]], label %[[EXIT:.*]]
+; BOOLEAN:       [[B]]:
+; BOOLEAN-NEXT:    [[VALB:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[VALB_MOVED:%.*]], %[[IRR_GUARD1]] ]
+; BOOLEAN-NEXT:    [[INCB:%.*]] = add i32 [[VALB]], 2
+; BOOLEAN-NEXT:    br i1 [[PREDB]], label %[[IRR_GUARD]], label %[[EXIT]]
+; BOOLEAN:       [[C]]:
+; BOOLEAN-NEXT:    [[INCC:%.*]] = add i32 [[VALC_MOVED]], 3
+; BOOLEAN-NEXT:    br i1 [[PREDC]], label %[[A]], label %[[EXIT]]
+; BOOLEAN:       [[EXIT]]:
+; BOOLEAN-NEXT:    [[RES:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[INCB]], %[[B]] ], [ [[INCC]], %[[C]] ]
+; BOOLEAN-NEXT:    ret i32 [[RES]]
+; BOOLEAN:       [[IRR_GUARD]]:
+; BOOLEAN-NEXT:    [[VALA_MOVED]] = phi i32 [ poison, %[[B]] ], [ poison, %[[ENTRY2]] ], [ [[X]], %[[ENTRY]] ]
+; BOOLEAN-NEXT:    [[VALB_MOVED]] = phi i32 [ poison, %[[B]] ], [ [[Y]], %[[ENTRY2]] ], [ poison, %[[ENTRY]] ]
+; BOOLEAN-NEXT:    [[VALC_MOVED]] = phi i32 [ [[INCB]], %[[B]] ], [ [[Z]], %[[ENTRY2]] ], [ poison, %[[ENTRY]] ]
+; BOOLEAN-NEXT:    [[GUARD_C:%.*]] = phi i1 [ true, %[[B]] ], [ [[PREDENTRY2_INV]], %[[ENTRY2]] ], [ false, %[[ENTRY]] ]
+; BOOLEAN-NEXT:    [[GUARD_B:%.*]] = phi i1 [ false, %[[B]] ], [ true, %[[ENTRY2]] ], [ false, %[[ENTRY]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_C]], label %[[C]], label %[[IRR_GUARD1]]
+; BOOLEAN:       [[IRR_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_B]], label %[[B]], label %[[A]]
+;
+entry:
+  br i1 %PredEntry, label %A, label %entry2
+
+entry2:
+  br i1 %PredEntry2, label %B, label %C
+
+A:
+  %valA = phi i32 [ %X, %entry ], [ %valC, %C ]
+  %incA = add i32 %valA, 1
+  br i1 %PredA, label %B, label %exit
+
+B:
+  %valB = phi i32 [ %Y, %entry2 ], [ %incA, %A ]
+  %incB = add i32 %valB, 2
+  br i1 %PredB, label %C, label %exit
+
+C:
+  %valC = phi i32 [ %Z, %entry2 ], [ %incB, %B ]
+  %incC = add i32 %valC, 3
+  br i1 %PredC, label %A, label %exit
+
+exit:
+  %res = phi i32 [ %incA, %A ], [ %incB, %B ], [ %incC, %C ]
+  ret i32 %res
+}
+
+; Irreducible cycle where one predecessor only enters on one branch arm.
+define i32 @irreducible_partial_condition_entry(i1 %PredEntry, i1 %PredEntry1, i1 %PredEntry2, i1 %PredA, i1 %PredB, i1 %PredC, i32 %X, i32 %Y, i32 %Z) {
+; SWITCH-LABEL: define i32 @irreducible_partial_condition_entry(
+; SWITCH-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDENTRY1:%.*]], i1 [[PREDENTRY2:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*:]]
+; SWITCH-NEXT:    br i1 [[PREDENTRY]], label %[[ENTRY1:.*]], label %[[ENTRY2:.*]]
+; SWITCH:       [[ENTRY1]]:
+; SWITCH-NEXT:    br i1 [[PREDENTRY1]], label %[[IRR_GUARD:.*]], label %[[OUTSIDE_EXIT:.*]]
+; SWITCH:       [[ENTRY2]]:
+; SWITCH-NEXT:    [[TARGET_BB_IDX:%.*]] = select i1 [[PREDENTRY2]], i32 1, i32 0
+; SWITCH-NEXT:    br label %[[IRR_GUARD]]
+; SWITCH:       [[A:.*]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ [[VALC_MOVED:%.*]], %[[C:.*]] ], [ [[VALA_MOVED:%.*]], %[[IRR_GUARD]] ]
+; SWITCH-NEXT:    [[INCA:%.*]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDA]], label %[[B:.*]], label %[[EXIT:.*]]
+; SWITCH:       [[B]]:
+; SWITCH-NEXT:    [[VALB:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[VALB_MOVED:%.*]], %[[IRR_GUARD]] ]
+; SWITCH-NEXT:    [[INCB:%.*]] = add i32 [[VALB]], 2
+; SWITCH-NEXT:    br i1 [[PREDB]], label %[[IRR_GUARD]], label %[[EXIT]]
+; SWITCH:       [[C]]:
+; SWITCH-NEXT:    [[INCC:%.*]] = add i32 [[VALC_MOVED]], 3
+; SWITCH-NEXT:    br i1 [[PREDC]], label %[[A]], label %[[EXIT]]
+; SWITCH:       [[EXIT]]:
+; SWITCH-NEXT:    [[RES:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[INCB]], %[[B]] ], [ [[INCC]], %[[C]] ]
+; SWITCH-NEXT:    ret i32 [[RES]]
+; SWITCH:       [[OUTSIDE_EXIT]]:
+; SWITCH-NEXT:    ret i32 0
+; SWITCH:       [[IRR_GUARD]]:
+; SWITCH-NEXT:    [[VALA_MOVED]] = phi i32 [ poison, %[[B]] ], [ poison, %[[ENTRY2]] ], [ [[X]], %[[ENTRY1]] ]
+; SWITCH-NEXT:    [[VALB_MOVED]] = phi i32 [ poison, %[[B]] ], [ [[Y]], %[[ENTRY2]] ], [ poison, %[[ENTRY1]] ]
+; SWITCH-NEXT:    [[VALC_MOVED]] = phi i32 [ [[INCB]], %[[B]] ], [ [[Z]], %[[ENTRY2]] ], [ poison, %[[ENTRY1]] ]
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[B]] ], [ [[TARGET_BB_IDX]], %[[ENTRY2]] ], [ 2, %[[ENTRY1]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[IRR_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[C]]
+; SWITCH-NEXT:      i32 1, label %[[B]]
+; SWITCH-NEXT:      i32 2, label %[[A]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[IRR_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define i32 @irreducible_partial_condition_entry(
+; BOOLEAN-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDENTRY1:%.*]], i1 [[PREDENTRY2:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*:]]
+; BOOLEAN-NEXT:    [[PREDENTRY2_INV:%.*]] = xor i1 [[PREDENTRY2]], true
+; BOOLEAN-NEXT:    br i1 [[PREDENTRY]], label %[[ENTRY1:.*]], label %[[ENTRY2:.*]]
+; BOOLEAN:       [[ENTRY1]]:
+; BOOLEAN-NEXT:    br i1 [[PREDENTRY1]], label %[[IRR_GUARD:.*]], label %[[OUTSIDE_EXIT:.*]]
+; BOOLEAN:       [[ENTRY2]]:
+; BOOLEAN-NEXT:    br label %[[IRR_GUARD]]
+; BOOLEAN:       [[A:.*]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ [[VALC_MOVED:%.*]], %[[C:.*]] ], [ [[VALA_MOVED:%.*]], %[[IRR_GUARD1:.*]] ]
+; BOOLEAN-NEXT:    [[INCA:%.*]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDA]], label %[[B:.*]], label %[[EXIT:.*]]
+; BOOLEAN:       [[B]]:
+; BOOLEAN-NEXT:    [[VALB:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[VALB_MOVED:%.*]], %[[IRR_GUARD1]] ]
+; BOOLEAN-NEXT:    [[INCB:%.*]] = add i32 [[VALB]], 2
+; BOOLEAN-NEXT:    br i1 [[PREDB]], label %[[IRR_GUARD]], label %[[EXIT]]
+; BOOLEAN:       [[C]]:
+; BOOLEAN-NEXT:    [[INCC:%.*]] = add i32 [[VALC_MOVED]], 3
+; BOOLEAN-NEXT:    br i1 [[PREDC]], label %[[A]], label %[[EXIT]]
+; BOOLEAN:       [[EXIT]]:
+; BOOLEAN-NEXT:    [[RES:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[INCB]], %[[B]] ], [ [[INCC]], %[[C]] ]
+; BOOLEAN-NEXT:    ret i32 [[RES]]
+; BOOLEAN:       [[OUTSIDE_EXIT]]:
+; BOOLEAN-NEXT:    ret i32 0
+; BOOLEAN:       [[IRR_GUARD]]:
+; BOOLEAN-NEXT:    [[VALA_MOVED]] = phi i32 [ poison, %[[B]] ], [ poison, %[[ENTRY2]] ], [ [[X]], %[[ENTRY1]] ]
+; BOOLEAN-NEXT:    [[VALB_MOVED]] = phi i32 [ poison, %[[B]] ], [ [[Y]], %[[ENTRY2]] ], [ poison, %[[ENTRY1]] ]
+; BOOLEAN-NEXT:    [[VALC_MOVED]] = phi i32 [ [[INCB]], %[[B]] ], [ [[Z]], %[[ENTRY2]] ], [ poison, %[[ENTRY1]] ]
+; BOOLEAN-NEXT:    [[GUARD_C:%.*]] = phi i1 [ true, %[[B]] ], [ [[PREDENTRY2_INV]], %[[ENTRY2]] ], [ false, %[[ENTRY1]] ]
+; BOOLEAN-NEXT:    [[GUARD_B:%.*]] = phi i1 [ false, %[[B]] ], [ true, %[[ENTRY2]] ], [ false, %[[ENTRY1]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_C]], label %[[C]], label %[[IRR_GUARD1]]
+; BOOLEAN:       [[IRR_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_B]], label %[[B]], label %[[A]]
+;
+entry:
+  br i1 %PredEntry, label %entry1, label %entry2
+
+entry1:
+  br i1 %PredEntry1, label %A, label %outside_exit
+
+entry2:
+  br i1 %PredEntry2, label %B, label %C
+
+A:
+  %valA = phi i32 [ %X, %entry1 ], [ %valC, %C ]
+  %incA = add i32 %valA, 1
+  br i1 %PredA, label %B, label %exit
+
+B:
+  %valB = phi i32 [ %Y, %entry2 ], [ %incA, %A ]
+  %incB = add i32 %valB, 2
+  br i1 %PredB, label %C, label %exit
+
+C:
+  %valC = phi i32 [ %Z, %entry2 ], [ %incB, %B ]
+  %incC = add i32 %valC, 3
+  br i1 %PredC, label %A, label %exit
+
+exit:
+  %res = phi i32 [ %incA, %A ], [ %incB, %B ], [ %incC, %C ]
+  ret i32 %res
+
+outside_exit:
+  ret i32 0
+}
+
+define i32 @irreducible_multiple_preds_same_entry(i1 %PredEntry1, i1 %PredEntry2, i1 %PredEntry3, i1 %PredA, i1 %PredB, i1 %PredC, i32 %X1, i32 %X2, i32 %Y, i32 %Z) {
+; SWITCH-LABEL: define i32 @irreducible_multiple_preds_same_entry(
+; SWITCH-SAME: i1 [[PREDENTRY1:%.*]], i1 [[PREDENTRY2:%.*]], i1 [[PREDENTRY3:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X1:%.*]], i32 [[X2:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*:]]
+; SWITCH-NEXT:    br i1 [[PREDENTRY1]], label %[[ENTRY2:.*]], label %[[ENTRY3:.*]]
+; SWITCH:       [[ENTRY2]]:
+; SWITCH-NEXT:    [[TARGET_BB_IDX1:%.*]] = select i1 [[PREDENTRY2]], i32 1, i32 2
+; SWITCH-NEXT:    br label %[[IRR_GUARD:.*]]
+; SWITCH:       [[ENTRY3]]:
+; SWITCH-NEXT:    [[TARGET_BB_IDX:%.*]] = select i1 [[PREDENTRY3]], i32 1, i32 0
+; SWITCH-NEXT:    br label %[[IRR_GUARD]]
+; SWITCH:       [[A:.*]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ [[VALC_MOVED:%.*]], %[[C:.*]] ], [ [[VALA_MOVED:%.*]], %[[IRR_GUARD]] ]
+; SWITCH-NEXT:    [[INCA:%.*]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDA]], label %[[B:.*]], label %[[EXIT:.*]]
+; SWITCH:       [[B]]:
+; SWITCH-NEXT:    [[VALB:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[VALB_MOVED:%.*]], %[[IRR_GUARD]] ]
+; SWITCH-NEXT:    [[INCB:%.*]] = add i32 [[VALB]], 2
+; SWITCH-NEXT:    br i1 [[PREDB]], label %[[IRR_GUARD]], label %[[EXIT]]
+; SWITCH:       [[C]]:
+; SWITCH-NEXT:    [[INCC:%.*]] = add i32 [[VALC_MOVED]], 3
+; SWITCH-NEXT:    br i1 [[PREDC]], label %[[A]], label %[[EXIT]]
+; SWITCH:       [[EXIT]]:
+; SWITCH-NEXT:    [[RES:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[INCB]], %[[B]] ], [ [[INCC]], %[[C]] ]
+; SWITCH-NEXT:    ret i32 [[RES]]
+; SWITCH:       [[IRR_GUARD]]:
+; SWITCH-NEXT:    [[VALB_MOVED]] = phi i32 [ poison, %[[B]] ], [ poison, %[[ENTRY3]] ], [ [[Y]], %[[ENTRY2]] ]
+; SWITCH-NEXT:    [[VALA_MOVED]] = phi i32 [ poison, %[[B]] ], [ [[X2]], %[[ENTRY3]] ], [ [[X1]], %[[ENTRY2]] ]
+; SWITCH-NEXT:    [[VALC_MOVED]] = phi i32 [ [[INCB]], %[[B]] ], [ [[Z]], %[[ENTRY3]] ], [ poison, %[[ENTRY2]] ]
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[B]] ], [ [[TARGET_BB_IDX]], %[[ENTRY3]] ], [ [[TARGET_BB_IDX1]], %[[ENTRY2]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[IRR_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[C]]
+; SWITCH-NEXT:      i32 1, label %[[A]]
+; SWITCH-NEXT:      i32 2, label %[[B]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[IRR_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define i32 @irreducible_multiple_preds_same_entry(
+; BOOLEAN-SAME: i1 [[PREDENTRY1:%.*]], i1 [[PREDENTRY2:%.*]], i1 [[PREDENTRY3:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X1:%.*]], i32 [[X2:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*:]]
+; BOOLEAN-NEXT:    [[PREDENTRY3_INV:%.*]] = xor i1 [[PREDENTRY3]], true
+; BOOLEAN-NEXT:    br i1 [[PREDENTRY1]], label %[[ENTRY2:.*]], label %[[ENTRY3:.*]]
+; BOOLEAN:       [[ENTRY2]]:
+; BOOLEAN-NEXT:    br label %[[IRR_GUARD:.*]]
+; BOOLEAN:       [[ENTRY3]]:
+; BOOLEAN-NEXT:    br label %[[IRR_GUARD]]
+; BOOLEAN:       [[A:.*]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ [[VALC_MOVED:%.*]], %[[C:.*]] ], [ [[VALA_MOVED:%.*]], %[[IRR_GUARD1:.*]] ]
+; BOOLEAN-NEXT:    [[INCA:%.*]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDA]], label %[[B:.*]], label %[[EXIT:.*]]
+; BOOLEAN:       [[B]]:
+; BOOLEAN-NEXT:    [[VALB:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[VALB_MOVED:%.*]], %[[IRR_GUARD1]] ]
+; BOOLEAN-NEXT:    [[INCB:%.*]] = add i32 [[VALB]], 2
+; BOOLEAN-NEXT:    br i1 [[PREDB]], label %[[IRR_GUARD]], label %[[EXIT]]
+; BOOLEAN:       [[C]]:
+; BOOLEAN-NEXT:    [[INCC:%.*]] = add i32 [[VALC_MOVED]], 3
+; BOOLEAN-NEXT:    br i1 [[PREDC]], label %[[A]], label %[[EXIT]]
+; BOOLEAN:       [[EXIT]]:
+; BOOLEAN-NEXT:    [[RES:%.*]] = phi i32 [ [[INCA]], %[[A]] ], [ [[INCB]], %[[B]] ], [ [[INCC]], %[[C]] ]
+; BOOLEAN-NEXT:    ret i32 [[RES]]
+; BOOLEAN:       [[IRR_GUARD]]:
+; BOOLEAN-NEXT:    [[VALB_MOVED]] = phi i32 [ poison, %[[B]] ], [ poison, %[[ENTRY3]] ], [ [[Y]], %[[ENTRY2]] ]
+; BOOLEAN-NEXT:    [[VALA_MOVED]] = phi i32 [ poison, %[[B]] ], [ [[X2]], %[[ENTRY3]] ], [ [[X1]], %[[ENTRY2]] ]
+; BOOLEAN-NEXT:    [[VALC_MOVED]] = phi i32 [ [[INCB]], %[[B]] ], [ [[Z]], %[[ENTRY3]] ], [ poison, %[[ENTRY2]] ]
+; BOOLEAN-NEXT:    [[GUARD_C:%.*]] = phi i1 [ true, %[[B]] ], [ [[PREDENTRY3_INV]], %[[ENTRY3]] ], [ false, %[[ENTRY2]] ]
+; BOOLEAN-NEXT:    [[GUARD_A:%.*]] = phi i1 [ false, %[[B]] ], [ true, %[[ENTRY3]] ], [ [[PREDENTRY2]], %[[ENTRY2]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_C]], label %[[C]], label %[[IRR_GUARD1]]
+; BOOLEAN:       [[IRR_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_A]], label %[[A]], label %[[B]]
+;
+entry:
+  br i1 %PredEntry1, label %entry2, label %entry3
+
+entry2:
+  br i1 %PredEntry2, label %A, label %B
+
+entry3:
+  br i1 %PredEntry3, label %A, label %C
+
+A:
+  %valA = phi i32 [ %X1, %entry2 ], [ %X2, %entry3 ], [ %valC, %C ]
+  %incA = add i32 %valA, 1
+  br i1 %PredA, label %B, label %exit
+
+B:
+  %valB = phi i32 [ %Y, %entry2 ], [ %incA, %A ]
+  %incB = add i32 %valB, 2
+  br i1 %PredB, label %C, label %exit
+
+C:
+  %valC = phi i32 [ %Z, %entry3 ], [ %incB, %B ]
+  %incC = add i32 %valC, 3
+  br i1 %PredC, label %A, label %exit
+
+exit:
+  %res = phi i32 [ %incA, %A ], [ %incB, %B ], [ %incC, %C ]
+  ret i32 %res
+}
+
+; Fallback to branch sled when cycle entries <= 2.
+define i32 @irreducible_two_entries_fallback(i1 %PredEntry, i1 %PredLeft, i1 %PredRight, i32 %X, i32 %Y) {
+; CHECK-LABEL: define i32 @irreducible_two_entries_fallback(
+; CHECK-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDLEFT:%.*]], i1 [[PREDRIGHT:%.*]], i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PREDENTRY_INV:%.*]] = xor i1 [[PREDENTRY]], true
+; CHECK-NEXT:    br label %[[IRR_GUARD:.*]]
+; CHECK:       [[LEFT:.*]]:
+; CHECK-NEXT:    [[L_PHI:%.*]] = phi i32 [ [[R_PHI_MOVED:%.*]], %[[RIGHT:.*]] ], [ [[L_PHI_MOVED:%.*]], %[[IRR_GUARD]] ]
+; CHECK-NEXT:    [[L:%.*]] = add i32 [[L_PHI]], 1
+; CHECK-NEXT:    br i1 [[PREDLEFT]], label %[[IRR_GUARD]], label %[[EXIT:.*]]
+; CHECK:       [[RIGHT]]:
+; CHECK-NEXT:    br i1 [[PREDRIGHT]], label %[[LEFT]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    [[Z:%.*]] = phi i32 [ [[L]], %[[LEFT]] ], [ [[R_PHI_MOVED]], %[[RIGHT]] ]
+; CHECK-NEXT:    ret i32 [[Z]]
+; CHECK:       [[IRR_GUARD]]:
+; CHECK-NEXT:    [[L_PHI_MOVED]] = phi i32 [ poison, %[[LEFT]] ], [ [[X]], %[[ENTRY]] ]
+; CHECK-NEXT:    [[R_PHI_MOVED]] = phi i32 [ [[L]], %[[LEFT]] ], [ [[Y]], %[[ENTRY]] ]
+; CHECK-NEXT:    [[GUARD_RIGHT:%.*]] = phi i1 [ true, %[[LEFT]] ], [ [[PREDENTRY_INV]], %[[ENTRY]] ]
+; CHECK-NEXT:    br i1 [[GUARD_RIGHT]], label %[[RIGHT]], label %[[LEFT]]
+;
+entry:
+  br i1 %PredEntry, label %left, label %right
+
+left:
+  %L.phi = phi i32 [ %X, %entry ], [ %R.phi, %right ]
+  %L = add i32 %L.phi, 1
+  br i1 %PredLeft, label %right, label %exit
+
+right:
+  %R.phi = phi i32 [ %Y, %entry ], [ %L, %left ]
+  br i1 %PredRight, label %left, label %exit
+
+exit:
+  %Z = phi i32 [ %L, %left ], [ %R.phi, %right ]
+  ret i32 %Z
+}
diff --git a/llvm/test/Transforms/UnifyLoopExits/switch_guards.ll b/llvm/test/Transforms/UnifyLoopExits/switch_guards.ll
new file mode 100644
index 0000000000000..41e1f17302462
--- /dev/null
+++ b/llvm/test/Transforms/UnifyLoopExits/switch_guards.ll
@@ -0,0 +1,428 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='unify-loop-exits<switch-guards>' -S | FileCheck %s --check-prefixes=CHECK,SWITCH
+; RUN: opt < %s -passes='unify-loop-exits' -S | FileCheck %s --check-prefixes=CHECK,BOOLEAN
+; RUN: opt < %s -passes='verify<loops>,unify-loop-exits<switch-guards>,verify<loops>' -S | FileCheck %s --check-prefixes=CHECK,SWITCH
+
+declare void @use(i32)
+
+define void @loop_three_exits(i1 %PredEntry, i1 %PredA, i1 %PredB, i1 %PredC) {
+; SWITCH-LABEL: define void @loop_three_exits(
+; SWITCH-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*]]:
+; SWITCH-NEXT:    br i1 [[PREDENTRY]], label %[[A:.*]], label %[[EXIT1:.*]]
+; SWITCH:       [[A]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[INC:%.*]], %[[C:.*]] ]
+; SWITCH-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B:.*]]
+; SWITCH:       [[B]]:
+; SWITCH-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[C]]
+; SWITCH:       [[C]]:
+; SWITCH-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDC]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; SWITCH:       [[EXIT1]]:
+; SWITCH-NEXT:    call void @use(i32 1)
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[EXIT2:.*]]:
+; SWITCH-NEXT:    call void @use(i32 2)
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[EXIT3:.*]]:
+; SWITCH-NEXT:    call void @use(i32 3)
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[LOOP_EXIT_GUARD]]:
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[A]] ], [ 1, %[[B]] ], [ 2, %[[C]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[LOOP_EXIT_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[EXIT1]]
+; SWITCH-NEXT:      i32 1, label %[[EXIT2]]
+; SWITCH-NEXT:      i32 2, label %[[EXIT3]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[LOOP_EXIT_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define void @loop_three_exits(
+; BOOLEAN-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*]]:
+; BOOLEAN-NEXT:    br i1 [[PREDENTRY]], label %[[A:.*]], label %[[EXIT1:.*]]
+; BOOLEAN:       [[A]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[INC:%.*]], %[[C:.*]] ]
+; BOOLEAN-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B:.*]]
+; BOOLEAN:       [[B]]:
+; BOOLEAN-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[C]]
+; BOOLEAN:       [[C]]:
+; BOOLEAN-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDC]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; BOOLEAN:       [[EXIT1]]:
+; BOOLEAN-NEXT:    call void @use(i32 1)
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[EXIT2:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 2)
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[EXIT3:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 3)
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[LOOP_EXIT_GUARD]]:
+; BOOLEAN-NEXT:    [[GUARD_EXIT1:%.*]] = phi i1 [ true, %[[A]] ], [ false, %[[B]] ], [ false, %[[C]] ]
+; BOOLEAN-NEXT:    [[GUARD_EXIT2:%.*]] = phi i1 [ false, %[[A]] ], [ true, %[[B]] ], [ false, %[[C]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT1]], label %[[EXIT1]], label %[[LOOP_EXIT_GUARD1:.*]]
+; BOOLEAN:       [[LOOP_EXIT_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT2]], label %[[EXIT2]], label %[[EXIT3]]
+;
+entry:
+  br i1 %PredEntry, label %A, label %Exit1
+
+A:
+  %valA = phi i32 [ 0, %entry ], [ %inc, %C ]
+  br i1 %PredA, label %Exit1, label %B
+
+B:
+  br i1 %PredB, label %Exit2, label %C
+
+C:
+  %inc = add i32 %valA, 1
+  br i1 %PredC, label %Exit3, label %A
+
+Exit1:
+  call void @use(i32 1)
+  ret void
+
+Exit2:
+  call void @use(i32 2)
+  ret void
+
+Exit3:
+  call void @use(i32 3)
+  ret void
+}
+
+define void @loop_switch_terminator(i32 %Val, i1 %PredLoop) {
+; SWITCH-LABEL: define void @loop_switch_terminator(
+; SWITCH-SAME: i32 [[VAL:%.*]], i1 [[PREDLOOP:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*]]:
+; SWITCH-NEXT:    br label %[[A:.*]]
+; SWITCH:       [[A]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[INC:%.*]], %[[LOOPBACK:.*]] ]
+; SWITCH-NEXT:    switch i32 [[VAL]], label %[[LOOPBACK]] [
+; SWITCH-NEXT:      i32 0, label %[[A_TARGET_EXIT1:.*]]
+; SWITCH-NEXT:      i32 1, label %[[A_TARGET_EXIT2:.*]]
+; SWITCH-NEXT:      i32 2, label %[[A_TARGET_EXIT3:.*]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[LOOPBACK]]:
+; SWITCH-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDLOOP]], label %[[A]], label %[[LOOP_EXIT_GUARD:.*]]
+; SWITCH:       [[EXIT1:.*]]:
+; SWITCH-NEXT:    call void @use(i32 10)
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[EXIT2:.*]]:
+; SWITCH-NEXT:    call void @use(i32 20)
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[EXIT3:.*]]:
+; SWITCH-NEXT:    call void @use(i32 30)
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[A_TARGET_EXIT1]]:
+; SWITCH-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; SWITCH:       [[A_TARGET_EXIT2]]:
+; SWITCH-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; SWITCH:       [[A_TARGET_EXIT3]]:
+; SWITCH-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; SWITCH:       [[LOOP_EXIT_GUARD]]:
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[A_TARGET_EXIT1]] ], [ 1, %[[A_TARGET_EXIT2]] ], [ 2, %[[A_TARGET_EXIT3]] ], [ 0, %[[LOOPBACK]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[LOOP_EXIT_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[EXIT1]]
+; SWITCH-NEXT:      i32 1, label %[[EXIT2]]
+; SWITCH-NEXT:      i32 2, label %[[EXIT3]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[LOOP_EXIT_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define void @loop_switch_terminator(
+; BOOLEAN-SAME: i32 [[VAL:%.*]], i1 [[PREDLOOP:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*]]:
+; BOOLEAN-NEXT:    br label %[[A:.*]]
+; BOOLEAN:       [[A]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[INC:%.*]], %[[LOOPBACK:.*]] ]
+; BOOLEAN-NEXT:    switch i32 [[VAL]], label %[[LOOPBACK]] [
+; BOOLEAN-NEXT:      i32 0, label %[[A_TARGET_EXIT1:.*]]
+; BOOLEAN-NEXT:      i32 1, label %[[A_TARGET_EXIT2:.*]]
+; BOOLEAN-NEXT:      i32 2, label %[[A_TARGET_EXIT3:.*]]
+; BOOLEAN-NEXT:    ]
+; BOOLEAN:       [[LOOPBACK]]:
+; BOOLEAN-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDLOOP]], label %[[A]], label %[[LOOP_EXIT_GUARD:.*]]
+; BOOLEAN:       [[EXIT1:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 10)
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[EXIT2:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 20)
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[EXIT3:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 30)
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[A_TARGET_EXIT1]]:
+; BOOLEAN-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; BOOLEAN:       [[A_TARGET_EXIT2]]:
+; BOOLEAN-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; BOOLEAN:       [[A_TARGET_EXIT3]]:
+; BOOLEAN-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; BOOLEAN:       [[LOOP_EXIT_GUARD]]:
+; BOOLEAN-NEXT:    [[GUARD_EXIT1:%.*]] = phi i1 [ true, %[[A_TARGET_EXIT1]] ], [ false, %[[A_TARGET_EXIT2]] ], [ false, %[[A_TARGET_EXIT3]] ], [ true, %[[LOOPBACK]] ]
+; BOOLEAN-NEXT:    [[GUARD_EXIT2:%.*]] = phi i1 [ false, %[[A_TARGET_EXIT1]] ], [ true, %[[A_TARGET_EXIT2]] ], [ false, %[[A_TARGET_EXIT3]] ], [ false, %[[LOOPBACK]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT1]], label %[[EXIT1]], label %[[LOOP_EXIT_GUARD1:.*]]
+; BOOLEAN:       [[LOOP_EXIT_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT2]], label %[[EXIT2]], label %[[EXIT3]]
+;
+entry:
+  br label %A
+
+A:
+  %valA = phi i32 [ 0, %entry ], [ %inc, %LoopBack ]
+  switch i32 %Val, label %LoopBack [
+  i32 0, label %Exit1
+  i32 1, label %Exit2
+  i32 2, label %Exit3
+  ]
+
+LoopBack:
+  %inc = add i32 %valA, 1
+  br i1 %PredLoop, label %A, label %Exit1
+
+Exit1:
+  call void @use(i32 10)
+  ret void
+
+Exit2:
+  call void @use(i32 20)
+  ret void
+
+Exit3:
+  call void @use(i32 30)
+  ret void
+}
+
+define void @loop_external_predecessor_to_exit(i1 %EarlyExit, i1 %PredA, i1 %PredB, i1 %PredC) {
+; SWITCH-LABEL: define void @loop_external_predecessor_to_exit(
+; SWITCH-SAME: i1 [[EARLYEXIT:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*]]:
+; SWITCH-NEXT:    br i1 [[EARLYEXIT]], label %[[EXIT1:.*]], label %[[A:.*]]
+; SWITCH:       [[A]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ 100, %[[ENTRY]] ], [ [[INC:%.*]], %[[C:.*]] ]
+; SWITCH-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B:.*]]
+; SWITCH:       [[B]]:
+; SWITCH-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[C]]
+; SWITCH:       [[C]]:
+; SWITCH-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDC]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; SWITCH:       [[EXIT1]]:
+; SWITCH-NEXT:    [[PHI_EXIT1:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[PHI_EXIT1_MOVED:%.*]], %[[LOOP_EXIT_GUARD]] ]
+; SWITCH-NEXT:    call void @use(i32 [[PHI_EXIT1]])
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[EXIT2:.*]]:
+; SWITCH-NEXT:    call void @use(i32 [[VALA_MOVED:%.*]])
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[EXIT3:.*]]:
+; SWITCH-NEXT:    call void @use(i32 [[INC_MOVED:%.*]])
+; SWITCH-NEXT:    ret void
+; SWITCH:       [[LOOP_EXIT_GUARD]]:
+; SWITCH-NEXT:    [[INC_MOVED]] = phi i32 [ poison, %[[A]] ], [ poison, %[[B]] ], [ [[INC]], %[[C]] ]
+; SWITCH-NEXT:    [[VALA_MOVED]] = phi i32 [ [[VALA]], %[[A]] ], [ [[VALA]], %[[B]] ], [ [[VALA]], %[[C]] ]
+; SWITCH-NEXT:    [[PHI_EXIT1_MOVED]] = phi i32 [ [[VALA]], %[[A]] ], [ poison, %[[B]] ], [ poison, %[[C]] ]
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[A]] ], [ 1, %[[B]] ], [ 2, %[[C]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[LOOP_EXIT_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[EXIT1]]
+; SWITCH-NEXT:      i32 1, label %[[EXIT2]]
+; SWITCH-NEXT:      i32 2, label %[[EXIT3]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[LOOP_EXIT_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define void @loop_external_predecessor_to_exit(
+; BOOLEAN-SAME: i1 [[EARLYEXIT:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*]]:
+; BOOLEAN-NEXT:    br i1 [[EARLYEXIT]], label %[[EXIT1:.*]], label %[[A:.*]]
+; BOOLEAN:       [[A]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ 100, %[[ENTRY]] ], [ [[INC:%.*]], %[[C:.*]] ]
+; BOOLEAN-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B:.*]]
+; BOOLEAN:       [[B]]:
+; BOOLEAN-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[C]]
+; BOOLEAN:       [[C]]:
+; BOOLEAN-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDC]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; BOOLEAN:       [[EXIT1]]:
+; BOOLEAN-NEXT:    [[PHI_EXIT1:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[PHI_EXIT1_MOVED:%.*]], %[[LOOP_EXIT_GUARD]] ]
+; BOOLEAN-NEXT:    call void @use(i32 [[PHI_EXIT1]])
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[EXIT2:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 [[VALA_MOVED:%.*]])
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[EXIT3:.*]]:
+; BOOLEAN-NEXT:    call void @use(i32 [[INC_MOVED:%.*]])
+; BOOLEAN-NEXT:    ret void
+; BOOLEAN:       [[LOOP_EXIT_GUARD]]:
+; BOOLEAN-NEXT:    [[INC_MOVED]] = phi i32 [ poison, %[[A]] ], [ poison, %[[B]] ], [ [[INC]], %[[C]] ]
+; BOOLEAN-NEXT:    [[VALA_MOVED]] = phi i32 [ [[VALA]], %[[A]] ], [ [[VALA]], %[[B]] ], [ [[VALA]], %[[C]] ]
+; BOOLEAN-NEXT:    [[PHI_EXIT1_MOVED]] = phi i32 [ [[VALA]], %[[A]] ], [ poison, %[[B]] ], [ poison, %[[C]] ]
+; BOOLEAN-NEXT:    [[GUARD_EXIT1:%.*]] = phi i1 [ true, %[[A]] ], [ false, %[[B]] ], [ false, %[[C]] ]
+; BOOLEAN-NEXT:    [[GUARD_EXIT2:%.*]] = phi i1 [ false, %[[A]] ], [ true, %[[B]] ], [ false, %[[C]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT1]], label %[[EXIT1]], label %[[LOOP_EXIT_GUARD1:.*]]
+; BOOLEAN:       [[LOOP_EXIT_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT2]], label %[[EXIT2]], label %[[EXIT3]]
+;
+entry:
+  br i1 %EarlyExit, label %Exit1, label %A
+
+A:
+  %valA = phi i32 [ 100, %entry ], [ %inc, %C ]
+  br i1 %PredA, label %Exit1, label %B
+
+B:
+  br i1 %PredB, label %Exit2, label %C
+
+C:
+  %inc = add i32 %valA, 1
+  br i1 %PredC, label %Exit3, label %A
+
+Exit1:
+  %phi.exit1 = phi i32 [ 1, %entry ], [ %valA, %A ]
+  call void @use(i32 %phi.exit1)
+  ret void
+
+Exit2:
+  call void @use(i32 %valA)
+  ret void
+
+Exit3:
+  call void @use(i32 %inc)
+  ret void
+}
+
+define i32 @loop_restore_ssa(i1 %PredEntry, i1 %PredA, i1 %PredB, i1 %PredC, i32 %X) {
+; SWITCH-LABEL: define i32 @loop_restore_ssa(
+; SWITCH-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X:%.*]]) {
+; SWITCH-NEXT:  [[ENTRY:.*]]:
+; SWITCH-NEXT:    br label %[[A:.*]]
+; SWITCH:       [[A]]:
+; SWITCH-NEXT:    [[VALA:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[INC:%.*]], %[[C:.*]] ]
+; SWITCH-NEXT:    [[DEF1:%.*]] = add i32 [[VALA]], 5
+; SWITCH-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B:.*]]
+; SWITCH:       [[B]]:
+; SWITCH-NEXT:    [[DEF2:%.*]] = mul i32 [[DEF1]], 2
+; SWITCH-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[C]]
+; SWITCH:       [[C]]:
+; SWITCH-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; SWITCH-NEXT:    br i1 [[PREDC]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; SWITCH:       [[EXIT1:.*]]:
+; SWITCH-NEXT:    ret i32 [[PHI1_MOVED:%.*]]
+; SWITCH:       [[EXIT2:.*]]:
+; SWITCH-NEXT:    ret i32 [[PHI2_MOVED:%.*]]
+; SWITCH:       [[EXIT3:.*]]:
+; SWITCH-NEXT:    ret i32 [[PHI3_MOVED:%.*]]
+; SWITCH:       [[LOOP_EXIT_GUARD]]:
+; SWITCH-NEXT:    [[PHI3_MOVED]] = phi i32 [ poison, %[[A]] ], [ poison, %[[B]] ], [ [[INC]], %[[C]] ]
+; SWITCH-NEXT:    [[PHI2_MOVED]] = phi i32 [ poison, %[[A]] ], [ [[DEF2]], %[[B]] ], [ poison, %[[C]] ]
+; SWITCH-NEXT:    [[PHI1_MOVED]] = phi i32 [ [[DEF1]], %[[A]] ], [ poison, %[[B]] ], [ poison, %[[C]] ]
+; SWITCH-NEXT:    [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, %[[A]] ], [ 1, %[[B]] ], [ 2, %[[C]] ]
+; SWITCH-NEXT:    switch i32 [[MERGED_BB_IDX]], label %[[LOOP_EXIT_GUARD_DEFAULT:.*]] [
+; SWITCH-NEXT:      i32 0, label %[[EXIT1]]
+; SWITCH-NEXT:      i32 1, label %[[EXIT2]]
+; SWITCH-NEXT:      i32 2, label %[[EXIT3]]
+; SWITCH-NEXT:    ]
+; SWITCH:       [[LOOP_EXIT_GUARD_DEFAULT]]:
+; SWITCH-NEXT:    unreachable
+;
+; BOOLEAN-LABEL: define i32 @loop_restore_ssa(
+; BOOLEAN-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i32 [[X:%.*]]) {
+; BOOLEAN-NEXT:  [[ENTRY:.*]]:
+; BOOLEAN-NEXT:    br label %[[A:.*]]
+; BOOLEAN:       [[A]]:
+; BOOLEAN-NEXT:    [[VALA:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[INC:%.*]], %[[C:.*]] ]
+; BOOLEAN-NEXT:    [[DEF1:%.*]] = add i32 [[VALA]], 5
+; BOOLEAN-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B:.*]]
+; BOOLEAN:       [[B]]:
+; BOOLEAN-NEXT:    [[DEF2:%.*]] = mul i32 [[DEF1]], 2
+; BOOLEAN-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[C]]
+; BOOLEAN:       [[C]]:
+; BOOLEAN-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; BOOLEAN-NEXT:    br i1 [[PREDC]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; BOOLEAN:       [[EXIT1:.*]]:
+; BOOLEAN-NEXT:    ret i32 [[PHI1_MOVED:%.*]]
+; BOOLEAN:       [[EXIT2:.*]]:
+; BOOLEAN-NEXT:    ret i32 [[PHI2_MOVED:%.*]]
+; BOOLEAN:       [[EXIT3:.*]]:
+; BOOLEAN-NEXT:    ret i32 [[PHI3_MOVED:%.*]]
+; BOOLEAN:       [[LOOP_EXIT_GUARD]]:
+; BOOLEAN-NEXT:    [[PHI3_MOVED]] = phi i32 [ poison, %[[A]] ], [ poison, %[[B]] ], [ [[INC]], %[[C]] ]
+; BOOLEAN-NEXT:    [[PHI2_MOVED]] = phi i32 [ poison, %[[A]] ], [ [[DEF2]], %[[B]] ], [ poison, %[[C]] ]
+; BOOLEAN-NEXT:    [[PHI1_MOVED]] = phi i32 [ [[DEF1]], %[[A]] ], [ poison, %[[B]] ], [ poison, %[[C]] ]
+; BOOLEAN-NEXT:    [[GUARD_EXIT1:%.*]] = phi i1 [ true, %[[A]] ], [ false, %[[B]] ], [ false, %[[C]] ]
+; BOOLEAN-NEXT:    [[GUARD_EXIT2:%.*]] = phi i1 [ false, %[[A]] ], [ true, %[[B]] ], [ false, %[[C]] ]
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT1]], label %[[EXIT1]], label %[[LOOP_EXIT_GUARD1:.*]]
+; BOOLEAN:       [[LOOP_EXIT_GUARD1]]:
+; BOOLEAN-NEXT:    br i1 [[GUARD_EXIT2]], label %[[EXIT2]], label %[[EXIT3]]
+;
+entry:
+  br label %A
+
+A:
+  %valA = phi i32 [ %X, %entry ], [ %inc, %C ]
+  %def1 = add i32 %valA, 5
+  br i1 %PredA, label %Exit1, label %B
+
+B:
+  %def2 = mul i32 %def1, 2
+  br i1 %PredB, label %Exit2, label %C
+
+C:
+  %inc = add i32 %valA, 1
+  br i1 %PredC, label %Exit3, label %A
+
+Exit1:
+  %phi1 = phi i32 [ %def1, %A ]
+  ret i32 %phi1
+
+Exit2:
+  %phi2 = phi i32 [ %def2, %B ]
+  ret i32 %phi2
+
+Exit3:
+  %phi3 = phi i32 [ %inc, %C ]
+  ret i32 %phi3
+}
+
+; Fallback to branch sled when exit blocks <= 2.
+define void @loop_two_exits_fallback(i1 %PredEntry, i1 %PredA, i1 %PredB) {
+; CHECK-LABEL: define void @loop_two_exits_fallback(
+; CHECK-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDA:%.*]], i1 [[PREDB:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[A:.*]]
+; CHECK:       [[A]]:
+; CHECK-NEXT:    [[VALA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[INC:%.*]], %[[B:.*]] ]
+; CHECK-NEXT:    br i1 [[PREDA]], label %[[LOOP_EXIT_GUARD:.*]], label %[[B]]
+; CHECK:       [[B]]:
+; CHECK-NEXT:    [[INC]] = add i32 [[VALA]], 1
+; CHECK-NEXT:    br i1 [[PREDB]], label %[[LOOP_EXIT_GUARD]], label %[[A]]
+; CHECK:       [[EXIT1:.*]]:
+; CHECK-NEXT:    call void @use(i32 1)
+; CHECK-NEXT:    ret void
+; CHECK:       [[EXIT2:.*]]:
+; CHECK-NEXT:    call void @use(i32 2)
+; CHECK-NEXT:    ret void
+; CHECK:       [[LOOP_EXIT_GUARD]]:
+; CHECK-NEXT:    [[GUARD_EXIT1:%.*]] = phi i1 [ true, %[[A]] ], [ false, %[[B]] ]
+; CHECK-NEXT:    br i1 [[GUARD_EXIT1]], label %[[EXIT1]], label %[[EXIT2]]
+;
+entry:
+  br label %A
+
+A:
+  %valA = phi i32 [ 0, %entry ], [ %inc, %B ]
+  br i1 %PredA, label %Exit1, label %B
+
+B:
+  %inc = add i32 %valA, 1
+  br i1 %PredB, label %Exit2, label %A
+
+Exit1:
+  call void @use(i32 1)
+  ret void
+
+Exit2:
+  call void @use(i32 2)
+  ret void
+}



More information about the llvm-commits mailing list