Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
61a278f
Devirtualize virtual methods that require an instantiating stub
hez2010 May 28, 2026
f01f4f4
R2R support
hez2010 May 28, 2026
b42c291
Bail shared MT for generic DIM as well
hez2010 May 28, 2026
b96fc2e
Stop returning an instantiating stub
hez2010 May 28, 2026
de840c2
Nit
hez2010 May 28, 2026
181e88e
Nit 2
hez2010 May 28, 2026
9331cc1
Remove redundant empty line
hez2010 May 28, 2026
f635686
Generic DIM devirt for NativeAOT
hez2010 May 28, 2026
5da42c5
Meh
hez2010 May 28, 2026
66c8d6a
More NativeAOT support
hez2010 May 28, 2026
152c44a
Address an assertion
hez2010 May 28, 2026
466ba2c
Use IsSharedByGenericMethodInstantiations
hez2010 May 28, 2026
dca1834
Add a couple of tests
hez2010 May 28, 2026
2582ba6
Address test name collisions
hez2010 May 29, 2026
800aa20
Fix crossgen2 not producing a const lookup
hez2010 May 30, 2026
a5c4574
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 May 30, 2026
31ff006
Fix NativeAOT build
hez2010 May 30, 2026
645e5dd
Pass a type dictionary fixup when the method doesn't have an instanti…
hez2010 May 30, 2026
1f2aacc
Nit
hez2010 May 30, 2026
cad1140
More fixes to AOT
hez2010 May 30, 2026
0c7102a
Use a better helper
hez2010 May 30, 2026
8f02750
Minor refactor
hez2010 May 30, 2026
e27aba9
Handle unboxing stub
hez2010 May 30, 2026
b77ad2d
Bail out unboxing stub in R2R for now
hez2010 May 30, 2026
de3bd27
Clean up tests
hez2010 May 31, 2026
60dfd3f
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 1, 2026
41e0827
Nit
hez2010 Jun 1, 2026
b55d5c1
Take canonical subtype into account as well
hez2010 Jun 1, 2026
0820190
Add a test case
hez2010 Jun 1, 2026
d47b382
Refactor and properly handle unboxing stubs
hez2010 Jun 2, 2026
fe25211
Cleanup getUnboxedEntry and getInstantiatedEntry
hez2010 Jun 11, 2026
852a572
Check the context directly
hez2010 Jun 11, 2026
959ad95
Merge branch 'main' into unboxing-stub-cleanup
hez2010 Jun 13, 2026
872c5dc
Merge branch 'main' into unboxing-stub-cleanup
hez2010 Jun 14, 2026
66f0836
Merge branch 'main' into unboxing-stub-cleanup
hez2010 Jun 19, 2026
272667a
Merge branch 'main' into unboxing-stub-cleanup
hez2010 Jun 20, 2026
4b0568e
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 20, 2026
109a293
Merge branch 'unboxing-stub-cleanup' into devirt-instantiating-stub-c…
hez2010 Jun 20, 2026
fe5b4fa
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 21, 2026
176d6b9
Rework unboxing stub and instantiating stub
hez2010 Jun 20, 2026
5dec373
Address some verification issues
hez2010 Jun 21, 2026
d0b8e36
Add file header
hez2010 Jun 21, 2026
e0bbb26
Reduce generic nesting depth to make ilc compile
hez2010 Jun 22, 2026
231533d
Gate the unboxing stub test case for NativeAOT
hez2010 Jun 22, 2026
f7145a5
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 22, 2026
933f030
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 24, 2026
044a8c3
Revert "Gate the unboxing stub test case for NativeAOT"
hez2010 Jun 24, 2026
374e5e3
Refine tests
hez2010 Jun 24, 2026
862d314
Exercise s_list element as well
hez2010 Jun 24, 2026
9f681d5
Oops
hez2010 Jun 24, 2026
07d6acd
More coverage
hez2010 Jun 24, 2026
66a508e
Expand to full test matrix
hez2010 Jun 24, 2026
bf8e205
Meh
hez2010 Jun 24, 2026
42858f8
Remove redundant code after merging
hez2010 Jun 24, 2026
ff93779
Cleanup
hez2010 Jun 24, 2026
b76ac1e
Address a missing case
hez2010 Jun 24, 2026
4517553
Clarify the instArg check for R2R
hez2010 Jun 24, 2026
d964fee
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 24, 2026
3fe11a8
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 26, 2026
87b4e0c
Skip generic recursion cases for NativeAOT due to type loader limitat…
hez2010 Jun 26, 2026
c196f84
Use IsNativeAot
hez2010 Jun 26, 2026
7c73562
Address feedbacks
hez2010 Jun 26, 2026
ee2bdf7
Turn the failure into an TypeLoadException
hez2010 Jun 26, 2026
79a2bdf
Don't capture generic arity mismatch
hez2010 Jun 26, 2026
42dc0d2
Meh
hez2010 Jun 26, 2026
0f840d7
Add non-recursive derived struct tests
hez2010 Jun 26, 2026
b4d9776
Merge branch 'main' into devirt-instantiating-stub-coreclr
hez2010 Jun 26, 2026
b527c60
Oops
hez2010 Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,19 +148,19 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
return null;

case DefaultInterfaceMethodResolution.DefaultImplementation:
if (dimMethod.OwningType.HasInstantiation || (declMethod != defaultInterfaceDispatchDeclMethod))
if (declMethod != defaultInterfaceDispatchDeclMethod)
{
// If we devirtualized into a default interface method on a generic type, we should actually return an
// instantiating stub but this is not happening.
// Making this work is tracked by https://github.com/dotnet/runtime/issues/9588

// In addition, we fail here for variant default interface dispatch
// Fail for variant default interface dispatch
devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DIM;
return null;
}
Comment thread
hez2010 marked this conversation as resolved.
else
{
impl = dimMethod;
if (originalDeclMethod.HasInstantiation)
{
impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation);
}
}
break;
}
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private static void ForEachEmbeddedGenericFormalWorker(TypeDesc type, Instantiat
TypeDesc genericTypeDefinition = type.GetTypeDefinition();
Instantiation genericTypeParameters = genericTypeDefinition.Instantiation;
Instantiation genericTypeArguments = type.Instantiation;

for (int i = 0; i < genericTypeArguments.Length; i++)
{
var genericTypeParameter = (EcmaGenericParameter)genericTypeParameters[i];
Expand Down
70 changes: 69 additions & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,74 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
info->resolvedTokenDevirtualizedUnboxedMethod = default(CORINFO_RESOLVED_TOKEN);
}

#if READYTORUN
bool isArray = decl.OwningType.IsInterface && objType.IsArray;
bool contextIsMethod = isArray || decl.HasInstantiation;
#else
bool contextIsMethod = decl.HasInstantiation;
#endif
MethodDesc instArgTarget = unboxingStub ? nonUnboxingImpl : impl;
bool requiresInstMethodDescArg = instArgTarget.RequiresInstMethodDescArg();
bool requiresInstMethodTableArg = instArgTarget.RequiresInstMethodTableArg();

// For unboxing stubs whose unboxed entry needs a MethodTable inst arg, the boxed object supplies the exact MT.
// For MethodDesc cases we always need to supply the exact MD.
if (requiresInstMethodDescArg || (requiresInstMethodTableArg && !unboxingStub))
{
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 even as a runtime lookup.
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}

if (originalImpl.IsRuntimeDeterminedExactMethod || originalImpl.IsSharedByGenericInstantiations)
{
// TODO: Support for runtime lookup
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}
}

#if READYTORUN
if (isArray)
{
// Array interface devirt is not yet supported by R2R.
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}
#endif

if (requiresInstMethodDescArg)
{
if (unboxingStub)
{
// Bail out for now. We need an unboxing stub that points to an instantiated method.
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}
#if READYTORUN
MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null);
info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodDictionary, originalImplWithToken));

#else
info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodGenericDictionary(originalImpl));
Comment thread
hez2010 marked this conversation as resolved.
#endif
}
else if (requiresInstMethodTableArg)
{
if (!unboxingStub)
{
#if READYTORUN
info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeDictionary, originalImpl.OwningType));

#else
info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ConstructedTypeSymbol(originalImpl.OwningType));
#endif
}
}

#if READYTORUN
// Testing has not shown that concerns about virtual matching are significant
// Only generate verification for builds with the stress mode enabled
Expand All @@ -1497,7 +1565,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
#endif
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_SUCCESS;
info->devirtualizedMethod = ObjectToHandle(impl);
info->tokenLookupContext = contextFromType(owningType);
info->tokenLookupContext = contextIsMethod ? contextFromMethod(originalImpl) : contextFromType(owningType);

return true;
Comment thread
hez2010 marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefTyp
//
// Result method checking
// 1. Ensure that the resolved result versions with the code, or is the decl method
// 2. Devirtualizing to a default interface method is not currently considered to be useful, and how to check for version
// resilience has not yet been analyzed.
// 2. When devirtualizing to a default interface method, the resolved result method must version with the code.
// 3. When checking that the resolved result versions with the code, validate that all of the types
// From implType to the owning type of resolved result method also version with the code.

Expand Down Expand Up @@ -163,6 +162,17 @@ protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefTyp

if (resolvedVirtualMethod != null)
{
if (resolvedVirtualMethod.OwningType.IsInterface)
{
if (_compilationModuleGroup.VersionsWithMethodBody(resolvedVirtualMethod))
{
return resolvedVirtualMethod;
}

devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE;
return null;
}

// Validate that the inheritance chain for resolution is within version bubble
// The rule is somewhat tricky here.
// If the resolved method is the declMethod, then only types which derive from the
Expand Down
107 changes: 75 additions & 32 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8770,15 +8770,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP;
return false;
}

// If we devirtualized into a default interface method on a generic type, we should actually return an
// instantiating stub but this is not happening.
// Making this work is tracked by https://github.com/dotnet/runtime/issues/9588
if (pDevirtMD->GetMethodTable()->IsInterface() && pDevirtMD->HasClassInstantiation())
{
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_DIM;
return false;
}
}
else
{
Expand Down Expand Up @@ -8841,53 +8832,105 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
bool isArray = false;
bool isGenericVirtual = false;

if (pApproxMT->IsInterface())
{
// As noted above, we can't yet handle generic interfaces
// with default methods.
_ASSERTE(!pDevirtMD->HasClassInstantiation());

}
else if (pBaseMT->IsInterface() && pObjMT->IsArray())
if (pBaseMT->IsInterface() && pObjMT->IsArray())
{
isArray = true;
}
else
else if (!pApproxMT->IsInterface())
{
pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT);
}

MethodDesc* pInstantiatedMD = pDevirtMD;
Comment thread
hez2010 marked this conversation as resolved.

// This is generic virtual method devirtualization.
if (!isArray && pBaseMD->HasMethodInstantiation())
{
MethodDesc* pPrimaryMD = pDevirtMD;
MethodDesc* pPrimaryMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;

pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true);
if (pDevirtMD->IsSharedByGenericMethodInstantiations())

pInstantiatedMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false);

isGenericVirtual = true;
}

MethodDesc* pInstArgMD = pDevirtMD;
bool isUnboxingStubOfInstantiatingStub = false;

if (pDevirtMD->IsUnboxingStub())
{
// RequiresInstMethodDescArg and RequiresInstMethodTableArg are only valid for canonical instantiatins,
// use pDevirtMD instead of pInstantiatedMD for pInstArgMD.
//
pInstArgMD = pDevirtMD->GetWrappedMethodDesc();
if (pInstArgMD->IsInstantiatingStub())
{
isUnboxingStubOfInstantiatingStub = true;
}
}
else if (pDevirtMD->IsInstantiatingStub())
{
pInstArgMD = pDevirtMD->GetWrappedMethodDesc();
}

if (pInstArgMD->RequiresInstMethodDescArg())
{
if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->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 even as a runtime lookup.
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}

isGenericVirtual = true;
}
if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation()))
{
// TODO: Support for runtime lookup
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}

if (isArray || isGenericVirtual)
info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstantiatedMD;
info->instParamLookup.constLookup.accessType = IAT_VALUE;
}
else if (pInstArgMD->RequiresInstMethodTableArg())
{
if (pDevirtMD->IsInstantiatingStub())
if (!pDevirtMD->IsUnboxingStub() && TypeHandle::IsCanonicalSubtypeInstantiation(pExactMT->GetInstantiation()))
{
info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pDevirtMD;
info->instParamLookup.constLookup.accessType = IAT_VALUE;
// If we end up with a shared MethodTable that is not exact,
// we can't devirtualize since it's not possible to compute the instantiation argument even as a runtime lookup.
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}

info->tokenLookupContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD);
pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pExactMT;
info->instParamLookup.constLookup.accessType = IAT_VALUE;
}
else
else if (isUnboxingStubOfInstantiatingStub)
{
info->tokenLookupContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT);
if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetClassInstantiation()) ||
TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation()))
{
// This is an unboxing stub that points to an instantiating stub that requires a runtime lookup.
// Bail out.
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}

// pInstArgMD is the wrapped instantiating stub in the unboxing stub.
//
info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstArgMD;
info->instParamLookup.constLookup.accessType = IAT_VALUE;
}

pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
info->tokenLookupContext = (isArray || isGenericVirtual)
? MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pInstantiatedMD)
: MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT);

// If we devirtualized into an unboxing stub, also hand back the unboxed entry
// so the jit can perform the unboxing transformation.
//
Expand Down Expand Up @@ -14639,8 +14682,8 @@ BOOL LoadDynamicInfoEntry(Module *currentModule,
}
}

// Strip off method instantiation for comparison if the method is generic virtual.
if (pDeclMethod->HasMethodInstantiation())
// Strip off method instantiation for comparison if the method is generic virtual or generic DIM.
if (pDeclMethod->HasMethodInstantiation() || pDeclMethod->IsInterface())
{
if (pImplMethodRuntime != NULL)
{
Expand Down
Loading
Loading