[mtouch/mmp] Add support for inlining calls to Runtime.DynamicRegistrarSupported and removal of the dynamic registrar.

This commit is contained in:
Rolf Bjarne Kvinge 2018-02-12 16:59:39 +01:00
Родитель 0c89cfd7e9
Коммит 92f8fab14b
14 изменённых файлов: 292 добавлений и 3 удалений

Просмотреть файл

@ -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 ");