From 6e0234bec0b48449e1901381dd0eea81c8bd716b Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 27 Apr 2021 17:13:14 -0700 Subject: [PATCH 1/8] Update to new linker custom steps API --- .../ApplyPreserveAttributeBase.cs | 6 +- tools/dotnet-linker/SetupStep.cs | 38 +++-- .../Steps/ConfigurationAwareMarkHandler.cs | 18 +++ .../Steps/DotNetMarkAssemblyDispatcher.cs | 11 ++ .../Steps/ExceptionalMarkHandler.cs | 116 ++++++++++++++ .../Steps/PreserveBlockCodeHandler.cs | 85 ++++++++++ .../Steps/PreserveBlockCodeSubStep.cs | 151 ------------------ tools/dotnet-linker/dotnet-linker.csproj | 4 +- tools/linker/ApplyPreserveAttribute.cs | 76 +++++---- tools/linker/CoreOptimizeGeneratedCode.cs | 98 ++++++++++-- ...tep.cs => PreserveSmartEnumConversions.cs} | 61 +++++++ tools/mmp/mmp.csproj | 4 +- tools/mtouch/mtouch.csproj | 4 +- 13 files changed, 463 insertions(+), 209 deletions(-) create mode 100644 tools/dotnet-linker/Steps/ConfigurationAwareMarkHandler.cs create mode 100644 tools/dotnet-linker/Steps/DotNetMarkAssemblyDispatcher.cs create mode 100644 tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs create mode 100644 tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs delete mode 100644 tools/dotnet-linker/Steps/PreserveBlockCodeSubStep.cs rename tools/linker/MonoTouch.Tuner/{PreserveSmartEnumConversionsSubStep.cs => PreserveSmartEnumConversions.cs} (79%) diff --git a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs index d9606fc5fedc..cb54e5ccb83b 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs @@ -23,7 +23,11 @@ public override SubStepTargets Targets { | SubStepTargets.Field | SubStepTargets.Method | SubStepTargets.Property - | SubStepTargets.Event; + | SubStepTargets.Event +#if NET + | SubStepTargets.Assembly +#endif + ; } } diff --git a/tools/dotnet-linker/SetupStep.cs b/tools/dotnet-linker/SetupStep.cs index 5fc343f8f49e..f224b76bfbb8 100644 --- a/tools/dotnet-linker/SetupStep.cs +++ b/tools/dotnet-linker/SetupStep.cs @@ -27,6 +27,17 @@ public List Steps { } } + List _markHandlers; + List MarkHandlers { + get { + if (_markHandlers == null) { + var pipeline = typeof (LinkContext).GetProperty ("Pipeline").GetGetMethod ().Invoke (Context, null); + _markHandlers = (List) pipeline.GetType ().GetProperty ("MarkHandlers").GetValue (pipeline); + } + return _markHandlers; + } + } + void InsertBefore (IStep step, string stepName) { for (int i = 0; i < Steps.Count; i++) { @@ -61,28 +72,31 @@ protected override void TryProcess () // This would not be needed of LinkContext.GetAssemblies () was exposed to us. InsertBefore (new CollectAssembliesStep (), "MarkStep"); - var pre_dynamic_dependency_lookup_substeps = new DotNetSubStepDispatcher (); - InsertBefore (pre_dynamic_dependency_lookup_substeps, "MarkStep"); - - var prelink_substeps = new DotNetSubStepDispatcher (); - InsertBefore (prelink_substeps, "MarkStep"); + var pre_mark_substeps = new DotNetSubStepDispatcher (); + InsertBefore (pre_mark_substeps, "MarkStep"); var post_sweep_substeps = new DotNetSubStepDispatcher (); InsertAfter (post_sweep_substeps, "SweepStep"); if (Configuration.LinkMode != LinkMode.None) { - pre_dynamic_dependency_lookup_substeps.Add (new PreserveBlockCodeSubStep ()); + MarkHandlers.Add (new PreserveBlockCodeHandler ()); // We need to run the ApplyPreserveAttribute step even we're only linking sdk assemblies, because even // though we know that sdk assemblies will never have Preserve attributes, user assemblies may have // [assembly: LinkSafe] attributes, which means we treat them as sdk assemblies and those may have // Preserve attributes. - prelink_substeps.Add (new ApplyPreserveAttribute ()); - prelink_substeps.Add (new OptimizeGeneratedCodeSubStep ()); - prelink_substeps.Add (new MarkNSObjects ()); - prelink_substeps.Add (new PreserveSmartEnumConversionsSubStep ()); - prelink_substeps.Add (new CollectUnmarkedMembersSubStep ()); - prelink_substeps.Add (new StoreAttributesStep ()); + // TODO: LinkSafeAttribute doesn't appear to be handled anywhere. Is this attribute still supported? + // ApplyPreserveAttribute no longer runs on all assemblies. [assembly: Preserve (typeof (SomeAttribute))] will + // no longer give SomeAttribute "Preserve" semantics. + MarkHandlers.Add (new DotNetMarkAssemblySubStepDispatcher (new ApplyPreserveAttribute ())); + MarkHandlers.Add (new OptimizeGeneratedCodeHandler ()); + // MarkNSObjects will run for all marked assemblies. + MarkHandlers.Add (new DotNetMarkAssemblySubStepDispatcher (new MarkNSObjects ())); + MarkHandlers.Add (new PreserveSmartEnumConversionsHandler ()); + + // TODO: these steps should probably run after mark. + pre_mark_substeps.Add (new CollectUnmarkedMembersSubStep ()); + pre_mark_substeps.Add (new StoreAttributesStep ()); post_sweep_substeps.Add (new RemoveAttributesStep ()); } diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareMarkHandler.cs b/tools/dotnet-linker/Steps/ConfigurationAwareMarkHandler.cs new file mode 100644 index 000000000000..a00d87a975a4 --- /dev/null +++ b/tools/dotnet-linker/Steps/ConfigurationAwareMarkHandler.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +using Xamarin.Bundler; + +namespace Xamarin.Linker { + public abstract class ConfigurationAwareMarkHandler : ExceptionalMarkHandler { + protected override void Report (Exception exception) + { + LinkerConfiguration.Report (context, exception); + } + + protected void Report (List exceptions) + { + LinkerConfiguration.Report (context, exceptions); + } + } +} diff --git a/tools/dotnet-linker/Steps/DotNetMarkAssemblyDispatcher.cs b/tools/dotnet-linker/Steps/DotNetMarkAssemblyDispatcher.cs new file mode 100644 index 000000000000..4daac49d8d85 --- /dev/null +++ b/tools/dotnet-linker/Steps/DotNetMarkAssemblyDispatcher.cs @@ -0,0 +1,11 @@ +using Mono.Linker.Steps; + +namespace Xamarin.Linker.Steps { + // MarkSubStepsDispatcher is abstract, so create a subclass we can instantiate. + // Can be removed when we update to the preview4 linker, which makes MarkSubStepsDispatcher non-abstract. + class DotNetMarkAssemblySubStepDispatcher : MarkSubStepsDispatcher { + public DotNetMarkAssemblySubStepDispatcher (params BaseSubStep[] subSteps) : base (subSteps) + { + } + } +} diff --git a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs new file mode 100644 index 000000000000..8b387020fe15 --- /dev/null +++ b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs @@ -0,0 +1,116 @@ +// Copyright 2016 Xamarin Inc. + +using System; +using Mono.Cecil; +using Mono.Tuner; +using Xamarin.Bundler; + +using Xamarin.Tuner; + +using Mono.Linker; +using Mono.Linker.Steps; + +namespace Xamarin.Linker { + + // Similar to ExceptionalSubStep, but this only runs for marked members + // that were registered for handling by the subclass. + public abstract class ExceptionalMarkHandler : IMarkHandler + { + public abstract void Initialize (LinkContext context, MarkContext markContext); + + protected DerivedLinkContext LinkContext => Configuration.DerivedLinkContext; + + protected LinkContext context { get; set; } + + protected AnnotationStore Annotations => context.Annotations; + protected LinkerConfiguration Configuration => LinkerConfiguration.GetInstance (context); + + protected Profile Profile => Configuration.Profile; + + public void ProcessAssembly (AssemblyDefinition assembly) + { + try { + Process (assembly); + } catch (Exception e) { + Report (Fail (assembly, e)); + } + } + + public void ProcessType (TypeDefinition type) + { + try { + Process (type); + } catch (Exception e) { + Report (Fail (type, e)); + } + } + + public void ProcessField (FieldDefinition field) + { + try { + Process (field); + } catch (Exception e) { + Report (Fail (field, e)); + } + } + + public void ProcessMethod (MethodDefinition method) + { + try { + Process (method); + } catch (Exception e) { + Report (Fail (method, e)); + } + } + + // state-aware versions to be subclassed + + protected virtual void Process (AssemblyDefinition assembly) + { + } + + protected virtual void Process (TypeDefinition type) + { + } + + protected virtual void Process (FieldDefinition field) + { + } + + protected virtual void Process (MethodDefinition method) + { + } + + // failure overrides, with defaults + + protected virtual Exception Fail (AssemblyDefinition assembly, Exception e) + { + return ErrorHelper.CreateError (ErrorCode, e, Errors.MX_ExceptionalSubSteps, Name, assembly?.FullName); + } + + protected virtual Exception Fail (TypeDefinition type, Exception e) + { + return ErrorHelper.CreateError (ErrorCode | 1, e, Errors.MX_ExceptionalSubSteps, Name, type?.FullName); + } + + protected virtual Exception Fail (FieldDefinition field, Exception e) + { + return ErrorHelper.CreateError (ErrorCode | 2, e, Errors.MX_ExceptionalSubSteps, Name, field?.FullName); + } + + protected virtual Exception Fail (MethodDefinition method, Exception e) + { + return ErrorHelper.CreateError (ErrorCode | 3, e, Errors.MX_ExceptionalSubSteps, Name, method?.FullName); + } + protected virtual void Report (Exception e) + { + throw e; + } + + // abstracts + + protected abstract string Name { get; } + + protected abstract int ErrorCode { get; } + } +} diff --git a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs new file mode 100644 index 000000000000..8ad49282bb07 --- /dev/null +++ b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; + +using Mono.Cecil; + +using Mono.Linker; +using Mono.Linker.Steps; +using Mono.Tuner; + +using Xamarin.Bundler; + +namespace Xamarin.Linker.Steps { + + public class PreserveBlockCodeHandler : ConfigurationAwareMarkHandler { + + protected override string Name { get; } = "Preserve Block Code"; + protected override int ErrorCode { get; } = 2240; + + public override void Initialize (LinkContext context, MarkContext markContext) + { + this.context = context; + markContext.RegisterMarkTypeAction (ProcessType); + } + + protected override void Process (TypeDefinition type) + { + /* For the following class: + + static internal class SDInnerBlock { + // this field is not preserved by other means, but it must not be linked away + static internal readonly DInnerBlock Handler = Invoke; + + [MonoPInvokeCallback (typeof (DInnerBlock))] + static internal void Invoke (IntPtr block, int magic_number) + { + } + } + + We need to make sure the linker doesn't remove the Handler field + and the Invoke method. + */ + + // First make sure we got the right class + // The type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field. + if (!type.IsAbstract || !type.IsSealed || !type.IsNested) + return; + if (type.Fields.Count != 1) + return; + + // The type is also nested inside ObjCRuntime.Trampolines class) + var nestingType = type.DeclaringType; + if (!nestingType.Is ("ObjCRuntime", "Trampolines")) + return; + + // The class has a readonly field named 'Handler' + var field = type.Fields.Single (); + if (!field.IsInitOnly) + return; + if (field.Name != "Handler") + return; + + // The class has a parameterless 'Invoke' method with a 'MonoPInvokeCallback' attribute + if (!type.HasMethods) + return; + var method = type.Methods.SingleOrDefault (v => { + if (v.Name != "Invoke") + return false; + if (v.Parameters.Count == 0) + return false; + if (!v.HasCustomAttributes) + return false; + if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute")) + return false; + return true; + }); + + if (method == null) + return; + + // The type was used, so preserve the method and field + context.Annotations.Mark (method); + context.Annotations.Mark (field); + } + } +} diff --git a/tools/dotnet-linker/Steps/PreserveBlockCodeSubStep.cs b/tools/dotnet-linker/Steps/PreserveBlockCodeSubStep.cs deleted file mode 100644 index 4af7179d617e..000000000000 --- a/tools/dotnet-linker/Steps/PreserveBlockCodeSubStep.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Linq; - -using Mono.Cecil; - -using Mono.Linker.Steps; -using Mono.Tuner; - -using Xamarin.Bundler; - -namespace Xamarin.Linker.Steps { - public class PreserveBlockCodeSubStep : ConfigurationAwareSubStep { - MethodDefinition ctor_string_def; - MethodReference ctor_string_ref; - - protected override string Name { get; } = "Preserve Block Code"; - protected override int ErrorCode { get; } = 2240; - - public override SubStepTargets Targets { - get { - return SubStepTargets.Assembly | - SubStepTargets.Field | - SubStepTargets.Type; - } - } - - MethodReference GetConstructorReference (AssemblyDefinition assembly) - { - if (ctor_string_def == null) { - // Find the method definition for the constructor we want to use - foreach (var asm in Configuration.Assemblies) { - var dependencyAttribute = asm.MainModule.GetType ("System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute"); - if (dependencyAttribute == null) - continue; - - foreach (var method in dependencyAttribute.Methods) { - if (!method.HasParameters) - continue; - - if (method.Parameters.Count == 1 && method.Parameters [0].ParameterType.Is ("System", "String")) { - ctor_string_def = method; - break; - } - } - - break; - } - - if (ctor_string_def == null) - throw ErrorHelper.CreateError (99, Errors.MX0099, "Could not find the constructor 'System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute..ctor(System.String)'"); - } - - // Import the constructor into the current assembly if it hasn't already been imported - ctor_string_ref ??= assembly.MainModule.ImportReference (ctor_string_def); - - return ctor_string_ref; - } - - protected override void Process (AssemblyDefinition assembly) - { - // Clear out the method reference we have, so that we import the method definition again - ctor_string_ref = null; - } - - protected override void Process (FieldDefinition field) - { - PreserveBlockField (field); - } - - void PreserveBlockField (FieldDefinition field) - { - /* For the following class: - - static internal class SDInnerBlock { - // this field is not preserved by other means, but it must not be linked away - static internal readonly DInnerBlock Handler = Invoke; - - [MonoPInvokeCallback (typeof (DInnerBlock))] - static internal void Invoke (IntPtr block, int magic_number) - { - } - } - - We need to make sure the linker doesn't remove the Handler field - and the Invoke method. Unfortunately there's no programmatic way - to preserve a field dependent upon the preservation of the - containing type, so we have to inject a DynamicDependency - attribute. And since we can't add a DynamicDependency attribute on - the type itself, we add it to the Invoke method. We also need to - preserve the Invoke method (which is done programmatically). Our - generator generates the required attributes, but since we have to - work with existing assemblies, we detect the scenario here as well - and inject the attributes manually if they're not already there. - - */ - - // First make sure we got the right field - // The containing type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field. - var td = field.DeclaringType; - if (!td.IsAbstract || !td.IsSealed || !td.IsNested) - return; - if (td.Fields.Count != 1) - return; - - // The containing type is also nested inside ObjCRuntime.Trampolines class) - var nestingType = td.DeclaringType; - if (!nestingType.Is ("ObjCRuntime", "Trampolines")) - return; - - // The field itself is a readonly field named 'Handler' - if (!field.IsInitOnly) - return; - if (field.Name != "Handler") - return; - - // One problem is that we can't add the DynamicDependency attribute to the type, nor the field itself, - // so we add it to the Invoke method in the same type. - if (!td.HasMethods) - return; - - var method = td.Methods.SingleOrDefault (v => { - if (v.Name != "Invoke") - return false; - if (v.Parameters.Count == 0) - return false; - if (!v.HasCustomAttributes) - return false; - if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute")) - return false; - return true; - }); - - if (method == null) - return; - - // We need to preserve the method, if the type is used (unless it's already preserved) - if (!method.CustomAttributes.Any (v => v.AttributeType.Name == "PreserveAttribute")) - Annotations.AddPreservedMethod (method.DeclaringType, method); - - // Does the method already have a DynamicDependency attribute? If so, no need to add another one - if (method.CustomAttributes.Any (v => v.AttributeType.Is ("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute"))) - return; - - // Create and add the DynamicDependency attribute to the method - var ctor = GetConstructorReference (field.DeclaringType.Module.Assembly); - var attrib = new CustomAttribute (ctor); - attrib.ConstructorArguments.Add (new CustomAttributeArgument (ctor.Parameters [0].ParameterType, "Handler")); - method.CustomAttributes.Add (attrib); - } - } -} diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index ecff53c7c8dd..d736c53d8a80 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -152,8 +152,8 @@ external\tools\linker\MonoTouch.Tuner\ListExportedSymbols.cs - - external\tools\linker\MonoTouch.Tuner\PreserveSmartEnumConversionsSubStep.cs + + external\tools\linker\MonoTouch.Tuner\PreserveSmartEnumConversions.cs external\tools\linker\MobileExtensions.cs diff --git a/tools/linker/ApplyPreserveAttribute.cs b/tools/linker/ApplyPreserveAttribute.cs index 8dcb942bcfd5..cce80d3cad7b 100644 --- a/tools/linker/ApplyPreserveAttribute.cs +++ b/tools/linker/ApplyPreserveAttribute.cs @@ -13,54 +13,68 @@ namespace Xamarin.Linker.Steps { public class ApplyPreserveAttribute : ApplyPreserveAttributeBase { +#if !NET HashSet preserve_synonyms; +#endif public override bool IsActiveFor (AssemblyDefinition assembly) { return Annotations.GetAction (assembly) == AssemblyAction.Link; } +#if NET + public override void ProcessAssembly (AssemblyDefinition assembly) + { + ProcessAssemblyAttributes (assembly); + } +#else public override void Initialize (LinkContext context) { base.Initialize (context); // we cannot override ProcessAssembly as some decisions needs to be done before applying the [Preserve] // synonyms + foreach (var assembly in context.GetAssemblies ()) + ProcessAssemblyAttributes (assembly); + } +#endif + + void ProcessAssemblyAttributes (AssemblyDefinition assembly) + { + if (!assembly.HasCustomAttributes) + return; - foreach (var assembly in context.GetAssemblies ()) { - if (!assembly.HasCustomAttributes) + foreach (var attribute in assembly.CustomAttributes) { + if (!attribute.Constructor.DeclaringType.Is (Namespaces.Foundation, "PreserveAttribute")) continue; - foreach (var attribute in assembly.CustomAttributes) { - if (!attribute.Constructor.DeclaringType.Is (Namespaces.Foundation, "PreserveAttribute")) - continue; - - if (!attribute.HasConstructorArguments) - continue; - var tr = (attribute.ConstructorArguments [0].Value as TypeReference); - if (tr == null) - continue; - - // we do not call `this.ProcessType` since - // (a) we're potentially processing a different assembly and `is_active` represent the current one - // (b) it will try to fetch the [Preserve] attribute on the type (and it's not there) as `base` would - var type = tr.Resolve (); - Annotations.Mark (type); - if (attribute.HasFields) { - foreach (var named_argument in attribute.Fields) { - if (named_argument.Name == "AllMembers" && (bool)named_argument.Argument.Value) - Annotations.SetPreserve (type, TypePreserve.All); - } - } + if (!attribute.HasConstructorArguments) + continue; + var tr = (attribute.ConstructorArguments [0].Value as TypeReference); + if (tr == null) + continue; - // if the type is a custom attribute then it means we want to preserve what's decorated - // with this attribute (not just the attribute alone) - if (type.Inherits ("System", "Attribute")) { - if (preserve_synonyms == null) - preserve_synonyms = new HashSet (); - preserve_synonyms.Add (type); + // we do not call `this.ProcessType` since + // (a) we're potentially processing a different assembly and `is_active` represent the current one + // (b) it will try to fetch the [Preserve] attribute on the type (and it's not there) as `base` would + var type = tr.Resolve (); + Annotations.Mark (type); + if (attribute.HasFields) { + foreach (var named_argument in attribute.Fields) { + if (named_argument.Name == "AllMembers" && (bool)named_argument.Argument.Value) + Annotations.SetPreserve (type, TypePreserve.All); } } + +#if !NET + // if the type is a custom attribute then it means we want to preserve what's decorated + // with this attribute (not just the attribute alone) + if (type.Inherits ("System", "Attribute")) { + if (preserve_synonyms == null) + preserve_synonyms = new HashSet (); + preserve_synonyms.Add (type); + } +#endif } } @@ -74,8 +88,12 @@ protected override bool IsPreservedAttribute (ICustomAttributeProvider provider, removeAttribute = true; return true; } +#if NET + return false; +#else // we need to resolve (as many reference instances can exists) return ((preserve_synonyms != null) && preserve_synonyms.Contains (type.Resolve ())); +#endif } } } diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 93754c2b00e3..729d5270f383 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -1,6 +1,7 @@ // Copyright 2012-2013, 2016 Xamarin Inc. All rights reserved. using System; +using System.Collections.Generic; using ObjCRuntime; using Mono.Cecil; using Mono.Cecil.Cil; @@ -18,16 +19,31 @@ namespace Xamarin.Linker { +#if NET + public class OptimizeGeneratedCodeHandler : ExceptionalMarkHandler { +#else public class OptimizeGeneratedCodeSubStep : ExceptionalSubStep { // If the type currently being processed is a direct binding or not. // A null value means it's not a constant value, and can't be inlined. bool? isdirectbinding_constant; +#endif protected override string Name { get; } = "Binding Optimizer"; protected override int ErrorCode { get; } = 2020; +#if NET + Dictionary _hasOptimizableCode; + Dictionary HasOptimizableCode { + get { + if (_hasOptimizableCode == null) + _hasOptimizableCode = new Dictionary (); + return _hasOptimizableCode; + } + } +#else protected bool HasOptimizableCode { get; private set; } protected bool IsExtensionType { get; private set; } +#endif public bool IsDualBuild { get { return LinkContext.App.IsDualBuild; } @@ -52,9 +68,14 @@ protected Optimizations Optimizations { bool? is_arm64_calling_convention; +#if NET + public override void Initialize (LinkContext context, MarkContext markContext) + { +#else public override void Initialize (LinkContext context) { base.Initialize (context); +#endif if (Optimizations.InlineIsARM64CallingConvention == true) { var target = LinkContext.Target; @@ -84,18 +105,42 @@ public override void Initialize (LinkContext context) is_arm64_calling_convention = false; } } +#if NET + this.context = context; + markContext.RegisterMarkMethodAction (ProcessMethod); +#endif } + +#if !NET public override SubStepTargets Targets { get { return SubStepTargets.Assembly | SubStepTargets.Type | SubStepTargets.Method; } } - +#endif + +#if NET + bool IsActiveFor (AssemblyDefinition assembly, out bool hasOptimizableCode) +#else public override bool IsActiveFor (AssemblyDefinition assembly) +#endif { +#if NET + hasOptimizableCode = false; + if (HasOptimizableCode.TryGetValue (assembly, out bool? optimizable)) { + if (optimizable == true) + hasOptimizableCode = true; + return optimizable != null; + } +#else + bool hasOptimizableCode = false; +#endif // we're sure "pure" SDK assemblies don't use XamMac.dll (i.e. they are the Product assemblies) if (Profile.IsSdkAssembly (assembly)) { #if DEBUG Console.WriteLine ("Assembly {0} : skipped (SDK)", assembly); +#endif +#if NET + HasOptimizableCode.Add (assembly, null); #endif return false; } @@ -105,15 +150,17 @@ public override bool IsActiveFor (AssemblyDefinition assembly) if (action != AssemblyAction.Link) { #if DEBUG Console.WriteLine ("Assembly {0} : skipped ({1})", assembly, action); +#endif +#if NET + HasOptimizableCode.Add (assembly, null); #endif return false; } // if the assembly does not refer to [CompilerGeneratedAttribute] then there's not much we can do - HasOptimizableCode = false; foreach (TypeReference tr in assembly.MainModule.GetTypeReferences ()) { if (tr.Is (Namespaces.ObjCRuntime, "BindingImplAttribute")) { - HasOptimizableCode = true; + hasOptimizableCode = true; break; } @@ -121,29 +168,34 @@ public override bool IsActiveFor (AssemblyDefinition assembly) #if DEBUG Console.WriteLine ("Assembly {0} : processing", assembly); #endif - HasOptimizableCode = true; + hasOptimizableCode = true; break; } } #if DEBUG - if (!HasOptimizableCode) + if (!hasOptimizableCode) Console.WriteLine ("Assembly {0} : no [CompilerGeneratedAttribute] nor [BindingImplAttribute] present (applying basic optimizations)", assembly); #endif // we always apply the step +#if NET + HasOptimizableCode.Add (assembly, hasOptimizableCode); +#else + HasOptimizableCode = hasOptimizableCode; +#endif return true; } +#if !NET protected override void Process (TypeDefinition type) { if (!HasOptimizableCode) return; - isdirectbinding_constant = type.IsNSObject (LinkContext) ? type.GetIsDirectBindingConstant (LinkContext) : null; + isdirectbinding_constant = IsDirectBindingConstant (type); - // if 'type' inherits from NSObject inside an assembly that has [GeneratedCode] - // or for static types used for optional members (using extensions methods), they can be optimized too - IsExtensionType = type.IsSealed && type.IsAbstract && type.Name.EndsWith ("_Extensions", StringComparison.Ordinal); + IsExtensionType = GetIsExtensionType (type); } +#endif // [GeneratedCode] is not enough - e.g. it's used for anonymous delegates even if the // code itself is not tool/compiler generated @@ -629,14 +681,32 @@ protected override void Process (AssemblyDefinition assembly) base.Process (assembly); } + bool GetIsExtensionType (TypeDefinition type) + { + // if 'type' inherits from NSObject inside an assembly that has [GeneratedCode] + // or for static types used for optional members (using extensions methods), they can be optimized too + return type.IsSealed && type.IsAbstract && type.Name.EndsWith ("_Extensions", StringComparison.Ordinal); + } + protected override void Process (MethodDefinition method) { +#if NET + if (!IsActiveFor (method.DeclaringType.Module.Assembly, out bool hasOptimizableCode)) + return; +#endif + if (!method.HasBody) return; if (method.IsBindingImplOptimizableCode (LinkContext)) { // We optimize all methods that have the [BindingImpl (BindingImplAttributes.Optimizable)] attribute. - } else if (!Driver.IsXAMCORE_4_0 && (method.IsGeneratedCode (LinkContext) && (IsExtensionType || IsExport (method)))) { + } else if (!Driver.IsXAMCORE_4_0 && (method.IsGeneratedCode (LinkContext) && ( +#if NET + GetIsExtensionType (method.DeclaringType) +#else + IsExtensionType +#endif + || IsExport (method)))) { // We optimize methods that have the [GeneratedCodeAttribute] and is either an extension type or an exported method } else { // but it would be too risky to apply on user-generated code @@ -762,6 +832,11 @@ void ProcessIntPtrSize (MethodDefinition caller, Instruction ins) ins.Operand = null; } + bool? IsDirectBindingConstant (TypeDefinition type) + { + return type.IsNSObject (LinkContext) ? type.GetIsDirectBindingConstant (LinkContext) : null; + } + void ProcessIsDirectBinding (MethodDefinition caller, Instruction ins) { const string operation = "inline IsDirectBinding"; @@ -769,6 +844,9 @@ void ProcessIsDirectBinding (MethodDefinition caller, Instruction ins) if (Optimizations.InlineIsDirectBinding != true) return; +#if NET + bool? isdirectbinding_constant = IsDirectBindingConstant (caller.DeclaringType); +#endif // If we don't know the constant isdirectbinding value, then we can't inline anything if (!isdirectbinding_constant.HasValue) return; diff --git a/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs b/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs similarity index 79% rename from tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs rename to tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs index 248ed45418c9..3d6ff6f8a612 100644 --- a/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs +++ b/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs @@ -17,19 +17,35 @@ namespace Xamarin.Linker.Steps { +#if NET + public class PreserveSmartEnumConversionsHandler : ExceptionalMarkHandler +#else public class PreserveSmartEnumConversionsSubStep : ExceptionalSubStep +#endif { Dictionary> cache; protected override string Name { get; } = "Smart Enum Conversion Preserver"; protected override int ErrorCode { get; } = 2200; +#if NET + public override void Initialize (LinkContext context, MarkContext markContext) + { + this.context = context; + markContext.RegisterMarkMethodAction (ProcessMethod); + } +#else public override SubStepTargets Targets { get { return SubStepTargets.Method | SubStepTargets.Property; } } +#endif +#if NET + bool IsActiveFor (AssemblyDefinition assembly) +#else public override bool IsActiveFor (AssemblyDefinition assembly) +#endif { if (Profile.IsProductAssembly (assembly)) return true; @@ -43,6 +59,13 @@ public override bool IsActiveFor (AssemblyDefinition assembly) return false; } +#if NET + void Mark (Tuple pair) + { + context.Annotations.Mark (pair.Item1); + context.Annotations.Mark (pair.Item2); + } +#else void Preserve (Tuple pair, MethodDefinition conditionA, MethodDefinition conditionB = null) { if (conditionA != null) { @@ -54,8 +77,13 @@ void Preserve (Tuple pair, MethodDefinition context.Annotations.AddPreservedMethod (conditionB, pair.Item2); } } +#endif +#if NET + void ProcessAttributeProvider (ICustomAttributeProvider provider) +#else void ProcessAttributeProvider (ICustomAttributeProvider provider, MethodDefinition conditionA, MethodDefinition conditionB = null) +#endif { if (provider?.HasCustomAttributes != true) return; @@ -84,7 +112,11 @@ void ProcessAttributeProvider (ICustomAttributeProvider provider, MethodDefiniti Tuple pair; if (cache != null && cache.TryGetValue (managedEnumType, out pair)) { +#if NET + // The pair was already marked if it was cached. +#else Preserve (pair, conditionA, conditionB); +#endif continue; } @@ -142,20 +174,48 @@ void ProcessAttributeProvider (ICustomAttributeProvider provider, MethodDefiniti if (cache == null) cache = new Dictionary> (); cache.Add (managedEnumType, pair); +#if NET + Mark (pair); +#else Preserve (pair, conditionA, conditionB); +#endif } } protected override void Process (MethodDefinition method) { +#if NET + static bool IsPropertyMethod (MethodDefinition method) + { + return (method.SemanticsAttributes & MethodSemanticsAttributes.Getter) != 0 || + (method.SemanticsAttributes & MethodSemanticsAttributes.Setter) != 0; + } + + ProcessAttributeProvider (method); + ProcessAttributeProvider (method.MethodReturnType); + + if (method.HasParameters) { + foreach (var p in method.Parameters) + ProcessAttributeProvider (p); + } + if (IsPropertyMethod (method)) { + foreach (PropertyDefinition property in method.DeclaringType.Properties) + if (property.GetMethod == method || property.SetMethod == method) { + ProcessAttributeProvider (property); + break; + } + } +#else ProcessAttributeProvider (method, method); ProcessAttributeProvider (method.MethodReturnType, method); if (method.HasParameters) { foreach (var p in method.Parameters) ProcessAttributeProvider (p, method); } +#endif } +#if !NET protected override void Process (PropertyDefinition property) { ProcessAttributeProvider (property, property.GetMethod, property.SetMethod); @@ -164,5 +224,6 @@ protected override void Process (PropertyDefinition property) if (property.SetMethod != null) Process (property.SetMethod); } +#endif } } diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index a8a4867caa84..f21964105186 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -319,8 +319,8 @@ tools\linker\MonoTouch.Tuner\ProcessExportedFields.cs - - tools\linker\MonoTouch.Tuner\PreserveSmartEnumConversionsSubStep.cs + + tools\linker\MonoTouch.Tuner\PreserveSmartEnumConversions.cs src\build\mac\Constants.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index e8c2e55ec97d..944b6e7406bd 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -420,8 +420,8 @@ tools\linker\MonoTouch.Tuner\InlinerSubStep.cs - - tools\linker\MonoTouch.Tuner\PreserveSmartEnumConversionsSubStep.cs + + tools\linker\MonoTouch.Tuner\PreserveSmartEnumConversions.cs tools\common\BuildTasks.cs From ae665f7654b94ba451eb721bedbf1c6d18687b35 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 29 Apr 2021 10:54:12 -0700 Subject: [PATCH 2/8] PR feedback - Fix indentation - Add Initialize(LinkContext) to ExceptionalMarkHandler - Remove unnecessary ifdef - Use IsSetter/IsGetter - Use [0] instead of Single() - Avoid allocating empty collections --- tools/dotnet-linker/ApplyPreserveAttributeBase.cs | 5 +---- tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs | 13 +++++++++---- .../dotnet-linker/Steps/PreserveBlockCodeHandler.cs | 8 ++++---- tools/linker/CoreOptimizeGeneratedCode.cs | 4 +--- .../MonoTouch.Tuner/PreserveSmartEnumConversions.cs | 5 ++--- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs index cb54e5ccb83b..2c360949dff1 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs @@ -24,10 +24,7 @@ public override SubStepTargets Targets { | SubStepTargets.Method | SubStepTargets.Property | SubStepTargets.Event -#if NET - | SubStepTargets.Assembly -#endif - ; + | SubStepTargets.Assembly; } } diff --git a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs index 8b387020fe15..d1913c73dca8 100644 --- a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs +++ b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs @@ -12,17 +12,22 @@ namespace Xamarin.Linker { - // Similar to ExceptionalSubStep, but this only runs for marked members - // that were registered for handling by the subclass. + // Similar to ExceptionalSubStep, but this only runs for marked members + // that were registered for handling by the subclass. public abstract class ExceptionalMarkHandler : IMarkHandler { public abstract void Initialize (LinkContext context, MarkContext markContext); + public virtual void Initialize (LinkContext context) + { + this.context = context; + } + protected DerivedLinkContext LinkContext => Configuration.DerivedLinkContext; - protected LinkContext context { get; set; } + protected LinkContext context { get; private set; } - protected AnnotationStore Annotations => context.Annotations; + protected AnnotationStore Annotations => context.Annotations; protected LinkerConfiguration Configuration => LinkerConfiguration.GetInstance (context); protected Profile Profile => Configuration.Profile; diff --git a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs index 8ad49282bb07..ffe8bc50a202 100644 --- a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs +++ b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs @@ -18,7 +18,7 @@ public class PreserveBlockCodeHandler : ConfigurationAwareMarkHandler { public override void Initialize (LinkContext context, MarkContext markContext) { - this.context = context; + base.Initialize (context); markContext.RegisterMarkTypeAction (ProcessType); } @@ -42,7 +42,7 @@ and the Invoke method. // First make sure we got the right class // The type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field. - if (!type.IsAbstract || !type.IsSealed || !type.IsNested) + if (!type.HasFields || !type.IsAbstract || !type.IsSealed || !type.IsNested) return; if (type.Fields.Count != 1) return; @@ -53,7 +53,7 @@ and the Invoke method. return; // The class has a readonly field named 'Handler' - var field = type.Fields.Single (); + var field = type.Fields [0]; if (!field.IsInitOnly) return; if (field.Name != "Handler") @@ -65,7 +65,7 @@ and the Invoke method. var method = type.Methods.SingleOrDefault (v => { if (v.Name != "Invoke") return false; - if (v.Parameters.Count == 0) + if (!v.HasParameters) return false; if (!v.HasCustomAttributes) return false; diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 729d5270f383..1b54bf95e390 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -70,12 +70,11 @@ protected Optimizations Optimizations { #if NET public override void Initialize (LinkContext context, MarkContext markContext) - { #else public override void Initialize (LinkContext context) +#endif { base.Initialize (context); -#endif if (Optimizations.InlineIsARM64CallingConvention == true) { var target = LinkContext.Target; @@ -106,7 +105,6 @@ public override void Initialize (LinkContext context) } } #if NET - this.context = context; markContext.RegisterMarkMethodAction (ProcessMethod); #endif } diff --git a/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs b/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs index 3d6ff6f8a612..46890a081b09 100644 --- a/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs +++ b/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversions.cs @@ -30,7 +30,7 @@ public class PreserveSmartEnumConversionsSubStep : ExceptionalSubStep #if NET public override void Initialize (LinkContext context, MarkContext markContext) { - this.context = context; + base.Initialize (context); markContext.RegisterMarkMethodAction (ProcessMethod); } #else @@ -187,8 +187,7 @@ protected override void Process (MethodDefinition method) #if NET static bool IsPropertyMethod (MethodDefinition method) { - return (method.SemanticsAttributes & MethodSemanticsAttributes.Getter) != 0 || - (method.SemanticsAttributes & MethodSemanticsAttributes.Setter) != 0; + return method.IsGetter || method.IsSetter; } ProcessAttributeProvider (method); From 11733b95613086bb3d511577bfcd0e23648c309a Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 5 May 2021 12:00:09 -0700 Subject: [PATCH 3/8] Note override issue --- tools/linker/MarkNSObjects.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/linker/MarkNSObjects.cs b/tools/linker/MarkNSObjects.cs index 22cefe45025d..f2d7d0591f33 100644 --- a/tools/linker/MarkNSObjects.cs +++ b/tools/linker/MarkNSObjects.cs @@ -85,6 +85,7 @@ void PreserveExportedMethods (TypeDefinition type) continue; // not optimal if "Link all" is used as the override might be removed later + // this may miss some overrides with the .NET6 linker (https://github.com/xamarin/xamarin-macios/issues/11449) if (!IsOverridenInUserCode (method)) continue; From 7362b743dd5b5aadf3eaaa39cbfe661bdb900cc7 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 5 May 2021 14:51:42 -0700 Subject: [PATCH 4/8] Clean up comments --- tools/dotnet-linker/SetupStep.cs | 7 ++----- tools/linker/ApplyPreserveAttribute.cs | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tools/dotnet-linker/SetupStep.cs b/tools/dotnet-linker/SetupStep.cs index f224b76bfbb8..eb031d8cab75 100644 --- a/tools/dotnet-linker/SetupStep.cs +++ b/tools/dotnet-linker/SetupStep.cs @@ -85,16 +85,13 @@ protected override void TryProcess () // though we know that sdk assemblies will never have Preserve attributes, user assemblies may have // [assembly: LinkSafe] attributes, which means we treat them as sdk assemblies and those may have // Preserve attributes. - // TODO: LinkSafeAttribute doesn't appear to be handled anywhere. Is this attribute still supported? - // ApplyPreserveAttribute no longer runs on all assemblies. [assembly: Preserve (typeof (SomeAttribute))] will - // no longer give SomeAttribute "Preserve" semantics. MarkHandlers.Add (new DotNetMarkAssemblySubStepDispatcher (new ApplyPreserveAttribute ())); MarkHandlers.Add (new OptimizeGeneratedCodeHandler ()); - // MarkNSObjects will run for all marked assemblies. MarkHandlers.Add (new DotNetMarkAssemblySubStepDispatcher (new MarkNSObjects ())); MarkHandlers.Add (new PreserveSmartEnumConversionsHandler ()); - // TODO: these steps should probably run after mark. + // This step could be run after Mark to avoid tracking all members: + // https://github.com/xamarin/xamarin-macios/issues/11447 pre_mark_substeps.Add (new CollectUnmarkedMembersSubStep ()); pre_mark_substeps.Add (new StoreAttributesStep ()); diff --git a/tools/linker/ApplyPreserveAttribute.cs b/tools/linker/ApplyPreserveAttribute.cs index cce80d3cad7b..ba65906968a2 100644 --- a/tools/linker/ApplyPreserveAttribute.cs +++ b/tools/linker/ApplyPreserveAttribute.cs @@ -66,6 +66,8 @@ void ProcessAssemblyAttributes (AssemblyDefinition assembly) } } + // In .NET6, ApplyPreserveAttribute no longer runs on all assemblies. + // [assembly: Preserve (typeof (SomeAttribute))] no longer gives SomeAttribute "Preserve" semantics. #if !NET // if the type is a custom attribute then it means we want to preserve what's decorated // with this attribute (not just the attribute alone) From 224c3d4a86cb214fe86dd7df2c21852d536aa52b Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 7 May 2021 16:33:31 -0400 Subject: [PATCH 5/8] Move `DynamicRegistrationSupported` change earlier, along with the detection code. This solve the issue that `ILLink` does a similar job _before_ we have the chance to disable the dynamic registrar. --- tools/linker/CoreOptimizeGeneratedCode.cs | 13 +++------ tools/linker/RegistrarRemovalTrackingStep.cs | 29 ++++++++++++++++---- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 1b54bf95e390..260fa2d0fbad 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -711,15 +711,6 @@ protected override void Process (MethodDefinition method) return; } - if (!LinkContext.App.DynamicRegistrationSupported && method.Name == "get_DynamicRegistrationSupported" && method.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) { - // Rewrite to return 'false' - var instr = method.Body.Instructions; - instr.Clear (); - instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); - instr.Add (Instruction.Create (OpCodes.Ret)); - return; // nothing else to do here. - } - if (Optimizations.InlineIsARM64CallingConvention == true && is_arm64_calling_convention.HasValue && method.Name == "GetIsARM64CallingConvention" && method.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) { // Rewrite to return the constant value var instr = method.Body.Instructions; @@ -759,9 +750,13 @@ protected virtual int ProcessCalls (MethodDefinition caller, Instruction ins) case "get_IsDirectBinding": ProcessIsDirectBinding (caller, ins); break; +#if !NET + // ILLink does this optimization since the property returns a constant `true` (built time) + // or `false` - if `RegistrarRemovalTrackingStep` decide it's possible to do without case "get_DynamicRegistrationSupported": ProcessIsDynamicSupported (caller, ins); break; +#endif case "SetupBlock": case "SetupBlockUnsafe": return ProcessSetupBlock (caller, ins); diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index 55ea447e3a36..9a72147851b5 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -1,12 +1,13 @@ using System; using System.Linq; using Mono.Cecil; +using Mono.Cecil.Cil; using Mono.Linker; -using Mono.Linker.Steps; using Xamarin.Bundler; using Xamarin.Linker; #if !NET +using Mono.Linker.Steps; using Mono.Tuner; using Xamarin.Tuner; #endif @@ -25,7 +26,7 @@ public class RegistrarRemovalTrackingStep : ConfigurationAwareStep { Optimizations Optimizations => Configuration.Application.Optimizations; - string PlatformAssembly => Configuration.PlatformAssembly; + string PlatformAssemblyName => Configuration.PlatformAssembly; protected override void TryProcessAssembly (AssemblyDefinition assembly) { @@ -36,7 +37,7 @@ public class RegistrarRemovalTrackingStep : BaseStep { Optimizations Optimizations => ((DerivedLinkContext) Context).App.Optimizations; - string PlatformAssembly => ((MobileProfile) Profile.Current).ProductAssembly; + string PlatformAssemblyName => ((MobileProfile) Profile.Current).ProductAssembly; int WarnCode => 2107; // for compatibility @@ -46,6 +47,7 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) base.ProcessAssembly (assembly); } #endif + AssemblyDefinition PlatformAssembly; bool dynamic_registration_support_required; @@ -63,8 +65,11 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) return false; // The product assembly itself is safe as long as it's linked - if (Profile.IsProductAssembly (assembly)) - return Annotations.GetAction (assembly) != AssemblyAction.Link; + if (Profile.IsProductAssembly (assembly)) { + if (Annotations.GetAction (assembly) != AssemblyAction.Link) + return false; + PlatformAssembly = assembly; + } // Can't touch the forbidden fruit in the product assembly unless there's a reference to it var hasProductReference = false; @@ -78,7 +83,7 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) return false; // Check if the assembly references any methods that require the dynamic registrar - var productAssemblyName = PlatformAssembly; + var productAssemblyName = PlatformAssemblyName; var requires = false; foreach (var mr in assembly.MainModule.GetMemberReferences ()) { if (mr.DeclaringType == null || string.IsNullOrEmpty (mr.DeclaringType.Namespace)) @@ -170,6 +175,18 @@ protected override void EndProcess () // If dynamic registration is not required, and removal of the dynamic registrar hasn't already // been disabled, then we can remove it! Optimizations.RemoveDynamicRegistrar = !dynamic_registration_support_required; + // ILLink will optimize `Runtime.Initialize` based on `DynamicRegistrationSupported` returning a constant (`true`) + // and this will runs before we have the chance to set it to `false` in `CoreOptimizedGeneratedCode` so we instead + // do the change here so the linker can do this without further ado + // note: it does not matter for _legacy_ so we apply the change (to earlier) to minimize the difference between them + if (PlatformAssembly != null) { + var method = PlatformAssembly.MainModule.GetType ("ObjCRuntime.Runtime").Methods.First ((n) => n.Name == "get_DynamicRegistrationSupported"); + // Rewrite to return 'false' + var instr = method.Body.Instructions; + instr.Clear (); + instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + instr.Add (Instruction.Create (OpCodes.Ret)); + } Driver.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); #if MTOUCH var app = (Context as DerivedLinkContext).App; From f15bbc1ea95ae4b446c1b868e278388f1deed53c Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Mon, 10 May 2021 11:35:27 -0400 Subject: [PATCH 6/8] ILLink does not support considering other attributes as `[Preserve]` when it is itself preserved at the assembly-level. This ignored test is checking that feature so it cannot be enabled for `NET` Added to known breaking changes https://github.com/xamarin/xamarin-macios/issues/8900 --- tests/linker/ios/link all/PreserveTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/linker/ios/link all/PreserveTest.cs b/tests/linker/ios/link all/PreserveTest.cs index caec2c0cb9f7..262a48673267 100644 --- a/tests/linker/ios/link all/PreserveTest.cs +++ b/tests/linker/ios/link all/PreserveTest.cs @@ -77,6 +77,9 @@ public void PreserveTypeWithoutMembers () } [Test] +#if NET + [Ignore ("This feature is not supported by dotnet's ILLink -> https://github.com/xamarin/xamarin-macios/issues/8900")] +#endif public void PreserveTypeWithCustomAttribute () { var t = Type.GetType ("LinkAll.Attributes.MemberWithCustomAttribute" + WorkAroundLinkerHeuristics); From 1d9e716f556818ae80e7dd013fb2786d17a36441 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Mon, 10 May 2021 14:48:40 -0400 Subject: [PATCH 7/8] Fix removal of the dynamic registrar on legacy --- tools/linker/CoreOptimizeGeneratedCode.cs | 2 ++ tools/linker/RegistrarRemovalTrackingStep.cs | 22 ++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 260fa2d0fbad..608d5adb77f7 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -864,6 +864,7 @@ void ProcessIsDirectBinding (MethodDefinition caller, Instruction ins) ins.Operand = null; } +#if !NET void ProcessIsDynamicSupported (MethodDefinition caller, Instruction ins) { const string operation = "inline Runtime.DynamicRegistrationSupported"; @@ -883,6 +884,7 @@ void ProcessIsDynamicSupported (MethodDefinition caller, Instruction ins) ins.OpCode = LinkContext.App.DynamicRegistrationSupported ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; ins.Operand = null; } +#endif int ProcessSetupBlock (MethodDefinition caller, Instruction ins) { diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index 9a72147851b5..7cd29d9702ac 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -175,6 +175,11 @@ protected override void EndProcess () // If dynamic registration is not required, and removal of the dynamic registrar hasn't already // been disabled, then we can remove it! Optimizations.RemoveDynamicRegistrar = !dynamic_registration_support_required; + } + + Driver.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); + + if (Optimizations.RemoveDynamicRegistrar.Value) { // ILLink will optimize `Runtime.Initialize` based on `DynamicRegistrationSupported` returning a constant (`true`) // and this will runs before we have the chance to set it to `false` in `CoreOptimizedGeneratedCode` so we instead // do the change here so the linker can do this without further ado @@ -187,18 +192,17 @@ protected override void EndProcess () instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); instr.Add (Instruction.Create (OpCodes.Ret)); } - Driver.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); + } #if MTOUCH - var app = (Context as DerivedLinkContext).App; - if (app.IsCodeShared) { - foreach (var appex in app.AppExtensions) { - if (!appex.IsCodeShared) - continue; - appex.Optimizations.RemoveDynamicRegistrar = app.Optimizations.RemoveDynamicRegistrar; - } + var app = (Context as DerivedLinkContext).App; + if (app.IsCodeShared) { + foreach (var appex in app.AppExtensions) { + if (!appex.IsCodeShared) + continue; + appex.Optimizations.RemoveDynamicRegistrar = app.Optimizations.RemoveDynamicRegistrar; } -#endif } +#endif } } } From 046e01bbc6f3c28095ed98c5bdc9e0dea9fd5f41 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 10 May 2021 14:57:46 -0700 Subject: [PATCH 8/8] Fix IntPtr size inlining --- tools/linker/CoreOptimizeGeneratedCode.cs | 51 +++++++++++++++++++---- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 608d5adb77f7..d244b8730fbb 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -40,9 +40,21 @@ public class OptimizeGeneratedCodeSubStep : ExceptionalSubStep { return _hasOptimizableCode; } } + + Dictionary _inlineIntPtrSize; + Dictionary InlineIntPtrSize { + get { + if (_inlineIntPtrSize == null) + _inlineIntPtrSize = new Dictionary (); + return _inlineIntPtrSize; + } + } #else protected bool HasOptimizableCode { get; private set; } protected bool IsExtensionType { get; private set; } + + // This is per assembly, so we set it in 'void Process (AssemblyDefinition)' + bool InlineIntPtrSize { get; set; } #endif public bool IsDualBuild { @@ -63,9 +75,6 @@ protected Optimizations Optimizations { } } - // This is per assembly, so we set it in 'void Process (AssemblyDefinition)' - bool InlineIntPtrSize { get; set; } - bool? is_arm64_calling_convention; #if NET @@ -653,8 +662,14 @@ protected void EliminateDeadCode (MethodDefinition caller) instructions.RemoveAt (last_reachable + 1); } - protected override void Process (AssemblyDefinition assembly) + bool GetInlineIntPtrSize (AssemblyDefinition assembly) { +#if NET + if (InlineIntPtrSize.TryGetValue (assembly, out bool inlineIntPtrSize)) + return inlineIntPtrSize; +#else + bool inlineIntPtrSize; +#endif // The "get_Size" is a performance (over size) optimization. // It always makes sense for platform assemblies because: // * Xamarin.TVOS.dll only ship the 64 bits code paths (all 32 bits code is extra weight better removed) @@ -668,16 +683,31 @@ protected override void Process (AssemblyDefinition assembly) // // TODO: we could make this an option "optimize for size vs optimize for speed" in the future if (Optimizations.InlineIntPtrSize.HasValue) { - InlineIntPtrSize = Optimizations.InlineIntPtrSize.Value; + inlineIntPtrSize = Optimizations.InlineIntPtrSize.Value; } else if (!IsDualBuild) { - InlineIntPtrSize = true; + inlineIntPtrSize = true; } else { - InlineIntPtrSize = (Profile.Current as BaseProfile).ProductAssembly == assembly.Name.Name; + inlineIntPtrSize = (Profile.Current as BaseProfile).ProductAssembly == assembly.Name.Name; } - Driver.Log (4, "Optimization 'inline-intptr-size' enabled for assembly '{0}'.", assembly.Name); + if (inlineIntPtrSize) + Driver.Log (4, "Optimization 'inline-intptr-size' enabled for assembly '{0}'.", assembly.Name); + +#if NET + InlineIntPtrSize.Add (assembly, inlineIntPtrSize); +#else + InlineIntPtrSize = inlineIntPtrSize; +#endif + return inlineIntPtrSize; + } + +#if !NET + protected override void Process (AssemblyDefinition assembly) + { + GetInlineIntPtrSize (assembly); base.Process (assembly); } +#endif bool GetIsExtensionType (TypeDefinition type) { @@ -810,8 +840,13 @@ void ProcessEnsureUIThread (MethodDefinition caller, Instruction ins) void ProcessIntPtrSize (MethodDefinition caller, Instruction ins) { +#if NET + if (!GetInlineIntPtrSize (caller.Module.Assembly)) + return; +#else if (!InlineIntPtrSize) return; +#endif // This will inline IntPtr.Size to load the corresponding constant value instead