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

[NativeAOT] Add support for [Preserve] attributes #18666

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
3 changes: 3 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,9 @@
<!-- Enable serialization discovery. Ref: https://github.com/xamarin/xamarin-macios/issues/15676 -->
<_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --enable-serialization-discovery</_ExtraTrimmerArgs>

<!-- If we're using NativeAOT, tell ILLink to not remove dependency attributes (DynamicDependencyAttribute), because NativeAOT's trimmer also needs to see them. -->
<_ExtraTrimmerArgs Condition="'$(_UseNativeAot)' == 'true'">$(_ExtraTrimmerArgs) --keep-dep-attributes</_ExtraTrimmerArgs>

<!-- We always want the linker to process debug symbols, even when building in Release mode, because the AOT compiler uses the managed debug symbols to output DWARF debugging symbols -->
<TrimmerRemoveSymbols Condition="'$(TrimmerRemoveSymbols)' == ''">false</TrimmerRemoveSymbols>

Expand Down
37 changes: 37 additions & 0 deletions tools/dotnet-linker/AppBundleRewriter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

using Mono.Cecil;
Expand Down Expand Up @@ -309,6 +310,12 @@ public TypeReference System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute
}
}

public TypeReference System_Diagnostics_CodeAnalysis_DynamicallyAccessedMemberTypes {
get {
return GetTypeReference (CorlibAssembly, "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes", out var _);
}
}

public TypeReference System_Reflection_MethodBase {
get {
return GetTypeReference (CorlibAssembly, "System.Reflection.MethodBase", out var _);
Expand Down Expand Up @@ -466,12 +473,25 @@ public MethodReference DynamicDependencyAttribute_ctor__String_Type {
return GetMethodReference (CorlibAssembly,
System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute,
".ctor",
".ctor(String,Type)",
isStatic: false,
System_String,
System_Type);
}
}

public MethodReference DynamicDependencyAttribute_ctor__DynamicallyAccessedMemberTypes_Type {
get {
return GetMethodReference (CorlibAssembly,
System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute,
".ctor",
".ctor(DynamicallyAccessedMemberTypes,Type)",
isStatic: false,
System_Diagnostics_CodeAnalysis_DynamicallyAccessedMemberTypes,
System_Type);
}
}

public MethodReference RuntimeTypeHandle_Equals {
get {
return GetMethodReference (CorlibAssembly, System_RuntimeTypeHandle, "Equals", isStatic: false, System_RuntimeTypeHandle);
Expand Down Expand Up @@ -1147,5 +1167,22 @@ public void ClearCurrentAssembly ()
method_map.Clear ();
field_map.Clear ();
}

public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, TypeDefinition type)
{
var attribute = new CustomAttribute (DynamicDependencyAttribute_ctor__String_Type);
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, memberSignature));
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_Type, type));
return attribute;
}

public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes memberTypes, TypeDefinition type)
{
var attribute = new CustomAttribute (DynamicDependencyAttribute_ctor__DynamicallyAccessedMemberTypes_Type);
// typed as 'int' because that's how the linker expects it: https://github.com/dotnet/runtime/blob/3c5ad6c677b4a3d12bc6a776d654558cca2c36a9/src/tools/illink/src/linker/Linker/DynamicDependency.cs#L97
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_Diagnostics_CodeAnalysis_DynamicallyAccessedMemberTypes, (int) memberTypes));
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_Type, type));
return attribute;
}
}
}
121 changes: 107 additions & 14 deletions tools/dotnet-linker/ApplyPreserveAttributeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,31 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;

using Mono.Linker;
using Mono.Linker.Steps;

using Mono.Cecil;
using Mono.Cecil.Cil;

using Xamarin.Bundler;
using Xamarin.Linker;
using Xamarin.Utils;

#nullable enable

namespace Mono.Tuner {

public abstract class ApplyPreserveAttributeBase : BaseSubStep {
public abstract class ApplyPreserveAttributeBase : ConfigurationAwareSubStep {

AppBundleRewriter? abr;

protected override string Name { get => "Apply Preserve Attribute"; }

protected override int ErrorCode { get => 2450; }

// set 'removeAttribute' to true if you want the preserved attribute to be removed from the final assembly
protected abstract bool IsPreservedAttribute (ICustomAttributeProvider provider, CustomAttribute attribute, out bool removeAttribute);
Expand All @@ -30,36 +43,44 @@ public override SubStepTargets Targets {
}
}

public override void Initialize (LinkContext context)
{
base.Initialize (context);

if (Configuration.Application.XamarinRuntime == XamarinRuntime.NativeAOT)
abr = Configuration.AppBundleRewriter;
}

public override bool IsActiveFor (AssemblyDefinition assembly)
{
return Annotations.GetAction (assembly) == AssemblyAction.Link;
}

public override void ProcessType (TypeDefinition type)
protected override void Process (TypeDefinition type)
{
TryApplyPreserveAttribute (type);
}

public override void ProcessField (FieldDefinition field)
protected override void Process (FieldDefinition field)
{
foreach (var attribute in GetPreserveAttributes (field))
Mark (field, attribute);
}

public override void ProcessMethod (MethodDefinition method)
protected override void Process (MethodDefinition method)
{
MarkMethodIfPreserved (method);
}

public override void ProcessProperty (PropertyDefinition property)
protected override void Process (PropertyDefinition property)
{
foreach (var attribute in GetPreserveAttributes (property)) {
MarkMethod (property.GetMethod, attribute);
MarkMethod (property.SetMethod, attribute);
}
}

public override void ProcessEvent (EventDefinition @event)
protected override void Process (EventDefinition @event)
{
foreach (var attribute in GetPreserveAttributes (@event)) {
MarkMethod (@event.AddMethod, attribute);
Expand Down Expand Up @@ -103,6 +124,7 @@ void PreserveConditional (IMetadataTokenProvider provider)
}

Annotations.AddPreservedMethod (method.DeclaringType, method);
AddConditionalDynamicDependencyAttribute (method.DeclaringType, method);
}

static bool IsConditionalAttribute (CustomAttribute? attribute)
Expand All @@ -120,6 +142,7 @@ static bool IsConditionalAttribute (CustomAttribute? attribute)
void PreserveUnconditional (IMetadataTokenProvider provider)
{
Annotations.Mark (provider);
AddDynamicDependencyAttribute (provider);

var member = provider as IMemberDefinition;
if (member is null || member.DeclaringType is null)
Expand All @@ -131,14 +154,7 @@ void PreserveUnconditional (IMetadataTokenProvider provider)
void TryApplyPreserveAttribute (TypeDefinition type)
{
foreach (var attribute in GetPreserveAttributes (type)) {
Annotations.Mark (type);

if (!attribute.HasFields)
continue;

foreach (var named_argument in attribute.Fields)
if (named_argument.Name == "AllMembers" && (bool) named_argument.Argument.Value)
Annotations.SetPreserve (type, TypePreserve.All);
PreserveType (type, attribute);
}
}

Expand All @@ -165,5 +181,82 @@ List<CustomAttribute> GetPreserveAttributes (ICustomAttributeProvider provider)

return attrs;
}

protected void PreserveType (TypeDefinition type, CustomAttribute preserveAttribute)
{
var allMembers = false;
if (preserveAttribute.HasFields) {
foreach (var named_argument in preserveAttribute.Fields)
if (named_argument.Name == "AllMembers" && (bool) named_argument.Argument.Value)
allMembers = true;
}

PreserveType (type, allMembers);
}

protected void PreserveType (TypeDefinition type, bool allMembers)
{
Annotations.Mark (type);
if (allMembers)
Annotations.SetPreserve (type, TypePreserve.All);
AddDynamicDependencyAttribute (type, allMembers);
}

MethodDefinition GetOrCreateModuleConstructor (ModuleDefinition @module)
{
var moduleType = @module.GetModuleType ();
var moduleConstructor = moduleType.GetTypeConstructor ();
if (moduleConstructor is null) {
moduleConstructor = moduleType.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, abr!.System_Void);
moduleConstructor.CreateBody (out var il);
il.Emit (OpCodes.Ret);
}
return moduleConstructor;
}

void AddDynamicDependencyAttribute (TypeDefinition type, bool allMembers)
{
if (abr is null)
return;

abr.ClearCurrentAssembly ();
abr.SetCurrentAssembly (type.Module.Assembly);

var moduleConstructor = GetOrCreateModuleConstructor (type.GetModule ());
var attrib = abr.CreateDynamicDependencyAttribute (allMembers ? DynamicallyAccessedMemberTypes.All : DynamicallyAccessedMemberTypes.None, type);
moduleConstructor.CustomAttributes.Add (attrib);

abr.ClearCurrentAssembly ();
}

void AddConditionalDynamicDependencyAttribute (TypeDefinition onType, MethodDefinition forMethod)
{
if (abr is null)
return;

// I haven't found a way to express a conditional Preserve attribute using DynamicDependencyAttribute :/
ErrorHelper.Warning (2112, Errors.MX2112 /* Unable to apply the conditional [Preserve] attribute on the member {0} */, forMethod.FullName);
}

void AddDynamicDependencyAttribute (IMetadataTokenProvider provider)
{
if (abr is null)
return;

var member = provider as IMemberDefinition;
if (member is null)
throw ErrorHelper.CreateError (99, $"Unable to add dynamic dependency attribute to {provider.GetType ().FullName}");

var module = member.GetModule ();
abr.ClearCurrentAssembly ();
abr.SetCurrentAssembly (module.Assembly);

var moduleConstructor = GetOrCreateModuleConstructor (module);
var signature = DocumentationComments.GetSignature (member);
var attrib = abr.CreateDynamicDependencyAttribute (signature, member.DeclaringType);
moduleConstructor.CustomAttributes.Add (attrib);

abr.ClearCurrentAssembly ();
}
}
}
23 changes: 23 additions & 0 deletions tools/dotnet-linker/CecilExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.Collections.Generic;
using System.Linq;

using Mono.Cecil;
using Mono.Cecil.Cil;

using Xamarin.Bundler;

#nullable enable

namespace Xamarin.Linker {
Expand Down Expand Up @@ -127,5 +130,25 @@ public static MethodDefinition AddDefaultConstructor (this TypeDefinition type,
il.Emit (OpCodes.Ret);
return defaultCtor;
}

public static ModuleDefinition GetModule (this IMetadataTokenProvider provider)
{
if (provider is TypeDefinition td)
return td.Module;

if (provider is IMemberDefinition md)
return md.DeclaringType.Module;

throw ErrorHelper.CreateError (99, $"Unable to get the module of {provider.GetType ().FullName}");
}

public static TypeDefinition GetModuleType (this ModuleDefinition @module)
{
var moduleType = @module.Types.SingleOrDefault (v => v.Name == "<Module>");
if (moduleType is null)
throw ErrorHelper.CreateError (99, $"No <Module> type found in {@module.Name}");
return moduleType;
}

}
}
80 changes: 80 additions & 0 deletions tools/dotnet-linker/DocumentionComments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Text;

using Mono.Cecil;

using Xamarin.Bundler;

#nullable enable

namespace Xamarin.Utils {
// signature format: https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d42-id-string-format
public static class DocumentationComments {
public static string GetSignature (IMetadataTokenProvider member)
{
if (member is FieldDefinition fd)
return GetSignature (fd);

if (member is MethodDefinition md)
return GetSignature (md);

if (member is TypeDefinition td)
return GetSignature (td);

throw ErrorHelper.CreateError (99, $"Unable to get the doc signature for {member.GetType ().FullName}");
}

public static string GetSignature (TypeDefinition type)
{
if (type.IsNested)
return type.Name;
return type.FullName;
}

public static string GetSignature (FieldDefinition field)
{
return field.Name.Replace ('.', '#');
}

public static string GetSignature (MethodDefinition method)
{
var sb = new StringBuilder ();
sb.Append (method.Name.Replace ('.', '#'));
sb.Append ('(');
for (var i = 0; i < method.Parameters.Count; i++) {
if (i > 0)
sb.Append (',');

var parameterType = method.Parameters [i].ParameterType;
WriteTypeSignature (sb, parameterType);
}
sb.Append (')');

return sb.ToString ();
}

static void WriteTypeSignature (StringBuilder sb, TypeReference type)
{
if (type is ByReferenceType brt) {
WriteTypeSignature (sb, brt.GetElementType ());
sb.Append ('@');
return;
}

if (type is ArrayType at) {
WriteTypeSignature (sb, at.GetElementType ());
sb.Append ("[]");
return;
}

if (type is PointerType pt) {
WriteTypeSignature (sb, pt.GetElementType ());
sb.Append ('*');
return;
}

sb.Append (type.FullName.Replace ('/', '.'));
}
}
}
Loading
Loading