Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4318,11 +4318,29 @@ bool InterpCompiler::DisallowTailCall(CORINFO_SIG_INFO* callerSig, CORINFO_SIG_I
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);
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
// On portable entry point platforms, managed callis defer cookie resolution to
// execution time. Allocate two consecutive data items: the sig token (immutable)
// and a cache slot (initially NULL) for the resolved cookie.
// calliCookie is non-NULL only for IL calli instructions where it holds
// the signature token. CODE_POINTER / LDVIRTFTN callis pass NULL because
// their targets always carry a MethodDesc resolved at execution time.
bool deferredCookie = !isPInvoke && calliCookie != NULL;
if (deferredCookie)
{
m_pLastNewIns->data[0] = GetNewDataItemIndex(calliCookie);
int32_t cacheSlot = GetNewDataItemIndex(nullptr);
_ASSERTE(cacheSlot == m_pLastNewIns->data[0] + 1);
}
else
#endif
{
m_pLastNewIns->data[0] = GetDataItemIndex(calliCookie);
}
if (isPInvoke)
{
if (m_compHnd->pInvokeMarshalingRequired(NULL, callSiteSig))
Expand All @@ -4334,7 +4352,11 @@ void InterpCompiler::EmitCalli(bool isTailCall, void* calliCookie, int callIFunc
m_compHnd->getUnmanagedCallConv(nullptr, callSiteSig, &suppressGCTransition);
}
m_pLastNewIns->data[1] = (suppressGCTransition ? (int32_t)CalliFlags::SuppressGCTransition : 0) |
(isPInvoke ? (int32_t)CalliFlags::PInvoke : 0);
(isPInvoke ? (int32_t)CalliFlags::PInvoke : 0)
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
| (deferredCookie ? (int32_t)CalliFlags::DeferredCookie : 0)
#endif
;
m_pLastNewIns->SetSVars2(CALL_ARGS_SVAR, callIFunctionPointerVar);
}

Expand Down Expand Up @@ -4796,7 +4818,30 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re

callIFunctionPointerVar = m_pStackPointer[-1].var;
m_pStackPointer--;
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
// On platforms with portable entry points, managed calli targets are portable
// entry points. At execution time, if the portable entry point carries a MethodDesc,
// the call is dispatched via CALL_INTERP_METHOD which derives the cookie from the
// MethodDesc. Targets without a MethodDesc — JIT helper portable entry points such
// as object allocators (e.g. CORINFO_HELP_NEWFAST) called via delegate* from BCL
// code like ActivatorCache — fall through to deferred cookie resolution using the
// calli's signature token stored here.
{
CorInfoCallConv callConv = (CorInfoCallConv)(callInfo.sig.callConv & IMAGE_CEE_CS_CALLCONV_MASK);
bool isUnmanaged = (callConv != CORINFO_CALLCONV_DEFAULT && callConv != CORINFO_CALLCONV_VARARG);
if (isUnmanaged)
{
calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig);
}
else
{
// Store the sig token for runtime cookie resolution.
calliCookie = (void*)(size_t)token;
}
}
#else
calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig);
#endif
m_ip += 5;
}
else
Expand Down Expand Up @@ -5445,7 +5490,11 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re
m_pStackPointer--;
int codePointerLookupResult = m_pStackPointer[0].var;

#ifdef FEATURE_PORTABLE_ENTRYPOINTS
calliCookie = NULL;
#else
calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig);
#endif

EmitCalli(tailcall, calliCookie, codePointerLookupResult, &callInfo.sig);

Expand Down Expand Up @@ -5488,7 +5537,11 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re
m_pStackPointer--;
int synthesizedLdvirtftnPtrVar = m_pStackPointer[0].var;

#ifdef FEATURE_PORTABLE_ENTRYPOINTS
calliCookie = NULL;
#else
calliCookie = m_compHnd->GetCookieForInterpreterCalliSig(&callInfo.sig);
#endif

EmitCalli(tailcall, calliCookie, synthesizedLdvirtftnPtrVar, &callInfo.sig);
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/interpreter/inc/interpretershared.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ 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
DeferredCookie = 1 << 3, // Cookie data item contains a sig token to be resolved at runtime
};

struct InterpIntervalMapEntry
Expand Down
38 changes: 32 additions & 6 deletions src/coreclr/vm/interpexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet
void InvokeCalliStub(CalliStubParam* pParam);
void InvokeUnmanagedCalli(PCODE ftn, void *cookie, int8_t *pArgs, int8_t *pRet);
void InvokeDelegateInvokeMethod(DelegateInvokeMethodParam* pParam);
void* GetCookieForCalliSig(MetaSig metaSig, MethodDesc *pContextMD);
extern "C" PCODE CID_VirtualOpenDelegateDispatch(TransitionBlock * pTransitionBlock);

// Filter to ignore SEH exceptions representing C++ exceptions.
Expand Down Expand Up @@ -3037,15 +3038,40 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
else
{
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
// WASM-TODO: We may end up here with native JIT helper entrypoint without MethodDesc
// that CALL_INTERP_METHOD is not able to handle. This is a potential problem for
// interpreter<->native code stub generator.
// https://github.com/dotnet/runtime/pull/119516#discussion_r2337631271
if (!PortableEntryPoint::HasNativeEntryPoint(calliFunctionPointer))
// On portable entry point platforms, calli targets are portable entry points.
// If the portable entry point has a MethodDesc, route through
// CALL_INTERP_METHOD which derives the cookie at runtime. This covers
// both interpreted methods and FCalls (which have native code set but
// still carry a MethodDesc).
// JIT helper portable entry points (no MethodDesc) fall through to
// InvokeCalliStub — the cookie is resolved at runtime from the calli's
// signature token.
targetMethod = PortableEntryPoint::TryGetMethodDesc(calliFunctionPointer);
if (targetMethod != nullptr)
{
targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer);
goto CALL_INTERP_METHOD;
}

if (flags & (int32_t)CalliFlags::DeferredCookie)
{
// pDataItems[calliCookie] = sig token (immutable)
// pDataItems[calliCookie + 1] = cached cookie (initially NULL)
cookie = VolatileLoadWithoutBarrier(&pMethod->pDataItems[calliCookie + 1]);
if (cookie == NULL)
{
mdToken sigToken = (mdToken)(size_t)pMethod->pDataItems[calliCookie];
MethodDesc* pCallerMD = (MethodDesc*)pMethod->methodHnd;
Module* pModule = pCallerMD->GetModule();
PCCOR_SIGNATURE pSig;
ULONG cbSig;
IfFailThrow(pModule->GetMDImport()->GetSigFromToken(sigToken, &cbSig, &pSig));
// nullptr type context is safe here — this path is only reached for
// JIT helper portable entry points which are non-generic.
MetaSig sig(pSig, cbSig, pModule, nullptr);
cookie = GetCookieForCalliSig(sig, nullptr);
VolatileStoreWithoutBarrier(&pMethod->pDataItems[calliCookie + 1], cookie);
}
}
#endif // FEATURE_PORTABLE_ENTRYPOINTS
CalliStubParam param = { calliFunctionPointer, cookie, callArgsAddress, returnValueAddress, pInterpreterFrame->GetContinuationPtr() };
InvokeCalliStub(&param);
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/vm/precode_portable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ MethodDesc* PortableEntryPoint::GetMethodDesc(PCODE addr)
return portableEntryPoint->_pMD;
}

MethodDesc* PortableEntryPoint::TryGetMethodDesc(PCODE addr)
{
LIMITED_METHOD_CONTRACT;

PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr);
return portableEntryPoint->_pMD;
}

void* PortableEntryPoint::GetInterpreterData(PCODE addr)
{
STANDARD_VM_CONTRACT;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/precode_portable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class PortableEntryPoint final
static void* GetActualCode(PCODE addr);
static void SetActualCode(PCODE addr, PCODE actualCode);
static MethodDesc* GetMethodDesc(PCODE addr);
static MethodDesc* TryGetMethodDesc(PCODE addr);
static void* GetInterpreterData(PCODE addr);
static void SetInterpreterData(PCODE addr, PCODE interpreterData);

Expand Down
Loading
Loading