Skip to content

Commit

Permalink
Make it possible to resolve static virtuals to unshared methods at ru…
Browse files Browse the repository at this point in the history
…ntime (#104965)

Static virtual methods are usually resolved at compile time but whenever there's a dynamic scenario (`MakeGenericType` or a instance generic virtual method call), we might need to resolve them at runtime. This worked when the static virtual is a shared method, but didn't work when it's unshared. This fixes it. The fix has two logical parts:

* Runtime part (GenericDictionaryCell.cs): when the computed target of the static virtual lookup is an unshared method, don't call into type loader. We know the function pointer to use and don't need anything else.
* Compiler part (everything else): we have a hashtable of all generic virtual methods we could potentially need in the ExactMethodInstantiations hashtable. This hashtable was previously filled with a heuristic: if we compiled an unshared generic virtual method, place it in the hashtable. This places too much and too little in the hashtable. Too much: if the generic virtual method isn't actually part of a generic virtual dispatch (e.g. was called non-virtually), we'd still put it in the hashtable and never use it at runtime. Too little: implementations of static virtual interface methods are not virtual themselves. So we missed them. This makes the tracking more precise so that we put exactly what we need (in a sense it's similar to #87466).
  • Loading branch information
MichalStrehovsky authored Jul 18, 2024
1 parent 8f3a317 commit 66ae898
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,24 @@ private class GenericStaticConstrainedMethodCell : GenericDictionaryCell
internal override void Prepare(TypeBuilder builder)
{
_resolvedMethod = TypeLoaderEnvironment.GVMLookupForSlotWorker(ConstraintType, ConstrainedMethod);
builder.PrepareMethod(_resolvedMethod);

if (_resolvedMethod.CanShareNormalGenericCode())
builder.PrepareMethod(_resolvedMethod);
}

internal override IntPtr Create(TypeBuilder builder)
{
IntPtr methodDictionary = _resolvedMethod.RuntimeMethodDictionary;
return FunctionPointerOps.GetGenericMethodFunctionPointer(_resolvedMethod.FunctionPointer, methodDictionary);
if (_resolvedMethod.CanShareNormalGenericCode())
{
IntPtr methodDictionary = _resolvedMethod.RuntimeMethodDictionary;
return FunctionPointerOps.GetGenericMethodFunctionPointer(_resolvedMethod.FunctionPointer, methodDictionary);
}
else
{
if (!TypeLoaderEnvironment.Instance.TryLookupExactMethodPointer(_resolvedMethod, out nint result))
Environment.FailFast("Unable to find exact method pointer for a resolved GVM.");
return result;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

using ILCompiler.DependencyAnalysisFramework;

using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.DependencyAnalysis
{
public class ExactMethodInstantiationsEntryNode : DependencyNodeCore<NodeFactory>
{
private readonly MethodDesc _method;

public ExactMethodInstantiationsEntryNode(MethodDesc method)
{
Debug.Assert(!method.IsAbstract);
Debug.Assert(method.HasInstantiation);
Debug.Assert(!method.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Any));
_method = method;
}

public MethodDesc Method => _method;

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
{
DependencyList dependencies = null;
ExactMethodInstantiationsNode.GetExactMethodInstantiationDependenciesForMethod(ref dependencies, factory, _method);
Debug.Assert(dependencies != null);
return dependencies;
}
protected override string GetName(NodeFactory factory)
{
return "Exact methods hashtable entry: " + _method.ToString();
}

public override bool InterestingForDynamicDependencyAnalysis => false;
public override bool HasDynamicDependencies => false;
public override bool HasConditionalStaticDependencies => false;
public override bool StaticDependenciesAreComputed => true;
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) => null;
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
nativeSection.Place(hashtable);


foreach (MethodDesc method in factory.MetadataManager.GetCompiledMethods())
foreach (MethodDesc method in factory.MetadataManager.GetExactMethodHashtableEntries())
{
if (!IsMethodEligibleForTracking(factory, method))
continue;

// Get the method pointer vertex

bool getUnboxingStub = method.OwningType.IsValueType && !method.Signature.IsStatic;
Expand Down Expand Up @@ -106,9 +103,6 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)

public static void GetExactMethodInstantiationDependenciesForMethod(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
{
if (!IsMethodEligibleForTracking(factory, method))
return;

dependencies ??= new DependencyList();

// Method entry point dependency
Expand All @@ -129,31 +123,6 @@ public static void GetExactMethodInstantiationDependenciesForMethod(ref Dependen
dependencies.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(nameAndSig), "Exact method instantiation entry"));
}

private static bool IsMethodEligibleForTracking(NodeFactory factory, MethodDesc method)
{
// Runtime determined methods should never show up here.
Debug.Assert(!method.IsRuntimeDeterminedExactMethod);

if (method.IsAbstract)
return false;

if (!method.HasInstantiation)
return false;

// This hashtable is only for method instantiations that don't use generic dictionaries,
// so check if the given method is shared before proceeding
if (method.IsSharedByGenericInstantiations || method.GetCanonMethodTarget(CanonicalFormKind.Specific) != method)
return false;

// The hashtable is used to find implementations of generic virtual methods at runtime
if (method.IsVirtual)
return true;

// The rest of the entries are potentially only useful for the universal
// canonical type loader.
return factory.TypeSystemContext.SupportsUniversalCanon;
}

protected internal override int Phase => (int)ObjectNodePhase.Ordered;
public override int ClassCode => (int)ObjectNodeOrder.ExactMethodInstantiationsNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
dependencies.Add(factory.NativeLayout.TemplateMethodEntry(_method), "GVM Dependency - Template entry");
dependencies.Add(factory.NativeLayout.TemplateMethodLayout(_method), "GVM Dependency - Template");
}
else
{
dependencies.Add(factory.ExactMethodInstantiationsHashtableEntry(_method), "GVM Dependency - runtime lookups");
}
}

return dependencies;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ private void CreateNodeCaches()
return new GenericMethodsHashtableEntryNode(method);
});

_exactMethodEntries = new NodeCache<MethodDesc, ExactMethodInstantiationsEntryNode>(method =>
{
return new ExactMethodInstantiationsEntryNode(method);
});

_gvmTableEntries = new NodeCache<TypeDesc, TypeGVMEntriesNode>(type =>
{
return new TypeGVMEntriesNode(type);
Expand Down Expand Up @@ -1056,6 +1061,12 @@ public GenericMethodsHashtableEntryNode GenericMethodsHashtableEntry(MethodDesc
return _genericMethodEntries.GetOrAdd(method);
}

private NodeCache<MethodDesc, ExactMethodInstantiationsEntryNode> _exactMethodEntries;
public ExactMethodInstantiationsEntryNode ExactMethodInstantiationsHashtableEntry(MethodDesc method)
{
return _exactMethodEntries.GetOrAdd(method);
}

private NodeCache<TypeDesc, TypeGVMEntriesNode> _gvmTableEntries;
internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private readonly SortedSet<TypeGVMEntriesNode> _typeGVMEntries
private readonly SortedSet<TypeDesc> _typeTemplates = new SortedSet<TypeDesc>(TypeSystemComparer.Instance);
private readonly SortedSet<MetadataType> _typesWithGenericStaticBaseInfo = new SortedSet<MetadataType>(TypeSystemComparer.Instance);
private readonly SortedSet<MethodDesc> _genericMethodHashtableEntries = new SortedSet<MethodDesc>(TypeSystemComparer.Instance);
private readonly SortedSet<MethodDesc> _exactMethodHashtableEntries = new SortedSet<MethodDesc>(TypeSystemComparer.Instance);
private readonly HashSet<TypeDesc> _usedInterfaces = new HashSet<TypeDesc>();

private List<(DehydratableObjectNode Node, ObjectNode.ObjectData Data)> _dehydratableData = new List<(DehydratableObjectNode Node, ObjectNode.ObjectData data)>();
Expand Down Expand Up @@ -330,6 +331,11 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore<NodeFactory> obj)
_genericMethodHashtableEntries.Add(genericMethodsHashtableEntryNode.Method);
}

if (obj is ExactMethodInstantiationsEntryNode exactMethodsHashtableEntryNode)
{
_exactMethodHashtableEntries.Add(exactMethodsHashtableEntryNode.Method);
}

if (obj is InterfaceUseNode interfaceUse)
{
_usedInterfaces.Add(interfaceUse.Type);
Expand Down Expand Up @@ -605,11 +611,6 @@ public virtual void GetDependenciesForOverridingMethod(ref CombinedDependencyLis
/// </summary>
public void GetDependenciesDueToMethodCodePresence(ref DependencyList dependencies, NodeFactory factory, MethodDesc method, MethodIL methodIL)
{
if (method.HasInstantiation)
{
ExactMethodInstantiationsNode.GetExactMethodInstantiationDependenciesForMethod(ref dependencies, factory, method);
}

InlineableStringsResourceNode.AddDependenciesDueToResourceStringUse(ref dependencies, factory, method);

GetDependenciesDueToMethodCodePresenceInternal(ref dependencies, factory, method, methodIL);
Expand Down Expand Up @@ -1012,6 +1013,11 @@ public IEnumerable<MethodDesc> GetGenericMethodHashtableEntries()
return _genericMethodHashtableEntries;
}

public IEnumerable<MethodDesc> GetExactMethodHashtableEntries()
{
return _exactMethodHashtableEntries;
}

public bool IsInterfaceUsed(TypeDesc type)
{
Debug.Assert(type.IsTypeDefinition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@
<Compile Include="Compiler\DependencyAnalysis\DataflowAnalyzedTypeDefinitionNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\DelegateMarshallingDataNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\DynamicDependencyAttributesOnEntityNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ExactMethodInstantiationsEntryNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ExternSymbolsImportedNodeProvider.cs" />
<Compile Include="Compiler\DependencyAnalysis\FieldRvaDataNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\FrozenObjectNode.cs" />
Expand Down
33 changes: 33 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ internal static int Run()
TestGvmLookupDependency.Run();
Test99198Regression.Run();
Test102259Regression.Run();
Test104913Regression.Run();
TestInvokeMemberCornerCaseInGenerics.Run();
TestRefAny.Run();
TestNullableCasting.Run();
Expand Down Expand Up @@ -3596,6 +3597,38 @@ public static void Run()
}
}

class Test104913Regression
{
interface IFoo
{
(Type, Type) InvokeInstance<T>() where T : IBar;
}

class Foo : IFoo
{
public (Type, Type) InvokeInstance<T>() where T : IBar
=> (typeof(T), T.InvokeStatic<int>());
}

interface IBar
{
static abstract Type InvokeStatic<T>();
}

class Bar : IBar
{
public static Type InvokeStatic<T>()
=> typeof(T);
}

public static void Run()
{
(Type t1, Type t2) = ((IFoo)new Foo()).InvokeInstance<Bar>();
if (t1 != typeof(Bar) || t2 != typeof(int))
throw new Exception();
}
}

class TestInvokeMemberCornerCaseInGenerics
{
class Generic<T>
Expand Down

0 comments on commit 66ae898

Please sign in to comment.