[llvm] [WebAssembly] Handle irreducible EH pad backedges (PR #204631)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 7 01:37:01 PDT 2026
https://github.com/ParkHanbum updated https://github.com/llvm/llvm-project/pull/204631
>From f3a29b2abc61c00a75bdc9c8a2fe2294c60734ff Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Thu, 18 Jun 2026 23:39:54 +0900
Subject: [PATCH 1/3] initial implmenetation
---
.../WebAssemblyFixIrreducibleControlFlow.cpp | 89 ++++++++++++++++++-
1 file changed, 88 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index 5b769bf4e4d7f..ca807884e4526 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -55,6 +55,7 @@
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
#include "WebAssembly.h"
#include "WebAssemblySubtarget.h"
+#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SCCIterator.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunction.h"
@@ -260,6 +261,87 @@ class WebAssemblyFixIrreducibleControlFlowLegacy final
WebAssemblyFixIrreducibleControlFlowLegacy() : MachineFunctionPass(ID) {}
};
+static bool entriesHasEHPad(const BlockSet &Entries) {
+ return llvm::any_of(
+ Entries, [](const MachineBasicBlock *MBB) { return MBB->isEHPad(); });
+}
+
+// Clone MBB for the PredMBB -> MBB edge while preserving the cloned block's
+// outgoing fallthrough behavior.
+static MachineBasicBlock *cloneBlockForPredecessor(MachineBasicBlock *MBB,
+ MachineBasicBlock *PredMBB,
+ MachineFunction *MF) {
+ assert(PredMBB->isSuccessor(MBB) &&
+ "cannot clone edge: PredMBB does not branch to MBB");
+
+ MachineBasicBlock *FallThrough = MBB->getFallThrough(false);
+ bool PredFallsThrough = PredMBB->isLayoutSuccessor(MBB);
+ MachineBasicBlock *Clone = MF->CreateMachineBasicBlock(MBB->getBasicBlock());
+ Clone->setIsEHPad(MBB->isEHPad());
+
+ if (PredFallsThrough)
+ MF->insert(std::next(PredMBB->getIterator()), Clone);
+ else
+ MF->push_back(Clone);
+
+ for (const auto &LiveIn : MBB->liveins())
+ Clone->addLiveIn(LiveIn);
+
+ for (auto *Succ : MBB->successors())
+ Clone->addSuccessor(Succ);
+
+ for (const MachineInstr &MI : *MBB) {
+ MachineInstr *NewMI = MF->CloneMachineInstr(&MI);
+ if (NewMI->getOpcode() == WebAssembly::EH_LABEL) {
+ MCSymbol *Sym = MF->getContext().createTempSymbol();
+ NewMI->getOperand(0).ChangeToMCSymbol(Sym);
+ }
+ Clone->push_back(NewMI);
+ }
+
+ for (MachineInstr &MI : PredMBB->terminators())
+ for (MachineOperand &MO : MI.operands())
+ if (MO.isMBB() && MO.getMBB() == MBB)
+ MO.setMBB(Clone);
+
+ if (FallThrough && !Clone->isLayoutSuccessor(FallThrough)) {
+ const auto &TII = *MF->getSubtarget<WebAssemblySubtarget>().getInstrInfo();
+ BuildMI(Clone, DebugLoc(), TII.get(WebAssembly::BR)).addMBB(FallThrough);
+ }
+ PredMBB->replaceSuccessor(MBB, Clone);
+
+ return Clone;
+}
+
+static void makeSingleEntryLoopEHPad(
+ const BlockSet &Entries, BlockSet &Blocks, MachineFunction &MF,
+ const ReachabilityGraph &Graph) {
+ SmallSetVector<std::pair<MachineBasicBlock *, MachineBasicBlock *>, 8>
+ PredEHPadEdges;
+
+ // Clone only normal predecessors inside the same SCC. Other predecessors keep
+ // entering the original EH pad.
+ for (auto Entry : Entries) {
+ if (Entry->isEHPad())
+ for (auto Pred : Entry->predecessors())
+ if (Blocks.count(Pred) && !Pred->isEHPad() &&
+ Graph.getSCCId(Pred) == Graph.getSCCId(Entry))
+ PredEHPadEdges.insert({Pred, Entry});
+ }
+ assert(!PredEHPadEdges.empty() &&
+ "EH pad irreducibility requires a clonable in-SCC predecessor");
+
+ for (const auto &Edge : PredEHPadEdges) {
+ auto *Pred = Edge.first;
+ auto *EHPadEntry = Edge.second;
+
+ MachineBasicBlock *CloneMBB =
+ cloneBlockForPredecessor(EHPadEntry, Pred, &MF);
+
+ Blocks.insert(CloneMBB);
+ }
+}
+
// 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
@@ -269,6 +351,7 @@ class WebAssemblyFixIrreducibleControlFlowLegacy final
void makeSingleEntryLoop(const BlockSet &Entries, BlockSet &Blocks,
MachineFunction &MF, const ReachabilityGraph &Graph) {
assert(Entries.size() >= 2);
+ assert(!entriesHasEHPad(Entries) && "Entries must not have EHPad");
// Sort the entries to ensure a deterministic build.
BlockVector SortedEntries = getSortedEntries(Entries);
@@ -453,7 +536,11 @@ bool processRegion(MachineBasicBlock *Entry, BlockSet &Blocks,
Graph.getLoopEntriesForSCC(Graph.getSCCId(LoopEntry));
if (MutualLoopEntries.size() > 1) {
- makeSingleEntryLoop(MutualLoopEntries, Blocks, MF, Graph);
+ if (entriesHasEHPad(MutualLoopEntries)) {
+ makeSingleEntryLoopEHPad(MutualLoopEntries, Blocks, MF, Graph);
+ } else {
+ makeSingleEntryLoop(MutualLoopEntries, Blocks, MF, Graph);
+ }
FoundIrreducibility = true;
Changed = true;
break;
>From e78d7fd3fa98e2eded802bb69eb461c083262f4e Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Thu, 18 Jun 2026 17:27:38 +0900
Subject: [PATCH 2/3] [WebAssembly] Handle irreducible EH pad backedges
Previously, EH pad entries could enter normal dispatch lowering.
This keeps EH pad entries out of dispatch lowering.
EH pad backedges use the clone path.
EH successors stay direct-to-EH-pad edges.
Fixed: #63183
Assisted-by: Codex (GPT-5)
---
.../WebAssemblyFixIrreducibleControlFlow.cpp | 303 +++--
.../irreducible-eh-pad-backedge.ll | 1047 +++++++++++++++++
2 files changed, 1278 insertions(+), 72 deletions(-)
create mode 100644 llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
index ca807884e4526..b38dd154c1d7c 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixIrreducibleControlFlow.cpp
@@ -15,7 +15,7 @@
/// it linearizes control flow, turning diamonds into two triangles, which is
/// both unnecessary and undesirable for WebAssembly.
///
-/// The big picture: We recursively process each "region", defined as a group
+/// The big picture: We recursively process each "region", WebAssembly/WebAssemblyFixIrreducibleControlFlow.cppdefined 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
@@ -55,7 +55,7 @@
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
#include "WebAssembly.h"
#include "WebAssemblySubtarget.h"
-#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SCCIterator.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunction.h"
@@ -261,87 +261,235 @@ class WebAssemblyFixIrreducibleControlFlowLegacy final
WebAssemblyFixIrreducibleControlFlowLegacy() : MachineFunctionPass(ID) {}
};
-static bool entriesHasEHPad(const BlockSet &Entries) {
- return llvm::any_of(
- Entries, [](const MachineBasicBlock *MBB) { return MBB->isEHPad(); });
+static bool hasEHPadEntry(const BlockSet &Entries) {
+ return llvm::any_of(Entries,
+ [](MachineBasicBlock *MBB) { return MBB->isEHPad(); });
}
-// Clone MBB for the PredMBB -> MBB edge while preserving the cloned block's
-// outgoing fallthrough behavior.
-static MachineBasicBlock *cloneBlockForPredecessor(MachineBasicBlock *MBB,
- MachineBasicBlock *PredMBB,
- MachineFunction *MF) {
- assert(PredMBB->isSuccessor(MBB) &&
- "cannot clone edge: PredMBB does not branch to MBB");
-
- MachineBasicBlock *FallThrough = MBB->getFallThrough(false);
- bool PredFallsThrough = PredMBB->isLayoutSuccessor(MBB);
- MachineBasicBlock *Clone = MF->CreateMachineBasicBlock(MBB->getBasicBlock());
- Clone->setIsEHPad(MBB->isEHPad());
-
- if (PredFallsThrough)
- MF->insert(std::next(PredMBB->getIterator()), Clone);
- else
- MF->push_back(Clone);
-
- for (const auto &LiveIn : MBB->liveins())
- Clone->addLiveIn(LiveIn);
-
- for (auto *Succ : MBB->successors())
- Clone->addSuccessor(Succ);
-
- for (const MachineInstr &MI : *MBB) {
- MachineInstr *NewMI = MF->CloneMachineInstr(&MI);
- if (NewMI->getOpcode() == WebAssembly::EH_LABEL) {
- MCSymbol *Sym = MF->getContext().createTempSymbol();
- NewMI->getOperand(0).ChangeToMCSymbol(Sym);
- }
- Clone->push_back(NewMI);
+static void cloneInstrIntoBlock(MachineInstr &MI, MachineBasicBlock *Clone,
+ MachineFunction &MF,
+ const TargetInstrInfo &TII) {
+ // EH_LABEL contains an MCSymbol for exception ranges. Do not reuse the same
+ // symbol in both the original and cloned EH pad.
+ if (MI.getOpcode() == WebAssembly::EH_LABEL) {
+ MCSymbol *Sym = MF.getContext().createTempSymbol();
+ BuildMI(*Clone, Clone->end(), MI.getDebugLoc(),
+ TII.get(WebAssembly::EH_LABEL))
+ .addSym(Sym);
+ return;
}
- for (MachineInstr &MI : PredMBB->terminators())
- for (MachineOperand &MO : MI.operands())
- if (MO.isMBB() && MO.getMBB() == MBB)
- MO.setMBB(Clone);
+ MachineInstr *NewMI = MF.CloneMachineInstr(&MI);
+ Clone->push_back(NewMI);
+}
- if (FallThrough && !Clone->isLayoutSuccessor(FallThrough)) {
- const auto &TII = *MF->getSubtarget<WebAssemblySubtarget>().getInstrInfo();
- BuildMI(Clone, DebugLoc(), TII.get(WebAssembly::BR)).addMBB(FallThrough);
+static bool explicitlyBranchesTo(const MachineBasicBlock *MBB,
+ const MachineBasicBlock *Target) {
+ for (const MachineInstr &Term : MBB->terminators()) {
+ for (const MachineOperand &MO : Term.explicit_uses()) {
+ if (MO.isMBB() && MO.getMBB() == Target)
+ return true;
+ }
}
- PredMBB->replaceSuccessor(MBB, Clone);
+ return false;
+}
+
+static MachineBasicBlock *
+getImplicitFallthroughSuccessor(MachineBasicBlock *MBB) {
+ auto Next = std::next(MBB->getIterator());
+ if (Next == MBB->getParent()->end())
+ return nullptr;
- return Clone;
+ MachineBasicBlock *LayoutSucc = &*Next;
+
+ // If the next block is not a successor, there is no fallthrough successor.
+ if (!MBB->isSuccessor(LayoutSucc))
+ return nullptr;
+
+ // Do not create an explicit branch to an EH pad. EH successors must remain
+ // exceptional successors, not normal branch targets.
+ if (LayoutSucc->isEHPad())
+ return nullptr;
+
+ // Already explicit, not a fallthrough successor.
+ if (explicitlyBranchesTo(MBB, LayoutSucc))
+ return nullptr;
+
+ return LayoutSucc;
}
-static void makeSingleEntryLoopEHPad(
- const BlockSet &Entries, BlockSet &Blocks, MachineFunction &MF,
- const ReachabilityGraph &Graph) {
- SmallSetVector<std::pair<MachineBasicBlock *, MachineBasicBlock *>, 8>
- PredEHPadEdges;
-
- // Clone only normal predecessors inside the same SCC. Other predecessors keep
- // entering the original EH pad.
- for (auto Entry : Entries) {
- if (Entry->isEHPad())
- for (auto Pred : Entry->predecessors())
- if (Blocks.count(Pred) && !Pred->isEHPad() &&
- Graph.getSCCId(Pred) == Graph.getSCCId(Entry))
- PredEHPadEdges.insert({Pred, Entry});
+static void makeFallthroughExplicitIfNeeded(MachineBasicBlock *MBB,
+ const TargetInstrInfo &TII) {
+ MachineBasicBlock *Fallthrough = getImplicitFallthroughSuccessor(MBB);
+ if (!Fallthrough)
+ return;
+
+ // MBB currently reaches Fallthrough by layout fallthrough. Preserve that
+ // edge before inserting another block after MBB.
+ BuildMI(*MBB, MBB->end(), DebugLoc(), TII.get(WebAssembly::BR))
+ .addMBB(Fallthrough);
+}
+
+static void redirectSuccessor(MachineBasicBlock *Pred,
+ MachineBasicBlock *OldSucc,
+ MachineBasicBlock *NewSucc) {
+ // Explicit MBB operands, if any.
+ for (MachineInstr &Term : Pred->terminators()) {
+ for (MachineOperand &MO : Term.explicit_uses()) {
+ if (MO.isMBB() && MO.getMBB() == OldSucc)
+ MO.setMBB(NewSucc);
+ }
}
- assert(!PredEHPadEdges.empty() &&
- "EH pad irreducibility requires a clonable in-SCC predecessor");
- for (const auto &Edge : PredEHPadEdges) {
- auto *Pred = Edge.first;
- auto *EHPadEntry = Edge.second;
+ // CFG successor list.
+ Pred->replaceSuccessor(OldSucc, NewSucc);
+}
- MachineBasicBlock *CloneMBB =
- cloneBlockForPredecessor(EHPadEntry, Pred, &MF);
+/// Fix irreducible SCCs whose entries include an EH pad without routing
+/// exceptional edges through normal dispatch blocks.
+///
+/// Before:
+///
+/// external EH
+/// |
+/// v
+/// original.catch [EHPad] ---> body <--- external normal
+/// ^ |
+/// | | unwind
+/// +----------------------+
+///
+/// After:
+///
+/// external EH
+/// |
+/// v
+/// original.catch [EHPad] ---> body <--- external normal
+/// |
+/// | unwind
+/// v
+/// cloned.catch [EHPad]
+/// |
+/// +---> original EH continuation
+///
+/// 1. Detect:
+/// irreducible SCC entries contain an EH pad entry.
+///
+/// 2. Trace EH backedges:
+/// same-SCC normal predecessor -> EH pad entry
+///
+/// 3. Prepare for cloning:
+/// remember the original EH pad fallthrough and make the predecessor's
+/// fallthrough explicit before inserting a clone.
+///
+/// 4. Clone EH pad:
+/// copy EH flags, live-ins, instructions, successors, give cloned
+/// EH_LABELs fresh symbols, and materialize the remembered fallthrough.
+///
+/// 5. Redirect:
+/// rewrite the in-loop exceptional edge to target the cloned EH pad.
+///
+/// This preserves the machine verifier invariant that exceptional successors
+/// remain direct-to-EHPad while turning the loop body into a single-entry loop.
+static bool cloneEHPadEntriesForBackedges(const BlockSet &Entries,
+ BlockSet &Blocks, MachineFunction &MF,
+ const ReachabilityGraph &Graph) {
+ const TargetInstrInfo &TII = *MF.getSubtarget().getInstrInfo();
+
+ // (1) This EH-specific path is selected only for irreducible SCC entries
+ // that include an EH pad.
+ assert(hasEHPadEntry(Entries) && "Expected an EH pad entry");
+
+ bool Changed = false;
+
+ for (MachineBasicBlock *EHPadEntry : getSortedEntries(Entries)) {
+ if (!EHPadEntry->isEHPad())
+ continue;
+
+ const unsigned SCCId = Graph.getSCCId(EHPadEntry);
+
+ SmallVector<MachineBasicBlock *, 4> InLoopPreds;
+ // (2) Find only same-SCC normal predecessors. External EH predecessors
+ // must continue to target the original EH pad.
+ for (MachineBasicBlock *Pred : EHPadEntry->predecessors()) {
+ if (!Blocks.count(Pred))
+ continue;
+ if (Graph.getSCCId(Pred) != SCCId)
+ continue;
- Blocks.insert(CloneMBB);
+ // Keep this first implementation conservative. The target pattern is:
+ //
+ // normal throwing block -> EHPad entry
+ //
+ // Do not try to clone paths from an EHPad predecessor yet.
+ if (Pred->isEHPad())
+ continue;
+
+ InLoopPreds.push_back(Pred);
+ }
+
+ for (MachineBasicBlock *Pred : InLoopPreds) {
+ LLVM_DEBUG({
+ dbgs() << "Cloning EH pad entry for irreducible EH backedge: ";
+ Pred->printName(dbgs());
+ dbgs() << " -> ";
+ EHPadEntry->printName(dbgs());
+ dbgs() << "\n";
+ });
+
+ // (3) Remember fallthroughs before changing block layout. If Pred falls
+ // through to the next layout block, inserting the clone immediately after
+ // Pred would otherwise break that edge.
+ MachineBasicBlock *EHPadFallthrough =
+ getImplicitFallthroughSuccessor(EHPadEntry);
+ makeFallthroughExplicitIfNeeded(Pred, TII);
+
+ // (4) Clone the EH pad before rewiring the exceptional edge.
+ MachineBasicBlock *Clone =
+ MF.CreateMachineBasicBlock(EHPadEntry->getBasicBlock());
+ MF.insert(std::next(Pred->getIterator()), Clone);
+
+ Clone->setIsEHPad(EHPadEntry->isEHPad());
+ Clone->setIsEHScopeEntry(EHPadEntry->isEHScopeEntry());
+ Clone->setIsEHFuncletEntry(EHPadEntry->isEHFuncletEntry());
+ Clone->setIsCleanupFuncletEntry(EHPadEntry->isCleanupFuncletEntry());
+
+ // Keep virtual registers unchanged because EH continuations are shared
+ // and may use values defined in either EH pad.
+ for (const MachineBasicBlock::RegisterMaskPair &LI :
+ EHPadEntry->liveins())
+ Clone->addLiveIn(LI);
+
+ for (MachineInstr &MI : *EHPadEntry)
+ cloneInstrIntoBlock(MI, Clone, MF, TII);
+
+ for (auto SI = EHPadEntry->succ_begin(), SE = EHPadEntry->succ_end();
+ SI != SE; ++SI)
+ Clone->copySuccessor(EHPadEntry, SI);
+
+ // (4) The cloned EH pad is not laid out before the original fallthrough
+ // successor, so make that edge explicit if the original used fallthrough.
+ if (EHPadFallthrough)
+ BuildMI(*Clone, Clone->end(), DebugLoc(), TII.get(WebAssembly::BR))
+ .addMBB(EHPadFallthrough);
+
+ Blocks.insert(Clone);
+
+ // (5) Keep the exceptional successor direct-to-EHPad. We change the
+ // target from the original EH pad to its clone, not to a routing block.
+ redirectSuccessor(Pred, EHPadEntry, Clone);
+
+ LLVM_DEBUG({
+ dbgs() << " created cloned EH pad ";
+ Clone->printName(dbgs());
+ dbgs() << "\n";
+ });
+
+ Changed = true;
+ }
}
-}
+ return Changed;
+}
// 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
@@ -351,7 +499,6 @@ static void makeSingleEntryLoopEHPad(
void makeSingleEntryLoop(const BlockSet &Entries, BlockSet &Blocks,
MachineFunction &MF, const ReachabilityGraph &Graph) {
assert(Entries.size() >= 2);
- assert(!entriesHasEHPad(Entries) && "Entries must not have EHPad");
// Sort the entries to ensure a deterministic build.
BlockVector SortedEntries = getSortedEntries(Entries);
@@ -482,9 +629,15 @@ void makeSingleEntryLoop(const BlockSet &Entries, BlockSet &Blocks,
if (Op.isMBB() && Indices.count(Op.getMBB()))
Op.setMBB(Map[{Op.getMBB(), PredInLoop}]);
- for (auto *Succ : Pred->successors()) {
+ for (MachineBasicBlock *Succ :
+ llvm::make_early_inc_range(Pred->successors())) {
if (!Entries.count(Succ))
continue;
+
+ assert(
+ !Succ->isEHPad() &&
+ "EH pad entries must not be routed through normal dispatch blocks");
+
auto *Routing = Map[{Succ, PredInLoop}];
Pred->replaceSuccessor(Succ, Routing);
}
@@ -536,8 +689,14 @@ bool processRegion(MachineBasicBlock *Entry, BlockSet &Blocks,
Graph.getLoopEntriesForSCC(Graph.getSCCId(LoopEntry));
if (MutualLoopEntries.size() > 1) {
- if (entriesHasEHPad(MutualLoopEntries)) {
- makeSingleEntryLoopEHPad(MutualLoopEntries, Blocks, MF, Graph);
+ if (hasEHPadEntry(MutualLoopEntries)) {
+ // EH pad entries cannot be routed through the normal dispatch block
+ // strategy, so split the in-loop EH backedges instead.
+ if (!cloneEHPadEntriesForBackedges(MutualLoopEntries, Blocks, MF,
+ Graph))
+ // Cannot safely fix this SCC; preserve changes made to earlier
+ // SCCs.
+ return Changed;
} else {
makeSingleEntryLoop(MutualLoopEntries, Blocks, MF, Graph);
}
diff --git a/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll b/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll
new file mode 100644
index 0000000000000..c79fd7a2e4fd5
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll
@@ -0,0 +1,1047 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=wasm32-unknown-unknown -wasm-enable-eh -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs | FileCheck %s
+
+; Test irreducible CFG handling when one of the loop entries is an EH pad.
+;
+; The normal irreducible-CFG fix routes entries through a dispatch block.
+; That is not valid for EH pad entries, because exceptional successors must
+; target EH pads directly.
+;
+; These tests use verifier-valid funclet EH IR:
+; - invoke unwind targets are catchswitch blocks, not catchpad blocks.
+; - invokes inside catchpads have funclet bundles.
+; - unwind edges out of a catchpad use the same unwind destination as the
+; parent catchswitch.
+
+target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
+target triple = "wasm32-unknown-unknown"
+
+ at g = external global i32
+
+declare void @foo()
+declare void @bar(ptr)
+declare ptr @llvm.wasm.get.exception(token)
+declare i32 @__gxx_wasm_personality_v0(...)
+
+; -----------------------------------------------------------------------------
+; Simple EH-shaped irreducible loop.
+;
+; Machine-level shape:
+;
+; inner.catch [EHPad] -> bb7
+; ^ |
+; | |
+; +---- unwind -----+
+;
+; bb7 also has an external predecessor through outer.catch -> outer.cont -> bb7.
+
+define void @eh_test0_simple() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test0_simple:
+; CHECK: .Lfunc_begin0:
+; CHECK-NEXT: .functype eh_test0_simple () -> ()
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp0: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp1: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label0
+; CHECK-NEXT: .LBB0_1: # %outer.catch
+; CHECK-NEXT: .Ltmp2: # EH_LABEL
+; CHECK-NEXT: .Ltmp3: # EH_LABEL
+; CHECK-NEXT: catch_all # catch2:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp4: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: .LBB0_3: # %inner.catch
+; CHECK-NEXT: end_try # label2:
+; CHECK-NEXT: .Ltmp5: # EH_LABEL
+; CHECK-NEXT: catch_all # catch1:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: .LBB0_4: # %bb7
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label1:
+; CHECK-NEXT: loop # label3:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp6: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp7: # EH_LABEL
+; CHECK-NEXT: .LBB0_5: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB0_4 Depth=1
+; CHECK-NEXT: .Ltmp8: # EH_LABEL
+; CHECK-NEXT: catch_all # catch4:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label3
+; CHECK-NEXT: .LBB0_6: # %exit
+; CHECK-NEXT: end_try # label4:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label0:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %bb7
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ catchret from %inner.pad to label %bb7
+
+bb7:
+ invoke void @foo()
+ to label %exit unwind label %inner.dispatch
+}
+
+; -----------------------------------------------------------------------------
+; Inner natural loop before entering the throwing block.
+
+define void @eh_test1_inner_loop() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test1_inner_loop:
+; CHECK: .Lfunc_begin1:
+; CHECK-NEXT: .functype eh_test1_inner_loop () -> ()
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp9: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp10: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label5
+; CHECK-NEXT: .LBB1_1: # %outer.catch
+; CHECK-NEXT: .Ltmp11: # EH_LABEL
+; CHECK-NEXT: .Ltmp12: # EH_LABEL
+; CHECK-NEXT: catch_all # catch7:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp13: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: .LBB1_3: # %preinner
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: loop # label8:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: i32.eqz
+; CHECK-NEXT: br_if 0 # 0: up to label8
+; CHECK-NEXT: .LBB1_4: # %inner.catch
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_try # label7:
+; CHECK-NEXT: .Ltmp14: # EH_LABEL
+; CHECK-NEXT: catch_all # catch6:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: .LBB1_5: # %body
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label6:
+; CHECK-NEXT: loop # label9:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp15: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp16: # EH_LABEL
+; CHECK-NEXT: .LBB1_6: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB1_5 Depth=1
+; CHECK-NEXT: .Ltmp17: # EH_LABEL
+; CHECK-NEXT: catch_all # catch10:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label9
+; CHECK-NEXT: .LBB1_7: # %exit
+; CHECK-NEXT: end_try # label10:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label5:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %preinner
+
+preinner:
+ %v0 = load volatile i32, ptr @g
+ %c0 = icmp eq i32 %v0, 0
+ br i1 %c0, label %preinner, label %body
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ catchret from %inner.pad to label %body
+
+body:
+ invoke void @foo()
+ to label %exit unwind label %inner.dispatch
+}
+
+; -----------------------------------------------------------------------------
+; Multiple in-loop throwing predecessors unwind to the same catchswitch.
+
+define void @eh_test2_multiple_backedges() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test2_multiple_backedges:
+; CHECK: .Lfunc_begin2:
+; CHECK-NEXT: .functype eh_test2_multiple_backedges () -> ()
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp18: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp19: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label11
+; CHECK-NEXT: .LBB2_1: # %outer.catch
+; CHECK-NEXT: .Ltmp20: # EH_LABEL
+; CHECK-NEXT: .Ltmp21: # EH_LABEL
+; CHECK-NEXT: catch_all # catch13:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp22: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: .LBB2_3: # %inner.catch
+; CHECK-NEXT: end_try # label13:
+; CHECK-NEXT: .Ltmp23: # EH_LABEL
+; CHECK-NEXT: catch_all # catch12:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: .LBB2_4: # %A
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label12:
+; CHECK-NEXT: loop # label14:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp24: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp25: # EH_LABEL
+; CHECK-NEXT: .LBB2_5: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB2_4 Depth=1
+; CHECK-NEXT: .Ltmp28: # EH_LABEL
+; CHECK-NEXT: catch_all # catch15:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label14
+; CHECK-NEXT: .LBB2_6: # %B
+; CHECK-NEXT: # in Loop: Header=BB2_4 Depth=1
+; CHECK-NEXT: end_try # label15:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp26: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp27: # EH_LABEL
+; CHECK-NEXT: br 1 # 1: up to label14
+; CHECK-NEXT: .LBB2_7: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB2_4 Depth=1
+; CHECK-NEXT: .Ltmp29: # EH_LABEL
+; CHECK-NEXT: catch_all # catch16:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label14
+; CHECK-NEXT: .LBB2_8: # %exit
+; CHECK-NEXT: end_try # label16:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label11:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %A
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ catchret from %inner.pad to label %A
+
+A:
+ invoke void @foo()
+ to label %B unwind label %inner.dispatch
+
+B:
+ invoke void @foo()
+ to label %A unwind label %inner.dispatch
+}
+
+; -----------------------------------------------------------------------------
+; If/else structure mixed into the EH irreducible region.
+
+define void @eh_test3_if_else() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test3_if_else:
+; CHECK: .Lfunc_begin3:
+; CHECK-NEXT: .functype eh_test3_if_else () -> ()
+; CHECK-NEXT: .local i32, i32, i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp30: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp31: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label17
+; CHECK-NEXT: .LBB3_1: # %outer.catch
+; CHECK-NEXT: .Ltmp32: # EH_LABEL
+; CHECK-NEXT: .Ltmp33: # EH_LABEL
+; CHECK-NEXT: catch_all # catch19:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp34: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: # %bb.3:
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: .LBB3_4: # %inner.catch
+; CHECK-NEXT: end_try # label19:
+; CHECK-NEXT: .Ltmp35: # EH_LABEL
+; CHECK-NEXT: catch_all # catch18:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 2
+; CHECK-NEXT: # %bb.5:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: .LBB3_6: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label18:
+; CHECK-NEXT: loop # label20:
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: br_table {0, 1, 1} # 1: down to label22
+; CHECK-NEXT: .LBB3_7: # %head
+; CHECK-NEXT: # in Loop: Header=BB3_6 Depth=1
+; CHECK-NEXT: end_block # label23:
+; CHECK-NEXT: block
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: br_if 0 # 0: down to label24
+; CHECK-NEXT: # %bb.8: # %then
+; CHECK-NEXT: # in Loop: Header=BB3_6 Depth=1
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: i32.eqz
+; CHECK-NEXT: br_if 2 # 2: down to label21
+; CHECK-NEXT: .LBB3_9: # in Loop: Header=BB3_6 Depth=1
+; CHECK-NEXT: end_block # label24:
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 2 # 2: up to label20
+; CHECK-NEXT: .LBB3_10: # %body
+; CHECK-NEXT: # in Loop: Header=BB3_6 Depth=1
+; CHECK-NEXT: end_block # label22:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp36: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp37: # EH_LABEL
+; CHECK-NEXT: br 3 # 3: down to label17
+; CHECK-NEXT: .LBB3_11: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB3_6 Depth=1
+; CHECK-NEXT: .Ltmp38: # EH_LABEL
+; CHECK-NEXT: catch_all # catch25:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 2
+; CHECK-NEXT: .LBB3_12: # in Loop: Header=BB3_6 Depth=1
+; CHECK-NEXT: end_try # label25:
+; CHECK-NEXT: end_block # label21:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 0 # 0: up to label20
+; CHECK-NEXT: .LBB3_13: # %exit
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label17:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %body
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ catchret from %inner.pad to label %head
+
+head:
+ %v0 = load volatile i32, ptr @g
+ %c0 = icmp eq i32 %v0, 0
+ br i1 %c0, label %then, label %body
+
+then:
+ %v1 = load volatile i32, ptr @g
+ %c1 = icmp eq i32 %v1, 0
+ br i1 %c1, label %head, label %body
+
+body:
+ invoke void @foo()
+ to label %exit unwind label %inner.dispatch
+}
+
+; -----------------------------------------------------------------------------
+; Two independent EH-shaped irreducible regions in one function. The pass should
+; fix one, recompute SCCs, and then fix the other.
+
+define void @eh_test4_two_regions(i1 %cond) personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test4_two_regions:
+; CHECK: .Lfunc_begin4:
+; CHECK-NEXT: .functype eh_test4_two_regions (i32) -> ()
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: block
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.eqz
+; CHECK-NEXT: br_if 0 # 0: down to label28
+; CHECK-NEXT: # %bb.1: # %r0.entry
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp47: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp48: # EH_LABEL
+; CHECK-NEXT: br 3 # 3: down to label26
+; CHECK-NEXT: .LBB4_2: # %r0.outer.catch
+; CHECK-NEXT: .Ltmp49: # EH_LABEL
+; CHECK-NEXT: .Ltmp50: # EH_LABEL
+; CHECK-NEXT: catch_all # catch29:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp51: # EH_LABEL
+; CHECK-NEXT: # %bb.3: # %r0.outer.cont
+; CHECK-NEXT: br 2 # 2: down to label27
+; CHECK-NEXT: .LBB4_4: # %r1.entry
+; CHECK-NEXT: end_try # label29:
+; CHECK-NEXT: end_block # label28:
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp39: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp40: # EH_LABEL
+; CHECK-NEXT: br 3 # 3: down to label26
+; CHECK-NEXT: .LBB4_5: # %r1.outer.catch
+; CHECK-NEXT: .Ltmp41: # EH_LABEL
+; CHECK-NEXT: .Ltmp42: # EH_LABEL
+; CHECK-NEXT: catch_all # catch31:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp43: # EH_LABEL
+; CHECK-NEXT: # %bb.6: # %r1.outer.cont
+; CHECK-NEXT: .LBB4_7: # %r1.inner.catch
+; CHECK-NEXT: end_try # label31:
+; CHECK-NEXT: .Ltmp44: # EH_LABEL
+; CHECK-NEXT: catch_all # catch30:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: .LBB4_8: # %r1.body
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label30:
+; CHECK-NEXT: loop # label32:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp45: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp46: # EH_LABEL
+; CHECK-NEXT: br 3 # 3: down to label26
+; CHECK-NEXT: .LBB4_9: # %r1.inner.catch
+; CHECK-NEXT: # in Loop: Header=BB4_8 Depth=1
+; CHECK-NEXT: .Ltmp56: # EH_LABEL
+; CHECK-NEXT: catch_all # catch33:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label32
+; CHECK-NEXT: .LBB4_10: # %r0.inner.catch
+; CHECK-NEXT: end_try # label33:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: .Ltmp52: # EH_LABEL
+; CHECK-NEXT: catch_all # catch27:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: .LBB4_11: # %r0.body
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label27:
+; CHECK-NEXT: loop # label34:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp53: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp54: # EH_LABEL
+; CHECK-NEXT: .LBB4_12: # %r0.inner.catch
+; CHECK-NEXT: # in Loop: Header=BB4_11 Depth=1
+; CHECK-NEXT: .Ltmp55: # EH_LABEL
+; CHECK-NEXT: catch_all # catch35:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label34
+; CHECK-NEXT: .LBB4_13: # %join
+; CHECK-NEXT: end_try # label35:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label26:
+; CHECK-NEXT: # fallthrough-return
+entry:
+ br i1 %cond, label %r0.entry, label %r1.entry
+
+r0.entry:
+ invoke void @foo()
+ to label %r0.exit unwind label %r0.outer.dispatch
+
+r0.exit:
+ br label %join
+
+r0.outer.dispatch:
+ %r0.outer = catchswitch within none [label %r0.outer.catch] unwind label %r0.inner.dispatch
+
+r0.outer.catch:
+ %r0.outer.pad = catchpad within %r0.outer [ptr null]
+ invoke void @foo() [ "funclet"(token %r0.outer.pad) ]
+ to label %r0.outer.cont unwind label %r0.inner.dispatch
+
+r0.outer.cont:
+ catchret from %r0.outer.pad to label %r0.body
+
+r0.inner.dispatch:
+ %r0.inner = catchswitch within none [label %r0.inner.catch] unwind to caller
+
+r0.inner.catch:
+ %r0.inner.pad = catchpad within %r0.inner [ptr null]
+ catchret from %r0.inner.pad to label %r0.body
+
+r0.body:
+ invoke void @foo()
+ to label %join unwind label %r0.inner.dispatch
+
+r1.entry:
+ invoke void @foo()
+ to label %r1.exit unwind label %r1.outer.dispatch
+
+r1.exit:
+ br label %join
+
+r1.outer.dispatch:
+ %r1.outer = catchswitch within none [label %r1.outer.catch] unwind label %r1.inner.dispatch
+
+r1.outer.catch:
+ %r1.outer.pad = catchpad within %r1.outer [ptr null]
+ invoke void @foo() [ "funclet"(token %r1.outer.pad) ]
+ to label %r1.outer.cont unwind label %r1.inner.dispatch
+
+r1.outer.cont:
+ catchret from %r1.outer.pad to label %r1.body
+
+r1.inner.dispatch:
+ %r1.inner = catchswitch within none [label %r1.inner.catch] unwind to caller
+
+r1.inner.catch:
+ %r1.inner.pad = catchpad within %r1.inner [ptr null]
+ catchret from %r1.inner.pad to label %r1.body
+
+r1.body:
+ invoke void @foo()
+ to label %join unwind label %r1.inner.dispatch
+
+join:
+ ret void
+}
+
+; -----------------------------------------------------------------------------
+; Interacting normal loops sharing part of the EH-shaped irreducible region.
+
+define void @eh_test5_interacting_loops() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test5_interacting_loops:
+; CHECK: .Lfunc_begin5:
+; CHECK-NEXT: .functype eh_test5_interacting_loops () -> ()
+; CHECK-NEXT: .local i32, i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp57: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp58: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label36
+; CHECK-NEXT: .LBB5_1: # %outer.catch
+; CHECK-NEXT: .Ltmp59: # EH_LABEL
+; CHECK-NEXT: .Ltmp60: # EH_LABEL
+; CHECK-NEXT: catch_all # catch38:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp61: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: # %bb.3:
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: .LBB5_4: # %inner.catch
+; CHECK-NEXT: end_try # label38:
+; CHECK-NEXT: .Ltmp62: # EH_LABEL
+; CHECK-NEXT: catch_all # catch37:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: # %bb.5:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: .LBB5_6: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label37:
+; CHECK-NEXT: loop # label39:
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: br_table {0, 1, 1} # 1: down to label43
+; CHECK-NEXT: .LBB5_7: # %for.cond
+; CHECK-NEXT: # in Loop: Header=BB5_6 Depth=1
+; CHECK-NEXT: end_block # label44:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: br_if 1 # 1: down to label42
+; CHECK-NEXT: br 2 # 2: down to label41
+; CHECK-NEXT: .LBB5_8: # %for.body
+; CHECK-NEXT: # in Loop: Header=BB5_6 Depth=1
+; CHECK-NEXT: end_block # label43:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp63: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp64: # EH_LABEL
+; CHECK-NEXT: .LBB5_9: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB5_6 Depth=1
+; CHECK-NEXT: .Ltmp65: # EH_LABEL
+; CHECK-NEXT: catch_all # catch45:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 3 # 3: down to label40
+; CHECK-NEXT: .LBB5_10: # %side.loop
+; CHECK-NEXT: # in Loop: Header=BB5_6 Depth=1
+; CHECK-NEXT: end_try # label45:
+; CHECK-NEXT: end_block # label42:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: i32.eqz
+; CHECK-NEXT: br_if 1 # 1: down to label40
+; CHECK-NEXT: .LBB5_11: # in Loop: Header=BB5_6 Depth=1
+; CHECK-NEXT: end_block # label41:
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 1 # 1: up to label39
+; CHECK-NEXT: .LBB5_12: # in Loop: Header=BB5_6 Depth=1
+; CHECK-NEXT: end_block # label40:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 0 # 0: up to label39
+; CHECK-NEXT: .LBB5_13: # %exit
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label36:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %for.body
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ catchret from %inner.pad to label %for.cond
+
+for.cond:
+ %v0 = load volatile i32, ptr @g
+ %c0 = icmp eq i32 %v0, 0
+ br i1 %c0, label %for.body, label %side.loop
+
+for.body:
+ invoke void @foo()
+ to label %side.loop unwind label %inner.dispatch
+
+side.loop:
+ %v1 = load volatile i32, ptr @g
+ %c1 = icmp eq i32 %v1, 0
+ br i1 %c1, label %for.cond, label %for.body
+}
+
+; -----------------------------------------------------------------------------
+; Negative/reducible control case. This has EH, but no normal external entry
+; into the EH backedge SCC.
+
+define void @eh_test6_reducible_control() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test6_reducible_control:
+; CHECK: .Lfunc_begin6:
+; CHECK-NEXT: .functype eh_test6_reducible_control () -> ()
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp66: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp67: # EH_LABEL
+; CHECK-NEXT: .LBB6_1: # %catch
+; CHECK-NEXT: .Ltmp68: # EH_LABEL
+; CHECK-NEXT: catch_all # catch46:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: # %bb.2: # %done
+; CHECK-NEXT: return
+; CHECK-NEXT: .LBB6_3: # %exit
+; CHECK-NEXT: end_try # label46:
+; CHECK-NEXT: # fallthrough-return
+entry:
+ invoke void @foo()
+ to label %exit unwind label %cs.dispatch
+
+exit:
+ ret void
+
+cs.dispatch:
+ %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+ %pad = catchpad within %cs [ptr null]
+ catchret from %pad to label %done
+
+done:
+ ret void
+}
+
+; -----------------------------------------------------------------------------
+; Nested normal loop around an EH-shaped irreducible region.
+
+define void @eh_test7_nested() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test7_nested:
+; CHECK: .Lfunc_begin7:
+; CHECK-NEXT: .functype eh_test7_nested () -> ()
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp69: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp70: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label47
+; CHECK-NEXT: .LBB7_1: # %outer.catch
+; CHECK-NEXT: .Ltmp71: # EH_LABEL
+; CHECK-NEXT: .Ltmp72: # EH_LABEL
+; CHECK-NEXT: catch_all # catch49:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp73: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: .LBB7_3: # %inner.catch
+; CHECK-NEXT: end_try # label49:
+; CHECK-NEXT: .Ltmp74: # EH_LABEL
+; CHECK-NEXT: catch_all # catch48:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: .LBB7_4: # %inner.entry.preheader
+; CHECK-NEXT: # =>This Loop Header: Depth=1
+; CHECK-NEXT: # Child Loop BB7_5 Depth 2
+; CHECK-NEXT: end_try # label48:
+; CHECK-NEXT: loop # label50:
+; CHECK-NEXT: .LBB7_5: # %inner.entry
+; CHECK-NEXT: # Parent Loop BB7_4 Depth=1
+; CHECK-NEXT: # => This Inner Loop Header: Depth=2
+; CHECK-NEXT: loop # label51:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: i32.eqz
+; CHECK-NEXT: br_if 0 # 0: up to label51
+; CHECK-NEXT: # %bb.6: # %body
+; CHECK-NEXT: # in Loop: Header=BB7_4 Depth=1
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp75: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp76: # EH_LABEL
+; CHECK-NEXT: .LBB7_7: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB7_4 Depth=1
+; CHECK-NEXT: .Ltmp77: # EH_LABEL
+; CHECK-NEXT: catch_all # catch52:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: br 1 # 1: up to label50
+; CHECK-NEXT: .LBB7_8: # %exit
+; CHECK-NEXT: end_try # label52:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label47:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %inner.entry
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ catchret from %inner.pad to label %outer.loop
+
+outer.loop:
+ br label %inner.entry
+
+inner.entry:
+ %v0 = load volatile i32, ptr @g
+ %c0 = icmp eq i32 %v0, 0
+ br i1 %c0, label %inner.back, label %body
+
+inner.back:
+ br label %inner.entry
+
+body:
+ invoke void @foo()
+ to label %exit unwind label %inner.dispatch
+}
+
+; -----------------------------------------------------------------------------
+; The cloned EH pad must preserve its own fallthrough successor, and values
+; defined in the cloned EH pad must remain usable in shared EH continuation
+; blocks.
+
+define void @eh_test8_branching_ehpad_liveout() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: eh_test8_branching_ehpad_liveout:
+; CHECK: .Lfunc_begin8:
+; CHECK-NEXT: .functype eh_test8_branching_ehpad_liveout () -> ()
+; CHECK-NEXT: .local i32, i32, i32
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: local.set 0
+; CHECK-NEXT: block
+; CHECK-NEXT: try
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp78: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp79: # EH_LABEL
+; CHECK-NEXT: br 2 # 2: down to label53
+; CHECK-NEXT: .LBB8_1: # %outer.catch
+; CHECK-NEXT: .Ltmp80: # EH_LABEL
+; CHECK-NEXT: .Ltmp81: # EH_LABEL
+; CHECK-NEXT: catch_all # catch55:
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp82: # EH_LABEL
+; CHECK-NEXT: # %bb.2: # %outer.cont
+; CHECK-NEXT: # %bb.3:
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: .LBB8_4: # %inner.catch
+; CHECK-NEXT: end_try # label55:
+; CHECK-NEXT: .Ltmp83: # EH_LABEL
+; CHECK-NEXT: catch __cpp_exception # catch54:
+; CHECK-NEXT: local.set 2
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: block
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: br_if 0 # 0: down to label56
+; CHECK-NEXT: # %bb.5:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 1 # 1: down to label54
+; CHECK-NEXT: .LBB8_6:
+; CHECK-NEXT: end_block # label56:
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: .LBB8_7: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: end_try # label54:
+; CHECK-NEXT: loop # label57:
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: block
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: br_table {0, 1, 3, 3} # 1: down to label60
+; CHECK-NEXT: # 3: down to label58
+; CHECK-NEXT: .LBB8_8: # %catch.use
+; CHECK-NEXT: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: end_block # label61:
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: call bar
+; CHECK-NEXT: br 1 # 1: down to label59
+; CHECK-NEXT: .LBB8_9: # %catch.use2
+; CHECK-NEXT: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: end_block # label60:
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: call bar
+; CHECK-NEXT: .LBB8_10: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: end_block # label59:
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 1 # 1: up to label57
+; CHECK-NEXT: .LBB8_11: # %body
+; CHECK-NEXT: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: end_block # label58:
+; CHECK-NEXT: try
+; CHECK-NEXT: .Ltmp84: # EH_LABEL
+; CHECK-NEXT: call foo
+; CHECK-NEXT: .Ltmp85: # EH_LABEL
+; CHECK-NEXT: .LBB8_12: # %inner.catch
+; CHECK-NEXT: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: .Ltmp86: # EH_LABEL
+; CHECK-NEXT: catch __cpp_exception # catch62:
+; CHECK-NEXT: local.set 2
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: global.set __stack_pointer
+; CHECK-NEXT: block
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: i32.load g
+; CHECK-NEXT: br_if 0 # 0: down to label63
+; CHECK-NEXT: # %bb.13: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 2 # 2: up to label57
+; CHECK-NEXT: .LBB8_14: # in Loop: Header=BB8_7 Depth=1
+; CHECK-NEXT: end_block # label63:
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: local.set 1
+; CHECK-NEXT: br 1 # 1: up to label57
+; CHECK-NEXT: .LBB8_15: # %exit
+; CHECK-NEXT: end_try # label62:
+; CHECK-NEXT: end_loop
+; CHECK-NEXT: end_block # label53:
+; CHECK-NEXT: unreachable
+entry:
+ invoke void @foo()
+ to label %exit unwind label %outer.dispatch
+
+exit:
+ unreachable
+
+outer.dispatch:
+ %outer = catchswitch within none [label %outer.catch] unwind label %inner.dispatch
+
+outer.catch:
+ %outer.pad = catchpad within %outer [ptr null]
+ invoke void @foo() [ "funclet"(token %outer.pad) ]
+ to label %outer.cont unwind label %inner.dispatch
+
+outer.cont:
+ catchret from %outer.pad to label %body
+
+inner.dispatch:
+ %inner = catchswitch within none [label %inner.catch] unwind to caller
+
+inner.catch:
+ %inner.pad = catchpad within %inner [ptr null]
+ %exn = call ptr @llvm.wasm.get.exception(token %inner.pad)
+ %v0 = load volatile i32, ptr @g
+ %c0 = icmp eq i32 %v0, 0
+ br i1 %c0, label %catch.use, label %catch.use2
+
+catch.use:
+ call void @bar(ptr %exn) [ "funclet"(token %inner.pad) ]
+ catchret from %inner.pad to label %body
+
+catch.use2:
+ call void @bar(ptr %exn) [ "funclet"(token %inner.pad) ]
+ catchret from %inner.pad to label %body
+
+body:
+ invoke void @foo()
+ to label %exit unwind label %inner.dispatch
+}
>From a9f705335a0e729f4cbba57a758a570e7a7a303a Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 19 Jun 2026 01:35:26 +0900
Subject: [PATCH 3/3] Add CFG figure for testcase clarity
---
.../irreducible-eh-pad-backedge.ll | 73 ++++++++++++++++---
1 file changed, 63 insertions(+), 10 deletions(-)
diff --git a/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll b/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll
index c79fd7a2e4fd5..a2a4247c65f5d 100644
--- a/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll
+++ b/llvm/test/CodeGen/WebAssembly/irreducible-eh-pad-backedge.ll
@@ -24,17 +24,37 @@ declare ptr @llvm.wasm.get.exception(token)
declare i32 @__gxx_wasm_personality_v0(...)
; -----------------------------------------------------------------------------
-; Simple EH-shaped irreducible loop.
+; simple CFG
;
-; Machine-level shape:
+; ┌────────────────────────────┐
+; │ entry │
+; │ save SP │
+; │ call foo │
+; └───────┬─────────────┬──────┘
+; │normal │exception
+; v v
+; exit outer.catch
+; │
+; │ call foo
+; ┌────────────┴────────────┐
+; │normal │exception
+; v v
+; inner.catch.original inner.catch.original
+; │
+; v
+; ┌────────────────────────────┐
+; │ bb7 loop header │◄──────────────┐
+; │ try { │ │
+; │ call foo │ │
+; │ } │ │
+; └───────┬─────────────┬──────┘ │
+; │normal │exception │
+; v v │
+; exit inner.catch.clone │
+; │ │
+; │ restore SP │
+; └──────── br loop ──────┘
;
-; inner.catch [EHPad] -> bb7
-; ^ |
-; | |
-; +---- unwind -----+
-;
-; bb7 also has an external predecessor through outer.catch -> outer.cont -> bb7.
-
define void @eh_test0_simple() personality ptr @__gxx_wasm_personality_v0 {
; CHECK-LABEL: eh_test0_simple:
; CHECK: .Lfunc_begin0:
@@ -117,7 +137,40 @@ bb7:
; -----------------------------------------------------------------------------
; Inner natural loop before entering the throwing block.
-
+;
+; ┌────────────────────────────┐
+; │ entry │
+; │ save SP │
+; │ call foo │
+; └───────┬─────────────┬──────┘
+; │normal │exception
+; v v
+; exit outer.catch
+; │
+; │ call foo
+; ┌────────────┴────────────┐
+; │normal │exception
+; v v
+; preinner loop inner.catch.original
+; │ │
+; │ │ restore SP
+; │ v
+; └──────────────────────► body
+; ▲
+; │
+; ┌────────────────────────────┐ │
+; │ body loop header │◄───┘
+; │ try { │
+; │ call foo │
+; │ } │
+; └───────┬─────────────┬──────┘
+; │normal │exception
+; v v
+; exit inner.catch.clone
+; │
+; │ restore SP
+; └──────── br loop ──────► body
+;
define void @eh_test1_inner_loop() personality ptr @__gxx_wasm_personality_v0 {
; CHECK-LABEL: eh_test1_inner_loop:
; CHECK: .Lfunc_begin1:
More information about the llvm-commits
mailing list