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

[ILLink] Mark recursive interface implementations in MarkStep #99922

Merged
merged 8 commits into from
Mar 27, 2024
Merged
28 changes: 17 additions & 11 deletions src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.Runtime.TypeParsing;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
Expand Down Expand Up @@ -2450,26 +2450,27 @@ void MarkNamedProperty (TypeDefinition type, string property_name, in Dependency

void MarkInterfaceImplementations (TypeDefinition type)
{
if (!type.HasInterfaces)
var ifaces = Annotations.GetRecursiveInterfaces (type);
if (ifaces is null)
return;

foreach (var iface in type.Interfaces) {
foreach (var (ifaceType, impls) in ifaces) {
// Only mark interface implementations of interface types that have been marked.
// This enables stripping of interfaces that are never used
if (ShouldMarkInterfaceImplementation (type, iface))
MarkInterfaceImplementation (iface, new MessageOrigin (type));
if (ShouldMarkInterfaceImplementationList (type, impls, ifaceType))
MarkInterfaceImplementationList (impls, new MessageOrigin (type));
}
}

protected virtual bool ShouldMarkInterfaceImplementation (TypeDefinition type, InterfaceImplementation iface)

protected virtual bool ShouldMarkInterfaceImplementationList (TypeDefinition type, List<InterfaceImplementation> ifaces, TypeReference ifaceType)
{
if (Annotations.IsMarked (iface))
if (ifaces.All (Annotations.IsMarked))
return false;

if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, type))
return true;

if (Context.Resolve (iface.InterfaceType) is not TypeDefinition resolvedInterfaceType)
if (Context.Resolve (ifaceType) is not TypeDefinition resolvedInterfaceType)
return false;

if (Annotations.IsMarked (resolvedInterfaceType))
Expand Down Expand Up @@ -3764,8 +3765,7 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
ScopeStack.UpdateCurrentScopeInstructionOffset (instruction.Offset);
if (markForReflectionAccess) {
MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), ScopeStack.CurrentScope.Origin);
}
else {
} else {
MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), ScopeStack.CurrentScope.Origin);
}
break;
Expand Down Expand Up @@ -3826,6 +3826,12 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
}


void MarkInterfaceImplementationList (List<InterfaceImplementation> ifaces, MessageOrigin? origin = null, DependencyInfo? reason = null)
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
{
foreach (var iface in ifaces) {
MarkInterfaceImplementation (iface, origin, reason);
}
}
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
protected internal virtual void MarkInterfaceImplementation (InterfaceImplementation iface, MessageOrigin? origin = null, DependencyInfo? reason = null)
{
if (Annotations.IsMarked (iface))
Expand Down
5 changes: 5 additions & 0 deletions src/tools/illink/src/linker/Linker/Annotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using ILLink.Shared.TrimAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
Expand Down Expand Up @@ -717,5 +718,9 @@ public void EnqueueVirtualMethod (MethodDefinition method)
if (FlowAnnotations.RequiresVirtualMethodDataFlowAnalysis (method) || HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (method))
VirtualMethodsWithAnnotationsToValidate.Add (method);
}
internal List<(TypeReference, List<InterfaceImplementation>)>? GetRecursiveInterfaces(TypeDefinition type)
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
{
return TypeMapInfo.GetRecursiveInterfaces (type);
}
}
}
89 changes: 89 additions & 0 deletions src/tools/illink/src/linker/Linker/TypeMapInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ public void AddDefaultInterfaceImplementation (MethodDefinition @base, Interface
default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, interfaceImplementor));
}

Dictionary<TypeDefinition, List<(TypeReference, List<InterfaceImplementation>)>> interfaces = new ();
protected virtual void MapType (TypeDefinition type)
{
MapVirtualMethods (type);
MapInterfaceMethodsInTypeHierarchy (type);
interfaces[type] = GetRecursiveInterfaceImplementations (type);

if (!type.HasNestedTypes)
return;
Expand All @@ -128,6 +130,93 @@ protected virtual void MapType (TypeDefinition type)
MapType (nested);
}

internal List<(TypeReference, List<InterfaceImplementation>)>? GetRecursiveInterfaces(TypeDefinition type)
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
{
if (interfaces.TryGetValue (type, out var value))
return value;
return null;
}

List<(TypeReference, List<InterfaceImplementation>)> GetRecursiveInterfaceImplementations (TypeDefinition type)
{
List<(TypeReference, List<InterfaceImplementation>)> firstImplementationChain = new ();

AddRecursiveInterfaces (type, [], firstImplementationChain, context);
Debug.Assert (firstImplementationChain.All (kvp => context.Resolve (kvp.Item1) == context.Resolve (kvp.Item2.Last ().InterfaceType)));

return firstImplementationChain;

static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable<InterfaceImplementation> pathToType, List<(TypeReference, List<InterfaceImplementation>)> firstImplementationChain, LinkContext Context)
{
var type = Context.TryResolve (typeRef);
if (type is null)
return;
// Get all explicit interfaces of this type
foreach (var directIface in type.Interfaces) {
//var directlyImplementedType = Context.Resolve (directIface.InterfaceType);
var directlyImplementedType = directIface.InterfaceType.TryInflateFrom (typeRef, Context);
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
if (directlyImplementedType is null) {
continue;
}
if (!firstImplementationChain.Any (i => InterfaceTypeEquals(i.Item1, directlyImplementedType, Context))) {
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
firstImplementationChain.Add ((directlyImplementedType, pathToType.Append (directIface).ToList ()));
}
}

// Recursive interfaces next to preserve Inherit/Implement tree order
foreach (var directIface in type.Interfaces) {
// If we can't resolve the interface type we can't find recursive interfaces
var ifaceDirectlyOnType = directIface.InterfaceType.TryInflateFrom(typeRef, Context);
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
if (ifaceDirectlyOnType is null) {
continue;
}
AddRecursiveInterfaces (ifaceDirectlyOnType, pathToType.Append (directIface), firstImplementationChain, Context);
}
}

/// <summary>
/// Compares two TypeReferences to interface types and determines if they are equivalent references, taking into account generic arguments and element types.
/// </summary>
static bool InterfaceTypeEquals (TypeReference? type, TypeReference? other, ITryResolveMetadata resolver)
jtschuster marked this conversation as resolved.
Show resolved Hide resolved
{
Debug.Assert (type is not null && other is not null);
Debug.Assert (resolver.TryResolve (type)?.IsInterface is null or true);
Debug.Assert (resolver.TryResolve (other)?.IsInterface is null or true);
return TypeEquals (type, other);

bool TypeEquals (TypeReference type1, TypeReference type2)
{
if (type1 == type2)
return true;

if (resolver.TryResolve (type1) != resolver.TryResolve (type2))
return false;

if (type1 is GenericInstanceType genericInstance1) {
if (type2 is not GenericInstanceType genericInstance2)
return false;
if (genericInstance1.HasGenericParameters != genericInstance2.HasGenericParameters)
return false;
if (genericInstance1.GenericParameters.Count != genericInstance2.GenericParameters.Count
|| genericInstance2.GenericArguments.Count != genericInstance2.GenericArguments.Count)
return false;
for (var i = 0; i < genericInstance1.GenericArguments.Count; ++i) {
if (!TypeEquals (genericInstance1.GenericArguments[i], genericInstance2.GenericArguments[i]))
return false;
}
return true;
}

if (type1 is TypeSpecification typeSpec1) {
if (type2 is not TypeSpecification typeSpec2)
return false;
return TypeEquals (typeSpec1.ElementType, typeSpec2.ElementType);
sbomer marked this conversation as resolved.
Show resolved Hide resolved
}
return type1.FullName == type2.FullName;
}
}
}

void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
{
if (!type.HasInterfaces)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ void parseArrayDimensions (ArrayType at)
Debug.Assert (false);
return null;
}
public static TypeReference? TryInflateFrom (this TypeReference type, TypeReference maybeGenericInstanceProvider, ITryResolveMetadata resolver)
sbomer marked this conversation as resolved.
Show resolved Hide resolved
{
if (maybeGenericInstanceProvider is GenericInstanceType git)
return InflateGenericType (git, type, resolver);
return type;
}

public static IEnumerable<(TypeReference InflatedInterface, InterfaceImplementation OriginalImpl)> GetInflatedInterfaces (this TypeReference typeRef, ITryResolveMetadata resolver)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ public Task DefaultInterfaceMethodCallIntoClass ()
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task DimProvidedByRecursiveInterface ()
{
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task GenericDefaultInterfaceMethods ()
{
Expand All @@ -39,6 +45,12 @@ public Task MostSpecificDefaultImplementationKeptStatic ()
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task MultipleDimsProvidedByRecursiveInterface ()
{
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task SimpleDefaultInterfaceMethod ()
{
Expand All @@ -51,6 +63,12 @@ public Task StaticDefaultInterfaceMethodOnStruct ()
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task StaticDimProvidedByUnreferencedIfaceInHierarchy ()
{
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task UnusedDefaultInterfaceImplementation ()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Xunit;

namespace ILLink.RoslynAnalyzer.Tests.Inheritance.Interfaces
{
public sealed partial class RecursiveInterfacesTests : LinkerTestBase
{

protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces";

[Fact]
public Task GenericInterfaceImplementedRecursively ()
{
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task InterfaceImplementedRecursively ()
{
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task RecursiveInterfaceKept ()
{
return RunTest (allowMissingWarnings: true);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public Task InterfaceVariants ()
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task InterfaceVariantsGeneric ()
{
return RunTest (allowMissingWarnings: true);
}

[Fact]
public Task InterfaceWithoutNewSlot ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@ static void GuardedLocalFunction ()

public static void Test ()
{
// Use the IEnumerable to mark the IEnumerable methods
GuardInIterator ();
StateFlowsAcrossYield ();
GuardInAsync ();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

.assembly extern mscorlib { }

.assembly 'library' { }

.class public auto ansi abstract sealed beforefieldinit Program
extends [mscorlib]System.Object
{
// Nested Types
.class interface nested public auto ansi abstract beforefieldinit IFoo
{
// Methods
.method public hidebysig newslot abstract virtual
instance void Method () cil managed
{
} // end of method IFoo::Method

} // end of class IFoo

.class interface nested public auto ansi abstract beforefieldinit IBar
implements Program/IFoo
{
// Methods
.method public final hidebysig virtual
instance void Program.IFoo.Method () cil managed
{
.override method instance void Program/IFoo::Method()
// Method begins at RVA 0x2068
// Code size 2 (0x2)
.maxstack 8

IL_0000: nop
IL_0001: ret
} // end of method IBar::Program.IFoo.Method

} // end of class IBar

.class interface nested public auto ansi abstract beforefieldinit IBaz
implements Program/IBar
{
} // end of class IBaz

.class nested public auto ansi beforefieldinit MyFoo
extends [mscorlib]System.Object
implements Program/IBaz
{
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2076
// Code size 8 (0x8)
.maxstack 8

IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method MyFoo::.ctor

} // end of class MyFoo


// Methods
.method public hidebysig static
void CallMethod (
class Program/IFoo foo
) cil managed
{
.custom instance void [mscorlib]mscorlib.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
// Method begins at RVA 0x2050
// Code size 9 (0x9)
.maxstack 8

IL_0000: nop
IL_0001: ldarg.0
IL_0002: callvirt instance void Program/IFoo::Method()
IL_0007: nop
IL_0008: ret
} // end of method Program::CallMethod

} // end of class Program
Loading
Loading