[llvm] 3f2d8eb - [FixIrreducible][UnifyLoopExits] Fix callbr multiedge splitting (#207598)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 12 05:40:20 PDT 2026


Author: Lukas
Date: 2026-07-12T14:40:15+02:00
New Revision: 3f2d8eb20a8d86d9aeea75a00e21a4ecff0a5234

URL: https://github.com/llvm/llvm-project/commit/3f2d8eb20a8d86d9aeea75a00e21a4ecff0a5234
DIFF: https://github.com/llvm/llvm-project/commit/3f2d8eb20a8d86d9aeea75a00e21a4ecff0a5234.diff

LOG: [FixIrreducible][UnifyLoopExits] Fix callbr multiedge splitting (#207598)

This fixes a bug where splitting `callbr` multiedges corrupts the
successor's PHI nodes.

Originally, the first split edge would replace all incoming edges from
the `callbr` block with the newly introduced target block by using
`replacePhiUsesWith`. Later edges would create new target blocks and
would try to update their edge's PHI as well, but would not find it,
leading to a corrupted PHI node.

To fix this, it is theoretically only necessary to ensure that exactly
one PHI argument is replaced per split edge. However, as all the PHI
nodes must have the same argument for every edge from one predecessor, a
single target block suffices, through which all traffic can be routed,
which will be especially useful for switches, where multiedges are
common.

[Trunk crashing](https://godbolt.org/z/4MM5eMrW5)

Found while working on PR #206567, cc @ro-i.

Added: 
    llvm/test/Transforms/FixIrreducible/multiedge.ll
    llvm/test/Transforms/UnifyLoopExits/multiedge.ll

Modified: 
    llvm/include/llvm/Transforms/Utils/BasicBlockUtils.h
    llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
    llvm/lib/Transforms/Utils/FixIrreducible.cpp
    llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
    llvm/test/Transforms/UnifyLoopExits/basic.ll

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Transforms/Utils/BasicBlockUtils.h b/llvm/include/llvm/Transforms/Utils/BasicBlockUtils.h
index 0e829ecbcc352..3088b96bf7a32 100644
--- a/llvm/include/llvm/Transforms/Utils/BasicBlockUtils.h
+++ b/llvm/include/llvm/Transforms/Utils/BasicBlockUtils.h
@@ -280,6 +280,8 @@ LLVM_ABI BasicBlock *SplitEdge(BasicBlock *From, BasicBlock *To,
 /// \param Succ           original successor block
 /// \param SuccIdx        index of the original successor in the callbr
 ///                       instruction
+/// \param CallBrTarget   optional \p BasicBlock generated by \c SplitCallBrEdge
+///                       to reuse for the split
 /// \param DTU            optional \p DomTreeUpdater for updating the
 ///                       dominator tree
 /// \param CI             optional \p CycleInfo for updating cycle membership
@@ -293,6 +295,7 @@ LLVM_ABI BasicBlock *SplitEdge(BasicBlock *From, BasicBlock *To,
 /// cycle info as needed.
 LLVM_ABI BasicBlock *
 SplitCallBrEdge(BasicBlock *CallBrBlock, BasicBlock *Succ, unsigned SuccIdx,
+                BasicBlock *CallBrTarget = nullptr,
                 DomTreeUpdater *DTU = nullptr, CycleInfo *CI = nullptr,
                 LoopInfo *LI = nullptr, bool *UpdatedLI = nullptr);
 

diff  --git a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
index 6321fc3a5126f..ea4f468418566 100644
--- a/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
+++ b/llvm/lib/Transforms/Utils/BasicBlockUtils.cpp
@@ -735,42 +735,61 @@ static bool updateCycleLoopInfo(TI *LCI, BasicBlock *CallBrBlock,
 }
 
 BasicBlock *llvm::SplitCallBrEdge(BasicBlock *CallBrBlock, BasicBlock *Succ,
-                                  unsigned SuccIdx, DomTreeUpdater *DTU,
-                                  CycleInfo *CI, LoopInfo *LI,
-                                  bool *UpdatedLI) {
+                                  unsigned SuccIdx, BasicBlock *CallBrTarget,
+                                  DomTreeUpdater *DTU, CycleInfo *CI,
+                                  LoopInfo *LI, bool *UpdatedLI) {
   CallBrInst *CallBr = dyn_cast<CallBrInst>(CallBrBlock->getTerminator());
   assert(CallBr && "expected callbr terminator");
   assert(SuccIdx < CallBr->getNumSuccessors() &&
          Succ == CallBr->getSuccessor(SuccIdx) && "invalid successor index");
 
+  if (UpdatedLI)
+    *UpdatedLI = false;
+
+  bool ReusesCallBrTarget = CallBrTarget;
   // Create a new block between callbr and the specified successor.
   // splitBlockBefore cannot be re-used here since it cannot split if the split
   // point is a PHI node (because BasicBlock::splitBasicBlockBefore cannot
   // handle that). But we don't need to rewire every part of a potential PHI
   // node. We only care about the edge between CallBrBlock and the original
   // successor.
-  BasicBlock *CallBrTarget =
-      BasicBlock::Create(CallBrBlock->getContext(),
-                         CallBrBlock->getName() + ".target." + Succ->getName(),
-                         CallBrBlock->getParent());
-  // Rewire control flow from the new target block to the original successor.
-  Succ->replacePhiUsesWith(CallBrBlock, CallBrTarget);
+  if (!ReusesCallBrTarget) {
+    CallBrTarget = BasicBlock::Create(CallBrBlock->getContext(),
+                                      CallBrBlock->getName() + ".target." +
+                                          Succ->getName(),
+                                      CallBrBlock->getParent());
+    // Jump from the new target block to the original successor.
+    UncondBrInst::Create(Succ, CallBrTarget);
+    // Replace a single incoming value with the callbr target block. We cannot
+    // use replacePhiUsesWith, as this would replace the value for every edge
+    // from the callbr block to succ.
+    for (PHINode &PN : Succ->phis()) {
+      int BBIdx = PN.getBasicBlockIndex(CallBrBlock);
+      assert(BBIdx != -1 && "expected incoming value form callbr block");
+      PN.setIncomingBlock(BBIdx, CallBrTarget);
+    }
+
+    bool Updated = updateCycleLoopInfo<LoopInfo, Loop>(LI, CallBrBlock,
+                                                       CallBrTarget, Succ);
+    if (UpdatedLI)
+      *UpdatedLI = Updated;
+    updateCycleLoopInfo<CycleInfo, Cycle>(CI, CallBrBlock, CallBrTarget, Succ);
+  } else {
+    for (PHINode &PN : Succ->phis())
+      PN.removeIncomingValue(CallBrBlock, false);
+  }
+
   // Rewire control flow from callbr to the new target block.
   CallBr->setSuccessor(SuccIdx, CallBrTarget);
-  // Jump from the new target block to the original successor.
-  UncondBrInst::Create(Succ, CallBrTarget);
 
-  bool Updated =
-      updateCycleLoopInfo<LoopInfo, Loop>(LI, CallBrBlock, CallBrTarget, Succ);
-  if (UpdatedLI)
-    *UpdatedLI = Updated;
-  updateCycleLoopInfo<CycleInfo, Cycle>(CI, CallBrBlock, CallBrTarget, Succ);
   if (DTU) {
-    DTU->applyUpdates({{DominatorTree::Insert, CallBrBlock, CallBrTarget}});
+    if (!ReusesCallBrTarget)
+      DTU->applyUpdates({{DominatorTree::Insert, CallBrBlock, CallBrTarget}});
     if (DTU->getDomTree().dominates(CallBrBlock, Succ)) {
       if (!is_contained(successors(CallBrBlock), Succ))
         DTU->applyUpdates({{DominatorTree::Delete, CallBrBlock, Succ}});
-      DTU->applyUpdates({{DominatorTree::Insert, CallBrTarget, Succ}});
+      if (!ReusesCallBrTarget)
+        DTU->applyUpdates({{DominatorTree::Insert, CallBrTarget, Succ}});
     }
   }
 

diff  --git a/llvm/lib/Transforms/Utils/FixIrreducible.cpp b/llvm/lib/Transforms/Utils/FixIrreducible.cpp
index b4f6262f7b309..352d5a854dbaf 100644
--- a/llvm/lib/Transforms/Utils/FixIrreducible.cpp
+++ b/llvm/lib/Transforms/Utils/FixIrreducible.cpp
@@ -129,6 +129,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Utils/FixIrreducible.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/Analysis/CycleAnalysis.h"
 #include "llvm/Analysis/DomTreeUpdater.h"
 #include "llvm/Analysis/LoopInfo.h"
@@ -308,16 +309,18 @@ static bool fixIrreducible(Cycle &C, CycleInfo &CI, DominatorTree &DT,
                         << (Succ0 && Succ1 ? " " : "") << printBasicBlock(Succ1)
                         << '\n');
     } else if (CallBrInst *CallBr = dyn_cast<CallBrInst>(P->getTerminator())) {
+      BasicBlock *NewSucc = nullptr;
       for (unsigned I = 0; I < CallBr->getNumSuccessors(); ++I) {
         BasicBlock *Succ = CallBr->getSuccessor(I);
         if (Succ != Header)
           continue;
-        BasicBlock *NewSucc = SplitCallBrEdge(P, Succ, I, &DTU, &CI, LI);
-        CHub.addBranch(NewSucc, Succ);
+        NewSucc = SplitCallBrEdge(P, Succ, I, NewSucc, &DTU, &CI, LI);
         LLVM_DEBUG(dbgs() << "Added internal branch: "
                           << printBasicBlock(NewSucc) << " -> "
                           << printBasicBlock(Succ) << '\n');
       }
+      if (NewSucc)
+        CHub.addBranch(NewSucc, Header);
     } else {
       reportFatalUsageError("unsupported block terminator: fix-irreducible "
                             "only supports br and callbr instructions");
@@ -353,12 +356,23 @@ static bool fixIrreducible(Cycle &C, CycleInfo &CI, DominatorTree &DT,
                         << (Succ0 && Succ1 ? " " : "") << printBasicBlock(Succ1)
                         << '\n');
     } else if (CallBrInst *CallBr = dyn_cast<CallBrInst>(P->getTerminator())) {
+      SmallDenseMap<BasicBlock *, BasicBlock *> CallBrTargets;
       for (unsigned I = 0; I < CallBr->getNumSuccessors(); ++I) {
         BasicBlock *Succ = CallBr->getSuccessor(I);
         if (!C.contains(Succ))
           continue;
-        BasicBlock *NewSucc = SplitCallBrEdge(P, Succ, I, &DTU, &CI, LI);
-        CHub.addBranch(NewSucc, Succ);
+        auto It = CallBrTargets.find(Succ);
+        BasicBlock *ExistingTarget =
+            (It != CallBrTargets.end()) ? It->second : nullptr;
+
+        BasicBlock *NewSucc =
+            SplitCallBrEdge(P, Succ, I, ExistingTarget, &DTU, &CI, LI);
+
+        if (!ExistingTarget) {
+          CHub.addBranch(NewSucc, Succ);
+          CallBrTargets[Succ] = NewSucc;
+        }
+
         LLVM_DEBUG(dbgs() << "Added external branch: "
                           << printBasicBlock(NewSucc) << " -> "
                           << printBasicBlock(Succ) << '\n');

diff  --git a/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp b/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
index 6947cb4f92723..c31d1dd13e1ea 100644
--- a/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
+++ b/llvm/lib/Transforms/Utils/UnifyLoopExits.cpp
@@ -21,6 +21,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Utils/UnifyLoopExits.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/MapVector.h"
 #include "llvm/Analysis/DomTreeUpdater.h"
 #include "llvm/Analysis/LoopInfo.h"
@@ -165,7 +166,8 @@ static bool unifyLoopExits(DominatorTree &DT, LoopInfo &LI, Loop *L) {
   ControlFlowHub CHub;
   bool Changed = false;
 
-  for (unsigned I = 0; I < ExitingBlocks.size(); ++I) {
+  unsigned NumExitingBlocks = ExitingBlocks.size();
+  for (unsigned I = 0; I < NumExitingBlocks; ++I) {
     BasicBlock *BB = ExitingBlocks[I];
     if (UncondBrInst *Branch = dyn_cast<UncondBrInst>(BB->getTerminator())) {
       BasicBlock *Succ0 = Branch->getSuccessor(0);
@@ -187,28 +189,41 @@ static bool unifyLoopExits(DominatorTree &DT, LoopInfo &LI, Loop *L) {
                         << (Succ0 && Succ1 ? " " : "") << printBasicBlock(Succ1)
                         << '\n');
     } else if (CallBrInst *CallBr = dyn_cast<CallBrInst>(BB->getTerminator())) {
+      SmallDenseMap<BasicBlock *, BasicBlock *> CallBrTargets;
       for (unsigned J = 0; J < CallBr->getNumSuccessors(); ++J) {
         BasicBlock *Succ = CallBr->getSuccessor(J);
         if (L->contains(Succ))
           continue;
-        bool UpdatedLI = false;
-        BasicBlock *NewSucc =
-            SplitCallBrEdge(BB, Succ, J, &DTU, nullptr, &LI, &UpdatedLI);
-        // SplitCallBrEdge modifies the CFG because it creates an intermediate
-        // block. So we need to set the changed flag no matter what the
-        // ControlFlowHub is going to do later.
-        Changed = true;
-        // Even if CallBr and Succ do not have a common parent loop, we need to
-        // add the new target block to the parent loop of the current loop.
-        if (!UpdatedLI)
-          CallBrTargetBlocksToFix.push_back(NewSucc);
-        // ExitingBlocks is later used to restore SSA, so we need to make sure
-        // that the blocks used for phi nodes in the guard blocks match the
-        // predecessors of the guard blocks, which, in the case of callbr, are
-        // the new intermediate target blocks instead of the callbr blocks
-        // themselves.
-        ExitingBlocks[I] = NewSucc;
-        CHub.addBranch(NewSucc, Succ);
+        bool UpdatedLI;
+        auto It = CallBrTargets.find(Succ);
+        BasicBlock *ExistingTarget =
+            (It != CallBrTargets.end()) ? It->second : nullptr;
+        BasicBlock *NewSucc = SplitCallBrEdge(BB, Succ, J, ExistingTarget, &DTU,
+                                              nullptr, &LI, &UpdatedLI);
+
+        if (!ExistingTarget) {
+          // SplitCallBrEdge modifies the CFG because it creates an intermediate
+          // block. So we need to set the changed flag no matter what the
+          // ControlFlowHub is going to do later.
+          Changed = true;
+          // Even if CallBr and Succ do not have a common parent loop, we need
+          // to add the new target block to the parent loop of the current loop.
+          if (!UpdatedLI)
+            CallBrTargetBlocksToFix.push_back(NewSucc);
+          // ExitingBlocks is later used to restore SSA, so we need to make sure
+          // that the blocks used for phi nodes in the guard blocks match the
+          // predecessors of the guard blocks, which, in the case of callbr, are
+          // the new intermediate target blocks instead of the callbr blocks
+          // themselves. If only one exiting block is generated, the callbr
+          // block itself is overwritten, while further blocks are appended as
+          // additional exiting blocks.
+          if (CallBrTargets.empty())
+            ExitingBlocks[I] = NewSucc;
+          else
+            ExitingBlocks.push_back(NewSucc);
+          CHub.addBranch(NewSucc, Succ);
+          CallBrTargets[Succ] = NewSucc;
+        }
         LLVM_DEBUG(dbgs() << "Added exiting branch: "
                           << printBasicBlock(NewSucc) << " -> "
                           << printBasicBlock(Succ) << '\n');

diff  --git a/llvm/test/Transforms/FixIrreducible/multiedge.ll b/llvm/test/Transforms/FixIrreducible/multiedge.ll
new file mode 100644
index 0000000000000..b61bfbdf814d2
--- /dev/null
+++ b/llvm/test/Transforms/FixIrreducible/multiedge.ll
@@ -0,0 +1,95 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -fix-irreducible --verify-loop-info -S | FileCheck %s
+
+define void @multiedge_external_callbr(i1 %Pred) {
+; CHECK-LABEL: define void @multiedge_external_callbr(
+; CHECK-SAME: i1 [[PRED:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    callbr void asm "", "r,!i,!i"(i1 [[PRED]])
+; CHECK-NEXT:            to label %[[ENTRY_TARGET_A:.*]] [label %[[ENTRY_TARGET_A]], label %[[ENTRY_TARGET_B:.*]]]
+; CHECK:       [[A:.*]]:
+; CHECK-NEXT:    [[PHI:%.*]] = phi i32 [ 1, %[[B:.*]] ], [ [[PHI_MOVED:%.*]], %[[IRR_GUARD:.*]] ]
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[B]]:
+; CHECK-NEXT:    br label %[[A]]
+; CHECK:       [[ENTRY_TARGET_A]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[ENTRY_TARGET_B]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[IRR_GUARD]]:
+; CHECK-NEXT:    [[PHI_MOVED]] = phi i32 [ poison, %[[A]] ], [ 0, %[[ENTRY_TARGET_A]] ], [ poison, %[[ENTRY_TARGET_B]] ]
+; CHECK-NEXT:    [[GUARD_B:%.*]] = phi i1 [ true, %[[A]] ], [ false, %[[ENTRY_TARGET_A]] ], [ true, %[[ENTRY_TARGET_B]] ]
+; CHECK-NEXT:    br i1 [[GUARD_B]], label %[[B]], label %[[A]]
+;
+entry:
+  callbr void asm "", "r,!i,!i"(i1 %Pred) to label %a [label %a, label %b]
+
+a:
+  %phi = phi i32 [ 0, %entry ], [ 0, %entry ], [ 1, %b ]
+  br label %b
+
+b:
+  br label %a
+}
+
+define void @multiedge_internal_callbr() {
+; CHECK-LABEL: define void @multiedge_internal_callbr() {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD:.*]]
+; CHECK:       [[A:.*]]:
+; CHECK-NEXT:    callbr void asm "", "!i"()
+; CHECK-NEXT:            to label %[[A_TARGET_B:.*]] [label %[[A_TARGET_B]]]
+; CHECK:       [[B:.*]]:
+; CHECK-NEXT:    br label %[[A]]
+; CHECK:       [[A_TARGET_B]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[IRR_GUARD]]:
+; CHECK-NEXT:    [[PHI_MOVED:%.*]] = phi i1 [ true, %[[A_TARGET_B]] ], [ false, %[[ENTRY]] ]
+; CHECK-NEXT:    [[GUARD_B:%.*]] = phi i1 [ true, %[[A_TARGET_B]] ], [ true, %[[ENTRY]] ]
+; CHECK-NEXT:    br i1 [[GUARD_B]], label %[[B]], label %[[A]]
+;
+entry:
+  br i1 false, label %a, label %b
+
+a:
+  callbr void asm "", "!i"() to label %b [label %b]
+
+b:
+  %phi = phi i1 [ false, %entry ], [ true, %a ], [ true, %a ]
+  br label %a
+}
+
+define void @multiedge_both_callbr() {
+; CHECK-LABEL: define void @multiedge_both_callbr() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    callbr void asm "", "!i,!i"()
+; CHECK-NEXT:            to label %[[ENTRY_TARGET_A:.*]] [label %[[ENTRY_TARGET_A]], label %[[ENTRY_TARGET_B:.*]]]
+; CHECK:       [[A:.*]]:
+; CHECK-NEXT:    [[PHI_A:%.*]] = phi i32 [ 1, %[[B:.*]] ], [ [[PHI_A_MOVED:%.*]], %[[IRR_GUARD:.*]] ]
+; CHECK-NEXT:    callbr void asm "", "!i"()
+; CHECK-NEXT:            to label %[[A_TARGET_B:.*]] [label %[[A_TARGET_B]]]
+; CHECK:       [[B]]:
+; CHECK-NEXT:    br label %[[A]]
+; CHECK:       [[A_TARGET_B]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[ENTRY_TARGET_A]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[ENTRY_TARGET_B]]:
+; CHECK-NEXT:    br label %[[IRR_GUARD]]
+; CHECK:       [[IRR_GUARD]]:
+; CHECK-NEXT:    [[PHI_A_MOVED]] = phi i32 [ poison, %[[A_TARGET_B]] ], [ 0, %[[ENTRY_TARGET_A]] ], [ poison, %[[ENTRY_TARGET_B]] ]
+; CHECK-NEXT:    [[PHI_B_MOVED:%.*]] = phi i32 [ 3, %[[A_TARGET_B]] ], [ poison, %[[ENTRY_TARGET_A]] ], [ 2, %[[ENTRY_TARGET_B]] ]
+; CHECK-NEXT:    [[GUARD_B:%.*]] = phi i1 [ true, %[[A_TARGET_B]] ], [ false, %[[ENTRY_TARGET_A]] ], [ true, %[[ENTRY_TARGET_B]] ]
+; CHECK-NEXT:    br i1 [[GUARD_B]], label %[[B]], label %[[A]]
+;
+entry:
+  callbr void asm "", "!i,!i"() to label %a [label %a, label %b]
+
+a:
+  %phi.a = phi i32 [ 0, %entry ], [ 0, %entry ], [ 1, %b ]
+  callbr void asm "", "!i"() to label %b [label %b]
+
+b:
+  %phi.b = phi i32 [ 2, %entry ], [ 3, %a ], [ 3, %a ]
+  br label %a
+}

diff  --git a/llvm/test/Transforms/UnifyLoopExits/basic.ll b/llvm/test/Transforms/UnifyLoopExits/basic.ll
index a1c4814883c93..5771453574626 100644
--- a/llvm/test/Transforms/UnifyLoopExits/basic.ll
+++ b/llvm/test/Transforms/UnifyLoopExits/basic.ll
@@ -241,13 +241,11 @@ define void @callbr_duplicate_successors() {
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    callbr void asm sideeffect "", "!i,!i"()
-; CHECK-NEXT:            to label [[LOOP_TARGET_EXIT:%.*]] [label [[LOOP]], label [[LOOP_TARGET_EXIT1:%.*]]]
+; CHECK-NEXT:            to label [[LOOP_TARGET_EXIT:%.*]] [label [[LOOP]], label [[LOOP_TARGET_EXIT]]]
 ; CHECK:       exit:
 ; CHECK-NEXT:    ret void
 ; CHECK:       loop.target.exit:
 ; CHECK-NEXT:    br label [[EXIT:%.*]]
-; CHECK:       loop.target.exit1:
-; CHECK-NEXT:    br label [[EXIT]]
 ;
 entry:
   br label %loop

diff  --git a/llvm/test/Transforms/UnifyLoopExits/multiedge.ll b/llvm/test/Transforms/UnifyLoopExits/multiedge.ll
new file mode 100644
index 0000000000000..2061a9aac042e
--- /dev/null
+++ b/llvm/test/Transforms/UnifyLoopExits/multiedge.ll
@@ -0,0 +1,144 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='unify-loop-exits' -S | FileCheck %s
+
+define void @loop_1_callbr(i1 %PredEntry, i1 %PredB, i1 %PredC, i1 %PredD) {
+; CHECK-LABEL: define void @loop_1_callbr(
+; CHECK-SAME: i1 [[PREDENTRY:%.*]], i1 [[PREDB:%.*]], i1 [[PREDC:%.*]], i1 [[PREDD:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br i1 [[PREDENTRY]], label %[[A:.*]], label %[[G:.*]]
+; CHECK:       [[A]]:
+; CHECK-NEXT:    br label %[[B:.*]]
+; CHECK:       [[B]]:
+; CHECK-NEXT:    callbr void asm "", "r,!i,!i"(i1 [[PREDB]])
+; CHECK-NEXT:            to label %[[C:.*]] [label %[[B_TARGET_E:.*]], label %[[B_TARGET_E]]]
+; CHECK:       [[C]]:
+; CHECK-NEXT:    callbr void asm "", "r,!i"(i1 [[PREDC]])
+; CHECK-NEXT:            to label %[[D:.*]] [label %[[C_TARGET_F:.*]]]
+; CHECK:       [[D]]:
+; CHECK-NEXT:    callbr void asm "", "r,!i"(i1 [[PREDD]])
+; CHECK-NEXT:            to label %[[A]] [label %[[D_TARGET_F:.*]]]
+; CHECK:       [[E:.*]]:
+; CHECK-NEXT:    br label %[[EXIT:.*]]
+; CHECK:       [[F:.*]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[G]]:
+; CHECK-NEXT:    br label %[[F]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+; CHECK:       [[B_TARGET_E]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD:.*]]
+; CHECK:       [[C_TARGET_F]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; CHECK:       [[D_TARGET_F]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; CHECK:       [[LOOP_EXIT_GUARD]]:
+; CHECK-NEXT:    [[GUARD_E:%.*]] = phi i1 [ true, %[[B_TARGET_E]] ], [ false, %[[C_TARGET_F]] ], [ false, %[[D_TARGET_F]] ]
+; CHECK-NEXT:    br i1 [[GUARD_E]], label %[[E]], label %[[F]]
+;
+entry:
+  br i1 %PredEntry, label %A, label %G
+
+A:
+  br label %B
+
+B:
+  callbr void asm "", "r,!i,!i"(i1 %PredB) to label %C [label %E, label %E]
+
+C:
+  callbr void asm "", "r,!i"(i1 %PredC) to label %D [label %F]
+
+D:
+  callbr void asm "", "r,!i"(i1 %PredD) to label %A [label %F]
+
+E:
+  br label %exit
+
+F:
+  br label %exit
+
+G:
+  br label %F
+
+exit:
+  ret void
+}
+
+define void @multiedge_both_callbr(i1 %Pred) {
+; CHECK-LABEL: define void @multiedge_both_callbr(
+; CHECK-SAME: i1 [[PRED:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    callbr void asm "", "r,!i"(i1 [[PRED]])
+; CHECK-NEXT:            to label %[[LOOP_NEXT:.*]] [label %[[LOOP_NEXT]]]
+; CHECK:       [[LOOP_NEXT]]:
+; CHECK-NEXT:    [[PHI_INTERNAL:%.*]] = phi i32 [ 1, %[[LOOP]] ], [ 1, %[[LOOP]] ]
+; CHECK-NEXT:    callbr void asm "", "r,!i,!i"(i1 [[PRED]])
+; CHECK-NEXT:            to label %[[LOOP]] [label %[[LOOP_NEXT_TARGET_EXIT:.*]], label %[[LOOP_NEXT_TARGET_EXIT]]]
+; CHECK:       [[EXIT:.*]]:
+; CHECK-NEXT:    [[PHI_EXTERNAL:%.*]] = phi i32 [ 2, %[[LOOP_NEXT_TARGET_EXIT]] ]
+; CHECK-NEXT:    ret void
+; CHECK:       [[LOOP_NEXT_TARGET_EXIT]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+;
+entry:
+  br label %loop
+
+loop:
+  callbr void asm "", "r,!i"(i1 %Pred) to label %loop.next [label %loop.next]
+
+loop.next:
+  %phi.internal = phi i32 [ 1, %loop ], [ 1, %loop ]
+  callbr void asm "", "r,!i,!i"(i1 %Pred) to label %loop [label %exit, label %exit]
+
+exit:
+  %phi.external = phi i32 [ 2, %loop.next ], [ 2, %loop.next ]
+  ret void
+}
+
+define i32 @multiedge_restore_ssa_callbr() {
+; CHECK-LABEL: define i32 @multiedge_restore_ssa_callbr() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[HEADER:.*]]
+; CHECK:       [[HEADER]]:
+; CHECK-NEXT:    [[VAL:%.*]] = add i32 0, 0
+; CHECK-NEXT:    callbr void asm "", "r,!i,!i"(i1 false)
+; CHECK-NEXT:            to label %[[HEADER_TARGET_EXIT1:.*]] [label %[[HEADER_TARGET_COMMON_RET:.*]], label %[[SECOND_EXITING:.*]]]
+; CHECK:       [[SECOND_EXITING]]:
+; CHECK-NEXT:    callbr void asm "", "r,!i,!i"(i1 false)
+; CHECK-NEXT:            to label %[[HEADER]] [label %[[SECOND_EXITING_TARGET_EXIT1:.*]], label %[[SECOND_EXITING_TARGET_COMMON_RET:.*]]]
+; CHECK:       [[COMMON_RET:.*]]:
+; CHECK-NEXT:    ret i32 0
+; CHECK:       [[EXIT1:.*]]:
+; CHECK-NEXT:    [[USE1:%.*]] = or i32 [[VAL_MOVED:%.*]], 0
+; CHECK-NEXT:    ret i32 0
+; CHECK:       [[HEADER_TARGET_EXIT1]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD:.*]]
+; CHECK:       [[HEADER_TARGET_COMMON_RET]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; CHECK:       [[SECOND_EXITING_TARGET_EXIT1]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; CHECK:       [[SECOND_EXITING_TARGET_COMMON_RET]]:
+; CHECK-NEXT:    br label %[[LOOP_EXIT_GUARD]]
+; CHECK:       [[LOOP_EXIT_GUARD]]:
+; CHECK-NEXT:    [[VAL_MOVED]] = phi i32 [ [[VAL]], %[[HEADER_TARGET_EXIT1]] ], [ [[VAL]], %[[SECOND_EXITING_TARGET_EXIT1]] ], [ [[VAL]], %[[HEADER_TARGET_COMMON_RET]] ], [ [[VAL]], %[[SECOND_EXITING_TARGET_COMMON_RET]] ]
+; CHECK-NEXT:    [[GUARD_EXIT1:%.*]] = phi i1 [ true, %[[HEADER_TARGET_EXIT1]] ], [ false, %[[HEADER_TARGET_COMMON_RET]] ], [ true, %[[SECOND_EXITING_TARGET_EXIT1]] ], [ false, %[[SECOND_EXITING_TARGET_COMMON_RET]] ]
+; CHECK-NEXT:    br i1 [[GUARD_EXIT1]], label %[[EXIT1]], label %[[COMMON_RET]]
+;
+entry:
+  br label %header
+
+header:                                           ; preds = %second_exiting, %entry
+  %val = add i32 0, 0
+  callbr void asm "", "r,!i,!i"(i1 false) to label %exit1 [label %common.ret, label %second_exiting]
+
+second_exiting:                                   ; preds = %header
+  callbr void asm "", "r,!i,!i"(i1 false) to label %header [label %exit1, label %common.ret]
+
+common.ret:                                       ; preds = %second_exiting, %header
+  ret i32 0
+
+exit1:                                            ; preds = %second_exiting, %header
+  %use1 = or i32 %val, 0
+  ret i32 0
+}


        


More information about the llvm-commits mailing list