diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 4cb53b6bdd8af9..69af35cc9ad8b1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -3220,8 +3220,12 @@ private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORIN ModuleToken moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle); MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, genericContextObject: null); - if ((ecmaMethod.GetPInvokeMethodCallingConventions() & UnmanagedCallingConventions.IsSuppressGcTransition) != 0) + if (((ecmaMethod.GetPInvokeMethodCallingConventions() & UnmanagedCallingConventions.IsSuppressGcTransition) != 0) + || _compilation.NodeFactory.Target.IsWasm) { + // Suppress GC transition P/Invokes are called directly, since we can't do a GC transition at this point. + // On Wasm, we also call directly because the runtime doesn't generate P/Invoke import precodes/stubs; instead, + // errors are reported when we fix up the method. pLookup.addr = (void*)ObjectToHandle(_compilation.SymbolNodeFactory.GetPInvokeTargetNode(methodWithToken)); pLookup.accessType = InfoAccessType.IAT_PVALUE; } diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 52241ce4b506e6..e66b5a6aebc298 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1114,7 +1114,7 @@ HRESULT EEToProfInterfaceImpl::SetEnterLeaveFunctionHooksForJit(FunctionEnter3 * // tailored to the post-pinvoke operations. extern "C" VOID JIT_PInvokeEndRarePath(); -void JIT_PInvokeEndRarePath() +NOINLINE void JIT_PInvokeEndRarePath() { PreserveLastErrorHolder preserveLastError; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index fc8802a1ff863c..1eab4d07217835 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14334,6 +14334,7 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } break; +#ifdef HAS_PINVOKE_IMPORT_PRECODE case READYTORUN_FIXUP_IndirectPInvokeTarget: { MethodDesc *pMethod = ZapSig::DecodeMethod(currentModule, pInfoModule, pBlob); @@ -14343,6 +14344,7 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, result = (size_t)(LPVOID)&(pMD->m_pPInvokeTarget); } break; +#endif // HAS_PINVOKE_IMPORT_PRECODE case READYTORUN_FIXUP_PInvokeTarget: { diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 01fa8ad6cb990e..3708b02e8d2af3 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -13,6 +13,7 @@ #define WASM_STRINGIFY_HELPER(value) #value #define WASM_STRINGIFY(value) WASM_STRINGIFY_HELPER(value) +#define INLINED_PINVOKE_FROM_R2R 1 void ExecuteInterpretedMethodWithArgs_PortableEntryPoint(PCODE portableEntrypoint, TransitionBlock* block, size_t argsSize, int8_t* retBuff); @@ -491,13 +492,22 @@ void InlinedCallFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateF return; } - pRD->pCurrentContext->InterpreterIP = *(DWORD *)&m_pCallerReturnAddress; - pRD->IsCallerContextValid = FALSE; - pRD->pCurrentContext->InterpreterSP = *(DWORD *)&m_pCallSiteSP; - pRD->pCurrentContext->InterpreterFP = *(DWORD *)&m_pCalleeSavedFP; - + if (m_pCallerReturnAddress == INLINED_PINVOKE_FROM_R2R) + { + pRD->pCurrentContext->InterpreterSP = (TADDR)m_pCallSiteSP; + pRD->pCurrentContext->InterpreterIP = GetWasmVirtualIPFromStackPointer((TADDR)m_pCallSiteSP); + _ASSERTE(pRD->pCurrentContext->InterpreterIP != 0); // We should be in RyuJit compiled code here + pRD->pCurrentContext->InterpreterFP = GetWasmFramePointerFromStackPointer((TADDR)m_pCallSiteSP, (PCODE)pRD->pCurrentContext->InterpreterIP); + } + else + { + pRD->pCurrentContext->InterpreterIP = *(DWORD *)&m_pCallerReturnAddress; + pRD->pCurrentContext->InterpreterSP = *(DWORD *)&m_pCallSiteSP; + pRD->pCurrentContext->InterpreterFP = *(DWORD *)&m_pCalleeSavedFP; + } + SyncRegDisplayToCurrentContext(pRD); #ifdef FEATURE_INTERPRETER @@ -681,15 +691,88 @@ extern "C" void STDCALL GenericPInvokeCalliHelper(void) PORTABILITY_ASSERT("GenericPInvokeCalliHelper is not implemented on wasm"); } -EXTERN_C void JIT_PInvokeBegin(InlinedCallFrame* pFrame) +// Does the pinvoke frame transition; the naked wrappers below have already set the wasm +// __stack_pointer global to sp so it is safe to run native code here. +EXTERN_C void JIT_PInvokeBeginImpl(void* sp, InlinedCallFrame* pFrame) +{ + Thread* pThread = GetThread(); + + // Initialize the JIT-provided frame storage, deriving its state from sp/pep since wasm + // has no machine registers to read the caller SP / return address from. + ::new ((void*)pFrame) InlinedCallFrame(); + pFrame->m_pCallSiteSP = sp; + pFrame->m_pCallerReturnAddress = INLINED_PINVOKE_FROM_R2R; // When this is true, UpdateRegDisplay_Impl derives state from m_pCallSiteSP. + pFrame->m_pCalleeSavedFP = 0; + pFrame->m_pThread = pThread; + + // Link the frame and transition to preemptive GC mode for the native call. + pFrame->Push(); + pThread->EnablePreemptiveGC(); +} + +// R2R keeps its shadow SP in a local and leaves the __stack_pointer global stale, so publish +// the incoming sp to __stack_pointer before any native code runs and leave it there so the +// subsequent native pinvoke target is also safe. +extern "C" __attribute__((naked)) void JIT_PInvokeBegin(void* sp, InlinedCallFrame* pFrame, PCODE pep) +{ + asm("local.get 0\n" /* sp */ + "global.set __stack_pointer\n" /* __stack_pointer = sp before any native code runs */ + "local.get 0\n" /* sp */ + "local.get 1\n" /* pFrame */ + "call %0\n" + "return" ::"i"(JIT_PInvokeBeginImpl)); +} + +extern "C" VOID JIT_PInvokeEndRarePath(); + +#ifdef DEBUG +// Debug variant of these apis tests that sp and __stack_pointer are in sync +EXTERN_C void JIT_PInvokeEndImpl(TADDR sp, TADDR stack_pointer_global_value, InlinedCallFrame* pFrame) { - PORTABILITY_ASSERT("JIT_PInvokeBegin is not implemented on wasm"); + _ASSERTE(sp == stack_pointer_global_value); + Thread* pThread = (Thread*)pFrame->m_pThread; + + pThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(1); + if (g_TrapReturningThreads) + { + JIT_PInvokeEndRarePath(); + } + else + { + pFrame->Pop(); + } } -EXTERN_C void JIT_PInvokeEnd(InlinedCallFrame* pFrame) +extern "C" __attribute__((naked)) void JIT_PInvokeEnd(void* sp, InlinedCallFrame* pFrame, PCODE pep) +{ + asm( + "local.get 0\n" /* sp */ + "global.get __stack_pointer\n" /* __stack_pointer */ + "local.get 1\n" /* pFrame */ + "local.get 0\n" /* sp */ + "global.set __stack_pointer\n" /* __stack_pointer = sp before any native code runs, set this here, so that if the assumption around sp == __stack_pointer is wrong the assert logic will work correctly. */ + "call %0\n" + "return" ::"i"(JIT_PInvokeEndImpl)); +} +#else +extern "C" void JIT_PInvokeEnd(void* sp, InlinedCallFrame* pFrame, PCODE pep) { - PORTABILITY_ASSERT("JIT_PInvokeEnd is not implemented on wasm"); + UNREFERENCED_PARAMETER(sp); + UNREFERENCED_PARAMETER(pep); + + Thread* pThread = (Thread*)pFrame->m_pThread; + + pThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(1); + if (g_TrapReturningThreads) + { + JIT_PInvokeEndRarePath(); + } + else + { + pFrame->Pop(); + } } +#endif extern "C" void STDCALL JIT_StackProbe() {