From 9925106a5044b3de0e81fb4e2dc9463b147489a8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 Jun 2026 21:51:24 +0200 Subject: [PATCH 1/9] [wasm] Fix interpreter dispatch of NULL byte code for R2R-compiled methods CallDescrWorkerInternal (the wasm reflection/CallDescr invoke path) resolved the interpreter byte code via MethodDesc::GetInterpreterCode() and, after a DoPrestub retry, passed the pointer straight to ExecuteInterpretedMethodWithArgs. When the target method has no interpreter byte code because it was compiled to native (R2R) code, GetInterpreterCode() returns NULL. On wasm a NULL pointer reads zeroed low linear memory instead of faulting, so the interpreter dispatched the NULL byte code as INTOP_INVALID and aborted with Unimplemented or invalid interpreter opcode plus a secondary stackwalk assert. Add the InvokeManagedMethod fallback (the interpreter->R2R thunk) when there is no interpreter code, mirroring the existing pattern in ExecuteInterpretedMethodWithArgs_PortableEntryPoint_Complex and the CALL_INTERP_METHOD path in InterpExecMethod. Also harden the sibling entry points: ExecuteInterpretedMethodFromUnmanaged now takes the same fallback, and ExecuteInterpretedMethodWithArgs asserts a non-NULL targetIp so future misuse fails loudly at the point of error. --- src/coreclr/vm/prestub.cpp | 15 +++++++++++++++ src/coreclr/vm/wasm/calldescrworkerwasm.cpp | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 4cbd3847c2e266..f87b5b923e3160 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2081,6 +2081,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) { @@ -2229,6 +2235,15 @@ extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* a (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); targetIp = pMD->GetInterpreterCode(); } + if (targetIp == NULL) + { + // No interpreter code: the method was compiled to native (R2R) code. Invoke it as a compiled + // managed method through the interpreter->R2R thunk instead of handing a NULL byte code pointer + // to the interpreter (which would dispatch it as INTOP_INVALID). + ManagedMethodParam param = { pMD, args, ret, (PCODE)NULL, nullptr }; + InvokeManagedMethod(¶m); + return; + } (void)ExecuteInterpretedMethodWithArgs((TADDR)targetIp, args, argSize, ret, callerIp); } #endif // FEATURE_INTERPRETER diff --git a/src/coreclr/vm/wasm/calldescrworkerwasm.cpp b/src/coreclr/vm/wasm/calldescrworkerwasm.cpp index ad7581265efb1a..6a290cfe6b60df 100644 --- a/src/coreclr/vm/wasm/calldescrworkerwasm.cpp +++ b/src/coreclr/vm/wasm/calldescrworkerwasm.cpp @@ -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(¶m); + return; + } + ExecuteInterpretedMethodWithArgs((TADDR)targetIp, args, argsSize, retBuff, (PCODE)&CallDescrWorkerInternal); } From b12ec34473e1e16df97020d8df8ff404291c275d Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 Jun 2026 17:21:56 +0200 Subject: [PATCH 2/9] Route UnmanagedCallersOnly calls to R2R native entrypoint on wasm Address PR #129766 review feedback: resolve R2R-compiled native code for UnmanagedCallersOnly methods directly instead of going through the interpreter reverse-thunk fallback. - GetUnmanagedCallersOnlyThunk (wasm/helpers.cpp): run DoPrestub under GCX_PREEMP to publish code, then return the native (R2R) entrypoint via PortableEntryPoint when present; the g_ReverseThunks fallback is only for interpreted methods. - ExecuteInterpretedMethodFromUnmanaged (prestub.cpp): R2R methods are now dispatched directly and never reach this path, so replace the InvokeManagedMethod fallback with an assert that the interpreter byte code exists. - Widen contracts for the prestub-on-resolve path: EnsureCodeForUnmanagedCallersOnly (precode_portable.cpp) is THROWS/GC_TRIGGERS/MODE_PREEMPTIVE; GetSingleCallableAddrOfCodeForUnmanagedCallersOnly (method.cpp) is GC_TRIGGERS under FEATURE_PORTABLE_ENTRYPOINTS. --- src/coreclr/vm/method.cpp | 6 ++++++ src/coreclr/vm/precode_portable.cpp | 9 ++++++++- src/coreclr/vm/prestub.cpp | 14 +++++--------- src/coreclr/vm/wasm/helpers.cpp | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index a71ea6efd1a0c2..ef576e504dcd49 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2006,7 +2006,13 @@ PCODE MethodDesc::GetSingleCallableAddrOfCodeForUnmanagedCallersOnly() CONTRACTL { THROWS; +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // 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; +#else GC_NOTRIGGER; +#endif MODE_ANY; PRECONDITION(HasUnmanagedCallersOnlyAttribute()); } diff --git a/src/coreclr/vm/precode_portable.cpp b/src/coreclr/vm/precode_portable.cpp index 0ffdaa130a8591..8949233d02876f 100644 --- a/src/coreclr/vm/precode_portable.cpp +++ b/src/coreclr/vm/precode_portable.cpp @@ -174,7 +174,14 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD); bool PortableEntryPoint::EnsureCodeForUnmanagedCallersOnly() { - LIMITED_METHOD_CONTRACT; + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + _ASSERTE(IsValid()); if (HasFlags(_flags, kUnmanagedCallersOnly_Checked | kUnmanagedCallersOnly_Has)) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index f87b5b923e3160..ef9cf13ad14f87 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2235,15 +2235,11 @@ extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* a (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); targetIp = pMD->GetInterpreterCode(); } - if (targetIp == NULL) - { - // No interpreter code: the method was compiled to native (R2R) code. Invoke it as a compiled - // managed method through the interpreter->R2R thunk instead of handing a NULL byte code pointer - // to the interpreter (which would dispatch it as INTOP_INVALID). - ManagedMethodParam param = { pMD, args, ret, (PCODE)NULL, nullptr }; - InvokeManagedMethod(¶m); - return; - } + // 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 diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 09825c48bc174f..d27a1d3d141ca4 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1361,6 +1361,24 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) _ASSERTE(pMD != NULL); _ASSERTE(pMD->HasUnmanagedCallersOnlyAttribute()); + // Prefer R2R-compiled native code. An UnmanagedCallersOnly method compiled by crossgen2 is emitted + // with the native (unmanaged) calling convention, so 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. Run the prestub so that any R2R native code (or, for + // interpreted methods, the interpreter byte code) is published into the portable entrypoint. + GCX_PREEMP(); + (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); + } + if (PortableEntryPoint::HasNativeEntryPoint(entryPoint)) + { + return PortableEntryPoint::GetActualCode(entryPoint); + } + const ReverseThunkMapValue* value = LookupThunk(pMD); if (value == NULL) { From 318b066b217ce35caba552b51ae11e18e37728b8 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 25 Jun 2026 19:08:05 +0200 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Jan Kotas --- src/coreclr/vm/method.cpp | 3 +-- src/coreclr/vm/precode_portable.cpp | 8 +------- src/coreclr/vm/prestub.cpp | 2 +- src/coreclr/vm/wasm/helpers.cpp | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index ef576e504dcd49..f0e26a3446c37e 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2006,14 +2006,13 @@ PCODE MethodDesc::GetSingleCallableAddrOfCodeForUnmanagedCallersOnly() CONTRACTL { THROWS; -#ifdef FEATURE_PORTABLE_ENTRYPOINTS // 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; #else GC_NOTRIGGER; #endif - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(HasUnmanagedCallersOnlyAttribute()); } CONTRACTL_END; diff --git a/src/coreclr/vm/precode_portable.cpp b/src/coreclr/vm/precode_portable.cpp index 8949233d02876f..dc7879ae388f04 100644 --- a/src/coreclr/vm/precode_portable.cpp +++ b/src/coreclr/vm/precode_portable.cpp @@ -174,13 +174,7 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD); bool PortableEntryPoint::EnsureCodeForUnmanagedCallersOnly() { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; + STANDARD_VM_CONTRACT; _ASSERTE(IsValid()); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index ef9cf13ad14f87..d93f4127232de9 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2232,7 +2232,7 @@ extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* a 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 diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index d27a1d3d141ca4..f7fdb44579d22a 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1361,7 +1361,7 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) _ASSERTE(pMD != NULL); _ASSERTE(pMD->HasUnmanagedCallersOnlyAttribute()); - // Prefer R2R-compiled native code. An UnmanagedCallersOnly method compiled by crossgen2 is emitted + // Prefer R2R-compiled native code for UnmanagedCallersOnly methods since it is emitted // with the native (unmanaged) calling convention, so 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 From 1d777c95a98d8cb99805e293b49eeb0cf27bff95 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 25 Jun 2026 19:22:17 +0200 Subject: [PATCH 4/9] fix --- src/coreclr/vm/method.cpp | 3 --- src/coreclr/vm/wasm/helpers.cpp | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index f0e26a3446c37e..dd1e2c07a9ed5c 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2009,9 +2009,6 @@ PCODE MethodDesc::GetSingleCallableAddrOfCodeForUnmanagedCallersOnly() // 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; -#else - GC_NOTRIGGER; -#endif MODE_PREEMPTIVE; PRECONDITION(HasUnmanagedCallersOnlyAttribute()); } diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index f7fdb44579d22a..099f4d4e225fa0 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1371,8 +1371,8 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) { // The method has not been prepared yet. Run the prestub so that any R2R native code (or, for // interpreted methods, the interpreter byte code) is published into the portable entrypoint. - GCX_PREEMP(); - (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); + // STANDARD_VM_CONTRACT already implies preemptive mode, so no GCX_PREEMP is needed here. + (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive); } if (PortableEntryPoint::HasNativeEntryPoint(entryPoint)) { From a5314f9e71ce0cb8abe8a48edbda46358f4d8756 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 26 Jun 2026 00:03:18 +0200 Subject: [PATCH 5/9] lazy --- src/coreclr/vm/method.hpp | 7 +++++++ src/coreclr/vm/prestub.cpp | 26 ++++++++++++++++++++++++++ src/coreclr/vm/wasm/helpers.cpp | 14 ++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 539b464b339147..2022d761d6fee1 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -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); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index d93f4127232de9..1d00c19c84e101 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -502,6 +502,32 @@ PCODE MethodDesc::GetPrecompiledR2RCode(PrepareCodeConfig* pConfig) return pCode; } +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +bool MethodDesc::TryPublishR2RCodeForUnmanagedCallersOnly() +{ + STANDARD_VM_CONTRACT; + _ASSERTE(HasUnmanagedCallersOnlyAttribute()); + +#ifdef FEATURE_READYTORUN + // Only plain IL/NoMetadata/PInvoke methods are eligible for R2R precompilation. + if (!(IsIL() || IsNoMetadata() || (IsPInvoke() && !IsVarArg()))) + return false; + + 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; diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 099f4d4e225fa0..486053898c8d2f 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1369,10 +1369,16 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) PCODE entryPoint = pMD->GetPortableEntryPoint(); if (!PortableEntryPoint::HasNativeEntryPoint(entryPoint) && pMD->GetInterpreterCode() == NULL) { - // The method has not been prepared yet. Run the prestub so that any R2R native code (or, for - // interpreted methods, the interpreter byte code) is published into the portable entrypoint. - // STANDARD_VM_CONTRACT already implies preemptive mode, so no GCX_PREEMP is needed here. - (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive); + // 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. STANDARD_VM_CONTRACT already implies preemptive mode. + if (pMD->TryPublishR2RCodeForUnmanagedCallersOnly()) + { + (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive); + } } if (PortableEntryPoint::HasNativeEntryPoint(entryPoint)) { From a51cda711d19e10e995b737cf57b5a697c6010fa Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 26 Jun 2026 00:07:08 +0200 Subject: [PATCH 6/9] feedback --- src/coreclr/vm/wasm/helpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 486053898c8d2f..80a24794c16d3c 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1374,7 +1374,7 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) // 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. STANDARD_VM_CONTRACT already implies preemptive mode. + // falls back to compiling byte code. if (pMD->TryPublishR2RCodeForUnmanagedCallersOnly()) { (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive); From 2031dfa91f3cbac6af0a8fd23d289f5c0c55afc1 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Thu, 25 Jun 2026 20:48:12 -0700 Subject: [PATCH 7/9] Apply suggestion from @jkotas --- src/coreclr/vm/wasm/helpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 80a24794c16d3c..af1f649e36215f 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1362,7 +1362,7 @@ void* GetUnmanagedCallersOnlyThunk(MethodDesc* pMD) _ASSERTE(pMD->HasUnmanagedCallersOnlyAttribute()); // Prefer R2R-compiled native code for UnmanagedCallersOnly methods since it is emitted - // with the native (unmanaged) calling convention, so its R2R code is itself the directly-callable + // 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. From 6bcced686bfad52487b0bdb33e3793acfd09d0f0 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Thu, 25 Jun 2026 20:54:24 -0700 Subject: [PATCH 8/9] Apply suggestion from @jkotas --- src/coreclr/vm/prestub.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 1d00c19c84e101..35618ffa65edbc 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2257,7 +2257,6 @@ extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* a InterpByteCodeStart* targetIp = pMD->GetInterpreterCode(); if (targetIp == NULL) { - GCX_PREEMP(); (void)pMD->DoPrestub(NULL /* MethodTable */, CallerGCMode::Preemptive); targetIp = pMD->GetInterpreterCode(); } From 3c85aefc3578f2bb16850b54f82c68303f355821 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Thu, 25 Jun 2026 21:04:54 -0700 Subject: [PATCH 9/9] Apply suggestion from @jkotas --- src/coreclr/vm/prestub.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 35618ffa65edbc..cb14f40777cdc7 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -509,10 +509,6 @@ bool MethodDesc::TryPublishR2RCodeForUnmanagedCallersOnly() _ASSERTE(HasUnmanagedCallersOnlyAttribute()); #ifdef FEATURE_READYTORUN - // Only plain IL/NoMetadata/PInvoke methods are eligible for R2R precompilation. - if (!(IsIL() || IsNoMetadata() || (IsPInvoke() && !IsVarArg()))) - return false; - PrepareCodeConfig config(NativeCodeVersion(this), TRUE, TRUE); config.SetCallerGCMode(CallerGCMode::Preemptive);