Skip to content

Commit 66ec273

Browse files
Optimize away GVM resolution metadata
Ever since GVM support was added to native AOT, we were generating the GVM resolution metadata for every type considered allocated. This included GVMs that were never even called (see `TypeGVMEntriesNode` that simply goes over everything on the type). This PR introduces tracking with method level granularity. I ran into this in a different PR where this was dragging `double`/`float` into compilation just because `int` implements generic math.
1 parent de3d75f commit 66ec273

File tree

11 files changed

+194
-160
lines changed

11 files changed

+194
-160
lines changed

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
5252

5353
dependencyList.Add(factory.VTable(closestDefType), "VTable");
5454

55-
if (_type.IsCanonicalSubtype(CanonicalFormKind.Any))
56-
{
57-
// Track generic virtual methods that will get added to the GVM tables
58-
if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0)
59-
{
60-
dependencyList.Add(new DependencyListEntry(factory.TypeGVMEntries(_type.GetTypeDefinition()), "Type with generic virtual methods"));
61-
}
62-
}
63-
else
55+
if (!_type.IsCanonicalSubtype(CanonicalFormKind.Any))
6456
{
6557
factory.InteropStubManager.AddInterestingInteropConstructedTypeDependencies(ref dependencyList, factory, _type);
6658
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,8 +628,6 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
628628
// Generated type contains generic virtual methods that will get added to the GVM tables
629629
if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0)
630630
{
631-
dependencies.Add(new DependencyListEntry(factory.TypeGVMEntries(_type.GetTypeDefinition()), "Type with generic virtual methods"));
632-
633631
TypeDesc canonicalType = _type.ConvertToCanonForm(CanonicalFormKind.Specific);
634632
if (canonicalType != _type)
635633
dependencies.Add(factory.ConstructedTypeSymbol(canonicalType), "Type with generic virtual methods");

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ public GVMDependenciesNode(MethodDesc method)
4141
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
4242
{
4343
if (!_method.IsAbstract)
44+
{
4445
yield return new DependencyListEntry(factory.GenericVirtualMethodImpl(_method), "Implementation of the generic virtual method");
46+
47+
if (!_method.OwningType.IsInterface)
48+
yield return new DependencyListEntry(factory.GVMMetadata(_method.GetTypicalMethodDefinition(), _method.GetTypicalMethodDefinition()), "Implementation of the generic virtual method");
49+
}
4550
}
4651

4752
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
@@ -124,11 +129,12 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
124129
MethodDesc slotDecl = interfaceMethod.Signature.IsStatic ?
125130
potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod)
126131
: potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodTarget(interfaceMethod);
132+
DefaultInterfaceMethodResolution defaultResolution = DefaultInterfaceMethodResolution.None;
127133
if (slotDecl == null)
128134
{
129135
// The method might be implemented through a default interface method
130-
var result = potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, out slotDecl);
131-
if (result != DefaultInterfaceMethodResolution.DefaultImplementation)
136+
defaultResolution = potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, out slotDecl);
137+
if (defaultResolution != DefaultInterfaceMethodResolution.DefaultImplementation)
132138
{
133139
slotDecl = null;
134140
}
@@ -147,6 +153,8 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
147153
else
148154
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation"));
149155

156+
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.InterfaceGVMMetadata(interfaceMethod, slotDecl.GetTypicalMethodDefinition(), potentialOverrideDefinition, defaultResolution), null, "Metadata"));
157+
150158
TypeSystemEntity origin = (implementingMethodInstantiation.OwningType != potentialOverrideType) ? potentialOverrideType : null;
151159
factory.MetadataManager.NoteOverridingMethod(_method, implementingMethodInstantiation, origin);
152160
}
@@ -200,6 +208,9 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
200208
dynamicDependencies.Add(new CombinedDependencyListEntry(
201209
factory.GenericVirtualMethodImpl(instantiatedTargetMethod), null, "DerivedMethodInstantiation"));
202210

211+
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMMetadata(
212+
methodToResolve.GetTypicalMethodDefinition(), instantiatedTargetMethod.GetTypicalMethodDefinition()), null, "Metadata"));
213+
203214
factory.MetadataManager.NoteOverridingMethod(_method, instantiatedTargetMethod);
204215
}
205216
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Collections.Generic;
6+
7+
using ILCompiler.DependencyAnalysisFramework;
8+
using Internal.TypeSystem;
9+
10+
namespace ILCompiler.DependencyAnalysis
11+
{
12+
internal sealed class GVMMetadataNode : SortableDependencyNode
13+
{
14+
public MethodDesc CallingMethod { get; }
15+
public MethodDesc ImplementationMethod { get; }
16+
public GVMMetadataNode(MethodDesc callingMethod, MethodDesc implementationMethod)
17+
=> (CallingMethod, ImplementationMethod) = (callingMethod, implementationMethod);
18+
19+
protected override string GetName(NodeFactory context) =>
20+
$"GVM method: {CallingMethod}: {ImplementationMethod}";
21+
22+
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
23+
{
24+
var list = new DependencyList();
25+
GenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref list, factory, CallingMethod, ImplementationMethod);
26+
return list;
27+
}
28+
29+
public override int ClassCode => 0x2898423;
30+
31+
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
32+
{
33+
var otherNode = (GVMMetadataNode)other;
34+
35+
int result = comparer.Compare(CallingMethod, otherNode.CallingMethod);
36+
if (result != 0)
37+
return result;
38+
39+
return comparer.Compare(ImplementationMethod, otherNode.ImplementationMethod);
40+
}
41+
42+
public override bool InterestingForDynamicDependencyAnalysis => false;
43+
public override bool HasDynamicDependencies => false;
44+
public override bool HasConditionalStaticDependencies => false;
45+
public override bool StaticDependenciesAreComputed => true;
46+
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
47+
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
48+
}
49+
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,9 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
8080
if (relocsOnly)
8181
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
8282

83-
// Build the GVM table entries from the list of interesting GVMTableEntryNodes
84-
foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries())
83+
foreach (var typeGVMEntryInfo in factory.MetadataManager.GetGVMMetadatas())
8584
{
86-
foreach (var typeGVMEntryInfo in interestingEntry.ScanForGenericVirtualMethodEntries())
87-
{
88-
AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod);
89-
}
85+
AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod);
9086
}
9187

9288
// Ensure the native layout blob has been saved
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Collections.Generic;
7+
8+
using ILCompiler.DependencyAnalysisFramework;
9+
using Internal.TypeSystem;
10+
11+
namespace ILCompiler.DependencyAnalysis
12+
{
13+
internal sealed class InterfaceGVMMetadataNode : SortableDependencyNode
14+
{
15+
public MethodDesc CallingMethod { get; }
16+
public MethodDesc ImplementationMethod { get; }
17+
public TypeDesc ImplementationType { get; }
18+
public DefaultInterfaceMethodResolution DefaultResolution { get; }
19+
20+
public InterfaceGVMMetadataNode(MethodDesc callingMethod, MethodDesc implementationMethod,
21+
TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution)
22+
=> (CallingMethod, ImplementationMethod, ImplementationType, DefaultResolution)
23+
= (callingMethod, implementationMethod, implementationType, defaultResolution);
24+
25+
protected override string GetName(NodeFactory context) =>
26+
$"GVM interface method: {CallingMethod} on {ImplementationType}: {ImplementationMethod}, {DefaultResolution}";
27+
28+
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
29+
{
30+
var list = new DependencyList();
31+
InterfaceGenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref list, factory, CallingMethod, ImplementationType, ImplementationMethod);
32+
return list;
33+
}
34+
35+
public override int ClassCode => 0x48bcaa1;
36+
37+
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
38+
{
39+
var otherNode = (InterfaceGVMMetadataNode)other;
40+
41+
int result = comparer.Compare(ImplementationType, otherNode.ImplementationType);
42+
if (result != 0)
43+
return result;
44+
45+
DefType[] interfaceList = ImplementationType.RuntimeInterfaces;
46+
int thisIndex = Array.IndexOf(interfaceList, CallingMethod.OwningType);
47+
int thatIndex = Array.IndexOf(interfaceList, otherNode.CallingMethod.OwningType);
48+
49+
Debug.Assert(thisIndex >= 0 && thatIndex >= 0);
50+
51+
result = Comparer<int>.Default.Compare(thisIndex, thatIndex);
52+
if (result != 0)
53+
return result;
54+
55+
return comparer.Compare(ImplementationMethod, otherNode.ImplementationMethod);
56+
}
57+
58+
public override bool InterestingForDynamicDependencyAnalysis => false;
59+
public override bool HasDynamicDependencies => false;
60+
public override bool HasConditionalStaticDependencies => false;
61+
public override bool StaticDependenciesAreComputed => true;
62+
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
63+
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
64+
}
65+
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,9 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
125125
if (relocsOnly)
126126
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
127127

128-
// Build the GVM table entries from the list of interesting GVMTableEntryNodes
129-
foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries())
128+
foreach (var typeGVMEntryInfo in factory.MetadataManager.GetInterfaceGVMMetadatas())
130129
{
131-
foreach (var typeGVMEntryInfo in interestingEntry.ScanForInterfaceGenericVirtualMethodEntries())
132-
{
133-
AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution);
134-
}
130+
AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution);
135131
}
136132

137133
// Ensure the native layout blob has been saved

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,14 @@ private void CreateNodeCaches()
339339
return new ExactMethodInstantiationsEntryNode(method);
340340
});
341341

342-
_gvmTableEntries = new NodeCache<TypeDesc, TypeGVMEntriesNode>(type =>
342+
_gvmMetadatas = new NodeCache<GVMMetadataKey, GVMMetadataNode>(key =>
343343
{
344-
return new TypeGVMEntriesNode(type);
344+
return new GVMMetadataNode(key.CallingMethod, key.ImplementationMethod);
345+
});
346+
347+
_interfaceGvmMetadatas = new NodeCache<InterfaceGVMMetadataKey, InterfaceGVMMetadataNode>(key =>
348+
{
349+
return new InterfaceGVMMetadataNode(key.CallingMethod, key.ImplementationMethod, key.ImplementationType, key.DefaultResolution);
345350
});
346351

347352
_addressTakenMethods = new NodeCache<MethodDesc, AddressTakenMethodNode>(method =>
@@ -1165,10 +1170,16 @@ public ExactMethodInstantiationsEntryNode ExactMethodInstantiationsHashtableEntr
11651170
return _exactMethodEntries.GetOrAdd(method);
11661171
}
11671172

1168-
private NodeCache<TypeDesc, TypeGVMEntriesNode> _gvmTableEntries;
1169-
internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type)
1173+
private NodeCache<GVMMetadataKey, GVMMetadataNode> _gvmMetadatas;
1174+
internal GVMMetadataNode GVMMetadata(MethodDesc callingMethod, MethodDesc implementationMethod)
1175+
{
1176+
return _gvmMetadatas.GetOrAdd(new GVMMetadataKey(callingMethod, implementationMethod));
1177+
}
1178+
1179+
private NodeCache<InterfaceGVMMetadataKey, InterfaceGVMMetadataNode> _interfaceGvmMetadatas;
1180+
internal InterfaceGVMMetadataNode InterfaceGVMMetadata(MethodDesc callingMethod, MethodDesc implementationMethod, TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution)
11701181
{
1171-
return _gvmTableEntries.GetOrAdd(type);
1182+
return _interfaceGvmMetadatas.GetOrAdd(new InterfaceGVMMetadataKey(callingMethod, implementationMethod, implementationType, defaultResolution));
11721183
}
11731184

11741185
private NodeCache<MethodDesc, AddressTakenMethodNode> _addressTakenMethods;
@@ -1739,5 +1750,33 @@ private struct MethodILKey : IEquatable<MethodILKey>
17391750
public override int GetHashCode() => MethodIL.OwningMethod.GetHashCode();
17401751

17411752
}
1753+
1754+
private struct GVMMetadataKey : IEquatable<GVMMetadataKey>
1755+
{
1756+
public readonly MethodDesc CallingMethod;
1757+
public readonly MethodDesc ImplementationMethod;
1758+
1759+
public GVMMetadataKey(MethodDesc callingMethod, MethodDesc implementationMethod)
1760+
=> (CallingMethod, ImplementationMethod) = (callingMethod, implementationMethod);
1761+
1762+
public override bool Equals(object obj) => obj is GVMMetadataKey other && Equals(other);
1763+
public bool Equals(GVMMetadataKey other) => CallingMethod == other.CallingMethod && ImplementationMethod == other.ImplementationMethod;
1764+
public override int GetHashCode() => HashCode.Combine(CallingMethod, ImplementationMethod);
1765+
}
1766+
1767+
private struct InterfaceGVMMetadataKey : IEquatable<InterfaceGVMMetadataKey>
1768+
{
1769+
public readonly MethodDesc CallingMethod;
1770+
public readonly MethodDesc ImplementationMethod;
1771+
public readonly TypeDesc ImplementationType;
1772+
public readonly DefaultInterfaceMethodResolution DefaultResolution;
1773+
1774+
public InterfaceGVMMetadataKey(MethodDesc callingMethod, MethodDesc implementationMethod, TypeDesc implementationType, DefaultInterfaceMethodResolution resolution)
1775+
=> (CallingMethod, ImplementationMethod, ImplementationType, DefaultResolution) = (callingMethod, implementationMethod, implementationType, resolution);
1776+
1777+
public override bool Equals(object obj) => obj is InterfaceGVMMetadataKey other && Equals(other);
1778+
public bool Equals(InterfaceGVMMetadataKey other) => CallingMethod == other.CallingMethod && ImplementationType == other.ImplementationType;
1779+
public override int GetHashCode() => HashCode.Combine(CallingMethod, ImplementationType);
1780+
}
17421781
}
17431782
}

0 commit comments

Comments
 (0)