From 56faa2d3cd7600c1ebe2d85b0cde7fdf235479a7 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 6 Apr 2026 16:15:47 -0700 Subject: [PATCH 1/6] [Wasm RyuJit] fix funclet prolog and epilog codegen Defer calling genEmitStartBlock until after we've set up the funclet prolog IG. Pass the BasicBlock back from emitter to codegen for the funclet epilog generation since epilog codegen is position dependent. --- src/coreclr/jit/codegen.h | 2 +- src/coreclr/jit/codegenarm.cpp | 2 +- src/coreclr/jit/codegenarm64.cpp | 2 +- src/coreclr/jit/codegenlinear.cpp | 4 ++-- src/coreclr/jit/codegenloongarch64.cpp | 2 +- src/coreclr/jit/codegenriscv64.cpp | 2 +- src/coreclr/jit/codegenwasm.cpp | 13 +++++++++++-- src/coreclr/jit/codegenxarch.cpp | 4 ++-- src/coreclr/jit/emit.cpp | 2 +- 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 11689400d529e2..8e7fb1f6b29f5e 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -608,7 +608,7 @@ class CodeGen final : public CodeGenInterface void genReserveFuncletProlog(BasicBlock* block); void genReserveFuncletEpilog(BasicBlock* block); void genFuncletProlog(BasicBlock* block); - void genFuncletEpilog(); + void genFuncletEpilog(BasicBlock* block); void genCaptureFuncletPrologEpilogInfo(); void genUpdateCurrentFunclet(BasicBlock* block); diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 174fddc8164f40..2d2ab0026bd9b0 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -2371,7 +2371,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * Generates code for an EH funclet epilog. */ -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { #ifdef DEBUG if (verbose) diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 22f89a566b3be6..15ee1ef3768a29 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -1495,7 +1495,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * See the description of frame shapes at genFuncletProlog(). */ -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { #ifdef DEBUG if (verbose) diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index f0c7a597e10cee..13cdd22b21eb92 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -420,8 +420,6 @@ void CodeGen::genCodeForBlock(BasicBlock* block) GetEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie); } - genEmitStartBlock(block); - // Both stacks are always empty on entry to a basic block. assert(genStackLevel == 0); genAdjustStackLevel(block); @@ -444,6 +442,8 @@ void CodeGen::genCodeForBlock(BasicBlock* block) genReserveFuncletProlog(block); } + genEmitStartBlock(block); + // Clear compCurStmt and compCurLifeTree. m_compiler->compCurStmt = nullptr; m_compiler->compCurLifeTree = nullptr; diff --git a/src/coreclr/jit/codegenloongarch64.cpp b/src/coreclr/jit/codegenloongarch64.cpp index 751a5c139c05e2..cf5e12f973114b 100644 --- a/src/coreclr/jit/codegenloongarch64.cpp +++ b/src/coreclr/jit/codegenloongarch64.cpp @@ -475,7 +475,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * Generates code for an EH funclet epilog. */ -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { #ifdef DEBUG if (verbose) diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index 83e42df320dae1..3f3ed234b37ce8 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -463,7 +463,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * * See the description of frame shapes at genFuncletProlog(). */ -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { #ifdef DEBUG if (verbose) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 9a418ebdeb309b..568c499fcd2a47 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -311,10 +311,19 @@ void CodeGen::genFuncletProlog(BasicBlock* block) //------------------------------------------------------------------------ // genFuncletEpilog: codegen for funclet epilogs. // -// For Wasm, funclet epilogs are empty +// Arguments: +// block - funclet epilog block // -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* block) { + if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) + { + instGen(INS_end); + } + else + { + instGen(INS_return); + } } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 6dc7b0a9348994..3d55de3773c122 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -10865,7 +10865,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * Note that we don't do anything with unwind codes, because AMD64 only cares about unwind codes for the prolog. */ -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* block) { #ifdef DEBUG if (verbose) @@ -10992,7 +10992,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * Generates code for an EH funclet epilog. */ -void CodeGen::genFuncletEpilog() +void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { #ifdef DEBUG if (verbose) diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index ca83731f55694f..98f74b755e1408 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -2226,7 +2226,7 @@ void emitter::emitGeneratePrologEpilog() case IGPT_FUNCLET_EPILOG: INDEBUG(++funcletEpilogCnt); emitBegFuncletEpilog(igPh); - codeGen->genFuncletEpilog(); + codeGen->genFuncletEpilog(igPhBB); emitEndFuncletEpilog(); break; From d54084ea295ca64f185b69beb3f32c0f836f49a1 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 8 Apr 2026 15:34:11 -0700 Subject: [PATCH 2/6] Force the JIT to fail at the very end for methods with funclets, unless JitWasmFunclets=1. This is temporary until the JIT host can extract out the funclets as separate Wasm functions. --- src/coreclr/jit/codegencommon.cpp | 10 ++++++++++ src/coreclr/jit/jitconfigvalues.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 64c389288fb598..668a42ceaa81b8 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2012,6 +2012,16 @@ void CodeGen::genGenerateCode(void** codePtr, uint32_t* nativeSizeOfCode) JITDUMPEXEC(JitR2RUnsupportedRange.Dump()); implReadyToRunUnsupported(); } + + // Also fail at this point for any method with funclets, since the Wasm we produce + // for such methods requires post-processing by the host before it can be validated. + // Remove this once the host can do the processing. + // + if ((JitConfig.JitWasmFunclets() == 0) && (m_compiler->compFuncCount() > 1)) + { + JITDUMP("Failing R2R codegen because method has funclets.\n", m_compiler->compFuncCount()); + implReadyToRunUnsupported(); + } #endif // defined(TARGET_WASM) #endif // DEBUG } diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 6890ee573919d7..d92375062706a3 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -882,6 +882,8 @@ CONFIG_INTEGER(JitWasmNyiToR2RUnsupported, "JitWasmNyiToR2RUnsupported", 0) // Specify methods that will fail with R2R unsupported after codegen. // Useful for bypassing methods that compile cleanly but have invalid Wasm codegen. CONFIG_STRING(JitR2RUnsupportedRange, "JitR2RUnsupportedRange") +// Enable processing methods with funclets. +CONFIG_INTEGER(JitWasmFunclets, "JitWasmFunclets", 0) #endif // defined(TARGET_WASM) // Allow to enregister locals with struct type. From 9359933311499dc7999e02b0fc9b7423b929544c Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 8 Apr 2026 17:14:55 -0700 Subject: [PATCH 3/6] Go back to using LIR flags to track collected nodes, since we may make non-consecutive collection calls. --- src/coreclr/jit/lir.h | 1 + src/coreclr/jit/regallocwasm.cpp | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/lir.h b/src/coreclr/jit/lir.h index b212ee69974fbd..8146bdbbdda8d1 100644 --- a/src/coreclr/jit/lir.h +++ b/src/coreclr/jit/lir.h @@ -44,6 +44,7 @@ class LIR final #ifdef TARGET_WASM MultiplyUsed = 0x08, // Set by lowering on nodes that the RA should allocate into // a dedicated register (WASM local), for multiple uses. + Collected = 0x10, // Set by the RA on nodes that have had their references collected. #endif // TARGET_WASM }; }; diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 01ff4a35107c05..c89cf79a64e7da 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -713,17 +713,14 @@ void WasmRegAlloc::CollectReference(GenTree* node) PerFuncletData* const data = m_perFuncletData[m_currentFunclet]; VirtualRegReferences* refs = data->m_virtualRegRefs; - // We may make multiple consecutive collection calls for the same node. + // We may make multiple collection calls for the same node. // We only want to collect it once. // - if (data->m_lastVirtualRegRefsCount > 0) + if ((node->gtLIRFlags & LIR::Flags::Collected) != 0) { - assert(refs != nullptr); - if (node == refs->Nodes[data->m_lastVirtualRegRefsCount - 1]) - { - return; - } + return; } + node->gtLIRFlags |= LIR::Flags::Collected; if (refs == nullptr) { @@ -819,6 +816,8 @@ void WasmRegAlloc::ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const // regNumber WasmRegAlloc::RequestInternalRegister(GenTree* node, var_types type) { + JITDUMP("Requesting internal %s register for [%06u]\n", varTypeName(type), Compiler::dspTreeID(node)); + regNumber reg = AllocateTemporaryRegister(type); m_codeGen->internalRegisters.Add(node, reg); CollectReference(node); From 553b4fa8f56283275f31d8d6c97d63ec1faec5d5 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 9 Apr 2026 07:35:14 -0700 Subject: [PATCH 4/6] review feedback --- src/coreclr/jit/codegencommon.cpp | 10 ++++++---- src/coreclr/jit/jitconfigvalues.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 668a42ceaa81b8..623d8d4856a09e 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2012,18 +2012,20 @@ void CodeGen::genGenerateCode(void** codePtr, uint32_t* nativeSizeOfCode) JITDUMPEXEC(JitR2RUnsupportedRange.Dump()); implReadyToRunUnsupported(); } +#endif // defined(TARGET_WASM) +#endif // DEBUG +#if defined(TARGET_WASM) // Also fail at this point for any method with funclets, since the Wasm we produce // for such methods requires post-processing by the host before it can be validated. - // Remove this once the host can do the processing. + // TODO-WASM: Remove this once the host can do the processing. // if ((JitConfig.JitWasmFunclets() == 0) && (m_compiler->compFuncCount() > 1)) { - JITDUMP("Failing R2R codegen because method has funclets.\n", m_compiler->compFuncCount()); + JITDUMP("Failing R2R codegen because method has funclets.\n"); implReadyToRunUnsupported(); } -#endif // defined(TARGET_WASM) -#endif // DEBUG +#endif } //---------------------------------------------------------------------- diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index d92375062706a3..ac5a708c07e291 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -883,7 +883,7 @@ CONFIG_INTEGER(JitWasmNyiToR2RUnsupported, "JitWasmNyiToR2RUnsupported", 0) // Useful for bypassing methods that compile cleanly but have invalid Wasm codegen. CONFIG_STRING(JitR2RUnsupportedRange, "JitR2RUnsupportedRange") // Enable processing methods with funclets. -CONFIG_INTEGER(JitWasmFunclets, "JitWasmFunclets", 0) +RELEASE_CONFIG_INTEGER(JitWasmFunclets, "JitWasmFunclets", 0) #endif // defined(TARGET_WASM) // Allow to enregister locals with struct type. From a672c619224796bce901458abd41f13b1563be52 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 9 Apr 2026 09:46:32 -0700 Subject: [PATCH 5/6] review feedback -- reorder collect call, get rid of lir flag --- src/coreclr/jit/lir.h | 1 - src/coreclr/jit/regallocwasm.cpp | 12 ++++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/lir.h b/src/coreclr/jit/lir.h index 8146bdbbdda8d1..b212ee69974fbd 100644 --- a/src/coreclr/jit/lir.h +++ b/src/coreclr/jit/lir.h @@ -44,7 +44,6 @@ class LIR final #ifdef TARGET_WASM MultiplyUsed = 0x08, // Set by lowering on nodes that the RA should allocate into // a dedicated register (WASM local), for multiple uses. - Collected = 0x10, // Set by the RA on nodes that have had their references collected. #endif // TARGET_WASM }; }; diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index c89cf79a64e7da..2d8cfcf359626f 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -716,11 +716,14 @@ void WasmRegAlloc::CollectReference(GenTree* node) // We may make multiple collection calls for the same node. // We only want to collect it once. // - if ((node->gtLIRFlags & LIR::Flags::Collected) != 0) + if (data->m_lastVirtualRegRefsCount > 0) { - return; + assert(refs != nullptr); + if (node == refs->Nodes[data->m_lastVirtualRegRefsCount - 1]) + { + return; + } } - node->gtLIRFlags |= LIR::Flags::Collected; if (refs == nullptr) { @@ -774,6 +777,8 @@ void WasmRegAlloc::RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node) regNumber reg = AllocateTemporaryRegister(node->TypeGet()); assert((node->GetRegNum() == REG_NA) && "Trying to double-assign a temporary register"); node->SetRegNum(reg); + + CollectReference(node); } //------------------------------------------------------------------------ @@ -796,7 +801,6 @@ void WasmRegAlloc::ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const regNumber reg = ReleaseTemporaryRegister(genActualType(operand)); assert((reg == operand->GetRegNum()) && "Temporary reg being consumed out of order"); - CollectReference(operand); operand->gtLIRFlags &= ~LIR::Flags::MultiplyUsed; JITDUMP("Consumed a temporary reg for [%06u]: %s\n", Compiler::dspTreeID(operand), reason); From 270d79f51faceac1e547dfdbadc1712532d394e0 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 9 Apr 2026 12:48:14 -0700 Subject: [PATCH 6/6] review feedback --- src/coreclr/jit/codegenwasm.cpp | 2 ++ src/coreclr/jit/codegenxarch.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 568c499fcd2a47..3c3ebe4d9e154f 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -316,6 +316,8 @@ void CodeGen::genFuncletProlog(BasicBlock* block) // void CodeGen::genFuncletEpilog(BasicBlock* block) { + ScopedSetVariable _setGeneratingEpilog(&m_compiler->compGeneratingEpilog, true); + if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { instGen(INS_end); diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 3d55de3773c122..65467cdcf0f4b5 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -10865,7 +10865,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) * Note that we don't do anything with unwind codes, because AMD64 only cares about unwind codes for the prolog. */ -void CodeGen::genFuncletEpilog(BasicBlock* block) +void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { #ifdef DEBUG if (verbose)