Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split generic virtual method slot use and impl tracking #82222

Merged
merged 3 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Represents a use of a generic virtual method slot. This node only tracks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not quite clear to me what a "slot" means in this context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slot in the sense of ECMA-335 - either a newslot method, or virtual that doesn't have another virtual method with the same name/sig in the inheritance hierarchy.

/// the use of the slot definition.
/// This analysis node is used for computing GVM dependencies for the following cases:
/// 1) Derived types where the GVM is overridden
/// 2) Variant-interfaces GVMs
Expand All @@ -19,17 +21,14 @@ namespace ILCompiler.DependencyAnalysis
/// </summary>
public class GVMDependenciesNode : DependencyNodeCore<NodeFactory>
{
private const int UniversalCanonGVMDepthHeuristic_CanonDepth = 2;
private readonly MethodDesc _method;

public GVMDependenciesNode(MethodDesc method)
{
Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method);
Debug.Assert(method.HasInstantiation);

// This is either a generic virtual method or a MethodImpl for a static interface method.
// We can't test for static MethodImpl so at least sanity check it's static and noninterface.
Debug.Assert(method.IsVirtual || (method.Signature.IsStatic && !method.OwningType.IsInterface));
Debug.Assert(method.IsVirtual);
Debug.Assert(MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method) == method);

_method = method;
}
Expand All @@ -39,41 +38,10 @@ public GVMDependenciesNode(MethodDesc method)
public override bool StaticDependenciesAreComputed => true;
protected override string GetName(NodeFactory factory) => "__GVMDependenciesNode_" + factory.NameMangler.GetMangledMethodName(_method);

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context)
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
{
DependencyList dependencies = null;

context.MetadataManager.GetDependenciesDueToVirtualMethodReflectability(ref dependencies, context, _method);

if (!_method.IsAbstract)
{
bool validInstantiation =
_method.IsSharedByGenericInstantiations || ( // Non-exact methods are always valid instantiations (always pass constraints check)
_method.Instantiation.CheckValidInstantiationArguments() &&
_method.OwningType.Instantiation.CheckValidInstantiationArguments() &&
_method.CheckConstraints());

if (validInstantiation)
{
if (context.TypeSystemContext.SupportsUniversalCanon && _method.IsGenericDepthGreaterThan(UniversalCanonGVMDepthHeuristic_CanonDepth))
{
// fall back to using the universal generic variant of the generic method
return dependencies;
}

bool getUnboxingStub = _method.OwningType.IsValueType && !_method.Signature.IsStatic;
dependencies ??= new DependencyList();
dependencies.Add(context.MethodEntrypoint(_method, getUnboxingStub), "GVM Dependency - Canon method");

if (_method.IsSharedByGenericInstantiations)
{
dependencies.Add(context.NativeLayout.TemplateMethodEntry(_method), "GVM Dependency - Template entry");
dependencies.Add(context.NativeLayout.TemplateMethodLayout(_method), "GVM Dependency - Template");
}
}
}

return dependencies;
yield return new DependencyListEntry(factory.GenericVirtualMethodImpl(_method), "Implementation of the generic virtual method");
}

public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
Expand All @@ -89,14 +57,6 @@ public override bool HasDynamicDependencies
(methodOwningType.IsSealed() || _method.IsFinal))
return false;

// We model static (non-virtual) methods that are MethodImpls for a static interface method.
// But those cannot be overriden (they're not virtual to begin with).
if (!methodOwningType.IsInterface && _method.Signature.IsStatic)
{
Debug.Assert(!_method.IsVirtual);
return false;
}

return true;
}
}
Expand Down Expand Up @@ -173,7 +133,12 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
for (int instArg = 0; instArg < openInstantiation.Length; instArg++)
openInstantiation[instArg] = context.GetSignatureVariable(instArg, method: true);
MethodDesc implementingMethodInstantiation = slotDecl.MakeInstantiatedMethod(openInstantiation).InstantiateSignature(potentialOverrideType.Instantiation, _method.Instantiation);
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation"));

// Static virtuals cannot be further overriden so this is an impl use. Otherwise it's a virtual slot use.
if (implementingMethodInstantiation.Signature.IsStatic)
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GenericVirtualMethodImpl(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation"));
else
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation"));

factory.MetadataManager.NoteOverridingMethod(_method, implementingMethodInstantiation);
}
Expand Down Expand Up @@ -225,7 +190,7 @@ public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependenci
if (instantiatedTargetMethod != _method)
{
dynamicDependencies.Add(new CombinedDependencyListEntry(
factory.GVMDependencies(instantiatedTargetMethod), null, "DerivedMethodInstantiation"));
factory.GenericVirtualMethodImpl(instantiatedTargetMethod), null, "DerivedMethodInstantiation"));

factory.MetadataManager.NoteOverridingMethod(_method, instantiatedTargetMethod);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Collections.Generic;

using ILCompiler.DependencyAnalysisFramework;

using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Represents a use of a generic virtual method body implementation.
/// </summary>
public class GenericVirtualMethodImplNode : DependencyNodeCore<NodeFactory>
{
private const int UniversalCanonGVMDepthHeuristic_CanonDepth = 2;
private readonly MethodDesc _method;

public GenericVirtualMethodImplNode(MethodDesc method)
{
Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method);
Debug.Assert(method.HasInstantiation);

// This is either a generic virtual method or a MethodImpl for a static interface method.
// We can't test for static MethodImpl so at least sanity check it's static and noninterface.
Debug.Assert(method.IsVirtual || (method.Signature.IsStatic && !method.OwningType.IsInterface));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about default interface implementations? In that case, isn't the method owned by an interface?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change with #80602 because we'll run into the assert. We don't currently track interfaces because they're assumed to be associated with a class (default implementations just show up on the owning class). This works for instance methods. It doesn't work for statics. It's a bit of a overhaul which is why I didn't want to do it in one go.


_method = method;
}

public override bool HasConditionalStaticDependencies => false;
public override bool InterestingForDynamicDependencyAnalysis => false;
public override bool StaticDependenciesAreComputed => true;
protected override string GetName(NodeFactory factory) => "__GVMImplNode_" + factory.NameMangler.GetMangledMethodName(_method);

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
{
DependencyList dependencies = null;

factory.MetadataManager.GetDependenciesDueToVirtualMethodReflectability(ref dependencies, factory, _method);

bool validInstantiation =
_method.IsSharedByGenericInstantiations || ( // Non-exact methods are always valid instantiations (always pass constraints check)
_method.Instantiation.CheckValidInstantiationArguments() &&
_method.OwningType.Instantiation.CheckValidInstantiationArguments() &&
_method.CheckConstraints());

if (validInstantiation)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "valid" here, do we mean that the code is not broken? That the compiler hasn't written incorrect IL?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a cargo cult I brought over from the previous spot. I don't know when it kicks in. It goes back all the way to dotnet/corert#2521.

{
if (factory.TypeSystemContext.SupportsUniversalCanon && _method.IsGenericDepthGreaterThan(UniversalCanonGVMDepthHeuristic_CanonDepth))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand the "generic depth" thing here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy paste from the previous spot - if universal shared generics are supported, we cut off analysis early if it gets too deep and leave universal shared code to handle it. SupportsUniversalCanon is always false in NativeAOT.

{
// fall back to using the universal generic variant of the generic method
return dependencies;
}

bool getUnboxingStub = _method.OwningType.IsValueType && !_method.Signature.IsStatic;
dependencies ??= new DependencyList();
dependencies.Add(factory.MethodEntrypoint(_method, getUnboxingStub), "GVM Dependency - Canon method");

if (_method.IsSharedByGenericInstantiations)
{
dependencies.Add(factory.NativeLayout.TemplateMethodEntry(_method), "GVM Dependency - Template entry");
dependencies.Add(factory.NativeLayout.TemplateMethodLayout(_method), "GVM Dependency - Template");
}
}

return dependencies;
}

public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;

public override bool HasDynamicDependencies => false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this is important, but it's not clear to me why.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All nodes have HasDynamicDependencies false, except for GVMDependencies node.

Returning true means the dependency analysis will call into SearchDynamicDependencies after every iteration of the graph expansion with a list of all nodes we've seen so far.

This node not reporting it is the optimization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why is this a valid optimization? Or, why is it necessary for GVMDependencies to always return true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GVMDependencies needs to look at each type in the graph that has something to do with generic virtual methods to check whether the type provides an override of the slot. If so, it needs to instantiate the override with whatever generic method instantiation this GVMDependencies has.

But we don't need to do this for generic virtual method implementations that don't define a new slot (i.e. they're just overrides) - this is what the optimization tries to split - a slot use + an override ("implementation", since we also introduce this node for non abstract new slots) of the slot.


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 @@ -284,6 +284,11 @@ private void CreateNodeCaches()
return new GVMDependenciesNode(method);
});

_gvmImpls = new NodeCache<MethodDesc, GenericVirtualMethodImplNode>(method =>
{
return new GenericVirtualMethodImplNode(method);
});

_gvmTableEntries = new NodeCache<TypeDesc, TypeGVMEntriesNode>(type =>
{
return new TypeGVMEntriesNode(type);
Expand Down Expand Up @@ -931,6 +936,12 @@ public GVMDependenciesNode GVMDependencies(MethodDesc method)
return _gvmDependenciesNode.GetOrAdd(method);
}

private NodeCache<MethodDesc, GenericVirtualMethodImplNode> _gvmImpls;
public GenericVirtualMethodImplNode GenericVirtualMethodImpl(MethodDesc method)
{
return _gvmImpls.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 @@ -64,14 +64,17 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
{
if (_method.IsVirtual)
{
// Virtual method use is tracked on the slot defining method only.
MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(_method);
if (_method.HasInstantiation)
{
dependencies.Add(factory.GVMDependencies(_method.GetCanonMethodTarget(CanonicalFormKind.Specific)), "GVM callable reflectable method");
// FindSlotDefiningMethod might uninstantiate. We might want to fix the method not to do that.
if (slotDefiningMethod.IsMethodDefinition)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely don't understand what "uninstantiate" means here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might remove the instantiation of the generic method. Sometimes. I think we just don't call this method with generic methods. I didn't want to fix this here because we call this method in two dozen places and it would require carefully reviewing each. So we just instantiate the method back if it became uninstantiated.

slotDefiningMethod = factory.TypeSystemContext.GetInstantiatedMethod(slotDefiningMethod, _method.Instantiation);
dependencies.Add(factory.GVMDependencies(slotDefiningMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "GVM callable reflectable method");
}
else
{
// Virtual method use is tracked on the slot defining method only.
MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(_method);
if (!factory.VTable(slotDefiningMethod.OwningType).HasFixedSlots)
dependencies.Add(factory.VirtualMethodUse(slotDefiningMethod), "Virtually callable reflectable method");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@
<Compile Include="Compiler\DependencyAnalysis\ExternSymbolsImportedNodeProvider.cs" />
<Compile Include="Compiler\DependencyAnalysis\FieldRvaDataNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\GenericStaticBaseInfoNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\GenericVirtualMethodImplNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\IndirectionExtensions.cs" />
<Compile Include="Compiler\DependencyAnalysis\InlineableStringsResourceNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\InterfaceDispatchCellSectionNode.cs" />
Expand Down