[mtouch/mmp] Add support for inlining calls to Runtime.DynamicRegistrarSupported and removal of the dynamic registrar.
This commit is contained in:
Родитель
0c89cfd7e9
Коммит
92f8fab14b
|
@ -236,6 +236,12 @@ Please file an [issue](https://github.com/xamarin/xamarin-macios/issues/new)
|
|||
along with a complete build log so that we can investigate what went wrong and
|
||||
possibly enable more scenarios in the future.
|
||||
|
||||
### <a name="MM2107"/>MM2107: It's not safe to remove the dynamic registrar because {reasons}
|
||||
|
||||
The linker reports this warning when the developer requests removal of the
|
||||
dynamic registrar (by passing `--optimize:remove-dynamic-registrar` to
|
||||
mmp), but the linker determines that it's not safe to do so.
|
||||
|
||||
# MM3xxx: AOT
|
||||
|
||||
## MM30xx: AOT (general) errors
|
||||
|
|
|
@ -1311,6 +1311,12 @@ Please file an [issue](https://github.com/xamarin/xamarin-macios/issues/new)
|
|||
along with a complete build log so that we can investigate what went wrong and
|
||||
possibly enable more scenarios in the future.
|
||||
|
||||
### <a name="MT2107"/>MT2107: It's not safe to remove the dynamic registrar because {reasons}
|
||||
|
||||
The linker reports this warning when the developer requests removal of the
|
||||
dynamic registrar (by passing `--optimize:remove-dynamic-registrar` to
|
||||
mmp), but the linker determines that it's not safe to do so.
|
||||
|
||||
# MT3xxx: AOT error messages
|
||||
|
||||
<!--
|
||||
|
|
|
@ -380,3 +380,71 @@ Xamarin.Mac supports loading assemblies dynamically, and those assemblies
|
|||
might not have been known at build time (and thus not optimized).
|
||||
|
||||
The default behavior can be overridden by passing `--optimize=-register-protocols` to mtouch/mmp.
|
||||
|
||||
Remove the dynamic registrar
|
||||
----------------------------
|
||||
|
||||
Both Xamarin.iOS and Xamarin.Mac include support for [registering managed
|
||||
types][1] with the Objective-C runtime. It can either be done at build time or
|
||||
at runtime (or partially at build time and the rest at runtime), but if it's
|
||||
completely done at build time, it means the supporting code for doing it at
|
||||
runtime can be removed. This results in a significant decrease in app size, in
|
||||
particular for smaller apps such as extensions or watchOS apps.
|
||||
|
||||
This optimization requires both the static registrar and the linker to be
|
||||
enabled.
|
||||
|
||||
The linker will attempt to determine if it's safe to remove the dynamic
|
||||
registrar, and if so will try to remove it.
|
||||
|
||||
Since Xamarin.Mac supports dynamically loading assemblies at runtime (which
|
||||
were not known at build time), it's impossible to determine at build time
|
||||
whether this is a safe optimization. This means that this optimization is
|
||||
never enabled by default for Xamarin.Mac apps.
|
||||
|
||||
The default behavior can be overridden by passing `--optimize=[+|-]remove-dynamic-registrar` to mtouch/mmp.
|
||||
|
||||
If the default is overridden to remove the dynamic registrar, the linker will
|
||||
emit warnings if it detects that it's not safe (but the dynamic registrar will
|
||||
still be removed).
|
||||
|
||||
Inline Runtime.DynamicRegistrationSupported
|
||||
-------------------------------------------
|
||||
|
||||
Inlines the value of Runtime.DynamicRegistrationSupported as determined at
|
||||
build time.
|
||||
|
||||
If the dynamic registrar is removed (see the "Remove the dynamic registrar"
|
||||
optimization), this is a constant 'false' value, otherwise it's a constant
|
||||
'true' value.
|
||||
|
||||
This optimization will change the following type of code:
|
||||
|
||||
```csharp
|
||||
if (Runtime.DynamicRegistrationSupported) {
|
||||
Console.WriteLine ("do something");
|
||||
} else {
|
||||
throw new Exception ("dynamic registration is not supported");
|
||||
}
|
||||
```
|
||||
|
||||
into the following when the dynamic registrar is removed:
|
||||
|
||||
```csharp
|
||||
throw new Exception ("dynamic registration is not supported");
|
||||
```
|
||||
|
||||
into the following when the dynamic registrar is not removed:
|
||||
|
||||
```csharp
|
||||
Console.WriteLine ("do something");
|
||||
```
|
||||
|
||||
This optimization requires the linker to be enabled, and is only applied to
|
||||
methods with the `[BindingImpl (BindingImplOptions.Optimizable)]` attribute.
|
||||
|
||||
It is always enabled by default (when the linker is enabled).
|
||||
|
||||
The default behavior can be overridden by passing `--optimize=[+|-]inline-dynamic-registration-supported` to mtouch/mmp.
|
||||
|
||||
[1]: https://developer.xamarin.com/guides/ios/advanced_topics/registrar/
|
||||
|
|
|
@ -451,9 +451,11 @@ xamarin_main (int argc, char *argv[], enum XamarinLaunchMode launch_mode)
|
|||
xamarin_process_managed_exception_gchandle (exception_gchandle);
|
||||
}
|
||||
|
||||
xamarin_register_entry_assembly (mono_assembly_get_object (mono_domain_get (), assembly), &exception_gchandle);
|
||||
if (exception_gchandle != 0)
|
||||
xamarin_process_managed_exception_gchandle (exception_gchandle);
|
||||
if (xamarin_supports_dynamic_registration) {
|
||||
xamarin_register_entry_assembly (mono_assembly_get_object (mono_domain_get (), assembly), &exception_gchandle);
|
||||
if (exception_gchandle != 0)
|
||||
xamarin_process_managed_exception_gchandle (exception_gchandle);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEBUG_LAUNCH_TIME_PRINT ("\tAssembly register time");
|
||||
|
|
|
@ -80,6 +80,7 @@ bool xamarin_is_gc_coop = false;
|
|||
enum MarshalObjectiveCExceptionMode xamarin_marshal_objectivec_exception_mode = MarshalObjectiveCExceptionModeDefault;
|
||||
enum MarshalManagedExceptionMode xamarin_marshal_managed_exception_mode = MarshalManagedExceptionModeDefault;
|
||||
enum XamarinLaunchMode xamarin_launch_mode = XamarinLaunchModeApp;
|
||||
bool xamarin_supports_dynamic_registration = true;
|
||||
|
||||
/* Callbacks */
|
||||
|
||||
|
@ -951,6 +952,10 @@ static bool
|
|||
register_assembly (MonoAssembly *assembly, guint32 *exception_gchandle)
|
||||
{
|
||||
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
|
||||
if (!xamarin_supports_dynamic_registration) {
|
||||
LOG (PRODUCT ": Skipping assembly registration for %s since it's not needed (dynamic registration is not supported)", mono_assembly_name_get_name (mono_assembly_get_name (assembly)));
|
||||
return true;
|
||||
}
|
||||
xamarin_register_assembly (mono_assembly_get_object (mono_domain_get (), assembly), exception_gchandle);
|
||||
return *exception_gchandle == 0;
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ extern bool xamarin_is_gc_coop;
|
|||
extern enum MarshalObjectiveCExceptionMode xamarin_marshal_objectivec_exception_mode;
|
||||
extern enum MarshalManagedExceptionMode xamarin_marshal_managed_exception_mode;
|
||||
extern enum XamarinLaunchMode xamarin_launch_mode;
|
||||
extern bool xamarin_supports_dynamic_registration;
|
||||
|
||||
typedef void (*xamarin_setup_callback) ();
|
||||
typedef int (*xamarin_extension_main_callback) (int argc, char** argv);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "main.h"
|
||||
#include "mono-runtime.h"
|
||||
#include "runtime-generated.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
|
|
@ -85,6 +85,12 @@ namespace Xamarin.Bundler {
|
|||
Cache = new Cache (arguments);
|
||||
}
|
||||
|
||||
public bool DynamicRegistrationSupported {
|
||||
get {
|
||||
return Optimizations.RemoveDynamicRegistrar != true;
|
||||
}
|
||||
}
|
||||
|
||||
// This is just a name for this app to show in log/error messages, etc.
|
||||
public string Name {
|
||||
get { return Path.GetFileNameWithoutExtension (AppDirectory); }
|
||||
|
|
|
@ -130,6 +130,7 @@ namespace Xamarin.Bundler {
|
|||
"Available optimizations:\n" +
|
||||
" dead-code-elimination: By default always enabled (requires the linker). Removes IL instructions the linker can determine will never be executed. This is most useful in combination with the inline-* optimizations, since inlined conditions almost always also results in blocks of code that will never be executed.\n" +
|
||||
" remove-uithread-checks: By default enabled for release builds (requires the linker). Remove all UI Thread checks (makes the app smaller, and slightly faster at runtime).\n" +
|
||||
" remove-dynamic-registrar: By default enabled when the static registrar is enabled. Removes the dynamic registrar (makes the app smaller).\n" +
|
||||
#if MONOTOUCH
|
||||
" inline-isdirectbinding: By default enabled (requires the linker). Tries to inline calls to NSObject.IsDirectBinding to load a constant value. Makes the app smaller, and slightly faster at runtime.\n" +
|
||||
#else
|
||||
|
@ -140,6 +141,7 @@ namespace Xamarin.Bundler {
|
|||
#endif
|
||||
" blockliteral-setupblock: By default enabled when using the static registrar. Optimizes calls to BlockLiteral.SetupBlock to avoid having to calculate the block signature at runtime.\n" +
|
||||
" inline-intptr-size: By default enabled for builds that target a single architecture (requires the linker). Inlines calls to IntPtr.Size to load a constant value. Makes the app smaller, and slightly faster at runtime.\n" +
|
||||
" inline-dynamic-registration-supported: By default always enabled (requires the linker). Optimizes calls to Runtime.DynamicRegistrationSupported to be a constant value. Makes the app smaller, and slightly faster at runtime.\n" +
|
||||
" register-protocols: Remove unneeded metadata for protocol support. Makes the app smaller and reduces memory requirements.\n",
|
||||
(v) => {
|
||||
app.Optimizations.Parse (v);
|
||||
|
|
|
@ -17,6 +17,8 @@ namespace Xamarin.Bundler
|
|||
#endif
|
||||
"blockliteral-setupblock",
|
||||
"register-protocols",
|
||||
"inline-dynamic-registration-supported",
|
||||
"remove-dynamic-registrar",
|
||||
};
|
||||
|
||||
enum Opt
|
||||
|
@ -64,6 +66,15 @@ namespace Xamarin.Bundler
|
|||
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? RemoveDynamicRegistrar {
|
||||
get { return values [(int) Opt.RemoveDynamicRegistrar]; }
|
||||
set { values [(int) Opt.RemoveDynamicRegistrar] = value; }
|
||||
}
|
||||
|
||||
public Optimizations ()
|
||||
{
|
||||
|
@ -78,6 +89,7 @@ namespace Xamarin.Bundler
|
|||
continue;
|
||||
switch ((Opt) i) {
|
||||
case Opt.RegisterProtocols:
|
||||
case Opt.RemoveDynamicRegistrar:
|
||||
if (app.Registrar != RegistrarMode.Static) {
|
||||
ErrorHelper.Warning (2003, $"Option '--optimize={(values [i].Value ? "" : "-")}{opt_names [i]}' will be ignored since the static registrar is not enabled");
|
||||
values [i] = false;
|
||||
|
@ -143,6 +155,28 @@ namespace Xamarin.Bundler
|
|||
RegisterProtocols = false; // we've already shown a warning for this.
|
||||
}
|
||||
|
||||
// By default we always inline calls to Runtime.DynamicRegistrationSupported
|
||||
if (!InlineDynamicRegistrationSupported.HasValue)
|
||||
InlineDynamicRegistrationSupported = true;
|
||||
|
||||
if (!RemoveDynamicRegistrar.HasValue) {
|
||||
if (InlineDynamicRegistrationSupported != true) {
|
||||
// Can't remove the dynamic registrar unless also inlining Runtime.DynamicRegistrationSupported
|
||||
RemoveDynamicRegistrar = false;
|
||||
} else if (app.Registrar != RegistrarMode.Static || app.LinkMode == LinkMode.None) {
|
||||
// Both the linker and the static registrar are also required
|
||||
RemoveDynamicRegistrar = false;
|
||||
} else {
|
||||
#if MONOTOUCH
|
||||
// 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;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (Driver.Verbosity > 3)
|
||||
Driver.Log (4, "Enabled optimizations: {0}", string.Join (", ", values.Select ((v, idx) => v == true ? opt_names [idx] : string.Empty).Where ((v) => !string.IsNullOrEmpty (v))));
|
||||
}
|
||||
|
|
|
@ -551,6 +551,15 @@ namespace Xamarin.Linker {
|
|||
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.
|
||||
}
|
||||
|
||||
var instructions = method.Body.Instructions;
|
||||
for (int i = 0; i < instructions.Count; i++) {
|
||||
var ins = instructions [i];
|
||||
|
@ -581,6 +590,9 @@ namespace Xamarin.Linker {
|
|||
case "get_IsDirectBinding":
|
||||
ProcessIsDirectBinding (caller, ins);
|
||||
break;
|
||||
case "get_DynamicRegistrationSupported":
|
||||
ProcessIsDynamicSupported (caller, ins);
|
||||
break;
|
||||
case "SetupBlock":
|
||||
case "SetupBlockUnsafe":
|
||||
return ProcessSetupBlock (caller, ins);
|
||||
|
@ -670,6 +682,26 @@ namespace Xamarin.Linker {
|
|||
ins.Operand = null;
|
||||
}
|
||||
|
||||
void ProcessIsDynamicSupported (MethodDefinition caller, Instruction ins)
|
||||
{
|
||||
const string operation = "inline Runtime.DynamicRegistrationSupported";
|
||||
|
||||
if (Optimizations.InlineDynamicRegistrationSupported != true)
|
||||
return;
|
||||
|
||||
// Verify we're checking the right Runtime.IsDynamicSupported call
|
||||
var mr = ins.Operand as MethodReference;
|
||||
if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime"))
|
||||
return;
|
||||
|
||||
if (!ValidateInstruction (caller, ins, operation, Code.Call))
|
||||
return;
|
||||
|
||||
// We're fine, inline the Runtime.IsDynamicSupported condition
|
||||
ins.OpCode = LinkContext.App.DynamicRegistrationSupported ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0;
|
||||
ins.Operand = null;
|
||||
}
|
||||
|
||||
int ProcessSetupBlock (MethodDefinition caller, Instruction ins)
|
||||
{
|
||||
if (Optimizations.OptimizeBlockLiteralSetupBlock != true)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Linker.Steps;
|
||||
|
@ -25,6 +26,7 @@ namespace MonoTouch.Tuner {
|
|||
public class CoreTypeMapStep : TypeMapStep {
|
||||
HashSet<TypeDefinition> cached_isnsobject = new HashSet<TypeDefinition> ();
|
||||
Dictionary<TypeDefinition, bool?> isdirectbinding_value = new Dictionary<TypeDefinition, bool?> ();
|
||||
bool dynamic_registration_support_required;
|
||||
|
||||
DerivedLinkContext LinkContext {
|
||||
get {
|
||||
|
@ -32,12 +34,133 @@ namespace MonoTouch.Tuner {
|
|||
}
|
||||
}
|
||||
|
||||
protected override void ProcessAssembly (AssemblyDefinition assembly)
|
||||
{
|
||||
if (LinkContext.App.Optimizations.RemoveDynamicRegistrar != false)
|
||||
dynamic_registration_support_required |= RequiresDynamicRegistrar (assembly, LinkContext.App.Optimizations.RemoveDynamicRegistrar == true);
|
||||
|
||||
base.ProcessAssembly (assembly);
|
||||
}
|
||||
|
||||
// If certain conditions are met, we can optimize away the code for the dynamic registrar.
|
||||
bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired)
|
||||
{
|
||||
// Disable removing the dynamic registrar for XM/Classic to simplify the code a little bit.
|
||||
if (!Driver.IsUnified)
|
||||
return true;
|
||||
|
||||
// We know that the SDK assemblies we ship don't use the methods we're looking for.
|
||||
if (Profile.IsSdkAssembly (assembly))
|
||||
return false;
|
||||
|
||||
// The product assembly itself is safe as long as it's linked
|
||||
if (Profile.IsProductAssembly (assembly))
|
||||
return LinkContext.Annotations.GetAction (assembly) != Mono.Linker.AssemblyAction.Link;
|
||||
|
||||
// Can't touch the forbidden fruit in the product assembly unless there's a reference to it
|
||||
var hasProductReference = false;
|
||||
foreach (var ar in assembly.MainModule.AssemblyReferences) {
|
||||
if (!Profile.IsProductAssembly (ar.Name))
|
||||
continue;
|
||||
hasProductReference = true;
|
||||
break;
|
||||
}
|
||||
if (!hasProductReference)
|
||||
return false;
|
||||
|
||||
// Check if the assembly references any methods that require the dynamic registrar
|
||||
var productAssemblyName = ((MobileProfile) Profile.Current).ProductAssembly;
|
||||
var requires = false;
|
||||
foreach (var mr in assembly.MainModule.GetMemberReferences ()) {
|
||||
if (mr.DeclaringType == null || string.IsNullOrEmpty (mr.DeclaringType.Namespace))
|
||||
continue;
|
||||
|
||||
var scope = mr.DeclaringType.Scope;
|
||||
var name = string.Empty;
|
||||
switch (scope.MetadataScopeType) {
|
||||
case MetadataScopeType.ModuleDefinition:
|
||||
name = ((ModuleDefinition) scope).Assembly.Name.Name;
|
||||
break;
|
||||
default:
|
||||
name = scope.Name;
|
||||
break;
|
||||
}
|
||||
if (name != productAssemblyName)
|
||||
continue;
|
||||
|
||||
switch (mr.DeclaringType.Namespace) {
|
||||
case "ObjCRuntime":
|
||||
switch (mr.DeclaringType.Name) {
|
||||
case "Runtime":
|
||||
switch (mr.Name) {
|
||||
case "ConnectMethod":
|
||||
// Req 1: Nobody must call Runtime.ConnectMethod.
|
||||
if (warnIfRequired)
|
||||
Show2107 (assembly, mr);
|
||||
requires = true;
|
||||
break;
|
||||
case "RegisterAssembly":
|
||||
// Req 3: Nobody must call Runtime.RegisterAssembly
|
||||
if (warnIfRequired)
|
||||
Show2107 (assembly, mr);
|
||||
requires = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "BlockLiteral":
|
||||
switch (mr.Name) {
|
||||
case "SetupBlock":
|
||||
case "SetupBlockUnsafe":
|
||||
// Req 2: Nobody must call BlockLiteral.SetupBlock[Unsafe].
|
||||
//
|
||||
// Fortunately the linker is able to rewrite calls to SetupBlock[Unsafe] to call
|
||||
// SetupBlockImpl (which doesn't need the dynamic registrar), which means we only have
|
||||
// to look in assemblies that aren't linked.
|
||||
if (LinkContext.Annotations.GetAction (assembly) == Mono.Linker.AssemblyAction.Link && LinkContext.App.Optimizations.OptimizeBlockLiteralSetupBlock == true)
|
||||
break;
|
||||
|
||||
if (warnIfRequired)
|
||||
Show2107 (assembly, mr);
|
||||
|
||||
requires = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "TypeConverter":
|
||||
switch (mr.Name) {
|
||||
case "ToManaged":
|
||||
// Req 4: Nobody must call TypeConverter.ToManaged
|
||||
if (warnIfRequired)
|
||||
Show2107 (assembly, mr);
|
||||
requires = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return requires;
|
||||
}
|
||||
|
||||
void Show2107 (AssemblyDefinition assembly, MemberReference mr)
|
||||
{
|
||||
ErrorHelper.Warning (2107, $"It's not safe to remove the dynamic registrar, because {assembly.Name.Name} references '{mr.DeclaringType.FullName}.{mr.Name} ({string.Join (", ", ((MethodReference) mr).Parameters.Select ((v) => v.ParameterType.FullName))})'.");
|
||||
}
|
||||
|
||||
protected override void EndProcess ()
|
||||
{
|
||||
base.EndProcess ();
|
||||
|
||||
LinkContext.CachedIsNSObject = cached_isnsobject;
|
||||
LinkContext.IsDirectBindingValue = isdirectbinding_value;
|
||||
|
||||
if (!LinkContext.App.Optimizations.RemoveDynamicRegistrar.HasValue) {
|
||||
// If dynamic registration is not required, and removal of the dynamic registrar hasn't already
|
||||
// been disabled, then we can remove it!
|
||||
LinkContext.App.Optimizations.RemoveDynamicRegistrar = !dynamic_registration_support_required;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void MapType (TypeDefinition type)
|
||||
|
|
|
@ -1130,6 +1130,8 @@ namespace Xamarin.Bundler {
|
|||
else
|
||||
sw.WriteLine ("\tsetenv (\"MONO_GC_PARAMS\", \"major=marksweep\", 1);");
|
||||
|
||||
sw.WriteLine ("\txamarin_supports_dynamic_registration = {0};", App.DynamicRegistrationSupported ? "TRUE" : "FALSE");
|
||||
|
||||
if (aotOptions != null && aotOptions.IsHybridAOT)
|
||||
sw.WriteLine ("\txamarin_mac_hybrid_aot = TRUE;");
|
||||
|
||||
|
|
|
@ -669,6 +669,7 @@ namespace Xamarin.Bundler
|
|||
sw.WriteLine ("\tsetenv (\"MONO_GC_PARAMS\", \"{0}\", 1);", app.MonoGCParams);
|
||||
foreach (var kvp in app.EnvironmentVariables)
|
||||
sw.WriteLine ("\tsetenv (\"{0}\", \"{1}\", 1);", kvp.Key.Replace ("\"", "\\\""), kvp.Value.Replace ("\"", "\\\""));
|
||||
sw.WriteLine ("\txamarin_supports_dynamic_registration = {0};", app.DynamicRegistrationSupported ? "TRUE" : "FALSE");
|
||||
sw.WriteLine ("}");
|
||||
sw.WriteLine ();
|
||||
sw.Write ("int ");
|
||||
|
|
Загрузка…
Ссылка в новой задаче