Skip to content

Commit 8dcfacb

Browse files
Add support for static virtual methods (#66084)
Took the type system changes from #54063 and cleaned them up, added unit tests. Hooked it up into JitInterface/ResolveConstraintMethodApprox. Using the pre-existing `ConstrainedMethodUseLookupResult` that wasn't currently getting emitted. We'll want to use it for its original purpose at some point, but I think we can make this work for both instance and static constrained calls. Missing things: * Support creating delegates to static virtual methods. This will need a RyuJIT/JitInterface change. * Type loader support. If `MakeGeneric` needs static virtuals at runtime, it will throw. But this is enough to get HttpClient working again. Fixes #65613. Contributes to dotnet/runtimelab#1665.
1 parent 7b83da5 commit 8dcfacb

21 files changed

+607
-23
lines changed

src/coreclr/tools/Common/Compiler/TypeExtensions.cs

+29-6
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,11 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
421421
{
422422
forceRuntimeLookup = false;
423423

424+
bool isStaticVirtualMethod = interfaceMethod.Signature.IsStatic;
425+
424426
// We can't resolve constraint calls effectively for reference types, and there's
425427
// not a lot of perf. benefit in doing it anyway.
426-
if (!constrainedType.IsValueType)
428+
if (!constrainedType.IsValueType && (!isStaticVirtualMethod || constrainedType.IsCanonicalDefinitionType(CanonicalFormKind.Any)))
427429
{
428430
return null;
429431
}
@@ -466,10 +468,17 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
466468
potentialInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)potentialInterfaceType);
467469
}
468470

469-
method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
471+
if (isStaticVirtualMethod)
472+
{
473+
method = canonType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(potentialInterfaceMethod);
474+
}
475+
else
476+
{
477+
method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
478+
}
470479

471480
// See code:#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
472-
if (method != null && !method.OwningType.IsValueType)
481+
if (!isStaticVirtualMethod && method != null && !method.OwningType.IsValueType)
473482
{
474483
// We explicitly wouldn't want to abort if we found a default implementation.
475484
// The above resolution doesn't consider the default methods.
@@ -500,7 +509,14 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
500509
// We can resolve to exact method
501510
MethodDesc exactInterfaceMethod = context.GetMethodForInstantiatedType(
502511
genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
503-
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
512+
if (isStaticVirtualMethod)
513+
{
514+
method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
515+
}
516+
else
517+
{
518+
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
519+
}
504520
isExactMethodResolved = method != null;
505521
}
506522
}
@@ -523,7 +539,14 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
523539
if (genInterfaceMethod.OwningType != interfaceType)
524540
exactInterfaceMethod = context.GetMethodForInstantiatedType(
525541
genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
526-
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
542+
if (isStaticVirtualMethod)
543+
{
544+
method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
545+
}
546+
else
547+
{
548+
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
549+
}
527550
}
528551
}
529552
}
@@ -548,7 +571,7 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
548571
//#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
549572
// Only return a method if the value type itself declares the method,
550573
// otherwise we might get a method from Object or System.ValueType
551-
if (!method.OwningType.IsValueType)
574+
if (!isStaticVirtualMethod && !method.OwningType.IsValueType)
552575
{
553576
// Fall back to VSD
554577
return null;

src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs

+117
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,16 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me
572572
return ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
573573
}
574574

575+
public override MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
576+
{
577+
return ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
578+
}
579+
580+
public override MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
581+
{
582+
return ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
583+
}
584+
575585
//////////////////////// INTERFACE RESOLUTION
576586
//Interface function resolution
577587
// Interface function resolution follows the following rules
@@ -588,6 +598,8 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me
588598
// See current interface call resolution for details on how that happens.
589599
private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
590600
{
601+
Debug.Assert(!interfaceMethod.Signature.IsStatic);
602+
591603
if (currentType.IsInterface)
592604
return null;
593605

@@ -657,6 +669,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
657669

658670
public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
659671
{
672+
Debug.Assert(!interfaceMethod.Signature.IsStatic);
673+
660674
MetadataType interfaceType = (MetadataType)interfaceMethod.OwningType;
661675
bool foundInterface = IsInterfaceImplementedOnType(currentType, interfaceType);
662676
MethodDesc implMethod;
@@ -841,5 +855,108 @@ public static IEnumerable<MethodDesc> EnumAllVirtualSlots(MetadataType type)
841855
} while (type != null);
842856
}
843857
}
858+
859+
/// <summary>
860+
/// Try to resolve a given virtual static interface method on a given constrained type and its base types.
861+
/// </summary>
862+
/// <param name="interfaceMethod">Interface method to resolve</param>
863+
/// <param name="currentType">Type to attempt virtual static method resolution on</param>
864+
/// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
865+
public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
866+
{
867+
TypeDesc interfaceType = interfaceMethod.OwningType;
868+
869+
// Search for match on a per-level in the type hierarchy
870+
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
871+
{
872+
MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod);
873+
if (resolvedMethodOnType != null)
874+
{
875+
return resolvedMethodOnType;
876+
}
877+
}
878+
return null;
879+
}
880+
881+
/// <summary>
882+
/// Try to resolve a given virtual static interface method on a given constrained type and its base types.
883+
/// </summary>
884+
/// <param name="interfaceMethod">Interface method to resolve</param>
885+
/// <param name="currentType">Type to attempt virtual static method resolution on</param>
886+
/// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
887+
public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
888+
{
889+
TypeDesc interfaceType = interfaceMethod.OwningType;
890+
891+
// Search for match on a per-level in the type hierarchy
892+
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
893+
{
894+
MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod);
895+
if (resolvedMethodOnType != null)
896+
{
897+
return resolvedMethodOnType;
898+
}
899+
900+
// Variant interface dispatch
901+
foreach (DefType runtimeInterfaceType in typeToCheck.RuntimeInterfaces)
902+
{
903+
if (runtimeInterfaceType == interfaceType)
904+
{
905+
// This is the variant interface check logic, skip this
906+
continue;
907+
}
908+
909+
if (!runtimeInterfaceType.HasSameTypeDefinition(interfaceType))
910+
{
911+
// Variance matches require a typedef match
912+
// Equivalence isn't sufficient, and is uninteresting as equivalent interfaces cannot have static virtuals.
913+
continue;
914+
}
915+
916+
if (runtimeInterfaceType.CanCastTo(interfaceType))
917+
{
918+
// Attempt to resolve on variance matched interface
919+
MethodDesc runtimeInterfaceMethod = runtimeInterfaceType.FindMethodOnExactTypeWithMatchingTypicalMethod(interfaceMethod);
920+
resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, runtimeInterfaceMethod);
921+
if (resolvedMethodOnType != null)
922+
{
923+
return resolvedMethodOnType;
924+
}
925+
}
926+
}
927+
}
928+
return null;
929+
}
930+
931+
/// <summary>
932+
/// Try to resolve a given virtual static interface method on a given constrained type and return the resolved method or null when not found.
933+
/// </summary>
934+
/// <param name="constrainedType">Type to attempt method resolution on</param>
935+
/// <param name="interfaceMethod">Method to resolve</param>
936+
/// <returns>MethodDesc of the resolved method or null when not found (runtime lookup must be used)</returns>
937+
private static MethodDesc TryResolveVirtualStaticMethodOnThisType(MetadataType constrainedType, MethodDesc interfaceMethod)
938+
{
939+
Debug.Assert(interfaceMethod.Signature.IsStatic);
940+
941+
MethodImplRecord[] possibleImpls = constrainedType.FindMethodsImplWithMatchingDeclName(interfaceMethod.Name);
942+
if (possibleImpls == null)
943+
return null;
944+
945+
MethodDesc interfaceMethodDefinition = interfaceMethod.GetMethodDefinition();
946+
foreach (MethodImplRecord methodImpl in possibleImpls)
947+
{
948+
if (methodImpl.Decl == interfaceMethodDefinition)
949+
{
950+
MethodDesc resolvedMethodImpl = methodImpl.Body;
951+
if (interfaceMethod != interfaceMethodDefinition)
952+
{
953+
resolvedMethodImpl = resolvedMethodImpl.MakeInstantiatedMethod(interfaceMethod.Instantiation);
954+
}
955+
return resolvedMethodImpl;
956+
}
957+
}
958+
959+
return null;
960+
}
844961
}
845962
}

src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs

+10
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,16 @@ public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(this
205205
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, type);
206206
}
207207

208+
public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod)
209+
{
210+
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type);
211+
}
212+
213+
public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod)
214+
{
215+
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type);
216+
}
217+
208218
public static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(this TypeDesc type, MethodDesc interfaceMethod, out MethodDesc implMethod)
209219
{
210220
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, type, out implMethod);

src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ public abstract class VirtualMethodAlgorithm
2424

2525
public abstract MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);
2626

27+
public abstract MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);
28+
29+
public abstract MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);
30+
2731
public abstract DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl);
2832

2933
/// <summary>

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs

+12
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ public bool NeedsRuntimeLookup(ReadyToRunHelperId lookupKind, object targetOfLoo
252252
case ReadyToRunHelperId.FieldHandle:
253253
return ((FieldDesc)targetOfLookup).OwningType.IsRuntimeDeterminedSubtype;
254254

255+
case ReadyToRunHelperId.ConstrainedDirectCall:
256+
return ((ConstrainedCallInfo)targetOfLookup).Method.IsRuntimeDeterminedExactMethod
257+
|| ((ConstrainedCallInfo)targetOfLookup).ConstrainedType.IsRuntimeDeterminedSubtype;
258+
255259
default:
256260
throw new NotImplementedException();
257261
}
@@ -643,4 +647,12 @@ public IEnumerable<TypeDesc> ConstructedEETypes
643647
}
644648
}
645649
}
650+
651+
public sealed class ConstrainedCallInfo
652+
{
653+
public readonly TypeDesc ConstrainedType;
654+
public readonly MethodDesc Method;
655+
public ConstrainedCallInfo(TypeDesc constrainedType, MethodDesc method)
656+
=> (ConstrainedType, Method) = (constrainedType, method);
657+
}
646658
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs

+4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ public override bool InterestingForDynamicDependencyAnalysis
162162
{
163163
Debug.Assert(method.IsVirtual);
164164

165+
// Static interface methods don't participate in GVM analysis
166+
if (method.Signature.IsStatic)
167+
continue;
168+
165169
if (method.HasInstantiation)
166170
{
167171
// We found a GVM on one of the implemented interfaces. Find if the type implements this method.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs

+26-9
Original file line numberDiff line numberDiff line change
@@ -1431,20 +1431,36 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo
14311431
{
14321432
MethodDesc instantiatedConstrainedMethod = _constrainedMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
14331433
TypeDesc instantiatedConstraintType = _constraintType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
1434-
MethodDesc implMethod = instantiatedConstrainedMethod;
1434+
MethodDesc implMethod;
14351435

1436-
if (implMethod.OwningType.IsInterface)
1436+
if (instantiatedConstrainedMethod.OwningType.IsInterface)
14371437
{
1438-
implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToVirtualMethodOnType(implMethod);
1438+
if (instantiatedConstrainedMethod.Signature.IsStatic)
1439+
{
1440+
implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(instantiatedConstrainedMethod);
1441+
}
1442+
else
1443+
{
1444+
throw new NotImplementedException();
1445+
}
1446+
}
1447+
else
1448+
{
1449+
implMethod = instantiatedConstraintType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(instantiatedConstrainedMethod);
14391450
}
1440-
1441-
implMethod = instantiatedConstraintType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(implMethod);
14421451

14431452
// AOT use of this generic lookup is restricted to finding methods on valuetypes (runtime usage of this slot in universal generics is more flexible)
1444-
Debug.Assert(instantiatedConstraintType.IsValueType);
1445-
Debug.Assert(implMethod.OwningType == instantiatedConstraintType);
1453+
Debug.Assert(instantiatedConstraintType.IsValueType || (instantiatedConstrainedMethod.OwningType.IsInterface && instantiatedConstrainedMethod.Signature.IsStatic));
1454+
Debug.Assert(!instantiatedConstraintType.IsValueType || implMethod.OwningType == instantiatedConstraintType);
14461455

1447-
if (implMethod.HasInstantiation)
1456+
if (implMethod.Signature.IsStatic)
1457+
{
1458+
if (implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsSharedByGenericInstantiations)
1459+
return factory.ExactCallableAddress(implMethod);
1460+
else
1461+
return factory.MethodEntrypoint(implMethod);
1462+
}
1463+
else if (implMethod.HasInstantiation)
14481464
{
14491465
return factory.ExactCallableAddress(implMethod);
14501466
}
@@ -1467,7 +1483,8 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde
14671483

14681484
public override NativeLayoutVertexNode TemplateDictionaryNode(NodeFactory factory)
14691485
{
1470-
return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall);
1486+
return factory.NativeLayout.NotSupportedDictionarySlot;
1487+
//return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall);
14711488
}
14721489

14731490
public override void WriteDictionaryTocData(NodeFactory factory, IGenericLookupResultTocWriter writer)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs

+4
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
111111

112112
foreach (MethodDesc slotMethod in slots)
113113
{
114+
// Static interface methods don't go in the dispatch map
115+
if (slotMethod.Signature.IsStatic)
116+
continue;
117+
114118
MethodDesc declMethod = slotMethod;
115119

116120
Debug.Assert(!declMethod.Signature.IsStatic && declMethod.IsVirtual);

0 commit comments

Comments
 (0)