diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index d61bf5fbe81840..69687399a5af8e 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2854,6 +2854,27 @@ void InterpCompiler::EmitPushLdvirtftn(int thisVar, CORINFO_RESOLVED_TOKEN* pRes m_pLastNewIns->info.pCallInfo->pCallArgs = callArgs; } +void InterpCompiler::EmitCalli(bool isTailCall, void* calliCookie, int callIFunctionPointerVar, CORINFO_SIG_INFO* callSiteSig) +{ + AddIns(isTailCall ? INTOP_CALLI_TAIL : INTOP_CALLI); + m_pLastNewIns->data[0] = GetDataItemIndex(calliCookie); + // data[1] is set to 1 if the calli is calling a pinvoke, 0 otherwise + bool suppressGCTransition = false; + CorInfoCallConv callConv = (CorInfoCallConv)(callSiteSig->callConv & IMAGE_CEE_CS_CALLCONV_MASK); + bool isPInvoke = (callConv != CORINFO_CALLCONV_DEFAULT && callConv != CORINFO_CALLCONV_VARARG); + if (isPInvoke) + { + if (m_compHnd->pInvokeMarshalingRequired(NULL, callSiteSig)) + { + BADCODE("PInvoke marshalling for calli is not supported in interpreted code"); + } + m_compHnd->getUnmanagedCallConv(nullptr, callSiteSig, &suppressGCTransition); + } + m_pLastNewIns->data[1] = (suppressGCTransition ? (int32_t)CalliFlags::SuppressGCTransition : 0) | + (isPInvoke ? (int32_t)CalliFlags::PInvoke : 0); + m_pLastNewIns->SetSVars2(CALL_ARGS_SVAR, callIFunctionPointerVar); +} + void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool readonly, bool tailcall, bool newObj, bool isCalli) { uint32_t token = getU4LittleEndian(m_ip + 1); @@ -3259,9 +3280,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re } else if (isCalli) { - AddIns(tailcall ? INTOP_CALLI_TAIL : INTOP_CALLI); - m_pLastNewIns->data[0] = GetDataItemIndex(calliCookie); - m_pLastNewIns->SetSVars2(CALL_ARGS_SVAR, callIFunctionPointerVar); + EmitCalli(tailcall, calliCookie, callIFunctionPointerVar, &callInfo.sig); } else { @@ -3318,9 +3337,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig); - AddIns(tailcall ? INTOP_CALLI_TAIL : INTOP_CALLI); - m_pLastNewIns->data[0] = GetDataItemIndex(calliCookie); - m_pLastNewIns->SetSVars2(CALL_ARGS_SVAR, codePointerLookupResult); + EmitCalli(tailcall, calliCookie, codePointerLookupResult, &callInfo.sig); break; } case CORINFO_VIRTUALCALL_VTABLE: @@ -3353,9 +3370,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig); - AddIns(tailcall ? INTOP_CALLI_TAIL : INTOP_CALLI); - m_pLastNewIns->data[0] = GetDataItemIndex(calliCookie); - m_pLastNewIns->SetSVars2(CALL_ARGS_SVAR, synthesizedLdvirtftnPtrVar); + EmitCalli(tailcall, calliCookie, synthesizedLdvirtftnPtrVar, &callInfo.sig); } else { diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index f8c63336cea119..6c7144ec3dc4d5 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -711,6 +711,7 @@ class InterpCompiler void EmitShiftOp(int32_t opBase); void EmitCompareOp(int32_t opBase); void EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool readonly, bool tailcall, bool newObj, bool isCalli); + void EmitCalli(bool isTailCall, void* calliCookie, int callIFunctionPointerVar, CORINFO_SIG_INFO* callSiteSig); bool EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig); void EmitLdind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset); void EmitStind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset, bool reverseSVarOrder); diff --git a/src/coreclr/interpreter/inc/interpretershared.h b/src/coreclr/interpreter/inc/interpretershared.h index 74af7e8ab0ee3a..d9e79f3bffea57 100644 --- a/src/coreclr/interpreter/inc/interpretershared.h +++ b/src/coreclr/interpreter/inc/interpretershared.h @@ -171,4 +171,11 @@ enum class PInvokeCallFlags : int32_t SuppressGCTransition = 1 << 1, // The pinvoke is marked by the SuppressGCTransition attribute }; +enum class CalliFlags : int32_t +{ + None = 0, + SuppressGCTransition = 1 << 1, // The call is marked by the SuppressGCTransition attribute + PInvoke = 1 << 2, // The call is a PInvoke call +}; + #endif diff --git a/src/coreclr/interpreter/inc/intops.def b/src/coreclr/interpreter/inc/intops.def index 3a6b9754d8b46e..f8ca4befbc9a42 100644 --- a/src/coreclr/interpreter/inc/intops.def +++ b/src/coreclr/interpreter/inc/intops.def @@ -358,7 +358,7 @@ OPDEF(INTOP_LDFLDA, "ldflda", 4, 1, 1, InterpOpInt) // Calls OPDEF(INTOP_CALL, "call", 4, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_CALLDELEGATE, "call.delegate", 4, 1, 1, InterpOpMethodHandle) -OPDEF(INTOP_CALLI, "calli", 5, 1, 2, InterpOpLdPtr) +OPDEF(INTOP_CALLI, "calli", 6, 1, 2, InterpOpLdPtr) OPDEF(INTOP_CALLVIRT, "callvirt", 4, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_CALL_PINVOKE, "call.pinvoke", 6, 1, 1, InterpOpMethodHandle) // inlined (no marshaling wrapper) pinvokes only OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodHandle) @@ -367,7 +367,7 @@ OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodHandle) // Tail calls OPDEF(INTOP_CALL_TAIL, "call.tail", 4, 1, 1, InterpOpMethodHandle) -OPDEF(INTOP_CALLI_TAIL, "calli", 5, 1, 2, InterpOpLdPtr) +OPDEF(INTOP_CALLI_TAIL, "calli.tail", 6, 1, 2, InterpOpLdPtr) OPDEF(INTOP_CALLVIRT_TAIL, "callvirt.tail", 4, 1, 1, InterpOpMethodHandle) // The following helper call instructions exist in 2 variants, one for normal methods, and one for cases where a shared generic lookup is needed. diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 040e366456a2b9..e5bd23c538a2cf 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -13,13 +13,14 @@ // 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 InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget); +void InvokeCalliStub(PCODE ftn, void* cookie, int8_t *pArgs, int8_t *pRet); +void InvokeUnmanagedCalli(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet); void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target); // 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) +void InvokeUnmanagedMethodWithTransition(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int8_t *pArgs, int8_t *pRet, PCODE callTarget) { InlinedCallFrame inlinedCallFrame; inlinedCallFrame.m_pCallerReturnAddress = (TADDR)pFrame->ip; @@ -32,12 +33,38 @@ void InvokeUnmanagedMethodWithTransition(MethodDesc *targetMethod, int8_t *stack { GCX_PREEMP(); // WASM-TODO: Handle unmanaged calling conventions - InvokeManagedMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); + InvokeManagedMethod(targetMethod, pArgs, pRet, callTarget); } inlinedCallFrame.Pop(); } +NOINLINE +void InvokeUnmanagedCalliWithTransition(PCODE ftn, void *cookie, int8_t *stack, InterpMethodContextFrame *pFrame, int8_t *pArgs, int8_t *pRet) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer((void*)ftn)); + PRECONDITION(CheckPointer(cookie)); + } + CONTRACTL_END + + 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(); + InvokeUnmanagedCalli(ftn, cookie, pArgs, pRet); + } + inlinedCallFrame.Pop(); +} + #ifndef TARGET_WASM #include "callstubgenerator.h" @@ -96,9 +123,9 @@ void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE tar 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) +void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) { - InvokeManagedMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); + InvokeManagedMethod(targetMethod, pArgs, pRet, callTarget); } void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target) @@ -129,6 +156,29 @@ void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, in pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } +void InvokeUnmanagedCalli(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + PRECONDITION(CheckPointer((void*)ftn)); + 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); + CallStubHeader *pHeader = (CallStubHeader*)actualCallStub; + pHeader->SetTarget(ftn); // The method to call + + pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); +} + void InvokeCalliStub(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet) { CONTRACTL @@ -2117,15 +2167,31 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr callArgsOffset = ip[2]; int32_t calliFunctionPointerVar = ip[3]; int32_t calliCookie = ip[4]; + int32_t flags = ip[5]; void* cookie = pMethod->pDataItems[calliCookie]; - ip += 5; + ip += 6; // Save current execution state for when we return from called method pFrame->ip = ip; // Interpreter-FIXME: isTailcall - InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack + callArgsOffset, stack + returnOffset); + if (flags & (int32_t)CalliFlags::PInvoke) + { + if (flags & (int32_t)CalliFlags::SuppressGCTransition) + { + InvokeUnmanagedCalli(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack + callArgsOffset, stack + returnOffset); + } + else + { + InvokeUnmanagedCalliWithTransition(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack, pFrame, stack + callArgsOffset, stack + returnOffset); + } + } + else + { + InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack + callArgsOffset, stack + returnOffset); + } + break; } @@ -2152,11 +2218,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr if (flags & (int32_t)PInvokeCallFlags::SuppressGCTransition) { - InvokeUnmanagedMethod(targetMethod, stack, pFrame, callArgsOffset, returnOffset, callTarget); + InvokeUnmanagedMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, callTarget); } else { - InvokeUnmanagedMethodWithTransition(targetMethod, stack, pFrame, callArgsOffset, returnOffset, callTarget); + InvokeUnmanagedMethodWithTransition(targetMethod, stack, pFrame, stack + callArgsOffset, stack + returnOffset, callTarget); } break; diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index a85456b76413d5..5db0695c61eebf 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -486,7 +486,7 @@ void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE tar PORTABILITY_ASSERT("Attempted to execute non-interpreter code from interpreter on wasm, this is not yet implemented"); } -void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *stack, InterpMethodContextFrame *pFrame, int32_t callArgsOffset, int32_t returnOffset, PCODE callTarget) +void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) { PORTABILITY_ASSERT("Attempted to execute unmanaged code from interpreter on wasm, this is not yet implemented"); } @@ -499,6 +499,11 @@ void InvokeCalliStub(PCODE ftn, void* cookie, int8_t *pArgs, int8_t *pRet) ((void(*)(PCODE, int8_t*, int8_t*))cookie)(ftn, pArgs, pRet); } +void InvokeUnmanagedCalli(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet) +{ + PORTABILITY_ASSERT("Attempted to execute unmanaged calli from interpreter on wasm, this is not yet implemented"); +} + 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");