From 3dc63e3c3123b69cc42276c59063b30612878a0b Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 25 Sep 2026 23:05:33 -0500 Subject: [PATCH 1/9] [wasm][interpreter] Resolve open virtual delegates invoked through the shuffle thunk When compiled (R2R) code invokes an open virtual delegate on portable entry point platforms, it calls the IL delegate shuffle thunk, which does a calli on _methodPtrAux. For open virtual delegates that is CID_VirtualOpenDelegateDispatch, which is not a portable entry point, so treating it as one read a garbage MethodDesc and trapped in PrepareInterpreterCode. Resolve the virtual target in INTOP_CALLI the same way INTOP_CALLDELEGATE does. Re-enable System.Linq.Expressions.Tests on the browser CoreCLR R2R lane. Fixes #134261 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 25 +++++++++++++++++++++++++ src/libraries/tests.proj | 2 -- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 70d5928cb5820e..150f202a18d4c8 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3386,6 +3386,31 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr goto CALL_INTERP_METHOD; } #endif // !FEATURE_PORTABLE_ENTRYPOINTS +#if defined(FEATURE_PORTABLE_ENTRYPOINTS) && defined(FEATURE_CACHED_INTERFACE_DISPATCH) + else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch) + { + // An open virtual delegate invoked from compiled code reaches the delegate shuffle thunk, which + // calls through _methodPtrAux. CID_VirtualOpenDelegateDispatch is not a portable entry point, + // so resolve the virtual target here, as INTOP_CALLDELEGATE does. The shuffle thunk's 'this' + // (the delegate) is its first argument, and the target's 'this' is the first call argument. + DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF); + _ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk()); + _ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer); + targetMethod = COMDelegate::GetMethodDescForOpenVirtualDelegate(delegateObj); + OBJECTREF *pThisArg = (OBJECTREF*)callArgsAddress; + NULL_CHECK(*pThisArg); + targetMethod = CallWithSEHWrapper( + [&targetMethod, &pThisArg]() { + MethodTable* pMT = (*pThisArg)->GetMethodTable(); + MethodDesc *pTarget; + GCX_PREEMP_REGION_BEGIN(); + pTarget = targetMethod->GetMethodDescOfVirtualizedCode(pThisArg, pMT, targetMethod->GetMethodTable()); + GCX_PREEMP_REGION_END(); + return pTarget; + }); + goto CALL_INTERP_METHOD; + } +#endif // FEATURE_PORTABLE_ENTRYPOINTS && FEATURE_CACHED_INTERFACE_DISPATCH else { Object** pCalliContinuationRet = pInterpreterFrame->GetContinuationPtr(); diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index af586522303251..7bac0a358e42ca 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -90,8 +90,6 @@ - - From a71ee30d05589011b167dbfc5f7d053ec563e370 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 25 Sep 2026 23:39:11 -0500 Subject: [PATCH 2/9] Share open virtual delegate resolution and add a focused test Extract ResolveOpenVirtualDelegateTarget for INTOP_CALLDELEGATE and INTOP_CALLI, use #else for the portable entry point branch, and add a DelegateTests case that invokes open virtual delegates (class override, boxed struct, enum, interface). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 48 ++++++++----------- .../System/DelegateTests.cs | 21 ++++++++ 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 150f202a18d4c8..9a465abe0f395e 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -1358,6 +1358,21 @@ static void ShiftDelegateCallArgs(int8_t* stack, int32_t callArgsOffset, int32_t } } +// Resolves the target of an open virtual delegate for the given 'this' argument. +static MethodDesc* ResolveOpenVirtualDelegateTarget(DELEGATEREF delegateObj, OBJECTREF* pThisArg) +{ + MethodDesc* pDeclMD = COMDelegate::GetMethodDescForOpenVirtualDelegate(delegateObj); + return CallWithSEHWrapper( + [pDeclMD, pThisArg]() { + MethodTable* pMT = (*pThisArg)->GetMethodTable(); + MethodDesc* pTarget; + GCX_PREEMP_REGION_BEGIN(); + pTarget = pDeclMD->GetMethodDescOfVirtualizedCode(pThisArg, pMT, pDeclMD->GetMethodTable()); + GCX_PREEMP_REGION_END(); + return pTarget; + }); +} + static void UpdateFrameForTailCall(InterpMethodContextFrame *pFrame, PTR_InterpByteCodeStart targetIp, int8_t *callArgsAddress) { InterpMethod *pTargetMethod = targetIp->Method; @@ -3385,32 +3400,20 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { goto CALL_INTERP_METHOD; } -#endif // !FEATURE_PORTABLE_ENTRYPOINTS -#if defined(FEATURE_PORTABLE_ENTRYPOINTS) && defined(FEATURE_CACHED_INTERFACE_DISPATCH) +#else // !FEATURE_PORTABLE_ENTRYPOINTS else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch) { - // An open virtual delegate invoked from compiled code reaches the delegate shuffle thunk, which - // calls through _methodPtrAux. CID_VirtualOpenDelegateDispatch is not a portable entry point, - // so resolve the virtual target here, as INTOP_CALLDELEGATE does. The shuffle thunk's 'this' - // (the delegate) is its first argument, and the target's 'this' is the first call argument. + // _methodPtrAux of an open virtual delegate is not a portable entry point; resolve the target + // as INTOP_CALLDELEGATE does. The shuffle thunk's 'this' is the delegate. DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF); _ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk()); _ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer); - targetMethod = COMDelegate::GetMethodDescForOpenVirtualDelegate(delegateObj); OBJECTREF *pThisArg = (OBJECTREF*)callArgsAddress; NULL_CHECK(*pThisArg); - targetMethod = CallWithSEHWrapper( - [&targetMethod, &pThisArg]() { - MethodTable* pMT = (*pThisArg)->GetMethodTable(); - MethodDesc *pTarget; - GCX_PREEMP_REGION_BEGIN(); - pTarget = targetMethod->GetMethodDescOfVirtualizedCode(pThisArg, pMT, targetMethod->GetMethodTable()); - GCX_PREEMP_REGION_END(); - return pTarget; - }); + targetMethod = ResolveOpenVirtualDelegateTarget(delegateObj, pThisArg); goto CALL_INTERP_METHOD; } -#endif // FEATURE_PORTABLE_ENTRYPOINTS && FEATURE_CACHED_INTERFACE_DISPATCH +#endif // !FEATURE_PORTABLE_ENTRYPOINTS else { Object** pCalliContinuationRet = pInterpreterFrame->GetContinuationPtr(); @@ -3525,18 +3528,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr if (isOpenVirtual) { - targetMethod = COMDelegate::GetMethodDescForOpenVirtualDelegate(*delegateObj); OBJECTREF *pThisArg = LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, OBJECTREF); NULL_CHECK(*pThisArg); - targetMethod = CallWithSEHWrapper( - [&targetMethod, &pThisArg]() { - MethodTable* pMT = (*pThisArg)->GetMethodTable(); - MethodDesc *pTarget; - GCX_PREEMP_REGION_BEGIN(); - pTarget = targetMethod->GetMethodDescOfVirtualizedCode(pThisArg, pMT, targetMethod->GetMethodTable()); - GCX_PREEMP_REGION_END(); - return pTarget; - }); + targetMethod = ResolveOpenVirtualDelegateTarget(*delegateObj, pThisArg); } else { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs index e145ac41227eac..2aa38785a3e4bd 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs @@ -553,6 +553,19 @@ public static void DifferentOpenVirtualDelegates() Assert.Equal(m2, b.Method); } + [Fact] + public static void OpenVirtualDelegates_InvokeResolvesOverride() + { + Func toString = typeof(object).GetMethod(nameof(object.ToString)).CreateDelegate>(); + Assert.Equal(nameof(OpenVirtualDerived), toString(new OpenVirtualDerived())); + Assert.Equal(typeof(Struct).ToString(), toString(new Struct())); + Assert.Equal(nameof(DayOfWeek.Monday), toString(DayOfWeek.Monday)); + + Func interfaceMethod = typeof(IOpenVirtual).GetMethod(nameof(IOpenVirtual.M)).CreateDelegate>(); + Assert.Equal(1, interfaceMethod(new OpenVirtualDerived())); + Assert.Equal(2, interfaceMethod(new OpenVirtualStruct())); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsTypeEquivalenceSupported))] public static void TypeEquivalentDelegatesPointingToSameMethod_AreEqualAndHaveSameHashCode() { @@ -588,6 +601,14 @@ internal virtual void M1() { } internal virtual void M2() { } } + interface IOpenVirtual { int M(); } + class OpenVirtualDerived : IOpenVirtual + { + public int M() => 1; + public override string ToString() => nameof(OpenVirtualDerived); + } + struct OpenVirtualStruct : IOpenVirtual { public int M() => 2; } + class Base { public virtual void M() { } } class Derived : Base { public override void M() { } } From e7a50b10e8d13638aa344252512ac706f640b591 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 26 Sep 2026 00:05:44 -0500 Subject: [PATCH 3/9] Clarify #else comment for the portable entry point branch Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 9a465abe0f395e..132e69df2ca16a 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3400,7 +3400,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { goto CALL_INTERP_METHOD; } -#else // !FEATURE_PORTABLE_ENTRYPOINTS +#else // FEATURE_PORTABLE_ENTRYPOINTS else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch) { // _methodPtrAux of an open virtual delegate is not a portable entry point; resolve the target From 9665c7d75e0dadfafbb1c89c330c10a9fe14de97 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 26 Sep 2026 09:00:49 -0500 Subject: [PATCH 4/9] Split interface open-virtual delegate test and disable it on browser Mono AOT Mono AOT on browser crashes invoking an open-instance interface delegate (dotnet/runtime#134707). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../tests/System.Runtime.Tests/System/DelegateTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs index 2aa38785a3e4bd..4275fd0f8becaa 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs @@ -560,7 +560,12 @@ public static void OpenVirtualDelegates_InvokeResolvesOverride() Assert.Equal(nameof(OpenVirtualDerived), toString(new OpenVirtualDerived())); Assert.Equal(typeof(Struct).ToString(), toString(new Struct())); Assert.Equal(nameof(DayOfWeek.Monday), toString(DayOfWeek.Monday)); + } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134707", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + public static void OpenVirtualDelegates_InterfaceMethod_InvokeResolvesImplementation() + { Func interfaceMethod = typeof(IOpenVirtual).GetMethod(nameof(IOpenVirtual.M)).CreateDelegate>(); Assert.Equal(1, interfaceMethod(new OpenVirtualDerived())); Assert.Equal(2, interfaceMethod(new OpenVirtualStruct())); From dcc90cf4a74ed3c14e251aa61ebf3ba27accf4c0 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 26 Sep 2026 12:34:05 -0500 Subject: [PATCH 5/9] Resolve open virtual delegates in INTOP_CALLI on all cached-interface-dispatch platforms CID_VirtualOpenDelegateDispatch needs the delegate (on arm64 it reads &_methodPtrAux from x11), so interpreted shuffle thunks cannot invoke it through calli. On maccatalyst/iOS/tvOS the interpreter called the native stub without x11 set and crashed in CID_VirtualOpenDelegateDispatchWorker (found by the new DelegateTests on maccatalyst-arm64 CoreCLR). Apply the INTOP_CALLI resolution under FEATURE_CACHED_INTERFACE_DISPATCH instead of only FEATURE_PORTABLE_ENTRYPOINTS. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 132e69df2ca16a..7e798cffdada61 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3391,20 +3391,12 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr InvokeUnmanagedCalliWithTransition(calliFunctionPointer, cookie, stack, pFrame, callArgsAddress, returnValueAddress); } } -#ifndef FEATURE_PORTABLE_ENTRYPOINTS -// If we're not using portable entrypoints, we can use NonVirtualEntry2MethodDesc to figure out where tailcalls go. Since this is -// somewhat expensive, we only do it for tailcalls which are relatively rare. -// TODO-Interpreter: It is plausible that we might want to do the NonVirtualEntry2MethodDesc check for non-tailcall calli as well -// or possibly a slight variant where we build a path for NonVitualEntry2MethodDesc which is lock-free but might fail - else if (frameNeedsTailcallUpdate && (targetMethod = NonVirtualEntry2MethodDesc(calliFunctionPointer)) != NULL) - { - goto CALL_INTERP_METHOD; - } -#else // FEATURE_PORTABLE_ENTRYPOINTS +#ifdef FEATURE_CACHED_INTERFACE_DISPATCH else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch) { - // _methodPtrAux of an open virtual delegate is not a portable entry point; resolve the target - // as INTOP_CALLDELEGATE does. The shuffle thunk's 'this' is the delegate. + // The shuffle thunk's 'this' is the delegate. For an open virtual delegate, _methodPtrAux is + // CID_VirtualOpenDelegateDispatch, which needs the delegate and so cannot be invoked through calli; + // resolve the target as INTOP_CALLDELEGATE does. DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF); _ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk()); _ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer); @@ -3413,6 +3405,16 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr targetMethod = ResolveOpenVirtualDelegateTarget(delegateObj, pThisArg); goto CALL_INTERP_METHOD; } +#endif // FEATURE_CACHED_INTERFACE_DISPATCH +#ifndef FEATURE_PORTABLE_ENTRYPOINTS +// If we're not using portable entrypoints, we can use NonVirtualEntry2MethodDesc to figure out where tailcalls go. Since this is +// somewhat expensive, we only do it for tailcalls which are relatively rare. +// TODO-Interpreter: It is plausible that we might want to do the NonVirtualEntry2MethodDesc check for non-tailcall calli as well +// or possibly a slight variant where we build a path for NonVitualEntry2MethodDesc which is lock-free but might fail + else if (frameNeedsTailcallUpdate && (targetMethod = NonVirtualEntry2MethodDesc(calliFunctionPointer)) != NULL) + { + goto CALL_INTERP_METHOD; + } #endif // !FEATURE_PORTABLE_ENTRYPOINTS else { From e2234aeec4e8b5090d7da58135548b7ea48c37cb Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 26 Sep 2026 17:22:37 -0500 Subject: [PATCH 6/9] Clarify why CID_VirtualOpenDelegateDispatch cannot be called through calli Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 7e798cffdada61..b2717f4748eff5 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3395,8 +3395,8 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch) { // The shuffle thunk's 'this' is the delegate. For an open virtual delegate, _methodPtrAux is - // CID_VirtualOpenDelegateDispatch, which needs the delegate and so cannot be invoked through calli; - // resolve the target as INTOP_CALLDELEGATE does. + // CID_VirtualOpenDelegateDispatch, which expects the address of _methodPtrAux in a hidden argument + // that calli cannot express; resolve the target as INTOP_CALLDELEGATE does. DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF); _ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk()); _ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer); From d9883317d93b3a196107454270c85420d8377dbc Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 26 Sep 2026 21:35:33 -0500 Subject: [PATCH 7/9] Link the IL shuffle thunk hidden-argument issue from both sites Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/comdelegate.cpp | 2 ++ src/coreclr/vm/interpexec.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index a26e71dab0fad4..150907e2094670 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -815,6 +815,8 @@ static PCODE CreateILDelegateShuffleThunk(MethodDesc* pDelegateMD, bool callTarg pCode->EmitLoadThis(); pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__METHOD_PTR_AUX))); + // For open virtual delegates, _methodPtrAux is a dispatch stub that expects the address of _methodPtrAux in a + // hidden argument, which calli does not pass: https://github.com/dotnet/runtime/issues/134733 pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, sig.NumFixedArgs(), sig.IsReturnTypeVoid() ? 0 : 1); pCode->EmitRET(); diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index b2717f4748eff5..24cb2b7347da65 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3397,6 +3397,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // The shuffle thunk's 'this' is the delegate. For an open virtual delegate, _methodPtrAux is // CID_VirtualOpenDelegateDispatch, which expects the address of _methodPtrAux in a hidden argument // that calli cannot express; resolve the target as INTOP_CALLDELEGATE does. + // Workaround for https://github.com/dotnet/runtime/issues/134733. DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF); _ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk()); _ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer); From a718e4eb1394aaf1692bd9a3371aea4b7adaa0ce Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sun, 27 Sep 2026 09:35:09 -0500 Subject: [PATCH 8/9] Update comdelegate.cpp Co-authored-by: Jan Kotas --- src/coreclr/vm/comdelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 150907e2094670..ac41bcefaff092 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -815,7 +815,7 @@ static PCODE CreateILDelegateShuffleThunk(MethodDesc* pDelegateMD, bool callTarg pCode->EmitLoadThis(); pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__METHOD_PTR_AUX))); - // For open virtual delegates, _methodPtrAux is a dispatch stub that expects the address of _methodPtrAux in a + // TODO: For open virtual delegates, _methodPtrAux is a dispatch stub that expects the address of _methodPtrAux in a // hidden argument, which calli does not pass: https://github.com/dotnet/runtime/issues/134733 pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, sig.NumFixedArgs(), sig.IsReturnTypeVoid() ? 0 : 1); pCode->EmitRET(); From d15c8ca1262fe428130ada5959e933c9e69ae614 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sun, 27 Sep 2026 09:38:28 -0500 Subject: [PATCH 9/9] Apply review suggestion to the INTOP_CALLI workaround comment Co-authored-by: Jan Kotas Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 24cb2b7347da65..0e21a28c23b16d 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3394,10 +3394,12 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr #ifdef FEATURE_CACHED_INTERFACE_DISPATCH else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch) { - // The shuffle thunk's 'this' is the delegate. For an open virtual delegate, _methodPtrAux is - // CID_VirtualOpenDelegateDispatch, which expects the address of _methodPtrAux in a hidden argument - // that calli cannot express; resolve the target as INTOP_CALLDELEGATE does. + // For an open virtual delegate, _methodPtrAux is CID_VirtualOpenDelegateDispatch, + // which expects the address of _methodPtrAux in a hidden argument that calli cannot + // express; resolve the target as INTOP_CALLDELEGATE does. // Workaround for https://github.com/dotnet/runtime/issues/134733. + + // The shuffle thunk's 'this' is the delegate. DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF); _ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk()); _ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer);