Skip to content

Commit

Permalink
Add support for static virtual methods
Browse files Browse the repository at this point in the history
Took the type system changes from dotnet#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 dotnet#65613. Contributes to dotnet/runtimelab#1665.
  • Loading branch information
MichalStrehovsky committed Mar 2, 2022
1 parent c41f452 commit 44f9189
Show file tree
Hide file tree
Showing 21 changed files with 607 additions and 23 deletions.
35 changes: 29 additions & 6 deletions src/coreclr/tools/Common/Compiler/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,11 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
{
forceRuntimeLookup = false;

bool isStaticVirtualMethod = interfaceMethod.Signature.IsStatic;

// We can't resolve constraint calls effectively for reference types, and there's
// not a lot of perf. benefit in doing it anyway.
if (!constrainedType.IsValueType)
if (!constrainedType.IsValueType && (!isStaticVirtualMethod || constrainedType.IsCanonicalDefinitionType(CanonicalFormKind.Any)))
{
return null;
}
Expand Down Expand Up @@ -466,10 +468,17 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
potentialInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)potentialInterfaceType);
}

method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
if (isStaticVirtualMethod)
{
method = canonType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(potentialInterfaceMethod);
}
else
{
method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
}

// See code:#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
if (method != null && !method.OwningType.IsValueType)
if (!isStaticVirtualMethod && method != null && !method.OwningType.IsValueType)
{
// We explicitly wouldn't want to abort if we found a default implementation.
// The above resolution doesn't consider the default methods.
Expand Down Expand Up @@ -500,7 +509,14 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
// We can resolve to exact method
MethodDesc exactInterfaceMethod = context.GetMethodForInstantiatedType(
genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
if (isStaticVirtualMethod)
{
method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
}
else
{
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
}
isExactMethodResolved = method != null;
}
}
Expand All @@ -523,7 +539,14 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
if (genInterfaceMethod.OwningType != interfaceType)
exactInterfaceMethod = context.GetMethodForInstantiatedType(
genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
if (isStaticVirtualMethod)
{
method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
}
else
{
method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
}
}
}
}
Expand All @@ -548,7 +571,7 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai
//#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
// Only return a method if the value type itself declares the method,
// otherwise we might get a method from Object or System.ValueType
if (!method.OwningType.IsValueType)
if (!isStaticVirtualMethod && !method.OwningType.IsValueType)
{
// Fall back to VSD
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,16 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me
return ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
}

public override MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
{
return ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
}

public override MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
{
return ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
}

//////////////////////// INTERFACE RESOLUTION
//Interface function resolution
// Interface function resolution follows the following rules
Expand All @@ -588,6 +598,8 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me
// See current interface call resolution for details on how that happens.
private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
{
Debug.Assert(!interfaceMethod.Signature.IsStatic);

if (currentType.IsInterface)
return null;

Expand Down Expand Up @@ -657,6 +669,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc

public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
{
Debug.Assert(!interfaceMethod.Signature.IsStatic);

MetadataType interfaceType = (MetadataType)interfaceMethod.OwningType;
bool foundInterface = IsInterfaceImplementedOnType(currentType, interfaceType);
MethodDesc implMethod;
Expand Down Expand Up @@ -841,5 +855,108 @@ public static IEnumerable<MethodDesc> EnumAllVirtualSlots(MetadataType type)
} while (type != null);
}
}

/// <summary>
/// Try to resolve a given virtual static interface method on a given constrained type and its base types.
/// </summary>
/// <param name="interfaceMethod">Interface method to resolve</param>
/// <param name="currentType">Type to attempt virtual static method resolution on</param>
/// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
{
TypeDesc interfaceType = interfaceMethod.OwningType;

// Search for match on a per-level in the type hierarchy
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod);
if (resolvedMethodOnType != null)
{
return resolvedMethodOnType;
}
}
return null;
}

/// <summary>
/// Try to resolve a given virtual static interface method on a given constrained type and its base types.
/// </summary>
/// <param name="interfaceMethod">Interface method to resolve</param>
/// <param name="currentType">Type to attempt virtual static method resolution on</param>
/// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
{
TypeDesc interfaceType = interfaceMethod.OwningType;

// Search for match on a per-level in the type hierarchy
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod);
if (resolvedMethodOnType != null)
{
return resolvedMethodOnType;
}

// Variant interface dispatch
foreach (DefType runtimeInterfaceType in typeToCheck.RuntimeInterfaces)
{
if (runtimeInterfaceType == interfaceType)
{
// This is the variant interface check logic, skip this
continue;
}

if (!runtimeInterfaceType.HasSameTypeDefinition(interfaceType))
{
// Variance matches require a typedef match
// Equivalence isn't sufficient, and is uninteresting as equivalent interfaces cannot have static virtuals.
continue;
}

if (runtimeInterfaceType.CanCastTo(interfaceType))
{
// Attempt to resolve on variance matched interface
MethodDesc runtimeInterfaceMethod = runtimeInterfaceType.FindMethodOnExactTypeWithMatchingTypicalMethod(interfaceMethod);
resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, runtimeInterfaceMethod);
if (resolvedMethodOnType != null)
{
return resolvedMethodOnType;
}
}
}
}
return null;
}

/// <summary>
/// Try to resolve a given virtual static interface method on a given constrained type and return the resolved method or null when not found.
/// </summary>
/// <param name="constrainedType">Type to attempt method resolution on</param>
/// <param name="interfaceMethod">Method to resolve</param>
/// <returns>MethodDesc of the resolved method or null when not found (runtime lookup must be used)</returns>
private static MethodDesc TryResolveVirtualStaticMethodOnThisType(MetadataType constrainedType, MethodDesc interfaceMethod)
{
Debug.Assert(interfaceMethod.Signature.IsStatic);

MethodImplRecord[] possibleImpls = constrainedType.FindMethodsImplWithMatchingDeclName(interfaceMethod.Name);
if (possibleImpls == null)
return null;

MethodDesc interfaceMethodDefinition = interfaceMethod.GetMethodDefinition();
foreach (MethodImplRecord methodImpl in possibleImpls)
{
if (methodImpl.Decl == interfaceMethodDefinition)
{
MethodDesc resolvedMethodImpl = methodImpl.Body;
if (interfaceMethod != interfaceMethodDefinition)
{
resolvedMethodImpl = resolvedMethodImpl.MakeInstantiatedMethod(interfaceMethod.Instantiation);
}
return resolvedMethodImpl;
}
}

return null;
}
}
}
10 changes: 10 additions & 0 deletions src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@ public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(this
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, type);
}

public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod)
{
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type);
}

public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod)
{
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type);
}

public static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(this TypeDesc type, MethodDesc interfaceMethod, out MethodDesc implMethod)
{
return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, type, out implMethod);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public abstract class VirtualMethodAlgorithm

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

public abstract MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);

public abstract MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);

public abstract DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl);

/// <summary>
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ public bool NeedsRuntimeLookup(ReadyToRunHelperId lookupKind, object targetOfLoo
case ReadyToRunHelperId.FieldHandle:
return ((FieldDesc)targetOfLookup).OwningType.IsRuntimeDeterminedSubtype;

case ReadyToRunHelperId.ConstrainedDirectCall:
return ((ConstrainedCallInfo)targetOfLookup).Method.IsRuntimeDeterminedExactMethod
|| ((ConstrainedCallInfo)targetOfLookup).ConstrainedType.IsRuntimeDeterminedSubtype;

default:
throw new NotImplementedException();
}
Expand Down Expand Up @@ -643,4 +647,12 @@ public IEnumerable<TypeDesc> ConstructedEETypes
}
}
}

public sealed class ConstrainedCallInfo
{
public readonly TypeDesc ConstrainedType;
public readonly MethodDesc Method;
public ConstrainedCallInfo(TypeDesc constrainedType, MethodDesc method)
=> (ConstrainedType, Method) = (constrainedType, method);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ public override bool InterestingForDynamicDependencyAnalysis
{
Debug.Assert(method.IsVirtual);

// Static interface methods don't participate in GVM analysis
if (method.Signature.IsStatic)
continue;

if (method.HasInstantiation)
{
// We found a GVM on one of the implemented interfaces. Find if the type implements this method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1431,20 +1431,36 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo
{
MethodDesc instantiatedConstrainedMethod = _constrainedMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
TypeDesc instantiatedConstraintType = _constraintType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
MethodDesc implMethod = instantiatedConstrainedMethod;
MethodDesc implMethod;

if (implMethod.OwningType.IsInterface)
if (instantiatedConstrainedMethod.OwningType.IsInterface)
{
implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToVirtualMethodOnType(implMethod);
if (instantiatedConstrainedMethod.Signature.IsStatic)
{
implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(instantiatedConstrainedMethod);
}
else
{
throw new NotImplementedException();
}
}
else
{
implMethod = instantiatedConstraintType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(instantiatedConstrainedMethod);
}

implMethod = instantiatedConstraintType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(implMethod);

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

if (implMethod.HasInstantiation)
if (implMethod.Signature.IsStatic)
{
if (implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsSharedByGenericInstantiations)
return factory.ExactCallableAddress(implMethod);
else
return factory.MethodEntrypoint(implMethod);
}
else if (implMethod.HasInstantiation)
{
return factory.ExactCallableAddress(implMethod);
}
Expand All @@ -1467,7 +1483,8 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde

public override NativeLayoutVertexNode TemplateDictionaryNode(NodeFactory factory)
{
return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall);
return factory.NativeLayout.NotSupportedDictionarySlot;
//return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall);
}

public override void WriteDictionaryTocData(NodeFactory factory, IGenericLookupResultTocWriter writer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

foreach (MethodDesc slotMethod in slots)
{
// Static interface methods don't go in the dispatch map
if (slotMethod.Signature.IsStatic)
continue;

MethodDesc declMethod = slotMethod;

Debug.Assert(!declMethod.Signature.IsStatic && declMethod.IsVirtual);
Expand Down
Loading

0 comments on commit 44f9189

Please sign in to comment.