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

Make it possible to trim custom attribute artifacts #100395

Closed
wants to merge 3 commits into from
Closed
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 @@ -20,6 +20,8 @@ namespace ILCompiler.DependencyAnalysis
/// </summary>
internal static class CustomAttributeBasedDependencyAlgorithm
{
public static bool CanOptimizeAttributeArtifacts(NodeFactory factory) => factory.CompilationModuleGroup.IsSingleFileCompilation;

public static void AddDependenciesDueToCustomAttributes(ref DependencyList dependencies, NodeFactory factory, EcmaMethod method)
{
MetadataReader reader = method.MetadataReader;
Expand Down Expand Up @@ -109,6 +111,7 @@ private static void AddDependenciesDueToCustomAttributes(ref DependencyList depe
var mdManager = (UsageBasedMetadataManager)factory.MetadataManager;
var attributeTypeProvider = new CustomAttributeTypeProvider(module);

bool metadataOnlyDependencies = CanOptimizeAttributeArtifacts(factory);

foreach (CustomAttributeHandle caHandle in attributeHandles)
{
Expand All @@ -129,10 +132,12 @@ private static void AddDependenciesDueToCustomAttributes(ref DependencyList depe
// Make a new list in case we need to abort.
var caDependencies = factory.MetadataManager.GetDependenciesForCustomAttribute(factory, constructor, decodedValue, parent) ?? new DependencyList();

caDependencies.Add(factory.ReflectedMethod(constructor.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Attribute constructor");
caDependencies.Add(factory.ReflectedType(constructor.OwningType), "Attribute type");
if (metadataOnlyDependencies)
caDependencies.Add(factory.MethodMetadata(constructor.GetTypicalMethodDefinition()), "Attribute constructor");
else
caDependencies.Add(factory.ReflectedMethod(constructor.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Attribute constructor");

if (AddDependenciesFromCustomAttributeBlob(caDependencies, factory, constructor.OwningType, decodedValue))
if (AddDependenciesFromCustomAttributeBlob(caDependencies, factory, constructor.OwningType, decodedValue, metadataOnlyDependencies))
{
dependencies ??= new DependencyList();
dependencies.AddRange(caDependencies);
Expand Down Expand Up @@ -161,46 +166,65 @@ private static void AddDependenciesDueToCustomAttributes(ref DependencyList depe
}
}

private static bool AddDependenciesFromCustomAttributeBlob(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, CustomAttributeValue<TypeDesc> value)
public static void AddDependenciesDueToCustomAttributeActivation(ref DependencyList dependencies, NodeFactory factory, EcmaModule module, CustomAttributeHandle attributeHandle)
{
MetadataReader reader = module.MetadataReader;
CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle);

dependencies ??= new DependencyList();

MethodDesc constructor = module.GetMethod(attribute.Constructor);
dependencies.Add(factory.ReflectedMethod(constructor.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Attribute constructor");

var attributeTypeProvider = new CustomAttributeTypeProvider(module);

CustomAttributeValue<TypeDesc> decodedValue = attribute.DecodeValue(attributeTypeProvider);
AddDependenciesFromCustomAttributeBlob(dependencies, factory, constructor.OwningType, decodedValue, metadataOnly: false);
}

private static bool AddDependenciesFromCustomAttributeBlob(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, CustomAttributeValue<TypeDesc> value, bool metadataOnly)
{
foreach (CustomAttributeTypedArgument<TypeDesc> decodedArgument in value.FixedArguments)
{
if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, decodedArgument.Type, decodedArgument.Value))
if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, decodedArgument.Type, decodedArgument.Value, metadataOnly))
return false;
}

foreach (CustomAttributeNamedArgument<TypeDesc> decodedArgument in value.NamedArguments)
{
if (decodedArgument.Kind == CustomAttributeNamedArgumentKind.Field)
{
if (!AddDependenciesFromField(dependencies, factory, attributeType, decodedArgument.Name))
if (!AddDependenciesFromField(dependencies, factory, attributeType, decodedArgument.Name, metadataOnly))
return false;
}
else
{
Debug.Assert(decodedArgument.Kind == CustomAttributeNamedArgumentKind.Property);

// Reflection will need to reflection-invoke the setter at runtime.
if (!AddDependenciesFromPropertySetter(dependencies, factory, attributeType, decodedArgument.Name))
if (!AddDependenciesFromPropertySetter(dependencies, factory, attributeType, decodedArgument.Name, metadataOnly))
return false;
}

if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, decodedArgument.Type, decodedArgument.Value))
if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, decodedArgument.Type, decodedArgument.Value, metadataOnly))
return false;
}

return true;
}

private static bool AddDependenciesFromField(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string fieldName)
private static bool AddDependenciesFromField(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string fieldName, bool metadataOnly)
{
FieldDesc field = attributeType.GetField(fieldName);
if (field is not null)
{
if (factory.MetadataManager.IsReflectionBlocked(field))
return false;

dependencies.Add(factory.ReflectedField(field), "Custom attribute blob");
if (metadataOnly)
dependencies.Add(factory.FieldMetadata(field), "Custom attribute blob");
else
dependencies.Add(factory.ReflectedField(field), "Custom attribute blob");

return true;
}
Expand All @@ -209,13 +233,13 @@ private static bool AddDependenciesFromField(DependencyList dependencies, NodeFa
TypeDesc baseType = attributeType.BaseType;

if (baseType != null)
return AddDependenciesFromField(dependencies, factory, baseType, fieldName);
return AddDependenciesFromField(dependencies, factory, baseType, fieldName, metadataOnly);

// Not found. This is bad metadata that will result in a runtime failure, but we shouldn't fail the compilation.
return true;
}

private static bool AddDependenciesFromPropertySetter(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string propertyName)
private static bool AddDependenciesFromPropertySetter(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string propertyName, bool metadataOnly)
{
EcmaType attributeTypeDefinition = (EcmaType)attributeType.GetTypeDefinition();

Expand All @@ -241,7 +265,10 @@ private static bool AddDependenciesFromPropertySetter(DependencyList dependencie
setterMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(setterMethod, (InstantiatedType)attributeType);
}

dependencies.Add(factory.ReflectedMethod(setterMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Custom attribute blob");
if (metadataOnly)
dependencies.Add(factory.MethodMetadata(setterMethod.GetTypicalMethodDefinition()), "Custom attribute blob");
else
dependencies.Add(factory.ReflectedMethod(setterMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Custom attribute blob");
}

return true;
Expand All @@ -252,21 +279,24 @@ private static bool AddDependenciesFromPropertySetter(DependencyList dependencie
TypeDesc baseType = attributeType.BaseType;

if (baseType != null)
return AddDependenciesFromPropertySetter(dependencies, factory, baseType, propertyName);
return AddDependenciesFromPropertySetter(dependencies, factory, baseType, propertyName, metadataOnly);

// Not found. This is bad metadata that will result in a runtime failure, but we shouldn't fail the compilation.
return true;
}

private static bool AddDependenciesFromCustomAttributeArgument(DependencyList dependencies, NodeFactory factory, TypeDesc type, object value)
private static bool AddDependenciesFromCustomAttributeArgument(DependencyList dependencies, NodeFactory factory, TypeDesc type, object value, bool metadataOnly)
{
// If this is an initializer that refers to e.g. a blocked enum, we can't encode this attribute.
if (factory.MetadataManager.IsReflectionBlocked(type))
return false;

// Reflection will need to be able to allocate this type at runtime
// (e.g. this could be an array that needs to be allocated, or an enum that needs to be boxed).
dependencies.Add(factory.ReflectedType(type), "Custom attribute blob");
if (metadataOnly)
TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, type, "Custom attribute blob");
else
dependencies.Add(factory.ReflectedType(type), "Custom attribute blob");

if (type.UnderlyingType.IsPrimitive || type.IsString || value == null)
return true;
Expand All @@ -279,7 +309,7 @@ private static bool AddDependenciesFromCustomAttributeArgument(DependencyList de

foreach (CustomAttributeTypedArgument<TypeDesc> arrayElement in (ImmutableArray<CustomAttributeTypedArgument<TypeDesc>>)value)
{
if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, arrayElement.Type, arrayElement.Value))
if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, arrayElement.Type, arrayElement.Value, metadataOnly))
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

using System.Collections.Generic;

using Internal.TypeSystem;

using ILCompiler.DependencyAnalysisFramework;
using System;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Sort usings


namespace ILCompiler.DependencyAnalysis
{
Expand All @@ -22,8 +25,25 @@ public CustomAttributeMetadataNode(ReflectableCustomAttribute customAttribute)
_customAttribute = customAttribute;
}

public override bool HasConditionalStaticDependencies => true;

public ReflectableCustomAttribute CustomAttribute => _customAttribute;

public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
{
if (!CustomAttributeBasedDependencyAlgorithm.CanOptimizeAttributeArtifacts(factory))
return Array.Empty<CombinedDependencyListEntry>();

// Presence of this type indicates that more than just the attribute metadata is needed:
// we also need runtime artifacts, such as the method body of the attribute constructor.
MetadataType nativeFormatType = factory.TypeSystemContext.SystemModule.GetType("System.Reflection.Runtime.CustomAttributes.NativeFormat", "NativeFormatCustomAttributeData");
return [new CombinedDependencyListEntry(
new ReflectedCustomAttributeNode(CustomAttribute),
factory.ConstructedTypeSymbol(nativeFormatType),
"Attributes are activated"
)];
}

// The metadata that the attribute depends on gets injected into the entity that owns the attribute.
// This makes the dependency graph less "nice", but it avoids either having to walk the attribute
// blob twice, or wasting memory holding on to dependencies here.
Expand All @@ -39,9 +59,7 @@ protected override string GetName(NodeFactory factory)

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 factory) => null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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;

namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Represents dependencies necessary to activate a custom attribute at runtime.
/// </summary>
internal sealed class ReflectedCustomAttributeNode : DependencyNodeCore<NodeFactory>
{
private readonly ReflectableCustomAttribute _customAttribute;

public ReflectedCustomAttributeNode(ReflectableCustomAttribute customAttribute)
{
_customAttribute = customAttribute;
}

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
{
DependencyList dependencies = null;
CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributeActivation(ref dependencies, factory, _customAttribute.Module, _customAttribute.CustomAttributeHandle);
return dependencies;
}

protected override string GetName(NodeFactory factory)
{
return $"Custom attribute activation {_customAttribute.CustomAttributeHandle} in {_customAttribute.Module}";
}

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

if (_type.IsEnum)
{
// A lot of the enum reflection actually happens on top of the respective MethodTable (e.g. getting the underlying type),
// so for enums also include their MethodTable.
dependencies.Add(factory.ReflectedType(_type), "Reflectable enum");

// Enums are not useful without their literal fields. The literal fields are not referenced
// from anywhere (source code reference to enums compiles to the underlying numerical constants in IL).
foreach (FieldDesc enumField in _type.GetFields())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@
<Compile Include="Compiler\DependencyAnalysis\NotReadOnlyFieldNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ObjectGetTypeFlowDependenciesNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\PointerTypeMapNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ReflectedCustomAttributeNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ReflectedDelegateNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ReflectedFieldNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ReflectedTypeNode.cs" />
Expand Down
9 changes: 9 additions & 0 deletions src/tests/nativeaot/AttributeTrimming/AttributeKept.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<CLRTestPriority>0</CLRTestPriority>
</PropertyGroup>
<ItemGroup>
<Compile Include="AttributeTrimming.cs" />
</ItemGroup>
</Project>
10 changes: 10 additions & 0 deletions src/tests/nativeaot/AttributeTrimming/AttributeTrimmed.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<CLRTestPriority>0</CLRTestPriority>
<DefineConstants>$(DefineConstants);TRIMMED</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="AttributeTrimming.cs" />
</ItemGroup>
</Project>
41 changes: 41 additions & 0 deletions src/tests/nativeaot/AttributeTrimming/AttributeTrimming.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;

[My]
class Program
{
static int Main()
{
#if !TRIMMED
typeof(Program).GetCustomAttributes(inherit: false);
#endif

Type t = GetTypeSecretly(nameof(Canary));

#if TRIMMED
return t == null ? 100 : 101;
#else
return t != null ? 100 : 101;
#endif
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
Justification = "That's the point")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2057:UnrecognizedReflectionPattern",
Justification = "That's the point")]
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
static Type GetTypeSecretly(string name) => Type.GetType(name);
}

class MyAttribute : Attribute
{
public MyAttribute()
{
Type.GetType(nameof(Canary)).ToString();
}
}

class Canary { }
Loading