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
6 changes: 4 additions & 2 deletions src/coreclr/vm/method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2006,8 +2006,10 @@ PCODE MethodDesc::GetSingleCallableAddrOfCodeForUnmanagedCallersOnly()
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
// On portable entrypoint platforms resolving the entrypoint may need to run the prestub
// (e.g. to publish R2R native code for the method), which can trigger a GC.
GC_TRIGGERS;
MODE_PREEMPTIVE;
PRECONDITION(HasUnmanagedCallersOnlyAttribute());
}
CONTRACTL_END;
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/vm/method.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2298,6 +2298,13 @@ class MethodDesc
PCODE PrepareInitialCode(CallerGCMode callerGCMode = CallerGCMode::Unknown);
PCODE PrepareCode(PrepareCodeConfig* pConfig);

#ifdef FEATURE_PORTABLE_ENTRYPOINTS
// Probe for precompiled R2R native code for an UnmanagedCallersOnly method and, if present,
// publish it into this method's portable entrypoint WITHOUT compiling interpreter byte code.
// Returns true if native code was found and published, false otherwise.
bool TryPublishR2RCodeForUnmanagedCallersOnly();
#endif // FEATURE_PORTABLE_ENTRYPOINTS

private:
PCODE GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldTier);
PCODE GetPrecompiledR2RCode(PrepareCodeConfig* pConfig);
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/vm/precode_portable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD);

bool PortableEntryPoint::EnsureCodeForUnmanagedCallersOnly()
{
LIMITED_METHOD_CONTRACT;
STANDARD_VM_CONTRACT;

_ASSERTE(IsValid());

if (HasFlags(_flags, kUnmanagedCallersOnly_Checked | kUnmanagedCallersOnly_Has))
Expand Down
36 changes: 34 additions & 2 deletions src/coreclr/vm/prestub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,28 @@ PCODE MethodDesc::GetPrecompiledR2RCode(PrepareCodeConfig* pConfig)
return pCode;
}

#ifdef FEATURE_PORTABLE_ENTRYPOINTS
bool MethodDesc::TryPublishR2RCodeForUnmanagedCallersOnly()
{
STANDARD_VM_CONTRACT;
_ASSERTE(HasUnmanagedCallersOnlyAttribute());

#ifdef FEATURE_READYTORUN
PrepareCodeConfig config(NativeCodeVersion(this), TRUE, TRUE);
config.SetCallerGCMode(CallerGCMode::Preemptive);

// GetPrecompiledR2RCode resolves the R2R entrypoint and, on portable-entrypoint (wasm) targets,
// publishes it into this method's portable entrypoint as a side effect (PortableEntryPoint::SetActualCode).
// It never compiles interpreter byte code, so purely interpreted methods simply return NULL here and
// are left unprepared for lazy byte code generation on first call.
PCODE pCode = GetPrecompiledR2RCode(&config);
return pCode != (PCODE)NULL;
#else // !FEATURE_READYTORUN
return false;
#endif // FEATURE_READYTORUN
}
#endif // FEATURE_PORTABLE_ENTRYPOINTS

PCODE MethodDesc::GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier0)
{
STANDARD_VM_CONTRACT;
Expand Down Expand Up @@ -2081,6 +2103,12 @@ extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBl

void ExecuteInterpretedMethodWithArgs(TADDR targetIp, int8_t* args, size_t argSize, void* retBuff, PCODE callerIp)
{
// targetIp must point to valid interpreter byte code. A NULL here means a caller failed to route a
// method that has native (R2R) code but no interpreter code to InvokeManagedMethod. Dispatching a
// NULL byte code pointer would be (mis)interpreted as INTOP_INVALID and fail with a cryptic fatal
// error, so assert here at the actual point of misuse.
_ASSERTE(targetIp != (TADDR)NULL);

// Copy arguments to the stack
if (argSize > 0)
{
Expand Down Expand Up @@ -2225,10 +2253,14 @@ extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* a
InterpByteCodeStart* targetIp = pMD->GetInterpreterCode();
if (targetIp == NULL)
{
GCX_PREEMP();
(void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop);
(void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive);
targetIp = pMD->GetInterpreterCode();
}
// The g_ReverseThunks reverse thunk is only used for UnmanagedCallersOnly methods that are executed
// by the interpreter. Methods compiled to native (R2R) code are dispatched directly to their R2R
// entrypoint by GetUnmanagedCallersOnlyThunk and never reach this path, so the interpreter byte code
// must exist here.
_ASSERTE(targetIp != NULL);
(void)ExecuteInterpretedMethodWithArgs((TADDR)targetIp, args, argSize, ret, callerIp);
}
#endif // FEATURE_INTERPRETER
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/vm/wasm/calldescrworkerwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,17 @@ extern "C" void STDCALL CallDescrWorkerInternal(CallDescrData* pCallDescrData)
retBuff = &pCallDescrData->returnValue;
}

if (targetIp == NULL)
{
// The target method has no interpreter code because it was compiled to native (R2R) code.
// Invoke it as a compiled managed method through the interpreter->R2R thunk, mirroring the
// fallback already present in ExecuteInterpretedMethodWithArgs_PortableEntryPoint_Complex and
// the CALL_INTERP_METHOD path in InterpExecMethod. Without this, the NULL bytecode pointer
// would be handed to the interpreter and dispatched as INTOP_INVALID.
ManagedMethodParam param = { pMethod, args, (int8_t*)retBuff, (PCODE)NULL, nullptr };
InvokeManagedMethod(&param);
return;
Comment on lines +41 to +50
Comment on lines +48 to +50
}

ExecuteInterpretedMethodWithArgs((TADDR)targetIp, args, argsSize, retBuff, (PCODE)&CallDescrWorkerInternal);
}
24 changes: 24 additions & 0 deletions src/coreclr/vm/wasm/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,30 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD)
_ASSERTE(pMD != NULL);
_ASSERTE(pMD->HasUnmanagedCallersOnlyAttribute());

// Prefer R2R-compiled native code for UnmanagedCallersOnly methods since it is emitted
// with the native (unmanaged) calling convention and its R2R code is itself the directly-callable
// unmanaged entrypoint. Resolve the method's code first and, if it has native (R2R) code, return that
// entrypoint directly. The g_ReverseThunks interpreter fallback below is only required for methods
// that are executed by the interpreter (no native code), and is intentionally unused for R2R methods.
PCODE entryPoint = pMD->GetPortableEntryPoint();
if (!PortableEntryPoint::HasNativeEntryPoint(entryPoint) && pMD->GetInterpreterCode() == NULL)
{
// The method has not been prepared yet. Probe for precompiled R2R native code and, if present,
// publish it into the portable entrypoint WITHOUT compiling interpreter byte code. Purely
// interpreted methods are intentionally left unprepared here so that their byte code is generated
// lazily on first call through the reverse thunk below (better for startup). For R2R methods we
// run the prestub to perform the canonical full preparation; it finds the R2R code first and never
// falls back to compiling byte code.
if (pMD->TryPublishR2RCodeForUnmanagedCallersOnly())
{
(void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive);
}
Comment on lines +1378 to +1381

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (pMD->TryPublishR2RCodeForUnmanagedCallersOnly())
{
(void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive);
}
pMD->TryPublishR2RCodeForUnmanagedCallersOnly();

Is DoPrestub actually going to do anything useful? I expect ShouldCallPrestub() is going to return false here

if (!ShouldCallPrestub())
and we take early out.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Jan, this likely does nothing.

}
if (PortableEntryPoint::HasNativeEntryPoint(entryPoint))
{
return PortableEntryPoint::GetActualCode(entryPoint);
}

const ReverseThunkMapValue* value = LookupThunk(pMD);
if (value == NULL)
{
Expand Down
Loading