diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index da319aed42c5b1..801f0fcaf82552 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -5635,7 +5635,7 @@ ClrDataAccess::GetJitHelperName( for (int i = 0; i < CORINFO_HELP_COUNT; i++) { - if (address == (TADDR)(pTable[i].pfnHelper)) + if (address == pTable[i].pfnHelper) return s_rgHelperNames[i]; } } @@ -5652,7 +5652,7 @@ ClrDataAccess::GetJitHelperName( PTR_READ(dac_cast(&hlpDynamicFuncTable), DYNAMIC_CORINFO_HELP_COUNT * sizeof(VMHELPDEF))); for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++) { - if (address == (TADDR)(pDynamicTable[d].pfnHelper)) + if (address == pDynamicTable[d].pfnHelper) { return s_rgHelperNames[s_rgDynamicHCallIds[d]]; } @@ -5991,7 +5991,7 @@ ClrDataAccess::GetMethodVarInfo(MethodDesc* methodDesc, BOOL success = DebugInfoManager::GetBoundariesAndVars( request, DebugInfoStoreNew, NULL, // allocator - BoundsType::Instrumented, + BoundsType::Instrumented, NULL, NULL, &countNativeVarInfo, &nativeVars); diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index 794f0a693bfeb8..eb7ea2f3205fcd 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -210,13 +210,13 @@ SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBu // BYTE* bufferBypassRW = pSharedPatchBypassBufferRW->BypassBuffer; - + // Overwrite the *signed* displacement. int dwOldDisp = *(int*)(&patchBypassRX[instrAttrib.m_dwOffsetToDisp]); int dwNewDisp = offsetof(SharedPatchBypassBuffer, BypassBuffer) - (offsetof(SharedPatchBypassBuffer, PatchBypass) + instrAttrib.m_cbInstr); *(int*)(&patchBypassRW[instrAttrib.m_dwOffsetToDisp]) = dwNewDisp; - + // This could be an LEA, which we'll just have to change into a MOV and copy the original address. if (((patchBypassRX[0] == 0x4C) || (patchBypassRX[0] == 0x48)) && (patchBypassRX[1] == 0x8d)) { @@ -229,7 +229,7 @@ SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBu _ASSERTE(instrAttrib.m_cOperandSize <= SharedPatchBypassBuffer::cbBufferBypass); // Copy the data into our buffer. memcpy(bufferBypassRW, this->address + instrAttrib.m_cbInstr + dwOldDisp, instrAttrib.m_cOperandSize); - + if (instrAttrib.m_fIsWrite) { // save the actual destination address and size so when we TriggerSingleStep() we can update the value @@ -238,17 +238,17 @@ SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBu } } } - + #endif // TARGET_AMD64 m_pSharedPatchBypassBuffer->SetInstructionAttrib(instrAttrib); - + // Since we just created a new buffer of code, but the CPU caches code and may // not be aware of our changes. This should force the CPU to dump any cached // instructions it has in this region and load the new ones from memory FlushInstructionCache(GetCurrentProcess(), patchBypassRW + CORDbg_BREAK_INSTRUCTION_SIZE, MAX_INSTRUCTION_LENGTH - CORDbg_BREAK_INSTRUCTION_SIZE); - + return m_pSharedPatchBypassBuffer; } #endif // !FEATURE_EMULATE_SINGLESTEP @@ -2405,10 +2405,10 @@ static bool _AddrIsJITHelper(PCODE addr) { for (int i = 0; i < CORINFO_HELP_COUNT; i++) { - if (hlpFuncTable[i].pfnHelper == (void*)addr) + if (hlpFuncTable[i].pfnHelper == addr) { LOG((LF_CORDB, LL_INFO10000, - "_ANIM: address of helper function found: 0x%08x\n", + "_ANIM: address of helper function found: 0x%zx\n", addr)); return true; } @@ -2416,10 +2416,10 @@ static bool _AddrIsJITHelper(PCODE addr) for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++) { - if (hlpDynamicFuncTable[d].pfnHelper == (void*)addr) + if (hlpDynamicFuncTable[d].pfnHelper == addr) { LOG((LF_CORDB, LL_INFO10000, - "_ANIM: address of helper function found: 0x%08x\n", + "_ANIM: address of helper function found: 0x%zx\n", addr)); return true; } @@ -2427,7 +2427,7 @@ static bool _AddrIsJITHelper(PCODE addr) LOG((LF_CORDB, LL_INFO10000, "_ANIM: address within runtime dll, but not a helper function " - "0x%08x\n", addr)); + "0x%zx\n", addr)); } #else // !defined(HOST_64BIT) && !defined(TARGET_UNIX) // TODO: Figure out what we want to do here diff --git a/src/coreclr/inc/regdisp.h b/src/coreclr/inc/regdisp.h index 7a5cf9d9d0cec4..78340a4268431b 100644 --- a/src/coreclr/inc/regdisp.h +++ b/src/coreclr/inc/regdisp.h @@ -471,7 +471,9 @@ inline void FillContextPointers(PT_KNONVOLATILE_CONTEXT_POINTERS pCtxPtrs, PT_CO *(&pCtxPtrs->Tp) = &pCtx->Tp; *(&pCtxPtrs->Fp) = &pCtx->Fp; *(&pCtxPtrs->Ra) = &pCtx->Ra; -#else // TARGET_RISCV64 +#elif defined(TARGET_WASM) + // Wasm doesn't have registers +#else // TARGET_WASM PORTABILITY_ASSERT("FillContextPointers"); #endif // _TARGET_???_ (ELSE) } diff --git a/src/coreclr/pal/src/arch/wasm/stubs.cpp b/src/coreclr/pal/src/arch/wasm/stubs.cpp index 66b73f8badf17b..5f7c99e408474f 100644 --- a/src/coreclr/pal/src/arch/wasm/stubs.cpp +++ b/src/coreclr/pal/src/arch/wasm/stubs.cpp @@ -8,9 +8,16 @@ SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do /* debugbreak */ +#ifdef _DEBUG +extern void DBG_PrintInterpreterStack(); +#endif // _DEBUG + extern "C" void DBG_DebugBreak() { +#ifdef _DEBUG + DBG_PrintInterpreterStack(); +#endif // _DEBUG asm volatile ("unreachable"); } diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index daf5df18ee75fc..da41b01f9c3310 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -225,8 +225,8 @@ void* DispatchCallSimple( callDescrData.pTarget = pTargetAddress; #ifdef TARGET_WASM - PORTABILITY_ASSERT("wasm need to fill call description data"); -#endif + callDescrData.nArgsSize = numStackSlotsToCopy * sizeof(ARGHOLDER_TYPE); +#endif // TARGET_WASM if ((dwDispatchCallSimpleFlags & DispatchCallSimple_CatchHandlerFoundNotification) != 0) { @@ -519,9 +519,6 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * CallDescrData callDescrData; callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); -#ifdef TARGET_WASM - callDescrData.pTransitionBlock = (TransitionBlock*)pTransitionBlock; -#endif _ASSERTE((nStackBytes % TARGET_POINTER_SIZE) == 0); callDescrData.numStackSlots = nStackBytes / TARGET_POINTER_SIZE; #ifdef CALLDESCR_ARGREGS @@ -539,9 +536,8 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * callDescrData.fpReturnSize = fpReturnSize; callDescrData.pTarget = m_pCallTarget; #ifdef TARGET_WASM - callDescrData.pMD = m_pMD; callDescrData.nArgsSize = m_argIt.GetArgSize(); -#endif +#endif // TARGET_WASM CallDescrWorkerWithHandler(&callDescrData); diff --git a/src/coreclr/vm/callhelpers.h b/src/coreclr/vm/callhelpers.h index 91a249cbc7660b..0527dbe0d8076c 100644 --- a/src/coreclr/vm/callhelpers.h +++ b/src/coreclr/vm/callhelpers.h @@ -29,11 +29,8 @@ struct CallDescrData UINT32 fpReturnSize; PCODE pTarget; #ifdef TARGET_WASM - // method description is used to compile the method with the interpreter - MethodDesc* pMD; // size of the arguments and the transition block are used to execute the method with the interpreter size_t nArgsSize; - TransitionBlock* pTransitionBlock; #endif #ifdef CALLDESCR_RETBUFFARGREG diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 3d4abbee5358d4..6fa48d725577a6 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -12,6 +12,10 @@ #ifndef __CALLING_CONVENTION_INCLUDED #define __CALLING_CONVENTION_INCLUDED +#ifdef FEATURE_INTERPRETER +#include "../../interpreter/interpretershared.h" +#endif // FEATURE_INTERPRETER + BOOL IsRetBuffPassedAsFirstArg(); // Describes how a single argument is laid out in registers and/or stack locations when given as an input to a @@ -491,31 +495,31 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE while (typ == ELEMENT_TYPE_VALUETYPE && pMT->GetNumInstanceFields() == 1 && (!pMT->HasLayout() || - pMT->GetNumInstanceFieldBytes() == 4 - )) // Don't do the optimization if we're getting specified anything but the trivial layout. - { - FieldDesc * pFD = pMT->GetApproxFieldDescListRaw(); + pMT->GetNumInstanceFieldBytes() == 4 + )) // Don't do the optimization if we're getting specified anything but the trivial layout. + { + FieldDesc * pFD = pMT->GetApproxFieldDescListRaw(); CorElementType type = pFD->GetFieldType(); bool exitLoop = false; - switch (type) + switch (type) { case ELEMENT_TYPE_VALUETYPE: { - //@todo: Is it more apropos to call LookupApproxFieldTypeHandle() here? - TypeHandle fldHnd = pFD->GetApproxFieldTypeHandleThrowing(); + //@todo: Is it more apropos to call LookupApproxFieldTypeHandle() here? + TypeHandle fldHnd = pFD->GetApproxFieldTypeHandleThrowing(); CONSISTENCY_CHECK(!fldHnd.IsNull()); pMT = fldHnd.GetMethodTable(); FALLTHROUGH; - } + } case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_I: case ELEMENT_TYPE_U: - case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: - { + { typ = type; - break; + break; } default: exitLoop = true; @@ -853,7 +857,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE pLoc->m_idxFloatReg = floatRegOfsInBytes / FLOAT_REGISTER_SIZE; pLoc->m_cFloatReg = 1; } - else + else #endif // UNIX_AMD64_ABI if (!TransitionBlock::IsStackArgumentOffset(argOffset)) { @@ -1874,11 +1878,11 @@ int ArgIteratorTemplate::GetNextOffset() return argOfs; #elif defined(TARGET_WASM) - int cbArg = ALIGN_UP(StackElemSize(argSize), TARGET_POINTER_SIZE); + int cbArg = ALIGN_UP(argSize, INTERP_STACK_SLOT_SIZE); int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; - + m_ofsStack += cbArg; - + return argOfs; #else PORTABILITY_ASSERT("ArgIteratorTemplate::GetNextOffset"); diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 00badfce6afc1f..d5a97e58a97afc 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -5654,6 +5654,9 @@ PCODE PInvoke::GetStubForILStub(PInvokeMethodDesc* pNMD, MethodDesc** ppStubMD, // varargs goes through vararg PInvoke stub // pStub = TheVarargPInvokeStub(pNMD->HasRetBuffArg()); + + // Only vararg P/Invoke use shared stubs, they need a precode to push the hidden argument. + (void)pNMD->GetOrCreatePrecode(); } if (pNMD->IsEarlyBound()) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index bc504bab76272e..9b245c393b5524 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -11,14 +11,37 @@ // for numeric_limits #include -#ifdef TARGET_WASM -void InvokeCalliStub(PCODE ftn, CallStubHeader *stubHeaderTemplate, int8_t *pArgs, int8_t *pRet); -void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target); +// Call invoker helpers provided by platform. +void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target); +void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int32_t callArgsOffset, int32_t returnOffset, PCODE callTarget); +void InvokeCalliStub(PCODE ftn, void* stubHeaderTemplate, int8_t *pArgs, int8_t *pRet); void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target); -#else + +// Use the NOINLINE to ensure that the InlinedCallFrame in this method is a lower stack address than any InterpMethodContextFrame values. +NOINLINE +void InvokeUnmanagedMethodWithTransition(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int32_t callArgsOffset, int32_t returnOffset, PCODE callTarget) +{ + InlinedCallFrame inlinedCallFrame; + inlinedCallFrame.m_pCallerReturnAddress = (TADDR)pFrame->ip; + inlinedCallFrame.m_pCallSiteSP = pFrame; + inlinedCallFrame.m_pCalleeSavedFP = (TADDR)stack; + inlinedCallFrame.m_pThread = GetThread(); + inlinedCallFrame.m_Datum = NULL; + inlinedCallFrame.Push(); + + { + GCX_PREEMP(); + // WASM-TODO: Handle unmanaged calling conventions + InvokeManagedMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); + } + + inlinedCallFrame.Pop(); +} + +#ifndef TARGET_WASM #include "callstubgenerator.h" -CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD) +static CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD) { CONTRACTL { @@ -49,7 +72,7 @@ CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD) return header; } -void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target) +void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target) { CONTRACTL { @@ -73,12 +96,17 @@ void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE ta pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } +void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int32_t callArgsOffset, int32_t returnOffset, PCODE callTarget) +{ + InvokeManagedMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); +} + void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target) { CONTRACTL { THROWS; - MODE_ANY; + MODE_COOPERATIVE; PRECONDITION(CheckPointer(pMDDelegateInvoke)); PRECONDITION(CheckPointer(pArgs)); PRECONDITION(CheckPointer(pRet)); @@ -101,19 +129,20 @@ void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, in pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } -void InvokeCalliStub(PCODE ftn, CallStubHeader *stubHeaderTemplate, int8_t *pArgs, int8_t *pRet) +void InvokeCalliStub(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet) { CONTRACTL { THROWS; MODE_ANY; PRECONDITION(CheckPointer((void*)ftn)); - PRECONDITION(CheckPointer(stubHeaderTemplate)); + PRECONDITION(CheckPointer(cookie)); } CONTRACTL_END // CallStubHeaders encode their destination addresses in the Routines array, so they need to be // copied to a local buffer before we can actually set their target address. + CallStubHeader* stubHeaderTemplate = (CallStubHeader*)cookie; size_t templateSize = stubHeaderTemplate->GetSize(); uint8_t* actualCallStub = (uint8_t*)alloca(templateSize); memcpy(actualCallStub, stubHeaderTemplate, templateSize); @@ -122,6 +151,14 @@ void InvokeCalliStub(PCODE ftn, CallStubHeader *stubHeaderTemplate, int8_t *pArg pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } +LPVOID GetCookieForCalliSig(MetaSig metaSig) +{ + STANDARD_VM_CONTRACT; + + CallStubGenerator callStubGenerator; + return callStubGenerator.GenerateCallStubForSig(metaSig); +} + // Create call stub for calling interpreted methods from JITted/AOTed code. CallStubHeader *CreateNativeToInterpreterCallStub(InterpMethod* pInterpMethod) { @@ -155,27 +192,45 @@ CallStubHeader *CreateNativeToInterpreterCallStub(InterpMethod* pInterpMethod) return pHeader; } - #endif // !TARGET_WASM -// Use the NOINLINE to ensure that the InlinedCallFrame in this method is a lower stack address than any InterpMethodContextFrame values. -NOINLINE void InvokePInvokeMethod(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int32_t callArgsOffset, int32_t returnOffset, PCODE callTarget) +#ifdef _DEBUG +void DBG_PrintInterpreterStack() { - InlinedCallFrame inlinedCallFrame; - inlinedCallFrame.m_pCallerReturnAddress = (TADDR)pFrame->ip; - inlinedCallFrame.m_pCallSiteSP = pFrame; - inlinedCallFrame.m_pCalleeSavedFP = (TADDR)stack; - inlinedCallFrame.m_pThread = GetThread(); - inlinedCallFrame.m_Datum = NULL; - inlinedCallFrame.Push(); + Thread* pThread = GetThread(); + if (pThread == NULL) + return; + int32_t frameCount = 0; + + // Get the "capital F" frame and start walking. + for (Frame* pFrame = pThread->GetFrame(); pFrame != FRAME_TOP; pFrame = pFrame->PtrNextFrame()) { - GCX_PREEMP(); - InvokeCompiledMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); - } + fprintf(stderr, "Frame (%s): %p\n", Frame::GetFrameTypeName(pFrame->GetFrameIdentifier()), pFrame); + if (pFrame->GetFrameIdentifier() != FrameIdentifier::InterpreterFrame) + { + fprintf(stderr, " Skipping %p\n", pFrame); + continue; + } - inlinedCallFrame.Pop(); + // Walk the current block of managed frames. + InterpreterFrame* pInterpFrame = (InterpreterFrame*)pFrame; + InterpMethodContextFrame* cxtFrame = pInterpFrame->GetTopInterpMethodContextFrame(); + while (cxtFrame != NULL) + { + MethodDesc* currentMD = ((MethodDesc*)cxtFrame->startIp->Method->methodHnd); + + size_t irOffset = ((size_t)cxtFrame->ip - (size_t)(&cxtFrame->startIp[1])) / sizeof(size_t); + fprintf(stderr, "%4d) %s::%s, IR_%04zx\n", + frameCount++, + currentMD->GetMethodTable()->GetDebugClassName(), + currentMD->GetName(), + irOffset); + cxtFrame = cxtFrame->pParent; + } + } } +#endif // _DEBUG typedef void* (*HELPER_FTN_P_P)(void*); typedef void* (*HELPER_FTN_BOX_UNBOX)(MethodTable*, void*); @@ -183,7 +238,6 @@ typedef Object* (*HELPER_FTN_NEWARR)(MethodTable*, intptr_t); typedef void* (*HELPER_FTN_P_PP)(void*, void*); typedef void (*HELPER_FTN_V_PPP)(void*, void*, void*); - InterpThreadContext::InterpThreadContext() { // FIXME VirtualAlloc/mmap with INTERP_STACK_ALIGNMENT alignment @@ -254,13 +308,13 @@ template static THelper GetPossiblyIndirectHelper(const Inter } #ifdef FEATURE_PORTABLE_ENTRYPOINTS - if (!PortableEntryPoint::IsNativeEntryPoint((TADDR)addr)) + if (!PortableEntryPoint::HasNativeEntryPoint((PCODE)addr)) { _ASSERTE(pILTargetMethod != NULL); - *pILTargetMethod = PortableEntryPoint::GetMethodDesc((TADDR)addr); + *pILTargetMethod = PortableEntryPoint::GetMethodDesc((PCODE)addr); return NULL; // Return null to interpret this entrypoint } - addr = PortableEntryPoint::GetActualCode((TADDR)addr); + addr = PortableEntryPoint::GetActualCode((PCODE)addr); #endif // FEATURE_PORTABLE_ENTRYPOINTS return (THelper)addr; @@ -428,8 +482,8 @@ void* DoGenericLookup(void* genericVarAsPtr, InterpGenericLookup* pLookup) // TODO! If this becomes a performance bottleneck, we could expand out the various permutations of this // so that we have 24 versions of lookup (or 48 is we allow for avoiding the null check), do the only // if check to figure out which one to use, and then have the rest of the logic be straight-line code. - MethodTable *pMT = nullptr; - MethodDesc* pMD = nullptr; + MethodTable *pMT = NULL; + MethodDesc* pMD = NULL; uint8_t* lookup; if (pLookup->lookupType == InterpGenericLookupType::This) { @@ -500,10 +554,14 @@ LONG IgnoreCppExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) : EXCEPTION_EXECUTE_HANDLER; } -// Wrapper around MethodDesc::PrepareInitialCode to handle possible managed exceptions thrown by it. -void PrepareInitialCode(MethodDesc *pMD) +// Wrapper around MethodDesc::DoPrestub to handle possible managed exceptions thrown by it. +static void CallPreStub(MethodDesc *pMD) { STATIC_STANDARD_VM_CONTRACT; + _ASSERTE(pMD != NULL); + + if (!pMD->IsPointingToPrestub()) + return; struct Param { @@ -513,7 +571,7 @@ void PrepareInitialCode(MethodDesc *pMD) PAL_TRY(Param *, pParam, ¶m) { - pParam->pMethodDesc->PrepareInitialCode(CallerGCMode::Coop); + (void)pParam->pMethodDesc->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); } PAL_EXCEPT_FILTER(IgnoreCppExceptionFilter) { @@ -1835,9 +1893,25 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_CALL_HELPER_P_P: { - HELPER_FTN_P_P helperFtn = GetPossiblyIndirectHelper(pMethod, ip[2]); void* helperArg = pMethod->pDataItems[ip[3]]; + MethodDesc *pILTargetMethod = NULL; + HELPER_FTN_P_P helperFtn = GetPossiblyIndirectHelper(pMethod, ip[2], &pILTargetMethod); + if (pILTargetMethod != NULL) + { + returnOffset = ip[1]; + int stackOffset = pMethod->allocaSize; + callArgsOffset = stackOffset; + + // Pass argument to the target method + LOCAL_VAR(stackOffset, void*) = helperArg; + + targetMethod = pILTargetMethod; + ip += 4; + goto CALL_INTERP_METHOD; + } + + _ASSERTE(helperFtn != NULL); LOCAL_VAR(ip[1], void*) = helperFtn(helperArg); ip += 4; break; @@ -1855,10 +1929,28 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_CALL_HELPER_P_PS: { - HELPER_FTN_P_PP helperFtn = GetPossiblyIndirectHelper(pMethod, ip[3]); - void* helperArg = pMethod->pDataItems[ip[4]]; + void* helperArg1 = pMethod->pDataItems[ip[4]]; + void* helperArg2 = LOCAL_VAR(ip[2], void*); - LOCAL_VAR(ip[1], void*) = helperFtn(helperArg, LOCAL_VAR(ip[2], void*)); + MethodDesc *pILTargetMethod = NULL; + HELPER_FTN_P_PP helperFtn = GetPossiblyIndirectHelper(pMethod, ip[3], &pILTargetMethod); + if (pILTargetMethod != NULL) + { + returnOffset = ip[1]; + int stackOffset = pMethod->allocaSize; + callArgsOffset = stackOffset; + + // Pass arguments to the target method + LOCAL_VAR(stackOffset, void*) = helperArg1; + LOCAL_VAR(stackOffset + INTERP_STACK_SLOT_SIZE, void*) = helperArg2; + + targetMethod = pILTargetMethod; + ip += 5; + goto CALL_INTERP_METHOD; + } + + _ASSERTE(helperFtn != NULL); + LOCAL_VAR(ip[1], void*) = helperFtn(helperArg1, helperArg2); ip += 5; break; } @@ -1878,8 +1970,23 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr InterpGenericLookup *pLookup = (InterpGenericLookup*)&pMethod->pDataItems[ip[4]]; void* helperArg = DoGenericLookup(LOCAL_VAR(ip[2], void*), pLookup); - HELPER_FTN_P_P helperFtn = GetPossiblyIndirectHelper(pMethod, ip[3]); + MethodDesc *pILTargetMethod = NULL; + HELPER_FTN_P_P helperFtn = GetPossiblyIndirectHelper(pMethod, ip[3], &pILTargetMethod); + if (pILTargetMethod != NULL) + { + returnOffset = ip[1]; + int stackOffset = pMethod->allocaSize; + callArgsOffset = stackOffset; + + // Pass argument to the target method + LOCAL_VAR(stackOffset, void*) = helperArg; + + targetMethod = pILTargetMethod; + ip += 5; + goto CALL_INTERP_METHOD; + } + _ASSERTE(helperFtn != NULL); LOCAL_VAR(ip[1], void*) = helperFtn(helperArg); ip += 5; break; @@ -1888,11 +1995,28 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_CALL_HELPER_P_GS: { InterpGenericLookup *pLookup = (InterpGenericLookup*)&pMethod->pDataItems[ip[5]]; - void* helperArg = DoGenericLookup(LOCAL_VAR(ip[2], void*), pLookup); + void* helperArg1 = DoGenericLookup(LOCAL_VAR(ip[2], void*), pLookup); + void* helperArg2 = LOCAL_VAR(ip[3], void*); - HELPER_FTN_P_PP helperFtn = GetPossiblyIndirectHelper(pMethod, ip[4]); + MethodDesc *pILTargetMethod = NULL; + HELPER_FTN_P_PP helperFtn = GetPossiblyIndirectHelper(pMethod, ip[4], &pILTargetMethod); + if (pILTargetMethod != NULL) + { + returnOffset = ip[1]; + int stackOffset = pMethod->allocaSize; + callArgsOffset = stackOffset; - LOCAL_VAR(ip[1], void*) = helperFtn(helperArg, LOCAL_VAR(ip[3], void*)); + // Pass arguments to the target method + LOCAL_VAR(stackOffset, void*) = helperArg1; + LOCAL_VAR(stackOffset + INTERP_STACK_SLOT_SIZE, void*) = helperArg2; + + targetMethod = pILTargetMethod; + ip += 6; + goto CALL_INTERP_METHOD; + } + + _ASSERTE(helperFtn != NULL); + LOCAL_VAR(ip[1], void*) = helperFtn(helperArg1, helperArg2); ip += 6; break; } @@ -1967,14 +2091,14 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int32_t calliFunctionPointerVar = ip[3]; int32_t calliCookie = ip[4]; - CallStubHeader *pCallStub = (CallStubHeader*)pMethod->pDataItems[calliCookie]; + void* cookie = pMethod->pDataItems[calliCookie]; ip += 5; // Save current execution state for when we return from called method pFrame->ip = ip; // Interpreter-FIXME: isTailcall - InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), pCallStub, stack + callArgsOffset, stack + returnOffset); + InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack + callArgsOffset, stack + returnOffset); break; } @@ -2001,11 +2125,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr if (flags & (int32_t)PInvokeCallFlags::SuppressGCTransition) { - InvokeCompiledMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); + InvokeUnmanagedMethod(targetMethod, stack, pFrame, callArgsOffset, returnOffset, callTarget); } else { - InvokePInvokeMethod(targetMethod, stack, pFrame, callArgsOffset, returnOffset, callTarget); + InvokeUnmanagedMethodWithTransition(targetMethod, stack, pFrame, callArgsOffset, returnOffset, callTarget); } break; @@ -2067,18 +2191,15 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // small subset of frames high. pInterpreterFrame->SetTopInterpMethodContextFrame(pFrame); GCX_PREEMP(); - // Attempt to setup the interpreter code for the target method. - if ((targetMethod->IsIL() || targetMethod->IsNoMetadata()) && !targetMethod->IsUnboxingStub()) - { - PrepareInitialCode(targetMethod); - } + CallPreStub(targetMethod); + targetIp = targetMethod->GetInterpreterCode(); } if (targetIp == NULL) { // If we didn't get the interpreter code pointer setup, then this is a method we need to invoke as a compiled method. // Interpreter-FIXME: Implement tailcall via helpers, see https://github.com/dotnet/runtime/blob/main/docs/design/features/tailcalls-with-helpers.md - InvokeCompiledMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, targetMethod->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); + InvokeManagedMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, targetMethod->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); break; } } @@ -2244,7 +2365,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_THROW: { OBJECTREF throwable; - if (LOCAL_VAR(ip[1], OBJECTREF) == nullptr) + if (LOCAL_VAR(ip[1], OBJECTREF) == NULL) { EEException ex(kNullReferenceException); throwable = ex.CreateThrowable(); diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index b93e15ad331e78..7f4a274c5fae30 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2515,24 +2515,31 @@ enum __CorInfoHelpFunc { #include "jithelpers.h" #ifdef _DEBUG -#define HELPERDEF(code, lpv, sig) { (LPVOID)(lpv), #code }, +#define HELPERDEF(code, lpv, isDynamicHelper) { (PCODE)(lpv), #code, isDynamicHelper }, +#elif defined(TARGET_WASM) +#define HELPERDEF(code, lpv, isDynamicHelper) { (PCODE)(lpv), isDynamicHelper }, #else // !_DEBUG -#define HELPERDEF(code, lpv, sig) { (LPVOID)(lpv) }, +#define HELPERDEF(code, lpv, isDynamicHelper) { (PCODE)(lpv) }, #endif // !_DEBUG // static helpers - constant array const VMHELPDEF hlpFuncTable[CORINFO_HELP_COUNT] = { -#define JITHELPER(code, pfnHelper, binderId) HELPERDEF(code, pfnHelper, binderId) -#define DYNAMICJITHELPER(code, pfnHelper, binderId) HELPERDEF(code, 1 + DYNAMIC_##code, binderId) +#define JITHELPER(code, pfnHelper, binderId) HELPERDEF(code, pfnHelper, false) +#define DYNAMICJITHELPER(code, pfnHelper, binderId) HELPERDEF(code, 1 + DYNAMIC_##code, true) #include "jithelpers.h" }; +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +// Collection of entry points for JIT helpers +PCODE hlpFuncEntryPoints[CORINFO_HELP_COUNT] = {}; +#endif // FEATURE_PORTABLE_ENTRYPOINTS + // dynamic helpers - filled in at runtime - See definition of DynamicCorInfoHelpFunc. VMHELPDEF hlpDynamicFuncTable[DYNAMIC_CORINFO_HELP_COUNT] = { #define JITHELPER(code, pfnHelper, binderId) -#define DYNAMICJITHELPER(code, pfnHelper, binderId) HELPERDEF(DYNAMIC_ ## code, pfnHelper, binderId) +#define DYNAMICJITHELPER(code, pfnHelper, binderId) HELPERDEF(DYNAMIC_##code, pfnHelper, true) #include "jithelpers.h" }; @@ -2544,6 +2551,34 @@ static const BinderMethodID hlpDynamicToBinderMap[DYNAMIC_CORINFO_HELP_COUNT] = #include "jithelpers.h" }; +bool VMHELPDEF::IsDynamicHelper(DynamicCorInfoHelpFunc* dynamicFtnNum) const +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(dynamicFtnNum != nullptr); + + size_t dynamicFtnNumMaybe = (size_t)pfnHelper - 1; + + bool isDynamic; +#ifdef TARGET_WASM + // Functions on Wasm are ordinal values, not memory addresses. + // On Wasm, we need some metadata to indicate whether the helper is a dynamic helper. + isDynamic = _isDynamicHelper; +#else // !TARGET_WASM + // If pfnHelper is an index into the dynamic helper table, it should be less + // than DYNAMIC_CORINFO_HELP_COUNT. + isDynamic = (dynamicFtnNumMaybe < DYNAMIC_CORINFO_HELP_COUNT); +#endif // TARGET_WASM + +#if defined(_DEBUG) || defined(TARGET_WASM) + _ASSERTE(isDynamic == _isDynamicHelper); +#endif // _DEBUG || TARGET_WASM + + if (isDynamic) + *dynamicFtnNum = (DynamicCorInfoHelpFunc)dynamicFtnNumMaybe; + + return isDynamic; +} + // Set the JIT helper function in the helper table // Handles the case where the function does not reside in mscorwks.dll @@ -2561,18 +2596,18 @@ void _SetJitHelperFunction(DynamicCorInfoHelpFunc ftnNum, void * pFunc) LOG((LF_JIT, LL_INFO1000000, "Setting JIT dynamic helper %3d (%s) to %p\n", ftnNum, hlpDynamicFuncTable[ftnNum].name, pFunc)); - hlpDynamicFuncTable[ftnNum].pfnHelper = (void*)pFunc; + hlpDynamicFuncTable[ftnNum].pfnHelper = (PCODE)pFunc; } -VMHELPDEF LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum, MethodDesc** methodDesc) +PCODE LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum, MethodDesc** methodDesc) { STANDARD_VM_CONTRACT; _ASSERTE(ftnNum < DYNAMIC_CORINFO_HELP_COUNT); MethodDesc* pMD = NULL; - void* helper = VolatileLoad(&hlpDynamicFuncTable[ftnNum].pfnHelper); - if (helper == NULL) + PCODE helper = VolatileLoad(&hlpDynamicFuncTable[ftnNum].pfnHelper); + if (helper == (PCODE)NULL) { BinderMethodID binderId = hlpDynamicToBinderMap[ftnNum]; @@ -2584,7 +2619,7 @@ VMHELPDEF LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum, MethodDesc** metho pMD = CoreLibBinder::GetMethod(binderId); PCODE pFunc = pMD->GetMultiCallableAddrOfCode(); - InterlockedCompareExchangeT(&hlpDynamicFuncTable[ftnNum].pfnHelper, (void*)pFunc, nullptr); + InterlockedCompareExchangeT(&hlpDynamicFuncTable[ftnNum].pfnHelper, (PCODE)pFunc, (PCODE)NULL); } // If the caller wants the MethodDesc, we may need to try and load it. @@ -2600,7 +2635,7 @@ VMHELPDEF LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum, MethodDesc** metho *methodDesc = pMD; } - return hlpDynamicFuncTable[ftnNum]; + return hlpDynamicFuncTable[ftnNum].pfnHelper; } bool HasILBasedDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index b2d2b11543c2f9..1c782837c80948 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -59,10 +59,6 @@ #include "pgo.h" #endif -#ifdef FEATURE_INTERPRETER -#include "callstubgenerator.h" -#endif - #include "tailcallhelp.h" #include "patchpointinfo.h" @@ -10755,25 +10751,59 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN CORINFO_CONST_LOOKUP* pNativeEntrypoint, /* OUT */ CORINFO_METHOD_HANDLE* pMethod) /* OUT */ { - CONTRACTL { + CONTRACTL + { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; - } CONTRACTL_END; + } + CONTRACTL_END; JIT_TO_EE_TRANSITION(); - if (pMethod != NULL) + _ASSERTE(ftnNum < CORINFO_HELP_COUNT); + + InfoAccessType accessType; + LPVOID targetAddr; + + MethodDesc* helperMD = NULL; + VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; + PCODE pfnHelper = helperDef.pfnHelper; + + DynamicCorInfoHelpFunc dynamicFtnNum; + +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + + accessType = IAT_VALUE; + targetAddr = (LPVOID)VolatileLoad(&hlpFuncEntryPoints[ftnNum]); + if (targetAddr == NULL) { - *pMethod = NULL; - } + if (helperDef.IsDynamicHelper(&dynamicFtnNum)) + { + pfnHelper = LoadDynamicJitHelper(dynamicFtnNum, &helperMD); + } - _ASSERTE(ftnNum < CORINFO_HELP_COUNT); + // LoadDynamicJitHelper returns PortableEntryPoint for helpers backed by managed methods. We need to wrap + // the code address by PortableEntryPoint in all other cases. + if (helperMD == NULL) + { + AllocMemHolder portableEntryPoint{ SystemDomain::GetGlobalLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T{ sizeof(PortableEntryPoint) }) }; + portableEntryPoint->Init((void*)pfnHelper); + if (InterlockedCompareExchangeT(&hlpFuncEntryPoints[ftnNum], (PCODE)(PortableEntryPoint*)portableEntryPoint, (PCODE)NULL) == (PCODE)NULL) + portableEntryPoint.SuppressRelease(); + pfnHelper = hlpFuncEntryPoints[ftnNum]; + } + else + { + VolatileStore(&hlpFuncEntryPoints[ftnNum], pfnHelper); + } - void* pfnHelper = hlpFuncTable[ftnNum].pfnHelper; + targetAddr = (LPVOID)pfnHelper; + } + +#else // !FEATURE_PORTABLE_ENTRYPOINTS - size_t dynamicFtnNum = ((size_t)pfnHelper - 1); - if (dynamicFtnNum < DYNAMIC_CORINFO_HELP_COUNT) + if (helperDef.IsDynamicHelper(&dynamicFtnNum)) { #if defined(TARGET_AMD64) // To avoid using a jump stub we always call certain helpers using an indirect call. @@ -10791,46 +10821,34 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN dynamicFtnNum == DYNAMIC_CORINFO_HELP_PROF_FCN_TAILCALL || dynamicFtnNum == DYNAMIC_CORINFO_HELP_DISPATCH_INDIRECT_CALL) { - if (pNativeEntrypoint != NULL) - { - pNativeEntrypoint->accessType = IAT_PVALUE; - _ASSERTE(hlpDynamicFuncTable[dynamicFtnNum].pfnHelper != NULL); // Confirm the helper is non-null and doesn't require lazy loading. - pNativeEntrypoint->addr = &hlpDynamicFuncTable[dynamicFtnNum].pfnHelper; - _ASSERTE(IndirectionAllowedForJitHelper(ftnNum)); - } + accessType = IAT_PVALUE; + _ASSERTE(hlpDynamicFuncTable[dynamicFtnNum].pfnHelper != (PCODE)NULL); // Confirm the helper is non-null and doesn't require lazy loading. + targetAddr = &hlpDynamicFuncTable[dynamicFtnNum].pfnHelper; + _ASSERTE(IndirectionAllowedForJitHelper(ftnNum)); goto exit; } -#endif +#endif // TARGET_AMD64 // Check if we already have a cached address of the final target static LPVOID hlpFinalTierAddrTable[DYNAMIC_CORINFO_HELP_COUNT] = {}; LPVOID finalTierAddr = hlpFinalTierAddrTable[dynamicFtnNum]; if (finalTierAddr != NULL) { - if (pNativeEntrypoint != NULL) - { - pNativeEntrypoint->accessType = IAT_VALUE; - pNativeEntrypoint->addr = finalTierAddr; - } - if (pMethod != nullptr && HasILBasedDynamicJitHelper((DynamicCorInfoHelpFunc)dynamicFtnNum)) + accessType = IAT_VALUE; + targetAddr = finalTierAddr; + if (pMethod != nullptr && HasILBasedDynamicJitHelper(dynamicFtnNum)) { - (void)LoadDynamicJitHelper((DynamicCorInfoHelpFunc)dynamicFtnNum, (MethodDesc**)pMethod); - _ASSERT(*pMethod != NULL); + (void)LoadDynamicJitHelper(dynamicFtnNum, &helperMD); + _ASSERT(helperMD != NULL); } goto exit; } - if (HasILBasedDynamicJitHelper((DynamicCorInfoHelpFunc)dynamicFtnNum)) + if (HasILBasedDynamicJitHelper(dynamicFtnNum)) { - MethodDesc* helperMD = NULL; - (void)LoadDynamicJitHelper((DynamicCorInfoHelpFunc)dynamicFtnNum, &helperMD); + (void)LoadDynamicJitHelper(dynamicFtnNum, &helperMD); _ASSERT(helperMD != NULL); - if (pMethod != NULL) - { - *pMethod = (CORINFO_METHOD_HANDLE)helperMD; - } - // Check if the target MethodDesc is already jitted to its final Tier // so we no longer need to use indirections and can emit a direct call instead. // @@ -10859,11 +10877,8 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN { // Cache it for future uses to avoid taking the lock again. hlpFinalTierAddrTable[dynamicFtnNum] = finalTierAddr; - if (pNativeEntrypoint != NULL) - { - pNativeEntrypoint->accessType = IAT_VALUE; - pNativeEntrypoint->addr = finalTierAddr; - } + accessType = IAT_VALUE; + targetAddr = finalTierAddr; goto exit; } } @@ -10873,27 +10888,31 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN { Precode* pPrecode = helperMD->GetPrecode(); _ASSERTE(pPrecode->GetType() == PRECODE_FIXUP); - if (pNativeEntrypoint != NULL) - { - pNativeEntrypoint->accessType = IAT_PVALUE; - pNativeEntrypoint->addr = ((FixupPrecode*)pPrecode)->GetTargetSlot(); - } + accessType = IAT_PVALUE; + targetAddr = ((FixupPrecode*)pPrecode)->GetTargetSlot(); goto exit; } } - pfnHelper = LoadDynamicJitHelper((DynamicCorInfoHelpFunc)dynamicFtnNum).pfnHelper; + pfnHelper = LoadDynamicJitHelper(dynamicFtnNum); } - _ASSERTE(pfnHelper != NULL); + _ASSERTE(pfnHelper != (PCODE)NULL); + accessType = IAT_VALUE; + targetAddr = (LPVOID)pfnHelper; + +exit: ; +#endif // FEATURE_PORTABLE_ENTRYPOINTS if (pNativeEntrypoint != NULL) { - pNativeEntrypoint->accessType = IAT_VALUE; - pNativeEntrypoint->addr = (LPVOID)GetEEFuncEntryPoint(pfnHelper); + pNativeEntrypoint->accessType = accessType; + pNativeEntrypoint->addr = targetAddr; } -exit: ; + if (pMethod != NULL) + *pMethod = (CORINFO_METHOD_HANDLE)helperMD; + EE_TO_JIT_TRANSITION(); } @@ -10905,20 +10924,20 @@ PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum) MODE_PREEMPTIVE; } CONTRACTL_END; - void* pfnHelper = hlpFuncTable[ftnNum].pfnHelper; + VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; + PCODE pfnHelper = helperDef.pfnHelper; - // If pfnHelper is an index into the dynamic helper table, it should be less - // than DYNAMIC_CORINFO_HELP_COUNT. In this case we need to find the actual pfnHelper - // using an extra indirection. Note the special case - // where pfnHelper==0 where pfnHelper-1 will underflow and we will avoid the indirection. - if (((size_t)pfnHelper - 1) < DYNAMIC_CORINFO_HELP_COUNT) + // In this case we need to find the actual pfnHelper + // using an extra indirection. + DynamicCorInfoHelpFunc dynamicFtnNum; + if (helperDef.IsDynamicHelper(&dynamicFtnNum)) { - pfnHelper = LoadDynamicJitHelper((DynamicCorInfoHelpFunc)((size_t)pfnHelper - 1)).pfnHelper; + pfnHelper = LoadDynamicJitHelper(dynamicFtnNum); } - _ASSERTE(pfnHelper != NULL); + _ASSERTE(pfnHelper != (PCODE)NULL); - return GetEEFuncEntryPoint(pfnHelper); + return pfnHelper; } // Wrapper around CEEInfo::GetProfilingHandle. The first time this is called for a @@ -11196,27 +11215,23 @@ LPVOID CEEInfo::GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) #ifdef FEATURE_INTERPRETER +// Forward declare the function for mapping MetaSig to a cookie. +LPVOID GetCookieForCalliSig(MetaSig metaSig); LPVOID CInterpreterJitInfo::GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) { void* result = NULL; -#ifndef TARGET_WASM JIT_TO_EE_TRANSITION(); - Module* module = GetModule(szMetaSig->scope); - Instantiation classInst = Instantiation((TypeHandle*) szMetaSig->sigInst.classInst, szMetaSig->sigInst.classInstCount); Instantiation methodInst = Instantiation((TypeHandle*) szMetaSig->sigInst.methInst, szMetaSig->sigInst.methInstCount); SigTypeContext typeContext = SigTypeContext(classInst, methodInst); + Module* mod = GetModule(szMetaSig->scope); - MetaSig sig(szMetaSig->pSig, szMetaSig->cbSig, module, &typeContext); - CallStubGenerator callStubGenerator; - result = callStubGenerator.GenerateCallStubForSig(sig); + MetaSig sig(szMetaSig->pSig, szMetaSig->cbSig, mod, &typeContext); + result = GetCookieForCalliSig(sig); EE_TO_JIT_TRANSITION(); -#else - PORTABILITY_ASSERT("GetCookieForInterpreterCalliSig is not supported on wasm yet"); -#endif // !TARGET_WASM return result; } @@ -13356,7 +13371,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, #ifdef FEATURE_PORTABLE_ENTRYPOINTS PCODE portableEntryPoint = ftn->GetPortableEntryPoint(); _ASSERTE(portableEntryPoint != NULL); - PortableEntryPoint::SetInterpreterData(PCODEToPINSTR(portableEntryPoint), ret); + PortableEntryPoint::SetInterpreterData(portableEntryPoint, ret); ret = portableEntryPoint; #else // !FEATURE_PORTABLE_ENTRYPOINTS diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 8cc6c6773ed43b..ed9203c364205c 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -973,12 +973,28 @@ class CInterpreterJitInfo final : public CEECodeGenInfo /*********************************************************************/ /*********************************************************************/ -typedef struct { - void * pfnHelper; +// enum for dynamically assigned helper calls +enum DynamicCorInfoHelpFunc { +#define JITHELPER(code, pfnHelper, binderId) +#define DYNAMICJITHELPER(code, pfnHelper, binderId) DYNAMIC_##code, +#include "jithelpers.h" + DYNAMIC_CORINFO_HELP_COUNT +}; + +struct VMHELPDEF +{ + PCODE pfnHelper; + #ifdef _DEBUG const char* name; -#endif -} VMHELPDEF; +#endif // _DEBUG + +#if defined(_DEBUG) || defined(TARGET_WASM) + bool _isDynamicHelper; +#endif // _DEBUG || TARGET_WASM + + bool IsDynamicHelper(DynamicCorInfoHelpFunc* dynamicFtnNum) const; +}; #if defined(DACCESS_COMPILE) @@ -988,15 +1004,11 @@ GARY_DECL(VMHELPDEF, hlpFuncTable, CORINFO_HELP_COUNT); extern "C" const VMHELPDEF hlpFuncTable[CORINFO_HELP_COUNT]; -#endif +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +extern "C" PCODE hlpFuncEntryPoints[CORINFO_HELP_COUNT]; +#endif // FEATURE_PORTABLE_ENTRYPOINTS -// enum for dynamically assigned helper calls -enum DynamicCorInfoHelpFunc { -#define JITHELPER(code, pfnHelper, binderId) -#define DYNAMICJITHELPER(code, pfnHelper, binderId) DYNAMIC_##code, -#include "jithelpers.h" - DYNAMIC_CORINFO_HELP_COUNT -}; +#endif #ifdef _MSC_VER // GCC complains about duplicate "extern". And it is not needed for the GCC build @@ -1007,7 +1019,7 @@ GARY_DECL(VMHELPDEF, hlpDynamicFuncTable, DYNAMIC_CORINFO_HELP_COUNT); #define SetJitHelperFunction(ftnNum, pFunc) _SetJitHelperFunction(DYNAMIC_##ftnNum, (void*)(pFunc)) void _SetJitHelperFunction(DynamicCorInfoHelpFunc ftnNum, void * pFunc); -VMHELPDEF LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum, MethodDesc** methodDesc = NULL); +PCODE LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum, MethodDesc** methodDesc = NULL); bool HasILBasedDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum); bool IndirectionAllowedForJitHelper(CorInfoHelpFunc ftnNum); diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 32bd8222e8577f..f07746a01e084b 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2216,7 +2216,7 @@ MethodDesc* NonVirtualEntry2MethodDesc(PCODE entryPoint) CONTRACTL_END #ifdef FEATURE_PORTABLE_ENTRYPOINTS - return PortableEntryPoint::GetMethodDesc(PCODEToPINSTR(entryPoint)); + return PortableEntryPoint::GetMethodDesc(entryPoint); #else // FEATURE_PORTABLE_ENTRYPOINTS RangeSection* pRS = ExecutionManager::FindCodeRange(entryPoint, ExecutionManager::GetScanFlags()); @@ -2648,7 +2648,7 @@ MethodDesc* MethodDesc::GetMethodDescFromPrecode(PCODE addr, BOOL fSpeculative / MethodDesc* pMD = NULL; #ifdef FEATURE_PORTABLE_ENTRYPOINTS - pMD = PortableEntryPoint::GetMethodDesc(PCODEToPINSTR(addr)); + pMD = PortableEntryPoint::GetMethodDesc(addr); #else // !FEATURE_PORTABLE_ENTRYPOINTS PTR_Precode pPrecode = Precode::GetPrecodeFromEntryPoint(addr, fSpeculative); @@ -2897,7 +2897,7 @@ void MethodDesc::MarkPrecodeAsStableEntrypoint() PCODE tempEntry = GetTemporaryEntryPointIfExists(); _ASSERTE(tempEntry != (PCODE)NULL); #ifdef FEATURE_PORTABLE_ENTRYPOINTS - _ASSERTE(PortableEntryPoint::GetMethodDesc(PCODEToPINSTR(tempEntry)) == this); + _ASSERTE(PortableEntryPoint::GetMethodDesc(tempEntry) == this); #else // !FEATURE_PORTABLE_ENTRYPOINTS PrecodeType requiredType = GetPrecodeType(); PrecodeType availableType = Precode::GetPrecodeFromEntryPoint(tempEntry)->GetType(); diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index fd695c46741bc1..780125df3d257f 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -2541,14 +2541,9 @@ void MarshalInfo::SetupArgumentSizes() } CONTRACTL_END; - const unsigned targetPointerSize = TARGET_POINTER_SIZE; - const bool pointerIsValueType = false; - const bool pointerIsFloatHfa = false; - _ASSERTE(targetPointerSize == StackElemSize(TARGET_POINTER_SIZE, pointerIsValueType, pointerIsFloatHfa)); - if (m_byref) { - m_nativeArgSize = targetPointerSize; + m_nativeArgSize = TARGET_POINTER_SIZE; } else { @@ -2562,7 +2557,7 @@ void MarshalInfo::SetupArgumentSizes() #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE if (m_nativeArgSize > ENREGISTERED_PARAMTYPE_MAXSIZE) { - m_nativeArgSize = targetPointerSize; + m_nativeArgSize = TARGET_POINTER_SIZE; } #endif // ENREGISTERED_PARAMTYPE_MAXSIZE } diff --git a/src/coreclr/vm/precode_portable.cpp b/src/coreclr/vm/precode_portable.cpp index 317add07b551a5..e9687978ea9769 100644 --- a/src/coreclr/vm/precode_portable.cpp +++ b/src/coreclr/vm/precode_portable.cpp @@ -8,73 +8,93 @@ #include "precode_portable.hpp" #ifdef HOST_64BIT - #define CANARY_VALUE 0x1234567812345678 +#define CANARY_VALUE 0x1234567812345678 #else // HOST_64BIT - #define CANARY_VALUE 0x12345678 +#define CANARY_VALUE 0x12345678 #endif // HOST_64BIT -bool PortableEntryPoint::IsNativeEntryPoint(TADDR addr) +bool PortableEntryPoint::HasNativeEntryPoint(PCODE addr) { - STANDARD_VM_CONTRACT; - - return false; + LIMITED_METHOD_CONTRACT; + PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); + return portableEntryPoint->HasNativeCode(); } -void* PortableEntryPoint::GetActualCode(TADDR addr) +void* PortableEntryPoint::GetActualCode(PCODE addr) { STANDARD_VM_CONTRACT; PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); - _ASSERTE(portableEntryPoint->_pActualCode != NULL); + _ASSERTE(portableEntryPoint->HasNativeCode()); return portableEntryPoint->_pActualCode; } -MethodDesc* PortableEntryPoint::GetMethodDesc(TADDR addr) +MethodDesc* PortableEntryPoint::GetMethodDesc(PCODE addr) { STANDARD_VM_CONTRACT; PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); - _ASSERTE(portableEntryPoint->_pMD != NULL); + _ASSERTE(portableEntryPoint->_pMD != nullptr); return portableEntryPoint->_pMD; } -void* PortableEntryPoint::GetInterpreterData(TADDR addr) +void* PortableEntryPoint::GetInterpreterData(PCODE addr) { STANDARD_VM_CONTRACT; PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); - _ASSERTE(portableEntryPoint->_pInterpreterData != NULL); + _ASSERTE(portableEntryPoint->HasInterpreterCode()); return portableEntryPoint->_pInterpreterData; } -void PortableEntryPoint::SetInterpreterData(TADDR addr, PCODE interpreterData) +void PortableEntryPoint::SetInterpreterData(PCODE addr, PCODE interpreterData) { STANDARD_VM_CONTRACT; PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); - _ASSERTE(portableEntryPoint->_pInterpreterData == NULL); + _ASSERTE(!portableEntryPoint->HasInterpreterCode()); + _ASSERTE(interpreterData != (PCODE)NULL); portableEntryPoint->_pInterpreterData = (void*)PCODEToPINSTR(interpreterData); } -PortableEntryPoint* PortableEntryPoint::ToPortableEntryPoint(TADDR addr) +PortableEntryPoint* PortableEntryPoint::ToPortableEntryPoint(PCODE addr) { LIMITED_METHOD_CONTRACT; _ASSERTE(addr != NULL); - PortableEntryPoint* portableEntryPoint = (PortableEntryPoint*)addr; - _ASSERTE(portableEntryPoint->_canary == CANARY_VALUE); + PortableEntryPoint* portableEntryPoint = (PortableEntryPoint*)PCODEToPINSTR(addr); + _ASSERTE(portableEntryPoint->IsValid()); return portableEntryPoint; } +#ifdef _DEBUG +bool PortableEntryPoint::IsValid() const +{ + LIMITED_METHOD_CONTRACT; + return _canary == CANARY_VALUE; +} +#endif // _DEBUG + void PortableEntryPoint::Init(MethodDesc* pMD) { LIMITED_METHOD_CONTRACT; + _ASSERTE(pMD != NULL); _pActualCode = NULL; _pMD = pMD; _pInterpreterData = NULL; INDEBUG(_canary = CANARY_VALUE); } +void PortableEntryPoint::Init(void* nativeEntryPoint) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(nativeEntryPoint != NULL); + _pActualCode = nativeEntryPoint; + _pMD = NULL; + _pInterpreterData = NULL; + INDEBUG(_canary = CANARY_VALUE); +} + InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig; void StubPrecode::Init(StubPrecode* pPrecodeRX, TADDR secretParam, LoaderAllocator *pLoaderAllocator, TADDR type, TADDR target) diff --git a/src/coreclr/vm/precode_portable.hpp b/src/coreclr/vm/precode_portable.hpp index 07aec0eff4f5c6..eec9c17b59283c 100644 --- a/src/coreclr/vm/precode_portable.hpp +++ b/src/coreclr/vm/precode_portable.hpp @@ -12,25 +12,55 @@ class PortableEntryPoint final { public: // static - static bool IsNativeEntryPoint(TADDR addr); - static void* GetActualCode(TADDR addr); - static MethodDesc* GetMethodDesc(TADDR addr); - static void* GetInterpreterData(TADDR addr); - static void SetInterpreterData(TADDR addr, PCODE interpreterData); + static bool HasNativeEntryPoint(PCODE addr); + + static void* GetActualCode(PCODE addr); + static MethodDesc* GetMethodDesc(PCODE addr); + static void* GetInterpreterData(PCODE addr); + static void SetInterpreterData(PCODE addr, PCODE interpreterData); private: // static - static PortableEntryPoint* ToPortableEntryPoint(TADDR addr); + static PortableEntryPoint* ToPortableEntryPoint(PCODE addr); private: - void* _pActualCode; + Volatile _pActualCode; MethodDesc* _pMD; void* _pInterpreterData; // We keep the canary value last to ensure a stable ABI across build flavors INDEBUG(size_t _canary); +#ifdef _DEBUG + bool IsValid() const; +#endif // _DEBUG + public: void Init(MethodDesc* pMD); + void Init(void* nativeEntryPoint); + + // Query methods for entry point state. + bool HasInterpreterCode() const + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(IsValid()); + return _pInterpreterData != nullptr; + } + + bool HasNativeCode() const + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(IsValid()); + return _pActualCode != nullptr; + } + + bool IsPreparedForNativeCall() const + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(IsValid()); + // State when interpreted method was prepared to be called from R2R compiled code. + // pActualCode is a managed calling convention -> interpreter executor call stub in this case. + return _pInterpreterData != nullptr && _pActualCode != nullptr; + } }; extern InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 0ef2ed8ff2fb29..a7edce009c1f09 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -996,8 +996,7 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, COR_ILMETHOD_ { InterpByteCodeStart* interpreterCode; #ifdef FEATURE_PORTABLE_ENTRYPOINTS - interpreterCode = (InterpByteCodeStart*)PortableEntryPoint::GetInterpreterData(PCODEToPINSTR(pCode)); - + interpreterCode = (InterpByteCodeStart*)PortableEntryPoint::GetInterpreterData(pCode); #else // !FEATURE_PORTABLE_ENTRYPOINTS InterpreterPrecode* pPrecode = InterpreterPrecode::FromEntryPoint(pCode); interpreterCode = dac_cast(pPrecode->GetData()->ByteCodeAddr); @@ -2297,7 +2296,13 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT, CallerGCMode callerGCMo pCode = GetStubForInteropMethod(this); } - GetOrCreatePrecode(); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // Store the IL Stub interpreter data on the actual + // P/Invoke MethodDesc. + void* ilStubInterpData = PortableEntryPoint::GetInterpreterData(pCode); + _ASSERTE(ilStubInterpData != NULL); + SetInterpreterCode((InterpByteCodeStart*)ilStubInterpData); +#endif // FEATURE_PORTABLE_ENTRYPOINTS } else if (IsFCall()) { diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 92344beb931331..61c339dbb8272e 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -1062,10 +1062,12 @@ void InitThreadManager() } CONTRACTL_END; +#ifndef TARGET_WASM // All patched helpers should fit into one page. // If you hit this assert on retail build, there is most likely problem with BBT script. _ASSERTE_ALL_BUILDS((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart > (ptrdiff_t)0); _ASSERTE_ALL_BUILDS((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart < (ptrdiff_t)GetOsPageSize()); +#endif // !TARGET_WASM if (IsWriteBarrierCopyEnabled()) { diff --git a/src/coreclr/vm/wasm/calldescrworkerwasm.cpp b/src/coreclr/vm/wasm/calldescrworkerwasm.cpp index afbf3e07ff7913..c8e7e101c36080 100644 --- a/src/coreclr/vm/wasm/calldescrworkerwasm.cpp +++ b/src/coreclr/vm/wasm/calldescrworkerwasm.cpp @@ -6,16 +6,17 @@ extern "C" void* STDCALL ExecuteInterpretedMethodWithArgs(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, int8_t* pArgs, size_t size, void* retBuff); -extern "C" void STDCALL CallDescrWorkerInternal(CallDescrData * pCallDescrData) +extern "C" void STDCALL CallDescrWorkerInternal(CallDescrData* pCallDescrData) { - MethodDesc* pMethod = pCallDescrData->pMD; + MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pCallDescrData->pTarget); InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); if (targetIp == NULL) { GCX_PREEMP(); - pMethod->PrepareInitialCode(CallerGCMode::Coop); + (void)pMethod->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); targetIp = pMethod->GetInterpreterCode(); } - ExecuteInterpretedMethodWithArgs(pCallDescrData->pTransitionBlock, (TADDR)targetIp, (int8_t*)pCallDescrData->pSrc, pCallDescrData->nArgsSize, pCallDescrData->returnValue); + TransitionBlock dummy{}; + ExecuteInterpretedMethodWithArgs(&dummy, (TADDR)targetIp, (int8_t*)pCallDescrData->pSrc, pCallDescrData->nArgsSize, pCallDescrData->returnValue); } diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index ac2298f34837a1..1122842a0b97fe 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // +#include "../../interpreter/interpretershared.h" + extern "C" void STDCALL CallCountingStubCode() { PORTABILITY_ASSERT("CallCountingStubCode is not implemented on wasm"); @@ -470,6 +472,7 @@ void _DacGlobals::Initialize() /* no-op on wasm */ } +// Incorrectly typed temporary symbol to satisfy the linker. int g_pDebugger; extern "C" int32_t mono_wasm_browser_entropy(uint8_t* buffer, int32_t bufferLength) @@ -478,17 +481,192 @@ extern "C" int32_t mono_wasm_browser_entropy(uint8_t* buffer, int32_t bufferLeng return -1; } -void InvokeCalliStub(PCODE ftn, CallStubHeader *stubHeaderTemplate, int8_t *pArgs, int8_t *pRet) +void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target) { - PORTABILITY_ASSERT("InvokeCalliStub is not implemented on wasm"); + PORTABILITY_ASSERT("Attempted to execute non-interpreter code from interpreter on wasm, this is not yet implemented"); } -void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target) +void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int32_t callArgsOffset, int32_t returnOffset, PCODE callTarget) { - PORTABILITY_ASSERT("Attempted to execute non-interpreter code from interpreter on wasm, this is not yet implemented"); + PORTABILITY_ASSERT("Attempted to execute unmanaged code from interpreter on wasm, this is not yet implemented"); +} + +void InvokeCalliStub(PCODE ftn, void* cookie, int8_t *pArgs, int8_t *pRet) +{ + _ASSERTE(ftn != (PCODE)NULL); + _ASSERTE(cookie != NULL); + + ((void(*)(PCODE, int8_t*, int8_t*))cookie)(ftn, pArgs, pRet); } void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target) { PORTABILITY_ASSERT("Attempted to execute non-interpreter code from interpreter on wasm, this is not yet implemented"); -} \ No newline at end of file +} + +namespace +{ + // Arguments are passed on the stack with each argument aligned to INTERP_STACK_SLOT_SIZE. +#define ARG(i) *((int32_t*)(pArgs + (i * INTERP_STACK_SLOT_SIZE))) + + void CallFunc_Void_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(void) = (void (*)(void))pcode; + (*fptr)(); + } + + void CallFunc_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t) = (void (*)(int32_t))pcode; + (*fptr)(ARG(0)); + } + + void CallFunc_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; + (*fptr)(ARG(0), ARG(1)); + } + + void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG(0), ARG(1), ARG(2)); + } + + void CallFunc_Void_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(void) = (int32_t (*)(void))pcode; + *(int32_t*)pRet = (*fptr)(); + } + + void CallFunc_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(int32_t) = (int32_t (*)(int32_t))pcode; + *(int32_t*)pRet = (*fptr)(ARG(0)); + } + + void CallFunc_I32_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode; + *(int32_t*)pRet = (*fptr)(ARG(0), ARG(1)); + } + + void CallFunc_I32_I32_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; + *(int32_t*)pRet = (*fptr)(ARG(0), ARG(1), ARG(2)); + } + +#undef ARG + + void* const RetVoidThunks[] = + { + (void*)&CallFunc_Void_RetVoid, + (void*)&CallFunc_I32_RetVoid, + (void*)&CallFunc_I32_I32_RetVoid, + (void*)&CallFunc_I32_I32_I32_RetVoid, + }; + + void* const RetI32Thunks[] = + { + (void*)&CallFunc_Void_RetI32, + (void*)&CallFunc_I32_RetI32, + (void*)&CallFunc_I32_I32_RetI32, + (void*)&CallFunc_I32_I32_I32_RetI32, + }; + + bool ConvertibleToI32(CorElementType argType) + { + // See https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md + switch (argType) + { + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_TYPEDBYREF: + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_FNPTR: + case ELEMENT_TYPE_SZARRAY: + return true; + default: + return false; + } + } + + // This is a simple signature computation routine for signatures currently supported in the wasm environment. + // Note: Currently only validates void return type and i32 wasm convertible arguments. + void* ComputeCalliSigThunk(MetaSig& sig) + { + STANDARD_VM_CONTRACT; + _ASSERTE(sizeof(int32_t) == sizeof(void*)); + + // Ensure an unmanaged calling convention. + BYTE callConv = sig.GetCallingConvention(); + switch (callConv) + { + case IMAGE_CEE_CS_CALLCONV_C: + case IMAGE_CEE_CS_CALLCONV_STDCALL: + case IMAGE_CEE_CS_CALLCONV_FASTCALL: + case IMAGE_CEE_CS_CALLCONV_UNMANAGED: + break; + default: + return NULL; + } + + // Check return value + bool returnsVoid = sig.IsReturnTypeVoid(); + if (!returnsVoid && !ConvertibleToI32(sig.GetReturnType())) + return NULL; + + // Ensure all arguments are wasm i32 compatible types. + for (CorElementType argType = sig.NextArg(); + argType != ELEMENT_TYPE_END; + argType = sig.NextArg()) + { + if (!ConvertibleToI32(argType)) + return NULL; + } + + UINT numArgs = sig.NumFixedArgs(); + void* const * thunks; + if (returnsVoid) + { + thunks = RetVoidThunks; + if (numArgs >= ARRAY_SIZE(RetVoidThunks)) + return NULL; + } + else + { + thunks = RetI32Thunks; + if (numArgs >= ARRAY_SIZE(RetI32Thunks)) + return NULL; + } + + return thunks[numArgs]; + } +} + +LPVOID GetCookieForCalliSig(MetaSig metaSig) +{ + STANDARD_VM_CONTRACT; + + void* thunk = ComputeCalliSigThunk(metaSig); + if (thunk == NULL) + { + PORTABILITY_ASSERT("GetCookieForCalliSig: unknown thunk signature"); + } + + return thunk; +}