From 2c75eabb32fa33034331464861c78ae91d4fcbb2 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Tue, 26 Aug 2025 20:01:34 -0700 Subject: [PATCH 01/15] Capture the hidden argument in TheUMEntryPrestubWorker for interpreted reverse p/invoke stubs and ensure that the worker runs on every reverse p/invoke, so that the interpreter can recover the hidden argument and make it available via GetStubContext. Add INTOP_GETSTUBCONTEXT and always generate it for the GetStubContext intrinsic, even if other intrinsics are disabled Address PR feedback Revert most changes --- src/coreclr/interpreter/compiler.cpp | 10 +++++++++- src/coreclr/interpreter/intops.def | 1 + src/coreclr/interpreter/intrinsics.cpp | 2 ++ src/coreclr/vm/dllimportcallback.cpp | 18 +++++++++++++++++- src/coreclr/vm/interpexec.cpp | 4 ++++ src/coreclr/vm/interpexec.h | 1 + src/coreclr/vm/prestub.cpp | 3 +++ 7 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index dbb26066c6a351..20976c42711991 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2306,6 +2306,14 @@ bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HAN AddIns(INTOP_THROW_PNSE); return true; + case NI_System_StubHelpers_GetStubContext: + { + AddIns(INTOP_GETSTUBCONTEXT); + PushStackType(StackTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + return true; + } + case NI_System_Runtime_CompilerServices_StaticsHelpers_VolatileReadAsByref: { CHECK_STACK(1); @@ -2852,7 +2860,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re // intrinsics for the recursive call. Otherwise we will just recurse infinitely and overflow stack. // This expansion can produce value that is inconsistent with the value seen by JIT/R2R code that can // cause user code to misbehave. This is by design. One-off method Interpretation is for internal use only. - bool isMustExpand = (callInfo.hMethod == m_methodHnd); + bool isMustExpand = (callInfo.hMethod == m_methodHnd) || (ni == NI_System_StubHelpers_GetStubContext); if ((InterpConfig.InterpMode() == 3) || isMustExpand) { if (EmitNamedIntrinsicCall(ni, resolvedCallToken.hClass, callInfo.hMethod, callInfo.sig)) diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 839e6057bf0c49..21339f4788344c 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -417,6 +417,7 @@ OPDEF(INTOP_COMPARE_EXCHANGE_I4, "compare.exchange.i4", 5, 1, 3, InterpOpNoArgs) OPDEF(INTOP_COMPARE_EXCHANGE_I8, "compare.exchange.i8", 5, 1, 3, InterpOpNoArgs) OPDEF(INTOP_EXCHANGE_I4, "exchange.i4", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_EXCHANGE_I8, "exchange.i8", 4, 1, 2, InterpOpNoArgs) +OPDEF(INTOP_GETSTUBCONTEXT, "getstubcontext", 2, 1, 0, InterpOpNoArgs) // All instructions after this point are IROPS, instructions that are not emitted/executed OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs) diff --git a/src/coreclr/interpreter/intrinsics.cpp b/src/coreclr/interpreter/intrinsics.cpp index 2d2ba35cccb772..e6ebabd2236670 100644 --- a/src/coreclr/interpreter/intrinsics.cpp +++ b/src/coreclr/interpreter/intrinsics.cpp @@ -44,6 +44,8 @@ NamedIntrinsic GetNamedIntrinsic(COMP_HANDLE compHnd, CORINFO_METHOD_HANDLE comp { if (!strcmp(methodName, "NextCallReturnAddress")) return NI_System_StubHelpers_NextCallReturnAddress; + else if (!strcmp(methodName, "GetStubContext")) + return NI_System_StubHelpers_GetStubContext; } } else if (!strcmp(namespaceName, "System.Numerics")) diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index 58cd290172cbc3..84bea4cd9819ee 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -210,6 +210,17 @@ VOID CallbackOnCollectedDelegate(UMEntryThunkData* pEntryThunkData) EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_FAILFAST, message.GetUnicode()); } +#ifdef FEATURE_INTERPRETER +PLATFORM_THREAD_LOCAL UMEntryThunkData * t_MostRecentUMEntryThunkData; + +UMEntryThunkData * GetMostRecentUMEntryThunkData() +{ + UMEntryThunkData * result = t_MostRecentUMEntryThunkData; + t_MostRecentUMEntryThunkData = nullptr; + return result; +} +#endif + PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) { STATIC_CONTRACT_THROWS; @@ -235,6 +246,11 @@ PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) // exceptions don't leak out into managed code. INSTALL_UNWIND_AND_CONTINUE_HANDLER; +#ifdef FEATURE_INTERPRETER + // HACK: Stash the entry thunk data address so that the interpreter can recover it. + // The InterpreterStub overwrites the register that normally would contain this address. + t_MostRecentUMEntryThunkData = pUMEntryThunkData; +#endif pUMEntryThunkData->RunTimeInit(); UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; @@ -263,7 +279,7 @@ UMEntryThunkData* UMEntryThunkData::CreateUMEntryThunk() LoaderAllocator *pLoaderAllocator = SystemDomain::GetGlobalLoaderAllocator(); AllocMemTracker amTracker; AllocMemTracker *pamTracker = &amTracker; - + pData = (UMEntryThunkData *)pamTracker->Track(pLoaderAllocator->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(UMEntryThunkData)))); UMEntryThunk* pThunk = (UMEntryThunk*)pamTracker->Track(pLoaderAllocator->GetNewStubPrecodeHeap()->AllocStub()); #ifdef FEATURE_PERFMAP diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index bc504bab76272e..a96c90d26123ed 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -610,6 +610,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr MemoryBarrier(); ip++; break; + case INTOP_GETSTUBCONTEXT: + LOCAL_VAR(ip[1], void*) = pFrame->hiddenArgument; + ip += 2; + break; case INTOP_LDC_I4: LOCAL_VAR(ip[1], int32_t) = ip[2]; ip += 3; diff --git a/src/coreclr/vm/interpexec.h b/src/coreclr/vm/interpexec.h index 47991849e16c07..d0dc5d9842a9d0 100644 --- a/src/coreclr/vm/interpexec.h +++ b/src/coreclr/vm/interpexec.h @@ -35,6 +35,7 @@ struct InterpMethodContextFrame int8_t *pRetVal; const int32_t *ip; // This ip is updated only when execution can leave the frame PTR_InterpMethodContextFrame pNext; + void *hiddenArgument; #ifndef DACCESS_COMPILE void ReInit(InterpMethodContextFrame *pParent, InterpByteCodeStart* startIp, int8_t *pRetVal, int8_t *pStack) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 0ef2ed8ff2fb29..a720efa084468b 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2023,6 +2023,8 @@ static InterpThreadContext* GetInterpThreadContext() EXTERN_C void STDCALL ReversePInvokeBadTransition(); +UMEntryThunkData * GetMostRecentUMEntryThunkData(); + extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, void* retBuff) { // Argument registers are in the TransitionBlock @@ -2062,6 +2064,7 @@ extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBl frames.interpMethodContextFrame.startIp = dac_cast(byteCodeAddr); frames.interpMethodContextFrame.pStack = sp; frames.interpMethodContextFrame.pRetVal = (retBuff != NULL) ? (int8_t*)retBuff : sp; + frames.interpMethodContextFrame.hiddenArgument = GetMostRecentUMEntryThunkData(); InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext); From 88e0184cdef5b6efba03d11df73c3167b272fa72 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 27 Aug 2025 14:52:29 -0700 Subject: [PATCH 02/15] Try a new approach --- src/coreclr/vm/dllimportcallback.cpp | 49 ++++++++++++++++++++++------ src/coreclr/vm/dllimportcallback.h | 24 ++++++++++++-- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index 84bea4cd9819ee..ca8a52637b28fd 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -221,6 +221,22 @@ UMEntryThunkData * GetMostRecentUMEntryThunkData() } #endif +static void PatchUMEntryPrecodeIfNecessary(UMEntryThunkData * pUMEntryThunkData, PCODE callTarget) +{ +#ifdef FEATURE_INTERPRETER + // HACK: We currently rely on the thread-local stash in TheUMEntryPrestubWorker for proper operation + // of reverse p/invoke IL stubs in the interpreter, so we can't patch the precode in this scenario. + // Instead, every reverse p/invoke will go through TheUMEntryPrestubWorker. + MethodDesc *pMD = NonVirtualEntry2MethodDesc(callTarget); + if (pMD && pMD->GetInterpreterCode()) + return; +#endif + + // NOTE: This may be run concurrently by multiple threads, but it should be safe since we will just be + // setting the target of the precode to the same thing multiple times in a row. + pUMEntryThunkData->PatchPrecode(); +} + PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) { STATIC_CONTRACT_THROWS; @@ -240,23 +256,36 @@ PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) if (pUMEntryThunkData->IsCollectedDelegate()) CallbackOnCollectedDelegate(pUMEntryThunkData); - INSTALL_MANAGED_EXCEPTION_DISPATCHER; - // this method is called by stubs which are called by managed code, - // so we need an unwind and continue handler so that our internal - // exceptions don't leak out into managed code. - INSTALL_UNWIND_AND_CONTINUE_HANDLER; - + PCODE callTarget = (PCODE)0; #ifdef FEATURE_INTERPRETER // HACK: Stash the entry thunk data address so that the interpreter can recover it. // The InterpreterStub overwrites the register that normally would contain this address. t_MostRecentUMEntryThunkData = pUMEntryThunkData; + + // See whether another thread has already prepared the IL stub for the reverse p/invoke. + callTarget = pUMEntryThunkData->GetCachedCallTarget(); #endif - pUMEntryThunkData->RunTimeInit(); - UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; - UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + // We don't have a pre-prepared reverse p/invoke, and might be racing with another thread. + if (!callTarget) + { + INSTALL_MANAGED_EXCEPTION_DISPATCHER; + // this method is called by stubs which are called by managed code, + // so we need an unwind and continue handler so that our internal + // exceptions don't leak out into managed code. + INSTALL_UNWIND_AND_CONTINUE_HANDLER; + + pUMEntryThunkData->RunTimeInit(); + callTarget = pUMEntryThunkData->GetCachedCallTarget(); + // We're likely to be the first thread to have gotten here for this UM entry thunk, so make + // sure that the precode is patched to point to the IL stub instead of us, if necessary. + PatchUMEntryPrecodeIfNecessary(pUMEntryThunkData, callTarget); + + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + } - return (PCODE)pUMEntryThunkData->GetCode(); + return callTarget; } UMEntryThunkData* UMEntryThunkData::CreateUMEntryThunk() diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index 5341c1420e4d61..2ac98ef6a649ad 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -134,7 +134,7 @@ class UMEntryThunk : private StubPrecode PTR_UMEntryThunkData GetData() const { LIMITED_METHOD_CONTRACT; - + return dac_cast(GetSecretParam()); } }; @@ -148,6 +148,9 @@ class UMEntryThunkData // if m_pObjectHandle is non-NULL, this field is still set to help with diagnostic of call on collected delegate crashes // but it may not have the correct value. PCODE m_pManagedTarget; + // The native code to tailcall. May or may not match what the UM entry precode has been patched to call directly. + // This is populated by RunTimeInit at the end of execution. + PCODE m_pCachedCallTarget; // This is used for debugging and profiling. PTR_MethodDesc m_pMD; @@ -200,6 +203,7 @@ class UMEntryThunkData m_pManagedTarget = pManagedTarget; m_pObjectHandle = pObjectHandle; m_pUMThunkMarshInfo = pUMThunkMarshInfo; + m_pCachedCallTarget = (PCODE)0; m_pMD = pMD; @@ -214,7 +218,20 @@ class UMEntryThunkData void Terminate(); - VOID RunTimeInit() + PCODE GetCachedCallTarget() + { + STANDARD_VM_CONTRACT; + return m_pCachedCallTarget; + } + + void PatchPrecode() + { + STANDARD_VM_CONTRACT; + + m_pUMEntryThunk->SetTargetUnconditional(m_pCachedCallTarget); + } + + void RunTimeInit() { STANDARD_VM_CONTRACT; @@ -227,7 +244,8 @@ class UMEntryThunkData if (m_pObjectHandle == NULL && m_pManagedTarget == (TADDR)0) m_pManagedTarget = m_pMD->GetMultiCallableAddrOfCode(); - m_pUMEntryThunk->SetTargetUnconditional(m_pUMThunkMarshInfo->GetExecStubEntryPoint()); + // FIXME: Do we need to use atomics here? + m_pCachedCallTarget = m_pUMThunkMarshInfo->GetExecStubEntryPoint(); #ifdef _DEBUG m_state = kRunTimeInited; From a1b6d91e7841c49c731feef9e0eec39a1e0ed208 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 11:58:48 -0700 Subject: [PATCH 03/15] Address PR feedback --- src/coreclr/vm/dllimportcallback.cpp | 61 ++++++++-------------------- src/coreclr/vm/dllimportcallback.h | 46 ++++++++++++++------- 2 files changed, 49 insertions(+), 58 deletions(-) diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index ca8a52637b28fd..cfeb43bb17c3fd 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -221,22 +221,6 @@ UMEntryThunkData * GetMostRecentUMEntryThunkData() } #endif -static void PatchUMEntryPrecodeIfNecessary(UMEntryThunkData * pUMEntryThunkData, PCODE callTarget) -{ -#ifdef FEATURE_INTERPRETER - // HACK: We currently rely on the thread-local stash in TheUMEntryPrestubWorker for proper operation - // of reverse p/invoke IL stubs in the interpreter, so we can't patch the precode in this scenario. - // Instead, every reverse p/invoke will go through TheUMEntryPrestubWorker. - MethodDesc *pMD = NonVirtualEntry2MethodDesc(callTarget); - if (pMD && pMD->GetInterpreterCode()) - return; -#endif - - // NOTE: This may be run concurrently by multiple threads, but it should be safe since we will just be - // setting the target of the precode to the same thing multiple times in a row. - pUMEntryThunkData->PatchPrecode(); -} - PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) { STATIC_CONTRACT_THROWS; @@ -249,6 +233,15 @@ PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) CREATETHREAD_IF_NULL_FAILFAST(pThread, W("Failed to setup new thread during reverse P/Invoke")); } +#ifdef FEATURE_INTERPRETER + PCODE pInterpreterTarget = pUMEntryThunkData->GetInterpreterTarget(); + if (pInterpreterTarget != NULL) + { + t_MostRecentUMEntryThunkData = pUMEntryThunkData; + return pInterpreterTarget; + } +#endif // FEATURE_INTERPRETER + // Verify the current thread isn't in COOP mode. if (pThread->PreemptiveGCDisabled()) ReversePInvokeBadTransition(); @@ -256,36 +249,18 @@ PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) if (pUMEntryThunkData->IsCollectedDelegate()) CallbackOnCollectedDelegate(pUMEntryThunkData); - PCODE callTarget = (PCODE)0; -#ifdef FEATURE_INTERPRETER - // HACK: Stash the entry thunk data address so that the interpreter can recover it. - // The InterpreterStub overwrites the register that normally would contain this address. - t_MostRecentUMEntryThunkData = pUMEntryThunkData; + INSTALL_MANAGED_EXCEPTION_DISPATCHER; + // this method is called by stubs which are called by managed code, + // so we need an unwind and continue handler so that our internal + // exceptions don't leak out into managed code. + INSTALL_UNWIND_AND_CONTINUE_HANDLER; - // See whether another thread has already prepared the IL stub for the reverse p/invoke. - callTarget = pUMEntryThunkData->GetCachedCallTarget(); -#endif + pUMEntryThunkData->RunTimeInit(); - // We don't have a pre-prepared reverse p/invoke, and might be racing with another thread. - if (!callTarget) - { - INSTALL_MANAGED_EXCEPTION_DISPATCHER; - // this method is called by stubs which are called by managed code, - // so we need an unwind and continue handler so that our internal - // exceptions don't leak out into managed code. - INSTALL_UNWIND_AND_CONTINUE_HANDLER; - - pUMEntryThunkData->RunTimeInit(); - callTarget = pUMEntryThunkData->GetCachedCallTarget(); - // We're likely to be the first thread to have gotten here for this UM entry thunk, so make - // sure that the precode is patched to point to the IL stub instead of us, if necessary. - PatchUMEntryPrecodeIfNecessary(pUMEntryThunkData, callTarget); - - UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; - UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; - } + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; - return callTarget; + return (PCODE)pUMEntryThunkData->GetCode(); } UMEntryThunkData* UMEntryThunkData::CreateUMEntryThunk() diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index 2ac98ef6a649ad..eb4d6250614956 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -148,9 +148,13 @@ class UMEntryThunkData // if m_pObjectHandle is non-NULL, this field is still set to help with diagnostic of call on collected delegate crashes // but it may not have the correct value. PCODE m_pManagedTarget; - // The native code to tailcall. May or may not match what the UM entry precode has been patched to call directly. - // This is populated by RunTimeInit at the end of execution. - PCODE m_pCachedCallTarget; + +#ifdef FEATURE_INTERPRETER + // InterpreterPrecode to tailcall if the target is interpreted. This allows TheUMEntryPrestubWorker + // stash the hidden argument in a thread static and avoid collision with the hidden argument + // used by InterpreterPrecode. + Volatile m_pIntepretedCallTarget; +#endif // This is used for debugging and profiling. PTR_MethodDesc m_pMD; @@ -203,7 +207,7 @@ class UMEntryThunkData m_pManagedTarget = pManagedTarget; m_pObjectHandle = pObjectHandle; m_pUMThunkMarshInfo = pUMThunkMarshInfo; - m_pCachedCallTarget = (PCODE)0; + m_pIntepretedCallTarget = (PCODE)0; m_pMD = pMD; @@ -218,17 +222,10 @@ class UMEntryThunkData void Terminate(); - PCODE GetCachedCallTarget() - { - STANDARD_VM_CONTRACT; - return m_pCachedCallTarget; - } - - void PatchPrecode() + PCODE GetInterpreterTarget() { STANDARD_VM_CONTRACT; - - m_pUMEntryThunk->SetTargetUnconditional(m_pCachedCallTarget); + return m_pIntepretedCallTarget; } void RunTimeInit() @@ -244,8 +241,27 @@ class UMEntryThunkData if (m_pObjectHandle == NULL && m_pManagedTarget == (TADDR)0) m_pManagedTarget = m_pMD->GetMultiCallableAddrOfCode(); - // FIXME: Do we need to use atomics here? - m_pCachedCallTarget = m_pUMThunkMarshInfo->GetExecStubEntryPoint(); + PCODE entryPoint = m_pUMThunkMarshInfo->GetExecStubEntryPoint(); + +#ifdef FEATURE_INTERPRETER + // For interpreted stubs we need to ensure that TheUMEntryPrestubWorker runs for every + // unmanaged-to-managed invocation in order to populate the TLS variable every time. + auto stubKind = RangeSectionStubManager::GetStubKind(entryPoint); + if (stubKind == STUB_CODE_BLOCK_STUBPRECODE) + { + StubPrecode* pPrecode = Precode::GetPrecodeFromEntryPoint(entryPoint)->AsStubPrecode(); + if (pPrecode->GetType() == PRECODE_INTERPRETER) + { + m_pIntepretedCallTarget = entryPoint; + entryPoint = NULL; + } + } + + if (entryPoint != NULL) +#endif // FEATURE_INTERPRETER + { + m_pUMEntryThunk->SetTargetUnconditional(entryPoint); + } #ifdef _DEBUG m_state = kRunTimeInited; From 7cd9fdeaa7f7ef47af649dc5a8d0b71b37293448 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 11:59:13 -0700 Subject: [PATCH 04/15] Fix typo --- src/coreclr/vm/dllimportcallback.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index eb4d6250614956..282435d4f67bd0 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -153,7 +153,7 @@ class UMEntryThunkData // InterpreterPrecode to tailcall if the target is interpreted. This allows TheUMEntryPrestubWorker // stash the hidden argument in a thread static and avoid collision with the hidden argument // used by InterpreterPrecode. - Volatile m_pIntepretedCallTarget; + Volatile m_pInterpretedCallTarget; #endif // This is used for debugging and profiling. @@ -207,7 +207,7 @@ class UMEntryThunkData m_pManagedTarget = pManagedTarget; m_pObjectHandle = pObjectHandle; m_pUMThunkMarshInfo = pUMThunkMarshInfo; - m_pIntepretedCallTarget = (PCODE)0; + m_pInterpretedCallTarget = (PCODE)0; m_pMD = pMD; @@ -225,7 +225,7 @@ class UMEntryThunkData PCODE GetInterpreterTarget() { STANDARD_VM_CONTRACT; - return m_pIntepretedCallTarget; + return m_pInterpretedCallTarget; } void RunTimeInit() @@ -252,7 +252,7 @@ class UMEntryThunkData StubPrecode* pPrecode = Precode::GetPrecodeFromEntryPoint(entryPoint)->AsStubPrecode(); if (pPrecode->GetType() == PRECODE_INTERPRETER) { - m_pIntepretedCallTarget = entryPoint; + m_pInterpretedCallTarget = entryPoint; entryPoint = NULL; } } From 763a2c9e1f82b7590054faf61c9b44e6117c391e Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 12:07:36 -0700 Subject: [PATCH 05/15] Only populate the hidden argument for InterpMethods that have one --- src/coreclr/interpreter/compiler.cpp | 3 ++- src/coreclr/interpreter/interpretershared.h | 7 ++++++- src/coreclr/vm/prestub.cpp | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 20976c42711991..b4371b397f61d0 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1256,8 +1256,9 @@ InterpMethod* InterpCompiler::CreateInterpMethod() assert(jitFlagsSize == sizeof(corJitFlags)); bool unmanagedCallersOnly = corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE); + bool hasHiddenArgument = corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_PUBLISH_SECRET_PARAM); - InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly); + InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly, hasHiddenArgument); return pMethod; } diff --git a/src/coreclr/interpreter/interpretershared.h b/src/coreclr/interpreter/interpretershared.h index 137077d6add124..dc2fb5bb0b2839 100644 --- a/src/coreclr/interpreter/interpretershared.h +++ b/src/coreclr/interpreter/interpretershared.h @@ -37,8 +37,12 @@ struct InterpMethod CallStubHeader *pCallStub; bool initLocals; bool unmanagedCallersOnly; + bool hasHiddenArgument; - InterpMethod(CORINFO_METHOD_HANDLE methodHnd, int32_t argsSize, int32_t allocaSize, void** pDataItems, bool initLocals, bool unmanagedCallersOnly) + InterpMethod( + CORINFO_METHOD_HANDLE methodHnd, int32_t argsSize, int32_t allocaSize, + void** pDataItems, bool initLocals, bool unmanagedCallersOnly, bool hasHiddenArgument + ) { #if DEBUG this->self = this; @@ -49,6 +53,7 @@ struct InterpMethod this->pDataItems = pDataItems; this->initLocals = initLocals; this->unmanagedCallersOnly = unmanagedCallersOnly; + this->hasHiddenArgument = hasHiddenArgument; pCallStub = NULL; } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index a720efa084468b..e6c68d699b849c 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2064,7 +2064,7 @@ extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBl frames.interpMethodContextFrame.startIp = dac_cast(byteCodeAddr); frames.interpMethodContextFrame.pStack = sp; frames.interpMethodContextFrame.pRetVal = (retBuff != NULL) ? (int8_t*)retBuff : sp; - frames.interpMethodContextFrame.hiddenArgument = GetMostRecentUMEntryThunkData(); + frames.interpMethodContextFrame.hiddenArgument = pInterpreterCode->Method->hasHiddenArgument ? GetMostRecentUMEntryThunkData() : NULL; InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext); From 9cbef655e6ec8f8b7ffa4ea286af75a7d8726d12 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 12:10:10 -0700 Subject: [PATCH 06/15] Address PR feedback --- src/coreclr/vm/dllimportcallback.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index 282435d4f67bd0..18e7e0a062e3ed 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -153,7 +153,7 @@ class UMEntryThunkData // InterpreterPrecode to tailcall if the target is interpreted. This allows TheUMEntryPrestubWorker // stash the hidden argument in a thread static and avoid collision with the hidden argument // used by InterpreterPrecode. - Volatile m_pInterpretedCallTarget; + Volatile m_pInterpretedTarget; #endif // This is used for debugging and profiling. @@ -207,7 +207,7 @@ class UMEntryThunkData m_pManagedTarget = pManagedTarget; m_pObjectHandle = pObjectHandle; m_pUMThunkMarshInfo = pUMThunkMarshInfo; - m_pInterpretedCallTarget = (PCODE)0; + m_pInterpretedTarget = (PCODE)0; m_pMD = pMD; @@ -225,7 +225,7 @@ class UMEntryThunkData PCODE GetInterpreterTarget() { STANDARD_VM_CONTRACT; - return m_pInterpretedCallTarget; + return m_pInterpretedTarget; } void RunTimeInit() @@ -252,7 +252,7 @@ class UMEntryThunkData StubPrecode* pPrecode = Precode::GetPrecodeFromEntryPoint(entryPoint)->AsStubPrecode(); if (pPrecode->GetType() == PRECODE_INTERPRETER) { - m_pInterpretedCallTarget = entryPoint; + m_pInterpretedTarget = entryPoint; entryPoint = NULL; } } From 702bd792af89930fb13469dc236b91fd6515a48a Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 12:12:30 -0700 Subject: [PATCH 07/15] Add missing contract Add missing ifdef --- src/coreclr/vm/dllimportcallback.cpp | 2 ++ src/coreclr/vm/dllimportcallback.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index cfeb43bb17c3fd..e2ffea326808be 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -215,6 +215,8 @@ PLATFORM_THREAD_LOCAL UMEntryThunkData * t_MostRecentUMEntryThunkData; UMEntryThunkData * GetMostRecentUMEntryThunkData() { + LIMITED_METHOD_CONTRACT; + UMEntryThunkData * result = t_MostRecentUMEntryThunkData; t_MostRecentUMEntryThunkData = nullptr; return result; diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index 18e7e0a062e3ed..38420f779728c8 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -222,11 +222,13 @@ class UMEntryThunkData void Terminate(); +#ifdef FEATURE_INTERPRETER PCODE GetInterpreterTarget() { STANDARD_VM_CONTRACT; return m_pInterpretedTarget; } +#endif void RunTimeInit() { From 3cf767b5b71b29c1d5cbccecd3f75ceaf43f0467 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 14:56:03 -0700 Subject: [PATCH 08/15] Fix build on CI --- src/coreclr/vm/dllimportcallback.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index 38420f779728c8..7ddf94ed6c9a1a 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -207,7 +207,9 @@ class UMEntryThunkData m_pManagedTarget = pManagedTarget; m_pObjectHandle = pObjectHandle; m_pUMThunkMarshInfo = pUMThunkMarshInfo; +#ifdef FEATURE_INTERPRETER m_pInterpretedTarget = (PCODE)0; +#endif m_pMD = pMD; From d0d525cc2797c11adc78e8376c30f69113a710ed Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 17:56:04 -0700 Subject: [PATCH 09/15] Fix build on CI --- src/coreclr/vm/dllimportcallback.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index 7ddf94ed6c9a1a..d7631129110105 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -257,11 +257,11 @@ class UMEntryThunkData if (pPrecode->GetType() == PRECODE_INTERPRETER) { m_pInterpretedTarget = entryPoint; - entryPoint = NULL; + entryPoint = (PCODE)0; } } - if (entryPoint != NULL) + if (entryPoint != (PCODE)0) #endif // FEATURE_INTERPRETER { m_pUMEntryThunk->SetTargetUnconditional(entryPoint); From 9f8b6a6d54957c627cff67a80e128a8314756bf6 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 28 Aug 2025 19:50:56 -0700 Subject: [PATCH 10/15] Fix build on CI --- src/coreclr/vm/dllimportcallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index e2ffea326808be..db17f2fc9ca4da 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -237,7 +237,7 @@ PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) #ifdef FEATURE_INTERPRETER PCODE pInterpreterTarget = pUMEntryThunkData->GetInterpreterTarget(); - if (pInterpreterTarget != NULL) + if (pInterpreterTarget != (PCODE)0) { t_MostRecentUMEntryThunkData = pUMEntryThunkData; return pInterpreterTarget; From 44b565986b88272c36b57f90e7661d2a2c6248f9 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 29 Aug 2025 11:25:12 -0700 Subject: [PATCH 11/15] Fix indentation --- src/coreclr/vm/dllimportcallback.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index db17f2fc9ca4da..94fe95775d418b 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -236,12 +236,12 @@ PCODE TheUMEntryPrestubWorker(UMEntryThunkData * pUMEntryThunkData) } #ifdef FEATURE_INTERPRETER - PCODE pInterpreterTarget = pUMEntryThunkData->GetInterpreterTarget(); - if (pInterpreterTarget != (PCODE)0) - { - t_MostRecentUMEntryThunkData = pUMEntryThunkData; - return pInterpreterTarget; - } + PCODE pInterpreterTarget = pUMEntryThunkData->GetInterpreterTarget(); + if (pInterpreterTarget != (PCODE)0) + { + t_MostRecentUMEntryThunkData = pUMEntryThunkData; + return pInterpreterTarget; + } #endif // FEATURE_INTERPRETER // Verify the current thread isn't in COOP mode. From 219101071abcd965bbe84b6a17a566866af9f932 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 29 Aug 2025 21:43:47 -0700 Subject: [PATCH 12/15] Fix incorrect contract for GetInterpreterTarget --- src/coreclr/vm/dllimportcallback.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index d7631129110105..eedf953b0ff394 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -227,7 +227,7 @@ class UMEntryThunkData #ifdef FEATURE_INTERPRETER PCODE GetInterpreterTarget() { - STANDARD_VM_CONTRACT; + LIMITED_METHOD_CONTRACT; return m_pInterpretedTarget; } #endif From 211cb992bd25267ad1f453f737903abdf3ccf0f9 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 1 Sep 2025 17:46:42 -0700 Subject: [PATCH 13/15] Allocate local storage for the hidden argument to interpreter methods on demand instead of always having a field for it on InterpreterFrame --- src/coreclr/interpreter/compiler.cpp | 19 ++++++++++++++++--- src/coreclr/interpreter/compiler.h | 4 ++++ src/coreclr/interpreter/interpretershared.h | 4 +--- src/coreclr/interpreter/intops.def | 2 +- src/coreclr/vm/interpexec.cpp | 6 ++++-- src/coreclr/vm/prestub.cpp | 3 --- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index b4371b397f61d0..ed0d4c8f68caa3 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1256,9 +1256,8 @@ InterpMethod* InterpCompiler::CreateInterpMethod() assert(jitFlagsSize == sizeof(corJitFlags)); bool unmanagedCallersOnly = corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE); - bool hasHiddenArgument = corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_PUBLISH_SECRET_PARAM); - InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly, hasHiddenArgument); + InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly); return pMethod; } @@ -1289,6 +1288,7 @@ InterpCompiler::InterpCompiler(COMP_HANDLE compHnd, CORINFO_METHOD_INFO* methodInfo) : m_stackmapsByClass(FreeInterpreterStackMap) , m_pInitLocalsIns(nullptr) + , m_hiddenArgumentVar(-1) , m_globalVarsWithRefsStackTop(0) { m_genericLookupToDataItemIndex.Init(&m_dataItems, this); @@ -2309,8 +2309,10 @@ bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HAN case NI_System_StubHelpers_GetStubContext: { - AddIns(INTOP_GETSTUBCONTEXT); + assert(m_hiddenArgumentVar); + AddIns(INTOP_MOV_P); PushStackType(StackTypeI, NULL); + m_pLastNewIns->SetSVar(m_hiddenArgumentVar); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); return true; } @@ -3625,6 +3627,17 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) // adding an opcode. AddIns(INTOP_SAFEPOINT); + CORJIT_FLAGS corJitFlags; + DWORD jitFlagsSize = m_compHnd->getJitFlags(&corJitFlags, sizeof(corJitFlags)); + assert(jitFlagsSize == sizeof(corJitFlags)); + + if (corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_PUBLISH_SECRET_PARAM)) + { + m_hiddenArgumentVar = CreateVarExplicit(InterpTypeI, NULL, sizeof(void *)); + AddIns(INTOP_STORESTUBCONTEXT); + m_pLastNewIns->SetDVar(m_hiddenArgumentVar); + } + CorInfoInitClassResult initOnFunctionStart = m_compHnd->initClass(NULL, NULL, METHOD_BEING_COMPILED_CONTEXT()); if ((initOnFunctionStart & CORINFO_INITCLASS_USE_HELPER) == CORINFO_INITCLASS_USE_HELPER) { diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index f880589bc6c653..79845ea7cb5d7b 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -515,6 +515,10 @@ class InterpCompiler int32_t m_currentILOffset; InterpInst* m_pInitLocalsIns; + // If the method has a hidden argument, GenerateCode allocates a var to store it and + // populates the var at method entry + int32_t m_hiddenArgumentVar; + // Table of mappings of leave instructions to the first finally call island the leave // needs to execute. TArray m_leavesTable; diff --git a/src/coreclr/interpreter/interpretershared.h b/src/coreclr/interpreter/interpretershared.h index dc2fb5bb0b2839..74af7e8ab0ee3a 100644 --- a/src/coreclr/interpreter/interpretershared.h +++ b/src/coreclr/interpreter/interpretershared.h @@ -37,11 +37,10 @@ struct InterpMethod CallStubHeader *pCallStub; bool initLocals; bool unmanagedCallersOnly; - bool hasHiddenArgument; InterpMethod( CORINFO_METHOD_HANDLE methodHnd, int32_t argsSize, int32_t allocaSize, - void** pDataItems, bool initLocals, bool unmanagedCallersOnly, bool hasHiddenArgument + void** pDataItems, bool initLocals, bool unmanagedCallersOnly ) { #if DEBUG @@ -53,7 +52,6 @@ struct InterpMethod this->pDataItems = pDataItems; this->initLocals = initLocals; this->unmanagedCallersOnly = unmanagedCallersOnly; - this->hasHiddenArgument = hasHiddenArgument; pCallStub = NULL; } diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 21339f4788344c..3a6b9754d8b46e 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -417,7 +417,7 @@ OPDEF(INTOP_COMPARE_EXCHANGE_I4, "compare.exchange.i4", 5, 1, 3, InterpOpNoArgs) OPDEF(INTOP_COMPARE_EXCHANGE_I8, "compare.exchange.i8", 5, 1, 3, InterpOpNoArgs) OPDEF(INTOP_EXCHANGE_I4, "exchange.i4", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_EXCHANGE_I8, "exchange.i8", 4, 1, 2, InterpOpNoArgs) -OPDEF(INTOP_GETSTUBCONTEXT, "getstubcontext", 2, 1, 0, InterpOpNoArgs) +OPDEF(INTOP_STORESTUBCONTEXT, "storestubcontext", 2, 1, 0, InterpOpNoArgs) // All instructions after this point are IROPS, instructions that are not emitted/executed OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index a96c90d26123ed..883b5327751085 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -530,6 +530,8 @@ void PrepareInitialCode(MethodDesc *pMD) PAL_ENDTRY } +UMEntryThunkData * GetMostRecentUMEntryThunkData(); + void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext, ExceptionClauseArgs *pExceptionClauseArgs) { CONTRACTL @@ -610,8 +612,8 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr MemoryBarrier(); ip++; break; - case INTOP_GETSTUBCONTEXT: - LOCAL_VAR(ip[1], void*) = pFrame->hiddenArgument; + case INTOP_STORESTUBCONTEXT: + LOCAL_VAR(ip[1], void*) = GetMostRecentUMEntryThunkData(); ip += 2; break; case INTOP_LDC_I4: diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index e6c68d699b849c..0ef2ed8ff2fb29 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2023,8 +2023,6 @@ static InterpThreadContext* GetInterpThreadContext() EXTERN_C void STDCALL ReversePInvokeBadTransition(); -UMEntryThunkData * GetMostRecentUMEntryThunkData(); - extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, void* retBuff) { // Argument registers are in the TransitionBlock @@ -2064,7 +2062,6 @@ extern "C" void* STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBl frames.interpMethodContextFrame.startIp = dac_cast(byteCodeAddr); frames.interpMethodContextFrame.pStack = sp; frames.interpMethodContextFrame.pRetVal = (retBuff != NULL) ? (int8_t*)retBuff : sp; - frames.interpMethodContextFrame.hiddenArgument = pInterpreterCode->Method->hasHiddenArgument ? GetMostRecentUMEntryThunkData() : NULL; InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext); From bb06723fba08ff0105fd0080e245a901450ab536 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 1 Sep 2025 17:47:37 -0700 Subject: [PATCH 14/15] Fix assert --- src/coreclr/interpreter/compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index ed0d4c8f68caa3..ad2df68abd405e 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2309,7 +2309,7 @@ bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HAN case NI_System_StubHelpers_GetStubContext: { - assert(m_hiddenArgumentVar); + assert(m_hiddenArgumentVar >= 0); AddIns(INTOP_MOV_P); PushStackType(StackTypeI, NULL); m_pLastNewIns->SetSVar(m_hiddenArgumentVar); From e7fa85e49188580e0432538248096c990998e777 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 1 Sep 2025 17:54:59 -0700 Subject: [PATCH 15/15] Add missing file --- src/coreclr/vm/interpexec.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/interpexec.h b/src/coreclr/vm/interpexec.h index d0dc5d9842a9d0..47991849e16c07 100644 --- a/src/coreclr/vm/interpexec.h +++ b/src/coreclr/vm/interpexec.h @@ -35,7 +35,6 @@ struct InterpMethodContextFrame int8_t *pRetVal; const int32_t *ip; // This ip is updated only when execution can leave the frame PTR_InterpMethodContextFrame pNext; - void *hiddenArgument; #ifndef DACCESS_COMPILE void ReInit(InterpMethodContextFrame *pParent, InterpByteCodeStart* startIp, int8_t *pRetVal, int8_t *pStack)