Skip to content

Commit 6ac1edf

Browse files
authored
Mark most specific static DIM for types marked RelevantToVariantCasting (#97487)
Previously, we weren't handling static DIMs to ensure that DIMs that provided an implementation of an interface method for an inheriting type would be kept. This method gets rid of _interfaceOverrides and uses _virtual_methods with TypeMapInfo to find all interface method / implementation pairs. This PR adds static method handling to the ProcessDefaultImplementation method where it previously only handled instance interface methods. It assumes all static interface methods will be needed if the type implementing the interface IsRelevantToVariantCasting. The DIM cache also is updated to include the method that provides the implementation for a type.
1 parent 96da5a0 commit 6ac1edf

File tree

7 files changed

+337
-51
lines changed

7 files changed

+337
-51
lines changed

src/tools/illink/src/linker/Linker.Steps/MarkStep.cs

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ protected LinkContext Context {
6565
readonly List<AttributeProviderPair> _ivt_attributes;
6666
protected Queue<(AttributeProviderPair, DependencyInfo, MarkScopeStack.Scope)> _lateMarkedAttributes;
6767
protected List<(TypeDefinition, MarkScopeStack.Scope)> _typesWithInterfaces;
68-
protected HashSet<(OverrideInformation, MarkScopeStack.Scope)> _interfaceOverrides;
6968
protected HashSet<AssemblyDefinition> _dynamicInterfaceCastableImplementationTypesDiscovered;
7069
protected List<TypeDefinition> _dynamicInterfaceCastableImplementationTypes;
7170
protected List<(MethodBody, MarkScopeStack.Scope)> _unreachableBodies;
@@ -226,7 +225,6 @@ public MarkStep ()
226225
_ivt_attributes = new List<AttributeProviderPair> ();
227226
_lateMarkedAttributes = new Queue<(AttributeProviderPair, DependencyInfo, MarkScopeStack.Scope)> ();
228227
_typesWithInterfaces = new List<(TypeDefinition, MarkScopeStack.Scope)> ();
229-
_interfaceOverrides = new HashSet<(OverrideInformation, MarkScopeStack.Scope)> ();
230228
_dynamicInterfaceCastableImplementationTypesDiscovered = new HashSet<AssemblyDefinition> ();
231229
_dynamicInterfaceCastableImplementationTypes = new List<TypeDefinition> ();
232230
_unreachableBodies = new List<(MethodBody, MarkScopeStack.Scope)> ();
@@ -573,9 +571,10 @@ protected virtual void EnqueueMethod (MethodDefinition method, in DependencyInfo
573571

574572
void ProcessVirtualMethods ()
575573
{
576-
foreach ((MethodDefinition method, MarkScopeStack.Scope scope) in _virtual_methods) {
577-
using (ScopeStack.PushScope (scope))
574+
foreach ((var method, var scope) in _virtual_methods) {
575+
using (ScopeStack.PushScope (scope)) {
578576
ProcessVirtualMethod (method);
577+
}
579578
}
580579
}
581580

@@ -603,26 +602,19 @@ void ProcessMarkedTypesWithInterfaces ()
603602
!unusedInterfacesOptimizationEnabled) {
604603
MarkInterfaceImplementations (type);
605604
}
606-
// OverrideInformation for interfaces in PreservedScope aren't added yet
605+
// Interfaces in PreservedScope should have their methods added to _virtual_methods so that they are properly processed
607606
foreach (var method in type.Methods) {
608-
var baseOverrideInformations = Annotations.GetBaseMethods (method);
609-
if (baseOverrideInformations is null)
607+
var baseMethods = Annotations.GetBaseMethods (method);
608+
if (baseMethods is null)
610609
continue;
611-
foreach (var ov in baseOverrideInformations) {
612-
if (ov.Base.DeclaringType is not null && ov.Base.DeclaringType.IsInterface && IgnoreScope (ov.Base.DeclaringType.Scope))
613-
_interfaceOverrides.Add ((ov, ScopeStack.CurrentScope));
610+
foreach (var ov in baseMethods) {
611+
if (ov.Base.DeclaringType is not null && ov.Base.DeclaringType.IsInterface && IgnoreScope (ov.Base.DeclaringType.Scope)) {
612+
_virtual_methods.Add ((ov.Base, ScopeStack.CurrentScope));
613+
}
614614
}
615615
}
616616
}
617617
}
618-
619-
var interfaceOverrides = _interfaceOverrides.ToArray ();
620-
foreach ((var overrideInformation, var scope) in interfaceOverrides) {
621-
using (ScopeStack.PushScope (scope)) {
622-
if (IsInterfaceImplementationMethodNeededByTypeDueToInterface (overrideInformation))
623-
MarkMethod (overrideInformation.Override, new DependencyInfo (DependencyKind.Override, overrideInformation.Base), scope.Origin);
624-
}
625-
}
626618
}
627619

628620
void DiscoverDynamicCastableImplementationInterfaces ()
@@ -705,10 +697,23 @@ void ProcessVirtualMethod (MethodDefinition method)
705697
{
706698
Annotations.EnqueueVirtualMethod (method);
707699

708-
var defaultImplementations = Annotations.GetDefaultInterfaceImplementations (method);
709-
if (defaultImplementations != null) {
710-
foreach (var defaultImplementationInfo in defaultImplementations) {
711-
ProcessDefaultImplementation (defaultImplementationInfo.InstanceType, defaultImplementationInfo.ProvidingInterface);
700+
if (method.DeclaringType.IsInterface) {
701+
var defaultImplementations = Annotations.GetDefaultInterfaceImplementations (method);
702+
if (defaultImplementations is not null) {
703+
foreach (var dimInfo in defaultImplementations) {
704+
ProcessDefaultImplementation (dimInfo.ImplementingType, dimInfo.InterfaceImpl, dimInfo.DefaultInterfaceMethod);
705+
706+
var ov = new OverrideInformation (method, dimInfo.DefaultInterfaceMethod, Context);
707+
if (IsInterfaceImplementationMethodNeededByTypeDueToInterface (ov, dimInfo.ImplementingType))
708+
MarkMethod (ov.Override, new DependencyInfo (DependencyKind.Override, ov.Base), ScopeStack.CurrentScope.Origin);
709+
}
710+
}
711+
var overridingMethods = Annotations.GetOverrides (method);
712+
if (overridingMethods is not null) {
713+
foreach (var ov in overridingMethods) {
714+
if (IsInterfaceImplementationMethodNeededByTypeDueToInterface (ov, ov.Override.DeclaringType))
715+
MarkMethod (ov.Override, new DependencyInfo (DependencyKind.Override, ov.Base), ScopeStack.CurrentScope.Origin);
716+
}
712717
}
713718
}
714719
}
@@ -724,10 +729,8 @@ bool ShouldMarkOverrideForBase (OverrideInformation overrideInformation)
724729
Debug.Assert (Annotations.IsMarked (overrideInformation.Base) || IgnoreScope (overrideInformation.Base.DeclaringType.Scope));
725730
if (!Annotations.IsMarked (overrideInformation.Override.DeclaringType))
726731
return false;
727-
if (overrideInformation.IsOverrideOfInterfaceMember) {
728-
_interfaceOverrides.Add ((overrideInformation, ScopeStack.CurrentScope));
732+
if (overrideInformation.IsOverrideOfInterfaceMember)
729733
return false;
730-
}
731734

732735
if (!Context.IsOptimizationEnabled (CodeOptimizations.OverrideRemoval, overrideInformation.Override))
733736
return true;
@@ -816,9 +819,10 @@ bool RequiresInterfaceRecursively (TypeDefinition typeToExamine, TypeDefinition
816819
return false;
817820
}
818821

819-
void ProcessDefaultImplementation (TypeDefinition typeWithDefaultImplementedInterfaceMethod, InterfaceImplementation implementation)
822+
void ProcessDefaultImplementation (TypeDefinition typeWithDefaultImplementedInterfaceMethod, InterfaceImplementation implementation, MethodDefinition implementationMethod)
820823
{
821-
if (!Annotations.IsInstantiated (typeWithDefaultImplementedInterfaceMethod))
824+
if ((!implementationMethod.IsStatic && !Annotations.IsInstantiated (typeWithDefaultImplementedInterfaceMethod))
825+
|| implementationMethod.IsStatic && !Annotations.IsRelevantToVariantCasting (typeWithDefaultImplementedInterfaceMethod))
822826
return;
823827

824828
MarkInterfaceImplementation (implementation);
@@ -2275,9 +2279,9 @@ void MarkTypeWithDebuggerDisplayAttribute (TypeDefinition type, CustomAttribute
22752279
// Record a logical dependency on the attribute so that we can blame it for the kept members below.
22762280
Tracer.AddDirectDependency (attribute, new DependencyInfo (DependencyKind.CustomAttribute, type), marked: false);
22772281

2278-
MarkTypeWithDebuggerDisplayAttributeValue(type, attribute, (string) attribute.ConstructorArguments[0].Value);
2282+
MarkTypeWithDebuggerDisplayAttributeValue (type, attribute, (string) attribute.ConstructorArguments[0].Value);
22792283
if (attribute.HasProperties) {
2280-
foreach (var property in attribute.Properties) {
2284+
foreach (var property in attribute.Properties) {
22812285
if (property.Name is "Name" or "Type") {
22822286
MarkTypeWithDebuggerDisplayAttributeValue (type, attribute, (string) property.Argument.Value);
22832287
}
@@ -2545,19 +2549,17 @@ bool IsMethodNeededByTypeDueToPreservedScope (MethodDefinition method)
25452549
/// <summary>
25462550
/// Returns true if the override method is required due to the interface that the base method is declared on. See doc at <see href="docs/methods-kept-by-interface.md"/> for explanation of logic.
25472551
/// </summary>
2548-
bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformation overrideInformation)
2552+
bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformation overrideInformation, TypeDefinition typeThatImplsInterface)
25492553
{
25502554
var @base = overrideInformation.Base;
25512555
var method = overrideInformation.Override;
2556+
Debug.Assert (@base.DeclaringType.IsInterface);
25522557
if (@base is null || method is null || @base.DeclaringType is null)
25532558
return false;
25542559

25552560
if (Annotations.IsMarked (method))
25562561
return false;
25572562

2558-
if (!@base.DeclaringType.IsInterface)
2559-
return false;
2560-
25612563
// If the interface implementation is not marked, do not mark the implementation method
25622564
// A type that doesn't implement the interface isn't required to have methods that implement the interface.
25632565
InterfaceImplementation? iface = overrideInformation.MatchingInterfaceImplementation;
@@ -2578,12 +2580,12 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat
25782580
// If the method is static and the implementing type is relevant to variant casting, mark the implementation method.
25792581
// A static method may only be called through a constrained call if the type is relevant to variant casting.
25802582
if (@base.IsStatic)
2581-
return Annotations.IsRelevantToVariantCasting (method.DeclaringType)
2583+
return Annotations.IsRelevantToVariantCasting (typeThatImplsInterface)
25822584
|| IgnoreScope (@base.DeclaringType.Scope);
25832585

25842586
// If the implementing type is marked as instantiated, mark the implementation method.
25852587
// If the type is not instantiated, do not mark the implementation method
2586-
return Annotations.IsInstantiated (method.DeclaringType);
2588+
return Annotations.IsInstantiated (typeThatImplsInterface);
25872589
}
25882590

25892591
static bool IsSpecialSerializationConstructor (MethodDefinition method)
@@ -3231,7 +3233,7 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo
32313233
} else if (method.TryGetProperty (out PropertyDefinition? property))
32323234
MarkProperty (property, new DependencyInfo (PropagateDependencyKindToAccessors (reason.Kind, DependencyKind.PropertyOfPropertyMethod), method));
32333235
else if (method.TryGetEvent (out EventDefinition? @event)) {
3234-
MarkEvent (@event, new DependencyInfo (PropagateDependencyKindToAccessors(reason.Kind, DependencyKind.EventOfEventMethod), method));
3236+
MarkEvent (@event, new DependencyInfo (PropagateDependencyKindToAccessors (reason.Kind, DependencyKind.EventOfEventMethod), method));
32353237
}
32363238

32373239
if (method.HasMetadataParameters ()) {
@@ -3315,7 +3317,7 @@ protected virtual void DoAdditionalMethodProcessing (MethodDefinition method)
33153317
{
33163318
}
33173319

3318-
static DependencyKind PropagateDependencyKindToAccessors(DependencyKind parentDependencyKind, DependencyKind kind)
3320+
static DependencyKind PropagateDependencyKindToAccessors (DependencyKind parentDependencyKind, DependencyKind kind)
33193321
{
33203322
switch (parentDependencyKind) {
33213323
// If the member is marked due to descriptor or similar, propagate the original reason to suppress some warnings correctly
@@ -3335,11 +3337,11 @@ void MarkImplicitlyUsedFields (TypeDefinition type)
33353337
return;
33363338

33373339
// keep fields for types with explicit layout, for enums and for InlineArray types
3338-
if (!type.IsAutoLayout || type.IsEnum || TypeIsInlineArrayType(type))
3340+
if (!type.IsAutoLayout || type.IsEnum || TypeIsInlineArrayType (type))
33393341
MarkFields (type, includeStatic: type.IsEnum, reason: new DependencyInfo (DependencyKind.MemberOfType, type));
33403342
}
33413343

3342-
static bool TypeIsInlineArrayType(TypeDefinition type)
3344+
static bool TypeIsInlineArrayType (TypeDefinition type)
33433345
{
33443346
if (!type.IsValueType)
33453347
return false;
@@ -3584,7 +3586,7 @@ protected internal virtual void MarkEvent (EventDefinition evt, in DependencyInf
35843586

35853587
MarkCustomAttributes (evt, new DependencyInfo (DependencyKind.CustomAttribute, evt));
35863588

3587-
DependencyKind dependencyKind = PropagateDependencyKindToAccessors(reason.Kind, DependencyKind.EventMethod);
3589+
DependencyKind dependencyKind = PropagateDependencyKindToAccessors (reason.Kind, DependencyKind.EventMethod);
35883590
MarkMethodIfNotNull (evt.AddMethod, new DependencyInfo (dependencyKind, evt), ScopeStack.CurrentScope.Origin);
35893591
MarkMethodIfNotNull (evt.InvokeMethod, new DependencyInfo (dependencyKind, evt), ScopeStack.CurrentScope.Origin);
35903592
MarkMethodIfNotNull (evt.RemoveMethod, new DependencyInfo (dependencyKind, evt), ScopeStack.CurrentScope.Origin);

src/tools/illink/src/linker/Linker/Annotations.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,22 +447,30 @@ public bool IsPublic (IMetadataTokenProvider provider)
447447
}
448448

449449
/// <summary>
450-
/// Returns a list of all known methods that override <paramref name="method"/>. The list may be incomplete if other overrides exist in assemblies that haven't been processed by TypeMapInfo yet
450+
/// Returns a list of all known methods that override <paramref name="method"/>.
451+
/// The list may be incomplete if other overrides exist in assemblies that haven't been processed by TypeMapInfo yet
451452
/// </summary>
452453
public IEnumerable<OverrideInformation>? GetOverrides (MethodDefinition method)
453454
{
454455
return TypeMapInfo.GetOverrides (method);
455456
}
456457

457-
public IEnumerable<(TypeDefinition InstanceType, InterfaceImplementation ProvidingInterface)>? GetDefaultInterfaceImplementations (MethodDefinition method)
458+
/// <summary>
459+
/// Returns a list of all default interface methods that implement <paramref name="method"/> for a type.
460+
/// ImplementingType is the type that implements the interface,
461+
/// InterfaceImpl is the <see cref="InterfaceImplementation" /> for the interface <paramref name="method" /> is declared on, and
462+
/// DefaultInterfaceMethod is the method that implements <paramref name="method"/>.
463+
/// </summary>
464+
/// <param name="method">The interface method to find default implementations for</param>
465+
public IEnumerable<(TypeDefinition ImplementingType, InterfaceImplementation InterfaceImpl, MethodDefinition DefaultInterfaceMethod)>? GetDefaultInterfaceImplementations (MethodDefinition method)
458466
{
459467
return TypeMapInfo.GetDefaultInterfaceImplementations (method);
460468
}
461469

462470
/// <summary>
463471
/// Returns all base methods that <paramref name="method"/> overrides.
464472
/// This includes methods on <paramref name="method"/>'s declaring type's base type (but not methods higher up in the type hierarchy),
465-
/// methods on an interface that <paramref name="method"/>'s delcaring type implements,
473+
/// methods on an interface that <paramref name="method"/>'s declaring type implements,
466474
/// and methods an interface implemented by a derived type of <paramref name="method"/>'s declaring type if the derived type uses <paramref name="method"/> as the implementing method.
467475
/// The list may be incomplete if there are derived types in assemblies that havent been processed yet that use <paramref name="method"/> to implement an interface.
468476
/// </summary>

src/tools/illink/src/linker/Linker/TypeMapInfo.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
//
3131

3232
using System.Collections.Generic;
33+
using System.Diagnostics;
3334
using System.Diagnostics.CodeAnalysis;
3435
using Mono.Cecil;
3536

@@ -42,7 +43,7 @@ public class TypeMapInfo
4243
readonly LinkContext context;
4344
protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> base_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
4445
protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> override_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
45-
protected readonly Dictionary<MethodDefinition, List<(TypeDefinition InstanceType, InterfaceImplementation ImplementationProvider)>> default_interface_implementations = new Dictionary<MethodDefinition, List<(TypeDefinition, InterfaceImplementation)>> ();
46+
protected readonly Dictionary<MethodDefinition, List<(TypeDefinition InstanceType, InterfaceImplementation ImplementationProvider, MethodDefinition DefaultImplementationMethod)>> default_interface_implementations = new Dictionary<MethodDefinition, List<(TypeDefinition, InterfaceImplementation, MethodDefinition)>> ();
4647

4748
public TypeMapInfo (LinkContext context)
4849
{
@@ -84,9 +85,16 @@ public void EnsureProcessed (AssemblyDefinition assembly)
8485
return bases;
8586
}
8687

87-
public IEnumerable<(TypeDefinition InstanceType, InterfaceImplementation ProvidingInterface)>? GetDefaultInterfaceImplementations (MethodDefinition method)
88+
/// <summary>
89+
/// Returns a list of all default interface methods that implement <paramref name="method"/> for a type.
90+
/// ImplementingType is the type that implements the interface,
91+
/// InterfaceImpl is the <see cref="InterfaceImplementation" /> for the interface <paramref name="method" /> is declared on, and
92+
/// DefaultInterfaceMethod is the method that implements <paramref name="method"/>.
93+
/// </summary>
94+
/// <param name="method">The interface method to find default implementations for</param>
95+
public IEnumerable<(TypeDefinition ImplementingType, InterfaceImplementation InterfaceImpl, MethodDefinition DefaultImplementationMethod)>? GetDefaultInterfaceImplementations (MethodDefinition baseMethod)
8896
{
89-
default_interface_implementations.TryGetValue (method, out var ret);
97+
default_interface_implementations.TryGetValue (baseMethod, out var ret);
9098
return ret;
9199
}
92100

@@ -110,14 +118,15 @@ public void AddOverride (MethodDefinition @base, MethodDefinition @override, Int
110118
methods.Add (new OverrideInformation (@base, @override, context, matchingInterfaceImplementation));
111119
}
112120

113-
public void AddDefaultInterfaceImplementation (MethodDefinition @base, TypeDefinition implementingType, InterfaceImplementation matchingInterfaceImplementation)
121+
public void AddDefaultInterfaceImplementation (MethodDefinition @base, TypeDefinition implementingType, (InterfaceImplementation, MethodDefinition) matchingInterfaceImplementation)
114122
{
123+
Debug.Assert(@base.DeclaringType.IsInterface);
115124
if (!default_interface_implementations.TryGetValue (@base, out var implementations)) {
116-
implementations = new List<(TypeDefinition, InterfaceImplementation)> ();
125+
implementations = new List<(TypeDefinition, InterfaceImplementation, MethodDefinition)> ();
117126
default_interface_implementations.Add (@base, implementations);
118127
}
119128

120-
implementations.Add ((implementingType, matchingInterfaceImplementation));
129+
implementations.Add ((implementingType, matchingInterfaceImplementation.Item1, matchingInterfaceImplementation.Item2));
121130
}
122131

123132
protected virtual void MapType (TypeDefinition type)
@@ -278,6 +287,7 @@ void FindAndAddDefaultInterfaceImplementations (TypeDefinition type, MethodDefin
278287
{
279288
// Go over all interfaces, trying to find a method that is an explicit MethodImpl of the
280289
// interface method in question.
290+
281291
foreach (var interfaceImpl in type.Interfaces) {
282292
var potentialImplInterface = context.TryResolve (interfaceImpl.InterfaceType);
283293
if (potentialImplInterface == null)
@@ -288,7 +298,9 @@ void FindAndAddDefaultInterfaceImplementations (TypeDefinition type, MethodDefin
288298
foreach (var potentialImplMethod in potentialImplInterface.Methods) {
289299
if (potentialImplMethod == interfaceMethod &&
290300
!potentialImplMethod.IsAbstract) {
291-
AddDefaultInterfaceImplementation (interfaceMethod, type, interfaceImpl);
301+
AddDefaultInterfaceImplementation (interfaceMethod, type, (interfaceImpl, potentialImplMethod));
302+
foundImpl = true;
303+
break;
292304
}
293305

294306
if (!potentialImplMethod.HasOverrides)
@@ -297,7 +309,7 @@ void FindAndAddDefaultInterfaceImplementations (TypeDefinition type, MethodDefin
297309
// This method is an override of something. Let's see if it's the method we are looking for.
298310
foreach (var @override in potentialImplMethod.Overrides) {
299311
if (context.TryResolve (@override) == interfaceMethod) {
300-
AddDefaultInterfaceImplementation (interfaceMethod, type, interfaceImpl);
312+
AddDefaultInterfaceImplementation (interfaceMethod, type, (interfaceImpl, @potentialImplMethod));
301313
foundImpl = true;
302314
break;
303315
}

src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,30 @@ public Task InterfaceWithAttributeOnImplementation ()
2727
return RunTest (allowMissingWarnings: true);
2828
}
2929

30+
[Fact]
31+
public Task MostSpecificDefaultImplementationKeptInstance ()
32+
{
33+
return RunTest (allowMissingWarnings: true);
34+
}
35+
36+
[Fact]
37+
public Task MostSpecificDefaultImplementationKeptStatic ()
38+
{
39+
return RunTest (allowMissingWarnings: true);
40+
}
41+
3042
[Fact]
3143
public Task SimpleDefaultInterfaceMethod ()
3244
{
3345
return RunTest (allowMissingWarnings: true);
3446
}
3547

48+
[Fact]
49+
public Task StaticDefaultInterfaceMethodOnStruct ()
50+
{
51+
return RunTest (allowMissingWarnings: true);
52+
}
53+
3654
[Fact]
3755
public Task UnusedDefaultInterfaceImplementation ()
3856
{

0 commit comments

Comments
 (0)