From 15018bdb2c31c6d70756b273e8571cee93464d71 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 18 Aug 2026 15:46:43 +0200 Subject: [PATCH 1/4] Cleanup GC mode switching in the interpreter The interpreter uses GCX_COOP_NO_DTOR / GCX_PREEMP_NO_DTOR when calling compiled methods with SEH wrapper / unmanaged methods. Due to that, it needed to have forceful restoration of cooperative mode in the catch for ResumeAfterCatchException. This change switches those usages to GCX_COOP() / GCX_PREEMP() instead. That removes the need to switch the GC mode explicitly in that catch. So I've replaced it by assert. --- src/coreclr/vm/interpexec.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index ae2b5d51edc5e1..ad06c658df8b7f 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -232,6 +232,14 @@ LONG IgnoreCppExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) : EXCEPTION_EXECUTE_HANDLER; } +void RethrowLastThrownObject() +{ + _ASSERTE(GetThread()->PreemptiveGCDisabled()); + GCX_COOP(); + OBJECTREF ohThrowable = GetThread()->LastThrownObject(); + DispatchManagedException(ohThrowable); +} + template std::invoke_result_t CallWithSEHWrapper(Function function) { @@ -253,9 +261,7 @@ std::invoke_result_t CallWithSEHWrapper(Function function) // INSTALL_/UNINSTALL_UNWIND_AND_CONTINUE_HANDLER in the InterpExecMethod. // The managed ones are represented by SEH exception, which cannot be handled there // because it is not possible to handle both SEH and C++ exceptions in the same frame. - GCX_COOP_NO_DTOR(); - OBJECTREF ohThrowable = GetThread()->LastThrownObject(); - DispatchManagedException(ohThrowable); + RethrowLastThrownObject(); } PAL_ENDTRY @@ -285,10 +291,8 @@ void InvokeUnmanagedMethodWithTransition(MethodDesc *targetMethod, int8_t *stack PAL_TRY(Param *, pParam, ¶m) { - GCX_PREEMP_NO_DTOR(); // WASM-TODO: Handle unmanaged calling conventions - InvokeManagedMethod(pParam->targetMethod, pParam->pArgs, pParam->pRet, pParam->callTarget, NULL); - GCX_PREEMP_NO_DTOR_END(); + InvokeUnmanagedMethod(pParam->targetMethod, pParam->pArgs, pParam->pRet, pParam->callTarget); } PAL_EXCEPT_FILTER(IgnoreCppExceptionFilter) { @@ -298,9 +302,7 @@ void InvokeUnmanagedMethodWithTransition(MethodDesc *targetMethod, int8_t *stack // INSTALL_/UNINSTALL_UNWIND_AND_CONTINUE_HANDLER in the InterpExecMethod. // The managed ones are represented by SEH exception, which cannot be handled there // because it is not possible to handle both SEH and C++ exceptions in the same frame. - GCX_COOP_NO_DTOR(); - OBJECTREF ohThrowable = GetThread()->LastThrownObject(); - DispatchManagedException(ohThrowable); + RethrowLastThrownObject(); } PAL_ENDTRY @@ -474,6 +476,9 @@ void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE tar void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) { + WRAPPER_NO_CONTRACT; + + GCX_PREEMP(); InvokeManagedMethod(targetMethod, pArgs, pRet, callTarget, NULL); } @@ -4809,7 +4814,7 @@ do \ } catch (const ResumeAfterCatchException& ex) { - GCX_COOP_NO_DTOR(); + _ASSERTE(GetThread()->PreemptiveGCDisabled()); ex.GetResumeContext(&resumeSP, &resumeIP); _ASSERTE(resumeSP != 0 && resumeIP != 0); From d497a831ff09d19ee47c1091b8d2b629e519737d Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 18 Aug 2026 16:10:11 +0200 Subject: [PATCH 2/4] Make RethrowLastThrownObject NOINLINE static Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index ad06c658df8b7f..160d042619f8c0 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -232,8 +232,10 @@ LONG IgnoreCppExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) : EXCEPTION_EXECUTE_HANDLER; } -void RethrowLastThrownObject() +NOINLINE static void DECLSPEC_NORETURN RethrowLastThrownObject() { + WRAPPER_NO_CONTRACT; + _ASSERTE(GetThread()->PreemptiveGCDisabled()); GCX_COOP(); OBJECTREF ohThrowable = GetThread()->LastThrownObject(); From a4807f7655ba22d6ceaa43779185bcdfac6b169d Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 18 Aug 2026 17:39:19 +0200 Subject: [PATCH 3/4] Fix copilot feedback --- src/coreclr/vm/interpexec.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 160d042619f8c0..736a392f1d7e48 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -218,6 +218,7 @@ static size_t CreateDispatchTokenForMethod(MethodDesc* pMD) // Call invoker helpers provided by platform. void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target, Object** pContinuationRet); void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget); +void InvokeUnmanagedMethodInPreemptiveMode(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget); void InvokeCalliStub(PCODE ftn, InterpreterCalliCookie cookie, int8_t *pArgs, int8_t *pRet, Object** pContinuationRet); void InvokeUnmanagedCalli(PCODE ftn, InterpreterCalliCookie cookie, int8_t *pArgs, int8_t *pRet); void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target, Object** pContinuationRet); @@ -236,7 +237,6 @@ NOINLINE static void DECLSPEC_NORETURN RethrowLastThrownObject() { WRAPPER_NO_CONTRACT; - _ASSERTE(GetThread()->PreemptiveGCDisabled()); GCX_COOP(); OBJECTREF ohThrowable = GetThread()->LastThrownObject(); DispatchManagedException(ohThrowable); @@ -294,7 +294,7 @@ void InvokeUnmanagedMethodWithTransition(MethodDesc *targetMethod, int8_t *stack PAL_TRY(Param *, pParam, ¶m) { // WASM-TODO: Handle unmanaged calling conventions - InvokeUnmanagedMethod(pParam->targetMethod, pParam->pArgs, pParam->pRet, pParam->callTarget); + InvokeUnmanagedMethodInPreemptiveMode(pParam->targetMethod, pParam->pArgs, pParam->pRet, pParam->callTarget); } PAL_EXCEPT_FILTER(IgnoreCppExceptionFilter) { @@ -477,11 +477,16 @@ void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE tar } void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) +{ + InvokeManagedMethod(targetMethod, pArgs, pRet, callTarget, NULL); +} + +void InvokeUnmanagedMethodInPreemptiveMode(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) { WRAPPER_NO_CONTRACT; GCX_PREEMP(); - InvokeManagedMethod(targetMethod, pArgs, pRet, callTarget, NULL); + InvokeUnmanagedMethod(targetMethod, pArgs, pRet, callTarget); } static NOINLINE CallStubHeader *InvokeDelegateInvokeMethodHelper(MethodDesc *pMDDelegateInvoke) From c9a98549d9a3470df407fc1001491db65733bd72 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 18 Aug 2026 19:21:13 +0200 Subject: [PATCH 4/4] Fix WASM build --- src/coreclr/vm/interpexec.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 736a392f1d7e48..9e6f1d9a5a810a 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -481,14 +481,6 @@ void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet InvokeManagedMethod(targetMethod, pArgs, pRet, callTarget, NULL); } -void InvokeUnmanagedMethodInPreemptiveMode(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) -{ - WRAPPER_NO_CONTRACT; - - GCX_PREEMP(); - InvokeUnmanagedMethod(targetMethod, pArgs, pRet, callTarget); -} - static NOINLINE CallStubHeader *InvokeDelegateInvokeMethodHelper(MethodDesc *pMDDelegateInvoke) { CONTRACTL @@ -628,6 +620,14 @@ CallStubHeader *CreateNativeToInterpreterCallStub(InterpMethod* pInterpMethod) } #endif // !TARGET_WASM +void InvokeUnmanagedMethodInPreemptiveMode(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget) +{ + WRAPPER_NO_CONTRACT; + + GCX_PREEMP(); + InvokeUnmanagedMethod(targetMethod, pArgs, pRet, callTarget); +} + #ifdef _DEBUG void DBG_PrintInterpreterStack() {