From 507c84cb93f4b50d9ba9197a082920b70959652d Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Mar 2026 11:26:39 -0700 Subject: [PATCH 1/7] initial workup --- src/coreclr/jit/block.h | 3 +- src/coreclr/jit/compiler.h | 37 +++++++++++++++++++++++ src/coreclr/jit/fgdiagnostic.cpp | 10 ++++++ src/coreclr/jit/fgwasm.cpp | 5 +-- src/coreclr/jit/flowgraph.cpp | 52 ++++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index 1ab4a449912f9e..40d6e0f1f62bea 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -445,7 +445,8 @@ enum BasicBlockFlags : uint64_t BBF_HAS_NEWARR = MAKE_BBFLAG(34), // BB contains 'new' of an array type. BBF_MAY_HAVE_BOUNDS_CHECKS = MAKE_BBFLAG(35), // BB *likely* has a bounds check (after rangecheck phase). BBF_ASYNC_RESUMPTION = MAKE_BBFLAG(36), // Block is a resumption block in an async method - BBF_THROW_HELPER = MAKE_BBFLAG(37), // Block is a call to a throw helper + BBF_CATCH_RESUMPTION = MAKE_BBFLAG(37), // Block is a resumption from a catch + BBF_THROW_HELPER = MAKE_BBFLAG(38), // Block is a call to a throw helper // The following are sets of flags. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a0c79914d7b328..49d2fe9f4876a6 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2253,7 +2253,13 @@ class FlowGraphTryRegion EHblkDsc* m_ehDsc; BitVec m_blocks; + // Edges from blocks outside the try region to blocks inside the try region. + // This includes edges from any catch handler, and edges from some ancestor + // region to some decendant region. + jitstd::vector m_entryEdges; + bool m_requiresRuntimeResumption; + bool m_hasSideEntry; FlowGraphTryRegion(EHblkDsc* ehDsc, FlowGraphTryRegions* regions); @@ -2262,11 +2268,21 @@ class FlowGraphTryRegion m_requiresRuntimeResumption = true; } + void SetHasSideEntry() + { + m_hasSideEntry = true; + } + bool IsMutualProtectWith(FlowGraphTryRegion* other) { return EHblkDsc::ebdIsSameTry(this->m_ehDsc, other->m_ehDsc); } + void AddEntryEdge(FlowEdge* edge) + { + m_entryEdges.push_back(edge); + } + public: template @@ -2279,6 +2295,11 @@ class FlowGraphTryRegion return m_ehDsc->HasCatchHandler(); } + const jitstd::vector& EntryEdges() + { + return m_entryEdges; + } + // True if resumption from a catch in this or in an enclosed // try region requires runtime support. // @@ -2287,6 +2308,13 @@ class FlowGraphTryRegion return m_requiresRuntimeResumption; } + // True if control can enter the try via some block other than the header block. + // + bool HasSideEntry() const + { + return m_hasSideEntry; + } + #ifdef DEBUG static void Dump(FlowGraphTryRegion* region); #endif @@ -2305,6 +2333,12 @@ class FlowGraphTryRegions unsigned m_numRegions; unsigned m_numTryCatchRegions; bool m_tryRegionsIncludeHandlerBlocks; + bool m_hasMultipleEntryTryRegions; + + void SetHasMultipleEntryTryRegions() + { + m_hasMultipleEntryTryRegions = true; + } public: @@ -2329,6 +2363,8 @@ class FlowGraphTryRegions bool TryRegionsIncludeHandlerBlocks() const { return m_tryRegionsIncludeHandlerBlocks; } + bool HasMultipleEntryTryRegions() const { return m_hasMultipleEntryTryRegions; } + #ifdef DEBUG static void Dump(FlowGraphTryRegions* regions); #endif @@ -5347,6 +5383,7 @@ class Compiler #ifdef TARGET_WASM jitstd::vector* fgWasmIntervals = nullptr; BasicBlock** fgIndexToBlockMap = nullptr; + bool fgWasmHasCatchResumptions = false; #endif FlowGraphDfsTree* m_dfsTree = nullptr; diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 275d0775045cab..4b9cb46c989056 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -2757,6 +2757,16 @@ bool BBPredsChecker::CheckEhTryDsc(BasicBlock* block, BasicBlock* blockPred, EHb return true; } +#if defined(TARGET_WASM) + // Catch resumptions are allowed to jump into try blocks at any point. + // They are transients during Wasm control flow restructuring. + // + if (m_compiler->fgWasmHasCatchResumptions && blockPred->HasFlag(BBF_CATCH_RESUMPTION)) + { + return true; + } +#endif // defined(TARGET_WASM) + JITDUMP("Jump into the middle of try region: " FMT_BB " branches to " FMT_BB "\n", blockPred->bbNum, block->bbNum); assert(!"Jump into middle of try region"); return false; diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index aa8c89fd522ab5..c490036f61c2df 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -2049,10 +2049,11 @@ PhaseStatus Compiler::fgWasmEhFlow() if (commonEnclosingTryIndex == innermostDispatchingTryIndex) { - JITDUMP("Continuation " FMT_BB " is within dispatching try EH#%02u; cannot handle this case yet\n", + JITDUMP("Continuation " FMT_BB " is within dispatching try EH#%02u, marking as catch resumption\n", continuationBlock->bbNum, innermostDispatchingTryIndex); - NYI_WASM("WasmEHFlow: continuation is within dispatching try"); + fgWasmHasCatchResumptions = true; + continuationBlock->SetFlags(BBF_CATCH_RESUMPTION); } ArrayStack* catchRetBlocks = getCatchRetBlocksForTryRegion(innermostDispatchingTryIndex); diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index f8c7c1dbb7a3e5..9c84528b15adab 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -7599,7 +7599,9 @@ FlowGraphTryRegion::FlowGraphTryRegion(EHblkDsc* ehDsc, FlowGraphTryRegions* reg , m_parent(nullptr) , m_ehDsc(ehDsc) , m_blocks(BitVecOps::UninitVal()) + , m_entryEdges(regions->GetCompiler()->getAllocator(CMK_BasicBlock)) , m_requiresRuntimeResumption(false) + , m_hasSideEntry(false) { BitVecTraits traits = regions->GetBlockBitVecTraits(); m_blocks = BitVecOps::MakeEmpty(&traits); @@ -7683,6 +7685,40 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree { BitVecOps::AddElemD(&traits, region->m_blocks, block->bbPostorderNum); region = region->m_parent; + + // Enumerate block's pred edges to find the try entry edges. + // + for (FlowEdge* const edge : block->PredEdges()) + { + BasicBlock* const predBlock = edge->getSourceBlock(); + + if (comp->bbInTryRegions(tryIndex, predBlock)) + { + continue; + } + + // "Normal" entry edges + // + if (block == dsc->ebdTryBeg) + { + region->AddEntryEdge(edge); + continue; + } + + // Runtime async edges and catch resumption edges + // + if (block->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)) + { + region->AddEntryEdge(edge); + region->SetHasSideEntry(); + regions->SetHasMultipleEntryTryRegions(); + continue; + } + + JITDUMP("Unexpected try region entry edge from " FMT_BB " to " FMT_BB "\n", predBlock->bbNum, + block->bbNum); + assert(!"Unexpected try region entry edge"); + } } } else @@ -7754,6 +7790,22 @@ void FlowGraphTryRegion::Dump(FlowGraphTryRegion* region) printf(" " FMT_BB, block->bbNum); return BasicBlockVisit::Continue; }); + + printf(" [entries]: "); + for (FlowEdge* const edge : region->EntryEdges()) + { + BasicBlock* const predBlock = edge->getSourceBlock(); + BasicBlock* const succBlock = edge->getDestinationBlock(); + printf(" " FMT_BB "->" FMT_BB, predBlock->bbNum, succBlock->bbNum); + if (succBlock->HasFlag(BBF_ASYNC_RESUMPTION)) + { + printf("[async]"); + } + if (succBlock->HasFlag(BBF_CATCH_RESUMPTION)) + { + printf("[catch]"); + } + } } //------------------------------------------------------------------------ From 1235991ae0cb40b2d60b132e5feae0f045b544bf Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Mar 2026 14:54:38 -0700 Subject: [PATCH 2/7] add scc logic --- src/coreclr/jit/compiler.h | 10 +++++++- src/coreclr/jit/fgwasm.h | 45 +++++++++++++++++++++++++++++++++-- src/coreclr/jit/flowgraph.cpp | 24 +++++++++++++++++-- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 49d2fe9f4876a6..c166e88f339748 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2273,7 +2273,7 @@ class FlowGraphTryRegion m_hasSideEntry = true; } - bool IsMutualProtectWith(FlowGraphTryRegion* other) + bool IsMutualProtectWith(FlowGraphTryRegion* other) const { return EHblkDsc::ebdIsSameTry(this->m_ehDsc, other->m_ehDsc); } @@ -2315,6 +2315,13 @@ class FlowGraphTryRegion return m_hasSideEntry; } + FlowGraphTryRegion* EnclosingRegion() const; + + BasicBlock* GetHeaderBlock() const + { + return m_ehDsc->ebdTryBeg; + } + #ifdef DEBUG static void Dump(FlowGraphTryRegion* region); #endif @@ -5384,6 +5391,7 @@ class Compiler jitstd::vector* fgWasmIntervals = nullptr; BasicBlock** fgIndexToBlockMap = nullptr; bool fgWasmHasCatchResumptions = false; + FlowGraphTryRegions* fgTryRegions = nullptr; #endif FlowGraphDfsTree* m_dfsTree = nullptr; diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index 4f0a4e60aeb4bb..b72766690004f9 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -418,8 +418,8 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc switch (block->GetKind()) { - // Funclet returns have no successors - // + // Funclet returns have no successors + // case BBJ_EHFINALLYRET: case BBJ_EHCATCHRET: case BBJ_EHFILTERRET: @@ -479,6 +479,47 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc default: unreached(); } + + // If the compiler has a try region object, add back edges from any + // catch resumption or async resumption to the header of each enclosing + // try with catch handler. + // + // This makes multi-entry try regions look like multi-entry loops and the SCC + // algorithm will transform them into single-entry try regions. + // + // Note we disregard try/finally/fault here as those do not need to be expressed + // as single-entry regions for Wasm codegen. And we consider all mutual-protect + // try/catch as a single region. + // + FlowGraphTryRegions* const tryRegions = comp->fgTryRegions; + + if ((tryRegions != nullptr) && block->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)) + { + EHblkDsc* const dsc = comp->ehGetBlockTryDsc(block); + FlowGraphTryRegion* region = tryRegions->GetTryRegionByHeader(dsc->ebdTryBeg); + + while (region != nullptr) + { + if (region->HasCatchHandler()) + { + if (!region->HasSideEntry()) + { + break; + } + + BasicBlock* const header = region->GetHeaderBlock(); + for (FlowEdge* const edge : region->EntryEdges()) + { + if (block == edge->getDestinationBlock()) + { + RETURN_ON_ABORT(func(header)); + break; + } + } + } + region = region->EnclosingRegion(); + } + } } #undef RETURN_ON_ABORT diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 9c84528b15adab..69e08ef3fd0645 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -7747,8 +7747,10 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree //------------------------------------------------------------------------ // FlowGraphTryRegion::NumBlocks: Return the number of blocks in the try region. // -// Arguments: -// region -- region of interest +// Returns: +// Number of blcoks in the region at the time of FlowGraphTryRegions::Build +// Includes blocks in enclosed try regions. Includes blocks in enclosed +// handler regions, if FlowGraphTryRegions::Build was called with includeHandlerBlocks == true. // unsigned FlowGraphTryRegion::NumBlocks() const { @@ -7756,6 +7758,24 @@ unsigned FlowGraphTryRegion::NumBlocks() const return BitVecOps::Count(&traits, m_blocks); } +//------------------------------------------------------------------------ +// FlowGraphTryRegion::EnclosingRegion: Return the enclosing non-mutual-protect +// region, or nullptr. +// +// Returns: +// Enclosing non-mutual-protect region, or nullptr if there is none. +// Note any enclosing region will have a distinct header block. +// +FlowGraphTryRegion* FlowGraphTryRegion::EnclosingRegion() const +{ + FlowGraphTryRegion* ancestor = m_parent; + while (ancestor != nullptr && IsMutualProtectWith(ancestor)) + { + ancestor = ancestor->m_parent; + } + return ancestor; +} + #ifdef DEBUG //------------------------------------------------------------------------ From a1b619c72bb09ad85fd5947bb281d817eeac4ab0 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Mar 2026 17:30:06 -0700 Subject: [PATCH 3/7] plausible if inefficient --- src/coreclr/jit/fgwasm.cpp | 19 +++++++++++++++++++ src/coreclr/jit/flowgraph.cpp | 12 +++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index c490036f61c2df..27d62a806524c3 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -74,6 +74,21 @@ FlowGraphDfsTree* FgWasm::WasmDfs(bool& hasBlocksOnlyReachableViaEH) Compiler* const comp = Comp(); comp->fgInvalidateDfsTree(); + // IIf we have EH, build the try region structure. This requires a DFS so try region + // membership can use compressed BV indices. We need this before computing the WasmDFS + // (it's ok if this initial DFS is a "full DFS" -- it can also be used to enumerate the + // try region blocks in postorder, but we won't need it for that purpose here). + // + // TODO: find some way to avoid needing this temporary DFS. + // + if (comp->compHndBBtabCount > 0) + { + FlowGraphDfsTree* tempDfs = comp->fgComputeDfs(); + FlowGraphTryRegions* tryRegions = FlowGraphTryRegions::Build(comp, tempDfs); + comp->fgTryRegions = tryRegions; + comp->fgInvalidateDfsTree(); + } + BasicBlock** postOrder = new (comp, CMK_WasmCfgLowering) BasicBlock*[comp->fgBBcount]; bool hasCycle = false; @@ -1078,6 +1093,10 @@ PhaseStatus Compiler::fgWasmControlFlow() FlowGraphTryRegions* tryRegions = FlowGraphTryRegions::Build(this, dfsTree); JITDUMPEXEC(FlowGraphTryRegions::Dump(tryRegions)); + // We should have fixed any multiple-entry try during SCC processing + // + assert(!tryRegions->HasMultipleEntryTryRegions()); + // Our interval ends are at the starts of blocks, so we need a block that // comes after all existing blocks. So allocate one extra slot. // diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 69e08ef3fd0645..7decedbe8e73bb 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -7684,7 +7684,6 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree while (region != nullptr) { BitVecOps::AddElemD(&traits, region->m_blocks, block->bbPostorderNum); - region = region->m_parent; // Enumerate block's pred edges to find the try entry edges. // @@ -7709,6 +7708,10 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree // if (block->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)) { + JITDUMP("Found %s resumption edge from " FMT_BB " to " FMT_BB "\n", + block->HasFlag(BBF_ASYNC_RESUMPTION) ? "async" : "catch", predBlock->bbNum, + block->bbNum); + region->AddEntryEdge(edge); region->SetHasSideEntry(); regions->SetHasMultipleEntryTryRegions(); @@ -7719,6 +7722,13 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree block->bbNum); assert(!"Unexpected try region entry edge"); } + + region = region->m_parent; + + if (region != nullptr) + { + tryIndex = comp->ehGetIndex(region->m_ehDsc); + } } } else From 92b0effb8198cc046dde5c8fef4f060ace88801e Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Mar 2026 19:13:18 -0700 Subject: [PATCH 4/7] catch resumption on dispatch switch... wasm successor enumerator updated. But looks like we need fake pred edges --- src/coreclr/jit/fgdiagnostic.cpp | 3 +- src/coreclr/jit/fgwasm.cpp | 2 +- src/coreclr/jit/fgwasm.h | 72 ++++++++++++++++++++------------ src/coreclr/jit/flowgraph.cpp | 22 +++++++--- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 4b9cb46c989056..caa55a6590a3c0 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -2752,6 +2752,7 @@ bool BBPredsChecker::CheckEhTryDsc(BasicBlock* block, BasicBlock* blockPred, EHb // Async resumptions are allowed to jump into try blocks at any point. They // are introduced late enough that the invariant of single entry is no // longer necessary. + // TODO: revoke for wasm after SCC if (blockPred->HasFlag(BBF_ASYNC_RESUMPTION)) { return true; @@ -2760,7 +2761,7 @@ bool BBPredsChecker::CheckEhTryDsc(BasicBlock* block, BasicBlock* blockPred, EHb #if defined(TARGET_WASM) // Catch resumptions are allowed to jump into try blocks at any point. // They are transients during Wasm control flow restructuring. - // + // TODO: revoke after SCC if (m_compiler->fgWasmHasCatchResumptions && blockPred->HasFlag(BBF_CATCH_RESUMPTION)) { return true; diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 27d62a806524c3..50082fd0ced317 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -2072,7 +2072,6 @@ PhaseStatus Compiler::fgWasmEhFlow() continuationBlock->bbNum, innermostDispatchingTryIndex); fgWasmHasCatchResumptions = true; - continuationBlock->SetFlags(BBF_CATCH_RESUMPTION); } ArrayStack* catchRetBlocks = getCatchRetBlocksForTryRegion(innermostDispatchingTryIndex); @@ -2339,6 +2338,7 @@ void Compiler::fgWasmEhTransformTry(ArrayStack* catchRetBlocks, // BBswtDesc* const swtDesc = new (this, CMK_BasicBlock) BBswtDesc(succs, succCount, cases, caseCount, true); switchBlock->SetSwitch(swtDesc); + switchBlock->SetFlags(BBF_CATCH_RESUMPTION); // Build the IR for the switch // diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index b72766690004f9..4c54e631050552 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -427,7 +427,7 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc case BBJ_THROW: case BBJ_RETURN: case BBJ_EHFAULTRET: - return BasicBlockVisit::Continue; + break; case BBJ_CALLFINALLY: if (block->isBBCallFinallyPair()) @@ -435,11 +435,12 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc RETURN_ON_ABORT(func(block->Next())); } - return BasicBlockVisit::Continue; + break; case BBJ_CALLFINALLYRET: case BBJ_ALWAYS: - return func(block->GetTarget()); + RETURN_ON_ABORT(func(block->GetTarget())); + break; case BBJ_COND: if (block->TrueEdgeIs(block->GetFalseEdge())) @@ -461,7 +462,7 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc RETURN_ON_ABORT(func(block->GetTrueTarget())); } - return BasicBlockVisit::Continue; + break; case BBJ_SWITCH: { @@ -473,19 +474,19 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc RETURN_ON_ABORT(func(desc->GetSucc(i)->getDestinationBlock())); } - return BasicBlockVisit::Continue; + break; } default: unreached(); } - // If the compiler has a try region object, add back edges from any - // catch resumption or async resumption to the header of each enclosing - // try with catch handler. + // If the compiler has a multi-entry try region object, add edges from any + // catch resumption or async resumption target to the header of each enclosing + // try/catch. // - // This makes multi-entry try regions look like multi-entry loops and the SCC - // algorithm will transform them into single-entry try regions. + // This makes multi-entry try/catch regions look like multi-entry loops and the SCC + // algorithm will transform them into single-entry try/catch regions. // // Note we disregard try/finally/fault here as those do not need to be expressed // as single-entry regions for Wasm codegen. And we consider all mutual-protect @@ -493,33 +494,50 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc // FlowGraphTryRegions* const tryRegions = comp->fgTryRegions; - if ((tryRegions != nullptr) && block->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)) + if ((tryRegions == nullptr) || !tryRegions->HasMultipleEntryTryRegions()) { - EHblkDsc* const dsc = comp->ehGetBlockTryDsc(block); - FlowGraphTryRegion* region = tryRegions->GetTryRegionByHeader(dsc->ebdTryBeg); + return BasicBlockVisit::Continue; + } + + EHblkDsc* const dsc = comp->ehGetBlockTryDsc(block); + + if (dsc == nullptr) + { + return BasicBlockVisit::Continue; + } - while (region != nullptr) + FlowGraphTryRegion* region = tryRegions->GetTryRegionByHeader(dsc->ebdTryBeg); + + // TODO: possibly flag blocks that are targets of resumption switches so + // we can quickly screen out blocks that are not try region side entries. + // + while (region != nullptr) + { + if (region->HasCatchHandler()) { - if (region->HasCatchHandler()) + if (!region->HasSideEntry()) { - if (!region->HasSideEntry()) - { - break; - } + break; + } - BasicBlock* const header = region->GetHeaderBlock(); - for (FlowEdge* const edge : region->EntryEdges()) + BasicBlock* const header = region->GetHeaderBlock(); + for (FlowEdge* const edge : region->EntryEdges()) + { + if ((block != header) && (block == edge->getDestinationBlock())) { - if (block == edge->getDestinationBlock()) - { - RETURN_ON_ABORT(func(header)); - break; - } + assert(edge->getSourceBlock()->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)); + JITDUMP("Wasm successor enumerator: adding pseudo-edge from " FMT_BB " -> " FMT_BB + " for in-try resumption\n", + block->bbNum, header->bbNum); + RETURN_ON_ABORT(func(header)); + break; } } - region = region->EnclosingRegion(); } + region = region->EnclosingRegion(); } + + return BasicBlockVisit::Continue; } #undef RETURN_ON_ABORT diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 7decedbe8e73bb..880622e97ad64f 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -7691,12 +7691,22 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree { BasicBlock* const predBlock = edge->getSourceBlock(); + // Disregard catchret edges, these are modelled by + // catch resumptions now. + // + if (predBlock->KindIs(BBJ_EHCATCHRET)) + { + continue; + } + + // Disregard edges from within the try region + // if (comp->bbInTryRegions(tryIndex, predBlock)) { continue; } - // "Normal" entry edges + // "Normal" entry edges. // if (block == dsc->ebdTryBeg) { @@ -7704,12 +7714,12 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree continue; } - // Runtime async edges and catch resumption edges + // Async resumption and catch resumption entry edges // - if (block->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)) + if (predBlock->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)) { JITDUMP("Found %s resumption edge from " FMT_BB " to " FMT_BB "\n", - block->HasFlag(BBF_ASYNC_RESUMPTION) ? "async" : "catch", predBlock->bbNum, + predBlock->HasFlag(BBF_ASYNC_RESUMPTION) ? "async" : "catch", predBlock->bbNum, block->bbNum); region->AddEntryEdge(edge); @@ -7827,11 +7837,11 @@ void FlowGraphTryRegion::Dump(FlowGraphTryRegion* region) BasicBlock* const predBlock = edge->getSourceBlock(); BasicBlock* const succBlock = edge->getDestinationBlock(); printf(" " FMT_BB "->" FMT_BB, predBlock->bbNum, succBlock->bbNum); - if (succBlock->HasFlag(BBF_ASYNC_RESUMPTION)) + if (predBlock->HasFlag(BBF_ASYNC_RESUMPTION)) { printf("[async]"); } - if (succBlock->HasFlag(BBF_CATCH_RESUMPTION)) + if (predBlock->HasFlag(BBF_CATCH_RESUMPTION)) { printf("[catch]"); } From f1b71bcfd4793b037a50c9cd53b7ee3b94e43a8c Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 30 Mar 2026 10:03:00 -0700 Subject: [PATCH 5/7] back off a bit to refactor --- src/coreclr/jit/fgwasm.cpp | 33 ++++++----------- src/coreclr/jit/fgwasm.h | 73 ++++---------------------------------- 2 files changed, 18 insertions(+), 88 deletions(-) diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 50082fd0ced317..95e36e7d376ae8 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -74,21 +74,6 @@ FlowGraphDfsTree* FgWasm::WasmDfs(bool& hasBlocksOnlyReachableViaEH) Compiler* const comp = Comp(); comp->fgInvalidateDfsTree(); - // IIf we have EH, build the try region structure. This requires a DFS so try region - // membership can use compressed BV indices. We need this before computing the WasmDFS - // (it's ok if this initial DFS is a "full DFS" -- it can also be used to enumerate the - // try region blocks in postorder, but we won't need it for that purpose here). - // - // TODO: find some way to avoid needing this temporary DFS. - // - if (comp->compHndBBtabCount > 0) - { - FlowGraphDfsTree* tempDfs = comp->fgComputeDfs(); - FlowGraphTryRegions* tryRegions = FlowGraphTryRegions::Build(comp, tempDfs); - comp->fgTryRegions = tryRegions; - comp->fgInvalidateDfsTree(); - } - BasicBlock** postOrder = new (comp, CMK_WasmCfgLowering) BasicBlock*[comp->fgBBcount]; bool hasCycle = false; @@ -1068,9 +1053,9 @@ PhaseStatus Compiler::fgWasmControlFlow() // // We don't install our DFS tree as "the" DFS tree as it is non-standard. // - FgWasm fgWasm(this); - bool hasBlocksOnlyReachableViaEH = false; - FlowGraphDfsTree* dfsTree = fgWasm.WasmDfs(hasBlocksOnlyReachableViaEH); + FgWasm fgWasm(this); + bool hasBlocksOnlyReachableViaEH = false; + FlowGraphDfsTree* const dfsTree = fgWasm.WasmDfs(hasBlocksOnlyReachableViaEH); if (hasBlocksOnlyReachableViaEH) { @@ -1082,7 +1067,7 @@ PhaseStatus Compiler::fgWasmControlFlow() } assert(dfsTree->IsForWasm()); - FlowGraphNaturalLoops* loops = FlowGraphNaturalLoops::Find(dfsTree); + FlowGraphNaturalLoops* const loops = FlowGraphNaturalLoops::Find(dfsTree); // We should have transformed these away earlier // @@ -1090,12 +1075,16 @@ PhaseStatus Compiler::fgWasmControlFlow() // Create descriptions of the try regions that can be enumerated in RPO // - FlowGraphTryRegions* tryRegions = FlowGraphTryRegions::Build(this, dfsTree); + FlowGraphTryRegions* const tryRegions = FlowGraphTryRegions::Build(this, dfsTree); JITDUMPEXEC(FlowGraphTryRegions::Dump(tryRegions)); - // We should have fixed any multiple-entry try during SCC processing + // We cannot handle multiple entry try regions yet. // - assert(!tryRegions->HasMultipleEntryTryRegions()); + if (tryRegions->HasMultipleEntryTryRegions()) + { + JITDUMP("\nThere are multiple entry try regions\n"); + NYI_WASM("Multiple entry try regions"); + } // Our interval ends are at the starts of blocks, so we need a block that // comes after all existing blocks. So allocate one extra slot. diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index 4c54e631050552..4f0a4e60aeb4bb 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -418,8 +418,8 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc switch (block->GetKind()) { - // Funclet returns have no successors - // + // Funclet returns have no successors + // case BBJ_EHFINALLYRET: case BBJ_EHCATCHRET: case BBJ_EHFILTERRET: @@ -427,7 +427,7 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc case BBJ_THROW: case BBJ_RETURN: case BBJ_EHFAULTRET: - break; + return BasicBlockVisit::Continue; case BBJ_CALLFINALLY: if (block->isBBCallFinallyPair()) @@ -435,12 +435,11 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc RETURN_ON_ABORT(func(block->Next())); } - break; + return BasicBlockVisit::Continue; case BBJ_CALLFINALLYRET: case BBJ_ALWAYS: - RETURN_ON_ABORT(func(block->GetTarget())); - break; + return func(block->GetTarget()); case BBJ_COND: if (block->TrueEdgeIs(block->GetFalseEdge())) @@ -462,7 +461,7 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc RETURN_ON_ABORT(func(block->GetTrueTarget())); } - break; + return BasicBlockVisit::Continue; case BBJ_SWITCH: { @@ -474,70 +473,12 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc RETURN_ON_ABORT(func(desc->GetSucc(i)->getDestinationBlock())); } - break; + return BasicBlockVisit::Continue; } default: unreached(); } - - // If the compiler has a multi-entry try region object, add edges from any - // catch resumption or async resumption target to the header of each enclosing - // try/catch. - // - // This makes multi-entry try/catch regions look like multi-entry loops and the SCC - // algorithm will transform them into single-entry try/catch regions. - // - // Note we disregard try/finally/fault here as those do not need to be expressed - // as single-entry regions for Wasm codegen. And we consider all mutual-protect - // try/catch as a single region. - // - FlowGraphTryRegions* const tryRegions = comp->fgTryRegions; - - if ((tryRegions == nullptr) || !tryRegions->HasMultipleEntryTryRegions()) - { - return BasicBlockVisit::Continue; - } - - EHblkDsc* const dsc = comp->ehGetBlockTryDsc(block); - - if (dsc == nullptr) - { - return BasicBlockVisit::Continue; - } - - FlowGraphTryRegion* region = tryRegions->GetTryRegionByHeader(dsc->ebdTryBeg); - - // TODO: possibly flag blocks that are targets of resumption switches so - // we can quickly screen out blocks that are not try region side entries. - // - while (region != nullptr) - { - if (region->HasCatchHandler()) - { - if (!region->HasSideEntry()) - { - break; - } - - BasicBlock* const header = region->GetHeaderBlock(); - for (FlowEdge* const edge : region->EntryEdges()) - { - if ((block != header) && (block == edge->getDestinationBlock())) - { - assert(edge->getSourceBlock()->HasAnyFlag(BBF_ASYNC_RESUMPTION | BBF_CATCH_RESUMPTION)); - JITDUMP("Wasm successor enumerator: adding pseudo-edge from " FMT_BB " -> " FMT_BB - " for in-try resumption\n", - block->bbNum, header->bbNum); - RETURN_ON_ABORT(func(header)); - break; - } - } - } - region = region->EnclosingRegion(); - } - - return BasicBlockVisit::Continue; } #undef RETURN_ON_ABORT From 2791a5d422ed6ce742ff59070533079d803c8fa7 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 30 Mar 2026 11:51:24 -0700 Subject: [PATCH 6/7] try regions with/without dfs --- src/coreclr/jit/compiler.h | 24 ++++++++++--- src/coreclr/jit/compiler.hpp | 5 +-- src/coreclr/jit/flowgraph.cpp | 64 +++++++++++++++++++++++++---------- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index c166e88f339748..3dcb7abbcbc952 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2322,6 +2322,8 @@ class FlowGraphTryRegion return m_ehDsc->ebdTryBeg; } + bool CanEnumerateInReversePostOrder() const; + #ifdef DEBUG static void Dump(FlowGraphTryRegion* region); #endif @@ -2331,16 +2333,18 @@ class FlowGraphTryRegion class FlowGraphTryRegions { private: + Compiler* m_compiler; FlowGraphDfsTree* m_dfsTree; // Collection of try regions that were found, indexed by EhID jitstd::vector m_tryRegions; - FlowGraphTryRegions(FlowGraphDfsTree* dfs, unsigned numRegions); + FlowGraphTryRegions(Compiler* comp, FlowGraphDfsTree* dfs, unsigned numRegions); unsigned m_numRegions; unsigned m_numTryCatchRegions; bool m_tryRegionsIncludeHandlerBlocks; bool m_hasMultipleEntryTryRegions; + BitVecTraits m_traits; void SetHasMultipleEntryTryRegions() { @@ -2351,14 +2355,26 @@ class FlowGraphTryRegions static FlowGraphTryRegions* Build(Compiler* comp, FlowGraphDfsTree* dfs, bool includeHandlerBlocks = false); - BitVecTraits GetBlockBitVecTraits() + BitVecTraits* GetBlockBitVecTraits() { - return m_dfsTree->PostOrderTraits(); + return &m_traits; + } + + unsigned GetBlockIndex(BasicBlock* block) const + { + if (m_dfsTree != nullptr) + { + return block->bbPostorderNum; + } + else + { + return block->bbNum; + } } Compiler* GetCompiler() const { - return m_dfsTree->GetCompiler(); + return m_compiler; } FlowGraphTryRegion* GetTryRegionByHeader(BasicBlock* block); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index efb1238c225591..22a49071fbbf10 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -5399,9 +5399,10 @@ void Compiler::fgVisitBlocksInTryAwareLoopAwareRPO(FlowGraphDfsTree* dfsTre template BasicBlockVisit FlowGraphTryRegion::VisitTryRegionBlocksReversePostOrder(TFunc func) { - BitVecTraits traits = m_regions->GetBlockBitVecTraits(); + assert(CanEnumerateInReversePostOrder()); FlowGraphDfsTree* const dfsTree = m_regions->GetDfsTree(); - bool result = BitVecOps::VisitBitsReverse(&traits, m_blocks, [=](unsigned index) { + BitVecTraits* traits = m_regions->GetBlockBitVecTraits(); + bool result = BitVecOps::VisitBitsReverse(traits, m_blocks, [=](unsigned index) { assert(index < dfsTree->GetPostOrderCount()); return func(dfsTree->GetPostOrder(index)) == BasicBlockVisit::Continue; }); diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 880622e97ad64f..c70af93cff6b3c 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -7548,15 +7548,19 @@ void BlockReachabilitySets::Dump() // FlowGraphTryRegions::FlowGraphTryRegions: Constructor for FlowGraphTryRegions. // // Arguments: +// // dfsTree -- DFS tree for the flow graph // numRegions -- Number of try regions in the method // -FlowGraphTryRegions::FlowGraphTryRegions(FlowGraphDfsTree* dfsTree, unsigned numRegions) - : m_dfsTree(dfsTree) - , m_tryRegions(numRegions, nullptr, dfsTree->GetCompiler()->getAllocator(CMK_BasicBlock)) +FlowGraphTryRegions::FlowGraphTryRegions(Compiler* comp, FlowGraphDfsTree* dfsTree, unsigned numRegions) + : m_compiler(comp) + , m_dfsTree(dfsTree) + , m_tryRegions(numRegions, nullptr, comp->getAllocator(CMK_BasicBlock)) , m_numRegions(0) , m_numTryCatchRegions(0) , m_tryRegionsIncludeHandlerBlocks(false) + , m_hasMultipleEntryTryRegions(false) + , m_traits((dfsTree == nullptr) ? comp->fgBBNumMax + 1 : dfsTree->GetPostOrderCount(), comp) { } @@ -7603,8 +7607,8 @@ FlowGraphTryRegion::FlowGraphTryRegion(EHblkDsc* ehDsc, FlowGraphTryRegions* reg , m_requiresRuntimeResumption(false) , m_hasSideEntry(false) { - BitVecTraits traits = regions->GetBlockBitVecTraits(); - m_blocks = BitVecOps::MakeEmpty(&traits); + BitVecTraits* const traits = regions->GetBlockBitVecTraits(); + m_blocks = BitVecOps::MakeEmpty(traits); } //------------------------------------------------------------------------ @@ -7612,7 +7616,7 @@ FlowGraphTryRegion::FlowGraphTryRegion(EHblkDsc* ehDsc, FlowGraphTryRegions* reg // // Arguments: // comp -- Compiler instance -// dfsTree -- DFS tree for the flow graph +// dfsTree -- DFS tree for the flow graph (optional, can be nullptr) // includeHandlerBlocks -- include blocks in handlers inside the try // // Returns: @@ -7624,7 +7628,7 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree // collection if we've deleted some EH regions. // unsigned const numTryRegions = comp->compEHID; - FlowGraphTryRegions* regions = new (comp, CMK_BasicBlock) FlowGraphTryRegions(dfsTree, numTryRegions); + FlowGraphTryRegions* regions = new (comp, CMK_BasicBlock) FlowGraphTryRegions(comp, dfsTree, numTryRegions); assert(numTryRegions >= comp->compHndBBtabCount); regions->m_tryRegionsIncludeHandlerBlocks = includeHandlerBlocks; @@ -7659,9 +7663,7 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree } } - // Collect the postorder numbers of each block in each region - // - BitVecTraits traits = regions->m_dfsTree->PostOrderTraits(); + BitVecTraits* const traits = regions->GetBlockBitVecTraits(); for (BasicBlock* block : comp->Blocks()) { @@ -7683,7 +7685,7 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree // while (region != nullptr) { - BitVecOps::AddElemD(&traits, region->m_blocks, block->bbPostorderNum); + BitVecOps::AddElemD(traits, region->m_blocks, regions->GetBlockIndex(block)); // Enumerate block's pred edges to find the try entry edges. // @@ -7774,8 +7776,8 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree // unsigned FlowGraphTryRegion::NumBlocks() const { - BitVecTraits traits = m_regions->GetBlockBitVecTraits(); - return BitVecOps::Count(&traits, m_blocks); + BitVecTraits* const traits = m_regions->GetBlockBitVecTraits(); + return BitVecOps::Count(traits, m_blocks); } //------------------------------------------------------------------------ @@ -7796,6 +7798,18 @@ FlowGraphTryRegion* FlowGraphTryRegion::EnclosingRegion() const return ancestor; } +//------------------------------------------------------------------------ +// FlowGraphTryRegion::CanEnumerateInReversePostOrder: check if the +// try region can be enumerated in reverse post order +// +// Returns: +// True if so. +// +bool FlowGraphTryRegion::CanEnumerateInReversePostOrder() const +{ + return m_regions->GetDfsTree() != nullptr; +} + #ifdef DEBUG //------------------------------------------------------------------------ @@ -7826,10 +7840,26 @@ void FlowGraphTryRegion::Dump(FlowGraphTryRegion* region) printf(" [outermost]:"); } - region->VisitTryRegionBlocksReversePostOrder([](BasicBlock* block) { - printf(" " FMT_BB, block->bbNum); - return BasicBlockVisit::Continue; - }); + if (region->CanEnumerateInReversePostOrder()) + { + printf(" [rpo]:"); + + region->VisitTryRegionBlocksReversePostOrder([](BasicBlock* block) { + printf(" " FMT_BB, block->bbNum); + return BasicBlockVisit::Continue; + }); + } + else + { + printf(" [bbNum]:"); + + BitVecOps::Iter iterator(regions->GetBlockBitVecTraits(), region->m_blocks); + unsigned int index; + while (iterator.NextElem(&index)) + { + printf(" " FMT_BB, index); + } + } printf(" [entries]: "); for (FlowEdge* const edge : region->EntryEdges()) From 83cc91e713c04c94618d42ac2141539ee5faa575 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 30 Mar 2026 13:33:57 -0700 Subject: [PATCH 7/7] review feedback --- src/coreclr/jit/block.cpp | 3 ++- src/coreclr/jit/compiler.h | 4 ++-- src/coreclr/jit/flowgraph.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index bbeb1390ef2ccf..4851dd5a90e31f 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -524,7 +524,8 @@ void BasicBlock::dspFlags() const {BBF_NEEDS_GCPOLL, "gcpoll"}, {BBF_HAS_VALUE_PROFILE, "val-prof"}, {BBF_MAY_HAVE_BOUNDS_CHECKS, "bnds-chk"}, - {BBF_ASYNC_RESUMPTION, "resume"}, + {BBF_ASYNC_RESUMPTION, "a-resume"}, + {BBF_CATCH_RESUMPTION, "c-resume"}, {BBF_THROW_HELPER, "throw-hlpr"}, }; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3dcb7abbcbc952..3eb54e4643516f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2255,7 +2255,7 @@ class FlowGraphTryRegion // Edges from blocks outside the try region to blocks inside the try region. // This includes edges from any catch handler, and edges from some ancestor - // region to some decendant region. + // region to some descendant region. jitstd::vector m_entryEdges; bool m_requiresRuntimeResumption; @@ -2295,7 +2295,7 @@ class FlowGraphTryRegion return m_ehDsc->HasCatchHandler(); } - const jitstd::vector& EntryEdges() + const jitstd::vector& EntryEdges() const { return m_entryEdges; } diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index c70af93cff6b3c..98d9431a8ff8a1 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -7770,7 +7770,7 @@ FlowGraphTryRegions* FlowGraphTryRegions::Build(Compiler* comp, FlowGraphDfsTree // FlowGraphTryRegion::NumBlocks: Return the number of blocks in the try region. // // Returns: -// Number of blcoks in the region at the time of FlowGraphTryRegions::Build +// Number of blocks in the region at the time of FlowGraphTryRegions::Build // Includes blocks in enclosed try regions. Includes blocks in enclosed // handler regions, if FlowGraphTryRegions::Build was called with includeHandlerBlocks == true. //