From 789055d573db8bfc4ff49133a041bef7a02ec733 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 18 Jan 2026 20:37:05 +0900 Subject: [PATCH 01/52] Support for shared GVM devirt that doesn't require a runtime lookup --- src/coreclr/jit/importercalls.cpp | 26 ++++++++++++++++--------- src/coreclr/vm/jitinterface.cpp | 32 +++++++++++-------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 869b947a318c80..ef53bfb55c08a6 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8822,15 +8822,10 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // assert(dvInfo.needsMethodContext); - // We don't expect NAOT to end up here, since it has Array - // and normal devirtualization. - // - assert(!IsTargetAbi(CORINFO_NATIVEAOT_ABI)); - - // We don't expect R2R to end up here, since it does not (yet) support - // array interface devirtualization. - // - assert(!IsAot()); + // We don't expect R2R/NAOT to end up here for array interface devirtualization. + // For NAOT, it has Array and normal devirtualization. + // For R2R, we don't (yet) support array interface devirtualization. + assert(call->IsGenericVirtual(this) || !IsAot()); // We don't expect there to be an existing inst param arg. // @@ -8858,6 +8853,19 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, assert((derivedMethod == NO_METHOD_HANDLE) || (instantiatingStub != NO_METHOD_HANDLE)); } + if (call->IsGenericVirtual(this) && dvInfo.needsMethodContext) + { + CallArg* lookupArg = call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle); + assert(lookupArg != nullptr); + + if (lookupArg->GetNode()->OperIs(GT_RUNTIMELOOKUP)) + { + // If we need a runtime lookup, we can't devirtualize yet because we don't have the right generic context. + JITDUMP("Generic virtual method devirt: runtime lookup present, sorry.\n"); + return; + } + } + // If we failed to get a method handle, we can't directly devirtualize. // // This can happen with AOT, if the devirtualization crosses diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4c9b875cebb958..956ed523cde1d1 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8768,6 +8768,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) MethodTable* pExactMT = pApproxMT; bool isArray = false; bool isGenericVirtual = false; + bool needsMethodContext = false; if (pApproxMT->IsInterface()) { @@ -8779,25 +8780,25 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) else if (pBaseMT->IsInterface() && pObjMT->IsArray()) { isArray = true; + needsMethodContext = true; } else { pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT); } - + // This is generic virtual method devirtualization. if (!isArray && pBaseMD->HasMethodInstantiation()) { - pDevirtMD = pDevirtMD->FindOrCreateAssociatedMethodDesc( - pDevirtMD, pExactMT, pExactMT->IsValueType() && !pDevirtMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true); + MethodDesc* pPrimaryMD = pDevirtMD; + pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( + pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true); - // We still can't handle shared generic methods because we don't have - // the right generic context for runtime lookup. - // TODO: Remove this limitation. if (pDevirtMD->IsSharedByGenericMethodInstantiations()) { - info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; + pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( + pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); + needsMethodContext = true; } isGenericVirtual = true; @@ -8805,22 +8806,11 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) // Success! Pass back the results. // - if (isArray) + if (isArray || isGenericVirtual) { - // Note if array devirtualization produced an instantiation stub - // so jit can try and inline it. - // info->isInstantiatingStub = pDevirtMD->IsInstantiatingStub(); info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); - info->needsMethodContext = true; - } - else if (isGenericVirtual) - { - // We don't support shared generic methods yet so this should always be false - info->needsMethodContext = false; - // We don't produce an instantiating stub - info->isInstantiatingStub = false; - info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); + info->needsMethodContext = needsMethodContext; } else { From 555ba83f6f9848b029ddab55bd0720dfac7c10c7 Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 18 Jan 2026 21:02:21 +0900 Subject: [PATCH 02/52] Update src/coreclr/vm/jitinterface.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/jitinterface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 956ed523cde1d1..c497ebbbd7600f 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8793,7 +8793,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) MethodDesc* pPrimaryMD = pDevirtMD; pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true); - if (pDevirtMD->IsSharedByGenericMethodInstantiations()) { pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( From 25c0967d2fa9aef53dcf35fb943c5c5b256de398 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 18 Jan 2026 22:14:14 +0900 Subject: [PATCH 03/52] A better way to check runtime lookup --- src/coreclr/inc/corinfo.h | 2 ++ src/coreclr/inc/jiteeversionguid.h | 10 +++++----- src/coreclr/jit/importercalls.cpp | 14 ++++---------- .../tools/Common/JitInterface/CorInfoImpl.cs | 1 + .../tools/Common/JitInterface/CorInfoTypes.cs | 3 +++ src/coreclr/vm/jitinterface.cpp | 3 +++ 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index f6922867abcfee..dedab832f70cf7 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1582,6 +1582,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO // - isInstantiatingStub is set to TRUE if the devirtualized method is a generic method instantiating stub // - needsMethodContext is set TRUE if the devirtualized method may require a method context // (in which case the method handle and context will be a generic method) + // - needsRuntimeLookup is set TRUE if the devirtualized generic method requires a runtime lookup // CORINFO_METHOD_HANDLE devirtualizedMethod; CORINFO_CONTEXT_HANDLE exactContext; @@ -1590,6 +1591,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; bool isInstantiatingStub; bool needsMethodContext; + bool needsRuntimeLookup; }; //---------------------------------------------------------------------------- diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 5f7f639550fb31..9816fd7d1744fa 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 976a4d6d-d1b2-4096-a3c9-46ddcae71196 */ - 0x976a4d6d, - 0xd1b2, - 0x4096, - {0xa3, 0xc9, 0x46, 0xdd, 0xca, 0xe7, 0x11, 0x96} +constexpr GUID JITEEVersionIdentifier = { /* fc5f63e7-921b-4091-b920-8df8d7b872c1 */ + 0xfc5f63e7, + 0x921b, + 0x4091, + {0xb9, 0x20, 0x8d, 0xf8, 0xd7, 0xb8, 0x72, 0xc1} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index ef53bfb55c08a6..6b44f3ee7b72b8 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8853,17 +8853,11 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, assert((derivedMethod == NO_METHOD_HANDLE) || (instantiatingStub != NO_METHOD_HANDLE)); } - if (call->IsGenericVirtual(this) && dvInfo.needsMethodContext) + if (call->IsGenericVirtual(this) && dvInfo.needsRuntimeLookup) { - CallArg* lookupArg = call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle); - assert(lookupArg != nullptr); - - if (lookupArg->GetNode()->OperIs(GT_RUNTIMELOOKUP)) - { - // If we need a runtime lookup, we can't devirtualize yet because we don't have the right generic context. - JITDUMP("Generic virtual method devirt: runtime lookup present, sorry.\n"); - return; - } + // If we need a runtime lookup, we can't devirtualize yet because we don't have the right generic context. + JITDUMP("Generic virtual method devirt: runtime lookup present, sorry.\n"); + return; } // If we failed to get a method handle, we can't directly devirtualize. diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 85d416c833878b..bc7529f7fd35ee 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1325,6 +1325,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; info->isInstantiatingStub = false; info->needsMethodContext = false; + info->needsRuntimeLookup = false; TypeDesc objType = HandleToObject(info->objClass); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 2ae65728f235e5..4c4568c97c6f28 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1161,6 +1161,7 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO // - isInstantiatingStub is set to TRUE if the devirtualized method is a method instantiation stub // - needsMethodContext is set TRUE if the devirtualized method may require a method context // (in which case the method handle and context will be a generic method) + // - needsRuntimeLookup is set TRUE if the devirtualized generic method requires a runtime lookup // public CORINFO_METHOD_STRUCT_* devirtualizedMethod; public CORINFO_CONTEXT_STRUCT* exactContext; @@ -1171,6 +1172,8 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO public bool isInstantiatingStub { get { return _isInstantiatingStub != 0; } set { _isInstantiatingStub = value ? (byte)1 : (byte)0; } } public byte _needsMethodContext; public bool needsMethodContext { get { return _needsMethodContext != 0; } set { _needsMethodContext = value ? (byte)1 : (byte)0; } } + public byte _needsRuntimeLookup; + public bool needsRuntimeLookup { get { return _needsRuntimeLookup != 0; } set { _needsRuntimeLookup = value ? (byte)1 : (byte)0; } } } //---------------------------------------------------------------------------- diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index c497ebbbd7600f..360291c14558a4 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8553,6 +8553,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) memset(&info->resolvedTokenDevirtualizedUnboxedMethod, 0, sizeof(info->resolvedTokenDevirtualizedUnboxedMethod)); info->isInstantiatingStub = false; info->needsMethodContext = false; + info->needsRuntimeLookup = false; MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8797,6 +8798,8 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) { pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); + info->needsRuntimeLookup = pDevirtMD->IsWrapperStub() && + (pExactMT->IsSharedByGenericInstantiations() || TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation())); needsMethodContext = true; } From 65a61bb189c9c7396fee44b457d627530efaed43 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 18 Jan 2026 22:21:05 +0900 Subject: [PATCH 04/52] Add a TODO --- src/coreclr/jit/importercalls.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 6b44f3ee7b72b8..b40e428be67e3c 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8856,6 +8856,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (call->IsGenericVirtual(this) && dvInfo.needsRuntimeLookup) { // If we need a runtime lookup, we can't devirtualize yet because we don't have the right generic context. + // TODO-CQ: resolve this later when we have the right context. JITDUMP("Generic virtual method devirt: runtime lookup present, sorry.\n"); return; } From c8b7f6952398e72a34e3a22227e5994b5d661c00 Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 2 Feb 2026 03:06:44 +0900 Subject: [PATCH 05/52] Add support for runtime lookup as well --- src/coreclr/jit/importer.cpp | 7 +++ src/coreclr/jit/importercalls.cpp | 32 +++++++++---- src/coreclr/jit/morph.cpp | 2 - src/coreclr/vm/jitinterface.cpp | 78 +++++++++++++++++++++++++------ 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index f9785145fd823d..386ff3eb362c16 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1406,6 +1406,13 @@ GenTree* Compiler::impLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, return nullptr; } + // If importation has finished, we cannot rely on importer helpers that may append + // statements (e.g. impRuntimeLookupToTree spills helper calls to temps via impAppendTree). + if (fgImportDone) + { + return getLookupTree(pResolvedToken, pLookup, handleFlags, compileTimeHandle); + } + // Need to use dictionary-based access which depends on the typeContext // which is only available at runtime, not at compile-time. return impRuntimeLookupToTree(pResolvedToken, pLookup, compileTimeHandle); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index e5c1aa96646e7d..2c1f48ddf56701 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8855,13 +8855,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, assert((derivedMethod == NO_METHOD_HANDLE) || (instantiatingStub != NO_METHOD_HANDLE)); } - if (call->IsGenericVirtual(this) && dvInfo.needsRuntimeLookup) - { - // If we need a runtime lookup, we can't devirtualize yet because we don't have the right generic context. - // TODO-CQ: resolve this later when we have the right context. - JITDUMP("Generic virtual method devirt: runtime lookup present, sorry.\n"); - return; - } + const bool gvmNeedsInstParamRuntimeLookup = call->IsGenericVirtual(this) && dvInfo.needsRuntimeLookup; // If we failed to get a method handle, we can't directly devirtualize. // @@ -8941,6 +8935,28 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } + // If we are devirtualizing a generic virtual method and the target requires a runtime lookup, + // materialize the correct instantiation argument for the implementing method. + if (gvmNeedsInstParamRuntimeLookup) + { + if ((pDerivedResolvedToken == nullptr) || (pDerivedResolvedToken->tokenScope == nullptr)) + { + JITDUMP("Generic virtual method devirt: missing derived token for runtime lookup, sorry.\n"); + return; + } + + GenTree* instParam = impTokenToHandle(pDerivedResolvedToken, nullptr, true /* mustRestoreHandle */); + + if (instParam == nullptr) + { + // If we're inlining, impTokenToHandle can return nullptr after recording a fatal observation. + JITDUMP("Generic virtual method devirt: failed to create instantiation argument, sorry.\n"); + return; + } + + call->gtArgs.InsertInstParam(this, instParam); + } + // All checks done. Time to transform the call. // assert(canDevirtualize); @@ -8948,7 +8964,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP(" %s; can devirtualize\n", note); - if (dvInfo.isInstantiatingStub) + if (!gvmNeedsInstParamRuntimeLookup && dvInfo.isInstantiatingStub) { // Pass the instantiating stub method desc as the inst param arg. // diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 96d72b392793e0..90d8b3cab18140 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5591,8 +5591,6 @@ GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_LOOKUP* pLookup, void* compileTimeHandle) { - assert(!compIsForInlining()); - CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; // If pRuntimeLookup->indirections is equal to CORINFO_USEHELPER, it specifies that a run-time helper should be diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 70b94edb3ad55f..78d33ac476ccf3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3223,9 +3223,14 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr mdMethodDef methodToken = pTemplateMD->GetMemberDef(); DWORD methodFlags = 0; - // Check for non-NULL method spec first. We can encode the method instantiation only if we have one in method spec to start with. Note that there are weird cases - // like instantiating stub for generic method definition that do not have method spec but that won't be caught by the later conditions either. - BOOL fMethodNeedsInstantiation = (pResolvedToken->pMethodSpec != NULL) && pTemplateMD->HasMethodInstantiation() && !pTemplateMD->IsGenericMethodDefinition(); + // Check for non-NULL method spec first. We can normally encode the method instantiation only if we have one in method spec to start with. + // + // However, for tokens synthesized by devirtualization we may not have a MethodSpec available. + // In that case, we can encode the method instantiation directly from the MethodDesc. + const BOOL haveMethodSpec = (pResolvedToken->pMethodSpec != NULL); + const BOOL allowNoMethodSpecInstantiation = (pResolvedToken->tokenType == CORINFO_TOKENKIND_DevirtualizedMethod); + BOOL fMethodNeedsInstantiation = pTemplateMD->HasMethodInstantiation() && !pTemplateMD->IsGenericMethodDefinition() && + (haveMethodSpec || allowNoMethodSpecInstantiation); if (pTemplateMD->IsUnboxingStub()) methodFlags |= ENCODE_METHOD_SIG_UnboxingStub; @@ -3271,23 +3276,43 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr if (fMethodNeedsInstantiation) { - SigPointer sigptr(pResolvedToken->pMethodSpec, pResolvedToken->cbMethodSpec); + if (haveMethodSpec) + { + SigPointer sigptr(pResolvedToken->pMethodSpec, pResolvedToken->cbMethodSpec); - BYTE etype; - IfFailThrow(sigptr.GetByte(&etype)); + BYTE etype; + IfFailThrow(sigptr.GetByte(&etype)); - // Load the generic method instantiation - THROW_BAD_FORMAT_MAYBE(etype == (BYTE)IMAGE_CEE_CS_CALLCONV_GENERICINST, 0, pModule); + // Load the generic method instantiation + THROW_BAD_FORMAT_MAYBE(etype == (BYTE)IMAGE_CEE_CS_CALLCONV_GENERICINST, 0, pModule); - uint32_t nGenericMethodArgs; - IfFailThrow(sigptr.GetData(&nGenericMethodArgs)); - sigBuilder.AppendData(nGenericMethodArgs); + uint32_t nGenericMethodArgs; + IfFailThrow(sigptr.GetData(&nGenericMethodArgs)); + sigBuilder.AppendData(nGenericMethodArgs); - _ASSERTE(nGenericMethodArgs == pTemplateMD->GetNumGenericMethodArgs()); + _ASSERTE(nGenericMethodArgs == pTemplateMD->GetNumGenericMethodArgs()); - for (DWORD i = 0; i < nGenericMethodArgs; i++) + for (DWORD i = 0; i < nGenericMethodArgs; i++) + { + sigptr.ConvertToInternalExactlyOne(pModule, NULL, &sigBuilder); + } + } + else { - sigptr.ConvertToInternalExactlyOne(pModule, NULL, &sigBuilder); + // Devirtualization synthesized token without MethodSpec: encode method instantiation + // directly from the instantiated MethodDesc. + Instantiation methodInst = pTemplateMD->GetMethodInstantiation(); + uint32_t nGenericMethodArgs = methodInst.GetNumArgs(); + + sigBuilder.AppendData(nGenericMethodArgs); + _ASSERTE(nGenericMethodArgs == pTemplateMD->GetNumGenericMethodArgs()); + + for (DWORD i = 0; i < nGenericMethodArgs; i++) + { + TypeHandle instArg = methodInst[i]; + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(instArg.AsPtr()); + } } } } @@ -8839,6 +8864,31 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) isGenericVirtual = true; } + + // Synthesize a resolved token for the devirtualized target so the JIT can + // materialize instantiation parameters for GVMs that require runtime lookup. + // + if (isGenericVirtual && needsMethodContext) + { + CORINFO_RESOLVED_TOKEN* const pTok = &info->resolvedTokenDevirtualizedMethod; + + pTok->tokenContext = info->context; + pTok->tokenScope = GetScopeHandle(pDevirtMD->GetModule()); + pTok->token = pDevirtMD->GetMemberDef(); + pTok->tokenType = CORINFO_TOKENKIND_DevirtualizedMethod; + + pTok->hClass = (CORINFO_CLASS_HANDLE)pExactMT; + pTok->hMethod = (CORINFO_METHOD_HANDLE)pDevirtMD; + pTok->hField = NULL; + + // Devirtualization-synthesized tokens are handled in ComputeRuntimeLookupForSharedGenericToken + // when a runtime lookup needs to encode method the instantiation, so we don't need to provide + // the MethodSpec here. + pTok->pTypeSpec = NULL; + pTok->cbTypeSpec = 0; + pTok->pMethodSpec = NULL; + pTok->cbMethodSpec = 0; + } // Success! Pass back the results. // From 0aea18010f4e7d4a25d8c4d1e66f282e494d81af Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 2 Feb 2026 04:04:11 +0900 Subject: [PATCH 06/52] Try another approach --- src/coreclr/jit/fginline.cpp | 6 ++- src/coreclr/jit/importercalls.cpp | 2 + src/coreclr/jit/inline.h | 2 + src/coreclr/vm/jitinterface.cpp | 69 +++++++++++-------------------- 4 files changed, 34 insertions(+), 45 deletions(-) diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 7cb39262367923..5084f8f33d5dfe 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -610,9 +610,13 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorIsTailPrefixedCall(); + CORINFO_RESOLVED_TOKEN resolvedToken{}; + resolvedToken.cbMethodSpec = call->gtLateDevirtualizationInfo->methodSpecSize; + resolvedToken.pMethodSpec = call->gtLateDevirtualizationInfo->methodSpec; + CORINFO_CONTEXT_HANDLE contextInput = context; context = nullptr; - m_compiler->impDevirtualizeCall(call, nullptr, &method, &methodFlags, &contextInput, &context, + m_compiler->impDevirtualizeCall(call, &resolvedToken, &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 2c1f48ddf56701..9dfd50f54d78e8 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1295,6 +1295,8 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->inlinersContext = compInlineContext; + info->methodSpec = pResolvedToken->pMethodSpec; + info->methodSpecSize = pResolvedToken->cbMethodSpec; call->AsCall()->gtLateDevirtualizationInfo = info; } } diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index 4021e420a5ff23..b92e3add64f943 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -637,6 +637,8 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; InlineContext* inlinersContext; + PCCOR_SIGNATURE methodSpec; + unsigned methodSpecSize; }; // InlArgInfo describes inline candidate argument properties. diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 78d33ac476ccf3..afb47f4b76b2c6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3223,14 +3223,9 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr mdMethodDef methodToken = pTemplateMD->GetMemberDef(); DWORD methodFlags = 0; - // Check for non-NULL method spec first. We can normally encode the method instantiation only if we have one in method spec to start with. - // - // However, for tokens synthesized by devirtualization we may not have a MethodSpec available. - // In that case, we can encode the method instantiation directly from the MethodDesc. - const BOOL haveMethodSpec = (pResolvedToken->pMethodSpec != NULL); - const BOOL allowNoMethodSpecInstantiation = (pResolvedToken->tokenType == CORINFO_TOKENKIND_DevirtualizedMethod); - BOOL fMethodNeedsInstantiation = pTemplateMD->HasMethodInstantiation() && !pTemplateMD->IsGenericMethodDefinition() && - (haveMethodSpec || allowNoMethodSpecInstantiation); + // Check for non-NULL method spec first. We can encode the method instantiation only if we have one in method spec to start with. Note that there are weird cases + // like instantiating stub for generic method definition that do not have method spec but that won't be caught by the later conditions either. + BOOL fMethodNeedsInstantiation = (pResolvedToken->pMethodSpec != NULL) && pTemplateMD->HasMethodInstantiation() && !pTemplateMD->IsGenericMethodDefinition(); if (pTemplateMD->IsUnboxingStub()) methodFlags |= ENCODE_METHOD_SIG_UnboxingStub; @@ -3276,43 +3271,23 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr if (fMethodNeedsInstantiation) { - if (haveMethodSpec) - { - SigPointer sigptr(pResolvedToken->pMethodSpec, pResolvedToken->cbMethodSpec); + SigPointer sigptr(pResolvedToken->pMethodSpec, pResolvedToken->cbMethodSpec); - BYTE etype; - IfFailThrow(sigptr.GetByte(&etype)); + BYTE etype; + IfFailThrow(sigptr.GetByte(&etype)); - // Load the generic method instantiation - THROW_BAD_FORMAT_MAYBE(etype == (BYTE)IMAGE_CEE_CS_CALLCONV_GENERICINST, 0, pModule); + // Load the generic method instantiation + THROW_BAD_FORMAT_MAYBE(etype == (BYTE)IMAGE_CEE_CS_CALLCONV_GENERICINST, 0, pModule); - uint32_t nGenericMethodArgs; - IfFailThrow(sigptr.GetData(&nGenericMethodArgs)); - sigBuilder.AppendData(nGenericMethodArgs); + uint32_t nGenericMethodArgs; + IfFailThrow(sigptr.GetData(&nGenericMethodArgs)); + sigBuilder.AppendData(nGenericMethodArgs); - _ASSERTE(nGenericMethodArgs == pTemplateMD->GetNumGenericMethodArgs()); + _ASSERTE(nGenericMethodArgs == pTemplateMD->GetNumGenericMethodArgs()); - for (DWORD i = 0; i < nGenericMethodArgs; i++) - { - sigptr.ConvertToInternalExactlyOne(pModule, NULL, &sigBuilder); - } - } - else + for (DWORD i = 0; i < nGenericMethodArgs; i++) { - // Devirtualization synthesized token without MethodSpec: encode method instantiation - // directly from the instantiated MethodDesc. - Instantiation methodInst = pTemplateMD->GetMethodInstantiation(); - uint32_t nGenericMethodArgs = methodInst.GetNumArgs(); - - sigBuilder.AppendData(nGenericMethodArgs); - _ASSERTE(nGenericMethodArgs == pTemplateMD->GetNumGenericMethodArgs()); - - for (DWORD i = 0; i < nGenericMethodArgs; i++) - { - TypeHandle instArg = methodInst[i]; - sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); - sigBuilder.AppendPointer(instArg.AsPtr()); - } + sigptr.ConvertToInternalExactlyOne(pModule, NULL, &sigBuilder); } } } @@ -8870,6 +8845,14 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) // if (isGenericVirtual && needsMethodContext) { + if (info->pResolvedTokenVirtualMethod == nullptr) + { + // We don't have enough information to synthesize the token. + // Just bail. + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + CORINFO_RESOLVED_TOKEN* const pTok = &info->resolvedTokenDevirtualizedMethod; pTok->tokenContext = info->context; @@ -8881,13 +8864,11 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pTok->hMethod = (CORINFO_METHOD_HANDLE)pDevirtMD; pTok->hField = NULL; - // Devirtualization-synthesized tokens are handled in ComputeRuntimeLookupForSharedGenericToken - // when a runtime lookup needs to encode method the instantiation, so we don't need to provide - // the MethodSpec here. pTok->pTypeSpec = NULL; pTok->cbTypeSpec = 0; - pTok->pMethodSpec = NULL; - pTok->cbMethodSpec = 0; + + pTok->pMethodSpec = info->pResolvedTokenVirtualMethod->pMethodSpec; + pTok->cbMethodSpec = info->pResolvedTokenVirtualMethod->cbMethodSpec; } // Success! Pass back the results. From 5fbd527264324d7528b20e167d7c1df914344995 Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 2 Feb 2026 04:06:43 +0900 Subject: [PATCH 07/52] Nit --- 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 9dfd50f54d78e8..2d9a1146680a95 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8941,7 +8941,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // materialize the correct instantiation argument for the implementing method. if (gvmNeedsInstParamRuntimeLookup) { - if ((pDerivedResolvedToken == nullptr) || (pDerivedResolvedToken->tokenScope == nullptr)) + if (pDerivedResolvedToken->tokenScope == nullptr) { JITDUMP("Generic virtual method devirt: missing derived token for runtime lookup, sorry.\n"); return; From eb692c5d20e60484a4fc754d9987e5562d998d55 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 01:00:22 +0900 Subject: [PATCH 08/52] Introduce a new kind of dictionary entry and switch to instParamLookup --- src/coreclr/inc/corinfo.h | 10 +- src/coreclr/jit/compiler.h | 2 + src/coreclr/jit/importercalls.cpp | 156 +++++++++++------- .../Compiler/DevirtualizationManager.cs | 7 - .../Internal/Runtime/ReadyToRunConstants.cs | 1 + .../tools/Common/JitInterface/CorInfoImpl.cs | 37 ++++- .../tools/Common/JitInterface/CorInfoTypes.cs | 13 +- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 26 ++- .../tools/superpmi/superpmi-shared/agnostic.h | 3 +- .../superpmi-shared/methodcontext.cpp | 13 +- src/coreclr/vm/genericdict.cpp | 3 +- src/coreclr/vm/genericdict.h | 1 + src/coreclr/vm/jitinterface.cpp | 75 ++++----- 13 files changed, 201 insertions(+), 146 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index cce7db4d649518..e903808da3bdac 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1579,19 +1579,15 @@ struct CORINFO_DEVIRTUALIZATION_INFO // - details on the computation done by the jit host // - If pResolvedTokenDevirtualizedMethod is not set to NULL and targeting an R2R image // use it as the parameter to getCallInfo - // - isInstantiatingStub is set to TRUE if the devirtualized method is a generic method instantiating stub - // - needsMethodContext is set TRUE if the devirtualized method may require a method context - // (in which case the method handle and context will be a generic method) - // - needsRuntimeLookup is set TRUE if the devirtualized generic method requires a runtime lookup + // - instParamLookup contains all the information necessary to pass the instantiation parameter for + // the devirtualized method. // CORINFO_METHOD_HANDLE devirtualizedMethod; CORINFO_CONTEXT_HANDLE exactContext; CORINFO_DEVIRTUALIZATION_DETAIL detail; CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; - bool isInstantiatingStub; - bool needsMethodContext; - bool needsRuntimeLookup; + CORINFO_LOOKUP instParamLookup; }; //---------------------------------------------------------------------------- diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 7800da07f0abb2..2b87dbeb09a973 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5202,6 +5202,8 @@ class Compiler methodPointerInfo* impAllocateMethodPointerInfo(const CORINFO_RESOLVED_TOKEN& token, mdToken tokenConstrained); + static bool impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo); + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 2d9a1146680a95..d4cae63b2e3534 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7496,6 +7496,24 @@ bool Compiler::isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gd return true; } +//------------------------------------------------------------------------ +// impDevirtualizedCallHasConstInstParam: check if the instantiation argument is a compile-time lookup. +// +// Arguments: +// dvInfo - Devirtualization information returned by resolveVirtualMethod. +// +// Returns: +// true if instParamLookup describes a valid constant handle/address lookup. +// +bool Compiler::impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo) +{ + return !dvInfo.instParamLookup.lookupKind.needsRuntimeLookup && + ((dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + dvInfo.instParamLookup.constLookup.handle != nullptr) || + (dvInfo.instParamLookup.constLookup.accessType == IAT_PVALUE && + dvInfo.instParamLookup.constLookup.addr != nullptr)); +} + //------------------------------------------------------------------------ // considerGuardedDevirtualization: see if we can profitably guess at the // class involved in an interface or virtual call. @@ -7615,6 +7633,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; CORINFO_METHOD_HANDLE exactMethod = dvInfo.devirtualizedMethod; uint32_t exactMethodAttrs = info.compCompHnd->getMethodAttribs(exactMethod); + const bool exactNeedsMethodContext = + ((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; // NOTE: This is currently used only with NativeAOT. In theory, we could also check if we // have static PGO data to decide which class to guess first. Presumably, this is a rare case. @@ -7629,8 +7649,9 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, } addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, - clsAttrs, likelyHood, dvInfo.needsMethodContext, - dvInfo.isInstantiatingStub, baseMethod, originalContext); + clsAttrs, likelyHood, exactNeedsMethodContext, + impDevirtualizedCallHasConstInstParam(dvInfo), baseMethod, + originalContext); } if (call->GetInlineCandidatesCount() == numExactClasses) @@ -7702,8 +7723,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyContext = dvInfo.exactContext; likelyMethod = dvInfo.devirtualizedMethod; - needsMethodContext = dvInfo.needsMethodContext; - instantiatingStub = dvInfo.isInstantiatingStub; + needsMethodContext = ((size_t)likelyContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; + instantiatingStub = impDevirtualizedCallHasConstInstParam(dvInfo); } else { @@ -8782,10 +8803,13 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, info.compCompHnd->resolveVirtualMethod(&dvInfo); - CORINFO_METHOD_HANDLE derivedMethod = dvInfo.devirtualizedMethod; - CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; - CORINFO_CLASS_HANDLE derivedClass = NO_CLASS_HANDLE; - CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; + CORINFO_METHOD_HANDLE derivedMethod = dvInfo.devirtualizedMethod; + CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; + CORINFO_CLASS_HANDLE derivedClass = NO_CLASS_HANDLE; + CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; + const bool needsRuntimeLookup = dvInfo.instParamLookup.lookupKind.needsRuntimeLookup; + const bool needsCompileTimeLookup = impDevirtualizedCallHasConstInstParam(dvInfo); + const bool needsInstParam = needsRuntimeLookup || needsCompileTimeLookup; if (derivedMethod != nullptr) { @@ -8793,7 +8817,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) { - assert(!dvInfo.needsMethodContext); + assert(!needsInstParam); derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } else @@ -8801,7 +8825,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class. // Generic virtual method devirt also returns a generic method. // - assert(call->IsGenericVirtual(this) || dvInfo.needsMethodContext); + assert(call->IsGenericVirtual(this) || needsInstParam); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } @@ -8820,45 +8844,66 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; - if (dvInfo.isInstantiatingStub) + if (needsInstParam) { - // We should only end up with generic methods that needs a method context (eg. array interface, GVM). - // - assert(dvInfo.needsMethodContext); + if (needsCompileTimeLookup) + { + // We should only end up with generic methods that need a method context (eg. array interface, GVM). + // + assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); - // We don't expect R2R/NAOT to end up here for array interface devirtualization. - // For NAOT, it has Array and normal devirtualization. - // For R2R, we don't (yet) support array interface devirtualization. - assert(call->IsGenericVirtual(this) || !IsAot()); + if (dvInfo.instParamLookup.constLookup.accessType != IAT_VALUE) + { + JITDUMP("Unsupported devirt instantiation lookup access type %d\n", + dvInfo.instParamLookup.constLookup.accessType); + return; + } - // We don't expect there to be an existing inst param arg. - // - CallArg* const instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam); - if (instParam != nullptr) + instantiatingStub = (CORINFO_METHOD_HANDLE)dvInfo.instParamLookup.constLookup.handle; + + // We don't expect R2R/NAOT to end up here for array interface devirtualization. + // For NAOT, it has Array and normal devirtualization. + // For R2R, we don't (yet) support array interface devirtualization. + assert(call->IsGenericVirtual(this) || !IsAot()); + + // If we don't know the array type exactly we may have the wrong interface type here. + // Bail out. + // + if (!isExact) + { + JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); + return; + } + } + + // Devirtualization can return an instantiating stub. If so, switch to the wrapped + // entrypoint and capture the stub as the instantiation argument source. + CORINFO_METHOD_HANDLE discoveredInstantiatingStub = NO_METHOD_HANDLE; + CORINFO_CLASS_HANDLE ignoredClassArg = NO_CLASS_HANDLE; + CORINFO_METHOD_HANDLE wrappedMethod = + info.compCompHnd->getInstantiatedEntry(derivedMethod, &discoveredInstantiatingStub, &ignoredClassArg); + + assert(ignoredClassArg == NO_CLASS_HANDLE); + if (wrappedMethod != NO_METHOD_HANDLE) { - assert(!"unexpected inst param in virtual/interface call"); - return; + derivedMethod = wrappedMethod; } - // If we don't know the array type exactly we may have the wrong interface type here. - // Bail out. - // - if (!isExact) + if (discoveredInstantiatingStub != NO_METHOD_HANDLE) { - JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); - return; + if (instantiatingStub == NO_METHOD_HANDLE) + { + instantiatingStub = discoveredInstantiatingStub; + } + else + { + assert(instantiatingStub == discoveredInstantiatingStub); + } } - // We want to inline the instantiating stub. Fetch the relevant info. - // - CORINFO_CLASS_HANDLE ignored = NO_CLASS_HANDLE; - derivedMethod = info.compCompHnd->getInstantiatedEntry(derivedMethod, &instantiatingStub, &ignored); - assert(ignored == NO_CLASS_HANDLE); - assert((derivedMethod == NO_METHOD_HANDLE) || (instantiatingStub != NO_METHOD_HANDLE)); + assert(!needsCompileTimeLookup || (instantiatingStub != NO_METHOD_HANDLE)); } - const bool gvmNeedsInstParamRuntimeLookup = call->IsGenericVirtual(this) && dvInfo.needsRuntimeLookup; - // If we failed to get a method handle, we can't directly devirtualize. // // This can happen with AOT, if the devirtualization crosses @@ -8887,7 +8932,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, { note = "final method"; } - if (dvInfo.isInstantiatingStub) + if (needsInstParam) { instArg = " [instantiating stub]"; } @@ -8937,22 +8982,26 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - // If we are devirtualizing a generic virtual method and the target requires a runtime lookup, - // materialize the correct instantiation argument for the implementing method. - if (gvmNeedsInstParamRuntimeLookup) + // Insert the instantiation argument when necessary. + if (needsInstParam) { - if (pDerivedResolvedToken->tokenScope == nullptr) + assert(call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam) == nullptr); + + CORINFO_METHOD_HANDLE compileTimeHandle = derivedMethod; + if (needsCompileTimeLookup) { - JITDUMP("Generic virtual method devirt: missing derived token for runtime lookup, sorry.\n"); - return; + compileTimeHandle = instantiatingStub; } - GenTree* instParam = impTokenToHandle(pDerivedResolvedToken, nullptr, true /* mustRestoreHandle */); + CORINFO_RESOLVED_TOKEN* lookupToken = + (pDerivedResolvedToken->tokenScope != nullptr) ? pDerivedResolvedToken : pResolvedToken; + GenTree* instParam = + impLookupToTree(lookupToken, &dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); if (instParam == nullptr) { - // If we're inlining, impTokenToHandle can return nullptr after recording a fatal observation. - JITDUMP("Generic virtual method devirt: failed to create instantiation argument, sorry.\n"); + // If we're inlining, impLookupToTree can return nullptr after recording a fatal observation. + JITDUMP("Failed to materialize instantiation argument for devirtualized call, sorry.\n"); return; } @@ -8966,17 +9015,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP(" %s; can devirtualize\n", note); - if (!gvmNeedsInstParamRuntimeLookup && dvInfo.isInstantiatingStub) - { - // Pass the instantiating stub method desc as the inst param arg. - // - // Note different embedding would be needed for NAOT/R2R, - // but we have ruled those out above. - // - GenTree* const instParam = gtNewIconEmbMethHndNode(instantiatingStub); - call->gtArgs.InsertInstParam(this, instParam); - } - // Make the updates. call->gtFlags &= ~GTF_CALL_VIRT_VTABLE; call->gtFlags &= ~GTF_CALL_VIRT_STUB; diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index ac544ed02e3fea..0ceb7197625faf 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -219,13 +219,6 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } } - if (impl != null && impl.HasInstantiation && impl.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific)) - { - // We don't support devirtualization of shared generic virtual methods yet. - devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; - impl = null; - } - return impl; } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index 0f607102a34400..e8283f82e2f3e8 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -121,6 +121,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 a1c33f7c9518f6..ac373ab472fad8 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1345,9 +1345,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->devirtualizedMethod = null; info->exactContext = null; info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; - info->isInstantiatingStub = false; - info->needsMethodContext = false; - info->needsRuntimeLookup = false; + info->instParamLookup = default(CORINFO_LOOKUP); TypeDesc objType = HandleToObject(info->objClass); @@ -1478,6 +1476,36 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->resolvedTokenDevirtualizedUnboxedMethod = default(CORINFO_RESOLVED_TOKEN); } + bool isArrayInterfaceDevirtualization = objType.IsArray && decl.OwningType.IsInterface; + bool isGenericVirtual = decl.HasInstantiation; + + if (isGenericVirtual && impl.IsSharedByGenericInstantiations) + { + if (info->pResolvedTokenVirtualMethod == null) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + +#if READYTORUN + ComputeRuntimeLookupForSharedGenericToken( + Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, + ref info->resolvedTokenDevirtualizedMethod, + pConstrainedResolvedToken: null, + impl, + MethodBeingCompiled, + ref info->instParamLookup); +#else + object runtimeDeterminedMethod = GetRuntimeDeterminedObjectForToken(ref info->resolvedTokenDevirtualizedMethod); + ComputeLookup( + ref info->resolvedTokenDevirtualizedMethod, + runtimeDeterminedMethod, + ReadyToRunHelperId.MethodHandle, + MethodBeingCompiled, + ref info->instParamLookup); +#endif + } + #if READYTORUN // Testing has not shown that concerns about virtual matching are significant // Only generate verification for builds with the stress mode enabled @@ -1492,8 +1520,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) #endif info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_SUCCESS; info->devirtualizedMethod = ObjectToHandle(impl); - info->isInstantiatingStub = false; - info->exactContext = contextFromType(owningType); + info->exactContext = (isArrayInterfaceDevirtualization || isGenericVirtual) ? contextFromMethod(impl) : contextFromType(owningType); return true; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 501538806c0986..671461137d4aca 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1161,22 +1161,15 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO // invariant is `resolveVirtualMethod(...) == (devirtualizedMethod != nullptr)`. // - exactContext is set to wrapped CORINFO_CLASS_HANDLE of devirt'ed method table. // - detail describes the computation done by the jit host - // - isInstantiatingStub is set to TRUE if the devirtualized method is a method instantiation stub - // - needsMethodContext is set TRUE if the devirtualized method may require a method context - // (in which case the method handle and context will be a generic method) - // - needsRuntimeLookup is set TRUE if the devirtualized generic method requires a runtime lookup + // - instParamLookup contains all the information necessary to pass the instantiation parameter for + // the devirtualized method. // public CORINFO_METHOD_STRUCT_* devirtualizedMethod; public CORINFO_CONTEXT_STRUCT* exactContext; public CORINFO_DEVIRTUALIZATION_DETAIL detail; public CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; public CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; - public byte _isInstantiatingStub; - public bool isInstantiatingStub { get { return _isInstantiatingStub != 0; } set { _isInstantiatingStub = value ? (byte)1 : (byte)0; } } - public byte _needsMethodContext; - public bool needsMethodContext { get { return _needsMethodContext != 0; } set { _needsMethodContext = value ? (byte)1 : (byte)0; } } - public byte _needsRuntimeLookup; - public bool needsRuntimeLookup { get { return _needsRuntimeLookup != 0; } set { _needsRuntimeLookup = value ? (byte)1 : (byte)0; } } + public CORINFO_LOOKUP instParamLookup; } //---------------------------------------------------------------------------- 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 b75f78f3506431..5fef47d618ba66 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -918,17 +918,23 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref constrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *(CORINFO_RESOLVED_TOKEN*)pGenericLookupKind.runtimeLookupArgs); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, constrainedType); } - object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + ref CORINFO_RESOLVED_TOKEN helperArgToken = ref pResolvedToken; + if (helperId == ReadyToRunHelperId.MethodHandle && pGenericLookupKind.runtimeLookupArgs != null) + { + helperArgToken = ref *(CORINFO_RESOLVED_TOKEN*)pGenericLookupKind.runtimeLookupArgs; + } + + object helperArg = GetRuntimeDeterminedObjectForToken(ref helperArgToken); if (helperArg is MethodDesc methodDesc) { - var methodIL = HandleToObject(pResolvedToken.tokenScope); + var methodIL = HandleToObject(helperArgToken.tokenScope); MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref helperArgToken), constrainedType, unboxing: false, context: sharedMethod); } else if (helperArg is FieldDesc fieldDesc) { - helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref helperArgToken)); } var methodContext = new GenericContext(HandleToObject(callerHandle)); @@ -2644,18 +2650,26 @@ 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) pResultLookup.lookupKind.runtimeLookupFlags = (ushort)ReadyToRunHelperId.MethodHandle; else if (entryKind == DictionaryEntryKind.MethodEntrySlot || entryKind == DictionaryEntryKind.ConstrainedMethodEntrySlot) pResultLookup.lookupKind.runtimeLookupFlags = (ushort)ReadyToRunHelperId.MethodEntry; else pResultLookup.lookupKind.runtimeLookupFlags = (ushort)ReadyToRunHelperId.VirtualDispatchCell; - pResultLookup.lookupKind.runtimeLookupArgs = pConstrainedResolvedToken; + if (entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) + { + pResultLookup.lookupKind.runtimeLookupArgs = Unsafe.AsPointer(ref pResolvedToken); + } + else + { + pResultLookup.lookupKind.runtimeLookupArgs = pConstrainedResolvedToken; + } break; } diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 1ad4fa291dfdeb..75c3a68cf7e285 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -680,12 +680,11 @@ struct Agnostic_ResolveVirtualMethodResult { bool returnValue; DWORDLONG devirtualizedMethod; - bool isInstantiatingStub; - bool needsMethodContext; DWORDLONG exactContext; DWORD detail; Agnostic_CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; Agnostic_CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; + Agnostic_CORINFO_LOOKUP instParamLookup; }; struct Agnostic_GetInstantiatedEntryResult diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 9d8c64356a9a5d..0a4dcc3bf80435 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3250,10 +3250,9 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info Agnostic_ResolveVirtualMethodResult result; result.returnValue = returnValue; result.devirtualizedMethod = CastHandle(info->devirtualizedMethod); - result.isInstantiatingStub = info->isInstantiatingStub; result.exactContext = CastHandle(info->exactContext); result.detail = (DWORD)info->detail; - result.needsMethodContext = info->needsMethodContext; + result.instParamLookup = SpmiRecordsHelper::StoreAgnostic_CORINFO_LOOKUP(&info->instParamLookup); if (returnValue) { @@ -3278,15 +3277,14 @@ void MethodContext::dmpResolveVirtualMethod(const Agnostic_ResolveVirtualMethodK key.context, key.pResolvedTokenVirtualMethodNonNull, key.pResolvedTokenVirtualMethodNonNull ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.pResolvedTokenVirtualMethod).c_str() : "???"); - printf(", value returnValue-%s, devirtMethod-%016" PRIX64 ", instantiatingStub-%s, needsMethodContext-%s, exactContext-%016" PRIX64 ", detail-%d, tokDvMeth{%s}, tokDvUnboxMeth{%s}", + printf(", value returnValue-%s, devirtMethod-%016" PRIX64 ", exactContext-%016" PRIX64 ", detail-%d, tokDvMeth{%s}, tokDvUnboxMeth{%s}, instParamLookup{%s}", result.returnValue ? "true" : "false", result.devirtualizedMethod, - result.isInstantiatingStub ? "true" : "false", - result.needsMethodContext ? "true" : "false", result.exactContext, result.detail, result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedMethod).c_str() : "???", - result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedUnboxedMethod).c_str() : "???"); + result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedUnboxedMethod).c_str() : "???", + SpmiDumpHelper::DumpAgnostic_CORINFO_LOOKUP(result.instParamLookup).c_str()); } bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info) @@ -3306,10 +3304,9 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info DEBUG_REP(dmpResolveVirtualMethod(key, result)); info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) result.devirtualizedMethod; - info->isInstantiatingStub = result.isInstantiatingStub; - info->needsMethodContext = result.needsMethodContext; info->exactContext = (CORINFO_CONTEXT_HANDLE) result.exactContext; info->detail = (CORINFO_DEVIRTUALIZATION_DETAIL) result.detail; + info->instParamLookup = SpmiRecordsHelper::RestoreCORINFO_LOOKUP(result.instParamLookup); if (result.returnValue) { info->resolvedTokenDevirtualizedMethod = SpmiRecordsHelper::Restore_CORINFO_RESOLVED_TOKEN(&result.resolvedTokenDevirtualizedMethod, ResolveToken); diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index e9d15c0fa6bb63..412a1f411facb9 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 957e8bbe1f1d78..36be51e12cf4ed 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3202,11 +3202,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); @@ -8585,9 +8593,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) info->detail = CORINFO_DEVIRTUALIZATION_UNKNOWN; memset(&info->resolvedTokenDevirtualizedMethod, 0, sizeof(info->resolvedTokenDevirtualizedMethod)); memset(&info->resolvedTokenDevirtualizedUnboxedMethod, 0, sizeof(info->resolvedTokenDevirtualizedUnboxedMethod)); - info->isInstantiatingStub = false; - info->needsMethodContext = false; - info->needsRuntimeLookup = false; + memset(&info->instParamLookup, 0, sizeof(info->instParamLookup)); MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8803,7 +8809,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) MethodTable* pExactMT = pApproxMT; bool isArray = false; bool isGenericVirtual = false; - bool needsMethodContext = false; if (pApproxMT->IsInterface()) { @@ -8815,7 +8820,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) else if (pBaseMT->IsInterface() && pObjMT->IsArray()) { isArray = true; - needsMethodContext = true; } else { @@ -8832,58 +8836,47 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) { pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); - info->needsRuntimeLookup = pDevirtMD->IsWrapperStub() && + + const bool requiresRuntimeLookup = pDevirtMD->IsWrapperStub() && (pExactMT->IsSharedByGenericInstantiations() || TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation())); - needsMethodContext = true; + + if (requiresRuntimeLookup) + { + if (info->pResolvedTokenVirtualMethod == nullptr) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + ComputeRuntimeLookupForSharedGenericToken(DevirtualizedMethodDescSlot, + info->pResolvedTokenVirtualMethod, + nullptr, + pDevirtMD, + m_pMethodBeingCompiled, + &info->instParamLookup); + } } isGenericVirtual = true; } - - // Synthesize a resolved token for the devirtualized target so the JIT can - // materialize instantiation parameters for GVMs that require runtime lookup. - // - if (isGenericVirtual && needsMethodContext) - { - if (info->pResolvedTokenVirtualMethod == nullptr) - { - // We don't have enough information to synthesize the token. - // Just bail. - info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; - } - - CORINFO_RESOLVED_TOKEN* const pTok = &info->resolvedTokenDevirtualizedMethod; - - pTok->tokenContext = info->context; - pTok->tokenScope = GetScopeHandle(pDevirtMD->GetModule()); - pTok->token = pDevirtMD->GetMemberDef(); - pTok->tokenType = CORINFO_TOKENKIND_DevirtualizedMethod; - pTok->hClass = (CORINFO_CLASS_HANDLE)pExactMT; - pTok->hMethod = (CORINFO_METHOD_HANDLE)pDevirtMD; - pTok->hField = NULL; - - pTok->pTypeSpec = NULL; - pTok->cbTypeSpec = 0; - - pTok->pMethodSpec = info->pResolvedTokenVirtualMethod->pMethodSpec; - pTok->cbMethodSpec = info->pResolvedTokenVirtualMethod->cbMethodSpec; + // For non-shared cases we pass the instantiating stub MethodDesc as a constant. + if (!info->instParamLookup.lookupKind.needsRuntimeLookup && (isArray || isGenericVirtual) && pDevirtMD->IsInstantiatingStub()) + { + info->instParamLookup.lookupKind.needsRuntimeLookup = false; + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pDevirtMD; + info->instParamLookup.constLookup.accessType = IAT_VALUE; } // Success! Pass back the results. // if (isArray || isGenericVirtual) { - info->isInstantiatingStub = pDevirtMD->IsInstantiatingStub(); info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); - info->needsMethodContext = needsMethodContext; } else { info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); - info->isInstantiatingStub = false; - info->needsMethodContext = false; } info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD; From abd5db242677a9aa9cc3d68b73a44b8182685c49 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 02:18:45 +0900 Subject: [PATCH 09/52] Save resolved token --- src/coreclr/jit/fginline.cpp | 19 ++++++++----------- src/coreclr/jit/importercalls.cpp | 3 +-- src/coreclr/jit/inline.h | 3 +-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 5084f8f33d5dfe..d036963fcd4f00 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -603,20 +603,17 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtLateDevirtualizationInfo->methodHnd; - CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; - InlineContext* inlinersContext = call->gtLateDevirtualizationInfo->inlinersContext; - unsigned methodFlags = 0; - const bool isLateDevirtualization = true; - const bool explicitTailCall = call->IsTailPrefixedCall(); - - CORINFO_RESOLVED_TOKEN resolvedToken{}; - resolvedToken.cbMethodSpec = call->gtLateDevirtualizationInfo->methodSpecSize; - resolvedToken.pMethodSpec = call->gtLateDevirtualizationInfo->methodSpec; + CORINFO_METHOD_HANDLE method = call->gtLateDevirtualizationInfo->methodHnd; + CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; + InlineContext* inlinersContext = call->gtLateDevirtualizationInfo->inlinersContext; + 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, &resolvedToken, &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 d4cae63b2e3534..fc65730c31c1f0 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1295,8 +1295,7 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->inlinersContext = compInlineContext; - info->methodSpec = pResolvedToken->pMethodSpec; - info->methodSpecSize = pResolvedToken->cbMethodSpec; + info->resolvedToken = *pResolvedToken; call->AsCall()->gtLateDevirtualizationInfo = info; } } diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index b92e3add64f943..9aebc1dce4c932 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -637,8 +637,7 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; InlineContext* inlinersContext; - PCCOR_SIGNATURE methodSpec; - unsigned methodSpecSize; + CORINFO_RESOLVED_TOKEN resolvedToken; }; // InlArgInfo describes inline candidate argument properties. From 21783322210c9102775f0afd9bae24598c1671e4 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 02:45:42 +0900 Subject: [PATCH 10/52] Also assert isArrayInterface --- 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 d253317e184428..8f41c48d73fd28 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8824,7 +8824,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class. // Generic virtual method devirt also returns a generic method. // - assert(call->IsGenericVirtual(this) || needsInstParam); + assert(call->IsGenericVirtual(this) || needsInstParam || ((objClassAttribs & CORINFO_FLG_ARRAY) != 0)); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } From d4c9f91820d6e364be0c832f5e71583b2a8fd83c Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 03:23:26 +0900 Subject: [PATCH 11/52] Always use impRuntimeLookupToTree --- src/coreclr/jit/importer.cpp | 46 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index c6d2f437fbdec5..8ac56edc574c14 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1406,13 +1406,6 @@ GenTree* Compiler::impLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, return nullptr; } - // If importation has finished, we cannot rely on importer helpers that may append - // statements (e.g. impRuntimeLookupToTree spills helper calls to temps via impAppendTree). - if (fgImportDone) - { - return getLookupTree(pResolvedToken, pLookup, handleFlags, compileTimeHandle); - } - // Need to use dictionary-based access which depends on the typeContext // which is only available at runtime, not at compile-time. return impRuntimeLookupToTree(pResolvedToken, pLookup, compileTimeHandle); @@ -1698,6 +1691,12 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken assert(pRuntimeLookup->indirections != 0); GenTreeCall* helperCall = gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, ctxTree, compileTimeHandle); + if (fgImportDone) + { + // If importation has finished, we cannot spill the helper call to a temp + return helperCall; + } + // Spilling it to a temp improves CQ (mainly in Tier0) unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); impStoreToTemp(callLclNum, helperCall, CHECK_SPILL_NONE); @@ -1717,10 +1716,34 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken // Applied repeated indirections for (WORD i = 0; i < pRuntimeLookup->indirections; i++) { + GenTree* tempStoreTree = nullptr; + if ((i == 1 && pRuntimeLookup->indirectFirstOffset) || (i == 2 && pRuntimeLookup->indirectSecondOffset)) { - indOffTree = impCloneExpr(slotPtrTree, &slotPtrTree, CHECK_SPILL_ALL, - nullptr DEBUGARG("impRuntimeLookup indirectOffset")); + GenTree* clonedSlotPtrTree = gtClone(slotPtrTree, true); + + if (clonedSlotPtrTree != nullptr) + { + indOffTree = clonedSlotPtrTree; + } + else + { + if (!fgImportDone) + { + indOffTree = impCloneExpr(slotPtrTree, &slotPtrTree, CHECK_SPILL_ALL, + nullptr DEBUGARG("impRuntimeLookup indirectOffset")); + } + else + { + // In post-import phases we cannot spill new temps. + unsigned tempNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup indirectOffset")); + tempStoreTree = gtNewTempStore(tempNum, slotPtrTree, CHECK_SPILL_NONE); + var_types tempTyp = genActualType(lvaGetDesc(tempNum)->TypeGet()); + + indOffTree = gtNewLclvNode(tempNum, tempTyp); + slotPtrTree = gtNewLclvNode(tempNum, tempTyp); + } + } } if (i != 0) @@ -1739,6 +1762,11 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken slotPtrTree = gtNewOperNode(GT_ADD, TYP_I_IMPL, slotPtrTree, gtNewIconNode(pRuntimeLookup->offsets[i], TYP_I_IMPL)); } + + if (tempStoreTree != nullptr) + { + slotPtrTree = gtNewOperNode(GT_COMMA, TYP_I_IMPL, tempStoreTree, slotPtrTree); + } } // No null test required From e96d509bc41a208b43fcb96896db876ee4d86e4b Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 03:43:59 +0900 Subject: [PATCH 12/52] Nit --- src/coreclr/jit/importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 8ac56edc574c14..97cec70630f95f 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1735,7 +1735,7 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken } else { - // In post-import phases we cannot spill new temps. + // In post-import phases we cannot append statements to spill the expression unsigned tempNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup indirectOffset")); tempStoreTree = gtNewTempStore(tempNum, slotPtrTree, CHECK_SPILL_NONE); var_types tempTyp = genActualType(lvaGetDesc(tempNum)->TypeGet()); From 21623ad378f60c03094109a08569db02578758f3 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 05:58:55 +0900 Subject: [PATCH 13/52] Fix inlining in case of runtime lookup not available (R2R) --- .../tools/Common/JitInterface/CorInfoImpl.cs | 7 ++++++ .../JitInterface/CorInfoImpl.ReadyToRun.cs | 15 ++++++++++-- src/coreclr/vm/jitinterface.cpp | 23 +++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index e8be5157da1704..4646262db26c3e 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1504,6 +1504,13 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) MethodBeingCompiled, ref info->instParamLookup); #endif + + if (info->instParamLookup.lookupKind.needsRuntimeLookup && + info->instParamLookup.lookupKind.runtimeLookupKind == CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + return false; + } } #if READYTORUN 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 d45c50ceff87e5..f5af8d2a31ef25 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2632,10 +2632,21 @@ private void ComputeRuntimeLookupForSharedGenericToken( MethodDesc contextMethod = callerHandle; - // There is a pathological case where invalid IL refereces __Canon type directly, but there is no dictionary availabled to store the lookup. + // There is a pathological case where invalid IL references __Canon type directly, but there is no + // dictionary available to store the lookup. + // DevirtualizedMethodDescSlot is an exception only when the caller can provide generic context via `this`. if (!contextMethod.IsSharedByGenericInstantiations) { - ThrowHelper.ThrowInvalidProgramException(); + if (entryKind != DictionaryEntryKind.DevirtualizedMethodDescSlot) + { + ThrowHelper.ThrowInvalidProgramException(); + } + + if (!contextMethod.AcquiresInstMethodTableFromThis()) + { + pResultLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED; + return; + } } if (contextMethod.RequiresInstMethodDescArg()) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index e94aead5f6a05f..68c03bd6195226 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3010,9 +3010,22 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr MethodDesc* pContextMD = pCallerMD; MethodTable* pContextMT = pContextMD->GetMethodTable(); - // There is a pathological case where invalid IL refereces __Canon type directly, but there is no dictionary availabled to store the lookup. + // There is a pathological case where invalid IL references __Canon type directly, but there is no + // dictionary available to store the lookup. + // DevirtualizedMethodDescSlot is an exception only when the caller can provide generic context via `this`. if (!pContextMD->IsSharedByGenericInstantiations()) - COMPlusThrow(kInvalidProgramException); + { + if (entryKind != DevirtualizedMethodDescSlot) + { + COMPlusThrow(kInvalidProgramException); + } + + if (!pContextMD->AcquiresInstMethodTableFromThis()) + { + pResultLookup->lookupKind.runtimeLookupKind = CORINFO_LOOKUP_NOT_SUPPORTED; + return; + } + } if (pContextMD->RequiresInstMethodDescArg()) { @@ -8854,6 +8867,12 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pDevirtMD, m_pMethodBeingCompiled, &info->instParamLookup); + + if (info->instParamLookup.lookupKind.runtimeLookupKind == CORINFO_LOOKUP_NOT_SUPPORTED) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + return false; + } } } From bc7d21036e5734e9b6402fd5283ef95c90c0e81e Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 19:21:00 +0900 Subject: [PATCH 14/52] Address a potential AV --- 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 8f41c48d73fd28..57324f8996e558 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8843,7 +8843,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; - if (needsInstParam) + if (derivedMethod != nullptr && needsInstParam) { if (needsCompileTimeLookup) { From aeca17277c791ce2ad016f4d97406669a40dec66 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 22:11:54 +0900 Subject: [PATCH 15/52] Always call getInstantiatedEntry --- src/coreclr/jit/importercalls.cpp | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 57324f8996e558..cfb7a83c6408b0 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7496,7 +7496,8 @@ bool Compiler::isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gd } //------------------------------------------------------------------------ -// impDevirtualizedCallHasConstInstParam: check if the instantiation argument is a compile-time lookup. +// impDevirtualizedCallHasConstInstParam: check if the instantiation argument +// is a compile-time lookup. // // Arguments: // dvInfo - Devirtualization information returned by resolveVirtualMethod. @@ -8851,15 +8852,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); - if (dvInfo.instParamLookup.constLookup.accessType != IAT_VALUE) - { - JITDUMP("Unsupported devirt instantiation lookup access type %d\n", - dvInfo.instParamLookup.constLookup.accessType); - return; - } - - instantiatingStub = (CORINFO_METHOD_HANDLE)dvInfo.instParamLookup.constLookup.handle; - // We don't expect R2R/NAOT to end up here for array interface devirtualization. // For NAOT, it has Array and normal devirtualization. // For R2R, we don't (yet) support array interface devirtualization. @@ -8877,29 +8869,16 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Devirtualization can return an instantiating stub. If so, switch to the wrapped // entrypoint and capture the stub as the instantiation argument source. - CORINFO_METHOD_HANDLE discoveredInstantiatingStub = NO_METHOD_HANDLE; - CORINFO_CLASS_HANDLE ignoredClassArg = NO_CLASS_HANDLE; + CORINFO_CLASS_HANDLE ignore = NO_CLASS_HANDLE; CORINFO_METHOD_HANDLE wrappedMethod = - info.compCompHnd->getInstantiatedEntry(derivedMethod, &discoveredInstantiatingStub, &ignoredClassArg); + info.compCompHnd->getInstantiatedEntry(derivedMethod, &instantiatingStub, &ignore); - assert(ignoredClassArg == NO_CLASS_HANDLE); + assert(ignore == NO_CLASS_HANDLE); if (wrappedMethod != NO_METHOD_HANDLE) { derivedMethod = wrappedMethod; } - if (discoveredInstantiatingStub != NO_METHOD_HANDLE) - { - if (instantiatingStub == NO_METHOD_HANDLE) - { - instantiatingStub = discoveredInstantiatingStub; - } - else - { - assert(instantiatingStub == discoveredInstantiatingStub); - } - } - assert(!needsCompileTimeLookup || (instantiatingStub != NO_METHOD_HANDLE)); } From aa460d5c335dfb4763a06645c694d916c3293884 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 23:23:39 +0900 Subject: [PATCH 16/52] Include instantiation in the method name --- src/coreclr/jit/importercalls.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index cfb7a83c6408b0..b9d652ae9ae3c6 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8747,7 +8747,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; objClassName = eeGetClassName(objClass); baseClassName = eeGetClassName(baseClass); - baseMethodName = eeGetMethodName(baseMethod); + baseMethodName = eeGetMethodFullName(baseMethod); if (verbose) { @@ -8917,7 +8917,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (verbose || doPrint) { - derivedMethodName = eeGetMethodName(derivedMethod); + derivedMethodName = eeGetMethodFullName(derivedMethod); derivedClassName = eeGetClassName(derivedClass); if (verbose) { From 263a6ae55700690387ec27df99ab47a7ecca2c89 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 23:32:52 +0900 Subject: [PATCH 17/52] Add more information to the dump --- src/coreclr/jit/importercalls.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index b9d652ae9ae3c6..acce3063ba94d5 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8912,7 +8912,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, } if (needsInstParam) { - instArg = " [instantiating stub]"; + instArg = needsCompileTimeLookup ? eeGetMethodFullName(instantiatingStub) : "runtime lookup"; } if (verbose || doPrint) @@ -8921,7 +8921,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, derivedClassName = eeGetClassName(derivedClass); if (verbose) { - printf(" devirt to %s::%s -- %s%s\n", derivedClassName, derivedMethodName, note, instArg); + printf(" devirt to %s::%s -- %s%s%s\n", derivedClassName, derivedMethodName, note, + needsInstParam ? ", instantiating stub: " : "", instArg); gtDispTree(call); } } @@ -9022,8 +9023,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (doPrint) { - printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n", callKind, baseClassName, - baseMethodName, derivedClassName, derivedMethodName, note); + printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]%s%s\n", callKind, baseClassName, + baseMethodName, derivedClassName, derivedMethodName, note, + needsInstParam ? ", instantiating stub: " : "", instArg); } // If we successfully devirtualized based on an exact or final class, From eaf4493c879d2a2e7e6f019c774ee66e8c7743fb Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 8 Feb 2026 23:58:43 +0900 Subject: [PATCH 18/52] Remove duplicated class name --- src/coreclr/jit/importercalls.cpp | 37 +++++++++++++------------------ 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index acce3063ba94d5..f7b510573480fe 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8736,25 +8736,23 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, const bool objClassIsFinal = (objClassAttribs & CORINFO_FLG_FINAL) != 0; #if defined(DEBUG) - const char* callKind = isInterface ? "interface" : "virtual"; - const char* objClassNote = "[?]"; - const char* objClassName = "?objClass"; - const char* baseClassName = "?baseClass"; - const char* baseMethodName = "?baseMethod"; + const char* callKind = isInterface ? "interface" : "virtual"; + const char* objClassNote = "[?]"; + const char* objClassName = "?objClass"; + const char* baseMethodFullName = "?baseMethod"; if (verbose || doPrint) { - objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; - objClassName = eeGetClassName(objClass); - baseClassName = eeGetClassName(baseClass); - baseMethodName = eeGetMethodFullName(baseMethod); + objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; + objClassName = eeGetClassName(objClass); + baseMethodFullName = eeGetMethodFullName(baseMethod); if (verbose) { printf("\nimpDevirtualizeCall: Trying to devirtualize %s call:\n" " class for 'this' is %s%s (attrib %08x)\n" - " base method is %s::%s\n", - callKind, objClassName, objClassNote, objClassAttribs, baseClassName, baseMethodName); + " base method is %s\n", + callKind, objClassName, objClassNote, objClassAttribs, baseMethodFullName); } } #endif // defined(DEBUG) @@ -8836,10 +8834,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, bool canDevirtualize = false; #if defined(DEBUG) - const char* derivedClassName = "?derivedClass"; - const char* derivedMethodName = "?derivedMethod"; - const char* note = "inexact or not final"; - const char* instArg = ""; + const char* derivedMethodFullName = "?derivedMethod"; + const char* note = "inexact or not final"; + const char* instArg = ""; #endif CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; @@ -8917,11 +8914,10 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (verbose || doPrint) { - derivedMethodName = eeGetMethodFullName(derivedMethod); - derivedClassName = eeGetClassName(derivedClass); + derivedMethodFullName = eeGetMethodFullName(derivedMethod); if (verbose) { - printf(" devirt to %s::%s -- %s%s%s\n", derivedClassName, derivedMethodName, note, + printf(" devirt to %s -- %s%s%s\n", derivedMethodFullName, note, needsInstParam ? ", instantiating stub: " : "", instArg); gtDispTree(call); } @@ -9023,9 +9019,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (doPrint) { - printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]%s%s\n", callKind, baseClassName, - baseMethodName, derivedClassName, derivedMethodName, note, - needsInstParam ? ", instantiating stub: " : "", instArg); + printf("Devirtualized %s call to %s; now direct call to %s [%s]%s%s\n", callKind, baseMethodFullName, + derivedMethodFullName, note, needsInstParam ? ", instantiating stub: " : "", instArg); } // If we successfully devirtualized based on an exact or final class, From 19da40998805a9bca4bd017f11dd88b247c16dff Mon Sep 17 00:00:00 2001 From: Steven He Date: Tue, 10 Feb 2026 02:11:47 +0900 Subject: [PATCH 19/52] Use getLookupTree --- src/coreclr/jit/importer.cpp | 39 ++----------------------------- src/coreclr/jit/importercalls.cpp | 2 +- src/coreclr/jit/morph.cpp | 21 +++++++++++++---- 3 files changed, 19 insertions(+), 43 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 97cec70630f95f..2fa1023a1587d0 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1691,12 +1691,6 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken assert(pRuntimeLookup->indirections != 0); GenTreeCall* helperCall = gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, ctxTree, compileTimeHandle); - if (fgImportDone) - { - // If importation has finished, we cannot spill the helper call to a temp - return helperCall; - } - // Spilling it to a temp improves CQ (mainly in Tier0) unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); impStoreToTemp(callLclNum, helperCall, CHECK_SPILL_NONE); @@ -1716,34 +1710,10 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken // Applied repeated indirections for (WORD i = 0; i < pRuntimeLookup->indirections; i++) { - GenTree* tempStoreTree = nullptr; - if ((i == 1 && pRuntimeLookup->indirectFirstOffset) || (i == 2 && pRuntimeLookup->indirectSecondOffset)) { - GenTree* clonedSlotPtrTree = gtClone(slotPtrTree, true); - - if (clonedSlotPtrTree != nullptr) - { - indOffTree = clonedSlotPtrTree; - } - else - { - if (!fgImportDone) - { - indOffTree = impCloneExpr(slotPtrTree, &slotPtrTree, CHECK_SPILL_ALL, - nullptr DEBUGARG("impRuntimeLookup indirectOffset")); - } - else - { - // In post-import phases we cannot append statements to spill the expression - unsigned tempNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup indirectOffset")); - tempStoreTree = gtNewTempStore(tempNum, slotPtrTree, CHECK_SPILL_NONE); - var_types tempTyp = genActualType(lvaGetDesc(tempNum)->TypeGet()); - - indOffTree = gtNewLclvNode(tempNum, tempTyp); - slotPtrTree = gtNewLclvNode(tempNum, tempTyp); - } - } + indOffTree = impCloneExpr(slotPtrTree, &slotPtrTree, CHECK_SPILL_ALL, + nullptr DEBUGARG("impRuntimeLookup indirectOffset")); } if (i != 0) @@ -1762,11 +1732,6 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken slotPtrTree = gtNewOperNode(GT_ADD, TYP_I_IMPL, slotPtrTree, gtNewIconNode(pRuntimeLookup->offsets[i], TYP_I_IMPL)); } - - if (tempStoreTree != nullptr) - { - slotPtrTree = gtNewOperNode(GT_COMMA, TYP_I_IMPL, tempStoreTree, slotPtrTree); - } } // No null test required diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index f7b510573480fe..0291616a085020 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8971,7 +8971,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, CORINFO_RESOLVED_TOKEN* lookupToken = (pDerivedResolvedToken->tokenScope != nullptr) ? pDerivedResolvedToken : pResolvedToken; GenTree* instParam = - impLookupToTree(lookupToken, &dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); + getLookupTree(lookupToken, &dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); if (instParam == nullptr) { diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 7e05992fd502f7..6ea821c051156a 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5582,17 +5582,28 @@ GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, { CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; + GenTree* result = 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); - } +#ifdef FEATURE_READYTORUN + if (pRuntimeLookup->indirections == CORINFO_USENULL) + { + return gtNewIconNode(0, TYP_I_IMPL); + } - GenTree* result = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); + if (IsAot()) + { + GenTree* ctxTree = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); + return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL, + &pLookup->lookupKind, result); + } +#endif + return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, result, compileTimeHandle); + } ArrayStack stmts(getAllocator(CMK_ArrayStack)); From 9c5faa714cc48c28c8d68a423ed3382931f71dff Mon Sep 17 00:00:00 2001 From: Steven He Date: Tue, 10 Feb 2026 02:14:03 +0900 Subject: [PATCH 20/52] Disable runtime lookup support in R2R --- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 4646262db26c3e..4942c08796564f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1505,10 +1505,10 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) ref info->instParamLookup); #endif - if (info->instParamLookup.lookupKind.needsRuntimeLookup && - info->instParamLookup.lookupKind.runtimeLookupKind == CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED) + if (info->instParamLookup.lookupKind.needsRuntimeLookup) { - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + // TODO: We can't handle runtime lookups for devirtualized generic methods in AOT yet + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; } } From 2fb9e79d2221834bce6c823a01ba2cc117eeba46 Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 10 Feb 2026 02:50:02 +0900 Subject: [PATCH 21/52] Remove unused ctxTree --- src/coreclr/jit/morph.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 6ea821c051156a..55d000ecedaad3 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5597,7 +5597,6 @@ GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, if (IsAot()) { - GenTree* ctxTree = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL, &pLookup->lookupKind, result); } From 53a52d42cee5ecd65aeb38f21719a018070d6b93 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 02:24:24 +0900 Subject: [PATCH 22/52] Get rid of getInstantiatedEntry and prevent computing a runtime lookup for context from this --- src/coreclr/jit/importercalls.cpp | 20 ++--- .../tools/Common/JitInterface/CorInfoImpl.cs | 83 +++++++++++++------ .../JitInterface/CorInfoImpl.ReadyToRun.cs | 15 +--- src/coreclr/vm/jitinterface.cpp | 43 ++-------- 4 files changed, 74 insertions(+), 87 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 0291616a085020..7cf9753dcc3762 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7507,8 +7507,12 @@ bool Compiler::isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gd // bool Compiler::impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo) { - return !dvInfo.instParamLookup.lookupKind.needsRuntimeLookup && - ((dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + if (dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) + { + return false; + } + + return ((dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && dvInfo.instParamLookup.constLookup.handle != nullptr) || (dvInfo.instParamLookup.constLookup.accessType == IAT_PVALUE && dvInfo.instParamLookup.constLookup.addr != nullptr)); @@ -8862,18 +8866,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); return; } - } - - // Devirtualization can return an instantiating stub. If so, switch to the wrapped - // entrypoint and capture the stub as the instantiation argument source. - CORINFO_CLASS_HANDLE ignore = NO_CLASS_HANDLE; - CORINFO_METHOD_HANDLE wrappedMethod = - info.compCompHnd->getInstantiatedEntry(derivedMethod, &instantiatingStub, &ignore); - assert(ignore == NO_CLASS_HANDLE); - if (wrappedMethod != NO_METHOD_HANDLE) - { - derivedMethod = wrappedMethod; + instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } assert(!needsCompileTimeLookup || (instantiatingStub != NO_METHOD_HANDLE)); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 4942c08796564f..f70b5dcbd1b25c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1339,6 +1339,20 @@ private void getEHinfo(CORINFO_METHOD_STRUCT_* ftn, uint EHnumber, ref CORINFO_E return ObjectToHandle(m.OwningType); } + private static bool IsCanonicalSubtypeInstantiation(Instantiation instantiation) + { + foreach (TypeDesc type in instantiation) + { + if (type.IsCanonicalSubtype(CanonicalFormKind.Specific)) + { + return true; + } + } + + return false; + } + + private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) { // Initialize OUT fields @@ -1479,40 +1493,57 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) bool isArrayInterfaceDevirtualization = objType.IsArray && decl.OwningType.IsInterface; bool isGenericVirtual = decl.HasInstantiation; - if (isGenericVirtual && impl.IsSharedByGenericInstantiations) + if (isGenericVirtual) { - if (info->pResolvedTokenVirtualMethod == null) + bool requiresRuntimeLookup = IsCanonicalSubtypeInstantiation(originalImpl.Instantiation); + if (requiresRuntimeLookup) { - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; - } + if (info->pResolvedTokenVirtualMethod == null) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } #if READYTORUN - ComputeRuntimeLookupForSharedGenericToken( - Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, - ref info->resolvedTokenDevirtualizedMethod, - pConstrainedResolvedToken: null, - impl, - MethodBeingCompiled, - ref info->instParamLookup); + ComputeRuntimeLookupForSharedGenericToken( + Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, + ref info->resolvedTokenDevirtualizedMethod, + pConstrainedResolvedToken: null, + originalImpl, + MethodBeingCompiled, + ref info->instParamLookup); #else - object runtimeDeterminedMethod = GetRuntimeDeterminedObjectForToken(ref info->resolvedTokenDevirtualizedMethod); - ComputeLookup( - ref info->resolvedTokenDevirtualizedMethod, - runtimeDeterminedMethod, - ReadyToRunHelperId.MethodHandle, - MethodBeingCompiled, - ref info->instParamLookup); + ComputeLookup( + ref info->resolvedTokenDevirtualizedMethod, + originalImpl, + ReadyToRunHelperId.MethodHandle, + HandleToObject(MethodBeingCompiled), + ref info->instParamLookup); #endif - - if (info->instParamLookup.lookupKind.needsRuntimeLookup) - { - // TODO: We can't handle runtime lookups for devirtualized generic methods in AOT yet - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; } } + if (!info->instParamLookup.lookupKind.needsRuntimeLookup && + (isArrayInterfaceDevirtualization || isGenericVirtual) && + impl.IsCanonicalMethod(CanonicalFormKind.Specific)) + { +#if READYTORUN + MethodWithToken originalImplWithToken = new MethodWithToken( + originalImpl, + methodWithTokenImpl.Token, + constrainedType: null, + unboxing: false, + context: null, + devirtualizedMethodOwner: originalImpl.OwningType); + info->instParamLookup.constLookup = CreateConstLookupToSymbol( + _compilation.SymbolNodeFactory.CreateReadyToRunHelper( + ReadyToRunHelperId.MethodHandle, + originalImplWithToken)); +#else + Debug.Assert(false); // TODO: NYI, and currently we should never hit this in NativeAOT +#endif + } + #if READYTORUN // Testing has not shown that concerns about virtual matching are significant // Only generate verification for builds with the stress mode enabled @@ -1527,7 +1558,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) #endif info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_SUCCESS; info->devirtualizedMethod = ObjectToHandle(impl); - info->exactContext = (isArrayInterfaceDevirtualization || isGenericVirtual) ? contextFromMethod(impl) : contextFromType(owningType); + info->exactContext = (isArrayInterfaceDevirtualization || isGenericVirtual) ? contextFromMethod(originalImpl) : contextFromType(owningType); return true; 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 f5af8d2a31ef25..bcfd2d1349cbaa 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2632,21 +2632,10 @@ private void ComputeRuntimeLookupForSharedGenericToken( MethodDesc contextMethod = callerHandle; - // There is a pathological case where invalid IL references __Canon type directly, but there is no - // dictionary available to store the lookup. - // DevirtualizedMethodDescSlot is an exception only when the caller can provide generic context via `this`. + // There is a pathological case where invalid IL references __Canon type directly, but there is no dictionary available to store the lookup. if (!contextMethod.IsSharedByGenericInstantiations) { - if (entryKind != DictionaryEntryKind.DevirtualizedMethodDescSlot) - { - ThrowHelper.ThrowInvalidProgramException(); - } - - if (!contextMethod.AcquiresInstMethodTableFromThis()) - { - pResultLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED; - return; - } + ThrowHelper.ThrowInvalidProgramException(); } if (contextMethod.RequiresInstMethodDescArg()) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 68c03bd6195226..18799a43913fdc 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3010,22 +3010,9 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr MethodDesc* pContextMD = pCallerMD; MethodTable* pContextMT = pContextMD->GetMethodTable(); - // There is a pathological case where invalid IL references __Canon type directly, but there is no - // dictionary available to store the lookup. - // DevirtualizedMethodDescSlot is an exception only when the caller can provide generic context via `this`. + // There is a pathological case where invalid IL references __Canon type directly, but there is no dictionary available to store the lookup. if (!pContextMD->IsSharedByGenericInstantiations()) - { - if (entryKind != DevirtualizedMethodDescSlot) - { - COMPlusThrow(kInvalidProgramException); - } - - if (!pContextMD->AcquiresInstMethodTableFromThis()) - { - pResultLookup->lookupKind.runtimeLookupKind = CORINFO_LOOKUP_NOT_SUPPORTED; - return; - } - } + COMPlusThrow(kInvalidProgramException); if (pContextMD->RequiresInstMethodDescArg()) { @@ -8850,9 +8837,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); - const bool requiresRuntimeLookup = pDevirtMD->IsWrapperStub() && - (pExactMT->IsSharedByGenericInstantiations() || TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation())); - + const bool requiresRuntimeLookup = pDevirtMD->IsWrapperStub() && TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); if (requiresRuntimeLookup) { if (info->pResolvedTokenVirtualMethod == nullptr) @@ -8867,37 +8852,25 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pDevirtMD, m_pMethodBeingCompiled, &info->instParamLookup); - - if (info->instParamLookup.lookupKind.runtimeLookupKind == CORINFO_LOOKUP_NOT_SUPPORTED) - { - info->detail = CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; - return false; - } } } isGenericVirtual = true; } - // For non-shared cases we pass the instantiating stub MethodDesc as a constant. + info->exactContext = isArray || isGenericVirtual + ? MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD) + : MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); + if (!info->instParamLookup.lookupKind.needsRuntimeLookup && (isArray || isGenericVirtual) && pDevirtMD->IsInstantiatingStub()) { - info->instParamLookup.lookupKind.needsRuntimeLookup = false; info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pDevirtMD; info->instParamLookup.constLookup.accessType = IAT_VALUE; + pDevirtMD = pDevirtMD->GetWrappedMethodDesc(); } // Success! Pass back the results. // - if (isArray || isGenericVirtual) - { - info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); - } - else - { - info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); - } - info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD; info->detail = CORINFO_DEVIRTUALIZATION_SUCCESS; From d994cb1e964f1a490d892a777e7ab4c2541ff660 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 02:29:57 +0900 Subject: [PATCH 23/52] Guard lookupToken --- src/coreclr/jit/importercalls.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 7cf9753dcc3762..7ef822a05a081a 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8964,6 +8964,13 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, CORINFO_RESOLVED_TOKEN* lookupToken = (pDerivedResolvedToken->tokenScope != nullptr) ? pDerivedResolvedToken : pResolvedToken; + + if ((lookupToken == nullptr) && dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) + { + JITDUMP("Cannot perform runtime instantiation lookup without a resolved token, sorry.\n"); + return; + } + GenTree* instParam = getLookupTree(lookupToken, &dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); From 399453751e2f5faf7980ca69c54654f9d3b59f3f Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 02:48:21 +0900 Subject: [PATCH 24/52] More fixes --- src/coreclr/vm/jitinterface.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 93e162642b1e7f..5357c6b0cfd8f6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8858,15 +8858,20 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) isGenericVirtual = true; } - info->exactContext = isArray || isGenericVirtual - ? MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD) - : MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); - if (!info->instParamLookup.lookupKind.needsRuntimeLookup && (isArray || isGenericVirtual) && pDevirtMD->IsInstantiatingStub()) { info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pDevirtMD; info->instParamLookup.constLookup.accessType = IAT_VALUE; - pDevirtMD = pDevirtMD->GetWrappedMethodDesc(); + } + + if (isArray || isGenericVirtual) + { + info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); + pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD; + } + else + { + info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); } // Success! Pass back the results. From 014fb46ecbd3e207062bb920077ec03a5c267ee4 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 02:49:33 +0900 Subject: [PATCH 25/52] Fix --- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index f70b5dcbd1b25c..d60f60e888dda1 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1517,7 +1517,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) ref info->resolvedTokenDevirtualizedMethod, originalImpl, ReadyToRunHelperId.MethodHandle, - HandleToObject(MethodBeingCompiled), + MethodBeingCompiled, ref info->instParamLookup); #endif } From fc995e5814e0dee8643c779f1461131b9736842d Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 03:20:09 +0900 Subject: [PATCH 26/52] Bail NativeAOT --- .../tools/Common/JitInterface/CorInfoImpl.cs | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index d60f60e888dda1..3d1cad51894adb 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1500,7 +1500,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) { if (info->pResolvedTokenVirtualMethod == null) { - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; return false; } @@ -1513,12 +1513,9 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) MethodBeingCompiled, ref info->instParamLookup); #else - ComputeLookup( - ref info->resolvedTokenDevirtualizedMethod, - originalImpl, - ReadyToRunHelperId.MethodHandle, - MethodBeingCompiled, - ref info->instParamLookup); + // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + return false; #endif } } @@ -1528,19 +1525,12 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) impl.IsCanonicalMethod(CanonicalFormKind.Specific)) { #if READYTORUN - MethodWithToken originalImplWithToken = new MethodWithToken( - originalImpl, - methodWithTokenImpl.Token, - constrainedType: null, - unboxing: false, - context: null, - devirtualizedMethodOwner: originalImpl.OwningType); - info->instParamLookup.constLookup = CreateConstLookupToSymbol( - _compilation.SymbolNodeFactory.CreateReadyToRunHelper( - ReadyToRunHelperId.MethodHandle, - originalImplWithToken)); + MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, originalImpl.OwningType); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodHandle, originalImplWithToken)); #else - Debug.Assert(false); // TODO: NYI, and currently we should never hit this in NativeAOT + // TODO: Implement generic virtual method devirtualization constant lookup for NativeAOT + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; #endif } From 583277bf0a5a2e150f8f3a90d8718a13f9f4a393 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 04:12:34 +0900 Subject: [PATCH 27/52] Make runtime lookup tests really do runtime lookup --- .../VirtualMethods/generic_virtual_methods.cs | 136 +++++++++++++----- 1 file changed, 102 insertions(+), 34 deletions(-) diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs index c971d07f273a4e..689531b2512a42 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -355,96 +355,164 @@ public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TM internal static class RuntimeLookupDispatcher { + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod SameClassSameMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeSameClassSameMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.SameClassSameMethod(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod SameClassDifferentMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeSameClassDifferentMethod(caller, value); + return SameClassDifferentMethodCore(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod DifferentClassSameMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeDifferentClassSameMethod(caller, value); + return RuntimeLookupDifferentClass.SameMethod(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeDifferentClassDifferentMethod(caller, value); + return RuntimeLookupDifferentClass.DifferentMethod(caller, value); } -} -internal static class RuntimeLookupThunks -{ - public static T InvokeSameClassSameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + private static TMethod SameClassDifferentMethodCore(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupHost.SameClassSameMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.SameClassDifferentMethod(caller, value); } +} - public static T InvokeDifferentClassSameMethod(IBaseMethodCaller caller, T value) +internal static class RuntimeLookupDifferentClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static T SameMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupHost.DifferentClassSameMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.DifferentClassSameMethod(caller, value); } - public static T InvokeSameClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public static T DifferentMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupHost.SameClassDifferentMethod(caller, value); + return DifferentMethodCore(caller, value); } - public static T InvokeDifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + private static T DifferentMethodCore(IBaseMethodCaller caller, T value) { - return RuntimeLookupHost.DifferentClassDifferentMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.DifferentClassDifferentMethod(caller, value); } } -internal static class RuntimeLookupHost +internal abstract class RuntimeLookupVirtualInvoker +{ + public abstract T SameClassSameMethod(IBaseMethodCaller caller, T value); + public abstract T SameClassDifferentMethod(IBaseMethodCaller caller, T value); + public abstract T DifferentClassSameMethod(IBaseMethodCaller caller, T value); + public abstract T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value); +} + +internal sealed class RuntimeLookupVirtualStage : RuntimeLookupVirtualInvoker { - public static T SameClassSameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassSameMethod(IBaseMethodCaller caller, T value) { - return caller.Invoke(value); + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.SameClassSameMethod(caller, value); } - public static T DifferentClassSameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassDifferentMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupRemote.SameMethod(caller, value); + return SameClassDifferentMethodCore(caller, value); } - public static T SameClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassSameMethod(IBaseMethodCaller caller, T value) { - return SameClassDifferentMethodCore(caller, value); + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.DifferentClassSameMethod(caller, value); } - public static T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupRemote.DifferentMethod(caller, value); + return DifferentClassDifferentMethodCore(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] private static T SameClassDifferentMethodCore(IBaseMethodCaller caller, T value) { - return caller.Invoke(value); + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.SameClassDifferentMethod(caller, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T DifferentClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.DifferentClassDifferentMethod(caller, value); + } +} + +internal static class RuntimeLookupTerminalFactory +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RuntimeLookupVirtualInvoker CreateInvoker() + { + return new RuntimeLookupTerminalInvoker(); } } -internal static class RuntimeLookupRemote +internal sealed class RuntimeLookupTerminalInvoker : RuntimeLookupVirtualInvoker { - public static T SameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassSameMethod(IBaseMethodCaller caller, T value) { return caller.Invoke(value); } - public static T DifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassDifferentMethod(IBaseMethodCaller caller, T value) { - return RemoteInner.Invoke(caller, value); + return SameClassDifferentMethodCore(caller, value); } - private static class RemoteInner + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassSameMethod(IBaseMethodCaller caller, T value) { - public static T Invoke(IBaseMethodCaller caller, T value) - { - return caller.Invoke(value); - } + return caller.Invoke(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + { + return DifferentClassDifferentMethodCore(caller, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T SameClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T DifferentClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + return DifferentClassDifferentMethodCoreInner.Invoke(caller, value); + } + + private static class DifferentClassDifferentMethodCoreInner + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static T Invoke(IBaseMethodCaller caller, T value) => caller.Invoke(value); } } From 1aea55585cf465f6854777f8aace37d178d5cfea Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 04:47:38 +0900 Subject: [PATCH 28/52] Fix R2R const lookup --- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 3d1cad51894adb..56b5d543c20f36 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1508,7 +1508,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) ComputeRuntimeLookupForSharedGenericToken( Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, ref info->resolvedTokenDevirtualizedMethod, - pConstrainedResolvedToken: null, + null, originalImpl, MethodBeingCompiled, ref info->instParamLookup); @@ -1525,7 +1525,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) impl.IsCanonicalMethod(CanonicalFormKind.Specific)) { #if READYTORUN - MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, originalImpl.OwningType); + MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, resolver.GetModuleTokenForMethod(originalImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: false), null, false, null, null); info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodHandle, originalImplWithToken)); #else // TODO: Implement generic virtual method devirtualization constant lookup for NativeAOT From d5e3485cf6ebebf00fdbd9e05a6cdc757c62d407 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 05:25:15 +0900 Subject: [PATCH 29/52] Bail on runtime lookup for R2R --- .../tools/Common/JitInterface/CorInfoImpl.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 56b5d543c20f36..3f63a5c5ef379b 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1498,25 +1498,9 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) bool requiresRuntimeLookup = IsCanonicalSubtypeInstantiation(originalImpl.Instantiation); if (requiresRuntimeLookup) { - if (info->pResolvedTokenVirtualMethod == null) - { - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; - return false; - } - -#if READYTORUN - ComputeRuntimeLookupForSharedGenericToken( - Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, - ref info->resolvedTokenDevirtualizedMethod, - null, - originalImpl, - MethodBeingCompiled, - ref info->instParamLookup); -#else - // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT + // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT and R2R info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; return false; -#endif } } From 267e1873e56510d2a11bbda9ef5c96435f67b32b Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 11 Feb 2026 06:20:02 +0900 Subject: [PATCH 30/52] R2R runtime lookup support --- .../tools/Common/JitInterface/CorInfoImpl.cs | 18 +++++++++++++++++- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 9 ++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 3f63a5c5ef379b..56b5d543c20f36 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1498,9 +1498,25 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) bool requiresRuntimeLookup = IsCanonicalSubtypeInstantiation(originalImpl.Instantiation); if (requiresRuntimeLookup) { - // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT and R2R + if (info->pResolvedTokenVirtualMethod == null) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + return false; + } + +#if READYTORUN + ComputeRuntimeLookupForSharedGenericToken( + Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, + ref info->resolvedTokenDevirtualizedMethod, + 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_LOOKUP; return false; +#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 d1e95032c0fb4b..9c482c268e6ea9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -931,7 +931,14 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref var methodIL = HandleToObject(helperArgToken.tokenScope); MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref helperArgToken), constrainedType, unboxing: false, context: sharedMethod); + if (helperArgToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod) + { + helperArg = ComputeMethodWithToken(HandleToObject(pResolvedToken.hMethod), ref helperArgToken, constrainedType, false); + } + else + { + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref helperArgToken), constrainedType, unboxing: false, context: sharedMethod); + } } else if (helperArg is FieldDesc fieldDesc) { From 757043dd961abbeab621f413479d84536b864765 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 12 Feb 2026 21:47:30 +0900 Subject: [PATCH 31/52] Use the correct impl method token --- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 56b5d543c20f36..9d933e789eb647 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1525,7 +1525,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) impl.IsCanonicalMethod(CanonicalFormKind.Specific)) { #if READYTORUN - MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, resolver.GetModuleTokenForMethod(originalImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: false), null, false, null, null); + MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null); info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodHandle, originalImplWithToken)); #else // TODO: Implement generic virtual method devirtualization constant lookup for NativeAOT From 29061735b90e61c8c22e8e217be790df197ec91d Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 12 Feb 2026 22:32:07 +0900 Subject: [PATCH 32/52] Remove the no longer necessary assertion --- src/coreclr/jit/importercalls.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 293041b63a3c6a..2f9147e1bf88fd 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8866,15 +8866,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // For R2R, we don't (yet) support array interface devirtualization. assert(call->IsGenericVirtual(this) || !IsAot()); - // If we don't know the array type exactly we may have the wrong interface type here. - // Bail out. - // - if (!isExact) - { - JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); - return; - } - instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } From a1838619f35a6ca74123e5794990305b4a9269ab Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 22 Feb 2026 00:30:29 +0900 Subject: [PATCH 33/52] Rework runtime lookup computation for devirtualized shared generics in R2R --- src/coreclr/inc/corinfo.h | 5 +++-- .../tools/Common/JitInterface/CorInfoTypes.cs | 4 +++- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 14 +++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 426eed55e91487..8c9dbf98fa72e1 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1176,11 +1176,12 @@ struct CORINFO_LOOKUP_KIND { bool needsRuntimeLookup; CORINFO_RUNTIME_LOOKUP_KIND runtimeLookupKind; - - // The 'runtimeLookupFlags' and 'runtimeLookupArgs' fields + + // The 'runtimeLookupFlags', 'runtimeLookupArgs' and 'runtimeLookupDevirtualized' fields // are just for internal VM / ZAP communication, not to be used by the JIT. uint16_t runtimeLookupFlags; void * runtimeLookupArgs; + bool runtimeLookupDevirtualized; } ; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 3da3c155923118..76eacbe93c65b9 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -209,10 +209,12 @@ public unsafe struct CORINFO_LOOKUP_KIND public bool needsRuntimeLookup { get { return _needsRuntimeLookup != 0; } set { _needsRuntimeLookup = value ? (byte)1 : (byte)0; } } public CORINFO_RUNTIME_LOOKUP_KIND runtimeLookupKind; - // The 'runtimeLookupFlags' and 'runtimeLookupArgs' fields + // The 'runtimeLookupFlags', 'runtimeLookupArgs' and 'runtimeLookupDevirtualized' fields // are just for internal VM / ZAP communication, not to be used by the JIT. public ushort runtimeLookupFlags; public void* runtimeLookupArgs; + private byte _runtimeLookupDevirtualized; + public bool runtimeLookupDevirtualized { get { return _runtimeLookupDevirtualized != 0; } set { _runtimeLookupDevirtualized = value ? (byte)1 : (byte)0; } } } // CORINFO_RUNTIME_LOOKUP indicates the details of the runtime lookup 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 0b636d9eb9956d..ce9ab693d7907e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -920,7 +920,7 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, constrainedType); } ref CORINFO_RESOLVED_TOKEN helperArgToken = ref pResolvedToken; - if (helperId == ReadyToRunHelperId.MethodHandle && pGenericLookupKind.runtimeLookupArgs != null) + if (pGenericLookupKind.runtimeLookupDevirtualized) { helperArgToken = ref *(CORINFO_RESOLVED_TOKEN*)pGenericLookupKind.runtimeLookupArgs; } @@ -931,9 +931,15 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref var methodIL = HandleToObject(helperArgToken.tokenScope); MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - if (helperArgToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod) + if (pGenericLookupKind.runtimeLookupDevirtualized) { - helperArg = ComputeMethodWithToken(HandleToObject(pResolvedToken.hMethod), ref helperArgToken, constrainedType, false); + helperArg = new MethodWithToken( + sharedMethod, + HandleToModuleToken(ref helperArgToken), + constrainedType, + unboxing: false, + context: HandleToObject(helperArgToken.hMethod), + devirtualizedMethodOwner: HandleToObject(helperArgToken.hClass)); } else { @@ -2627,6 +2633,7 @@ private void ComputeRuntimeLookupForSharedGenericToken( pResultLookup.lookupKind.needsRuntimeLookup = true; pResultLookup.lookupKind.runtimeLookupFlags = 0; + pResultLookup.lookupKind.runtimeLookupDevirtualized = false; ref CORINFO_RUNTIME_LOOKUP pResult = ref pResultLookup.runtimeLookup; pResult.signature = null; @@ -2688,6 +2695,7 @@ private void ComputeRuntimeLookupForSharedGenericToken( if (entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) { pResultLookup.lookupKind.runtimeLookupArgs = Unsafe.AsPointer(ref pResolvedToken); + pResultLookup.lookupKind.runtimeLookupDevirtualized = true; } else { From 63df39edf3b42b4ab89fc32f0c5421bac7a7a518 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 22 Feb 2026 01:57:09 +0900 Subject: [PATCH 34/52] Bail on non-exact array interface devirt --- src/coreclr/jit/importercalls.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 3f5f3a3b2e12f4..ce98b81f96911e 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8829,6 +8829,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, const bool needsRuntimeLookup = dvInfo.instParamLookup.lookupKind.needsRuntimeLookup; const bool needsCompileTimeLookup = impDevirtualizedCallHasConstInstParam(dvInfo); const bool needsInstParam = needsRuntimeLookup || needsCompileTimeLookup; + const bool isArrayInterfaceDevirt = ((objClassAttribs & CORINFO_FLG_ARRAY) != 0); if (derivedMethod != nullptr) { @@ -8844,7 +8845,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class. // Generic virtual method devirt also returns a generic method. // - assert(call->IsGenericVirtual(this) || needsInstParam || ((objClassAttribs & CORINFO_FLG_ARRAY) != 0)); + assert(call->IsGenericVirtual(this) || isArrayInterfaceDevirt); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } @@ -8870,6 +8871,15 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); + // If we don't know the array type exactly we may have the wrong interface type here. + // Bail out. + // + if (isArrayInterfaceDevirt && !isExact) + { + JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); + return; + } + // We don't expect R2R/NAOT to end up here for array interface devirtualization. // For NAOT, it has Array and normal devirtualization. // For R2R, we don't (yet) support array interface devirtualization. From d9f24766a70ef9ce95cd603c3b5bd36e8230c0a6 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sun, 22 Feb 2026 01:58:28 +0900 Subject: [PATCH 35/52] Nit --- 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 ce98b81f96911e..51faffc62a544a 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8829,7 +8829,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, const bool needsRuntimeLookup = dvInfo.instParamLookup.lookupKind.needsRuntimeLookup; const bool needsCompileTimeLookup = impDevirtualizedCallHasConstInstParam(dvInfo); const bool needsInstParam = needsRuntimeLookup || needsCompileTimeLookup; - const bool isArrayInterfaceDevirt = ((objClassAttribs & CORINFO_FLG_ARRAY) != 0); + const bool isArrayInterfaceDevirt = (objClassAttribs & CORINFO_FLG_ARRAY) != 0; if (derivedMethod != nullptr) { From d87e421795a5939168eb62a74561e2df6e207664 Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 23 Feb 2026 12:53:26 +0900 Subject: [PATCH 36/52] Build helperArg from templateMethod --- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) 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 59d8c43e10a62c..fc733c77f82d5f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2659,17 +2659,27 @@ 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(); - _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + Debug.Assert(templateMethod != null); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, templateMethod); + helperArg = ComputeMethodWithToken(templateMethod, ref pResolvedToken, constrainedType: null, unboxing: false); } - else if (helperArg is FieldDesc fieldDesc) + else { - helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (helperArg is MethodDesc methodDesc) + { + var methodIL = HandleToObject(pResolvedToken.tokenScope); + MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + } + else if (helperArg is FieldDesc fieldDesc) + { + helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + } } var methodContext = new GenericContext(callerHandle); From d32649214432749eab05e460c94e7576e21ad13c Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 23 Feb 2026 20:35:39 +0900 Subject: [PATCH 37/52] Pass the correct token --- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 1ba31f7a2f9480..3e64e658d7f7c0 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1509,7 +1509,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) #if READYTORUN ComputeRuntimeLookupForSharedGenericToken( Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, - ref info->resolvedTokenDevirtualizedMethod, + ref *info->pResolvedTokenVirtualMethod, null, originalImpl, MethodBeingCompiled, From c90906c5f97f69c27ce91f61db97416aef7bdf5b Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 23 Feb 2026 22:14:19 +0900 Subject: [PATCH 38/52] Cleanup lookupToken --- src/coreclr/jit/compiler.h | 12 +++++------- src/coreclr/jit/importercalls.cpp | 14 ++------------ src/coreclr/jit/morph.cpp | 20 ++++++++------------ 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 9339be8867c3ad..2d7c57614ca498 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6601,13 +6601,11 @@ class Compiler GenTree* fgCreateCallDispatcherAndGetResult(GenTreeCall* origCall, CORINFO_METHOD_HANDLE callTargetStubHnd, CORINFO_METHOD_HANDLE dispatcherHnd); - GenTree* getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - GenTreeFlags handleFlags, - void* compileTimeHandle); - GenTree* getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - void* compileTimeHandle); + GenTree* getLookupTree(CORINFO_LOOKUP* pLookup, + GenTreeFlags handleFlags, + void* compileTimeHandle); + GenTree* getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, + void* compileTimeHandle); GenTree* getVirtMethodPointerTree(GenTree* thisPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 7c6e2451dc427e..2d65c49b2335ce 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -9017,22 +9017,12 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, compileTimeHandle = instantiatingStub; } - CORINFO_RESOLVED_TOKEN* lookupToken = - (pDerivedResolvedToken->tokenScope != nullptr) ? pDerivedResolvedToken : pResolvedToken; - - if ((lookupToken == nullptr) && dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) - { - JITDUMP("Cannot perform runtime instantiation lookup without a resolved token, sorry.\n"); - return; - } - - GenTree* instParam = - getLookupTree(lookupToken, &dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); + GenTree* instParam = getLookupTree(&dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); if (instParam == nullptr) { // If we're inlining, impLookupToTree can return nullptr after recording a fatal observation. - JITDUMP("Failed to materialize instantiation argument for devirtualized call, sorry.\n"); + JITDUMP("Failed to produce the lookup for devirtualized call, sorry.\n"); return; } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 850131f2cc78d6..d8be60ee55e498 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5536,7 +5536,6 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // getLookupTree: get a lookup tree // // Arguments: -// pResolvedToken - resolved token of the call // pLookup - the lookup to get the tree for // handleFlags - flags to set on the result node // compileTimeHandle - compile-time handle corresponding to the lookup @@ -5544,10 +5543,9 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // Return Value: // A node representing the lookup tree // -GenTree* Compiler::getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - GenTreeFlags handleFlags, - void* compileTimeHandle) +GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, + GenTreeFlags handleFlags, + void* compileTimeHandle) { if (!pLookup->lookupKind.needsRuntimeLookup) { @@ -5570,23 +5568,21 @@ GenTree* Compiler::getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, compileTimeHandle); } - return getRuntimeLookupTree(pResolvedToken, pLookup, compileTimeHandle); + return getRuntimeLookupTree(pLookup, compileTimeHandle); } //------------------------------------------------------------------------ // getRuntimeLookupTree: get a tree for a runtime lookup // // Arguments: -// pResolvedToken - resolved token of the call // pLookup - the lookup to get the tree for // compileTimeHandle - compile-time handle corresponding to the lookup // // Return Value: // A node representing the runtime lookup tree // -GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - void* compileTimeHandle) +GenTree* Compiler::getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, + void* compileTimeHandle) { CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; @@ -5708,8 +5704,8 @@ GenTree* Compiler::getTokenHandleTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, bo // info.compCompHnd->embedGenericHandle(pResolvedToken, parent, info.compMethodHnd, &embedInfo); - GenTree* result = getLookupTree(pResolvedToken, &embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), - embedInfo.compileTimeHandle); + GenTree* result = + getLookupTree(&embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), embedInfo.compileTimeHandle); // If we have a result and it requires runtime lookup, wrap it in a runtime lookup node. if ((result != nullptr) && embedInfo.lookup.lookupKind.needsRuntimeLookup) From ac7c3e29b9f42662d9d026fed8d2c12516ecbeac Mon Sep 17 00:00:00 2001 From: Steven He Date: Mon, 23 Feb 2026 22:54:11 +0900 Subject: [PATCH 39/52] JIT format --- src/coreclr/jit/morph.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index d8be60ee55e498..948b1ac4255795 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5543,9 +5543,7 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // 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) { if (!pLookup->lookupKind.needsRuntimeLookup) { @@ -5581,8 +5579,7 @@ GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, // 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) { CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; From 154f832ab95fd3e74983848ab44c767cffda86ad Mon Sep 17 00:00:00 2001 From: Steven He Date: Tue, 24 Feb 2026 02:16:00 +0900 Subject: [PATCH 40/52] Disable runtime lookup support in R2R --- .../tools/Common/JitInterface/CorInfoImpl.cs | 12 +-------- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 26 ++++++------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 3e64e658d7f7c0..424fb35b5ddb27 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1506,19 +1506,9 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) 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 + // TODO: Implement generic virtual method devirtualization runtime lookup for R2R and NativeAOT info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; return false; -#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 fc733c77f82d5f..59d8c43e10a62c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2659,27 +2659,17 @@ private void ComputeRuntimeLookupForSharedGenericToken( throw new NotImplementedException(entryKind.ToString()); } - object helperArg; - if (entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) + object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (helperArg is MethodDesc methodDesc) { - Debug.Assert(templateMethod != null); - _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, templateMethod); - helperArg = ComputeMethodWithToken(templateMethod, ref pResolvedToken, constrainedType: null, unboxing: false); + var methodIL = HandleToObject(pResolvedToken.tokenScope); + MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); } - else + else if (helperArg is FieldDesc fieldDesc) { - helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); - if (helperArg is MethodDesc methodDesc) - { - var methodIL = HandleToObject(pResolvedToken.tokenScope); - MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); - _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); - } - else if (helperArg is FieldDesc fieldDesc) - { - helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); - } + helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); } var methodContext = new GenericContext(callerHandle); From df87b8477cf8e17c0776bb50945d632d0a1d193b Mon Sep 17 00:00:00 2001 From: Steven He Date: Tue, 24 Feb 2026 02:23:17 +0900 Subject: [PATCH 41/52] Add one more test case --- .../VirtualMethods/generic_virtual_methods.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs index 689531b2512a42..7aba5abe26bab3 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -3,6 +3,7 @@ // using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using Xunit; @@ -137,6 +138,12 @@ public static void GenericInterfaceBase_GenericStructDerived_NoInliningVariants( ValidateCaller("GenericInterfaceBase_GenericStructDerived_NoInlining_String_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_NoInlining())); } + [Fact] + public static void RuntimeLookupDelegate() + { + RuntimeLookupDelegateGenericVirtual.Test(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ValidateCaller(string scenarioName, IBaseMethodCaller caller) { @@ -199,6 +206,45 @@ private static void Equal(T expected, T actual, [CallerArgumentExpression(nam } } +internal class RuntimeLookupDelegateGenericVirtual +{ + internal static readonly List s_list = new(); + + internal static void Test() + { + var test = new Base(); + test.Foo>(); + + var test2 = new Derived(); + Delegate m1 = test2.Foo>(); + Delegate m2 = test2.Foo>>; + Assert.Equal(m1, m2); + } +} + +internal class Base +{ + public virtual Delegate Foo() + { + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(U)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>>>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>>>>)); + return Foo; + } +} + +internal class Derived : Base +{ + public override Delegate Foo() + { + return Foo>; + } +} + internal static class IconContextBridgeNonShared { public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value) From ae461738ef6fb71d43edfe723df815e8ae2709d1 Mon Sep 17 00:00:00 2001 From: Steven He Date: Tue, 24 Feb 2026 23:30:24 +0900 Subject: [PATCH 42/52] Bail on shared method table --- .../tools/Common/JitInterface/CorInfoImpl.cs | 20 +++++++++++++- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 26 +++++++++++++------ src/coreclr/vm/jitinterface.cpp | 8 ++++++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 424fb35b5ddb27..20cc83f614a6a5 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1497,6 +1497,14 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) if (isGenericVirtual) { + if (IsCanonicalSubtypeInstantiation(originalImpl.OwningType.Instantiation)) + { + // 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 as a runtime lookup. + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + bool requiresRuntimeLookup = IsCanonicalSubtypeInstantiation(originalImpl.Instantiation); if (requiresRuntimeLookup) { @@ -1506,9 +1514,19 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) return false; } - // TODO: Implement generic virtual method devirtualization runtime lookup for R2R and NativeAOT +#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_LOOKUP; return false; +#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 59d8c43e10a62c..fc733c77f82d5f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2659,17 +2659,27 @@ 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(); - _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + Debug.Assert(templateMethod != null); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, templateMethod); + helperArg = ComputeMethodWithToken(templateMethod, ref pResolvedToken, constrainedType: null, unboxing: false); } - else if (helperArg is FieldDesc fieldDesc) + else { - helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (helperArg is MethodDesc methodDesc) + { + var methodIL = HandleToObject(pResolvedToken.tokenScope); + MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + } + else if (helperArg is FieldDesc fieldDesc) + { + helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + } } var methodContext = new GenericContext(callerHandle); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 0963aaa6f74bd3..4fb6b140b4cd67 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8869,6 +8869,14 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); + if (TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetClassInstantiation())) + { + // 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 as a runtime lookup. + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + const bool requiresRuntimeLookup = pDevirtMD->IsWrapperStub() && TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); if (requiresRuntimeLookup) { From c7e4a22f2906bad8358c09dac07c97184fc8b54c Mon Sep 17 00:00:00 2001 From: Steven He Date: Tue, 24 Feb 2026 23:47:34 +0900 Subject: [PATCH 43/52] Drop IsWrapperStub check on pDevirtMD --- 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 4fb6b140b4cd67..d4794b3d305fd2 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8877,7 +8877,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) return false; } - const bool requiresRuntimeLookup = pDevirtMD->IsWrapperStub() && TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); + const bool requiresRuntimeLookup = TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); if (requiresRuntimeLookup) { if (info->pResolvedTokenVirtualMethod == nullptr) From 951655b84d22728cd0f10b56baa478b94184f970 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 00:31:12 +0900 Subject: [PATCH 44/52] Address several review feedbacks --- src/coreclr/inc/corinfo.h | 1 + src/coreclr/jit/compiler.h | 2 - src/coreclr/jit/fginline.cpp | 7 +- src/coreclr/jit/importercalls.cpp | 92 ++++++++----------- src/coreclr/jit/inline.h | 4 +- .../tools/Common/JitInterface/CorInfoImpl.cs | 22 +---- src/coreclr/vm/jitinterface.cpp | 6 +- 7 files changed, 55 insertions(+), 79 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index a741fe0ec8ddc1..44044041604a99 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1153,6 +1153,7 @@ struct CORINFO_CONST_LOOKUP // IAT_PVALUE --> "addr" stores a pointer to a location which will hold the real handle // IAT_RELPVALUE --> "addr" stores a relative pointer to a location which will hold the real handle // IAT_PPVALUE --> "addr" stores a double indirection to a location which will hold the real handle + // IAT_VALUE with a nullptr handle indicates no constant lookup is needed InfoAccessType accessType; union diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3f37b8a8005fd8..45bec64feb49fa 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5204,8 +5204,6 @@ class Compiler methodPointerInfo* impAllocateMethodPointerInfo(const CORINFO_RESOLVED_TOKEN& token, mdToken tokenConstrained); - static bool impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo); - /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 6f846961ad524d..069a36efc76157 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -606,11 +606,16 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtLateDevirtualizationInfo->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_RESOLVED_TOKEN resolvedToken{}; + resolvedToken.tokenScope = call->gtLateDevirtualizationInfo->tokenScope; + resolvedToken.pMethodSpec = call->gtLateDevirtualizationInfo->pMethodSpec; + resolvedToken.cbMethodSpec = call->gtLateDevirtualizationInfo->cbMethodSpec; + CORINFO_RESOLVED_TOKEN* pResolvedToken = &resolvedToken; + CORINFO_CONTEXT_HANDLE contextInput = context; context = nullptr; m_compiler->impDevirtualizeCall(call, pResolvedToken, &method, &methodFlags, &contextInput, &context, diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 8be5ebe3c6982a..a900232ec6a665 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1288,7 +1288,9 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->ilLocation = impCurStmtDI.GetLocation(); - info->resolvedToken = *pResolvedToken; + info->tokenScope = pResolvedToken->tokenScope; + info->pMethodSpec = pResolvedToken->pMethodSpec; + info->cbMethodSpec = pResolvedToken->cbMethodSpec; call->AsCall()->gtLateDevirtualizationInfo = info; } } @@ -7544,29 +7546,6 @@ bool Compiler::isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gd return true; } -//------------------------------------------------------------------------ -// impDevirtualizedCallHasConstInstParam: check if the instantiation argument -// is a compile-time lookup. -// -// Arguments: -// dvInfo - Devirtualization information returned by resolveVirtualMethod. -// -// Returns: -// true if instParamLookup describes a valid constant handle/address lookup. -// -bool Compiler::impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo) -{ - if (dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) - { - return false; - } - - return ((dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && - dvInfo.instParamLookup.constLookup.handle != nullptr) || - (dvInfo.instParamLookup.constLookup.accessType == IAT_PVALUE && - dvInfo.instParamLookup.constLookup.addr != nullptr)); -} - //------------------------------------------------------------------------ // considerGuardedDevirtualization: see if we can profitably guess at the // class involved in an interface or virtual call. @@ -7701,10 +7680,13 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyHood += 100 - likelyHood * numExactClasses; } + const bool needsCompileTimeLookup = !dvInfo.instParamLookup.lookupKind.needsRuntimeLookup && + !(dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + dvInfo.instParamLookup.constLookup.handle == nullptr); + addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, clsAttrs, likelyHood, exactNeedsMethodContext, - impDevirtualizedCallHasConstInstParam(dvInfo), baseMethod, - originalContext); + needsCompileTimeLookup, baseMethod, originalContext); } if (call->GetInlineCandidatesCount() == numExactClasses) @@ -7777,7 +7759,9 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyContext = dvInfo.exactContext; likelyMethod = dvInfo.devirtualizedMethod; needsMethodContext = ((size_t)likelyContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; - instantiatingStub = impDevirtualizedCallHasConstInstParam(dvInfo); + instantiatingStub = !dvInfo.instParamLookup.lookupKind.needsRuntimeLookup && + !(dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + dvInfo.instParamLookup.constLookup.handle == nullptr); } else { @@ -8877,10 +8861,11 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, CORINFO_CLASS_HANDLE derivedClass = NO_CLASS_HANDLE; CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; const bool needsRuntimeLookup = dvInfo.instParamLookup.lookupKind.needsRuntimeLookup; - const bool needsCompileTimeLookup = impDevirtualizedCallHasConstInstParam(dvInfo); - const bool needsInstParam = needsRuntimeLookup || needsCompileTimeLookup; const bool isArrayInterfaceDevirt = (objClassAttribs & CORINFO_FLG_ARRAY) != 0; + const bool needsInstParam = needsRuntimeLookup || !(dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + dvInfo.instParamLookup.constLookup.handle == nullptr); + if (derivedMethod != nullptr) { assert(exactContext != nullptr); @@ -8911,34 +8896,31 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, const char* instArg = ""; #endif - CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; + CORINFO_METHOD_HANDLE instParam = NO_METHOD_HANDLE; - if (derivedMethod != nullptr && needsInstParam) + if (derivedMethod != nullptr && needsInstParam && !needsRuntimeLookup) { - if (needsCompileTimeLookup) - { - // We should only end up with generic methods that need a method context (eg. array interface, GVM). - // - assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); + // We should only end up with generic methods that need a method context (eg. array interface, GVM). + // + assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); - // If we don't know the array type exactly we may have the wrong interface type here. - // Bail out. - // - if (isArrayInterfaceDevirt && !isExact) - { - JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); - return; - } + // If we don't know the array type exactly we may have the wrong interface type here. + // Bail out. + // + if (isArrayInterfaceDevirt && !isExact) + { + JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); + return; + } - // We don't expect R2R/NAOT to end up here for array interface devirtualization. - // For NAOT, it has Array and normal devirtualization. - // For R2R, we don't (yet) support array interface devirtualization. - assert(call->IsGenericVirtual(this) || !IsAot()); + // We don't expect R2R/NAOT to end up here for array interface devirtualization. + // For NAOT, it has Array and normal devirtualization. + // For R2R, we don't (yet) support array interface devirtualization. + assert(call->IsGenericVirtual(this) || !IsAot()); - instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); - } + instParam = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); - assert(!needsCompileTimeLookup || (instantiatingStub != NO_METHOD_HANDLE)); + assert(instParam != NO_METHOD_HANDLE); } // If we failed to get a method handle, we can't directly devirtualize. @@ -8971,7 +8953,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, } if (needsInstParam) { - instArg = needsCompileTimeLookup ? eeGetMethodFullName(instantiatingStub) : "runtime lookup"; + instArg = needsRuntimeLookup ? "runtime lookup" : eeGetMethodFullName(instParam); } if (verbose || doPrint) @@ -8980,7 +8962,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (verbose) { printf(" devirt to %s -- %s%s%s\n", derivedMethodFullName, note, - needsInstParam ? ", instantiating stub: " : "", instArg); + needsInstParam ? ", instantiation: " : "", instArg); gtDispTree(call); } } @@ -9025,9 +9007,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, assert(call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam) == nullptr); CORINFO_METHOD_HANDLE compileTimeHandle = derivedMethod; - if (needsCompileTimeLookup) + if (!needsRuntimeLookup) { - compileTimeHandle = instantiatingStub; + compileTimeHandle = instParam; } GenTree* instParam = getLookupTree(&dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index 8980f54ac5f99e..ac47b30b20a3c6 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -637,7 +637,9 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; ILLocation ilLocation; - CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_MODULE_HANDLE tokenScope; + PCCOR_SIGNATURE pMethodSpec; + uint32_t cbMethodSpec; }; // InlArgInfo describes inline candidate argument properties. diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 969d645d93fac4..b5c8b6be5d4d16 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1340,20 +1340,6 @@ private void getEHinfo(CORINFO_METHOD_STRUCT_* ftn, uint EHnumber, ref CORINFO_E return ObjectToHandle(m.OwningType); } - private static bool IsCanonicalSubtypeInstantiation(Instantiation instantiation) - { - foreach (TypeDesc type in instantiation) - { - if (type.IsCanonicalSubtype(CanonicalFormKind.Specific)) - { - return true; - } - } - - return false; - } - - private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) { // Initialize OUT fields @@ -1496,7 +1482,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) if (isGenericVirtual) { - if (IsCanonicalSubtypeInstantiation(originalImpl.OwningType.Instantiation)) + if (originalImpl.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) { // 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 as a runtime lookup. @@ -1504,12 +1490,12 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) return false; } - bool requiresRuntimeLookup = IsCanonicalSubtypeInstantiation(originalImpl.Instantiation); + bool requiresRuntimeLookup = originalImpl.IsSharedByGenericInstantiations; if (requiresRuntimeLookup) { if (info->pResolvedTokenVirtualMethod == null) { - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; } @@ -1523,7 +1509,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) ref info->instParamLookup); #else // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; #endif } diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 47e2127d0a581c..30b846bbe21e17 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8599,6 +8599,8 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) memset(&info->resolvedTokenDevirtualizedMethod, 0, sizeof(info->resolvedTokenDevirtualizedMethod)); memset(&info->resolvedTokenDevirtualizedUnboxedMethod, 0, sizeof(info->resolvedTokenDevirtualizedUnboxedMethod)); memset(&info->instParamLookup, 0, sizeof(info->instParamLookup)); + info->instParamLookup.constLookup.accessType = IAT_VALUE; + info->instParamLookup.constLookup.handle = NULL; MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8842,7 +8844,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); - if (TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetClassInstantiation())) + if (pDevirtMD->GetMethodTable()->IsSharedByGenericInstantiations()) { // 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 as a runtime lookup. @@ -8850,7 +8852,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) return false; } - const bool requiresRuntimeLookup = TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); + const bool requiresRuntimeLookup = pDevirtMD->IsSharedByGenericInstantiations(); if (requiresRuntimeLookup) { if (info->pResolvedTokenVirtualMethod == nullptr) From 84bdb414197cd4f23455de39218253ae18468d50 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 00:34:43 +0900 Subject: [PATCH 45/52] Nit --- src/coreclr/jit/importercalls.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index a900232ec6a665..a2a74a5dde5753 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8872,7 +8872,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) { - assert(!needsInstParam); derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } else @@ -8880,7 +8879,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class. // Generic virtual method devirt also returns a generic method. // - assert(call->IsGenericVirtual(this) || isArrayInterfaceDevirt); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } From cc9cbab35221299874a2b599ca869ee1ec542ec2 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 01:00:37 +0900 Subject: [PATCH 46/52] JIT format --- src/coreclr/jit/fginline.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 069a36efc76157..5d2757184d6d2e 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -603,12 +603,12 @@ 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; + unsigned methodFlags = 0; + const bool isLateDevirtualization = true; + const bool explicitTailCall = call->IsTailPrefixedCall(); CORINFO_RESOLVED_TOKEN resolvedToken{}; resolvedToken.tokenScope = call->gtLateDevirtualizationInfo->tokenScope; From 088f8c119a47f5f079e151550e891d65c3348adf Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 01:35:58 +0900 Subject: [PATCH 47/52] Change GDV to use the new instParamLookup --- src/coreclr/jit/compiler.h | 3 +- src/coreclr/jit/importercalls.cpp | 73 ++++++++++++++----------------- src/coreclr/vm/jitinterface.cpp | 2 +- 3 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 45bec64feb49fa..6333e8ae282506 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7498,8 +7498,7 @@ class Compiler unsigned methodAttr, unsigned classAttr, unsigned likelihood, - bool arrayInterface, - bool instantiatingStub, + CORINFO_LOOKUP instParamLookup, CORINFO_METHOD_HANDLE originalMethodHandle, CORINFO_CONTEXT_HANDLE originalContextHandle); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index a2a74a5dde5753..52edc25d5e9341 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7665,8 +7665,6 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; CORINFO_METHOD_HANDLE exactMethod = dvInfo.devirtualizedMethod; uint32_t exactMethodAttrs = info.compCompHnd->getMethodAttribs(exactMethod); - const bool exactNeedsMethodContext = - ((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; // NOTE: This is currently used only with NativeAOT. In theory, we could also check if we // have static PGO data to decide which class to guess first. Presumably, this is a rare case. @@ -7680,13 +7678,9 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyHood += 100 - likelyHood * numExactClasses; } - const bool needsCompileTimeLookup = !dvInfo.instParamLookup.lookupKind.needsRuntimeLookup && - !(dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && - dvInfo.instParamLookup.constLookup.handle == nullptr); - addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, - clsAttrs, likelyHood, exactNeedsMethodContext, - needsCompileTimeLookup, baseMethod, originalContext); + clsAttrs, likelyHood, dvInfo.instParamLookup, baseMethod, + originalContext); } if (call->GetInlineCandidatesCount() == numExactClasses) @@ -7709,11 +7703,14 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // Iterate over the guesses for (int candidateId = 0; candidateId < candidatesCount; candidateId++) { - CORINFO_CLASS_HANDLE likelyClass = likelyClasses[candidateId]; - CORINFO_METHOD_HANDLE likelyMethod = likelyMethods[candidateId]; - unsigned likelihood = likelihoods[candidateId]; - bool needsMethodContext = false; - bool instantiatingStub = false; + CORINFO_CLASS_HANDLE likelyClass = likelyClasses[candidateId]; + CORINFO_METHOD_HANDLE likelyMethod = likelyMethods[candidateId]; + unsigned likelihood = likelihoods[candidateId]; + + CORINFO_LOOKUP instParamLookup{}; + instParamLookup.lookupKind.needsRuntimeLookup = false; + instParamLookup.constLookup.accessType = IAT_VALUE; + instParamLookup.constLookup.handle = nullptr; CORINFO_CONTEXT_HANDLE likelyContext = originalContext; @@ -7758,10 +7755,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyContext = dvInfo.exactContext; likelyMethod = dvInfo.devirtualizedMethod; - needsMethodContext = ((size_t)likelyContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; - instantiatingStub = !dvInfo.instParamLookup.lookupKind.needsRuntimeLookup && - !(dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && - dvInfo.instParamLookup.constLookup.handle == nullptr); + instParamLookup = dvInfo.instParamLookup; } else { @@ -7836,8 +7830,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // Add this as a potential candidate. // addGuardedDevirtualizationCandidate(call, likelyMethod, likelyClass, likelyContext, likelyMethodAttribs, - likelyClassAttribs, likelihood, needsMethodContext, instantiatingStub, - baseMethod, originalContext); + likelyClassAttribs, likelihood, instParamLookup, baseMethod, + originalContext); } } @@ -7862,8 +7856,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // methodAttr - attributes of the method // classAttr - attributes of the class // likelihood - odds that this class is the class seen at runtime -// needsMethodContext - devirtualized method may need generic method context (e.g. array interfaces) -// instantiatingStub - devirtualized method in an instantiating stub +// instParamLookup - the lookup information required by instantiation param // originalMethodHandle - method handle of base method (before devirt) // originalContextHandle - context for the original call // @@ -7874,8 +7867,7 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, unsigned methodAttr, unsigned classAttr, unsigned likelihood, - bool needsMethodContext, - bool instantiatingStub, + CORINFO_LOOKUP instParamLookup, CORINFO_METHOD_HANDLE originalMethodHandle, CORINFO_CONTEXT_HANDLE originalContextHandle) { @@ -7930,9 +7922,21 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, // We're all set, proceed with candidate creation. // + assert(!instParamLookup.lookupKind.needsRuntimeLookup); + CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; + if (!(instParamLookup.constLookup.accessType == IAT_VALUE && instParamLookup.constLookup.handle == nullptr)) + { + assert(((size_t)contextHandle & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); + instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)contextHandle & ~CORINFO_CONTEXTFLAGS_MASK); + assert(instantiatingStub != NO_METHOD_HANDLE); + } + JITDUMP("Marking call [%06u] as guarded devirtualization candidate; will guess for %s %s\n", dspTreeID(call), classHandle != NO_CLASS_HANDLE ? "class" : "method", - classHandle != NO_CLASS_HANDLE ? eeGetClassName(classHandle) : eeGetMethodFullName(methodHandle)); + classHandle != NO_CLASS_HANDLE + ? eeGetClassName(classHandle) + : eeGetMethodFullName(instantiatingStub != NO_METHOD_HANDLE ? instantiatingStub : methodHandle)); + setMethodHasGuardedDevirtualization(); // Spill off any GT_RET_EXPR subtrees so we can clone the call. @@ -7953,24 +7957,15 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, pInfo->originalContextHandle = originalContextHandle; pInfo->likelihood = likelihood; pInfo->exactContextHandle = contextHandle; - pInfo->needsMethodContext = needsMethodContext; + pInfo->needsMethodContext = instantiatingStub != NO_METHOD_HANDLE; // If the guarded method is an instantiating stub, find the instantiated method // - if (instantiatingStub) + if (instantiatingStub != NO_METHOD_HANDLE) { - JITDUMP(" ... method is an instantiating stub, looking for instantiated entry\n"); - CORINFO_CLASS_HANDLE ignoredClass = NO_CLASS_HANDLE; - CORINFO_METHOD_HANDLE ignoredMethod = NO_METHOD_HANDLE; - CORINFO_METHOD_HANDLE instantiatedMethod = - info.compCompHnd->getInstantiatedEntry(methodHandle, &ignoredMethod, &ignoredClass); - assert(ignoredClass == NO_CLASS_HANDLE); - - if (instantiatedMethod != NO_METHOD_HANDLE) - { - JITDUMP(" ... updating GDV candidate with instantiated entry info\n"); - pInfo->guardedMethodInstantiatedEntryHandle = instantiatedMethod; - } + JITDUMP(" ... updating GDV candidate with instantiated entry info\n"); + pInfo->guardedMethodHandle = instantiatingStub; + pInfo->guardedMethodInstantiatedEntryHandle = methodHandle; } // If the guarded class is a value class, look for an unboxed entry point. @@ -9059,7 +9054,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (doPrint) { printf("Devirtualized %s call to %s; now direct call to %s [%s]%s%s\n", callKind, baseMethodFullName, - derivedMethodFullName, note, needsInstParam ? ", instantiating stub: " : "", instArg); + derivedMethodFullName, note, needsInstParam ? ", instantiation: " : "", instArg); } // If we successfully devirtualized based on an exact or final class, diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 30b846bbe21e17..dddd955a4cbda5 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8598,7 +8598,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) info->detail = CORINFO_DEVIRTUALIZATION_UNKNOWN; memset(&info->resolvedTokenDevirtualizedMethod, 0, sizeof(info->resolvedTokenDevirtualizedMethod)); memset(&info->resolvedTokenDevirtualizedUnboxedMethod, 0, sizeof(info->resolvedTokenDevirtualizedUnboxedMethod)); - memset(&info->instParamLookup, 0, sizeof(info->instParamLookup)); + info->instParamLookup.lookupKind.needsRuntimeLookup = false; info->instParamLookup.constLookup.accessType = IAT_VALUE; info->instParamLookup.constLookup.handle = NULL; From cff6fe73c4d668958def8716880f2539498338f9 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 01:37:08 +0900 Subject: [PATCH 48/52] Revert the resolvedToken change --- src/coreclr/jit/fginline.cpp | 19 +++++++------------ src/coreclr/jit/importercalls.cpp | 4 +--- src/coreclr/jit/inline.h | 4 +--- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 5d2757184d6d2e..6f846961ad524d 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -603,18 +603,13 @@ 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_RESOLVED_TOKEN resolvedToken{}; - resolvedToken.tokenScope = call->gtLateDevirtualizationInfo->tokenScope; - resolvedToken.pMethodSpec = call->gtLateDevirtualizationInfo->pMethodSpec; - resolvedToken.cbMethodSpec = call->gtLateDevirtualizationInfo->cbMethodSpec; - CORINFO_RESOLVED_TOKEN* pResolvedToken = &resolvedToken; + 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; diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 52edc25d5e9341..ce961c243b6787 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1288,9 +1288,7 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->ilLocation = impCurStmtDI.GetLocation(); - info->tokenScope = pResolvedToken->tokenScope; - info->pMethodSpec = pResolvedToken->pMethodSpec; - info->cbMethodSpec = pResolvedToken->cbMethodSpec; + info->resolvedToken = *pResolvedToken; call->AsCall()->gtLateDevirtualizationInfo = info; } } diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index ac47b30b20a3c6..8980f54ac5f99e 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -637,9 +637,7 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; ILLocation ilLocation; - CORINFO_MODULE_HANDLE tokenScope; - PCCOR_SIGNATURE pMethodSpec; - uint32_t cbMethodSpec; + CORINFO_RESOLVED_TOKEN resolvedToken; }; // InlArgInfo describes inline candidate argument properties. From 9a7ef2e1a016c999974d2b8a3636c0a67a75aeed Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 01:44:34 +0900 Subject: [PATCH 49/52] Oops --- 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 dddd955a4cbda5..7a444d0baa813d 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8852,7 +8852,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) return false; } - const bool requiresRuntimeLookup = pDevirtMD->IsSharedByGenericInstantiations(); + const bool requiresRuntimeLookup = pDevirtMD->IsSharedByGenericMethodInstantiations(); if (requiresRuntimeLookup) { if (info->pResolvedTokenVirtualMethod == nullptr) From f7fb221f3d7cd4cf5a849399a65ebb7a7a908677 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 01:48:44 +0900 Subject: [PATCH 50/52] Revert some changes --- src/coreclr/vm/jitinterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 7a444d0baa813d..c35349a883a392 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8844,7 +8844,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); - if (pDevirtMD->GetMethodTable()->IsSharedByGenericInstantiations()) + if (TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetClassInstantiation())) { // 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 as a runtime lookup. @@ -8852,7 +8852,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) return false; } - const bool requiresRuntimeLookup = pDevirtMD->IsSharedByGenericMethodInstantiations(); + const bool requiresRuntimeLookup = TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); if (requiresRuntimeLookup) { if (info->pResolvedTokenVirtualMethod == nullptr) From 3a8c39864f5c43537e2e97fc16691329040dcb5a Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 4 Mar 2026 02:00:29 +0900 Subject: [PATCH 51/52] Poor format --- src/coreclr/jit/importercalls.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index ce961c243b6787..40fadea062e938 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7751,9 +7751,9 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, break; } - likelyContext = dvInfo.exactContext; - likelyMethod = dvInfo.devirtualizedMethod; - instParamLookup = dvInfo.instParamLookup; + likelyContext = dvInfo.exactContext; + likelyMethod = dvInfo.devirtualizedMethod; + instParamLookup = dvInfo.instParamLookup; } else { From 417ec515fd3c3aa200ffd46276ff2b00be370fbc Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 7 Mar 2026 01:15:26 +0900 Subject: [PATCH 52/52] Nit --- src/coreclr/jit/importercalls.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 40fadea062e938..61bb31020bf03f 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7921,10 +7921,11 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, // We're all set, proceed with candidate creation. // assert(!instParamLookup.lookupKind.needsRuntimeLookup); + const bool needsMethodContext = ((size_t)contextHandle & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; if (!(instParamLookup.constLookup.accessType == IAT_VALUE && instParamLookup.constLookup.handle == nullptr)) { - assert(((size_t)contextHandle & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); + assert(needsMethodContext); instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)contextHandle & ~CORINFO_CONTEXTFLAGS_MASK); assert(instantiatingStub != NO_METHOD_HANDLE); } @@ -7955,7 +7956,7 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, pInfo->originalContextHandle = originalContextHandle; pInfo->likelihood = likelihood; pInfo->exactContextHandle = contextHandle; - pInfo->needsMethodContext = instantiatingStub != NO_METHOD_HANDLE; + pInfo->needsMethodContext = needsMethodContext; // If the guarded method is an instantiating stub, find the instantiated method //