Skip to content

Commit af9da6d

Browse files
authored
[R2R] Add discovery of generic virtual methods (#128369)
Called methods are obtained from MethodFixupSignature and are stored into GVMDependenciesNode. These will be nodes that HasDynamicDependencies so the dependency analysis will do additional iterations for these nodes on the newly added nodes that are InterestingForDynamicDependencyAnalysis. These will be InheritedVirtualMethodsNode added as part of TypeFixupSignature, which represent types used by the application. We resolve the target method for calling the virtual/interface method on the existing types. GVMDependenciesNode is moved from NativeAOT sources so that it is shared with ReadyToRun, with just a few minor tweaks guarded by ReadyToRun define (we don't report back via NoteOverridingMethod and we don't need to add TypeGVMEntries).
1 parent b521e15 commit af9da6d

7 files changed

Lines changed: 195 additions & 24 deletions

File tree

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs renamed to src/coreclr/tools/Common/Compiler/DependencyAnalysis/GVMDependenciesNode.cs

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
using ILCompiler.DependencyAnalysisFramework;
88
using Internal.TypeSystem;
99

10+
#if READYTORUN
11+
using ILCompiler.DependencyAnalysis.ReadyToRun;
12+
#endif
13+
1014
namespace ILCompiler.DependencyAnalysis
1115
{
1216
/// <summary>
@@ -42,13 +46,16 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
4246
{
4347
if (!_method.IsAbstract)
4448
{
45-
yield return new DependencyListEntry(factory.GenericVirtualMethodImpl(_method), "Implementation of the generic virtual method");
49+
DependencyNodeCore<NodeFactory> node = GetVirtualMethodImplNode(factory, _method);
50+
if (node != null)
51+
yield return new DependencyListEntry(node, "Implementation of the generic virtual method");
4652
}
47-
53+
#if !READYTORUN
4854
if (!_method.OwningType.IsInterface)
4955
{
5056
yield return new DependencyListEntry(factory.TypeGVMEntries(_method.OwningType.GetTypeDefinition()), "Resolution metadata");
5157
}
58+
#endif
5259
}
5360

5461
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
@@ -80,19 +87,21 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
8087
for (int i = firstNode; i < markedNodes.Count; i++)
8188
{
8289
DependencyNodeCore<NodeFactory> entry = markedNodes[i];
83-
EETypeNode entryAsEETypeNode;
8490

91+
#if !READYTORUN
8592
// This method is often called with a long list of ScannedMethodNode
8693
// or MethodCodeNode nodes. We are not interested in those. In order
8794
// to make the type check as cheap as possible we check for specific
8895
// *sealed* types instead of doing `entry is EETypeNode` which has
8996
// to walk the whole class hierarchy for the non matching nodes.
90-
if (entry is ConstructedEETypeNode constructedEETypeNode)
91-
entryAsEETypeNode = constructedEETypeNode;
92-
else
97+
if (entry is not ConstructedEETypeNode typeNode)
9398
continue;
99+
#else
100+
if (entry is not InheritedVirtualMethodsNode typeNode)
101+
continue;
102+
#endif
94103

95-
TypeDesc potentialOverrideType = entryAsEETypeNode.Type;
104+
TypeDesc potentialOverrideType = typeNode.Type;
96105
if (!potentialOverrideType.IsDefType || potentialOverrideType.IsInterface)
97106
continue;
98107

@@ -102,11 +111,12 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
102111
potentialOverrideType.ConvertToCanonForm(CanonicalFormKind.Specific) != potentialOverrideType)
103112
continue;
104113

114+
#if !READYTORUN
105115
bool foundImpl = false;
106-
116+
#endif
107117
// If this is an interface gvm, look for types that implement the interface
108-
// and other instantantiations that have the same canonical form.
109-
// This ensure the various slot numbers remain equivalent across all types where there is an equivalence
118+
// and other instantiations that have the same canonical form.
119+
// This ensures the various slot numbers remain equivalent across all types where there is an equivalence
110120
// relationship in the vtable.
111121
if (methodOwningType.IsInterface)
112122
{
@@ -145,22 +155,33 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
145155

146156
if (slotDecl != null)
147157
{
148-
TypeDesc[] openInstantiation = new TypeDesc[_method.Instantiation.Length];
149-
for (int instArg = 0; instArg < openInstantiation.Length; instArg++)
150-
openInstantiation[instArg] = context.GetSignatureVariable(instArg, method: true);
151-
MethodDesc implementingMethodInstantiation = slotDecl.MakeInstantiatedMethod(openInstantiation).InstantiateSignature(potentialOverrideType.Instantiation, _method.Instantiation);
158+
MethodDesc implementingMethodInstantiation = slotDecl
159+
.MakeInstantiatedMethod(_method.Instantiation)
160+
.InstantiateSignature(potentialOverrideType.Instantiation, _method.Instantiation);
161+
162+
MethodDesc canonImpl = implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific);
152163

153164
// Static virtuals cannot be further overridden so this is an impl use. Otherwise it's a virtual slot use.
154165
if (implementingMethodInstantiation.Signature.IsStatic)
155-
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GenericVirtualMethodImpl(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation"));
166+
{
167+
DependencyNodeCore<NodeFactory> node = GetVirtualMethodImplNode(factory, canonImpl);
168+
if (node != null)
169+
dynamicDependencies.Add(new CombinedDependencyListEntry(node, null, "ImplementingMethodInstantiation"));
170+
}
156171
else
157-
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation"));
172+
{
173+
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(canonImpl), null, "ImplementingMethodInstantiation"));
174+
}
158175

176+
#if !READYTORUN
159177
TypeSystemEntity origin = (implementingMethodInstantiation.OwningType != potentialOverrideType) ? potentialOverrideType : null;
160178
factory.MetadataManager.NoteOverridingMethod(_method, implementingMethodInstantiation, origin);
179+
#endif
161180
}
162181

182+
#if !READYTORUN
163183
foundImpl = true;
184+
#endif
164185
}
165186
}
166187
}
@@ -169,8 +190,8 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
169190
// This is not an interface GVM. Check whether the current type overrides the virtual method.
170191
// We might need to change what virtual method we ask about - consider:
171192
//
172-
// class Base<T> { virtual Method(); }
173-
// class Derived : Base<string> { override Method(); }
193+
// class Base<T> { virtual Method<U>(); }
194+
// class Derived : Base<string> { override Method<U>(); }
174195
//
175196
// We need to resolve Base<__Canon>.Method on Derived, but if we were to ask the virtual
176197
// method resolution algorithm, the answer would be "does not override" because Base<__Canon>
@@ -208,15 +229,18 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
208229
.GetCanonMethodTarget(CanonicalFormKind.Specific);
209230
if (instantiatedTargetMethod != _method)
210231
{
211-
dynamicDependencies.Add(new CombinedDependencyListEntry(
212-
factory.GenericVirtualMethodImpl(instantiatedTargetMethod), null, "DerivedMethodInstantiation"));
213-
232+
DependencyNodeCore<NodeFactory> node = GetVirtualMethodImplNode(factory, instantiatedTargetMethod);
233+
if (node != null)
234+
dynamicDependencies.Add(new CombinedDependencyListEntry(node, null, "DerivedMethodInstantiation"));
235+
#if !READYTORUN
214236
factory.MetadataManager.NoteOverridingMethod(_method, instantiatedTargetMethod);
215237

216238
foundImpl = true;
239+
#endif
217240
}
218241
}
219242

243+
#if !READYTORUN
220244
if (foundImpl)
221245
{
222246
TypeDesc currentType = potentialOverrideType;
@@ -227,9 +251,30 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
227251
}
228252
while (currentType != null);
229253
}
254+
#endif
230255
}
231256

232257
return dynamicDependencies;
233258
}
259+
260+
private static DependencyNodeCore<NodeFactory> GetVirtualMethodImplNode(NodeFactory factory, MethodDesc method)
261+
{
262+
#if !READYTORUN
263+
return factory.GenericVirtualMethodImpl(method);
264+
#else
265+
if (!factory.CompilationModuleGroup.ContainsMethodBody(method, false))
266+
return null;
267+
268+
try
269+
{
270+
factory.DetectGenericCycles(method, method);
271+
return factory.CompiledMethodNode(method);
272+
}
273+
catch (TypeSystemException)
274+
{
275+
return null;
276+
}
277+
#endif
278+
}
234279
}
235280
}

src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@
302302
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\AssemblyStubNode.cs" Link="Compiler\DependencyAnalysis\AssemblyStubNode.cs" />
303303
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\CompilerComparer.cs" Link="Compiler\DependencyAnalysis\CompilerComparer.cs" />
304304
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\EmbeddedDataContainerNode.cs" Link="Compiler\DependencyAnalysis\EmbeddedDataContainerNode.cs" />
305+
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\GVMDependenciesNode.cs" Link="Compiler\DependencyAnalysis\GVMDependenciesNode.cs" />
305306
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\IMethodBodyNode.cs" Link="Compiler\DependencyAnalysis\IMethodBodyNode.cs" />
306307
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\IMethodNode.cs" Link="Compiler\DependencyAnalysis\IMethodNode.cs" />
307308
<Compile Include="..\..\Common\Compiler\DependencyAnalysis\INodeWithCodeInfo.cs" Link="Compiler\DependencyAnalysis\INodeWithCodeInfo.cs" />
@@ -548,7 +549,6 @@
548549
<Compile Include="Compiler\DependencyAnalysis\GenericVirtualMethodTableNode.cs" />
549550
<Compile Include="Compiler\DependencyAnalysis\InterfaceGenericVirtualMethodTableNode.cs" />
550551
<Compile Include="Compiler\DependencyAnalysis\TypeGVMEntriesNode.cs" />
551-
<Compile Include="Compiler\DependencyAnalysis\GVMDependenciesNode.cs" />
552552
<Compile Include="Compiler\DependencyAnalysis\ReflectionFieldMapNode.cs" />
553553
<Compile Include="Compiler\DependencyAnalysis\NativeLayoutInfoNode.cs" />
554554
<Compile Include="Compiler\DependencyAnalysis\NativeLayoutVertexNode.cs" />
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.Collections.Generic;
5+
using System.Diagnostics;
6+
using ILCompiler.DependencyAnalysisFramework;
7+
using Internal.TypeSystem;
8+
9+
namespace ILCompiler.DependencyAnalysis.ReadyToRun
10+
{
11+
/// <summary>
12+
/// Type discovery marker node for virtual dispatch dependency analysis.
13+
///
14+
/// Marked InterestingForDynamicDependencyAnalysis so that GVMDependenciesNode
15+
/// can iterate on these type nodes to discover new virtual method targets to include in compilation.
16+
/// </summary>
17+
public class InheritedVirtualMethodsNode : DependencyNodeCore<NodeFactory>
18+
{
19+
private readonly TypeDesc _type;
20+
21+
public InheritedVirtualMethodsNode(TypeDesc type)
22+
{
23+
Debug.Assert(type.IsDefType && !type.IsInterface);
24+
_type = type;
25+
}
26+
27+
public TypeDesc Type => _type;
28+
29+
public override bool InterestingForDynamicDependencyAnalysis => true;
30+
public override bool HasDynamicDependencies => false;
31+
public override bool HasConditionalStaticDependencies => false;
32+
public override bool StaticDependenciesAreComputed => true;
33+
34+
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
35+
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
36+
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => null;
37+
38+
protected override string GetName(NodeFactory factory) => $"Inherited virtual methods on {_type}";
39+
}
40+
}

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ public MethodFixupSignature(
5454
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
5555
{
5656
DependencyList list = base.ComputeNonRelocationBasedDependencies(factory);
57+
MethodDesc canonMethod = Method.GetCanonMethodTarget(CanonicalFormKind.Specific);
5758
if (_fixupKind == ReadyToRunFixupKind.VirtualEntry &&
5859
!Method.IsAbstract &&
5960
!Method.HasInstantiation &&
60-
Method.GetCanonMethodTarget(CanonicalFormKind.Specific) is var canonMethod &&
6161
!factory.CompilationModuleGroup.VersionsWithMethodBody(canonMethod) &&
6262
factory.CompilationModuleGroup.CrossModuleCompileable(canonMethod) &&
6363
factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false))
6464
{
65-
list = list ?? new DependencyAnalysisFramework.DependencyNodeCore<NodeFactory>.DependencyList();
65+
list = list ?? new DependencyList();
6666
try
6767
{
6868
factory.DetectGenericCycles(_method.Method, canonMethod);
@@ -73,9 +73,39 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
7373
}
7474
}
7575

76+
// For generic virtual method calls, create a virtual dependency node that will
77+
// dynamically discover implementations on types as they are added to the graph.
78+
if (_fixupKind == ReadyToRunFixupKind.VirtualEntry &&
79+
Method.IsVirtual &&
80+
Method.HasInstantiation &&
81+
!Method.IsFinal &&
82+
!Method.IsGenericMethodDefinition &&
83+
!Method.OwningType.IsGenericDefinition &&
84+
(Method.OwningType.IsInterface || !Method.OwningType.IsSealed()))
85+
{
86+
// Because methods with generic parameters are already compiled in their canonical form, we are only interested in finding
87+
// instantiations of virtual methods that have at least one non-canonical argument (aka a valuetype).
88+
if (HasNonCanonicalInstantiationArguments(canonMethod))
89+
{
90+
list = list ?? new DependencyList();
91+
list.Add(factory.GVMDependencies(Method), "Virtual dispatch dependency");
92+
}
93+
}
94+
7695
return list;
7796
}
7897

98+
private static bool HasNonCanonicalInstantiationArguments(MethodDesc canonMethod)
99+
{
100+
TypeDesc canonType = canonMethod.Context.CanonType;
101+
foreach (TypeDesc arg in canonMethod.Instantiation)
102+
{
103+
if (arg != canonType)
104+
return true;
105+
}
106+
return false;
107+
}
108+
79109
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
80110
{
81111
if (relocsOnly)

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,40 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
213213
dependencies.Add(factory.AllMethodsOnType(_typeDesc), "Methods on generic type instantiation");
214214
}
215215

216+
// We record the usage of this type, so that GVM dependency analysis can resolve all implementations.
217+
if (!_typeDesc.IsGenericDefinition &&
218+
!_typeDesc.IsInterface &&
219+
_typeDesc.IsDefType &&
220+
(factory.CompilationCurrentPhase == 0) &&
221+
factory.CompilationModuleGroup.VersionsWithType(_typeDesc) &&
222+
TypeHasGVMSlots(_typeDesc))
223+
{
224+
dependencies.Add(factory.InheritedVirtualMethods(_typeDesc), "Inherited virtual/interface methods on type");
225+
}
226+
216227
if (_fixupKind == ReadyToRunFixupKind.TypeHandle)
217228
{
218229
AddDependenciesForAsyncStateMachineBox(ref dependencies, factory, _typeDesc);
219230
}
220231
return dependencies;
221232
}
222233

234+
private static bool TypeHasGVMSlots(TypeDesc type)
235+
{
236+
TypeDesc currentType = type;
237+
while (currentType != null)
238+
{
239+
foreach (MethodDesc method in currentType.GetVirtualMethods())
240+
{
241+
if (method.HasInstantiation)
242+
return true;
243+
}
244+
currentType = currentType.BaseType;
245+
}
246+
247+
return false;
248+
}
249+
223250
public static void AddDependenciesForAsyncStateMachineBox(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
224251
{
225252
ReadyToRunCompilerContext context = (ReadyToRunCompilerContext)type.Context;

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,23 @@ public AllMethodsOnTypeNode AllMethodsOnType(TypeDesc type)
141141
return _allMethodsOnType.GetOrAdd(type.ConvertToCanonForm(CanonicalFormKind.Specific));
142142
}
143143

144+
private NodeCache<TypeDesc, InheritedVirtualMethodsNode> _inheritedVirtualMethods;
145+
146+
public InheritedVirtualMethodsNode InheritedVirtualMethods(TypeDesc type)
147+
{
148+
return _inheritedVirtualMethods.GetOrAdd(type.ConvertToCanonForm(CanonicalFormKind.Specific));
149+
}
150+
151+
private NodeCache<MethodDesc, GVMDependenciesNode> _gvmDependenciesNode;
152+
153+
public GVMDependenciesNode GVMDependencies(MethodDesc method)
154+
{
155+
Debug.Assert(method.IsVirtual);
156+
MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
157+
canonMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(canonMethod);
158+
return _gvmDependenciesNode.GetOrAdd(canonMethod);
159+
}
160+
144161
private NodeCache<ReadyToRunGenericHelperKey, ISymbolNode> _genericReadyToRunHelpersFromDict;
145162

146163
public ISymbolNode ReadyToRunHelperFromDictionaryLookup(ReadyToRunHelperId id, Object target, TypeSystemEntity dictionaryOwner)
@@ -263,6 +280,16 @@ private void CreateNodeCaches()
263280
return new AllMethodsOnTypeNode(type);
264281
});
265282

283+
_inheritedVirtualMethods = new NodeCache<TypeDesc, InheritedVirtualMethodsNode>(type =>
284+
{
285+
return new InheritedVirtualMethodsNode(type);
286+
});
287+
288+
_gvmDependenciesNode = new NodeCache<MethodDesc, GVMDependenciesNode>(method =>
289+
{
290+
return new GVMDependenciesNode(method);
291+
});
292+
266293
_genericReadyToRunHelpersFromDict = new NodeCache<ReadyToRunGenericHelperKey, ISymbolNode>(helperKey =>
267294
{
268295
return new DelayLoadHelperImport(

0 commit comments

Comments
 (0)