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

Trim unused interfaces #100000

Merged
merged 3 commits into from
Mar 23, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,17 @@ internal enum AssignmentVariation
interfaceMap++;
interfaceCount--;
} while (interfaceCount > 0);
}

extra:
if (mt->IsIDynamicInterfaceCastable)
{
goto slowPath;
}
extra:
// NOTE: this check is outside the `if (interfaceCount != 0)` check because
// we could have devirtualized and inlined all uses of IDynamicInterfaceCastable
// (and optimized the interface MethodTable away) and still have a type that
// is legitimately marked IDynamicInterfaceCastable (without having the MethodTable
// of IDynamicInterfaceCastable in the interface list).
if (mt->IsIDynamicInterfaceCastable)
{
goto slowPath;
}

obj = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ public static IEnumerable<TypeSystemEntity> GetDynamicallyAccessedMembers(this T
foreach (var e in typeDefinition.GetEventsOnTypeHierarchy(filter: null, bindingFlags: BindingFlags.Public | declaredOnlyFlags))
yield return e;
}

if (memberTypes.HasFlag(DynamicallyAccessedMemberTypes.Interfaces))
{
foreach (DefType iface in typeDefinition.GetAllInterfaceImplementations(declaredOnly))
yield return iface;
}
}

public static IEnumerable<MethodDesc> GetConstructorsOnType(this TypeDesc type, Func<MethodDesc, bool> filter, BindingFlags? bindingFlags = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ public bool GeneratesMetadata(EcmaModule module, ExportedTypeHandle exportedType
return GeneratesMetadata(targetType);
}

public bool GeneratesInterfaceImpl(MetadataType typeDef, MetadataType interfaceImpl)
{
return _parent.IsInterfaceUsed(interfaceImpl.GetTypeDefinition());
}

public bool IsBlocked(MetadataType typeDef)
{
return _blockingPolicy.IsBlocked(typeDef);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ internal void MarkTypeSystemEntity(in MessageOrigin origin, TypeSystemEntity ent
MarkEvent(origin, @event, reason, accessKind);
break;
// case InterfaceImplementation
// Nothing to do currently as Native AOT will preserve all interfaces on a preserved type
// This is handled in the MetadataType case above
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,12 @@ protected override void OutputGCDesc(ref ObjectDataBuilder builder)

protected override void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBuilder objData)
{
for (int i = 0; i < _type.RuntimeInterfaces.Length; i++)
foreach (DefType intface in _type.RuntimeInterfaces)
{
// If the interface was optimized away, skip it
if (!factory.InterfaceUse(intface.GetTypeDefinition()).Marked)
continue;

// Interface omitted for canonical instantiations (constructed at runtime for dynamic types from the native layout info)
objData.EmitZeroPointer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ public sealed override bool HasConditionalStaticDependencies
return true;
}

// If the type implements at least one interface, calls against that interface could result in this type's
// implementation being used.
// It might also be necessary for casting purposes.
if (_type.RuntimeInterfaces.Length > 0)
return true;

if (!EmitVirtualSlots)
return false;

Expand Down Expand Up @@ -328,11 +334,6 @@ public sealed override bool HasConditionalStaticDependencies
currentType = currentType.BaseType;
}

// If the type implements at least one interface, calls against that interface could result in this type's
// implementation being used.
if (_type.RuntimeInterfaces.Length > 0)
return true;

return _hasConditionalDependenciesFromMetadataManager;
}
}
Expand Down Expand Up @@ -367,6 +368,18 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
"Information about static bases for type with template"));
}

if (!_type.IsGenericDefinition && !_type.IsCanonicalSubtype(CanonicalFormKind.Any))
{
foreach (DefType iface in _type.RuntimeInterfaces)
{
var ifaceDefinition = (DefType)iface.GetTypeDefinition();
result.Add(new CombinedDependencyListEntry(
GetInterfaceTypeNode(factory, iface),
factory.InterfaceUse(ifaceDefinition),
"Interface definition was visible"));
}
}

if (!EmitVirtualSlots)
return result;

Expand Down Expand Up @@ -526,6 +539,14 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
{
// Canonical instance default methods need to go through a thunk that adds the right generic context
defaultIntfMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(defaultIntfMethod, defType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType);

// The above thunk will index into interface list to find the right context. Make sure to keep all interfaces prior to this one
for (int i = 0; i < interfaceIndex; i++)
{
result.Add(new CombinedDependencyListEntry(
factory.InterfaceUse(defTypeRuntimeInterfaces[i].GetTypeDefinition()),
factory.VirtualMethodUse(interfaceMethod), "Interface with shared default methods folows this"));
}
}
result.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(defaultIntfMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method"));

Expand Down Expand Up @@ -580,6 +601,9 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
// emitting it.
dependencies.Add(new DependencyListEntry(_optionalFieldsNode, "Optional fields"));

if (_type.IsInterface)
dependencies.Add(factory.InterfaceUse(_type.GetTypeDefinition()), "Interface is used");

if (EmitVirtualSlots)
{
if (!_type.IsArrayTypeWithoutGenericInterfaces())
Expand Down Expand Up @@ -690,7 +714,11 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo

// Emit interface map
SlotCounter interfaceSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
OutputInterfaceMap(factory, ref objData);

if (!relocsOnly)
{
OutputInterfaceMap(factory, ref objData);
}

// Update slot count
int numberOfInterfaceSlots = interfaceSlotCounter.CountSlots(ref /* readonly */ objData);
Expand Down Expand Up @@ -1090,7 +1118,13 @@ protected virtual void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBui
{
foreach (var itf in _type.RuntimeInterfaces)
{
objData.EmitPointerReloc(GetInterfaceTypeNode(factory, itf));
IEETypeNode interfaceTypeNode = GetInterfaceTypeNode(factory, itf);

// Only emit interfaces that were not optimized away.
if (interfaceTypeNode.Marked)
{
objData.EmitPointerReloc(interfaceTypeNode);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,18 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

int entryIndex = 0;

// Resolve all the interfaces, but only emit non-static and non-default implementations
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
var interfaceType = declTypeRuntimeInterfaces[interfaceIndex];
var definitionInterfaceType = declTypeDefinitionRuntimeInterfaces[interfaceIndex];
Debug.Assert(interfaceType.IsInterface);

if (!factory.InterfaceUse(interfaceType.GetTypeDefinition()).Marked)
continue;

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(interfaceType).Slots;

for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
Expand Down Expand Up @@ -210,11 +215,11 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
int genericContext = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstArg()
? StaticVirtualMethodContextSource.ContextFromThisClass
: StaticVirtualMethodContextSource.None;
staticImplementations.Add((interfaceIndex, emittedInterfaceSlot, emittedImplSlot, genericContext));
staticImplementations.Add((entryIndex, emittedInterfaceSlot, emittedImplSlot, genericContext));
}
else
{
builder.EmitShort((short)checked((ushort)interfaceIndex));
builder.EmitShort((short)checked((ushort)entryIndex));
builder.EmitShort((short)checked((ushort)emittedInterfaceSlot));
builder.EmitShort((short)checked((ushort)emittedImplSlot));
entryCount++;
Expand Down Expand Up @@ -271,21 +276,23 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
}
}
staticDefaultImplementations.Add((
interfaceIndex,
entryIndex,
emittedInterfaceSlot,
implSlot.Value,
genericContext));
}
else
{
defaultImplementations.Add((
interfaceIndex,
entryIndex,
emittedInterfaceSlot,
implSlot.Value));
}
}
}
}

entryIndex++;
}

// Now emit the default implementations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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
{
/// <summary>
/// Tracks uses of interface in IL sense: at the level of type definitions.
/// Trim warning suppressions within the framework prevent us from optimizing
/// at a smaller granularity (e.g. canonical forms or concrete instantiations).
/// </summary>
internal sealed class InterfaceUseNode : DependencyNodeCore<NodeFactory>
{
public TypeDesc Type { get; }

public InterfaceUseNode(TypeDesc type)
{
Debug.Assert(type.IsTypeDefinition);
Debug.Assert(type.IsInterface);
Type = type;
}

protected override string GetName(NodeFactory factory) => $"Interface use: {Type}";

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory) => null;
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 context) => null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,15 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
}
}

foreach (GenericParameterDesc genericParam in _method.GetTypicalMethodDefinition().Instantiation)
{
foreach (TypeDesc typeConstraint in genericParam.TypeConstraints)
{
if (typeConstraint.IsInterface)
yield return new DependencyListEntry(context.InterfaceUse(typeConstraint.GetTypeDefinition()), "Used as constraint");
}
}

yield return new DependencyListEntry(context.GenericDictionaryLayout(_method), "Dictionary layout");
}

Expand Down Expand Up @@ -1026,6 +1035,15 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
yield return new DependencyListEntry(context.MethodEntrypoint(_type.GetStaticConstructor().GetCanonMethodTarget(CanonicalFormKind.Specific)), "cctor for template");
}

foreach (GenericParameterDesc genericParam in _type.GetTypeDefinition().Instantiation)
{
foreach (TypeDesc typeConstraint in genericParam.TypeConstraints)
{
if (typeConstraint.IsInterface)
yield return new DependencyListEntry(context.InterfaceUse(typeConstraint.GetTypeDefinition()), "Used as constraint");
}
}

if (!_isUniversalCanon)
{
DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ public NecessaryCanonicalEETypeNode(NodeFactory factory, TypeDesc type) : base(f

protected override void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBuilder objData)
{
for (int i = 0; i < _type.RuntimeInterfaces.Length; i++)
foreach (DefType intface in _type.RuntimeInterfaces)
{
// If the interface was optimized away, skip it
if (!factory.InterfaceUse(intface.GetTypeDefinition()).Marked)
continue;

// Interface omitted for canonical instantiations (constructed at runtime for dynamic types from the native layout info)
objData.EmitZeroPointer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ private void CreateNodeCaches()
return new VariantInterfaceMethodUseNode(method);
});

_interfaceUses = new NodeCache<TypeDesc, InterfaceUseNode>((TypeDesc type) =>
{
return new InterfaceUseNode(type);
});

_readyToRunHelpers = new NodeCache<ReadyToRunHelperKey, ISymbolNode>(CreateReadyToRunHelperNode);

_genericReadyToRunHelpersFromDict = new NodeCache<ReadyToRunGenericHelperKey, ISymbolNode>(CreateGenericLookupFromDictionaryNode);
Expand Down Expand Up @@ -1175,6 +1180,13 @@ public DependencyNodeCore<NodeFactory> VariantInterfaceMethodUse(MethodDesc decl
return _variantMethods.GetOrAdd(decl);
}

private NodeCache<TypeDesc, InterfaceUseNode> _interfaceUses;

public DependencyNodeCore<NodeFactory> InterfaceUse(TypeDesc type)
{
return _interfaceUses.GetOrAdd(type);
}

private NodeCache<ReadyToRunHelperKey, ISymbolNode> _readyToRunHelpers;

public ISymbolNode ReadyToRunHelper(ReadyToRunHelperId id, object target)
Expand Down