From 76595b904513c56bf919805e6fc666455ce4b31c Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 24 Sep 2026 18:36:05 +0000 Subject: [PATCH] Avoid frame identity for leaf Wasm funclets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/codegenwasm.cpp | 58 ++++++++++++++++++++++++++++++--- src/coreclr/jit/compiler.h | 1 + src/coreclr/jit/emitwasm.cpp | 4 +++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 96d1443f5f75b1..aa39dbd52f2139 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -91,6 +91,49 @@ void CodeGen::genMarkLabelsForCodegen() // for (FuncInfoDsc* const func : m_compiler->Funcs()) { + // Finalize call presence from the lowered IR before generating any code. + // BBF_HAS_CALL is a quick path, but funclet creation does not preserve it + // in every case, so inspect LIR for GT_CALL nodes too. Account separately + // for flow and codegen constructs that emit calls without a GT_CALL node. + // + for (BasicBlock* const block : func->Blocks(m_compiler)) + { + if (block->HasFlag(BBF_HAS_CALL) || block->KindIs(BBJ_CALLFINALLY)) + { + func->hasCalls = true; + break; + } + + for (GenTree* const node : LIR::AsRange(block)) + { + const bool hasCall = node->OperIs(GT_CALL); + const bool hasInlineThrowHelper = + !m_compiler->fgUseThrowHelperBlocks() && ((node->gtFlags & GTF_EXCEPT) != 0); + const bool hasWriteBarrier = + node->OperIs(GT_STOREIND) && + (gcInfo.gcIsWriteBarrierCandidate(node->AsStoreInd()) != GCInfo::WBF_NoBarrier); + const bool hasCpObjHelper = node->OperIs(GT_STORE_BLK) && + (node->AsBlk()->gtBlkOpKind != GenTreeBlk::BlkOpKindNativeOpcode) && + (node->AsBlk()->gtBlkOpKind != GenTreeBlk::BlkOpKindLoop); + + if (hasCall || hasInlineThrowHelper || hasWriteBarrier || hasCpObjHelper) + { + func->hasCalls = true; + break; + } + } + + if (func->hasCalls) + { + break; + } + } + + if (func->hasCalls) + { + func->ensureUnwindableFrame(m_compiler); + } + BasicBlock* const firstBlock = func->GetStartBlock(m_compiler); firstBlock->SetFlags(BBF_HAS_LABEL); @@ -521,8 +564,7 @@ void CodeGen::genFuncletProlog(BasicBlock* block) // All the funclet params are used from their home registers, so nothing // needs homing here. // - // If the funclet needs to be unwindable (contains any calls), set up - // what we need. + // If the funclet needs to be unwindable, set up what we need. // if (func->needsUnwindableFrame) { @@ -540,9 +582,15 @@ void CodeGen::genFuncletProlog(BasicBlock* block) GetEmitter()->emitIns(INS_I_sub); GetEmitter()->emitIns_I(INS_local_set, EA_PTRSIZE, GetStackPointerRegIndex()); - GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); - GetEmitter()->emitFuncletAddressConstant((cnsval_ssize_t)funcletIndex); - GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, 0); + // A leaf funclet can require a frame for EH virtual IP tracking, but no + // suspended caller needs its function identity for stack walking. + // + if (func->hasCalls) + { + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); + GetEmitter()->emitFuncletAddressConstant((cnsval_ssize_t)funcletIndex); + GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, 0); + } } } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 8392a0a609a4af..4b12d6d37cec3a 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1841,6 +1841,7 @@ struct FuncInfoDsc jitstd::vector* funWasmLocalDecls; unsigned funWasmFrameSize; unsigned funWasmExnRefLocalIndex = UINT_MAX; + bool hasCalls; bool needsUnwindableFrame; emitLocation* startLoc; emitLocation* endLoc; diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index e8eb2e047730a7..63718c274a64b0 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -287,6 +287,10 @@ void emitter::emitIns_Call(const EmitCallParams& params) assert(params.wasmSignature != nullptr); + FuncInfoDsc* const func = m_compiler->funCurrentFunc(); + assert(func->hasCalls); + func->hasCalls = true; + /* We need to allocate the appropriate instruction descriptor based on whether this is a direct/indirect call, and whether we need to