diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 8a9e8f06e4f428..de0b749b0abdcd 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -161,6 +161,7 @@ class CodeGen final : public CodeGenInterface //------------------------------------------------------------------------- void genReportEH(); + void genReportEHClauses(EHClauseInfo* clauses); // Allocates storage for the GC info, writes the GC info into that storage, records the address of the // GC info of the method with the EE, and returns a pointer to the "info" portion (just post-header) of @@ -221,6 +222,7 @@ class CodeGen final : public CodeGenInterface void genEmitNullCheck(regNumber reg); unsigned GetStackPointerRegIndex() const; unsigned GetFramePointerRegIndex() const; + void ensureCurrentFuncIsUnwindable(); #endif void genEmitStartBlock(BasicBlock* block); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index d01f10408d9bfe..7168218a1339e5 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2418,11 +2418,6 @@ void CodeGen::genEmitMachineCode() // void CodeGen::genEmitUnwindDebugGCandEH() { -#ifdef TARGET_WASM - // TODO-WASM: Fix this phase causing an assertion failure even for methods with no GC locals or EH clauses - return; -#endif - /* Now that the code is issued, we can finalize and emit the unwind data */ m_compiler->unwindEmit(*codePtr, coldCodePtr); @@ -2529,11 +2524,9 @@ void CodeGen::genEmitUnwindDebugGCandEH() } #ifndef TARGET_WASM -/***************************************************************************** - * - * Report EH clauses to the VM - */ - +//---------------------------------------------------------------------- +// genReportEH: create and report EH info to the VM +// void CodeGen::genReportEH() { if (m_compiler->compHndBBtabCount == 0) @@ -2561,12 +2554,6 @@ void CodeGen::genReportEH() m_compiler->eeSetEHcount(m_compiler->compHndBBtabCount); m_compiler->Metrics.EHClauseCount = (int)m_compiler->compHndBBtabCount; - struct EHClauseInfo - { - CORINFO_EH_CLAUSE clause; - EHblkDsc* HBtab; - }; - EHClauseInfo* clauses = new (m_compiler, CMK_Codegen) EHClauseInfo[m_compiler->compHndBBtabCount]; // Set up EH clause table, but don't report anything to the VM, yet. @@ -2605,6 +2592,19 @@ void CodeGen::genReportEH() clauses[XTnum++] = {clause, HBtab}; } + genReportEHClauses(clauses); +} + +#endif // !defined(TARGET_WASM) + +//---------------------------------------------------------------------- +// genReportEH: create and report EH info to the VM +// +// Arguments: +// clauses -- eh clause data to report +// +void CodeGen::genReportEHClauses(EHClauseInfo* clauses) +{ // The JIT's ordering of EH clauses does not guarantee that clauses covering the same try region are contiguous. // We need this property to hold true so the CORINFO_EH_CLAUSE_SAMETRY flag is accurate. jitstd::sort(clauses, clauses + m_compiler->compHndBBtabCount, @@ -2624,6 +2624,8 @@ void CodeGen::genReportEH() return leftTryIndex < rightTryIndex; }); + unsigned XTnum; + // Now, report EH clauses to the VM in order of increasing try region index. for (XTnum = 0; XTnum < m_compiler->compHndBBtabCount; XTnum++) { @@ -2653,6 +2655,8 @@ void CodeGen::genReportEH() assert(XTnum == m_compiler->compHndBBtabCount); } +#ifndef TARGET_WASM + //---------------------------------------------------------------------- // genUseOptimizedWriteBarriers: Determine if an optimized write barrier // helper should be used. diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index 5e116a80cdd8cd..ea160060717233 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -79,6 +79,12 @@ class NodeInternalRegisters #endif // !HAS_FIXED_REGISTER_SET }; +struct EHClauseInfo +{ + CORINFO_EH_CLAUSE clause; + EHblkDsc* HBtab; +}; + class CodeGenInterface { friend class emitter; diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 0ce8d19640e56c..2183301e1d8b8f 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -38,6 +38,16 @@ static const instruction INS_I_ge_u = INS_i32_ge_u; static const instruction INS_I_gt_u = INS_i32_gt_u; #endif // !TARGET_64BIT +//------------------------------------------------------------------------ +// ensureCurrentFuncIsUnwindable: ensure we set up an unwindable frame +// for the current function or funclet. +// +void CodeGen::ensureCurrentFuncIsUnwindable() +{ + FuncInfoDsc* const func = m_compiler->funCurrentFunc(); + func->ensureUnwindableFrame(m_compiler); +} + //------------------------------------------------------------------------ // GetStackPointerRegIndex: get the Wasm local index for the stack pointer // @@ -58,10 +68,32 @@ unsigned CodeGen::GetFramePointerRegIndex() const return WasmRegToIndex(fpReg); } +//------------------------------------------------------------------------ +// genMarkLabelsForCodegen: mark labels for codegen +// void CodeGen::genMarkLabelsForCodegen() { - // No work needed here for now. - // We mark labels as needed in genEmitStartBlock. + assert(!m_compiler->fgSafeBasicBlockCreation); + + JITDUMP("Mark labels for codegen\n"); + +#ifdef DEBUG + // No label flags should be set before this. + for (BasicBlock* const block : m_compiler->Blocks()) + { + assert(!block->HasFlag(BBF_HAS_LABEL)); + } +#endif // DEBUG + + // Mark all the funclet boundaries. + // + for (FuncInfoDsc* const func : m_compiler->Funcs()) + { + BasicBlock* const firstBlock = func->GetStartBlock(m_compiler); + firstBlock->SetFlags(BBF_HAS_LABEL); + + JITDUMP(" " FMT_BB " : %s begin\n", firstBlock->bbNum, (func->funKind == FUNC_ROOT) ? "method" : "funclet"); + } } //------------------------------------------------------------------------ @@ -108,6 +140,8 @@ void CodeGen::genAllocLclFrame(unsigned frameSize, regNumber initReg, bool* pIni return; } + m_compiler->unwindAllocStack(frameSize); + // TODO-WASM: reverse pinvoke frame allocation // if (!m_compiler->lvaGetDesc(m_compiler->lvaWasmSpArg)->lvIsParam) @@ -132,6 +166,22 @@ void CodeGen::genAllocLclFrame(unsigned frameSize, regNumber initReg, bool* pIni GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, spLclIndex); GetEmitter()->emitIns_I(INS_local_set, EA_PTRSIZE, WasmRegToIndex(fpReg)); } + + FuncInfoDsc* const func = m_compiler->funGetFunc(ROOT_FUNC_IDX); + + if (func->needsUnwindableFrame) + { + assert(m_compiler->lvaWasmVirtualIP != BAD_VAR_NUM); + assert(m_compiler->lvaWasmFunctionIndex != BAD_VAR_NUM); + + // fp[0] == functionIndex + // + // TODO-WASM: Save the actual function index. For now we save a fixed constant + // + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex()); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, 0xBBBB); + GetEmitter()->emitIns_S(ins_Store(TYP_I_IMPL), EA_PTRSIZE, m_compiler->lvaWasmFunctionIndex, 0); + } } //------------------------------------------------------------------------ @@ -306,6 +356,32 @@ 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 (func->needsUnwindableFrame) + { + // We need two stack slots for the function index and for the funclet virtual IP. + // We also need to keep SP aligned. + // + size_t slotSize = 2 * TARGET_POINTER_SIZE; + size_t frameSize = AlignUp(slotSize, STACK_ALIGN); + m_compiler->unwindAllocStack((unsigned)frameSize); + + // Move SP + // + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, frameSize); + GetEmitter()->emitIns(INS_I_sub); + GetEmitter()->emitIns_I(INS_local_set, EA_PTRSIZE, GetStackPointerRegIndex()); + + // TODO-WASM: Save the funclet index. For now we save a fixed constant + // + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, 0xAAAA); + GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, 0); + } } //------------------------------------------------------------------------ @@ -2375,6 +2451,8 @@ void CodeGen::genCall(GenTreeCall* call) // void CodeGen::genCallInstruction(GenTreeCall* call) { + ensureCurrentFuncIsUnwindable(); + EmitCallParams params; params.isJump = call->IsFastTailCall(); params.hasAsyncRet = call->IsAsync(); @@ -2504,6 +2582,8 @@ void CodeGen::genCallInstruction(GenTreeCall* call) // void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, regNumber callTargetReg /*= REG_NA */) { + ensureCurrentFuncIsUnwindable(); + EmitCallParams params; CORINFO_CONST_LOOKUP helperFunction = m_compiler->compGetHelperFtn((CorInfoHelpFunc)helper); @@ -2788,9 +2868,9 @@ void CodeGen::genCompareFloat(GenTreeOp* treeNode) // * whether the size operand is a constant or not // * whether the allocated memory needs to be zeroed or not (info.compInitMem) // -// If the sp is changed, the value at sp[0] must be the frame pointer -// so that the runtime unwinder can locate the base of the fixed area -// in the shadow stack. +// If the sp is changed, the value at sp[0] must be set to zero and +// the frame pointer stored at sp[TARGET_POINTER_SIZE] so that the runtime unwinder +// can locate the base of the fixed area in the shadow stack. // void CodeGen::genLclHeap(GenTree* tree) { @@ -2845,10 +2925,16 @@ void CodeGen::genLclHeap(GenTree* tree) } // SP now points at the reserved space just below the allocation. - // Save the frame pointer at sp[0]. + // Save the frame pointer at sp[TARGET_POINTER_SIZE]. // GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex()); + GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, TARGET_POINTER_SIZE); + + // Set sp[0] zero. + // + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, 0); GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, 0); // Leave the base address of the allocated region on the stack. @@ -2918,9 +3004,17 @@ void CodeGen::genLclHeap(GenTree* tree) GetEmitter()->emitIns_I(INS_memory_fill, EA_4BYTE, LINEAR_MEMORY_INDEX); } - // Re-establish unwind invariant: store FP at SP[0] + // SP now points at the reserved space just below the allocation. + // Save the frame pointer at sp[TARGET_POINTER_SIZE]. + // GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex()); + GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, TARGET_POINTER_SIZE); + + // Set sp[0] zero. + // + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex()); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, 0); GetEmitter()->emitIns_I(ins_Store(TYP_I_IMPL), EA_PTRSIZE, 0); // Return value @@ -3175,7 +3269,11 @@ void CodeGen::genCallFinally(BasicBlock* block) // void CodeGen::genEHCatchRet(BasicBlock* block) { - // No codegen needed for Wasm + // TODO-WASM: return the actual ResumeIP here? + // The runtime expects a return value from a catch funclet, + // but nothing will depend on it. + // + GetEmitter()->emitIns_I(INS_i32_const, EA_4BYTE, 0); } void CodeGen::genStructReturn(GenTree* treeNode) @@ -3262,9 +3360,23 @@ void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, unsigned prologSize, un // GCInfo not captured/created by codegen. } +//--------------------------------------------------------------------- +// genReportEH - report EH info to the VM +// void CodeGen::genReportEH() { - // EHInfo not captured/created by codegen. + // We created the EH info earlier, during fgWasmVirtualIP. + // + EHClauseInfo* const info = m_compiler->fgWasmEHInfo; + if (info != nullptr) + { + + // Tell the VM how many EH clauses to expect. + m_compiler->eeSetEHcount(m_compiler->compHndBBtabCount); + m_compiler->Metrics.EHClauseCount = (int)m_compiler->compHndBBtabCount; + + genReportEHClauses(info); + } } //--------------------------------------------------------------------- diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 9461a56ff9d71f..f33d0dd2ee7583 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -5030,6 +5030,13 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl stackLevelSetter.Run(); m_pLowering->FinalizeOutgoingArgSpace(); +#ifdef TARGET_WASM + // Determine if a Virtual IP is needed and add code as needed to + // keep the Virtual IP updated. + // + DoPhase(this, PHASE_WASM_VIRTUAL_IP, &Compiler::fgWasmVirtualIP); +#endif + FinalizeEH(); // We can not add any new tracked variables after this point. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 2ba16e70020f29..d2edbaa8d90659 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1714,6 +1714,15 @@ struct FuncInfoDsc regNumber funFramePointerReg; #endif + EHblkDsc* GetEHDesc(Compiler* comp) const; + BasicBlock* GetStartBlock(Compiler* comp) const; + BasicBlock* GetLastBlock(Compiler* comp) const; + BasicBlockRangeList Blocks(Compiler* comp) const; + unsigned GetFuncletIdx(Compiler* comp) const; + bool IsFunclet() const { return funKind != FUNC_ROOT; } + bool IsMethod() const { return funKind == FUNC_ROOT; } + + #if defined(TARGET_WASM) struct WasmLocalsDecl { @@ -1722,13 +1731,13 @@ struct FuncInfoDsc }; jitstd::vector* funWasmLocalDecls; -#endif // defined(TARGET_WASM) + unsigned funWasmFrameSize; + bool needsUnwindableFrame; + emitLocation* startLoc; + emitLocation* endLoc; - EHblkDsc* GetEHDesc(Compiler* comp) const; - BasicBlock* GetStartBlock(Compiler* comp) const; - BasicBlock* GetLastBlock(Compiler* comp) const; - BasicBlockRangeList Blocks(Compiler* comp) const; - unsigned GetFuncletIdx(Compiler* comp) const; + void ensureUnwindableFrame(Compiler* comp); +#endif // defined(TARGET_WASM) #if defined(TARGET_AMD64) @@ -4155,6 +4164,9 @@ class Compiler #if defined(TARGET_WASM) unsigned lvaWasmSpArg = BAD_VAR_NUM; // lcl var index of Wasm stack pointer arg + unsigned lvaWasmVirtualIP = BAD_VAR_NUM; // Wasm virtual IP slot + unsigned lvaWasmFunctionIndex = BAD_VAR_NUM; // Wasm function index slot + unsigned lvaWasmResumeIP = BAD_VAR_NUM; // Wasm catch resumption IP slot #endif // defined(TARGET_WASM) unsigned lvaInlinedPInvokeFrameVar = BAD_VAR_NUM; // variable representing the InlinedCallFrame @@ -5467,6 +5479,7 @@ class Compiler BasicBlock** fgIndexToBlockMap = nullptr; bool fgWasmHasCatchResumptions = false; FlowGraphTryRegions* fgTryRegions = nullptr; + EHClauseInfo* fgWasmEHInfo = nullptr; #endif FlowGraphDfsTree* m_dfsTree = nullptr; @@ -6442,6 +6455,7 @@ class Compiler void fgWasmEhTransformTry(ArrayStack* catchRetBlocks, unsigned regionIndex, unsigned catchRetIndexLocalNum); PhaseStatus fgWasmControlFlow(); PhaseStatus fgWasmTransformSccs(); + PhaseStatus fgWasmVirtualIP(); #ifdef DEBUG void fgDumpWasmControlFlow(); void fgDumpWasmControlFlowDot(); @@ -12973,6 +12987,18 @@ inline unsigned FuncInfoDsc::GetFuncletIdx(Compiler* comp) const return funcletIdx; } +#if defined(TARGET_WASM) +inline void FuncInfoDsc::ensureUnwindableFrame(Compiler* comp) +{ + if (!needsUnwindableFrame) + { + JITDUMP("%s (index %u) needs to be unwindable\n", IsFunclet() ? "Funclet" : "Main method", GetFuncletIdx(comp)); + + needsUnwindableFrame = true; + } +} +#endif // defined(TARGET_WASM) + // FuncInfoRange: adapter class for forward or reverse iteration of a contiguous range of function/funclet // descriptors using range-based `for`, e.g.: // for (FuncInfoDsc* const func : compiler->Funcs()) diff --git a/src/coreclr/jit/compmemkind.h b/src/coreclr/jit/compmemkind.h index f4583eb55b5570..b6cc1a7cfc251b 100644 --- a/src/coreclr/jit/compmemkind.h +++ b/src/coreclr/jit/compmemkind.h @@ -72,6 +72,7 @@ CompMemKindMacro(Async) CompMemKindMacro(RangeCheckCloning) CompMemKindMacro(WasmSccTransform) CompMemKindMacro(WasmCfgLowering) +CompMemKindMacro(WasmEH) //clang-format on #undef CompMemKindMacro diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 1fe74bbe5468e0..a40ecfd7fe932b 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -128,6 +128,7 @@ CompPhaseNameMacro(PHASE_DFS_BLOCKS_WASM, "Wasm remove unreachable bl CompPhaseNameMacro(PHASE_WASM_EH_FLOW, "Wasm eh control flow", false, -1, false) CompPhaseNameMacro(PHASE_WASM_TRANSFORM_SCCS, "Wasm transform sccs", false, -1, false) CompPhaseNameMacro(PHASE_WASM_CONTROL_FLOW, "Wasm control flow", false, -1, false) +CompPhaseNameMacro(PHASE_WASM_VIRTUAL_IP, "Wasm virtual IP", false, -1, false) CompPhaseNameMacro(PHASE_ASYNC, "Transform async", false, -1, true) CompPhaseNameMacro(PHASE_LCLVARLIVENESS, "Local var liveness", true, -1, false) diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 3fc418a2d9fbe3..3492406ccf2cbd 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -1143,3 +1143,32 @@ void emitter::emitInsSanityCheck(instrDesc* id) { } #endif // DEBUG + +//---------------------------------------------------------------------------------------- +// emitUpdateFuncletLocations: update the start and end locations of each funclet +// +void emitter::emitUpdateFuncletLocations() +{ + insGroup* ig = emitIGlist; + insGroup* prev = nullptr; + + while (ig != nullptr) + { + FuncInfoDsc* const func = m_compiler->funGetFunc(ig->igFuncIdx); + + if ((prev == nullptr) || (prev->igFuncIdx != ig->igFuncIdx)) + { + func->startLoc = new (m_compiler, CMK_UnwindInfo) emitLocation(ig); + } + + insGroup* const next = ig->igNext; + + if ((next == nullptr) || (next->igFuncIdx != ig->igFuncIdx)) + { + func->endLoc = new (m_compiler, CMK_UnwindInfo) emitLocation(ig, ig->igInsCnt); + } + + prev = ig; + ig = next; + } +} diff --git a/src/coreclr/jit/emitwasm.h b/src/coreclr/jit/emitwasm.h index 8c4b580222754b..631d7b9c1f2e2f 100644 --- a/src/coreclr/jit/emitwasm.h +++ b/src/coreclr/jit/emitwasm.h @@ -66,3 +66,5 @@ size_t emitOutputOpcode(BYTE* dst, instruction ins); size_t emitOutputPaddedReloc(uint8_t* destination); size_t emitOutputConstant(uint8_t* destination, const instrDesc* id, bool isSigned, CorInfoReloc relocType); size_t emitOutputValtypeSig(uint8_t* destination, WasmValueType valtype); + +void emitUpdateFuncletLocations(); diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 7e15d45f71e34f..3bca9c2052ac55 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -8,6 +8,7 @@ #include "fgwasm.h" #include "algorithm.h" +#include "lower.h" // for LowerRange() //------------------------------------------------------------------------ // WasmSuccessorEnumerator: Construct an instance of the enumerator. @@ -2226,14 +2227,13 @@ PhaseStatus Compiler::fgWasmEhFlow() } // Allocate an exposed int local to hold the catchret number. - // TODO-WASM: possibly share this with the "virtual IP" - // TODO-WASM: this will need to be at a known offset from $fp so runtime can set it - // when control will not resume in this method. // We do not want any opts acting on this local (eg jump threading) // - unsigned const catchRetIndexLocalNum = lvaGrabTemp(true DEBUGARG("Wasm EH catchret index")); - lvaGetDesc(catchRetIndexLocalNum)->lvType = TYP_INT; - lvaSetVarAddrExposed(catchRetIndexLocalNum DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); + unsigned const resumeIPLocalNum = lvaGrabTemp(true DEBUGARG("Wasm Resume IP")); + lvaGetDesc(resumeIPLocalNum)->lvType = TYP_INT; + lvaSetVarAddrExposed(resumeIPLocalNum DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); + + lvaWasmResumeIP = resumeIPLocalNum; // Now for each region with continuations, add a branch at region entry that // branches to a switch to transfer control to the continuations. @@ -2243,7 +2243,7 @@ PhaseStatus Compiler::fgWasmEhFlow() { if (catchRetBlocks != nullptr) { - fgWasmEhTransformTry(catchRetBlocks, regionIndex, catchRetIndexLocalNum); + fgWasmEhTransformTry(catchRetBlocks, regionIndex, resumeIPLocalNum); } regionIndex++; @@ -2260,10 +2260,10 @@ PhaseStatus Compiler::fgWasmEhFlow() for (BasicBlock* const catchRetBlock : catchRetBlocks->TopDownOrder()) { - JITDUMP("Setting control variable V%02u to %u in " FMT_BB "\n", catchRetIndexLocalNum, + JITDUMP("Setting control variable V%02u to %u in " FMT_BB "\n", resumeIPLocalNum, catchRetBlock->bbPreorderNum, catchRetBlock->bbNum); GenTree* const valueNode = gtNewIconNode(catchRetBlock->bbPreorderNum); - GenTree* const storeNode = gtNewStoreLclVarNode(catchRetIndexLocalNum, valueNode); + GenTree* const storeNode = gtNewStoreLclVarNode(resumeIPLocalNum, valueNode); LIR::Range range = LIR::SeqTree(this, storeNode); LIR::InsertBeforeTerminator(catchRetBlock, std::move(range)); @@ -2281,11 +2281,11 @@ PhaseStatus Compiler::fgWasmEhFlow() // Arguments: // catchRetBlocks - the catch-return blocks for this try region // regionIndex - the EH region index of the try -// catchRetIndexLocalNum - the local variable number holding the catchret index +// resumeIPLocalNum - the local variable number holding the resume IP // void Compiler::fgWasmEhTransformTry(ArrayStack* catchRetBlocks, unsigned regionIndex, - unsigned catchRetIndexLocalNum) + unsigned resumeIPLocalNum) { assert(catchRetBlocks->Height() > 0); @@ -2446,7 +2446,7 @@ void Compiler::fgWasmEhTransformTry(ArrayStack* catchRetBlocks, // Build the IR for the switch // GenTree* const biasValue = gtNewIconNode(caseBias); - GenTree* const controlVar2 = gtNewLclvNode(catchRetIndexLocalNum, TYP_INT); + GenTree* const controlVar2 = gtNewLclvNode(resumeIPLocalNum, TYP_INT); GenTree* const adjustedControlVar = gtNewOperNode(GT_SUB, TYP_INT, controlVar2, biasValue); GenTree* const switchNode = gtNewOperNode(GT_SWITCH, TYP_VOID, adjustedControlVar); { @@ -2462,3 +2462,237 @@ void Compiler::fgWasmEhTransformTry(ArrayStack* catchRetBlocks, LIR::AsRange(rethrowBlock).InsertAtEnd(std::move(range)); } } + +//----------------------------------------------------------------------------- +// fgWasmVirtualIP: set up virtual IP mapping for EH and calls +// +// Returns: +// suitable phase status +// +// Notes: +// We run this before fgWasmControlFlow, so that a linear walk of the +// blocks enumerates all the calls and regions in properly nested order. +// +PhaseStatus Compiler::fgWasmVirtualIP() +{ + unsigned virtualIP = 0; + unsigned updatesAdded = 0; + + // Prefill the EH data fields that are not dependent on + // the Virtual IP. + // + EHClauseInfo* clauses = nullptr; + + if (compHndBBtabCount > 0) + { + clauses = new (this, CMK_WasmEH) EHClauseInfo[compHndBBtabCount]; + + for (EHblkDsc* const dsc : EHClauses(this)) + { + const unsigned index = ehGetIndex(dsc); + CORINFO_EH_CLAUSE clause; + clause.ClassToken = dsc->HasFilter() ? 0 : dsc->ebdTyp; + clause.Flags = ToCORINFO_EH_CLAUSE_FLAGS(dsc->ebdHandlerType); + clause.TryOffset = 0; + clause.TryLength = 0; + clause.HandlerOffset = 0; + clause.HandlerLength = 0; + clauses[index] = {clause, dsc}; + } + } + + fgWasmEHInfo = clauses; + + // Get the local num For the Virtual IP. + // Create it if needed. + // + auto getVirtualIPLclNum = [&]() { + if (lvaWasmVirtualIP != BAD_VAR_NUM) + { + return lvaWasmVirtualIP; + } + + lvaWasmVirtualIP = lvaGrabTemp(true DEBUGARG("Wasm Virtual IP")); + lvaGetDesc(lvaWasmVirtualIP)->lvType = TYP_INT; + lvaGetDesc(lvaWasmVirtualIP)->lvImplicitlyReferenced = true; + lvaSetVarAddrExposed(lvaWasmVirtualIP DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); + + // We'll also need a local for the function index, so create it now as well. + // + lvaWasmFunctionIndex = lvaGrabTemp(true DEBUGARG("Wasm Function Index")); + lvaGetDesc(lvaWasmFunctionIndex)->lvType = TYP_INT; + lvaGetDesc(lvaWasmFunctionIndex)->lvImplicitlyReferenced = true; + lvaSetVarAddrExposed(lvaWasmFunctionIndex DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); + + return lvaWasmVirtualIP; + }; + + // Insert code to update the virtual IP on the stack frame before `beforeNode`. + // If `beforeNode` is null, insert at the start of the block. + // + // In funclet regions we update $sp[4] (on the funclet frame) + // by storing indirect through the SP local sym. This sym reference + // will be translated by RA to refer to the funclet's $sp. + // + // In the main method we update the VirtualIP local + // (which will end up at $fp[4]). + // + auto updateVirtualIPOnFrame = [&](FuncInfoDsc* func, BasicBlock* block, GenTree* beforeNode = nullptr) { + const unsigned virtualIPlclNum = getVirtualIPLclNum(); + GenTree* const virtualIPValue = gtNewIconNode(virtualIP); + GenTree* setVirtualIP = nullptr; + + if (func->IsFunclet()) + { + func->ensureUnwindableFrame(this); + + GenTree* const spLocal = gtNewLclVarNode(lvaWasmSpArg, TYP_I_IMPL); + GenTree* const virtualIPslotAddr = + gtNewOperNode(GT_ADD, TYP_I_IMPL, spLocal, gtNewIconNode(TARGET_POINTER_SIZE)); + GenTreeFlags indirFlags = GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP; + setVirtualIP = gtNewStoreIndNode(TYP_INT, virtualIPslotAddr, virtualIPValue, indirFlags); + } + else + { + setVirtualIP = gtNewStoreLclVarNode(virtualIPlclNum, virtualIPValue); + } + + LIR::Range range = LIR::SeqTree(this, setVirtualIP); + GenTree* const firstNode = range.FirstNode(); + GenTree* const lastNode = range.LastNode(); + + if (beforeNode != nullptr) + { + LIR::AsRange(block).InsertBefore(beforeNode, std::move(range)); + } + else + { + LIR::AsRange(block).InsertAtBeginning(std::move(range)); + } + + LIR::ReadOnlyRange blockRange(firstNode, lastNode); + m_pLowering->LowerRange(block, blockRange); + + updatesAdded++; + }; + + for (FuncInfoDsc* const func : Funcs()) + { + for (BasicBlock* const block : func->Blocks(this)) + { + EHblkDsc* const hndDsc = ehGetBlockHndDsc(block); + EHblkDsc* const tryDsc = ehGetBlockTryDsc(block); + + // Bump the virtual IP each time we enter a new region. + // + if ((tryDsc != nullptr) && (block == tryDsc->ebdTryBeg)) + { + virtualIP++; + clauses[block->getTryIndex()].clause.TryOffset = virtualIP; + + // Multiple try regions can begin at the same block. + // Update all of their offsets here. + // + for (EHblkDsc* const enclosingDsc : EHClauses(this, tryDsc)) + { + if (enclosingDsc->ebdTryBeg == block) + { + // These should be mutual-protect trys. + // + assert(EHblkDsc::ebdIsSameTry(tryDsc, enclosingDsc)); + const unsigned enclosingIndex = ehGetIndex(enclosingDsc); + clauses[enclosingIndex].clause.TryOffset = virtualIP; + } + } + } + + if ((hndDsc != nullptr) && (block == hndDsc->ebdHndBeg)) + { + virtualIP++; + clauses[block->getHndIndex()].clause.HandlerOffset = virtualIP; + } + + if ((hndDsc != nullptr) && hndDsc->HasFilter() && (block == hndDsc->ebdFilter)) + { + virtualIP++; + // For filters we store the offset in the class token field. + // + clauses[block->getHndIndex()].clause.ClassToken = virtualIP; + } + + // For now, just refresh the stack Virtual IP at the start of each non-empty + // block (later we can refine this to something like: blocks that have calls + // or will inspire calls during codegen). + // + if (!block->isEmpty()) + { + updateVirtualIPOnFrame(func, block); + } + + // If this is the end of the region, update its extent. + // (note TryLength and HandlerLength are actually interpreted as the end offset). + // + if ((tryDsc != nullptr) && (block == tryDsc->ebdTryLast)) + { + virtualIP++; + assert(virtualIP > clauses[block->getTryIndex()].clause.TryOffset); + clauses[block->getTryIndex()].clause.TryLength = virtualIP; + + // Multiple try regions can end at the same block. + // Update all of their extents here. + // + // These do not have to be mutual-protect trys. + // + for (EHblkDsc* const enclosingDsc : EHClauses(this, tryDsc)) + { + if (enclosingDsc->ebdTryLast == block) + { + const unsigned enclosingIndex = ehGetIndex(enclosingDsc); + assert(virtualIP > clauses[enclosingIndex].clause.TryOffset); + clauses[enclosingIndex].clause.TryLength = virtualIP; + } + } + } + + if ((hndDsc != nullptr) && (block == hndDsc->ebdHndLast)) + { + virtualIP++; + assert(virtualIP > clauses[block->getHndIndex()].clause.HandlerOffset); + clauses[block->getHndIndex()].clause.HandlerLength = virtualIP; + } + + if ((hndDsc != nullptr) && hndDsc->HasFilter() && (block->Next() == hndDsc->ebdHndBeg)) + { + // Filter length is implicit + virtualIP++; + } + } + } + +#ifdef DEBUG + if (compHndBBtabCount > 0) + { + // Describe the EH clause info... + // + JITDUMP("EH virtual IP ranges\n"); + for (EHblkDsc* const dsc : EHClauses(this)) + { + const unsigned index = ehGetIndex(dsc); + + JITDUMP("EH#%02u: Try [%04u..%04u)", index, clauses[index].clause.TryOffset, + clauses[index].clause.TryLength); + + if (dsc->HasFilter()) + { + JITDUMP(" Filter [%04u..%04u)\n", clauses[index].clause.ClassToken, + clauses[index].clause.HandlerOffset); + } + + JITDUMP(" Handler [%04u..%04u)\n", clauses[index].clause.HandlerOffset, + clauses[index].clause.HandlerLength); + } + } +#endif // DEBUG + + return updatesAdded == 0 ? PhaseStatus::MODIFIED_NOTHING : PhaseStatus::MODIFIED_EVERYTHING; +} diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index b62b77c24596bd..90f866b78cedb3 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -386,15 +386,11 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc { // Behave as if these blocks have edges from their respective region entry blocks. // - if ((block == comp->fgFirstBB) || comp->bbIsFuncletBeg(block)) + if ((block == comp->fgFirstBB) || comp->bbIsFuncletBeg(block) || comp->bbIsTryBeg(block)) { Compiler::AcdKeyDesignator dsg; const unsigned blockData = comp->bbThrowIndex(block, &dsg); - // We do not expect any ACDs to be mapped to try regions (only method/handler/filter) - // - assert(dsg != Compiler::AcdKeyDesignator::KD_TRY); - for (const Compiler::AddCodeDscKey& key : Compiler::AddCodeDscMap::KeyIteration(acdMap)) { if (key.Data() == blockData) diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 8f5b5be1820ef7..d03716102f0ee8 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -3613,14 +3613,6 @@ void Compiler::fgCreateThrowHelperBlock(AddCodeDsc* add) unsigned tryIndex = add->acdTryIndex; unsigned hndIndex = add->acdHndIndex; -#if defined(TARGET_WASM) - // For wasm we put throw helpers in the main method region, or in a non-try - // region of a handler. - // - assert(add->acdKeyDsg != AcdKeyDesignator::KD_TRY); - tryIndex = 0; -#endif - BasicBlock* const newBlk = fgNewBBinRegion(jumpKinds[add->acdKind], tryIndex, hndIndex, /* nearBlk */ nullptr, putInFilter, /* runRarely */ true, /* insertAtEnd */ true); @@ -3849,20 +3841,6 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) assert(inTry || inHnd); -#if defined(TARGET_WASM) - // The current plan for Wasm: method regions or funclets with - // trys will have a single Wasm try handle all - // resumption from catches via virtual IPs. - // - // So we do not need to consider the nesting of the throw - // in try regions, just in handlers. - // - if (!inHnd) - { - *dsg = AcdKeyDesignator::KD_NONE; - return 0; - } -#else if (inTry && (!inHnd || (tryIndex < hndIndex))) { // The most enclosing region is a try body, use it @@ -3870,7 +3848,6 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) *dsg = AcdKeyDesignator::KD_TRY; return tryIndex; } -#endif // !defined(TARGET_WASM) // The most enclosing region is a handler which will be a funclet // Now we have to figure out if blk is in the filter or handler diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index c53e8a69078cc4..1881933d20f818 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -4174,6 +4174,83 @@ unsigned Compiler::lvaGetMaxSpillTempSize() * | | downward | * V * + * Wasm leaf frame, no localloc + * + * + * | caller frame | + * +=======================+ <---- Virtual '0' + * | | + * ~ Variables ~ + * | | + * |-----------------------| <---- $sp (== $fp) + * | | + * | | | + * | | Stack grows | + * | downward + * V + * + * Wasm, leaf frame, localloc + * + * | caller frame | + * +=======================+ <---- Virtual '0' + * | | + * ~ Variables ~ + * | | + * ~-----------------------| <---- $fp + * | localloc | + * |-----------------------| <---- $sp + * | | + * | | | + * | | Stack grows | + * | downward + * V + * + * Wasm, non-leaf frame, no localloc + * + * + * | caller frame | + * +=======================+ <---- Virtual '0' + * | | + * ~ Variables ~ + * | | + * |-----------------------| + * | Resume IP | + * |-----------------------| + * | Virtual IP | + * |-----------------------| + * | Function Index | + * |-----------------------| <---- $sp (== $fp) + * | | + * | | | + * | | Stack grows | + * | downward + * V + * + * Wasm, non-leaf frame, localloc + * + * | caller frame | + * +=======================+ <---- Virtual '0' + * | | + * ~ Variables ~ + * | | + * |-----------------------| + * | Resume IP | + * |-----------------------| + * | Virtual IP | + * |-----------------------| + * | Function Index | + * |-----------------------| <---- $fp + * | localloc | + * |-----------------------| + * | $fp | + * |-----------------------| + * | 0 | + * |-----------------------| <---- $sp + * | | + * | | | + * | | Stack grows | + * | downward + * V * * Doing this all in one pass is 'hard'. So instead we do it in 2 basic passes: * 1. Assign all the offsets relative to the Virtual '0'. Offsets above (the @@ -5110,6 +5187,14 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() } #endif +#if defined(TARGET_WASM) + // These Wasm locals must be allocated last. + if ((lclNum == lvaWasmVirtualIP) || (lclNum == lvaWasmResumeIP) || (lclNum == lvaWasmFunctionIndex)) + { + continue; + } +#endif + bool allocateOnFrame = varDsc->lvOnFrame; if (varDsc->lvRegister && (lvaDoneFrameLayout == REGALLOC_FRAME_LAYOUT) && @@ -5436,6 +5521,25 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() } #endif // FEATURE_FIXED_OUT_ARGS +#if defined(TARGET_WASM) + // These Wasm locals must be allocated last + // + if (lvaWasmResumeIP != BAD_VAR_NUM) + { + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaWasmResumeIP, TARGET_POINTER_SIZE, stkOffs); + } + + if (lvaWasmVirtualIP != BAD_VAR_NUM) + { + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaWasmVirtualIP, TARGET_POINTER_SIZE, stkOffs); + } + + if (lvaWasmFunctionIndex != BAD_VAR_NUM) + { + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaWasmFunctionIndex, TARGET_POINTER_SIZE, stkOffs); + } +#endif + #if HAS_FIXED_REGISTER_SET // compLclFrameSize equals our negated virtual stack offset minus the pushed registers and return address // and the pushed frame pointer register which for some strange reason isn't part of 'compCalleeRegsPushed'. diff --git a/src/coreclr/jit/unwindwasm.cpp b/src/coreclr/jit/unwindwasm.cpp index 0f464ace472afc..ee5ab16979a423 100644 --- a/src/coreclr/jit/unwindwasm.cpp +++ b/src/coreclr/jit/unwindwasm.cpp @@ -6,9 +6,10 @@ #pragma hdrstop #endif -// -// We don't have native-code-offset-based unwind info on WASM so the following functions are all no-ops. -// +#ifndef TARGET_WASM +#error "This should be included only for Wasm" +#endif // TARGET_WASM + void Compiler::unwindBegProlog() { } @@ -17,18 +18,101 @@ void Compiler::unwindEndProlog() { } +//------------------------------------------------------------------------ +// Compiler::unwindAllocStack: account for stack pointer movement in the prolog. +// +// Arguments: +// size - The amount of stack space to allocate. +// +void Compiler::unwindAllocStack(unsigned size) +{ + FuncInfoDsc* const func = funCurrentFunc(); + assert(func != nullptr); + func->funWasmFrameSize += size; +} + +//------------------------------------------------------------------------ +// Compiler::unwindReserveFunc: Reserve the unwind information from the VM for a +// given main function or funclet. +// +// Arguments: +// func - The main function or funclet to reserve unwind info for. +// void Compiler::unwindReserve() { + assert(!compGeneratingProlog); + assert(!compGeneratingEpilog); + + for (FuncInfoDsc* const func : Funcs()) + { + unwindReserveFunc(func); + } } +//------------------------------------------------------------------------ +// Compiler::unwindReserveFunc: Reserve the unwind information from the VM for +// the main function or funclet. +// +// Arguments: +// func - The main function or funclet to reserve unwind info for. +// void Compiler::unwindReserveFunc(FuncInfoDsc* func) { + bool isFunclet = func->IsFunclet(); + bool isColdCode = false; + ULONG encodedSize = emitter::SizeOfULEB128(func->funWasmFrameSize); + + eeReserveUnwindInfo(isFunclet, isColdCode, encodedSize); } +//------------------------------------------------------------------------ +// Compiler::unwindEmit: Report all the unwind information to the VM. +// +// Arguments: +// pHotCode - Pointer to the beginning of the memory with the function and funclet hot code. +// pColdCode - Pointer to the beginning of the memory with the function and funclet cold code. +// void Compiler::unwindEmit(void* pHotCode, void* pColdCode) { + assert(!compGeneratingProlog); + assert(!compGeneratingEpilog); + + GetEmitter()->emitUpdateFuncletLocations(); + + for (FuncInfoDsc* const func : Funcs()) + { + unwindEmitFunc(func, pHotCode, pColdCode); + } } +//------------------------------------------------------------------------ +// Compiler::unwindEmitFunc: Report the unwind information to the VM for +// the main function or funclet. +// +// Arguments: +// func - The main function or funclet to report unwind info for. +// pHotCode - Pointer to the beginning of the memory with the function and funclet hot code. +// pColdCode - Pointer to the beginning of the memory with the function and funclet cold code. +// +// Notes: +// For Wasm the unwind extent describes the entire span of Wasm code for the method or funclet. +// void Compiler::unwindEmitFunc(FuncInfoDsc* func, void* pHotCode, void* pColdCode) { + UNATIVE_OFFSET startOffset = func->startLoc->CodeOffset(GetEmitter()); + UNATIVE_OFFSET endOffset = func->endLoc->CodeOffset(GetEmitter()); + + // Wasm does not have cold code. + // + pColdCode = nullptr; + + // Unwind info is just the frame size. + // Record frame size with ULEB128 compression. + // + uint8_t buffer[5]; + ULONG encodedSize = (ULONG)GetEmitter()->emitOutputULEB128(buffer, func->funWasmFrameSize); + assert(encodedSize <= sizeof(buffer)); + + eeAllocUnwindInfo((BYTE*)pHotCode, (BYTE*)pColdCode, startOffset, endOffset, encodedSize, (BYTE*)&buffer, + (CorJitFuncKind)func->funKind); }