[llvm] [WebAssembly] Replace Reachability with SCCs in Irreducible CFG Fixer (PR #179722)

Kamil Jakubus via llvm-commits llvm-commits at lists.llvm.org
Wed Feb 4 23:23:49 PST 2026


https://github.com/jkbz64 updated https://github.com/llvm/llvm-project/pull/179722

>From 30acaf4394bfa3e6e961993d77ee9064b3e592da Mon Sep 17 00:00:00 2001
From: Kamil Jakubus <kamil.jakubus at usagi.coffee>
Date: Wed, 4 Feb 2026 19:04:16 +0100
Subject: [PATCH 1/2] [WebAssembly] Replace Reachability with SCCs in
 Irreducible CFG Fixer

Replace reachability-based loop analysis with SCC-based
detection in the WebAssembly irreducible control-flow fixer.

Assisted-By: codex
Co-Authored-By: Demetrius Kanios <demetrius at kanios.net>
---
 .../WebAssemblyFixIrreducibleControlFlow.cpp  | 152 ++++++++++++------
 1 file changed, 106 insertions(+), 46 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 07171d472dc2d..65df95f43ad12 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -58,6 +58,7 @@
 #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,7 +79,7 @@ static BlockVector getSortedEntries(const BlockSet &Entries) {
   return SortedEntries;
 }
 
-// Calculates reachability in a region. Ignores branches to blocks outside of
+// Analyzes SCC 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 {
@@ -98,16 +99,8 @@ 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.
+  // that are in a non-trivial SCC or have a self-loop.
   const BlockSet &getLoopers() const { return Loopers; }
 
   // Get all blocks that are loop entries.
@@ -120,65 +113,134 @@ class ReachabilityGraph {
     assert(I != LoopEnterers.end());
     return I->second;
   }
+  unsigned getSCCId(MachineBasicBlock *MBB) const {
+    return SccId[getIndex(MBB)];
+  }
 
 private:
   MachineBasicBlock *Entry;
   const BlockSet &Blocks;
+  DenseMap<int, unsigned> BlockIndex;
 
   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;
+  // Per-node adjacency in the region (excluding edges to Entry).
+  SmallVector<SmallVector<unsigned, 4>, 0> Succs;
+  SmallVector<bool, 0> SelfLoop;
+  SmallVector<unsigned, 0> SccId;
+  SmallVector<unsigned, 0> SccSize;
+
+  unsigned getIndex(MachineBasicBlock *MBB) const {
+    auto It = BlockIndex.find(MBB->getNumber());
+    assert(It != BlockIndex.end());
+    return It->second;
+  }
 
   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;
+    auto NumBlocks = Blocks.size();
+    Succs.assign(NumBlocks, {});
+    SelfLoop.assign(NumBlocks, false);
+
+    unsigned MMBIdx = 0;
+    BlockIndex.clear();
+    BlockIndex.reserve(NumBlocks);
+    for (auto *MBB : Blocks)
+      BlockIndex[MBB->getNumber()] = MMBIdx++;
 
     // Add all relevant direct branches.
+    MMBIdx = 0;
     for (auto *MBB : Blocks) {
       for (auto *Succ : MBB->successors()) {
         if (Succ != Entry && inRegion(Succ)) {
-          Reachable[MBB].insert(Succ);
-          WorkList.emplace_back(MBB, Succ);
+          if (Succ == MBB)
+            SelfLoop[MMBIdx] = true;
+          Succs[MMBIdx].push_back(getIndex(Succ));
         }
       }
+
+      ++MMBIdx;
     }
 
-    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);
+    // Tarjan SCC (iterative) on the region graph.
+    SccId.assign(NumBlocks, std::numeric_limits<unsigned>::max());
+    SccSize.clear();
+    SmallVector<int, 0> Index(NumBlocks, -1);
+    SmallVector<int, 0> Lowlink(NumBlocks, 0);
+    SmallVector<unsigned, 0> Stack;
+    SmallVector<bool, 0> OnStack(NumBlocks, false);
+    int NextIndex = 0;
+
+    struct Frame {
+      unsigned V;
+      unsigned NextSucc;
+    };
+    SmallVector<Frame, 0> DFS;
+
+    auto pushNode = [&](unsigned V) {
+      Index[V] = Lowlink[V] = NextIndex++;
+      Stack.push_back(V);
+      OnStack[V] = true;
+      DFS.push_back({V, 0});
+    };
+
+    for (unsigned V = 0; V < NumBlocks; ++V) {
+      if (Index[V] != -1)
+        continue;
+      pushNode(V);
+      while (!DFS.empty()) {
+        Frame &F = DFS.back();
+        unsigned Cur = F.V;
+        if (F.NextSucc < Succs[Cur].size()) {
+          unsigned W = Succs[Cur][F.NextSucc++];
+          if (Index[W] == -1) {
+            pushNode(W);
+          } else if (OnStack[W]) {
+            Lowlink[Cur] = std::min(Lowlink[Cur], Index[W]);
+          }
+          continue;
+        }
+
+        // Finished exploring Cur.
+        if (Lowlink[Cur] == Index[Cur]) {
+          unsigned Size = 0;
+          while (true) {
+            unsigned W = Stack.pop_back_val();
+            OnStack[W] = false;
+            SccId[W] = SccSize.size();
+            ++Size;
+            if (W == Cur)
+              break;
           }
+          SccSize.push_back(Size);
+        }
+
+        DFS.pop_back();
+        if (!DFS.empty()) {
+          unsigned Parent = DFS.back().V;
+          Lowlink[Parent] = std::min(Lowlink[Parent], Lowlink[Cur]);
         }
       }
     }
 
-    // Blocks that can return to themselves are in a loop.
+    // Blocks that are in a loop are those in non-trivial SCCs or self-loops.
+    MMBIdx = 0;
     for (auto *MBB : Blocks) {
-      if (canReach(MBB, MBB)) {
+      if (MBB != Entry && (SccSize[SccId[MMBIdx]] > 1 || SelfLoop[MMBIdx])) {
         Loopers.insert(MBB);
       }
+      ++MMBIdx;
     }
     assert(!Loopers.count(Entry));
 
-    // Find the loop entries - loopers reachable from blocks not in that loop -
+    // Find the loop entries - loopers with predecessors outside their SCC -
     // and those outside blocks that reach them, the "loop enterers".
     for (auto *Looper : Loopers) {
+      unsigned LoopScc = SccId[getIndex(Looper)];
       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)) {
+        if (SccId[getIndex(Pred)] != LoopScc) {
           LoopEntries.insert(Looper);
           LoopEnterers[Looper].insert(Pred);
         }
@@ -258,10 +320,16 @@ bool WebAssemblyFixIrreducibleControlFlow::processRegion(
 
     bool FoundIrreducibility = false;
 
-    for (auto *LoopEntry : getSortedEntries(Graph.getLoopEntries())) {
+    BlockVector SortedLoopEntries = getSortedEntries(Graph.getLoopEntries());
+    DenseMap<unsigned, BlockSet> EntriesByScc;
+    EntriesByScc.reserve(SortedLoopEntries.size());
+    for (auto *LoopEntry : SortedLoopEntries)
+      EntriesByScc[Graph.getSCCId(LoopEntry)].insert(LoopEntry);
+
+    for (auto *LoopEntry : SortedLoopEntries) {
       // 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 +352,7 @@ 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 = EntriesByScc[Graph.getSCCId(LoopEntry)];
 
       if (MutualLoopEntries.size() > 1) {
         makeSingleEntryLoop(MutualLoopEntries, Blocks, MF, Graph);
@@ -404,7 +464,7 @@ void WebAssemblyFixIrreducibleControlFlow::makeSingleEntryLoop(
     for (auto *Entry : Pred->successors()) {
       if (!Entries.count(Entry))
         continue;
-      if (Graph.canReach(Entry, Pred)) {
+      if (Graph.getSCCId(Entry) == Graph.getSCCId(Pred)) {
         InLoop.insert(Pred);
         break;
       }

>From 59b2f9863218ce1eb6063e4dccd20685bb6a47ea Mon Sep 17 00:00:00 2001
From: Kamil Jakubus <kamil.jakubus at usagi.coffee>
Date: Thu, 5 Feb 2026 08:11:36 +0100
Subject: [PATCH 2/2] Fix potential ordering issue by using mbb as key in dense
 map

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

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 65df95f43ad12..aa6b4a74e7925 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -120,7 +120,7 @@ class ReachabilityGraph {
 private:
   MachineBasicBlock *Entry;
   const BlockSet &Blocks;
-  DenseMap<int, unsigned> BlockIndex;
+  DenseMap<MachineBasicBlock *, unsigned> BlockIndex;
 
   BlockSet Loopers, LoopEntries;
   DenseMap<MachineBasicBlock *, BlockSet> LoopEnterers;
@@ -134,7 +134,7 @@ class ReachabilityGraph {
   SmallVector<unsigned, 0> SccSize;
 
   unsigned getIndex(MachineBasicBlock *MBB) const {
-    auto It = BlockIndex.find(MBB->getNumber());
+    auto It = BlockIndex.find(MBB);
     assert(It != BlockIndex.end());
     return It->second;
   }
@@ -148,7 +148,7 @@ class ReachabilityGraph {
     BlockIndex.clear();
     BlockIndex.reserve(NumBlocks);
     for (auto *MBB : Blocks)
-      BlockIndex[MBB->getNumber()] = MMBIdx++;
+      BlockIndex[MBB] = MMBIdx++;
 
     // Add all relevant direct branches.
     MMBIdx = 0;



More information about the llvm-commits mailing list