Bug type: crash
Component: CodeMoverUtils
LoopFuse
Bug location: llvm/lib/Transforms/Scalar/LoopFuse.cpp: collectFusionCandidates
dependencesAllowFusion
fuseCandidates
invalidate
isAdjacent
isControlFlowEquivalent
operator()
operator<<
peelFusionCandidate
printFusionCandidates
printLoopVector
reportInvalidCandidate
llvm/lib/Transforms/Utils/CodeMoverUtils.cpp: ControlConditions::isInverse
llvm::isControlFlowEquivalent
llvm::isSafeToMoveBefore
llvm::nonStrictlyPostDominate
reportInvalidCandidate
Issue title: [LoopFusion] Assert "ThisBlock and OtherBlock must be CFG equivalent"
Labels: loopoptim
crash-on-valid
loop-fusion
Reported by: sjoerdmeijer

Compilation of this:

```
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-gnu"

define void @_Z3fn1bis() {
entry:
  br i1 true, label %for.inc68, label %for.cond8thread-pre-split

for.cond8thread-pre-split:                        ; preds = %entry
  br i1 false, label %for.inc68, label %for.inc33

for.inc33:                                        ; preds = %for.inc33, %for.cond8thread-pre-split
  br i1 false, label %for.inc33, label %for.cond52.preheader

for.cond52.preheader:                             ; preds = %for.cond52.preheader, %for.inc33
  br i1 false, label %for.cond52.preheader, label %for.inc68

for.inc68:                                        ; preds = %for.cond52.preheader, %for.cond8thread-pre-split, %entry
  br i1 true, label %for.inc68.2, label %for.cond8thread-pre-split.1

for.cond8thread-pre-split.1:                      ; preds = %for.inc68
  br i1 false, label %for.inc68.2, label %for.inc33.1

for.inc33.1:                                      ; preds = %for.inc33.1, %for.cond8thread-pre-split.1
  br i1 false, label %for.inc33.1, label %for.cond52.preheader.1.preheader

for.cond52.preheader.1.preheader:                 ; preds = %for.inc33.1
  ret void

for.inc68.2:                                      ; preds = %for.cond8thread-pre-split.1, %for.inc68
  ret void
}
```

with `-S -passes=loop-fusion ` results in this crash:

```
opt: /root/llvm-project/llvm/lib/Transforms/Utils/CodeMoverUtils.cpp:453: bool llvm::nonStrictlyPostDominate(const llvm::BasicBlock*, const llvm::BasicBlock*, const llvm::DominatorTree*, const llvm::PostDominatorTree*): Assertion `isControlFlowEquivalent(*ThisBlock, *OtherBlock, *DT, *PDT) && "ThisBlock and OtherBlock must be CFG equivalent!"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
Stack dump:
0.	Program arguments: /opt/compiler-explorer/clang-assertions-trunk/bin/opt -o /app/output.s -S -S -passes=loop-fusion <source>
1.	Running pass "function(loop-fusion)" on module "<source>"
2.	Running pass "loop-fusion" on function "_Z3fn1bis"
 #0 0x0000000005965e58 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x5965e58)
 #1 0x0000000005962d04 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #2 0x0000723070842520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #3 0x00007230708969fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #4 0x0000723070842476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #5 0x00007230708287f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #6 0x000072307082871b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #7 0x0000723070839e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #8 0x0000000004a9c0b8 llvm::nonStrictlyPostDominate(llvm::BasicBlock const*, llvm::BasicBlock const*, llvm::DominatorTree const*, llvm::PostDominatorTree const*) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4a9c0b8)
 #9 0x0000000004686775 std::pair<std::_Rb_tree_iterator<(anonymous namespace)::FusionCandidate>, bool> std::_Rb_tree<(anonymous namespace)::FusionCandidate, (anonymous namespace)::FusionCandidate, std::_Identity<(anonymous namespace)::FusionCandidate>, (anonymous namespace)::FusionCandidateCompare, std::allocator<(anonymous namespace)::FusionCandidate>>::_M_insert_unique<(anonymous namespace)::FusionCandidate const&>((anonymous namespace)::FusionCandidate const&) LoopFuse.cpp:0:0
#10 0x000000000468fe41 (anonymous namespace)::LoopFuser::fuseCandidates() LoopFuse.cpp:0:0
#11 0x0000000004691861 llvm::LoopFusePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4691861)
```

See also https://godbolt.org/z/999r9Ksez

LLVM IR reduced test came from this C code reproducer:

```
#include <algorithm>
int a;
short c, b;
long f, g;
char i;
void fn1(bool d, int e, short h) {
  for (int j = 0; j < 10; j += 4) {
    for (char k; k < i; k += 0301)
      ;
    if (g) {
      for (; f;)
        ;
      for (bool l = 0; l < (bool)b; l = 1) {
        for (long m = 4;
             m < std::max(~d, int((signed char)std::max(h, (short)255))) -
                     18446744073709551608;
             m += 2)
          if (e)
            a = 0;
        for (long n = 4;
             n < std::max(~d, int((signed char)std::max(h, (short)255))) -
                     18446744073709551608;
             n += 2)
          for (short o; o < short(); o += 4)
            c = 0;
      }
    }
  }
}
```


Comments: author: sjoerdmeijer
body: CC: @madhur13490 
author: amehsan
body: I took the liberty of taking a look at this even though it is assigned to @madhur13490 . The reason is that I analyzed a few loop fusion issues yesterday and all of them were CFG related and three of them related to extra flexible `isControlFlowEquivalent`. So did a quick check and this is also related to `isControlFlowEquivalent`.

It is just another instance that we have relaxed control flow condition in one part of the code but not consistently everywhere and so we hit assertions here and there that requires domination, while `isControlFlowEquivalent` is more flexible than that.

I believe fixing `isControlFlowEquivalent` as discussed in https://github.com/llvm/llvm-project/issues/166560 will fix this issue.

Fix patch:
commit 9bc38df587def5e88cfb18ec7f912c9a588c5d4b
Author: Alireza Torabian <alireza.torabian@huawei.com>
Date:   Fri Dec 12 15:09:34 2025 -0500

    [LoopFusion] Simplifying the legality checks (#171889)
    
    Considering that the current loop fusion only supports adjacent loops,
    we are able to simplify the checks in this pass. By removing
    `isControlFlowEquivalent` check, this patch fixes multiple issues
    including #166560, #166535, #165031, #80301 and #168263.
    
    Now only the sequential/adjacent candidates are collected in the same
    list. This patch is the implementation of approach 2 discussed in post
    #171207.

diff --git a/llvm/include/llvm/Transforms/Utils/CodeMoverUtils.h b/llvm/include/llvm/Transforms/Utils/CodeMoverUtils.h
index 877872485ab5..d473f7092f62 100644
--- a/llvm/include/llvm/Transforms/Utils/CodeMoverUtils.h
+++ b/llvm/include/llvm/Transforms/Utils/CodeMoverUtils.h
@@ -27,22 +27,6 @@ class DominatorTree;
 class Instruction;
 class PostDominatorTree;
 
-/// Return true if \p I0 and \p I1 are control flow equivalent.
-/// Two instructions are control flow equivalent if their basic blocks are
-/// control flow equivalent.
-LLVM_ABI bool isControlFlowEquivalent(const Instruction &I0,
-                                      const Instruction &I1,
-                                      const DominatorTree &DT,
-                                      const PostDominatorTree &PDT);
-
-/// Return true if \p BB0 and \p BB1 are control flow equivalent.
-/// Two basic blocks are control flow equivalent if when one executes, the other
-/// is guaranteed to execute.
-LLVM_ABI bool isControlFlowEquivalent(const BasicBlock &BB0,
-                                      const BasicBlock &BB1,
-                                      const DominatorTree &DT,
-                                      const PostDominatorTree &PDT);
-
 /// Return true if \p I can be safely moved before \p InsertPoint.
 LLVM_ABI bool isSafeToMoveBefore(Instruction &I, Instruction &InsertPoint,
                                  DominatorTree &DT,
diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 9ffa602416b0..3a06c3f00fa0 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -64,6 +64,7 @@
 #include "llvm/Transforms/Utils/CodeMoverUtils.h"
 #include "llvm/Transforms/Utils/LoopPeel.h"
 #include "llvm/Transforms/Utils/LoopSimplify.h"
+#include <list>
 
 using namespace llvm;
 
@@ -85,7 +86,6 @@ STATISTIC(InvalidDependencies, "Dependencies prevent fusion");
 STATISTIC(UnknownTripCount, "Loop has unknown trip count");
 STATISTIC(UncomputableTripCount, "SCEV cannot compute trip count of loop");
 STATISTIC(NonEqualTripCount, "Loop trip counts are not the same");
-STATISTIC(NonAdjacent, "Loops are not adjacent");
 STATISTIC(
     NonEmptyPreheader,
     "Loop has a non-empty preheader with instructions that cannot be moved");
@@ -174,10 +174,6 @@ struct FusionCandidate {
   /// Has this loop been Peeled
   bool Peeled;
 
-  /// Dominator and PostDominator trees are needed for the
-  /// FusionCandidateCompare function, required by FusionCandidateSet to
-  /// determine where the FusionCandidate should be inserted into the set. These
-  /// are used to establish ordering of the FusionCandidates based on dominance.
   DominatorTree &DT;
   const PostDominatorTree *PDT;
 
@@ -358,10 +354,10 @@ struct FusionCandidate {
 private:
   // This is only used internally for now, to clear the MemWrites and MemReads
   // list and setting Valid to false. I can't envision other uses of this right
-  // now, since once FusionCandidates are put into the FusionCandidateSet they
+  // now, since once FusionCandidates are put into the FusionCandidateList they
   // are immutable. Thus, any time we need to change/update a FusionCandidate,
-  // we must create a new one and insert it into the FusionCandidateSet to
-  // ensure the FusionCandidateSet remains ordered correctly.
+  // we must create a new one and insert it into the FusionCandidateList to
+  // ensure the FusionCandidateList remains ordered correctly.
   void invalidate() {
     MemWrites.clear();
     MemReads.clear();
@@ -381,86 +377,15 @@ private:
     return false;
   }
 };
-
-struct FusionCandidateCompare {
-  /// Comparison functor to sort two Control Flow Equivalent fusion candidates
-  /// into dominance order.
-  /// If LHS dominates RHS and RHS post-dominates LHS, return true;
-  /// If RHS dominates LHS and LHS post-dominates RHS, return false;
-  /// If both LHS and RHS are not dominating each other then, non-strictly
-  /// post dominate check will decide the order of candidates. If RHS
-  /// non-strictly post dominates LHS then, return true. If LHS non-strictly
-  /// post dominates RHS then, return false. If both are non-strictly post
-  /// dominate each other then, level in the post dominator tree will decide
-  /// the order of candidates.
-  bool operator()(const FusionCandidate &LHS,
-                  const FusionCandidate &RHS) const {
-    const DominatorTree *DT = &(LHS.DT);
-
-    BasicBlock *LHSEntryBlock = LHS.getEntryBlock();
-    BasicBlock *RHSEntryBlock = RHS.getEntryBlock();
-
-    // Do not save PDT to local variable as it is only used in asserts and thus
-    // will trigger an unused variable warning if building without asserts.
-    assert(DT && LHS.PDT && "Expecting valid dominator tree");
-
-    // Do this compare first so if LHS == RHS, function returns false.
-    if (DT->dominates(RHSEntryBlock, LHSEntryBlock)) {
-      // RHS dominates LHS
-      // Verify LHS post-dominates RHS
-      assert(LHS.PDT->dominates(LHSEntryBlock, RHSEntryBlock));
-      return false;
-    }
-
-    if (DT->dominates(LHSEntryBlock, RHSEntryBlock)) {
-      // Verify RHS Postdominates LHS
-      assert(LHS.PDT->dominates(RHSEntryBlock, LHSEntryBlock));
-      return true;
-    }
-
-    // If two FusionCandidates are in the same level of dominator tree,
-    // they will not dominate each other, but may still be control flow
-    // equivalent. To sort those FusionCandidates, nonStrictlyPostDominate()
-    // function is needed.
-    bool WrongOrder =
-        nonStrictlyPostDominate(LHSEntryBlock, RHSEntryBlock, DT, LHS.PDT);
-    bool RightOrder =
-        nonStrictlyPostDominate(RHSEntryBlock, LHSEntryBlock, DT, LHS.PDT);
-    if (WrongOrder && RightOrder) {
-      // If common predecessor of LHS and RHS post dominates both
-      // FusionCandidates then, Order of FusionCandidate can be
-      // identified by its level in post dominator tree.
-      DomTreeNode *LNode = LHS.PDT->getNode(LHSEntryBlock);
-      DomTreeNode *RNode = LHS.PDT->getNode(RHSEntryBlock);
-      return LNode->getLevel() > RNode->getLevel();
-    } else if (WrongOrder)
-      return false;
-    else if (RightOrder)
-      return true;
-
-    // If LHS does not non-strict Postdominate RHS and RHS does not non-strict
-    // Postdominate LHS then, there is no dominance relationship between the
-    // two FusionCandidates. Thus, they should not be in the same set together.
-    llvm_unreachable(
-        "No dominance relationship between these fusion candidates!");
-  }
-};
 } // namespace
 
 using LoopVector = SmallVector<Loop *, 4>;
 
-// Set of Control Flow Equivalent (CFE) Fusion Candidates, sorted in dominance
-// order. Thus, if FC0 comes *before* FC1 in a FusionCandidateSet, then FC0
-// dominates FC1 and FC1 post-dominates FC0.
-// std::set was chosen because we want a sorted data structure with stable
-// iterators. A subsequent patch to loop fusion will enable fusing non-adjacent
-// loops by moving intervening code around. When this intervening code contains
-// loops, those loops will be moved also. The corresponding FusionCandidates
-// will also need to be moved accordingly. As this is done, having stable
-// iterators will simplify the logic. Similarly, having an efficient insert that
-// keeps the FusionCandidateSet sorted will also simplify the implementation.
-using FusionCandidateSet = std::set<FusionCandidate, FusionCandidateCompare>;
-using FusionCandidateCollection = SmallVector<FusionCandidateSet, 4>;
+// List of adjacent fusion candidates in order. Thus, if FC0 comes *before* FC1
+// in a FusionCandidateList, then FC0 dominates FC1, FC1 post-dominates FC0,
+// and they are adjacent.
+using FusionCandidateList = std::list<FusionCandidate>;
+using FusionCandidateCollection = SmallVector<FusionCandidateList, 4>;
 
 #ifndef NDEBUG
 static void printLoopVector(const LoopVector &LV) {
@@ -480,8 +405,8 @@ static raw_ostream &operator<<(raw_ostream &OS, const FusionCandidate &FC) {
 }
 
 static raw_ostream &operator<<(raw_ostream &OS,
-                               const FusionCandidateSet &CandSet) {
-  for (const FusionCandidate &FC : CandSet)
+                               const FusionCandidateList &CandList) {
+  for (const FusionCandidate &FC : CandList)
     OS << FC << '\n';
 
   return OS;
@@ -490,9 +415,9 @@ static raw_ostream &operator<<(raw_ostream &OS,
 static void
 printFusionCandidates(const FusionCandidateCollection &FusionCandidates) {
   dbgs() << "Fusion Candidates: \n";
-  for (const auto &CandidateSet : FusionCandidates) {
-    dbgs() << "*** Fusion Candidate Set ***\n";
-    dbgs() << CandidateSet;
+  for (const auto &CandidateList : FusionCandidates) {
+    dbgs() << "*** Fusion Candidate List ***\n";
+    dbgs() << CandidateList;
     dbgs() << "****************************\n";
   }
 }
@@ -648,20 +573,6 @@ public:
   }
 
 private:
-  /// Determine if two fusion candidates are control flow equivalent.
-  ///
-  /// Two fusion candidates are control flow equivalent if when one executes,
-  /// the other is guaranteed to execute. This is determined using dominators
-  /// and post-dominators: if A dominates B and B post-dominates A then A and B
-  /// are control-flow equivalent.
-  bool isControlFlowEquivalent(const FusionCandidate &FC0,
-                               const FusionCandidate &FC1) const {
-    assert(FC0.Preheader && FC1.Preheader && "Expecting valid preheaders");
-
-    return ::isControlFlowEquivalent(*FC0.getEntryBlock(), *FC1.getEntryBlock(),
-                                     DT, PDT);
-  }
-
   /// Iterate over all loops in the given loop set and identify the loops that
   /// are eligible for fusion. Place all eligible fusion candidates into Control
   /// Flow Equivalent sets, sorted by dominance.
@@ -673,34 +584,42 @@ private:
       if (!CurrCand.isEligibleForFusion(SE))
         continue;
 
-      // Go through each list in FusionCandidates and determine if L is control
-      // flow equivalent with the first loop in that list. If it is, append LV.
+      // Go through each list in FusionCandidates and determine if the first or
+      // last loop in the list is strictly adjacent to L. If it is, append L.
       // If not, go to the next list.
       // If no suitable list is found, start another list and add it to
       // FusionCandidates.
-      bool FoundSet = false;
-
-      for (auto &CurrCandSet : FusionCandidates) {
-        if (isControlFlowEquivalent(*CurrCandSet.begin(), CurrCand)) {
-          CurrCandSet.insert(CurrCand);
-          FoundSet = true;
+      bool FoundAdjacent = false;
+      for (auto &CurrCandList : FusionCandidates) {
+        if (isStrictlyAdjacent(CurrCand, CurrCandList.front())) {
+          CurrCandList.push_front(CurrCand);
+          FoundAdjacent = true;
 #ifndef NDEBUG
           if (VerboseFusionDebugging)
             LLVM_DEBUG(dbgs() << "Adding " << CurrCand
-                              << " to existing candidate set\n");
+                              << " to existing candidate list\n");
+#endif
+          break;
+        } else if (isStrictlyAdjacent(CurrCandList.back(), CurrCand)) {
+          CurrCandList.push_back(CurrCand);
+          FoundAdjacent = true;
+#ifndef NDEBUG
+          if (VerboseFusionDebugging)
+            LLVM_DEBUG(dbgs() << "Adding " << CurrCand
+                              << " to existing candidate list\n");
 #endif
           break;
         }
       }
-      if (!FoundSet) {
-        // No set was found. Create a new set and add to FusionCandidates
+      if (!FoundAdjacent) {
+        // No list was found. Create a new list and add to FusionCandidates
 #ifndef NDEBUG
         if (VerboseFusionDebugging)
-          LLVM_DEBUG(dbgs() << "Adding " << CurrCand << " to new set\n");
+          LLVM_DEBUG(dbgs() << "Adding " << CurrCand << " to new list\n");
 #endif
-        FusionCandidateSet NewCandSet;
-        NewCandSet.insert(CurrCand);
-        FusionCandidates.push_back(NewCandSet);
+        FusionCandidateList NewCandList;
+        NewCandList.push_back(CurrCand);
+        FusionCandidates.push_back(NewCandList);
       }
       NumFusionCandidates++;
     }
@@ -849,218 +768,205 @@ private:
     }
   }
 
-  /// Walk each set of control flow equivalent fusion candidates and attempt to
-  /// fuse them. This does a single linear traversal of all candidates in the
-  /// set. The conditions for legal fusion are checked at this point. If a pair
-  /// of fusion candidates passes all legality checks, they are fused together
-  /// and a new fusion candidate is created and added to the FusionCandidateSet.
+  /// Walk each set of strictly adjacent fusion candidates and attempt to fuse
+  /// them. This does a single linear traversal of all candidates in the list.
+  /// The conditions for legal fusion are checked at this point. If a pair of
+  /// fusion candidates passes all legality checks, they are fused together and
+  /// a new fusion candidate is created and added to the FusionCandidateList.
   /// The original fusion candidates are then removed, as they are no longer
   /// valid.
   bool fuseCandidates() {
     bool Fused = false;
     LLVM_DEBUG(printFusionCandidates(FusionCandidates));
-    for (auto &CandidateSet : FusionCandidates) {
-      if (CandidateSet.size() < 2)
+    for (auto &CandidateList : FusionCandidates) {
+      if (CandidateList.size() < 2)
         continue;
 
-      LLVM_DEBUG(dbgs() << "Attempting fusion on Candidate Set:\n"
-                        << CandidateSet << "\n");
-
-      for (auto FC0 = CandidateSet.begin(); FC0 != CandidateSet.end(); ++FC0) {
-        assert(!LDT.isRemovedLoop(FC0->L) &&
-               "Should not have removed loops in CandidateSet!");
-        auto FC1 = FC0;
-        for (++FC1; FC1 != CandidateSet.end(); ++FC1) {
-          assert(!LDT.isRemovedLoop(FC1->L) &&
-                 "Should not have removed loops in CandidateSet!");
-
-          LLVM_DEBUG(dbgs() << "Attempting to fuse candidate \n"; FC0->dump();
-                     dbgs() << " with\n"; FC1->dump(); dbgs() << "\n");
-
-          FC0->verify();
-          FC1->verify();
-
-          // Check if the candidates have identical tripcounts (first value of
-          // pair), and if not check the difference in the tripcounts between
-          // the loops (second value of pair). The difference is not equal to
-          // std::nullopt iff the loops iterate a constant number of times, and
-          // have a single exit.
-          std::pair<bool, std::optional<unsigned>> IdenticalTripCountRes =
-              haveIdenticalTripCounts(*FC0, *FC1);
-          bool SameTripCount = IdenticalTripCountRes.first;
-          std::optional<unsigned> TCDifference = IdenticalTripCountRes.second;
-
-          // Here we are checking that FC0 (the first loop) can be peeled, and
-          // both loops have different tripcounts.
-          if (FC0->AbleToPeel && !SameTripCount && TCDifference) {
-            if (*TCDifference > FusionPeelMaxCount) {
-              LLVM_DEBUG(dbgs()
-                         << "Difference in loop trip counts: " << *TCDifference
-                         << " is greater than maximum peel count specificed: "
-                         << FusionPeelMaxCount << "\n");
-            } else {
-              // Dependent on peeling being performed on the first loop, and
-              // assuming all other conditions for fusion return true.
-              SameTripCount = true;
-            }
-          }
+      LLVM_DEBUG(dbgs() << "Attempting fusion on Candidate List:\n"
+                        << CandidateList << "\n");
 
-          if (!SameTripCount) {
-            LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical trip "
-                                 "counts. Not fusing.\n");
-            reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                       NonEqualTripCount);
-            continue;
-          }
+      for (auto It = CandidateList.begin(), NextIt = std::next(It);
+           NextIt != CandidateList.end(); It = NextIt, NextIt = std::next(It)) {
 
-          if (!isAdjacent(*FC0, *FC1)) {
-            LLVM_DEBUG(dbgs()
-                       << "Fusion candidates are not adjacent. Not fusing.\n");
-            reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1, NonAdjacent);
-            continue;
-          }
+        auto FC0 = *It;
+        auto FC1 = *NextIt;
 
-          if ((!FC0->GuardBranch && FC1->GuardBranch) ||
-              (FC0->GuardBranch && !FC1->GuardBranch)) {
-            LLVM_DEBUG(dbgs() << "The one of candidate is guarded while the "
-                                 "another one is not. Not fusing.\n");
-            reportLoopFusion<OptimizationRemarkMissed>(
-                *FC0, *FC1, OnlySecondCandidateIsGuarded);
-            continue;
-          }
+        assert(!LDT.isRemovedLoop(FC0.L) &&
+               "Should not have removed loops in CandidateList!");
+        assert(!LDT.isRemovedLoop(FC1.L) &&
+               "Should not have removed loops in CandidateList!");
 
-          // Ensure that FC0 and FC1 have identical guards.
-          // If one (or both) are not guarded, this check is not necessary.
-          if (FC0->GuardBranch && FC1->GuardBranch &&
-              !haveIdenticalGuards(*FC0, *FC1) && !TCDifference) {
-            LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical "
-                                 "guards. Not Fusing.\n");
-            reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                       NonIdenticalGuards);
-            continue;
-          }
+        LLVM_DEBUG(dbgs() << "Attempting to fuse candidate \n"; FC0.dump();
+                   dbgs() << " with\n"; FC1.dump(); dbgs() << "\n");
 
-          if (FC0->GuardBranch) {
-            assert(FC1->GuardBranch && "Expecting valid FC1 guard branch");
-
-            if (!isSafeToMoveBefore(*FC0->ExitBlock,
-                                    *FC1->ExitBlock->getFirstNonPHIOrDbg(), DT,
-                                    &PDT, &DI)) {
-              LLVM_DEBUG(dbgs() << "Fusion candidate contains unsafe "
-                                   "instructions in exit block. Not fusing.\n");
-              reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                         NonEmptyExitBlock);
-              continue;
-            }
+        FC0.verify();
+        FC1.verify();
 
-            if (!isSafeToMoveBefore(
-                    *FC1->GuardBranch->getParent(),
-                    *FC0->GuardBranch->getParent()->getTerminator(), DT, &PDT,
-                    &DI)) {
-              LLVM_DEBUG(dbgs()
-                         << "Fusion candidate contains unsafe "
-                            "instructions in guard block. Not fusing.\n");
-              reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                         NonEmptyGuardBlock);
-              continue;
-            }
-          }
-
-          // Check the dependencies across the loops and do not fuse if it would
-          // violate them.
-          if (!dependencesAllowFusion(*FC0, *FC1)) {
-            LLVM_DEBUG(dbgs() << "Memory dependencies do not allow fusion!\n");
-            reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                       InvalidDependencies);
-            continue;
-          }
+        // Check if the candidates have identical tripcounts (first value of
+        // pair), and if not check the difference in the tripcounts between
+        // the loops (second value of pair). The difference is not equal to
+        // std::nullopt iff the loops iterate a constant number of times, and
+        // have a single exit.
+        std::pair<bool, std::optional<unsigned>> IdenticalTripCountRes =
+            haveIdenticalTripCounts(FC0, FC1);
+        bool SameTripCount = IdenticalTripCountRes.first;
+        std::optional<unsigned> TCDifference = IdenticalTripCountRes.second;
 
-          // If the second loop has instructions in the pre-header, attempt to
-          // hoist them up to the first loop's pre-header or sink them into the
-          // body of the second loop.
-          SmallVector<Instruction *, 4> SafeToHoist;
-          SmallVector<Instruction *, 4> SafeToSink;
-          // At this point, this is the last remaining legality check.
-          // Which means if we can make this pre-header empty, we can fuse
-          // these loops
-          if (!isEmptyPreheader(*FC1)) {
-            LLVM_DEBUG(dbgs() << "Fusion candidate does not have empty "
-                                 "preheader.\n");
-
-            // If it is not safe to hoist/sink all instructions in the
-            // pre-header, we cannot fuse these loops.
-            if (!collectMovablePreheaderInsts(*FC0, *FC1, SafeToHoist,
-                                              SafeToSink)) {
-              LLVM_DEBUG(dbgs() << "Could not hoist/sink all instructions in "
-                                   "Fusion Candidate Pre-header.\n"
-                                << "Not Fusing.\n");
-              reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                         NonEmptyPreheader);
-              continue;
-            }
+        // Here we are checking that FC0 (the first loop) can be peeled, and
+        // both loops have different tripcounts.
+        if (FC0.AbleToPeel && !SameTripCount && TCDifference) {
+          if (*TCDifference > FusionPeelMaxCount) {
+            LLVM_DEBUG(dbgs()
+                       << "Difference in loop trip counts: " << *TCDifference
+                       << " is greater than maximum peel count specificed: "
+                       << FusionPeelMaxCount << "\n");
+          } else {
+            // Dependent on peeling being performed on the first loop, and
+            // assuming all other conditions for fusion return true.
+            SameTripCount = true;
           }
+        }
 
-          bool BeneficialToFuse = isBeneficialFusion(*FC0, *FC1);
-          LLVM_DEBUG(dbgs()
-                     << "\tFusion appears to be "
-                     << (BeneficialToFuse ? "" : "un") << "profitable!\n");
-          if (!BeneficialToFuse) {
-            reportLoopFusion<OptimizationRemarkMissed>(*FC0, *FC1,
-                                                       FusionNotBeneficial);
-            continue;
-          }
-          // All analysis has completed and has determined that fusion is legal
-          // and profitable. At this point, start transforming the code and
-          // perform fusion.
+        if (!SameTripCount) {
+          LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical trip "
+                               "counts. Not fusing.\n");
+          reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                     NonEqualTripCount);
+          continue;
+        }
 
-          // Execute the hoist/sink operations on preheader instructions
-          movePreheaderInsts(*FC0, *FC1, SafeToHoist, SafeToSink);
+        if ((!FC0.GuardBranch && FC1.GuardBranch) ||
+            (FC0.GuardBranch && !FC1.GuardBranch)) {
+          LLVM_DEBUG(dbgs() << "The one of candidate is guarded while the "
+                               "another one is not. Not fusing.\n");
+          reportLoopFusion<OptimizationRemarkMissed>(
+              FC0, FC1, OnlySecondCandidateIsGuarded);
+          continue;
+        }
 
-          LLVM_DEBUG(dbgs() << "\tFusion is performed: " << *FC0 << " and "
-                            << *FC1 << "\n");
+        // Ensure that FC0 and FC1 have identical guards.
+        // If one (or both) are not guarded, this check is not necessary.
+        if (FC0.GuardBranch && FC1.GuardBranch &&
+            !haveIdenticalGuards(FC0, FC1) && !TCDifference) {
+          LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical "
+                               "guards. Not Fusing.\n");
+          reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                     NonIdenticalGuards);
+          continue;
+        }
 
-          FusionCandidate FC0Copy = *FC0;
-          // Peel the loop after determining that fusion is legal. The Loops
-          // will still be safe to fuse after the peeling is performed.
-          bool Peel = TCDifference && *TCDifference > 0;
-          if (Peel)
-            peelFusionCandidate(FC0Copy, *FC1, *TCDifference);
+        if (FC0.GuardBranch) {
+          assert(FC1.GuardBranch && "Expecting valid FC1 guard branch");
 
-          // Report fusion to the Optimization Remarks.
-          // Note this needs to be done *before* performFusion because
-          // performFusion will change the original loops, making it not
-          // possible to identify them after fusion is complete.
-          reportLoopFusion<OptimizationRemark>((Peel ? FC0Copy : *FC0), *FC1,
-                                               FuseCounter);
+          if (!isSafeToMoveBefore(*FC0.ExitBlock,
+                                  *FC1.ExitBlock->getFirstNonPHIOrDbg(), DT,
+                                  &PDT, &DI)) {
+            LLVM_DEBUG(dbgs() << "Fusion candidate contains unsafe "
+                                 "instructions in exit block. Not fusing.\n");
+            reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                       NonEmptyExitBlock);
+            continue;
+          }
 
-          FusionCandidate FusedCand(
-              performFusion((Peel ? FC0Copy : *FC0), *FC1), DT, &PDT, ORE,
-              FC0Copy.PP);
-          FusedCand.verify();
-          assert(FusedCand.isEligibleForFusion(SE) &&
-                 "Fused candidate should be eligible for fusion!");
+          if (!isSafeToMoveBefore(
+                  *FC1.GuardBranch->getParent(),
+                  *FC0.GuardBranch->getParent()->getTerminator(), DT, &PDT,
+                  &DI)) {
+            LLVM_DEBUG(dbgs() << "Fusion candidate contains unsafe "
+                                 "instructions in guard block. Not fusing.\n");
+            reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                       NonEmptyGuardBlock);
+            continue;
+          }
+        }
 
-          // Notify the loop-depth-tree that these loops are not valid objects
-          LDT.removeLoop(FC1->L);
+        // Check the dependencies across the loops and do not fuse if it would
+        // violate them.
+        if (!dependencesAllowFusion(FC0, FC1)) {
+          LLVM_DEBUG(dbgs() << "Memory dependencies do not allow fusion!\n");
+          reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                     InvalidDependencies);
+          continue;
+        }
 
-          CandidateSet.erase(FC0);
-          CandidateSet.erase(FC1);
+        // If the second loop has instructions in the pre-header, attempt to
+        // hoist them up to the first loop's pre-header or sink them into the
+        // body of the second loop.
+        SmallVector<Instruction *, 4> SafeToHoist;
+        SmallVector<Instruction *, 4> SafeToSink;
+        // At this point, this is the last remaining legality check.
+        // Which means if we can make this pre-header empty, we can fuse
+        // these loops
+        if (!isEmptyPreheader(FC1)) {
+          LLVM_DEBUG(dbgs() << "Fusion candidate does not have empty "
+                               "preheader.\n");
+
+          // If it is not safe to hoist/sink all instructions in the
+          // pre-header, we cannot fuse these loops.
+          if (!collectMovablePreheaderInsts(FC0, FC1, SafeToHoist,
+                                            SafeToSink)) {
+            LLVM_DEBUG(dbgs() << "Could not hoist/sink all instructions in "
+                                 "Fusion Candidate Pre-header.\n"
+                              << "Not Fusing.\n");
+            reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                       NonEmptyPreheader);
+            continue;
+          }
+        }
 
-          auto InsertPos = CandidateSet.insert(FusedCand);
+        bool BeneficialToFuse = isBeneficialFusion(FC0, FC1);
+        LLVM_DEBUG(dbgs() << "\tFusion appears to be "
+                          << (BeneficialToFuse ? "" : "un") << "profitable!\n");
+        if (!BeneficialToFuse) {
+          reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
+                                                     FusionNotBeneficial);
+          continue;
+        }
+        // All analysis has completed and has determined that fusion is legal
+        // and profitable. At this point, start transforming the code and
+        // perform fusion.
 
-          assert(InsertPos.second &&
-                 "Unable to insert TargetCandidate in CandidateSet!");
+        // Execute the hoist/sink operations on preheader instructions
+        movePreheaderInsts(FC0, FC1, SafeToHoist, SafeToSink);
 
-          // Reset FC0 and FC1 the new (fused) candidate. Subsequent iterations
-          // of the FC1 loop will attempt to fuse the new (fused) loop with the
-          // remaining candidates in the current candidate set.
-          FC0 = FC1 = InsertPos.first;
+        LLVM_DEBUG(dbgs() << "\tFusion is performed: " << FC0 << " and " << FC1
+                          << "\n");
 
-          LLVM_DEBUG(dbgs() << "Candidate Set (after fusion): " << CandidateSet
-                            << "\n");
+        FusionCandidate FC0Copy = FC0;
+        // Peel the loop after determining that fusion is legal. The Loops
+        // will still be safe to fuse after the peeling is performed.
+        bool Peel = TCDifference && *TCDifference > 0;
+        if (Peel)
+          peelFusionCandidate(FC0Copy, FC1, *TCDifference);
+
+        // Report fusion to the Optimization Remarks.
+        // Note this needs to be done *before* performFusion because
+        // performFusion will change the original loops, making it not
+        // possible to identify them after fusion is complete.
+        reportLoopFusion<OptimizationRemark>((Peel ? FC0Copy : FC0), FC1,
+                                             FuseCounter);
+
+        FusionCandidate FusedCand(performFusion((Peel ? FC0Copy : FC0), FC1),
+                                  DT, &PDT, ORE, FC0Copy.PP);
+        FusedCand.verify();
+        assert(FusedCand.isEligibleForFusion(SE) &&
+               "Fused candidate should be eligible for fusion!");
+
+        // Notify the loop-depth-tree that these loops are not valid objects
+        LDT.removeLoop(FC1.L);
+
+        // Replace FC0 and FC1 with their fused loop
+        It = CandidateList.erase(It);
+        It = CandidateList.erase(It);
+        It = CandidateList.insert(It, FusedCand);
+
+        // Start from FusedCand in the next iteration
+        NextIt = It;
+
+        LLVM_DEBUG(dbgs() << "Candidate List (after fusion): " << CandidateList
+                          << "\n");
 
-          Fused = true;
-        }
+        Fused = true;
       }
     }
     return Fused;
@@ -1488,7 +1394,7 @@ private:
     return true;
   }
 
-  /// Determine if two fusion candidates are adjacent in the CFG.
+  /// Determine if two fusion candidates are strictly adjacent in the CFG.
   ///
   /// This method will determine if there are additional basic blocks in the CFG
   /// between the exit of \p FC0 and the entry of \p FC1.
@@ -1497,11 +1403,14 @@ private:
   /// FC1. If not, then the loops are not adjacent. If the two candidates are
   /// not guarded loops, then it checks whether the exit block of \p FC0 is the
   /// preheader of \p FC1.
-  bool isAdjacent(const FusionCandidate &FC0,
-                  const FusionCandidate &FC1) const {
+  /// Strictly means there is no predecessor for FC1 unless it is from FC0,
+  /// i.e., FC0 dominates FC1.
+  bool isStrictlyAdjacent(const FusionCandidate &FC0,
+                          const FusionCandidate &FC1) const {
     // If the successor of the guard branch is FC1, then the loops are adjacent
     if (FC0.GuardBranch)
-      return FC0.getNonLoopBlock() == FC1.getEntryBlock();
+      return DT.dominates(FC0.getEntryBlock(), FC1.getEntryBlock()) &&
+             FC0.getNonLoopBlock() == FC1.getEntryBlock();
     else
       return FC0.ExitBlock == FC1.getEntryBlock();
   }
diff --git a/llvm/lib/Transforms/Utils/CodeMoverUtils.cpp b/llvm/lib/Transforms/Utils/CodeMoverUtils.cpp
index 0e076c60d608..8384d46837a7 100644
--- a/llvm/lib/Transforms/Utils/CodeMoverUtils.cpp
+++ b/llvm/lib/Transforms/Utils/CodeMoverUtils.cpp
@@ -25,8 +25,6 @@ using namespace llvm;
 STATISTIC(HasDependences,
           "Cannot move across instructions that has memory dependences");
 STATISTIC(MayThrowException, "Cannot move across instructions that may throw");
-STATISTIC(NotControlFlowEquivalent,
-          "Instructions are not control flow equivalent");
 STATISTIC(NotMovedPHINode, "Movement of PHINodes are not supported");
 STATISTIC(NotMovedTerminator, "Movement of Terminator are not supported");
 
@@ -228,44 +226,6 @@ bool ControlConditions::isInverse(const Value &V1, const Value &V2) {
   return false;
 }
 
-bool llvm::isControlFlowEquivalent(const Instruction &I0, const Instruction &I1,
-                                   const DominatorTree &DT,
-                                   const PostDominatorTree &PDT) {
-  return isControlFlowEquivalent(*I0.getParent(), *I1.getParent(), DT, PDT);
-}
-
-bool llvm::isControlFlowEquivalent(const BasicBlock &BB0, const BasicBlock &BB1,
-                                   const DominatorTree &DT,
-                                   const PostDominatorTree &PDT) {
-  if (&BB0 == &BB1)
-    return true;
-
-  if ((DT.dominates(&BB0, &BB1) && PDT.dominates(&BB1, &BB0)) ||
-      (PDT.dominates(&BB0, &BB1) && DT.dominates(&BB1, &BB0)))
-    return true;
-
-  // If the set of conditions required to execute BB0 and BB1 from their common
-  // dominator are the same, then BB0 and BB1 are control flow equivalent.
-  const BasicBlock *CommonDominator = DT.findNearestCommonDominator(&BB0, &BB1);
-  LLVM_DEBUG(dbgs() << "The nearest common dominator of " << BB0.getName()
-                    << " and " << BB1.getName() << " is "
-                    << CommonDominator->getName() << "\n");
-
-  const std::optional<ControlConditions> BB0Conditions =
-      ControlConditions::collectControlConditions(BB0, *CommonDominator, DT,
-                                                  PDT);
-  if (BB0Conditions == std::nullopt)
-    return false;
-
-  const std::optional<ControlConditions> BB1Conditions =
-      ControlConditions::collectControlConditions(BB1, *CommonDominator, DT,
-                                                  PDT);
-  if (BB1Conditions == std::nullopt)
-    return false;
-
-  return BB0Conditions->isEquivalent(*BB1Conditions);
-}
-
 static bool reportInvalidCandidate(const Instruction &I,
                                    llvm::Statistic &Stat) {
   ++Stat;
@@ -330,10 +290,6 @@ bool llvm::isSafeToMoveBefore(Instruction &I, Instruction &InsertPoint,
   if (I.isTerminator())
     return reportInvalidCandidate(I, NotMovedTerminator);
 
-  // TODO remove this limitation.
-  if (!isControlFlowEquivalent(I, InsertPoint, DT, *PDT))
-    return reportInvalidCandidate(I, NotControlFlowEquivalent);
-
   if (isReachedBefore(&I, &InsertPoint, &DT, PDT))
     for (const Use &U : I.uses())
       if (auto *UserInst = dyn_cast<Instruction>(U.getUser())) {
@@ -450,8 +406,6 @@ bool llvm::nonStrictlyPostDominate(const BasicBlock *ThisBlock,
                                    const BasicBlock *OtherBlock,
                                    const DominatorTree *DT,
                                    const PostDominatorTree *PDT) {
-  assert(isControlFlowEquivalent(*ThisBlock, *OtherBlock, *DT, *PDT) &&
-         "ThisBlock and OtherBlock must be CFG equivalent!");
   const BasicBlock *CommonDominator =
       DT->findNearestCommonDominator(ThisBlock, OtherBlock);
   if (CommonDominator == nullptr)


Regression tests:
file: <module>
commands: opt -S -passes=loop-fusion -disable-output < %s 2>&1
tests: test_name: <module>
test_body: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-gnu"

define void @_Z3fn1bis() {
entry:
  br i1 true, label %for.inc68, label %for.cond8thread-pre-split

for.cond8thread-pre-split:                        ; preds = %entry
  br i1 false, label %for.inc68, label %for.inc33

for.inc33:                                        ; preds = %for.inc33, %for.cond8thread-pre-split
  br i1 false, label %for.inc33, label %for.cond52.preheader

for.cond52.preheader:                             ; preds = %for.cond52.preheader, %for.inc33
  br i1 false, label %for.cond52.preheader, label %for.inc68

for.inc68:                                        ; preds = %for.cond52.preheader, %for.cond8thread-pre-split, %entry
  br i1 true, label %for.inc68.2, label %for.cond8thread-pre-split.1

for.cond8thread-pre-split.1:                      ; preds = %for.inc68
  br i1 false, label %for.inc68.2, label %for.inc33.1

for.inc33.1:                                      ; preds = %for.inc33.1, %for.cond8thread-pre-split.1
  br i1 false, label %for.inc33.1, label %for.cond52.preheader.1.preheader

for.cond52.preheader.1.preheader:                 ; preds = %for.inc33.1
  ret void

for.inc68.2:                                      ; preds = %for.cond8thread-pre-split.1, %for.inc68
  ret void
}