From 2723cddea17b90c3d62ba2292fa726b0f1b31ac3 Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 1 Jun 2026 21:53:07 +0900 Subject: [PATCH 01/14] CoreCLR support --- src/coreclr/jit/compiler.h | 3 ++- src/coreclr/jit/fginline.cpp | 15 +++++++------- src/coreclr/jit/importercalls.cpp | 14 +++++++------ src/coreclr/jit/inline.h | 1 + src/coreclr/vm/genericdict.cpp | 3 ++- src/coreclr/vm/genericdict.h | 1 + src/coreclr/vm/jitinterface.cpp | 33 +++++++++++++++++++++++++------ 7 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 306209162514bc..8fa3dcba6d75cc 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8103,7 +8103,8 @@ class Compiler bool isInterface, CORINFO_METHOD_HANDLE baseMethod, CORINFO_CLASS_HANDLE baseClass, - CORINFO_CONTEXT_HANDLE* pContextHandle); + CORINFO_CONTEXT_HANDLE* pContextHandle, + CORINFO_RESOLVED_TOKEN* pResolvedToken); bool isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gdvTarget); diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 9c88f80329bbe3..04b86e0b4beee4 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -608,16 +608,17 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtLateDevirtualizationInfo->methodHnd; - CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; - InlineContext* inlinersContext = call->gtInlineContext; - unsigned methodFlags = 0; - const bool isLateDevirtualization = true; - const bool explicitTailCall = call->IsTailPrefixedCall(); + CORINFO_METHOD_HANDLE method = call->gtLateDevirtualizationInfo->methodHnd; + CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; + InlineContext* inlinersContext = call->gtInlineContext; + CORINFO_RESOLVED_TOKEN* pResolvedToken = &call->gtLateDevirtualizationInfo->resolvedToken; + unsigned methodFlags = 0; + const bool isLateDevirtualization = true; + const bool explicitTailCall = call->IsTailPrefixedCall(); CORINFO_CONTEXT_HANDLE contextInput = context; context = nullptr; - m_compiler->impDevirtualizeCall(call, nullptr, &method, &methodFlags, &contextInput, &context, + m_compiler->impDevirtualizeCall(call, pResolvedToken, &method, &methodFlags, &contextInput, &context, isLateDevirtualization, explicitTailCall); if (!call->IsDevirtualizationCandidate(m_compiler)) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 8f6b7e101437d1..01fe863adab943 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1092,7 +1092,7 @@ var_types Compiler::impImportCall(OPCODE opcode, else if (call->AsCall()->IsDelegateInvoke()) { considerGuardedDevirtualization(call->AsCall(), rawILOffset, false, call->AsCall()->gtCallMethHnd, - NO_CLASS_HANDLE, nullptr); + NO_CLASS_HANDLE, nullptr, pResolvedToken); } } @@ -1361,6 +1361,7 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->ilLocation = impCurStmtDI.GetLocation(); + info->resolvedToken = *pResolvedToken; call->AsCall()->gtLateDevirtualizationInfo = info; } } @@ -7993,7 +7994,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, bool isInterface, CORINFO_METHOD_HANDLE baseMethod, CORINFO_CLASS_HANDLE baseClass, - CORINFO_CONTEXT_HANDLE* pContextHandle) + CORINFO_CONTEXT_HANDLE* pContextHandle, + CORINFO_RESOLVED_TOKEN* pResolvedToken) { JITDUMP("Considering guarded devirtualization at IL offset %u (0x%x)\n", ilOffset, ilOffset); @@ -8074,7 +8076,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, dvInfo.virtualMethod = baseMethod; dvInfo.objClass = exactCls; dvInfo.context = originalContext; - dvInfo.pResolvedTokenVirtualMethod = nullptr; + dvInfo.pResolvedTokenVirtualMethod = pResolvedToken; JITDUMP("GDV exact: resolveVirtualMethod (method %p class %p context %p)\n", dvInfo.virtualMethod, dvInfo.objClass, dvInfo.context); @@ -9209,7 +9211,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, pResolvedToken); return; } @@ -9253,7 +9255,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, pResolvedToken); return; } @@ -9375,7 +9377,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, objClass, pContextHandle); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, objClass, pContextHandle, pResolvedToken); return; } diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index 90e79dbb3fc8e7..4d1357bb0f3db2 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -634,6 +634,7 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; ILLocation ilLocation; + CORINFO_RESOLVED_TOKEN resolvedToken; }; // InlArgInfo describes inline candidate argument properties. diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index e9d15c0fa6bb63..326f5500e53f52 100644 --- a/src/coreclr/vm/genericdict.cpp +++ b/src/coreclr/vm/genericdict.cpp @@ -864,6 +864,7 @@ Dictionary::PopulateEntry( } case MethodDescSlot: + case DevirtualizedMethodDescSlot: case DispatchStubAddrSlot: case MethodEntrySlot: { @@ -1202,7 +1203,7 @@ Dictionary::PopulateEntry( } else { - _ASSERTE(kind == MethodDescSlot); + _ASSERTE(kind == MethodDescSlot || kind == DevirtualizedMethodDescSlot); result = (CORINFO_GENERIC_HANDLE)pMethod; } break; diff --git a/src/coreclr/vm/genericdict.h b/src/coreclr/vm/genericdict.h index be2a0af5569029..16bc515eb19998 100644 --- a/src/coreclr/vm/genericdict.h +++ b/src/coreclr/vm/genericdict.h @@ -58,6 +58,7 @@ enum DictionaryEntryKind DispatchStubAddrSlot = 5, FieldDescSlot = 6, DeclaringTypeHandleSlot = 7, + DevirtualizedMethodDescSlot = 8, }; enum DictionaryEntrySignatureSource : BYTE diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index ce3b2018008d7b..627799215ecd63 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3209,11 +3209,19 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr FALLTHROUGH; case MethodDescSlot: + case DevirtualizedMethodDescSlot: case MethodEntrySlot: case DispatchStubAddrSlot: { // Encode containing type - if (pResolvedToken->pTypeSpec != NULL) + if (entryKind == DevirtualizedMethodDescSlot) + { + // For shared GVM devirtualization use the devirtualized method owner type from pTemplateMD. + _ASSERTE(pTemplateMD != NULL); + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(pTemplateMD->GetMethodTable()); + } + else if (pResolvedToken->pTypeSpec != NULL) { SigPointer sigptr(pResolvedToken->pTypeSpec, pResolvedToken->cbTypeSpec); sigptr.ConvertToInternalExactlyOne(pModule, NULL, &sigBuilder); @@ -8888,13 +8896,26 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) { - // TODO: Support for runtime lookup - info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; + if (info->pResolvedTokenVirtualMethod == nullptr) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + ComputeRuntimeLookupForSharedGenericToken( + DevirtualizedMethodDescSlot, + info->pResolvedTokenVirtualMethod, + nullptr, + pInstantiatedMD->IsUnboxingStub() ? pInstantiatedMD->GetWrappedMethodDesc() : pInstantiatedMD, + m_pMethodBeingCompiled, + &info->instParamLookup); } - info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstantiatedMD; - info->instParamLookup.constLookup.accessType = IAT_VALUE; + if (!info->instParamLookup.lookupKind.needsRuntimeLookup) + { + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pInstantiatedMD; + info->instParamLookup.constLookup.accessType = IAT_VALUE; + } } else if (pInstArgMD->RequiresInstMethodTableArg()) { From 9ce2f81cb54e49b319032495c31ce0c922d73f07 Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 1 Jun 2026 23:43:52 +0900 Subject: [PATCH 02/14] R2R --- .../Internal/Runtime/ReadyToRunConstants.cs | 1 + .../tools/Common/JitInterface/CorInfoImpl.cs | 52 +++++++++++++------ .../JitInterface/CorInfoImpl.ReadyToRun.cs | 44 +++++++++++----- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index f70b278f2de695..99ec54082ac19a 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -124,6 +124,7 @@ public enum DictionaryEntryKind DispatchStubAddrSlot = 5, FieldDescSlot = 6, DeclaringTypeHandleSlot = 7, + DevirtualizedMethodDescSlot = 8, } public enum ReadyToRunFixupKind diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index b93f7e3577e31a..8d7f7926f45b96 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1507,9 +1507,25 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) if (originalImpl.IsRuntimeDeterminedExactMethod || originalImpl.IsSharedByGenericInstantiations) { - // TODO: Support for runtime lookup + if (info->pResolvedTokenVirtualMethod == null || unboxingStub) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + +#if READYTORUN + ComputeRuntimeLookupForSharedGenericToken( + Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, + ref *info->pResolvedTokenVirtualMethod, + null, + originalImpl, + MethodBeingCompiled, + ref info->instParamLookup); +#else + // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; +#endif } } @@ -1522,32 +1538,34 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) } #endif - if (requiresInstMethodDescArg) + if (!info->instParamLookup.lookupKind.needsRuntimeLookup) { - if (unboxingStub) + if (requiresInstMethodDescArg) { - // Bail out for now. We need an unboxing stub that points to an instantiated method. - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; - } + if (unboxingStub) + { + // Bail out for now. We need an unboxing stub that points to an instantiated method. + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } #if READYTORUN - MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null); - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodDictionary, originalImplWithToken)); - + MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodDictionary, originalImplWithToken)); #else - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodGenericDictionary(originalImpl)); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodGenericDictionary(originalImpl)); #endif - } - else if (requiresInstMethodTableArg) - { - if (!unboxingStub) + } + else if (requiresInstMethodTableArg) { + if (!unboxingStub) + { #if READYTORUN - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeDictionary, originalImpl.OwningType)); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeDictionary, originalImpl.OwningType)); #else - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ConstructedTypeSymbol(originalImpl.OwningType)); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ConstructedTypeSymbol(originalImpl.OwningType)); #endif + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index ce81545834795c..cf6b9c8c2c04bc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2774,11 +2774,12 @@ private void ComputeRuntimeLookupForSharedGenericToken( break; case DictionaryEntryKind.MethodDescSlot: + case DictionaryEntryKind.DevirtualizedMethodDescSlot: case DictionaryEntryKind.MethodEntrySlot: case DictionaryEntryKind.ConstrainedMethodEntrySlot: case DictionaryEntryKind.DispatchStubAddrSlot: { - if (entryKind == DictionaryEntryKind.MethodDescSlot) + if (entryKind == DictionaryEntryKind.MethodDescSlot || entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) { helperId = ReadyToRunHelperId.MethodHandle; } @@ -2806,21 +2807,38 @@ private void ComputeRuntimeLookupForSharedGenericToken( throw new NotImplementedException(entryKind.ToString()); } - object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); - if (helperArg is MethodDesc methodDesc) + object helperArg; + if (entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) { - var methodIL = HandleToObject(pResolvedToken.tokenScope); - MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); - // We shouldn't be needing shared generics in resumption stubs - generics info should all be stored in the continuation - Debug.Assert(MethodBeingCompiled is not AsyncResumptionStub); - _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation), constrainedType, unboxing: false, genericContextObject: sharedMethod, forceOwningTypeFromMethodDesc: strippedInstantiation); + Debug.Assert(templateMethod != null); + MethodDesc tokenMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (tokenMethod.HasInstantiation) + { + templateMethod = _compilation.TypeSystemContext.GetInstantiatedMethod(templateMethod.GetMethodDefinition(), tokenMethod.Instantiation); + } + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, templateMethod); + ModuleToken templateMethodToken = + _compilation.NodeFactory.Resolver.GetModuleTokenForMethod(templateMethod.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: true); + helperArg = new MethodWithToken(templateMethod, templateMethodToken, constrainedType: null, unboxing: false, genericContextObject: null); } - else if (helperArg is FieldDesc fieldDesc) + else { - ModuleToken fieldToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); - Debug.Assert(!strippedInstantiation); - helperArg = new FieldWithToken(fieldDesc, fieldToken, forceOwningTypeNotDerivedFromToken: strippedInstantiation); + helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (helperArg is MethodDesc methodDesc) + { + var methodIL = HandleToObject(pResolvedToken.tokenScope); + MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); + // We shouldn't be needing shared generics in resumption stubs - generics info should all be stored in the continuation + Debug.Assert(MethodBeingCompiled is not AsyncResumptionStub); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation), constrainedType, unboxing: false, genericContextObject: sharedMethod, forceOwningTypeFromMethodDesc: strippedInstantiation); + } + else if (helperArg is FieldDesc fieldDesc) + { + ModuleToken fieldToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); + Debug.Assert(!strippedInstantiation); + helperArg = new FieldWithToken(fieldDesc, fieldToken, forceOwningTypeNotDerivedFromToken: strippedInstantiation); + } } var methodContext = new GenericContext(callerHandle); From 2f787b6f5b0e6ac4da738a81394797020a6da7e4 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 24 Jun 2026 23:51:27 +0900 Subject: [PATCH 03/14] Various fixes --- src/coreclr/vm/jitinterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 627799215ecd63..cebdd525dd9ba3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8913,7 +8913,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (!info->instParamLookup.lookupKind.needsRuntimeLookup) { - info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pInstantiatedMD; + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstantiatedMD; info->instParamLookup.constLookup.accessType = IAT_VALUE; } } From 071523a5b055aebcbaea437ec25d3f990c12322e Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 25 Jun 2026 02:02:23 +0900 Subject: [PATCH 04/14] JIT format --- src/coreclr/jit/importercalls.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 01fe863adab943..e6fbb91cffbfcb 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -9211,7 +9211,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, pResolvedToken); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, + pResolvedToken); return; } @@ -9255,7 +9256,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, pResolvedToken); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, + pResolvedToken); return; } @@ -9377,7 +9379,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, objClass, pContextHandle, pResolvedToken); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, objClass, pContextHandle, + pResolvedToken); return; } From 309d8fd9e291d4e1bff3a6cc7962919a605d8fb2 Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 29 Jun 2026 21:52:41 +0900 Subject: [PATCH 05/14] Format --- src/coreclr/vm/jitinterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index cebdd525dd9ba3..2233f01b6d6be6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3214,7 +3214,7 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr case DispatchStubAddrSlot: { // Encode containing type - if (entryKind == DevirtualizedMethodDescSlot) + if (entryKind == DevirtualizedMethodDescSlot) { // For shared GVM devirtualization use the devirtualized method owner type from pTemplateMD. _ASSERTE(pTemplateMD != NULL); From 0dc166e8fd70a65aa8059ebeb14c85345cada8c9 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 1 Jul 2026 20:32:56 +0900 Subject: [PATCH 06/14] Remove unnecessary unboxing stub handling --- src/coreclr/vm/jitinterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 8fbeedcc78bca1..69bd709062daad 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8905,7 +8905,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) DevirtualizedMethodDescSlot, info->pResolvedTokenVirtualMethod, nullptr, - pInstantiatedMD->IsUnboxingStub() ? pInstantiatedMD->GetWrappedMethodDesc() : pInstantiatedMD, + pInstantiatedMD, m_pMethodBeingCompiled, &info->instParamLookup); } From 7de7f0c959914d50d58b7dd140b09c9f20d22928 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 1 Jul 2026 20:41:54 +0900 Subject: [PATCH 07/14] Support runtime lookup together with unboxing stub --- src/coreclr/vm/jitinterface.cpp | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 69bd709062daad..081109229c9cd6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8931,19 +8931,38 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) } else if (isUnboxingStubOfInstantiatingStub) { - if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetClassInstantiation()) || - TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) + if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetClassInstantiation())) { - // This is an unboxing stub that points to an instantiating stub that requires a runtime lookup. - // Bail out. + // If we end up with a shared MethodTable that is not exact, + // we can't devirtualize since it's not possible to compute the instantiation argument even as a runtime lookup. info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; } - // pInstArgMD is the wrapped instantiating stub in the unboxing stub. - // - info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstArgMD; - info->instParamLookup.constLookup.accessType = IAT_VALUE; + if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) + { + if (info->pResolvedTokenVirtualMethod == nullptr) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + ComputeRuntimeLookupForSharedGenericToken( + DevirtualizedMethodDescSlot, + info->pResolvedTokenVirtualMethod, + nullptr, + pInstArgMD, + m_pMethodBeingCompiled, + &info->instParamLookup); + } + + if (!info->instParamLookup.lookupKind.needsRuntimeLookup) + { + // pInstArgMD is the wrapped instantiating stub in the unboxing stub. + // + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstArgMD; + info->instParamLookup.constLookup.accessType = IAT_VALUE; + } } pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD; From fd30d8d84fd62e00a5499a6f6d3910caddf071a4 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 1 Jul 2026 22:43:24 +0900 Subject: [PATCH 08/14] Add a generic context shift test for runtime lookups --- .../VirtualMethods/generic_virtual_methods.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs index c5e12ccf7f4609..70788c9401325d 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -151,6 +151,26 @@ public static void RuntimeLookupDelegate() RuntimeLookupDelegateGenericVirtual.TestGenericMethodOnStringType(); } + [Fact] + public static void RuntimeLookupInlining() + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Type Inlinee(IDevirtTarget target) + { + return target.GetTypeArg(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type Caller() + { + return Inlinee(new DevirtTarget()); + } + + Type actual = Caller(); + Type expected = typeof(object); + Assert.Equal(expected, actual); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ValidateCaller(string scenarioName, IBaseMethodCaller caller) { @@ -492,6 +512,17 @@ public Delegate Foo() } } +interface IDevirtTarget +{ + Type GetTypeArg(); +} + +sealed class DevirtTarget : IDevirtTarget +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public Type GetTypeArg() => typeof(T); +} + internal static class IconContextBridgeNonShared { public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value) From 3704f25e9eaf4e9eb22fab387a45c89b9ce357bd Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 1 Jul 2026 23:02:21 +0900 Subject: [PATCH 09/14] Use the correct caller method --- src/coreclr/inc/corinfo.h | 1 + src/coreclr/inc/jiteeversionguid.h | 10 +++++----- src/coreclr/jit/importercalls.cpp | 1 + src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 2 +- src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | 1 + src/coreclr/tools/superpmi/superpmi-shared/agnostic.h | 1 + .../tools/superpmi/superpmi-shared/methodcontext.cpp | 4 +++- src/coreclr/vm/jitinterface.cpp | 6 +++--- 8 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 4dfa9d39bc10c7..31897588adf722 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1576,6 +1576,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO // [In] arguments of resolveVirtualMethod // CORINFO_METHOD_HANDLE virtualMethod; + CORINFO_METHOD_HANDLE callerMethod; CORINFO_CLASS_HANDLE objClass; CORINFO_CONTEXT_HANDLE context; CORINFO_RESOLVED_TOKEN *pResolvedTokenVirtualMethod; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index e0177d76cd5826..a13c35e2f55004 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* fcb1b400-696c-4425-a8a7-bb082430a217 */ - 0xfcb1b400, - 0x696c, - 0x4425, - {0xa8, 0xa7, 0xbb, 0x08, 0x24, 0x30, 0xa2, 0x17} +constexpr GUID JITEEVersionIdentifier = { /* 01657eff-500b-4529-a977-df9eb508cd6a */ + 0x01657eff, + 0x500b, + 0x4529, + {0xa9, 0x77, 0xdf, 0x9e, 0xb5, 0x08, 0xcd, 0x6a} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index f72660ec94bcd9..d94e9fd6bd8db8 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -9294,6 +9294,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // CORINFO_DEVIRTUALIZATION_INFO dvInfo; dvInfo.virtualMethod = baseMethod; + dvInfo.callerMethod = info.compMethodHnd; dvInfo.objClass = objClass; dvInfo.context = *pContextHandle; dvInfo.detail = CORINFO_DEVIRTUALIZATION_UNKNOWN; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index a9538d3eff2a3b..8d9c2b999266b5 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1553,7 +1553,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) ref *info->pResolvedTokenVirtualMethod, null, originalImpl, - MethodBeingCompiled, + HandleToObject(info->callerMethod), ref info->instParamLookup); #else // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 0c843d13aa1a7f..bc48fdd678cbb7 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1165,6 +1165,7 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO // [In] arguments of resolveVirtualMethod // public CORINFO_METHOD_STRUCT_* virtualMethod; + public CORINFO_METHOD_STRUCT_* callerMethod; public CORINFO_CLASS_STRUCT_* objClass; public CORINFO_CONTEXT_STRUCT* context; public CORINFO_RESOLVED_TOKEN* pResolvedTokenVirtualMethod; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 020112dbb4f40c..2e95102863f0ce 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -681,6 +681,7 @@ struct Agnostic_GetWasmTypeSymbol struct Agnostic_ResolveVirtualMethodKey { DWORDLONG virtualMethod; + DWORDLONG callerMethod; DWORDLONG objClass; DWORDLONG context; DWORD pResolvedTokenVirtualMethodNonNull; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index b9e8b4f17e81e8..f905ac7f4103e2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3238,6 +3238,7 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info Agnostic_ResolveVirtualMethodKey key; ZeroMemory(&key, sizeof(key)); // Zero token including any struct padding key.virtualMethod = CastHandle(info->virtualMethod); + key.callerMethod = CastHandle(info->callerMethod); key.objClass = CastHandle(info->objClass); key.context = CastHandle(info->context); @@ -3290,6 +3291,7 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info Agnostic_ResolveVirtualMethodKey key; ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding key.virtualMethod = CastHandle(info->virtualMethod); + key.callerMethod = CastHandle(info->callerMethod); key.objClass = CastHandle(info->objClass); key.context = CastHandle(info->context); @@ -3297,7 +3299,7 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info if (key.pResolvedTokenVirtualMethodNonNull) key.pResolvedTokenVirtualMethod = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKEN(info->pResolvedTokenVirtualMethod, ResolveToken); - Agnostic_ResolveVirtualMethodResult result = LookupByKeyOrMiss(ResolveVirtualMethod, key, ": %016" PRIX64 "-%016" PRIX64 "-%016" PRIX64 "-%08X", key.virtualMethod, key.objClass, key.context, key.pResolvedTokenVirtualMethodNonNull); + Agnostic_ResolveVirtualMethodResult result = LookupByKeyOrMiss(ResolveVirtualMethod, key, ": %016" PRIX64 "-%016" PRIX64 "-%016" PRIX64 "-%016" PRIX64 "-%08X", key.virtualMethod, key.callerMethod, key.objClass, key.context, key.pResolvedTokenVirtualMethodNonNull); DEBUG_REP(dmpResolveVirtualMethod(key, result)); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 081109229c9cd6..d5ca5f8f031b0f 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8906,7 +8906,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) info->pResolvedTokenVirtualMethod, nullptr, pInstantiatedMD, - m_pMethodBeingCompiled, + GetMethod(info->callerMethod), &info->instParamLookup); } @@ -8941,7 +8941,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) { - if (info->pResolvedTokenVirtualMethod == nullptr) + if (info->pResolvedTokenVirtualMethod == nullptr || info->callerMethod == nullptr) { info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; @@ -8952,7 +8952,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) info->pResolvedTokenVirtualMethod, nullptr, pInstArgMD, - m_pMethodBeingCompiled, + GetMethod(info->callerMethod), &info->instParamLookup); } From ecb547556891bc6fff3c8674b9a5177141e47cc9 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 1 Jul 2026 23:38:13 +0900 Subject: [PATCH 10/14] Unboxed entry should not be an instantiating stub --- src/coreclr/vm/jitinterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index d5ca5f8f031b0f..dc6ce51d58f16e 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8976,6 +8976,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (pDevirtMD->IsUnboxingStub()) { MethodDesc* pUnboxedMD = pDevirtMD->GetMethodTable()->GetUnboxedEntryPointMD(pDevirtMD); + if (pUnboxedMD->IsInstantiatingStub()) pUnboxedMD = pUnboxedMD->GetWrappedMethodDesc(); info->resolvedTokenDevirtualizedUnboxedMethod.hMethod = (CORINFO_METHOD_HANDLE) pUnboxedMD; } From 218669d345336e4291ee9075367e203693324f3c Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 3 Jul 2026 00:33:48 +0900 Subject: [PATCH 11/14] Find the lookup context from IR in late devirt --- src/coreclr/jit/compiler.h | 7 ++- src/coreclr/jit/importer.cpp | 15 +++-- src/coreclr/jit/importercalls.cpp | 67 ++++++++++++++++++--- src/coreclr/jit/indirectcalltransformer.cpp | 1 + src/coreclr/jit/morph.cpp | 20 +++--- src/coreclr/vm/jitinterface.cpp | 2 +- 6 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 90184f635435fc..a4e159b2e37f7a 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5042,6 +5042,7 @@ class Compiler CORINFO_RESOLVED_TOKEN* pResolvedToken; // Resolved token for the target method, used by R2R. CORINFO_RESOLVED_TOKEN* pUnboxedResolvedToken; // Resolved token and method handle for the unboxed entry. CORINFO_LOOKUP* pInstParamLookup; // All the information needed for the instantiation parameter lookup. + GenTree* runtimeLookupContext; // Runtime context tree to use for pInstParamLookup, or nullptr. CORINFO_SIG_INFO* pMethSig; // The devirted method signature. bool objIsNonNull; // True if the receiver is known non-null. bool hadImplicitNullCheck; // True if the original call's null check was implicit. @@ -7213,9 +7214,11 @@ class Compiler CORINFO_METHOD_HANDLE dispatcherHnd); GenTree* getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFlags, - void* compileTimeHandle); + void* compileTimeHandle, + GenTree* runtimeLookupContext = nullptr); GenTree* getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, - void* compileTimeHandle); + void* compileTimeHandle, + GenTree* runtimeLookupContext = nullptr); GenTree* getVirtMethodPointerTree(GenTree* thisPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 0409edb99e6bbb..5e148476d644b7 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1628,10 +1628,17 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_LOOKUP* pLookup, void* compile assert(pRuntimeLookup->indirections != 0); GenTreeCall* helperCall = gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, ctxTree, compileTimeHandle); - // Spilling it to a temp improves CQ (mainly in Tier0) - unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); - impStoreToTemp(callLclNum, helperCall, CHECK_SPILL_NONE); - return gtNewLclvNode(callLclNum, helperCall->TypeGet()); + if (opts.OptimizationEnabled()) + { + return helperCall; + } + else + { + // Spilling it to a temp improves CQ (mainly in Tier0) + unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); + impStoreToTemp(callLclNum, helperCall, CHECK_SPILL_NONE); + return gtNewLclvNode(callLclNum, helperCall->TypeGet()); + } } // Size-check is not expected without testForNull diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index d94e9fd6bd8db8..3919f180cb3f77 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8094,6 +8094,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // CORINFO_DEVIRTUALIZATION_INFO dvInfo; dvInfo.virtualMethod = baseMethod; + dvInfo.callerMethod = call->gtInlineContext->GetCallee(); dvInfo.objClass = exactCls; dvInfo.context = originalContext; dvInfo.pResolvedTokenVirtualMethod = pResolvedToken; @@ -8179,6 +8180,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // Figure out which method will be called. // dvInfo.virtualMethod = baseMethod; + dvInfo.callerMethod = call->gtInlineContext->GetCallee(); dvInfo.objClass = likelyClass; dvInfo.context = originalContext; dvInfo.pResolvedTokenVirtualMethod = nullptr; @@ -9289,12 +9291,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP("--- base class is interface\n"); } - // Fetch the method that would be called based on the declared type of 'this', - // and prepare to fetch the method attributes. - // CORINFO_DEVIRTUALIZATION_INFO dvInfo; dvInfo.virtualMethod = baseMethod; - dvInfo.callerMethod = info.compMethodHnd; + dvInfo.callerMethod = call->gtInlineContext->GetCallee(); dvInfo.objClass = objClass; dvInfo.context = *pContextHandle; dvInfo.detail = CORINFO_DEVIRTUALIZATION_UNKNOWN; @@ -9305,6 +9304,56 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, info.compCompHnd->resolveVirtualMethod(&dvInfo); + GenTree* runtimeLookupContext = nullptr; + + if (isLateDevirtualization && dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) + { + // Late devirtualization may revisit a call that was imported in an inlinee. + // If token context is not the current root context, runtime lookups can be rooted in the wrong generic context. + // + CORINFO_CONTEXT_HANDLE tokenContext = pResolvedToken->tokenContext; + const SIZE_T tokenContextHandle = (SIZE_T)tokenContext & ~CORINFO_CONTEXTFLAGS_MASK; + + const bool isMethodContext = ((SIZE_T)tokenContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; + const bool isCurrentContext = + (tokenContext == METHOD_BEING_COMPILED_CONTEXT()) || + (isMethodContext ? ((CORINFO_METHOD_HANDLE)tokenContextHandle == info.compMethodHnd) + : ((CORINFO_CLASS_HANDLE)tokenContextHandle == info.compClassHnd)); + + // If we don't have the right context, try recover it from the tree. + // + if (!isCurrentContext) + { + CallArg* runtimeMethodHandleArg = nullptr; + if ((call->gtControlExpr != nullptr) && call->gtControlExpr->OperIs(GT_CALL)) + { + runtimeMethodHandleArg = + call->gtControlExpr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle); + } + + if (runtimeMethodHandleArg != nullptr && runtimeMethodHandleArg->GetNode()->OperIs(GT_RUNTIMELOOKUP)) + { + if (runtimeMethodHandleArg->GetNode()->AsRuntimeLookup()->Lookup()->OperIs(GT_CALL)) + { + GenTreeCall* const helperCall = + runtimeMethodHandleArg->GetNode()->AsRuntimeLookup()->Lookup()->AsCall(); + + if (helperCall->IsHelperCall(CORINFO_HELP_RUNTIMEHANDLE_METHOD) || + helperCall->IsHelperCall(CORINFO_HELP_RUNTIMEHANDLE_CLASS)) + { + runtimeLookupContext = gtCloneExpr(helperCall->gtArgs.GetArgByIndex(0)->GetNode()); + } + } + } + + if (runtimeLookupContext == nullptr) + { + JITDUMP("Late devirt needs a runtime lookup context cannot be figured out. Bail out.\n"); + return; + } + } + } + CORINFO_METHOD_HANDLE derivedMethod = dvInfo.devirtualizedMethod; CORINFO_CONTEXT_HANDLE exactContext = dvInfo.tokenLookupContext; CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; @@ -9416,6 +9465,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, dcInfo.pInstParamLookup = &dvInfo.instParamLookup; dcInfo.pResolvedToken = pDerivedResolvedToken; dcInfo.pUnboxedResolvedToken = &dvInfo.resolvedTokenDevirtualizedUnboxedMethod; + dcInfo.runtimeLookupContext = runtimeLookupContext; dcInfo.pMethSig = &derivedSig; dcInfo.objIsNonNull = objIsNonNull; dcInfo.hadImplicitNullCheck = true; @@ -9634,7 +9684,8 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_METHOD_HANDLE exactMethodHandle = (CORINFO_METHOD_HANDLE)((SIZE_T)dcInfo->tokenLookupContext & ~CORINFO_CONTEXTFLAGS_MASK); - instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); + instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle, + dcInfo->runtimeLookupContext); JITDUMP("revising call to invoke unboxed entry with additional method desc arg\n"); } else @@ -9716,7 +9767,8 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_METHOD_HANDLE exactMethodHandle = (CORINFO_METHOD_HANDLE)((SIZE_T)dcInfo->tokenLookupContext & ~CORINFO_CONTEXTFLAGS_MASK); - instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); + instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle, + dcInfo->runtimeLookupContext); } else { @@ -9725,7 +9777,8 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_CLASS_HANDLE exactClassHandle = (CORINFO_CLASS_HANDLE)((SIZE_T)dcInfo->tokenLookupContext & ~CORINFO_CONTEXTFLAGS_MASK); - instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle); + instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle, + dcInfo->runtimeLookupContext); } } } diff --git a/src/coreclr/jit/indirectcalltransformer.cpp b/src/coreclr/jit/indirectcalltransformer.cpp index 13059d41d766c3..7e64346af0958f 100644 --- a/src/coreclr/jit/indirectcalltransformer.cpp +++ b/src/coreclr/jit/indirectcalltransformer.cpp @@ -987,6 +987,7 @@ class IndirectCallTransformer dcInfo.pUnboxedResolvedToken = (clsHnd != NO_CLASS_HANDLE) ? &inlineInfo->guardedMethodUnboxedResolvedToken : nullptr; + dcInfo.runtimeLookupContext = nullptr; dcInfo.objIsNonNull = objIsNonNull; dcInfo.hadImplicitNullCheck = m_origCall->IsVirtual(); dcInfo.isDelegateCall = m_origCall->IsDelegateInvoke(); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 7c99a66f80bc9f..0b42d1d27e34b8 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5512,11 +5512,15 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // pLookup - the lookup to get the tree for // handleFlags - flags to set on the result node // compileTimeHandle - compile-time handle corresponding to the lookup +// runtimeLookupContext - context tree to use for a runtime lookup, or nullptr to use the current context // // Return Value: // A node representing the lookup tree // -GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFlags, void* compileTimeHandle) +GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, + GenTreeFlags handleFlags, + void* compileTimeHandle, + GenTree* runtimeLookupContext) { if (!pLookup->lookupKind.needsRuntimeLookup) { @@ -5539,7 +5543,7 @@ GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFla return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, compileTimeHandle); } - return getRuntimeLookupTree(pLookup, compileTimeHandle); + return getRuntimeLookupTree(pLookup, compileTimeHandle, runtimeLookupContext); } //------------------------------------------------------------------------ @@ -5548,25 +5552,27 @@ GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFla // Arguments: // pLookup - the lookup to get the tree for // compileTimeHandle - compile-time handle corresponding to the lookup +// runtimeLookupContext - context tree to use for the lookup, or nullptr to use the current context // // Return Value: // A node representing the runtime lookup tree // -GenTree* Compiler::getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, void* compileTimeHandle) +GenTree* Compiler::getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, void* compileTimeHandle, GenTree* runtimeLookupContext) { CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; + GenTree* contextTree = runtimeLookupContext != nullptr + ? runtimeLookupContext + : getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); // If pRuntimeLookup->indirections is equal to CORINFO_USEHELPER, it specifies that a run-time helper should be // used; otherwise, it specifies the number of indirections via pRuntimeLookup->offsets array. if ((pRuntimeLookup->indirections == CORINFO_USEHELPER) || (pRuntimeLookup->indirections == CORINFO_USENULL) || pRuntimeLookup->testForNull) { - return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, - getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind), - compileTimeHandle); + return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, contextTree, compileTimeHandle); } - GenTree* result = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); + GenTree* result = contextTree; ArrayStack stmts(getAllocator(CMK_ArrayStack)); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index dc6ce51d58f16e..6237275af62ecd 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8941,7 +8941,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) { - if (info->pResolvedTokenVirtualMethod == nullptr || info->callerMethod == nullptr) + if (info->pResolvedTokenVirtualMethod == nullptr) { info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; From e93af8c0521dda22c3cfee93003e053fcdbc04af Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 3 Jul 2026 00:33:56 +0900 Subject: [PATCH 12/14] Add tests --- .../VirtualMethods/generic_virtual_methods.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs index 70788c9401325d..10f6650ef18319 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -154,6 +154,24 @@ public static void RuntimeLookupDelegate() [Fact] public static void RuntimeLookupInlining() { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static IDevirtTarget MakeTarget() + { + return new DevirtTarget(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Type LateInlinee() + { + return MakeTarget().GetTypeArg(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type LateCaller() + { + return LateInlinee(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] static Type Inlinee(IDevirtTarget target) { @@ -166,9 +184,19 @@ static Type Caller() return Inlinee(new DevirtTarget()); } - Type actual = Caller(); + [MethodImpl(MethodImplOptions.NoInlining)] + static Type LateRootCaller() + { + return MakeTarget().GetTypeArg(); + } + + Type immediateActual = Caller(); + Type lateActual = LateCaller(); + Type lateRootActual = LateRootCaller(); Type expected = typeof(object); - Assert.Equal(expected, actual); + Assert.Equal(expected, immediateActual); + Assert.Equal(expected, lateActual); + Assert.Equal(expected, lateRootActual); } [MethodImpl(MethodImplOptions.AggressiveInlining)] From fb4213567c31cf1094a0209ece7015f7533534a4 Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 3 Jul 2026 01:48:16 +0900 Subject: [PATCH 13/14] Address feedbacks --- src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index f905ac7f4103e2..65c5f807ade1e2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3270,8 +3270,10 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info void MethodContext::dmpResolveVirtualMethod(const Agnostic_ResolveVirtualMethodKey& key, const Agnostic_ResolveVirtualMethodResult& result) { - printf("ResolveVirtualMethod key virtMethod-%016" PRIX64 ", objClass-%016" PRIX64 ", context-%016" PRIX64 " pResolvedTokenVirtualMethodNonNull-%08X pResolvedTokenVirtualMethod{%s}", + printf("ResolveVirtualMethod key virtMethod-%016" PRIX64 ", callerMethod-%016" PRIX64 ", objClass-%016" PRIX64 + ", context-%016" PRIX64 " pResolvedTokenVirtualMethodNonNull-%08X pResolvedTokenVirtualMethod{%s}", key.virtualMethod, + key.callerMethod, key.objClass, key.context, key.pResolvedTokenVirtualMethodNonNull, From a9780b4c86ad8a2f839ef4abad70f63e3737b6e3 Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 3 Jul 2026 12:24:32 +0900 Subject: [PATCH 14/14] Remove redundant clone as the old context will be thrown away --- src/coreclr/jit/importercalls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 3919f180cb3f77..0ffe988ce9d644 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -9341,7 +9341,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (helperCall->IsHelperCall(CORINFO_HELP_RUNTIMEHANDLE_METHOD) || helperCall->IsHelperCall(CORINFO_HELP_RUNTIMEHANDLE_CLASS)) { - runtimeLookupContext = gtCloneExpr(helperCall->gtArgs.GetArgByIndex(0)->GetNode()); + runtimeLookupContext = helperCall->gtArgs.GetArgByIndex(0)->GetNode(); } } }