using System; using System.Collections.Generic; using System.Linq; using System.Text; using Xamarin.Utils; namespace Xamarin.Bundler { public class Optimizations { static string [] opt_names = { "remove-uithread-checks", "dead-code-elimination", "inline-isdirectbinding", "inline-intptr-size", "inline-runtime-arch", "blockliteral-setupblock", "register-protocols", "inline-dynamic-registration-supported", "static-block-to-delegate-lookup", "remove-dynamic-registrar", "trim-architectures", "remove-unsupported-il-for-bitcode", "inline-is-arm64-calling-convention", "seal-and-devirtualize", "cctor-beforefieldinit", "custom-attributes-removal", "experimental-xforms-product-type", "force-rejected-types-removal", "redirect-class-handles", }; static ApplePlatform [] [] valid_platforms = new ApplePlatform [] [] { /* Opt.RemoveUIThreadChecks */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.DeadCodeElimination */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.InlineIsDirectBinding */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.InlineIntPtrSize */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.InlineRuntimeArch */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.WatchOS, ApplePlatform.TVOS }, /* Opt.BlockLiteralSetupBlock */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.RegisterProtocols */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.InlineDynamicRegistrationSupported */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.StaticBlockToDelegateLookup */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.RemoveDynamicRegistrar */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.TrimArchitectures */ new ApplePlatform [] { ApplePlatform.MacOSX, }, /* Opt.RemoveUnsupportedILForBitcode */ new ApplePlatform [] { ApplePlatform.WatchOS, }, /* Opt.InlineIsARM64CallingConvention */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.SealAndDevirtualize */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.StaticConstructorBeforeFieldInit */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.CustomAttributesRemoval */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.ExperimentalFormsProductType */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.ForceRejectedTypesRemoval */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.RedirectClassHandles */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.WatchOS, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, }; enum Opt { RemoveUIThreadChecks, DeadCodeElimination, InlineIsDirectBinding, InlineIntPtrSize, InlineRuntimeArch, BlockLiteralSetupBlock, RegisterProtocols, InlineDynamicRegistrationSupported, StaticBlockToDelegateLookup, RemoveDynamicRegistrar, TrimArchitectures, RemoveUnsupportedILForBitcode, InlineIsARM64CallingConvention, SealAndDevirtualize, StaticConstructorBeforeFieldInit, CustomAttributesRemoval, ExperimentalFormsProductType, ForceRejectedTypesRemoval, RedirectClassHandles, } bool? [] values; public bool? RemoveUIThreadChecks { get { return values [(int) Opt.RemoveUIThreadChecks]; } set { values [(int) Opt.RemoveUIThreadChecks] = value; } } public bool? DeadCodeElimination { get { return values [(int) Opt.DeadCodeElimination]; } set { values [(int) Opt.DeadCodeElimination] = value; } } public bool? InlineIsDirectBinding { get { return values [(int) Opt.InlineIsDirectBinding]; } set { values [(int) Opt.InlineIsDirectBinding] = value; } } public bool? InlineIntPtrSize { get { return values [(int) Opt.InlineIntPtrSize]; } set { values [(int) Opt.InlineIntPtrSize] = value; } } public bool? InlineRuntimeArch { get { return values [(int) Opt.InlineRuntimeArch]; } set { values [(int) Opt.InlineRuntimeArch] = value; } } public bool? OptimizeBlockLiteralSetupBlock { get { return values [(int) Opt.BlockLiteralSetupBlock]; } set { values [(int) Opt.BlockLiteralSetupBlock] = value; } } public bool? RegisterProtocols { get { return values [(int) Opt.RegisterProtocols]; } set { values [(int) Opt.RegisterProtocols] = value; } } public bool? InlineDynamicRegistrationSupported { get { return values [(int) Opt.InlineDynamicRegistrationSupported]; } set { values [(int) Opt.InlineDynamicRegistrationSupported] = value; } } public bool? StaticBlockToDelegateLookup { get { return values [(int) Opt.StaticBlockToDelegateLookup]; } set { values [(int) Opt.StaticBlockToDelegateLookup] = value; } } public bool? RemoveDynamicRegistrar { get { return values [(int) Opt.RemoveDynamicRegistrar]; } set { values [(int) Opt.RemoveDynamicRegistrar] = value; } } public bool? TrimArchitectures { get { return values [(int) Opt.TrimArchitectures]; } set { values [(int) Opt.TrimArchitectures] = value; } } public bool? RemoveUnsupportedILForBitcode { get { return values [(int) Opt.RemoveUnsupportedILForBitcode]; } set { values [(int) Opt.RemoveUnsupportedILForBitcode] = value; } } public bool? InlineIsARM64CallingConvention { get { return values [(int) Opt.InlineIsARM64CallingConvention]; } set { values [(int) Opt.InlineIsARM64CallingConvention] = value; } } public bool? SealAndDevirtualize { get { return values [(int) Opt.SealAndDevirtualize]; } set { values [(int) Opt.SealAndDevirtualize] = value; } } public bool? StaticConstructorBeforeFieldInit { get { return values [(int) Opt.StaticConstructorBeforeFieldInit]; } set { values [(int) Opt.StaticConstructorBeforeFieldInit] = value; } } public bool? CustomAttributesRemoval { get { return values [(int) Opt.CustomAttributesRemoval]; } set { values [(int) Opt.CustomAttributesRemoval] = value; } } public bool? ExperimentalFormsProductType { get { return true; } set { } } public bool? ForceRejectedTypesRemoval { get { return values [(int) Opt.ForceRejectedTypesRemoval]; } set { values [(int) Opt.ForceRejectedTypesRemoval] = value; } } public bool? RedirectClassHandles { get { return values [(int) Opt.RedirectClassHandles]; } set { values [(int) Opt.RedirectClassHandles] = value; } } public Optimizations () { values = new bool? [opt_names.Length]; } public void Initialize (Application app, out List messages) { messages = new List (); // warn if the user asked to optimize something when the optimization can't be applied for (int i = 0; i < values.Length; i++) { if (!values [i].HasValue) continue; // The remove-dynamic-registrar optimization is required when using NativeAOT if (app.XamarinRuntime == XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar && values [i] == false) { messages.Add (ErrorHelper.CreateWarning (2016, Errors.MX2016 /* Keeping the dynamic registrar (by passing '--optimize=-remove-dynamic-registrar') is not possible, because the dynamic registrar is not supported when using NativeAOT. Support for dynamic registration will still be removed. */)); values [i] = true; continue; } // The remove-dynamic-registrar optimization is a bit if a special case on macOS: // it only works in very specific circumstances, so we don't add it to valid_platforms. // This means it won't be listed in --help, and it won't be enabled if all optimizations // are enabled. Yet we still might want to enable it manually, and this condition // allows us to manually pass --optimize=remove-dynamic-registrar and enable it that way. if (app.Platform == ApplePlatform.MacOSX && app.XamarinRuntime != XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar) continue; // check if the optimization is valid for the current platform var valid = valid_platforms [i]; if (Array.IndexOf (valid, app.Platform) < 0) { messages.Add (ErrorHelper.CreateWarning (2003, Errors.MT2003_C, opt_names [i], string.Join (", ", valid.Select (v => v.AsString ())))); values [i] = false; continue; } switch ((Opt) i) { case Opt.StaticBlockToDelegateLookup: if (app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic) { messages.Add (ErrorHelper.CreateWarning (2003, Errors.MT2003, (values [i].Value ? "" : "-"), opt_names [i])); values [i] = false; continue; } break; // does not require the linker case Opt.TrimArchitectures: break; // Does not require linker case Opt.RegisterProtocols: case Opt.RemoveDynamicRegistrar: case Opt.RedirectClassHandles: if (app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic) { messages.Add (ErrorHelper.CreateWarning (2003, Errors.MT2003, (values [i].Value ? "" : "-"), opt_names [i])); values [i] = false; continue; } goto default; // also requires the linker default: if (!app.AreAnyAssembliesTrimmed) { messages.Add (ErrorHelper.CreateWarning (2003, Errors.MT2003_B, (values [i].Value ? "" : "-"), opt_names [i])); values [i] = false; } break; } } // by default we keep the code to ensure we're executing on the UI thread (for UI code) for debug builds // but this can be overridden to either (a) remove it from debug builds or (b) keep it in release builds if (!RemoveUIThreadChecks.HasValue) RemoveUIThreadChecks = !app.EnableDebug; // By default we always eliminate dead code. if (!DeadCodeElimination.HasValue) DeadCodeElimination = true; if (!InlineIsDirectBinding.HasValue) { if (app.Platform != ApplePlatform.MacOSX) { // By default we always inline calls to NSObject.IsDirectBinding // unless the interpreter is enabled (we can't predict if code will be subclassed) InlineIsDirectBinding = !app.UseInterpreter; } else { // NSObject.IsDirectBinding is not a safe optimization to apply to XM apps, // because there may be additional code/assemblies we don't know about at build time. InlineIsDirectBinding = false; } } // The default behavior for InlineIntPtrSize depends on the assembly being linked, // which means we can't set it to a global constant. It's handled in the OptimizeGeneratedCodeSubStep directly. if (app.Platform != ApplePlatform.MacOSX) { // By default we always inline calls to Runtime.Arch if (!InlineRuntimeArch.HasValue) InlineRuntimeArch = true; } // We try to optimize calls to BlockLiteral.SetupBlock and certain BlockLiteral constructors if the static registrar is enabled if (!OptimizeBlockLiteralSetupBlock.HasValue) { OptimizeBlockLiteralSetupBlock = app.Registrar == RegistrarMode.Static || app.Registrar == RegistrarMode.ManagedStatic; } // We will register protocols if the static registrar is enabled and loading assemblies is not possible if (!RegisterProtocols.HasValue) { if (app.Platform != ApplePlatform.MacOSX || app.XamarinRuntime == XamarinRuntime.NativeAOT) { RegisterProtocols = (app.Registrar == RegistrarMode.Static || app.Registrar == RegistrarMode.ManagedStatic) && !app.UseInterpreter; } else { RegisterProtocols = false; } } else if (app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic && RegisterProtocols == true) { RegisterProtocols = false; // we've already shown a warning for this. } // By default we always inline calls to Runtime.DynamicRegistrationSupported if (!InlineDynamicRegistrationSupported.HasValue) InlineDynamicRegistrationSupported = true; // By default always enable static block-to-delegate lookup (it won't make a difference unless the static registrar is used though) if (!StaticBlockToDelegateLookup.HasValue) StaticBlockToDelegateLookup = true; if (!RemoveDynamicRegistrar.HasValue) { if (app.XamarinRuntime == XamarinRuntime.NativeAOT) { RemoveDynamicRegistrar = true; } else if (InlineDynamicRegistrationSupported != true) { // Can't remove the dynamic registrar unless also inlining Runtime.DynamicRegistrationSupported RemoveDynamicRegistrar = false; } else if (StaticBlockToDelegateLookup != true) { // Can't remove the dynamic registrar unless also generating static lookup of block-to-delegates in the static registrar. RemoveDynamicRegistrar = false; } else if ((app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic) || !app.AreAnyAssembliesTrimmed) { // Both the linker and the static registrar are also required RemoveDynamicRegistrar = false; } else { if (app.Platform != ApplePlatform.MacOSX) { // we can't predict is unknown (at build time) code will require registration (at runtime) if (app.UseInterpreter) { RemoveDynamicRegistrar = false; } // We don't have enough information yet to determine if we can remove the dynamic // registrar or not, so let the value stay unset until we do know (when running the linker). } else { // By default disabled for XM apps RemoveDynamicRegistrar = false; } } } if (app.Platform == ApplePlatform.MacOSX) { // By default on macOS trim-architectures for Release and not for debug if (!TrimArchitectures.HasValue) TrimArchitectures = !app.EnableDebug; } if (app.Platform != ApplePlatform.MacOSX) { if (!RemoveUnsupportedILForBitcode.HasValue) { // By default enabled for watchOS device builds. RemoveUnsupportedILForBitcode = app.Platform == ApplePlatform.WatchOS && app.IsDeviceBuild; } if (!SealAndDevirtualize.HasValue) { // by default run the linker SealerSubStep unless the interpreter is enabled SealAndDevirtualize = !app.UseInterpreter; } } // By default Runtime.IsARM64CallingConvention inlining is always enabled. if (!InlineIsARM64CallingConvention.HasValue) InlineIsARM64CallingConvention = true; // by default we try to eliminate any .cctor we can if (!StaticConstructorBeforeFieldInit.HasValue) StaticConstructorBeforeFieldInit = true; // by default we remove rarely used custom attributes if (!CustomAttributesRemoval.HasValue) CustomAttributesRemoval = true; } public void Parse (ApplePlatform platform, string options, List messages) { foreach (var option in options.Split (',')) { if (option is null || option.Length < 2) { messages.Add (ErrorHelper.CreateError (10, Errors.MX0010, $"'--optimize={options}'")); return; } ParseOption (platform, option, messages); } } void ParseOption (ApplePlatform platform, string option, List messages) { bool enabled; string opt; switch (option [0]) { case '-': enabled = false; opt = option.Substring (1); break; case '+': enabled = true; opt = option.Substring (1); break; default: opt = option; enabled = true; break; } if (opt == "all") { for (int i = 0; i < values.Length; i++) { var valid = valid_platforms [i]; if (Array.IndexOf (valid, platform) < 0) continue; // Don't apply 'all' to optimizations that aren't valid for the current platform values [i] = enabled; } } else { var found = false; for (int i = 0; i < values.Length; i++) { if (opt_names [i] != opt) continue; found = true; values [i] = enabled; } if (!found) #if NET messages.Add (ErrorHelper.CreateWarning (132, Errors.MX0132, opt, string.Join (", ", Enum.GetValues ().Where (o => Array.IndexOf (valid_platforms [(int) o], platform) >= 0).Select (o => opt_names [(int) o])))); #else messages.Add (ErrorHelper.CreateWarning (132, Errors.MX0132, opt, string.Join (", ", Enum.GetValues (typeof (Opt)).Cast ().Where (o => Array.IndexOf (valid_platforms [(int) o], platform) >= 0).Select (o => opt_names [(int) o])))); #endif } } public override string ToString () { var sb = new StringBuilder (); for (var i = 0; i < values.Length; i++) { if (values [i] is null) continue; if (sb.Length > 0) sb.Append (' '); sb.Append (values [i].Value ? "+" : "-"); sb.Append (opt_names [i]); } if (sb.Length == 0) return ""; return sb.ToString (); } } }