[llvm] [WebAssembly] Reapply "[WebAssembly] Incorporate SCCs into WebAssemblyFixIrreducibleControlFlow" (#181755) (PR #184441)

Demetrius Kanios via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 4 22:09:34 PST 2026


https://github.com/QuantumSegfault updated https://github.com/llvm/llvm-project/pull/184441

>From 001ba61b0ba82030179e20efe879a6c1d30e07e4 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Wed, 4 Feb 2026 15:15:02 -0800
Subject: [PATCH 1/9] Incorporate SCCs into
 WebAssemblyFixIrreducibleControlFlow

---
 .../WebAssemblyFixIrreducibleControlFlow.cpp  | 258 ++++++++++--------
 1 file changed, 140 insertions(+), 118 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 07171d472dc2d..90a8a0fe5fcf6 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -55,9 +55,12 @@
 #include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
 #include "WebAssembly.h"
 #include "WebAssemblySubtarget.h"
+#include "llvm/ADT/SCCIterator.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/MachineFunctionPass.h"
 #include "llvm/CodeGen/MachineInstrBuilder.h"
 #include "llvm/Support/Debug.h"
+#include <limits>
 using namespace llvm;
 
 #define DEBUG_TYPE "wasm-fix-irreducible-control-flow"
@@ -78,9 +81,17 @@ static BlockVector getSortedEntries(const BlockSet &Entries) {
   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;
+
+struct ReachabilityNode {
+  MachineBasicBlock *MBB;
+  SmallVector<ReachabilityNode *, 4> Succs;
+  unsigned SCCId = std::numeric_limits<unsigned>::max();
+};
+
+// Analyzes the SCC (strongly-connected component) structure 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)
@@ -98,137 +109,144 @@ class ReachabilityGraph {
     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; }
+  const BlockSet &getLoopEntriesForSCC(unsigned SCCId) const {
+    return LoopEntriesBySCC[SCCId];
+  }
 
-  // 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;
+  unsigned getSCCId(MachineBasicBlock *MBB) const {
+    return getNode(MBB)->SCCId;
   }
 
+  friend struct GraphTraits<ReachabilityGraph *>;
+
 private:
   MachineBasicBlock *Entry;
   const BlockSet &Blocks;
 
-  BlockSet Loopers, LoopEntries;
-  DenseMap<MachineBasicBlock *, BlockSet> LoopEnterers;
+  BlockSet LoopEntries;
+  SmallVector<BlockSet, 0> LoopEntriesBySCC;
 
   bool inRegion(MachineBasicBlock *MBB) const { return Blocks.count(MBB); }
 
-  // Maps a block to all the other blocks it can reach.
-  DenseMap<MachineBasicBlock *, BlockSet> Reachable;
+  SmallVector<ReachabilityNode, 0> Nodes;
+  DenseMap<MachineBasicBlock *, ReachabilityNode *> MBBToNodeMap;
 
-  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;
+  ReachabilityNode *getNode(MachineBasicBlock *MBB) const {
+    auto It = MBBToNodeMap.find(MBB);
+    assert(It != MBBToNodeMap.end());
+    return It->second;
+  }
 
-    // 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);
-        }
-      }
-    }
+  void calculate();
+};
+} // end anonymous namespace
 
-    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);
-          }
-        }
-      }
-    }
+namespace llvm {
+template <> struct GraphTraits<ReachabilityGraph *> {
+  typedef ReachabilityNode NodeType;
+  typedef NodeType *NodeRef;
+  typedef SmallVectorImpl<NodeRef>::iterator ChildIteratorType;
 
-    // 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);
-        }
-      }
-    }
+  static NodeRef getEntryNode(ReachabilityGraph *G) {
+    return G->getNode(G->Entry);
+  }
+
+  static inline ChildIteratorType child_begin(NodeRef N) {
+    return N->Succs.begin();
+  }
+
+  static inline ChildIteratorType child_end(NodeRef N) {
+    return N->Succs.end();
   }
 };
+} // end namespace llvm
 
-// 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();
+namespace {
+
+void ReachabilityGraph::calculate() {
+  auto NumBlocks = Blocks.size();
+  Nodes.assign(NumBlocks, {});
+
+  MBBToNodeMap.clear();
+  MBBToNodeMap.reserve(NumBlocks);
+
+  // Initialize mappings.
+  unsigned MBBIdx = 0;
+  for (auto *MBB : Blocks) {
+    auto &Node = Nodes[MBBIdx++];
+
+    Node.MBB = MBB;
+    MBBToNodeMap[MBB] = &Node;
   }
 
-  BlockSet &getBlocks() { return Blocks; }
+  // Add all relevant direct branches.
+  MBBIdx = 0;
+  for (auto *MBB : Blocks) {
+    auto &Node = Nodes[MBBIdx++];
 
-private:
-  MachineBasicBlock *Entry;
-  const BlockSet &Enterers;
+    for (auto *Succ : MBB->successors()) {
+      if (Succ != Entry && inRegion(Succ)) {
+        Node.Succs.push_back(getNode(Succ));
+      }
+    }
+  }
 
-  BlockSet Blocks;
+  unsigned CurrSCCIdx = 0;
+  for (auto &SCC : make_range(scc_begin(this), scc_end(this))) {
+    LoopEntriesBySCC.push_back({});
+    auto &SCCLoopEntries = LoopEntriesBySCC[CurrSCCIdx];
 
-  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);
+    for (auto *Node : SCC) {
+      // Make sure nodes are only ever assigned one SCC
+      assert(Node->SCCId == std::numeric_limits<unsigned>::max());
+
+      Node->SCCId = CurrSCCIdx;
+    }
+
+    bool SelfLoop = false;
+    if (SCC.size() == 1) {
+      auto &Node = SCC[0];
+
+      if (Node->MBB != Entry) {
+        for (auto *Succ : Node->Succs) {
+          if (Succ == Node) {
+            SelfLoop = true;
+            break;
+          }
+        }
       }
     }
 
-    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);
+    // Blocks outside any (multi-block) loop will be isolated in their own
+    // single-element SCC. Thus blocks that are in a loop are those in
+    // multi-element SCCs or are self-looping.
+    if (SCC.size() > 1 || SelfLoop) {
+      // Find the loop entries - loop body blocks with predecessors outside
+      // their SCC
+      for (auto *Node : SCC) {
+        if (Node->MBB == Entry)
+          continue;
+
+        for (auto *Pred : Node->MBB->predecessors()) {
+          // This test is accurate despite not having assigned all nodes an SCC
+          // yet. We only care if a node has been assigned into this SCC or not.
+          if (getSCCId(Pred) != CurrSCCIdx) {
+            LoopEntries.insert(Node->MBB);
+            SCCLoopEntries.insert(Node->MBB);
+          }
         }
       }
     }
+    ++CurrSCCIdx;
   }
-};
+
+  // Make sure all nodes have been processed
+  for (auto &Node : Nodes) {
+    assert(Node.SCCId != std::numeric_limits<unsigned>::max());
+  }
+}
 
 class WebAssemblyFixIrreducibleControlFlow final : public MachineFunctionPass {
   StringRef getPassName() const override {
@@ -240,7 +258,7 @@ class WebAssemblyFixIrreducibleControlFlow final : public MachineFunctionPass {
   bool processRegion(MachineBasicBlock *Entry, BlockSet &Blocks,
                      MachineFunction &MF);
 
-  void makeSingleEntryLoop(BlockSet &Entries, BlockSet &Blocks,
+  void makeSingleEntryLoop(const BlockSet &Entries, BlockSet &Blocks,
                            MachineFunction &MF, const ReachabilityGraph &Graph);
 
 public:
@@ -261,7 +279,7 @@ bool WebAssemblyFixIrreducibleControlFlow::processRegion(
     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
+      // entries must be in the same SCC, 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
@@ -284,15 +302,8 @@ bool WebAssemblyFixIrreducibleControlFlow::processRegion(
       // 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);
-        }
-      }
+      auto &MutualLoopEntries =
+          Graph.getLoopEntriesForSCC(Graph.getSCCId(LoopEntry));
 
       if (MutualLoopEntries.size() > 1) {
         makeSingleEntryLoop(MutualLoopEntries, Blocks, MF, Graph);
@@ -301,6 +312,7 @@ bool WebAssemblyFixIrreducibleControlFlow::processRegion(
         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
@@ -312,14 +324,22 @@ bool WebAssemblyFixIrreducibleControlFlow::processRegion(
     }
 
     for (auto *LoopEntry : Graph.getLoopEntries()) {
-      LoopBlocks InnerBlocks(LoopEntry, Graph.getLoopEnterers(LoopEntry));
+      BlockSet InnerBlocks;
+
+      auto EntrySCCId = Graph.getSCCId(LoopEntry);
+      for (auto *Block : Blocks) {
+        if (EntrySCCId == Graph.getSCCId(Block)) {
+          InnerBlocks.insert(Block);
+        }
+      }
+
       // 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)) {
+      if (processRegion(LoopEntry, InnerBlocks, MF)) {
         Changed = true;
       }
     }
@@ -335,7 +355,7 @@ bool WebAssemblyFixIrreducibleControlFlow::processRegion(
 // ReachabilityGraph; this will be updated in the caller of this function as
 // needed.
 void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
-    BlockSet &Entries, BlockSet &Blocks, MachineFunction &MF,
+    const BlockSet &Entries, BlockSet &Blocks, MachineFunction &MF,
     const ReachabilityGraph &Graph) {
   assert(Entries.size() >= 2);
 
@@ -401,10 +421,12 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
   // This set stores predecessors within this loop.
   DenseSet<MachineBasicBlock *> InLoop;
   for (auto *Pred : AllPreds) {
+    auto PredSCCId = Graph.getSCCId(Pred);
+
     for (auto *Entry : Pred->successors()) {
       if (!Entries.count(Entry))
         continue;
-      if (Graph.canReach(Entry, Pred)) {
+      if (Graph.getSCCId(Entry) == PredSCCId) {
         InLoop.insert(Pred);
         break;
       }

>From 797eb1327292cb07618a579b2e69486dc2bb7288 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Tue, 17 Feb 2026 21:01:47 -0800
Subject: [PATCH 2/9] Remove needless forward decl. Use `DenseMap::at` in
 `getNode`

---
 .../WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp    | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 90a8a0fe5fcf6..a88997f3b1bb6 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -81,8 +81,6 @@ static BlockVector getSortedEntries(const BlockSet &Entries) {
   return SortedEntries;
 }
 
-class ReachabilityGraph;
-
 struct ReachabilityNode {
   MachineBasicBlock *MBB;
   SmallVector<ReachabilityNode *, 4> Succs;
@@ -134,9 +132,7 @@ class ReachabilityGraph {
   DenseMap<MachineBasicBlock *, ReachabilityNode *> MBBToNodeMap;
 
   ReachabilityNode *getNode(MachineBasicBlock *MBB) const {
-    auto It = MBBToNodeMap.find(MBB);
-    assert(It != MBBToNodeMap.end());
-    return It->second;
+    return MBBToNodeMap.at(MBB);
   }
 
   void calculate();

>From 24c936f4de860470fa7c03a1b25414f466a41d86 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Fri, 20 Feb 2026 11:42:05 -0800
Subject: [PATCH 3/9] Minor tweaks

---
 .../WebAssemblyFixIrreducibleControlFlow.cpp  | 33 +++++++++----------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index a88997f3b1bb6..2cedc7835565e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -18,14 +18,16 @@
 /// The big picture: We recursively process each "region", defined as a group
 /// of blocks with a single entry and no branches back to that entry. A region
 /// may be the entire function body, or the inner part of a loop, i.e., the
-/// loop's body without branches back to the loop entry. In each region we fix
-/// up multi-entry loops by adding a new block that can dispatch to each of the
-/// loop entries, based on the value of a label "helper" variable, and we
-/// replace direct branches to the entries with assignments to the label
-/// variable and a branch to the dispatch block. Then the dispatch block is the
-/// single entry in the loop containing the previous multiple entries. After
-/// ensuring all the loops in a region are reducible, we recurse into them. The
-/// total time complexity of this pass is:
+/// loop's body without branches back to the loop entry. In each region we
+/// identify all the strongly-connected components (SCCs). We fix up multi-entry
+/// loops (SCCs) by adding a new block that can dispatch to each of the loop
+/// entries, based on the value of a label "helper" variable, and we replace
+/// direct branches to the entries with assignments to the label variable and a
+/// branch to the dispatch block. Then the dispatch block is the single entry in
+/// the loop containing the previous multiple entries. Each time we fix some
+/// irreducibility, we recalculate the SCCs. After ensuring all the SCCs in a
+/// region are reducible, we recurse into them. The total time complexity of
+/// this pass is:
 ///
 ///   O(NumBlocks * NumNestedLoops * NumIrreducibleLoops +
 ///     NumLoops * NumLoops)
@@ -141,9 +143,8 @@ class ReachabilityGraph {
 
 namespace llvm {
 template <> struct GraphTraits<ReachabilityGraph *> {
-  typedef ReachabilityNode NodeType;
-  typedef NodeType *NodeRef;
-  typedef SmallVectorImpl<NodeRef>::iterator ChildIteratorType;
+  using NodeRef = ReachabilityNode *;
+  using ChildIteratorType = SmallVectorImpl<NodeRef>::iterator;
 
   static NodeRef getEntryNode(ReachabilityGraph *G) {
     return G->getNode(G->Entry);
@@ -205,12 +206,10 @@ void ReachabilityGraph::calculate() {
     if (SCC.size() == 1) {
       auto &Node = SCC[0];
 
-      if (Node->MBB != Entry) {
-        for (auto *Succ : Node->Succs) {
-          if (Succ == Node) {
-            SelfLoop = true;
-            break;
-          }
+      for (auto *Succ : Node->Succs) {
+        if (Succ == Node) {
+          SelfLoop = true;
+          break;
         }
       }
     }

>From 912a9593f81a9309bfb42e59f86cdfa29c0f45b3 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Fri, 20 Feb 2026 15:57:56 -0800
Subject: [PATCH 4/9] Update time complexity. Use `LoopEntriesBySCC.back()`

---
 .../WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp  | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 2cedc7835565e..0b9c4967149e4 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -27,10 +27,8 @@
 /// the loop containing the previous multiple entries. Each time we fix some
 /// irreducibility, we recalculate the SCCs. After ensuring all the SCCs in a
 /// region are reducible, we recurse into them. The total time complexity of
-/// this pass is:
-///
-///   O(NumBlocks * NumNestedLoops * NumIrreducibleLoops +
-///     NumLoops * NumLoops)
+/// this pass is roughly:
+/// O((NumBlocks + NumEdges) * (NumNestedLoops + NumIrreducibleLoops))
 ///
 /// This pass is similar to what the Relooper [1] does. Both identify looping
 /// code that requires multiple entries, and resolve it in a similar way (in
@@ -193,7 +191,7 @@ void ReachabilityGraph::calculate() {
   unsigned CurrSCCIdx = 0;
   for (auto &SCC : make_range(scc_begin(this), scc_end(this))) {
     LoopEntriesBySCC.push_back({});
-    auto &SCCLoopEntries = LoopEntriesBySCC[CurrSCCIdx];
+    auto &SCCLoopEntries = LoopEntriesBySCC.back();
 
     for (auto *Node : SCC) {
       // Make sure nodes are only ever assigned one SCC

>From 989263c2e07c72cbb943ffa5e6a43f11941b66d9 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Tue, 3 Mar 2026 13:09:55 -0800
Subject: [PATCH 5/9] Fix warning due to unused variable in release/NDEBUG
 builds

---
 .../Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 0b9c4967149e4..0c7acafca0d8b 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -235,10 +235,12 @@ void ReachabilityGraph::calculate() {
     ++CurrSCCIdx;
   }
 
+#ifndef NDEBUG
   // Make sure all nodes have been processed
   for (auto &Node : Nodes) {
     assert(Node.SCCId != std::numeric_limits<unsigned>::max());
   }
+#endif
 }
 
 class WebAssemblyFixIrreducibleControlFlow final : public MachineFunctionPass {

>From 92bb3bd25ed0faf1f29c19895699bbe23e83bca7 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Tue, 3 Mar 2026 13:33:41 -0800
Subject: [PATCH 6/9] Keep isolated orphan blocks from tripping assertions

---
 .../WebAssemblyFixIrreducibleControlFlow.cpp          | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 0c7acafca0d8b..26526712f7556 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -556,6 +556,17 @@ bool WebAssemblyFixIrreducibleControlFlow::runOnMachineFunction(
   // Start the recursive process on the entire function body.
   BlockSet AllBlocks;
   for (auto &MBB : MF) {
+    // Prevent isolated orphan blocks from being processed.
+    //
+    // Even at O0, translation from IR to SelectionDAG or gMIR may optimize away
+    // branches to blocks containing only `unreachable`. Doing so without DCE
+    // orphans the block, which trips our assertion that all MBBs get assigned
+    // an SCC.
+    //
+    // Ignoring these blocks is safe, as they couldn't possibly be irreducible.
+    if (&MBB != &*MF.begin() && MBB.pred_empty() && MBB.succ_empty())
+      continue;
+
     AllBlocks.insert(&MBB);
   }
 

>From b53b29e20a2db4528015161df3da89c2fe7b1ade Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Wed, 4 Mar 2026 12:08:12 -0800
Subject: [PATCH 7/9] Add regression test based on reproducer.

---
 llvm/test/CodeGen/WebAssembly/pr184441.ll | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 llvm/test/CodeGen/WebAssembly/pr184441.ll

diff --git a/llvm/test/CodeGen/WebAssembly/pr184441.ll b/llvm/test/CodeGen/WebAssembly/pr184441.ll
new file mode 100644
index 0000000000000..66ab12083de1a
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/pr184441.ll
@@ -0,0 +1,21 @@
+; RUN: llc < %s -O0 -verify-machineinstrs
+target triple = "wasm32-unknown-unknown"
+
+; Regression test for #184441.
+
+; Ensures that the orphanation of block %b due to the eliding of the
+; `unreachable` default label of the switch during IR => DAG translation
+; (without any subsequent DCE) doesn't trip our assertions in
+; WebAssemblyFixIrreducibleControlFlow, which expect all blocks in a function
+; to be reachable from the function's entry.
+
+define void @test() {
+entry:
+  switch i32 poison, label %b [
+    i32 0, label %a
+  ]
+a:
+  ret void
+b:
+  unreachable
+}

>From 48690ae242c573e7d8ba566a7de3a544621e15ed Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Wed, 4 Mar 2026 12:42:36 -0800
Subject: [PATCH 8/9] Revert "Keep isolated orphan blocks from tripping
 assertions"

This reverts commit 92bb3bd25ed0faf1f29c19895699bbe23e83bca7.
---
 .../WebAssemblyFixIrreducibleControlFlow.cpp          | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 26526712f7556..0c7acafca0d8b 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -556,17 +556,6 @@ bool WebAssemblyFixIrreducibleControlFlow::runOnMachineFunction(
   // Start the recursive process on the entire function body.
   BlockSet AllBlocks;
   for (auto &MBB : MF) {
-    // Prevent isolated orphan blocks from being processed.
-    //
-    // Even at O0, translation from IR to SelectionDAG or gMIR may optimize away
-    // branches to blocks containing only `unreachable`. Doing so without DCE
-    // orphans the block, which trips our assertion that all MBBs get assigned
-    // an SCC.
-    //
-    // Ignoring these blocks is safe, as they couldn't possibly be irreducible.
-    if (&MBB != &*MF.begin() && MBB.pred_empty() && MBB.succ_empty())
-      continue;
-
     AllBlocks.insert(&MBB);
   }
 

>From ed8e671de9fbd3737fa824b8d4df3bd9a6fbe5ac Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Wed, 4 Mar 2026 22:08:51 -0800
Subject: [PATCH 9/9] Add `UnreachableMachineBlockElim` pass running before
 `WebAssemblyFixIrreducibleControlFlow`

---
 llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
index ab1b24a4fd9b1..dce8f54409c41 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -604,6 +604,10 @@ void WebAssemblyPassConfig::addPreEmitPass() {
   // Nullify DBG_VALUE_LISTs that we cannot handle.
   addPass(createWebAssemblyNullifyDebugValueLists());
 
+  // Remove any unreachable blocks that may be left floating around.
+  // Rare, but possible. Needed for WebAssemblyFixIrreducibleControlFlow.
+  addPass(&UnreachableMachineBlockElimID);
+
   // Eliminate multiple-entry loops.
   if (!WasmDisableFixIrreducibleControlFlowPass)
     addPass(createWebAssemblyFixIrreducibleControlFlow());



More information about the llvm-commits mailing list