diff --git a/src/coreclr/hosts/corerun/CMakeLists.txt b/src/coreclr/hosts/corerun/CMakeLists.txt index 85f7fef9575c7c..a7f6ce370c5e06 100644 --- a/src/coreclr/hosts/corerun/CMakeLists.txt +++ b/src/coreclr/hosts/corerun/CMakeLists.txt @@ -74,13 +74,17 @@ else() -sMAXIMUM_MEMORY=2147483648 -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=5MB - -sWASM_BIGINT=1 -sEXPORTED_RUNTIME_METHODS=cwrap,ccall,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAP64,HEAPU64,HEAPF32,HEAPF64,safeSetTimeout,maybeExit,exitJS,abort,lengthBytesUTF8,UTF8ToString,stringToUTF8Array -sEXPORTED_FUNCTIONS=_main,_GetDotNetRuntimeContractDescriptor -sENVIRONMENT=node,shell,web -Wl,-error-limit=0) if (CORERUN_IN_BROWSER) + # Node.js doesn't have good support for WASM_BIGINT + # so it only is added when running in the browser. + target_link_options(corerun PRIVATE + -sWASM_BIGINT=1) + # Include the virtual file system data for the # browser scenario. set(WASM_PRELOAD_DIR "${CMAKE_INSTALL_PREFIX}/IL") diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index bdedfa685b37e8..1761492fe52cd3 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -807,6 +807,7 @@ enum CORINFO_ACCESS_FLAGS CORINFO_ACCESS_NONNULL = 0x0004, // Instance is guaranteed non-null CORINFO_ACCESS_LDFTN = 0x0010, // Accessed via ldftn + CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE = 0x0020, // Method might be attributed with UnmanagedCallersOnlyAttribute. // Field access flags CORINFO_ACCESS_GET = 0x0100, // Field get (ldfld) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index ff37fc2f9bc60b..44725c3b1839ef 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -547,6 +547,7 @@ public enum CORINFO_ACCESS_FLAGS CORINFO_ACCESS_NONNULL = 0x0004, // Instance is guaranteed non-null CORINFO_ACCESS_LDFTN = 0x0010, // Accessed via ldftn + CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE = 0x0020, // Method might be attributed with UnmanagedCallersOnlyAttribute. // Field access flags CORINFO_ACCESS_GET = 0x0100, // Field get (ldfld) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index da3ba5c4c4f94d..75dd154611d003 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1277,17 +1277,22 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, // We can get virtual delegates closed over null through this code path, so be careful to handle that case (no need to // virtualize since we're just going to throw NullRefException at invocation time). // - if (pTargetMethod->IsVirtual() && - *pRefFirstArg != NULL && - pTargetMethod->GetMethodTable() != (*pRefFirstArg)->GetMethodTable()) + if (pTargetMethod->IsVirtual() + && *pRefFirstArg != NULL + && pTargetMethod->GetMethodTable() != (*pRefFirstArg)->GetMethodTable()) + { pTargetCode = pTargetMethod->GetMultiCallableAddrOfVirtualizedCode(pRefFirstArg, pTargetMethod->GetMethodTable()); - else + } #ifdef HAS_THISPTR_RETBUF_PRECODE - if (pTargetMethod->IsStatic() && pTargetMethod->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + else if (pTargetMethod->IsStatic() && pTargetMethod->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + { pTargetCode = pTargetMethod->GetLoaderAllocator()->GetFuncPtrStubs()->GetFuncPtrStub(pTargetMethod, PRECODE_THISPTR_RETBUF); - else + } #endif // HAS_THISPTR_RETBUF_PRECODE + else + { pTargetCode = pTargetMethod->GetMultiCallableAddrOfCode(); + } _ASSERTE(pTargetCode); refRealDelegate->SetTarget(*pRefFirstArg); diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index 4f1c5afdd4bc1b..001a8968825c00 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -694,8 +694,9 @@ HRESULT CorHost2::CreateDelegate( EMPTY_STRING_TO_NULL(wszClassName); EMPTY_STRING_TO_NULL(wszMethodName); - if (fnPtr == 0) + if (fnPtr == NULL) return E_POINTER; + *fnPtr = 0; if(wszAssemblyName == NULL) @@ -714,10 +715,6 @@ HRESULT CorHost2::CreateDelegate( HRESULT hr = S_OK; BEGIN_EXTERNAL_ENTRYPOINT(&hr); -#ifdef FEATURE_PORTABLE_ENTRYPOINTS - hr = E_NOTIMPL; - -#else // !FEATURE_PORTABLE_ENTRYPOINTS GCX_COOP_THREAD_EXISTS(GET_THREAD()); MAKE_UTF8PTR_FROMWIDE(szClassName, wszClassName); @@ -754,15 +751,19 @@ HRESULT CorHost2::CreateDelegate( if (pMD->HasUnmanagedCallersOnlyAttribute()) { - *fnPtr = pMD->GetMultiCallableAddrOfCode(); + pMD->PrepareForUseAsAFunctionPointer(); + *fnPtr = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE); } else { +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + ThrowHR(COR_E_NOTSUPPORTED); +#else // !FEATURE_PORTABLE_ENTRYPOINTS UMEntryThunkData* pUMEntryThunk = pMD->GetLoaderAllocator()->GetUMEntryThunkCache()->GetUMEntryThunk(pMD); *fnPtr = (INT_PTR)pUMEntryThunk->GetCode(); +#endif // FEATURE_PORTABLE_ENTRYPOINTS } } -#endif // FEATURE_PORTABLE_ENTRYPOINTS END_EXTERNAL_ENTRYPOINT; diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index f35f4088b00b46..af8a66a0a992fc 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6165,6 +6165,26 @@ EXTERN_C void STDCALL GenericPInvokeCalliStubWorker(TransitionBlock * pTransitio pFrame->Pop(CURRENT_THREAD); } +EXTERN_C void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + ENTRY_POINT; + } + CONTRACTL_END; + + _ASSERTE(fullQualifiedTypeName != nullptr); + _ASSERTE(methodName != nullptr); + _ASSERTE(ppMD != nullptr); + + SString fullQualifiedTypeNameUtf8(SString::Utf8, fullQualifiedTypeName); + TypeHandle type = TypeName::GetTypeFromAsmQualifiedName(fullQualifiedTypeNameUtf8.GetUnicode(), /*bThrowIfNotFound*/ TRUE); + _ASSERTE(!type.IsTypeDesc()); + + *ppMD = MemberLoader::FindMethodByName(type.GetMethodTable(), methodName); +} + namespace { //------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 56fadf0867afbe..845c416241f669 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -293,7 +293,7 @@ void InvokeCalliStub(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet) pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } -LPVOID GetCookieForCalliSig(MetaSig metaSig) +void* GetCookieForCalliSig(MetaSig metaSig) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/interpexec.h b/src/coreclr/vm/interpexec.h index d03f3b236943f7..498c9c2d16bbbb 100644 --- a/src/coreclr/vm/interpexec.h +++ b/src/coreclr/vm/interpexec.h @@ -79,6 +79,9 @@ struct ExceptionClauseArgs void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext, ExceptionClauseArgs *pExceptionClauseArgs = NULL); +extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD); +extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret); + CallStubHeader *CreateNativeToInterpreterCallStub(InterpMethod* pInterpMethod); -#endif +#endif // _INTERPEXEC_H_ diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 6a4184e51ea18c..ec5717e0c85004 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -9158,7 +9158,7 @@ void CEEInfo::getFunctionEntryPoint(CORINFO_METHOD_HANDLE ftnHnd, } else { - ret = (void *)ftn->TryGetMultiCallableAddrOfCode(accessFlags); + ret = (void*)ftn->TryGetMultiCallableAddrOfCode((CORINFO_ACCESS_FLAGS)(accessFlags | CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE)); // TryGetMultiCallableAddrOfCode returns NULL if indirect access is desired if (ret == NULL) @@ -9166,7 +9166,7 @@ void CEEInfo::getFunctionEntryPoint(CORINFO_METHOD_HANDLE ftnHnd, // should never get here for EnC methods or if interception via remoting stub is required _ASSERTE(!ftn->InEnCEnabledModule()); - ret = (void *)ftn->GetAddrOfSlot(); + ret = (void*)ftn->GetAddrOfSlot(); accessType = IAT_PVALUE; } @@ -9205,7 +9205,7 @@ void CEEInfo::getFunctionFixedEntryPoint(CORINFO_METHOD_HANDLE ftn, pMD->PrepareForUseAsAFunctionPointer(); pResult->accessType = IAT_VALUE; - pResult->addr = (void*)pMD->GetMultiCallableAddrOfCode(); + pResult->addr = (void*)pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE); EE_TO_JIT_TRANSITION(); } @@ -11291,7 +11291,7 @@ LPVOID CEEInfo::GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) #ifdef FEATURE_INTERPRETER // Forward declare the function for mapping MetaSig to a cookie. -LPVOID GetCookieForCalliSig(MetaSig metaSig); +void* GetCookieForCalliSig(MetaSig metaSig); LPVOID CInterpreterJitInfo::GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) { @@ -13936,7 +13936,7 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } MethodEntry: - result = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY); + result = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE); } break; diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 6d40e0ee449113..bcd06ec4479bc6 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2104,12 +2104,13 @@ PCODE MethodDesc::TryGetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags COMPlusThrow(kInvalidOperationException, IDS_EE_CODEEXECUTION_CONTAINSGENERICVAR); } +#ifdef _DEBUG if (accessFlags & CORINFO_ACCESS_LDFTN) { // Whenever we use LDFTN on shared-generic-code-which-requires-an-extra-parameter - // we need to give out the address of an instantiating stub. This is why we give - // out GetStableEntryPoint() for the IsInstantiatingStub() case: this is - // safe. But first we assert that we only use GetMultiCallableAddrOfCode on + // we need to give out the address of an instantiating stub. This is why we give + // out GetStableEntryPoint() for IsInstantiatingStub() via the IsWrapperStub() case: this is + // safe. But first we assert that we only use GetMultiCallableAddrOfCode on // the instantiating stubs and not on the shared code itself. _ASSERTE(!RequiresInstArg()); _ASSERTE(!IsSharedByGenericMethodInstantiations()); @@ -2118,8 +2119,26 @@ PCODE MethodDesc::TryGetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags _ASSERTE((accessFlags & ~CORINFO_ACCESS_LDFTN) == 0); } +#ifndef FEATURE_PORTABLE_ENTRYPOINTS + // If HasUnmanagedCallersOnlyAttribute() is true, then CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE must be set. + // We only validate this on non portable entrypoints because HasUnmanagedCallersOnlyAttribute() + // is an unnecessary cost, even in non-Release builds, on portable entrypoint platforms. + if (HasUnmanagedCallersOnlyAttribute()) + { + _ASSERTE((accessFlags & CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE) != 0); + } +#endif // !FEATURE_PORTABLE_ENTRYPOINTS +#endif // _DEBUG + #ifdef FEATURE_PORTABLE_ENTRYPOINTS - return GetPortableEntryPoint(); + PCODE entryPoint = GetPortableEntryPoint(); + if (accessFlags & CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE + && PortableEntryPoint::ToPortableEntryPoint(entryPoint)->HasUnmanagedCallersOnlyAttribute()) + { + entryPoint = (PCODE)PortableEntryPoint::GetActualCode(entryPoint); + } + + return entryPoint; #else // !FEATURE_PORTABLE_ENTRYPOINTS if (RequiresStableEntryPoint() && !HasStableEntryPoint()) diff --git a/src/coreclr/vm/precode_portable.cpp b/src/coreclr/vm/precode_portable.cpp index 5b2b96c83271d4..2e81588278c4fb 100644 --- a/src/coreclr/vm/precode_portable.cpp +++ b/src/coreclr/vm/precode_portable.cpp @@ -76,6 +76,14 @@ void PortableEntryPoint::SetInterpreterData(PCODE addr, PCODE interpreterData) portableEntryPoint->_pInterpreterData = (void*)PCODEToPINSTR(interpreterData); } +#ifdef _DEBUG +bool PortableEntryPoint::IsValid() const +{ + LIMITED_METHOD_CONTRACT; + return _canary == CANARY_VALUE; +} +#endif // _DEBUG + PortableEntryPoint* PortableEntryPoint::ToPortableEntryPoint(PCODE addr) { LIMITED_METHOD_CONTRACT; @@ -86,14 +94,6 @@ PortableEntryPoint* PortableEntryPoint::ToPortableEntryPoint(PCODE addr) 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; @@ -101,6 +101,7 @@ void PortableEntryPoint::Init(MethodDesc* pMD) _pActualCode = NULL; _pMD = pMD; _pInterpreterData = NULL; + _flags = kNone; INDEBUG(_canary = CANARY_VALUE); } @@ -111,9 +112,52 @@ void PortableEntryPoint::Init(void* nativeEntryPoint) _pActualCode = nativeEntryPoint; _pMD = NULL; _pInterpreterData = NULL; + _flags = kNone; INDEBUG(_canary = CANARY_VALUE); } +namespace +{ + bool HasFlags(Volatile& flags, int32_t flagMask) + { + LIMITED_METHOD_CONTRACT; + return (flags.Load() & flagMask) == flagMask; + } + + void SetFlags(Volatile& flags, int32_t flagMask) + { + LIMITED_METHOD_CONTRACT; + ::InterlockedOr(flags.GetPointer(), flagMask); + } +} + +// Forward declaration +void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD); + +bool PortableEntryPoint::HasUnmanagedCallersOnlyAttribute() +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(IsValid()); + + if (HasFlags(_flags, kUnmanagedCallersOnly_Checked | kUnmanagedCallersOnly_Has)) + return true; + + if (HasFlags(_flags, kUnmanagedCallersOnly_Checked)) + return false; + + // First time check, do the full check + if (_pMD == nullptr || !_pMD->HasUnmanagedCallersOnlyAttribute()) + { + SetFlags(_flags, kUnmanagedCallersOnly_Checked); + return false; + } + + _pActualCode = GetUnmanagedCallersOnlyThunk(_pMD); + SetFlags(_flags, kUnmanagedCallersOnly_Checked | kUnmanagedCallersOnly_Has); + + return true; +} + InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig; Precode* Precode::Allocate(PrecodeType t, MethodDesc* pMD, diff --git a/src/coreclr/vm/precode_portable.hpp b/src/coreclr/vm/precode_portable.hpp index cae51ba32225b1..fe1a9a3c78feb1 100644 --- a/src/coreclr/vm/precode_portable.hpp +++ b/src/coreclr/vm/precode_portable.hpp @@ -21,14 +21,19 @@ class PortableEntryPoint final static void* GetInterpreterData(PCODE addr); static void SetInterpreterData(PCODE addr, PCODE interpreterData); -private: // static - static PortableEntryPoint* ToPortableEntryPoint(PCODE addr); - private: Volatile _pActualCode; MethodDesc* _pMD; void* _pInterpreterData; + enum PortableEntryPointFlag + { + kNone = 0, + kUnmanagedCallersOnly_Has = 0x1, + kUnmanagedCallersOnly_Checked = 0x2, + }; + Volatile _flags; + // We keep the canary value last to ensure a stable ABI across build flavors INDEBUG(size_t _canary); @@ -36,10 +41,16 @@ class PortableEntryPoint final bool IsValid() const; #endif // _DEBUG +public: // static + static PortableEntryPoint* ToPortableEntryPoint(PCODE addr); + public: void Init(MethodDesc* pMD); void Init(void* nativeEntryPoint); + // Check if the entry point represents a method with the UnmanagedCallersOnly attribute + bool HasUnmanagedCallersOnlyAttribute(); + // Query methods for entry point state. bool HasInterpreterCode() const { diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index a3544a20b119a0..8e0d24173d763b 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2056,18 +2056,37 @@ extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBl return frames.interpMethodContextFrame.pRetVal; } -extern "C" void* STDCALL ExecuteInterpretedMethodWithArgs(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, int8_t* pArgs, size_t size, void* retBuff) +void ExecuteInterpretedMethodWithArgs(TADDR targetIp, int8_t* args, size_t argSize, void* retBuff) { - // copy the arguments to the stack - if (size > 0 && pArgs != nullptr) + // Copy arguments to the stack + if (argSize > 0) { + _ASSERTE(args != NULL); InterpThreadContext *threadContext = GetInterpThreadContext(); - int8_t *sp = threadContext->pStackPointer; - - memcpy(sp, pArgs, size); + int8_t* sp = threadContext->pStackPointer; + memcpy(sp, args, argSize); } - return ExecuteInterpretedMethod(pTransitionBlock, byteCodeAddr, retBuff); + TransitionBlock dummy{}; + (void)ExecuteInterpretedMethod(&dummy, (TADDR)targetIp, retBuff); +} + +extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret) +{ + _ASSERTE(pMD != NULL); + + // This path assumes the caller is unmanaged code. This assumption is important for + // because the DoPrestub call may trigger a GC. If a GC occurs, there would be no explicit + // protection for the arguments, but since this from an unmanaged caller, no protection is needed. + + InterpByteCodeStart* targetIp = pMD->GetInterpreterCode(); + if (targetIp == NULL) + { + GCX_PREEMP(); + (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); + targetIp = pMD->GetInterpreterCode(); + } + (void)ExecuteInterpretedMethodWithArgs((TADDR)targetIp, args, argSize, ret); } #endif // FEATURE_INTERPRETER diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 824e1aa035eb94..fd69e6cc10c0dd 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1271,7 +1271,7 @@ extern "C" void * QCALLTYPE RuntimeMethodHandle_GetFunctionPointer(MethodDesc * // Ensure the method is active and all types have been loaded so the function pointer can be used. pMethod->EnsureActive(); pMethod->PrepareForUseAsAFunctionPointer(); - funcPtr = (void*)pMethod->GetMultiCallableAddrOfCode(); + funcPtr = (void*)pMethod->GetMultiCallableAddrOfCode(CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE); END_QCALL; diff --git a/src/coreclr/vm/wasm/calldescrworkerwasm.cpp b/src/coreclr/vm/wasm/calldescrworkerwasm.cpp index c8e7e101c36080..b5075d7228d356 100644 --- a/src/coreclr/vm/wasm/calldescrworkerwasm.cpp +++ b/src/coreclr/vm/wasm/calldescrworkerwasm.cpp @@ -4,10 +4,19 @@ #include -extern "C" void* STDCALL ExecuteInterpretedMethodWithArgs(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, int8_t* pArgs, size_t size, void* retBuff); +// Forward declaration +void ExecuteInterpretedMethodWithArgs(TADDR targetIp, int8_t* args, size_t argSize, void* retBuff); extern "C" void STDCALL CallDescrWorkerInternal(CallDescrData* pCallDescrData) { + _ASSERTE(pCallDescrData != NULL); + _ASSERTE(pCallDescrData->pTarget != (PCODE)NULL); + + // WASM-TODO: This path has a flaw. The DoPrestub call may trigger a GC, and there is no + // explicit protection for the arguments. All platforms assume part of the call is + // a no GC trigger region, but DoPrestub may trigger a GC. Therefore this needs to be + // revisited to ensure correctness. + MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pCallDescrData->pTarget); InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); if (targetIp == NULL) @@ -17,6 +26,5 @@ extern "C" void STDCALL CallDescrWorkerInternal(CallDescrData* pCallDescrData) targetIp = pMethod->GetInterpreterCode(); } - TransitionBlock dummy{}; - ExecuteInterpretedMethodWithArgs(&dummy, (TADDR)targetIp, (int8_t*)pCallDescrData->pSrc, pCallDescrData->nArgsSize, pCallDescrData->returnValue); + ExecuteInterpretedMethodWithArgs((TADDR)targetIp, (int8_t*)pCallDescrData->pSrc, pCallDescrData->nArgsSize, (int8_t*)pCallDescrData->returnValue); } diff --git a/src/coreclr/vm/wasm/callhelpers.cpp b/src/coreclr/vm/wasm/callhelpers.cpp index 4cbcad98f5382a..b4d7bdb752a197 100644 --- a/src/coreclr/vm/wasm/callhelpers.cpp +++ b/src/coreclr/vm/wasm/callhelpers.cpp @@ -735,3 +735,53 @@ const StringToWasmSigThunk g_wasmThunks[] = { }; const size_t g_wasmThunksCount = sizeof(g_wasmThunks) / sizeof(g_wasmThunks[0]); + +// Define reverse thunks here + +// Entry point for interpreted method execution from unmanaged code +class MethodDesc; + +// WASM-TODO: The method lookup would ideally be fully qualified assembly and then methodDef token. +// The current approach has limitations with overloaded methods. +extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD); +extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret); + +static MethodDesc* MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler() +{ + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid) + { + LookupMethodByName("System.Threading.ThreadPool, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, System.Private.CoreLib", "BackgroundJobHandler", &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid); + } + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, nullptr, 0, nullptr); +} + +extern "C" void SystemJS_ExecuteBackgroundJobCallback() +{ + Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler(); +} + +static MethodDesc* MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler() +{ + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid) + { + LookupMethodByName("System.Threading.TimerQueue, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, System.Private.CoreLib", "TimerHandler", &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid); + } + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, nullptr, 0, nullptr); +} + +extern "C" void SystemJS_ExecuteTimerCallback() +{ + Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler(); +} + +extern const ReverseThunkMapEntry g_ReverseThunks[] = +{ + { 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler } }, + { 100678363, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler } }, +}; + +const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]); diff --git a/src/coreclr/vm/wasm/callhelpers.hpp b/src/coreclr/vm/wasm/callhelpers.hpp index 3fdd44db36efd4..66cff3660a197d 100644 --- a/src/coreclr/vm/wasm/callhelpers.hpp +++ b/src/coreclr/vm/wasm/callhelpers.hpp @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // -#ifndef __callhelpers_h__ -#define __callhelpers_h__ +#ifndef __WASM_CALLHELPERS_HPP__ +#define __WASM_CALLHELPERS_HPP__ struct StringToWasmSigThunk { @@ -14,4 +14,19 @@ struct StringToWasmSigThunk extern const StringToWasmSigThunk g_wasmThunks[]; extern const size_t g_wasmThunksCount; -#endif // __callhelpers_h__ +struct ReverseThunkMapValue +{ + MethodDesc** Target; + void* EntryPoint; +}; + +struct ReverseThunkMapEntry +{ + ULONG key; + ReverseThunkMapValue value; +}; + +extern const ReverseThunkMapEntry g_ReverseThunks[]; +extern const size_t g_ReverseThunksCount; + +#endif // __WASM_CALLHELPERS_HPP__ diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 8c0342e4ea1fb9..742e3a947da208 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -3,6 +3,7 @@ // #include +#include #include "callhelpers.hpp" #include "shash.h" @@ -416,6 +417,7 @@ void InvokeCalliStub(PCODE ftn, void* cookie, int8_t *pArgs, int8_t *pRet) _ASSERTE(ftn != (PCODE)NULL); _ASSERTE(cookie != NULL); + // WASM-TODO: Reconcile calling conventions for managed calli. PCODE actualFtn = (PCODE)PortableEntryPoint::GetActualCode(ftn); ((void(*)(PCODE, int8_t*, int8_t*))cookie)(actualFtn, pArgs, pRet); } @@ -424,8 +426,6 @@ void InvokeUnmanagedCalli(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet) { _ASSERTE(ftn != (PCODE)NULL); _ASSERTE(cookie != NULL); - - // WASM-TODO: Reconcile calling conventions. ((void(*)(PCODE, int8_t*, int8_t*))cookie)(ftn, pArgs, pRet); } @@ -558,14 +558,14 @@ namespace return true; } - class StringWasmThunkSHashTraits : public MapSHashTraits + class StringThunkSHashTraits : public MapSHashTraits { public: static BOOL Equals(const char* s1, const char* s2) { return strcmp(s1, s2) == 0; } static count_t Hash(const char* key) { return HashStringA(key); } }; - typedef MapSHash> StringToWasmSigThunkHash; + typedef MapSHash> StringToWasmSigThunkHash; static StringToWasmSigThunkHash* thunkCache = nullptr; void* LookupThunk(const char* key) @@ -620,9 +620,56 @@ namespace return LookupThunk(keyBuffer); } + + // TODO: This hashing function should be replaced. + ULONG CreateKey(MethodDesc* pMD) + { + _ASSERTE(pMD != nullptr); + + // Get the fully qualified name hash of the method as the key. + // Example: 'MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' + SString strAssemblyName; + pMD->GetAssembly()->GetDisplayName(strAssemblyName); + + // Get the member def token for the method. + mdMethodDef token = pMD->GetMemberDef(); + + // Combine the two to create a reasonably unique key. + return strAssemblyName.Hash() ^ token; + } + + typedef MapSHash HashToReverseThunkHash; + HashToReverseThunkHash* reverseThunkCache = nullptr; + + const ReverseThunkMapValue* LookupThunk(MethodDesc* pMD) + { + HashToReverseThunkHash* table = VolatileLoad(&reverseThunkCache); + if (table == nullptr) + { + HashToReverseThunkHash* newTable = new HashToReverseThunkHash(); + newTable->Reallocate(g_ReverseThunksCount * HashToReverseThunkHash::s_density_factor_denominator / HashToReverseThunkHash::s_density_factor_numerator + 1); + for (size_t i = 0; i < g_ReverseThunksCount; i++) + { + newTable->Add(g_ReverseThunks[i].key, &g_ReverseThunks[i].value); + } + + if (InterlockedCompareExchangeT(&reverseThunkCache, newTable, nullptr) != nullptr) + { + // Another thread won the race, discard ours + delete newTable; + } + table = reverseThunkCache; + } + + ULONG key = CreateKey(pMD); + + const ReverseThunkMapValue* thunk; + bool success = table->Lookup(key, &thunk); + return success ? thunk : nullptr; + } } -LPVOID GetCookieForCalliSig(MetaSig metaSig) +void* GetCookieForCalliSig(MetaSig metaSig) { STANDARD_VM_CONTRACT; @@ -635,6 +682,29 @@ LPVOID GetCookieForCalliSig(MetaSig metaSig) return thunk; } +void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(pMD != NULL); + _ASSERTE(pMD->HasUnmanagedCallersOnlyAttribute()); + + const ReverseThunkMapValue* value = LookupThunk(pMD); + if (value == NULL) + { + PORTABILITY_ASSERT("GetUnmanagedCallersOnlyThunk: unknown thunk for unmanaged callers only method"); + return NULL; + } + + // Update the target method if not already set. + _ASSERTE(value->Target != NULL); + if (NULL == (*value->Target)) + *value->Target = pMD; + + _ASSERTE((*value->Target) == pMD); + _ASSERTE(value->EntryPoint != NULL); + return value->EntryPoint; +} + void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target) { MetaSig sig(pMD); @@ -649,21 +719,3 @@ void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet { PORTABILITY_ASSERT("Attempted to execute unmanaged code from interpreter on wasm, this is not yet implemented"); } - -// WASM-TODO: use [UnmanagedCallersOnly] once is supported in wasm -// https://github.com/dotnet/runtime/issues/121006 -extern "C" void SystemJS_ExecuteTimerCallback() -{ - ARG_SLOT stackVarWrapper[] = { }; - MethodDescCallSite timerHandler(METHOD__TIMER_QUEUE__TIMER_HANDLER); - timerHandler.Call(stackVarWrapper); -} - -// WASM-TODO: use [UnmanagedCallersOnly] once is supported in wasm -// https://github.com/dotnet/runtime/issues/121006 -extern "C" void SystemJS_ExecuteBackgroundJobCallback() -{ - ARG_SLOT stackVarWrapper[] = { }; - MethodDescCallSite backgroundJobHandler(METHOD__THREAD_POOL__BACKGROUND_JOB_HANDLER); - backgroundJobHandler.Call(stackVarWrapper); -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.cs index 3f48adb129c520..75e5d38767aa2b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.cs @@ -83,7 +83,7 @@ internal static unsafe void RequestWorkerThread() return; _callbackQueued = true; #if MONO - MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); + MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged)&BackgroundJobHandler); #else SystemJS_ScheduleBackgroundJob(); #endif @@ -128,9 +128,7 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( private static unsafe partial void SystemJS_ScheduleBackgroundJob(); #endif -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] -#pragma warning restore CS3016 + [UnmanagedCallersOnly(EntryPoint = "SystemJS_ExecuteBackgroundJobCallback")] // this callback will arrive on the bound thread, called from mono_background_exec private static void BackgroundJobHandler() { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.cs index d79639d8f6c234..78157e57924637 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.cs @@ -40,9 +40,7 @@ private TimerQueue(int _) private static unsafe partial void SystemJS_ScheduleTimer(int shortestDueTimeMs); #endif -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] -#pragma warning restore CS3016 + [UnmanagedCallersOnly(EntryPoint = "SystemJS_ExecuteTimerCallback")] // this callback will arrive on the main thread, called from mono_wasm_execute_timer private static void TimerHandler() { @@ -95,7 +93,7 @@ private static unsafe void ReplaceNextTimer(long shortestDueTimeMs, long current int shortestWait = Math.Max((int)(shortestDueTimeMs - currentTimeMs), 0); // this would cancel the previous schedule and create shorter one, it is expensive callback #if MONO - MainThreadScheduleTimer((void*)(delegate* unmanaged[Cdecl])&TimerHandler, shortestWait); + MainThreadScheduleTimer((void*)(delegate* unmanaged)&TimerHandler, shortestWait); #else SystemJS_ScheduleTimer(shortestWait); #endif