[llvm] [WebAssembly] Use `MachineCycleInfo` in `WebAssemblyFixIrreducibleControlFlow` (PR #182237)

via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 19 01:01:23 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-webassembly

Author: Demetrius Kanios (QuantumSegfault)

<details>
<summary>Changes</summary>

Rather than mapping out full "reachability" between blocks in a region to find loops and using LoopBlocks to find the bodies of said loops, we make use of the `MachineCycleInfo` analysis pass (`MachineCycleInfoWrapperPass`) instead to provide all this information.

We are simply left with the task of iterating over all detected cycles, depth-first, outside-in, and insert the appropriate headers (and patch up the relevant cycles to include the new blocks as we go) where we find irreducible cycles.

Alternative to #<!-- -->181755 (SCCs).




---
Full diff: https://github.com/llvm/llvm-project/pull/182237.diff


1 Files Affected:

- (modified) llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp (+92-318) 


``````````diff
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 07171d472dc2d..19cede5c05fe5 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -55,8 +55,13 @@
 #include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
 #include "WebAssembly.h"
 #include "WebAssemblySubtarget.h"
+#include "llvm/ADT/DepthFirstIterator.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineCycleAnalysis.h"
+#include "llvm/CodeGen/MachineFunction.h"
 #include "llvm/CodeGen/MachineFunctionPass.h"
 #include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
 using namespace llvm;
 
@@ -67,280 +72,86 @@ namespace {
 using BlockVector = SmallVector<MachineBasicBlock *, 4>;
 using BlockSet = SmallPtrSet<MachineBasicBlock *, 4>;
 
-static BlockVector getSortedEntries(const BlockSet &Entries) {
-  BlockVector SortedEntries(Entries.begin(), Entries.end());
-  llvm::sort(SortedEntries,
-             [](const MachineBasicBlock *A, const MachineBasicBlock *B) {
-               auto ANum = A->getNumber();
-               auto BNum = B->getNumber();
-               return ANum < BNum;
-             });
-  return SortedEntries;
-}
-
-// Calculates reachability in a region. Ignores branches to blocks outside of
-// the region, and ignores branches to the region entry (for the case where
-// the region is the inner part of a loop).
-class ReachabilityGraph {
-public:
-  ReachabilityGraph(MachineBasicBlock *Entry, const BlockSet &Blocks)
-      : Entry(Entry), Blocks(Blocks) {
-#ifndef NDEBUG
-    // The region must have a single entry.
-    for (auto *MBB : Blocks) {
-      if (MBB != Entry) {
-        for (auto *Pred : MBB->predecessors()) {
-          assert(inRegion(Pred));
-        }
-      }
-    }
-#endif
-    calculate();
-  }
-
-  bool canReach(MachineBasicBlock *From, MachineBasicBlock *To) const {
-    assert(inRegion(From) && inRegion(To));
-    auto I = Reachable.find(From);
-    if (I == Reachable.end())
-      return false;
-    return I->second.count(To);
-  }
-
-  // "Loopers" are blocks that are in a loop. We detect these by finding blocks
-  // that can reach themselves.
-  const BlockSet &getLoopers() const { return Loopers; }
-
-  // Get all blocks that are loop entries.
-  const BlockSet &getLoopEntries() const { return LoopEntries; }
-
-  // Get all blocks that enter a particular loop from outside.
-  const BlockSet &getLoopEnterers(MachineBasicBlock *LoopEntry) const {
-    assert(inRegion(LoopEntry));
-    auto I = LoopEnterers.find(LoopEntry);
-    assert(I != LoopEnterers.end());
-    return I->second;
-  }
-
-private:
-  MachineBasicBlock *Entry;
-  const BlockSet &Blocks;
-
-  BlockSet Loopers, LoopEntries;
-  DenseMap<MachineBasicBlock *, BlockSet> LoopEnterers;
-
-  bool inRegion(MachineBasicBlock *MBB) const { return Blocks.count(MBB); }
-
-  // Maps a block to all the other blocks it can reach.
-  DenseMap<MachineBasicBlock *, BlockSet> Reachable;
-
-  void calculate() {
-    // Reachability computation work list. Contains pairs of recent additions
-    // (A, B) where we just added a link A => B.
-    using BlockPair = std::pair<MachineBasicBlock *, MachineBasicBlock *>;
-    SmallVector<BlockPair, 4> WorkList;
-
-    // Add all relevant direct branches.
-    for (auto *MBB : Blocks) {
-      for (auto *Succ : MBB->successors()) {
-        if (Succ != Entry && inRegion(Succ)) {
-          Reachable[MBB].insert(Succ);
-          WorkList.emplace_back(MBB, Succ);
-        }
-      }
-    }
-
-    while (!WorkList.empty()) {
-      MachineBasicBlock *MBB, *Succ;
-      std::tie(MBB, Succ) = WorkList.pop_back_val();
-      assert(inRegion(MBB) && Succ != Entry && inRegion(Succ));
-      if (MBB != Entry) {
-        // We recently added MBB => Succ, and that means we may have enabled
-        // Pred => MBB => Succ.
-        for (auto *Pred : MBB->predecessors()) {
-          if (Reachable[Pred].insert(Succ).second) {
-            WorkList.emplace_back(Pred, Succ);
-          }
-        }
-      }
-    }
-
-    // Blocks that can return to themselves are in a loop.
-    for (auto *MBB : Blocks) {
-      if (canReach(MBB, MBB)) {
-        Loopers.insert(MBB);
-      }
-    }
-    assert(!Loopers.count(Entry));
-
-    // Find the loop entries - loopers reachable from blocks not in that loop -
-    // and those outside blocks that reach them, the "loop enterers".
-    for (auto *Looper : Loopers) {
-      for (auto *Pred : Looper->predecessors()) {
-        // Pred can reach Looper. If Looper can reach Pred, it is in the loop;
-        // otherwise, it is a block that enters into the loop.
-        if (!canReach(Looper, Pred)) {
-          LoopEntries.insert(Looper);
-          LoopEnterers[Looper].insert(Pred);
-        }
-      }
-    }
-  }
-};
-
-// Finds the blocks in a single-entry loop, given the loop entry and the
-// list of blocks that enter the loop.
-class LoopBlocks {
-public:
-  LoopBlocks(MachineBasicBlock *Entry, const BlockSet &Enterers)
-      : Entry(Entry), Enterers(Enterers) {
-    calculate();
-  }
-
-  BlockSet &getBlocks() { return Blocks; }
-
-private:
-  MachineBasicBlock *Entry;
-  const BlockSet &Enterers;
-
-  BlockSet Blocks;
-
-  void calculate() {
-    // Going backwards from the loop entry, if we ignore the blocks entering
-    // from outside, we will traverse all the blocks in the loop.
-    BlockVector WorkList;
-    BlockSet AddedToWorkList;
-    Blocks.insert(Entry);
-    for (auto *Pred : Entry->predecessors()) {
-      if (!Enterers.count(Pred)) {
-        WorkList.push_back(Pred);
-        AddedToWorkList.insert(Pred);
-      }
-    }
-
-    while (!WorkList.empty()) {
-      auto *MBB = WorkList.pop_back_val();
-      assert(!Enterers.count(MBB));
-      if (Blocks.insert(MBB).second) {
-        for (auto *Pred : MBB->predecessors()) {
-          if (AddedToWorkList.insert(Pred).second)
-            WorkList.push_back(Pred);
-        }
-      }
-    }
-  }
-};
-
 class WebAssemblyFixIrreducibleControlFlow final : public MachineFunctionPass {
   StringRef getPassName() const override {
     return "WebAssembly Fix Irreducible Control Flow";
   }
 
   bool runOnMachineFunction(MachineFunction &MF) override;
-
-  bool processRegion(MachineBasicBlock *Entry, BlockSet &Blocks,
-                     MachineFunction &MF);
-
-  void makeSingleEntryLoop(BlockSet &Entries, BlockSet &Blocks,
-                           MachineFunction &MF, const ReachabilityGraph &Graph);
+  void getAnalysisUsage(AnalysisUsage &AU) const override;
 
 public:
   static char ID; // Pass identification, replacement for typeid
   WebAssemblyFixIrreducibleControlFlow() : MachineFunctionPass(ID) {}
 };
 
-bool WebAssemblyFixIrreducibleControlFlow::processRegion(
-    MachineBasicBlock *Entry, BlockSet &Blocks, MachineFunction &MF) {
-  bool Changed = false;
-  // Remove irreducibility before processing child loops, which may take
-  // multiple iterations.
-  while (true) {
-    ReachabilityGraph Graph(Entry, Blocks);
-
-    bool FoundIrreducibility = false;
-
-    for (auto *LoopEntry : getSortedEntries(Graph.getLoopEntries())) {
-      // Find mutual entries - all entries which can reach this one, and
-      // are reached by it (that always includes LoopEntry itself). All mutual
-      // entries must be in the same loop, so if we have more than one, then we
-      // have irreducible control flow.
-      //
-      // (Note that we need to sort the entries here, as otherwise the order can
-      // matter: being mutual is a symmetric relationship, and each set of
-      // mutuals will be handled properly no matter which we see first. However,
-      // there can be multiple disjoint sets of mutuals, and which we process
-      // first changes the output.)
-      //
-      // Note that irreducibility may involve inner loops, e.g. imagine A
-      // starts one loop, and it has B inside it which starts an inner loop.
-      // If we add a branch from all the way on the outside to B, then in a
-      // sense B is no longer an "inner" loop, semantically speaking. We will
-      // fix that irreducibility by adding a block that dispatches to either
-      // either A or B, so B will no longer be an inner loop in our output.
-      // (A fancier approach might try to keep it as such.)
-      //
-      // Note that we still need to recurse into inner loops later, to handle
-      // the case where the irreducibility is entirely nested - we would not
-      // be able to identify that at this point, since the enclosing loop is
-      // a group of blocks all of whom can reach each other. (We'll see the
-      // irreducibility after removing branches to the top of that enclosing
-      // loop.)
-      BlockSet MutualLoopEntries;
-      MutualLoopEntries.insert(LoopEntry);
-      for (auto *OtherLoopEntry : Graph.getLoopEntries()) {
-        if (OtherLoopEntry != LoopEntry &&
-            Graph.canReach(LoopEntry, OtherLoopEntry) &&
-            Graph.canReach(OtherLoopEntry, LoopEntry)) {
-          MutualLoopEntries.insert(OtherLoopEntry);
-        }
-      }
+} // end anonymous namespace
 
-      if (MutualLoopEntries.size() > 1) {
-        makeSingleEntryLoop(MutualLoopEntries, Blocks, MF, Graph);
-        FoundIrreducibility = true;
-        Changed = true;
-        break;
-      }
-    }
-    // Only go on to actually process the inner loops when we are done
-    // removing irreducible control flow and changing the graph. Modifying
-    // the graph as we go is possible, and that might let us avoid looking at
-    // the already-fixed loops again if we are careful, but all that is
-    // complex and bug-prone. Since irreducible loops are rare, just starting
-    // another iteration is best.
-    if (FoundIrreducibility) {
+char WebAssemblyFixIrreducibleControlFlow::ID = 0;
+INITIALIZE_PASS(WebAssemblyFixIrreducibleControlFlow, DEBUG_TYPE,
+                "Removes irreducible control flow", false, false)
+
+FunctionPass *llvm::createWebAssemblyFixIrreducibleControlFlow() {
+  return new WebAssemblyFixIrreducibleControlFlow();
+}
+
+// Test whether the given register has an ARGUMENT def.
+static bool hasArgumentDef(unsigned Reg, const MachineRegisterInfo &MRI) {
+  for (const auto &Def : MRI.def_instructions(Reg))
+    if (WebAssembly::isArgument(Def.getOpcode()))
+      return true;
+  return false;
+}
+
+// Add a register definition with IMPLICIT_DEFs for every register to cover for
+// register uses that don't have defs in every possible path.
+// TODO: This is fairly heavy-handed; find a better approach.
+static void addImplicitDefs(MachineFunction &MF) {
+  const MachineRegisterInfo &MRI = MF.getRegInfo();
+  const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
+  MachineBasicBlock &Entry = *MF.begin();
+  for (unsigned I = 0, E = MRI.getNumVirtRegs(); I < E; ++I) {
+    Register Reg = Register::index2VirtReg(I);
+
+    // Skip unused registers.
+    if (MRI.use_nodbg_empty(Reg))
       continue;
-    }
 
-    for (auto *LoopEntry : Graph.getLoopEntries()) {
-      LoopBlocks InnerBlocks(LoopEntry, Graph.getLoopEnterers(LoopEntry));
-      // Each of these calls to processRegion may change the graph, but are
-      // guaranteed not to interfere with each other. The only changes we make
-      // to the graph are to add blocks on the way to a loop entry. As the
-      // loops are disjoint, that means we may only alter branches that exit
-      // another loop, which are ignored when recursing into that other loop
-      // anyhow.
-      if (processRegion(LoopEntry, InnerBlocks.getBlocks(), MF)) {
-        Changed = true;
-      }
-    }
+    // Skip registers that have an ARGUMENT definition.
+    if (hasArgumentDef(Reg, MRI))
+      continue;
+
+    BuildMI(Entry, Entry.begin(), DebugLoc(),
+            TII.get(WebAssembly::IMPLICIT_DEF), Reg);
+  }
 
-    return Changed;
+  // Move ARGUMENT_* instructions to the top of the entry block, so that their
+  // liveness reflects the fact that these really are live-in values.
+  for (MachineInstr &MI : llvm::make_early_inc_range(Entry)) {
+    if (WebAssembly::isArgument(MI.getOpcode())) {
+      MI.removeFromParent();
+      Entry.insert(Entry.begin(), &MI);
+    }
   }
 }
 
-// Given a set of entries to a single loop, create a single entry for that
-// loop by creating a dispatch block for them, routing control flow using
-// a helper variable. Also updates Blocks with any new blocks created, so
-// that we properly track all the blocks in the region. But this does not update
-// ReachabilityGraph; this will be updated in the caller of this function as
-// needed.
-void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
-    BlockSet &Entries, BlockSet &Blocks, MachineFunction &MF,
-    const ReachabilityGraph &Graph) {
+static bool fixIrreducible(MachineCycle &C, MachineCycleInfo &MCI,
+                           MachineFunction &MF) {
+  if (C.isReducible())
+    return false;
+
+  auto &Entries = C.getEntries();
+
   assert(Entries.size() >= 2);
 
   // Sort the entries to ensure a deterministic build.
-  BlockVector SortedEntries = getSortedEntries(Entries);
+  BlockVector SortedEntries(Entries.begin(), Entries.end());
+  llvm::sort(SortedEntries,
+             [](const MachineBasicBlock *A, const MachineBasicBlock *B) {
+               auto ANum = A->getNumber();
+               auto BNum = B->getNumber();
+               return ANum < BNum;
+             });
 
 #ifndef NDEBUG
   for (auto *Block : SortedEntries)
@@ -358,7 +169,7 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
   // Create a dispatch block which will contain a jump table to the entries.
   MachineBasicBlock *Dispatch = MF.CreateMachineBasicBlock();
   MF.insert(MF.end(), Dispatch);
-  Blocks.insert(Dispatch);
+  MCI.addBlockToCycle(Dispatch, &C);
 
   // Add the jump table.
   const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
@@ -401,13 +212,8 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
   // This set stores predecessors within this loop.
   DenseSet<MachineBasicBlock *> InLoop;
   for (auto *Pred : AllPreds) {
-    for (auto *Entry : Pred->successors()) {
-      if (!Entries.count(Entry))
-        continue;
-      if (Graph.canReach(Entry, Pred)) {
-        InLoop.insert(Pred);
-        break;
-      }
+    if (C.contains(Pred)) {
+      InLoop.insert(Pred);
     }
   }
 
@@ -418,7 +224,7 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
   for (auto *Pred : AllPreds) {
     bool PredInLoop = InLoop.count(Pred);
     for (auto *Entry : Pred->successors())
-      if (Entries.count(Entry) && Pred->isLayoutSuccessor(Entry))
+      if (C.isEntry(Entry) && Pred->isLayoutSuccessor(Entry))
         EntryToLayoutPred[{Entry, PredInLoop}] = Pred;
   }
 
@@ -431,7 +237,7 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
   for (auto *Pred : AllPreds) {
     bool PredInLoop = InLoop.count(Pred);
     for (auto *Entry : Pred->successors()) {
-      if (!Entries.count(Entry) || Map.count({Entry, PredInLoop}))
+      if (!C.isEntry(Entry) || Map.count({Entry, PredInLoop}))
         continue;
       // If there exists a layout predecessor of this entry and this predecessor
       // is not that, we rather create a routing block after that layout
@@ -446,7 +252,13 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
                     ? MachineFunction::iterator(Entry)
                     : MF.end(),
                 Routing);
-      Blocks.insert(Routing);
+
+      if (PredInLoop) {
+        MCI.addBlockToCycle(Routing, &C);
+      } else {
+        if (auto *Parent = C.getParentCycle())
+          MCI.addBlockToCycle(Routing, Parent);
+      }
 
       // Set the jump table's register of the index of the block we wish to
       // jump to, and jump to the jump table.
@@ -467,7 +279,7 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
           Op.setMBB(Map[{Op.getMBB(), PredInLoop}]);
 
     for (auto *Succ : Pred->successors()) {
-      if (!Entries.count(Succ))
+      if (!C.isEntry(Succ))
         continue;
       auto *Routing = Map[{Succ, PredInLoop}];
       Pred->replaceSuccessor(Succ, Routing);
@@ -478,56 +290,10 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
   MIB.addMBB(MIB.getInstr()
                  ->getOperand(MIB.getInstr()->getNumExplicitOperands() - 1)
                  .getMBB());
-}
-
-} // end anonymous namespace
 
-char WebAssemblyFixIrreducibleControlFlow::ID = 0;
-INITIALIZE_PASS(WebAssemblyFixIrreducibleControlFlow, DEBUG_TYPE,
-                "Removes irreducible control flow", false, false)
+  C.setSingleEntry(Dispatch);
 
-FunctionPass *llvm::createWebAssemblyFixIrreducibleControlFlow() {
-  return new WebAssemblyFixIrreducibleControlFlow();
-}
-
-// Test whether the given register has an ARGUMENT def.
-static bool hasArgumentDef(unsigned Reg, const MachineRegisterInfo &MRI) {
-  for (const auto &Def : MRI.def_instructions(Reg))
-    if (WebAssembly::isArgument(Def.getOpcode()))
-      return true;
-  return false;
-}
-
-// Add a register definition with IMPLICIT_DEFs for every register to cover for
-// register uses that don't have defs in every possible path.
-// TODO: This is fairly heavy-handed; find a better approach.
-static void addImplicitDefs(MachineFunction &MF) {
-  const MachineRegisterInfo &MRI = MF.getRegInfo();
-  const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
-  MachineBasicBlock &Entry = *MF.begin();
-  for (unsigned I = 0, E = MRI.getNumVirtRegs(); I < E; ++I) {
-    Register Reg = Register::index2VirtReg(I);
-
-    // Skip unused registers.
-    if (MRI.use_nodbg_empty(Reg))
-      continue;
-
-    // Skip registers that have an ARGUMENT definition.
-    if (hasArgumentDef(Reg, MRI))
-      continue;
-
-    BuildMI(Entry, Entry.begin(), DebugLoc(),
-            TII.get(WebAssembly::IMPLICIT_DEF), Reg);
-  }
-
-  // Move ARGUMENT_* instructions to the top of the entry block, so that their
-  // liveness reflects the fact that these really are live-in values.
-  for (MachineInstr &MI : llvm::make_early_inc_range(Entry)) {
-    if (WebAssembly::isArgument(MI.getOpcode())) {
-      MI.removeFromParent();
-      Entry.insert(Entry.begin(), &MI);
-    }
-  }
+  return true;
 }
 
 bool WebAssemblyFixIrreducibleControlFlow::runOnMachineFunction(
@@ -536,13 +302,16 @@ bool WebAssemblyFixIrreducibleControlFlow::runOnMachineFunction(
                        "********** Function: "
                     << MF.getName() << '\n');
 
-  // Start the recursive process on the entire function body.
-  BlockSet AllBlocks;
-  for (auto &MBB : MF) {
-    AllBlocks.insert(&MBB);
+  auto &MCI = getAnalysis<MachineCycleInfoWrapperPass>().getCycleInfo();
+
+  bool Changed = false;
+  for (MachineCycle *TopCycle : MCI.toplevel_cycles()) {
+    for (MachineCycle *C : depth_first(TopCycle)) {
+      Changed |= fixIrreducible(*C, MCI, MF);
+    }
   }
 
-  if (LLVM_UNLIKELY(processRegion(&*MF.begin(), AllBlocks, MF))) {
+  if (LLVM_UNLIKELY(Changed)) {
     // We rewrote part of the function; recompute relevant things.
     MF.RenumberBlocks();
     // Now we've inserted dispatch blocks, some register uses can have incoming
@@ -555,6 +324,11 @@ bool WebAssemblyFixIrreducibleControlFlow::runOnMachineFunction(
     addImplicitDefs(MF);
     return true;
   }
-
   return false;
 }
+
+void WebAssemblyFixIrreducibleControlFlow::getAnalysisUsage(
+    AnalysisUsage &AU) const {
+  AU.addRequired<MachineCycleInfoWrapperPass>();
+  MachineFunctionPass::getAnalysisUsage(AU);
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/182237


More information about the llvm-commits mailing list