Merge pull request #3495 (Implement support for optimizing away the dynamic registrar) from rolfbjarne/full-static-registrar

Implement support for optimizing away the dynamic registrar.

* Add a new property (Runtime.DynamicRegistrationSupported) that indicates at
  runtime whether dynamic registration is available.

* Check this new property whenever we need dynamic registration.

* Add an optimization to mtouch/mmp that detects whether dynamic registration
  is required, and if not, changes Runtime.DynamicRegistrationSupported to
  return false.

* Add an optimization to mtouch/mmp that inlines
  Runtime.DynamicRegistrationSupported as a constant value.

The end result is that the linker will link away the dynamic registrar if
mtouch/mmp detects that it's not needed.

Benchmark
---------

I've compared the size of entire apps built for device:

|test                          | Before |  After |   Diff |     % |
|:-----------------------------|-------:|-------:|-------:|------:|
|[monotouch-test/Release][1]   | 95.7mb | 95.0mb | -680kb | -0.7% |
|[link sdk/Release][2]         | 21.2mb | 20.9mb | -245kb | -1.2% |
|[minimalistic app/Release][3] | 4.58mb | 4.32mb | -259kb | -5.7% |

[1]: https://gist.github.com/rolfbjarne/3871e36d2de8db1a8eee1d9f9276d3d2#monotouch-test-release-wall-optimizations-enabled
[2]: https://gist.github.com/rolfbjarne/3871e36d2de8db1a8eee1d9f9276d3d2#link-sdk
[3]: https://gist.github.com/rolfbjarne/3871e36d2de8db1a8eee1d9f9276d3d2#minimalistic-app
This commit is contained in:
Rolf Bjarne Kvinge 2018-02-15 23:56:00 +01:00 коммит произвёл GitHub
Родитель 94ec17e875 4123bf7ede
Коммит c838a11c4d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
36 изменённых файлов: 936 добавлений и 148 удалений

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

@ -236,6 +236,18 @@ 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.
To remove the warning either remove the optimization argument to mmp, or pass
`--nowarn:2107` to ignore it.
By default this option will be automatically enabled whenever it's possible
and safe to do so.
# MM3xxx: AOT
## MM30xx: AOT (general) errors
@ -375,3 +387,11 @@ This indicates a bug in Xamarin.Mac. Please file a bug at [https://bugzilla.xama
A potential workaround would be to disable the `register-protocols`
optimization, by passing `--optimize:-register-protocols` as an additional mmp
argument in the project's Mac Build options.
### <a name="MM8026"/>MM8026: * is not supported when the dynamic registrar has been linked away.
This usually indicates a bug in Xamarin.Mac, because the dynamic registrar should not be linked away if it's needed. Please file a bug at [https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=iOS).
It's possible to force the linker to keep the dynamic registrar by adding
`--optimize=-remove-dynamic-registrar` to the additional mmp arguments in
the project's Mac Build options.

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

@ -1311,6 +1311,18 @@ 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
mtouch), but the linker determines that it's not safe to do so.
To remove the warning either remove the optimization argument to mtouch, or pass
`--nowarn:2107` to ignore it.
By default this option will be automatically enabled whenever it's possible
and safe to do so.
# MT3xxx: AOT error messages
<!--
@ -2384,3 +2396,11 @@ This indicates a bug in Xamarin.iOS. Please file a bug at [https://bugzilla.xama
A potential workaround would be to disable the `register-protocols`
optimization, by passing `--optimize:-register-protocols` as an additional
mtouch argument in the project's iOS Build options.
### <a name="MT8026"/>MT8026: * is not supported when the dynamic registrar has been linked away.
This usually indicates a bug in Xamarin.iOS, because the dynamic registrar should not be linked away if it's needed. Please file a bug at [https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=iOS).
It's possible to force the linker to keep the dynamic registrar by adding
`--optimize=-remove-dynamic-registrar` to the additional mtouch arguments in
the project's iOS Build options.

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

@ -380,3 +380,70 @@ 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 the Xamarin.iOS and the Xamarin.Mac runtime include support for
[registering managed types](https://developer.xamarin.com/guides/ios/advanced_topics/registrar/)
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.

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

@ -68,9 +68,23 @@ namespace ObjCRuntime {
<# } #>
[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
static void RegisterDelegates (InitializationOptions* options)
{
<# foreach (var d in delegates) { #>
<# foreach (var d in delegates) {
if (d.OnlyDynamicUsage) continue; #>
options->Delegates-><#= d.SimpleEntryPoint #> = GetFunctionPointer (new <#= d.SimpleEntryPoint #>_delegate (<#= d.SimpleEntryPoint #>));
<# } #>
// The linker will remove this condition (and the subsequent method call) if possible
if (DynamicRegistrationSupported)
RegisterDelegatesDynamic (options);
}
static void RegisterDelegatesDynamic (InitializationOptions* options)
{
<# foreach (var d in delegates) {
if (!d.OnlyDynamicUsage) continue; #>
options->Delegates-><#= d.SimpleEntryPoint #> = GetFunctionPointer (new <#= d.SimpleEntryPoint #>_delegate (<#= d.SimpleEntryPoint #>));
<# } #>
}

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

@ -18,7 +18,10 @@
extern "C" {
#endif
<# foreach (var d in delegates) { #>
<# foreach (var d in delegates) {
if (!d.OnlyDynamicUsage)
continue;
#>
<#= d.CReturnType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignature #>);

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

@ -9,128 +9,204 @@
new XDelegate ("void", "void", "xamarin_register_nsobject",
"MonoObject *", "IntPtr", "managed_obj",
"id", "IntPtr", "native_obj"
) { WrappedManagedFunction = "RegisterNSObject" },
) {
WrappedManagedFunction = "RegisterNSObject",
OnlyDynamicUsage = true,
},
new XDelegate ("void", "void", "xamarin_register_assembly",
"MonoReflectionAssembly *", "IntPtr", "assembly"
) { WrappedManagedFunction = "RegisterAssembly" },
) {
WrappedManagedFunction = "RegisterAssembly",
OnlyDynamicUsage = true,
},
new XDelegate ("void", "void", "xamarin_throw_ns_exception",
"NSException *", "IntPtr", "exc"
) {
WrappedManagedFunction = "ThrowNSException",
ExceptionHandling = false,
OnlyDynamicUsage = false,
},
new XDelegate ("void", "void", "xamarin_rethrow_managed_exception",
"guint32", "uint", "original_exception_gchandle"
) { WrappedManagedFunction = "RethrowManagedException" },
) {
WrappedManagedFunction = "RethrowManagedException",
OnlyDynamicUsage = false,
},
new XDelegate ("int", "int", "xamarin_create_ns_exception",
"NSException *", "IntPtr", "exc"
) { WrappedManagedFunction = "CreateNSException" },
) {
WrappedManagedFunction = "CreateNSException",
OnlyDynamicUsage = false,
},
new XDelegate ("NSException *", "IntPtr", "xamarin_unwrap_ns_exception",
"int", "int", "exc_handle"
) { WrappedManagedFunction = "UnwrapNSException" },
) {
WrappedManagedFunction = "UnwrapNSException",
OnlyDynamicUsage = true,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_block_wrapper_creator",
"MonoObject *", "IntPtr", "method",
"int", "int", "parameter"
) { WrappedManagedFunction = "GetBlockWrapperCreator" },
) {
WrappedManagedFunction = "GetBlockWrapperCreator",
OnlyDynamicUsage = true,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_create_block_proxy",
"MonoObject *", "IntPtr", "method",
"void *", "IntPtr", "block"
) { WrappedManagedFunction = "CreateBlockProxy" },
) {
WrappedManagedFunction = "CreateBlockProxy",
OnlyDynamicUsage = true,
},
new XDelegate ("id", "IntPtr", "xamarin_create_delegate_proxy",
"MonoObject *", "IntPtr", "method",
"MonoObject *", "IntPtr", "block",
"const char *", "IntPtr", "signature"
) { WrappedManagedFunction = "CreateDelegateProxy" },
) {
WrappedManagedFunction = "CreateDelegateProxy",
OnlyDynamicUsage = false,
},
new XDelegate ("void", "void", "xamarin_register_entry_assembly",
"MonoReflectionAssembly *", "IntPtr", "assembly"
) { WrappedManagedFunction = "RegisterEntryAssembly" },
) {
WrappedManagedFunction = "RegisterEntryAssembly",
OnlyDynamicUsage = true,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_class",
"Class", "IntPtr", "ptr"
) { WrappedManagedFunction = "GetClass" },
) {
WrappedManagedFunction = "GetClass",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_selector",
"SEL", "IntPtr", "ptr"
) { WrappedManagedFunction = "GetSelector" },
) {
WrappedManagedFunction = "GetSelector",
OnlyDynamicUsage = false,
},
new XDelegate ("void", "void", "xamarin_get_method_for_selector",
"Class", "IntPtr", "cls",
"SEL", "IntPtr", "sel",
"bool", "bool", "is_static",
"MethodDescription *", "IntPtr", "desc"
) { WrappedManagedFunction = "GetMethodForSelector" },
) {
WrappedManagedFunction = "GetMethodForSelector",
OnlyDynamicUsage = true,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_nsobject",
"id", "IntPtr", "obj"
) { WrappedManagedFunction = "GetNSObjectWrapped" },
) {
WrappedManagedFunction = "GetNSObjectWrapped",
OnlyDynamicUsage = true,
},
new XDelegate ("bool", "bool", "xamarin_has_nsobject",
"id", "IntPtr", "obj"
) { WrappedManagedFunction = "HasNSObject" },
) {
WrappedManagedFunction = "HasNSObject",
OnlyDynamicUsage = false,
},
new XDelegate ("id", "IntPtr", "xamarin_get_handle_for_inativeobject",
"MonoObject *", "IntPtr", "obj"
) { WrappedManagedFunction = "GetHandleForINativeObject" },
) {
WrappedManagedFunction = "GetHandleForINativeObject",
OnlyDynamicUsage = false,
},
new XDelegate ("void", "void", "xamarin_unregister_nsobject",
"id", "IntPtr", "native_obj",
"MonoObject *", "IntPtr", "managed_obj"
) { WrappedManagedFunction = "UnregisterNSObject" },
) {
WrappedManagedFunction = "UnregisterNSObject",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_try_get_or_construct_nsobject",
"id", "IntPtr", "obj"
) { WrappedManagedFunction = "TryGetOrConstructNSObjectWrapped" },
) {
WrappedManagedFunction = "TryGetOrConstructNSObjectWrapped",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_inative_object_dynamic",
"id", "IntPtr", "obj",
"bool", "bool", "owns",
"void *", "IntPtr", "type"
) { WrappedManagedFunction = "GetINativeObject_Dynamic" },
) {
WrappedManagedFunction = "GetINativeObject_Dynamic",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoReflectionMethod *", "IntPtr", "xamarin_get_method_from_token",
"unsigned int", "uint", "token_ref"
) { WrappedManagedFunction = "GetMethodFromToken" },
) {
WrappedManagedFunction = "GetMethodFromToken",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoReflectionMethod *", "IntPtr", "xamarin_get_generic_method_from_token",
"MonoObject *", "IntPtr", "obj",
"unsigned int", "uint", "token_ref"
) { WrappedManagedFunction = "GetGenericMethodFromToken" },
) {
WrappedManagedFunction = "GetGenericMethodFromToken",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_inative_object_static",
"id", "IntPtr", "obj",
"bool", "bool", "owns",
"const char *", "string", "type_name",
"const char *", "string", "iface_name"
) { WrappedManagedFunction = "GetINativeObject_Static" },
) {
WrappedManagedFunction = "GetINativeObject_Static",
OnlyDynamicUsage = false,
},
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_nsobject_with_type",
"id", "IntPtr", "obj",
"void *", "IntPtr", "type",
"int32_t *", "out bool", "created"
) { WrappedManagedFunction = "GetNSObjectWithType" },
) {
WrappedManagedFunction = "GetNSObjectWithType",
OnlyDynamicUsage = false,
},
new XDelegate ("void", "void", "xamarin_dispose",
"MonoObject *", "IntPtr", "mobj"
) { WrappedManagedFunction = "Dispose" },
) {
WrappedManagedFunction = "Dispose",
OnlyDynamicUsage = false,
},
new XDelegate ("bool", "bool", "xamarin_is_parameter_transient",
"MonoReflectionMethod *", "IntPtr", "method",
"int", "int", "parameter"
) { WrappedManagedFunction = "IsParameterTransient" },
) {
WrappedManagedFunction = "IsParameterTransient",
OnlyDynamicUsage = true,
},
new XDelegate ("bool", "bool", "xamarin_is_parameter_out",
"MonoReflectionMethod *", "IntPtr", "method",
"int", "int", "parameter"
) { WrappedManagedFunction = "IsParameterOut" },
) {
WrappedManagedFunction = "IsParameterOut",
OnlyDynamicUsage = true,
},
new XDelegate ("void", "void", "xamarin_get_method_and_object_for_selector",
"Class", "IntPtr", "cls",
@ -139,38 +215,62 @@
"id", "IntPtr", "obj",
"MonoObject **", "ref IntPtr", "mthis",
"MethodDescription *", "IntPtr", "desc"
) { WrappedManagedFunction = "GetMethodAndObjectForSelector" },
) {
WrappedManagedFunction = "GetMethodAndObjectForSelector",
OnlyDynamicUsage = true,
},
new XDelegate ("guint32", "int", "xamarin_create_product_exception_for_error",
"int", "int", "code",
"const char *", "string", "message"
) { WrappedManagedFunction = "CreateProductException" },
) {
WrappedManagedFunction = "CreateProductException",
OnlyDynamicUsage = false,
},
new XDelegate ("char *", "IntPtr", "xamarin_reflection_type_get_full_name",
"MonoReflectionType *", "IntPtr", "type"
) { WrappedManagedFunction = "TypeGetFullName" },
) {
WrappedManagedFunction = "TypeGetFullName",
OnlyDynamicUsage = true,
},
new XDelegate ("char *", "IntPtr", "xamarin_lookup_managed_type_name",
"Class", "IntPtr", "klass"
) { WrappedManagedFunction = "LookupManagedTypeName" },
) {
WrappedManagedFunction = "LookupManagedTypeName",
OnlyDynamicUsage = false,
},
new XDelegate ("MarshalManagedExceptionMode", "MarshalManagedExceptionMode", "xamarin_on_marshal_managed_exception",
"int", "int", "exception"
) { WrappedManagedFunction = "OnMarshalManagedException" },
) {
WrappedManagedFunction = "OnMarshalManagedException",
OnlyDynamicUsage = false,
},
new XDelegate ("MarshalObjectiveCExceptionMode", "MarshalObjectiveCExceptionMode", "xamarin_on_marshal_objectivec_exception",
"id", "IntPtr", "exception",
"bool", "bool", "throwManagedAsDefault"
) { WrappedManagedFunction = "OnMarshalObjectiveCException" },
) {
WrappedManagedFunction = "OnMarshalObjectiveCException",
OnlyDynamicUsage = false,
},
new XDelegate ("NSString *", "IntPtr", "xamarin_convert_smart_enum_to_nsstring",
"void *", "IntPtr", "value"
) { WrappedManagedFunction = "ConvertSmartEnumToNSString" },
) {
WrappedManagedFunction = "ConvertSmartEnumToNSString",
OnlyDynamicUsage = true,
},
new XDelegate ("void *", "IntPtr", "xamarin_convert_nsstring_to_smart_enum",
"NSString *", "IntPtr", "value",
"MonoReflectionType *", "IntPtr", "type"
) { WrappedManagedFunction = "ConvertNSStringToSmartEnum" },
) {
WrappedManagedFunction = "ConvertNSStringToSmartEnum",
OnlyDynamicUsage = true,
},
};
delegates.CalculateLengths ();
#><#+
@ -204,6 +304,8 @@
public List<Arg> Arguments;
public string WrappedManagedFunction;
public bool ExceptionHandling = true;
// Detemines whether the function is only used by the dynamic registrar (in which case we might be able to link the function away if the static registrar is being used)
public bool OnlyDynamicUsage;
public XDelegates Delegates;

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

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

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

@ -22,6 +22,8 @@ extern "C" {
#endif
<# foreach (var d in delegates) {
if (d.OnlyDynamicUsage)
continue;
#>
<#= d.CReturnType #>
<#= d.EntryPoint #> (<#= d.CArgumentSignature #>);

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

@ -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" {

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

@ -282,6 +282,7 @@ namespace Foundation {
[Export ("conformsToProtocol:")]
[Preserve ()]
[BindingImpl (BindingImplOptions.Optimizable)]
public virtual bool ConformsToProtocol (IntPtr protocol)
{
bool does;
@ -316,6 +317,9 @@ namespace Foundation {
if (does)
return true;
if (!Runtime.DynamicRegistrationSupported)
return false;
object [] adoptedProtocols = GetType ().GetCustomAttributes (typeof (AdoptsAttribute), true);
foreach (AdoptsAttribute adopts in adoptedProtocols){
if (adopts.ProtocolHandle == protocol)

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

@ -83,8 +83,12 @@ namespace ObjCRuntime {
[DllImport ("__Internal")]
static extern IntPtr xamarin_get_block_descriptor ();
[BindingImpl (BindingImplOptions.Optimizable)]
void SetupBlock (Delegate trampoline, Delegate userDelegate, bool safe)
{
if (!Runtime.DynamicRegistrationSupported)
throw ErrorHelper.CreateError (8026, "BlockLiteral.SetupBlock is not supported when the dynamic registrar has been linked away.");
// We need to get the signature of the target method, so that we can compute
// the ObjC signature correctly (the generated method that's actually
// invoked by native code does not have enough type information to compute
@ -239,6 +243,7 @@ namespace ObjCRuntime {
return descriptor->copy_helper == ((BlockDescriptor *) literal->block_descriptor)->copy_helper;
}
[BindingImpl (BindingImplOptions.Optimizable)]
internal static IntPtr GetBlockForDelegate (MethodInfo minfo, object @delegate, string signature)
{
if (@delegate == null)
@ -275,7 +280,11 @@ namespace ObjCRuntime {
// with the proper reference count.
BlockLiteral block = new BlockLiteral ();
if (signature == null) {
block.SetupBlock ((Delegate) handlerDelegate, (Delegate) @delegate);
if (Runtime.DynamicRegistrationSupported) {
block.SetupBlock ((Delegate) handlerDelegate, (Delegate) @delegate);
} else {
throw ErrorHelper.CreateError (8026, $"BlockLiteral.GetBlockForDelegate with a null signature is not supported when the dynamic registrar has been linked away (delegate type: {@delegate.GetType ().FullName}).");
}
} else {
block.SetupBlockImpl ((Delegate) handlerDelegate, (Delegate) @delegate, true, signature);
}

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

@ -27,6 +27,7 @@ namespace ObjCRuntime {
internal IntPtr handle;
[BindingImpl (BindingImplOptions.Optimizable)]
internal unsafe static void Initialize (Runtime.InitializationOptions* options)
{
var map = options->RegistrationMap;
@ -36,6 +37,9 @@ namespace ObjCRuntime {
if (map == null)
return;
if (!Runtime.DynamicRegistrationSupported)
return; // Only the dynamic registrar needs the list of registered assemblies.
for (int i = 0; i < map->assembly_count; i++) {
var ptr = Marshal.ReadIntPtr (map->assembly, i * IntPtr.Size);
Runtime.Registrar.SetAssemblyRegistered (Marshal.PtrToStringAuto (ptr));
@ -98,6 +102,7 @@ namespace ObjCRuntime {
return GetClassHandle (type);
}
[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
static IntPtr GetClassHandle (Type type)
{
IntPtr @class = IntPtr.Zero;
@ -116,6 +121,8 @@ namespace ObjCRuntime {
}
if (@class == IntPtr.Zero) {
if (!Runtime.DynamicRegistrationSupported)
throw ErrorHelper.CreateError (8026, $"Can't register the class {type.FullName} when the dynamic registrar has been linked away.");
@class = Register (type);
lock (type_to_class)
type_to_class [type] = @class;
@ -143,12 +150,36 @@ namespace ObjCRuntime {
internal static Type Lookup (IntPtr klass)
{
return Lookup (klass, true);
return LookupClass (klass, true);
}
internal static Type Lookup (IntPtr klass, bool throw_on_error)
{
return Runtime.Registrar.Lookup (klass, throw_on_error);
return LookupClass (klass, throw_on_error);
}
[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
static Type LookupClass (IntPtr klass, bool throw_on_error)
{
bool is_custom_type;
var find_class = klass;
do {
var tp = FindType (find_class, out is_custom_type);
if (tp != null)
return tp;
if (Runtime.DynamicRegistrationSupported)
break; // We can't continue looking up the hierarchy if we have the dynamic registrar, because we might be supposed to register this class.
find_class = class_getSuperclass (find_class);
} while (find_class != IntPtr.Zero);
// The linker will remove this condition (and the subsequent method call) if possible
if (Runtime.DynamicRegistrationSupported)
return Runtime.Registrar.Lookup (klass, throw_on_error);
if (throw_on_error)
throw ErrorHelper.CreateError (8026, $"Can't lookup the Objective-C class 0x{klass.ToString ("x")} ({class_getName (klass)}) when the dynamic registrar has been linked away.");
return null;
}
internal static IntPtr Register (Type type)
@ -439,6 +470,7 @@ namespace ObjCRuntime {
/*
Type must have been previously registered.
*/
[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
#if !XAMCORE_2_0 && !MONOTOUCH // Accidently exposed this to public, can't break API
public
#else
@ -446,7 +478,15 @@ namespace ObjCRuntime {
#endif
static bool IsCustomType (Type type)
{
return Runtime.Registrar.IsCustomType (type);
bool is_custom_type;
var @class = FindClass (type, out is_custom_type);
if (@class != IntPtr.Zero)
return is_custom_type;
if (Runtime.DynamicRegistrationSupported)
return Runtime.Registrar.IsCustomType (type);
throw ErrorHelper.CreateError (8026, $"Can't determine if {type.FullName} is a custom type when the dynamic registrar has been linked away.");
}
[DllImport ("/usr/lib/libobjc.dylib")]

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

@ -162,6 +162,14 @@ namespace ObjCRuntime {
internal static unsafe InitializationOptions* options;
[BindingImpl (BindingImplOptions.Optimizable)]
public static bool DynamicRegistrationSupported {
get {
// The linker may turn calls to this property into a constant
return true;
}
}
internal static bool Initialized {
get { return initialized; }
}
@ -172,6 +180,7 @@ namespace ObjCRuntime {
#endif
[Preserve] // called from native - runtime.m.
[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
unsafe static void Initialize (InitializationOptions* options)
{
#if PROFILE
@ -234,7 +243,8 @@ namespace ObjCRuntime {
NSObjectClass = NSObject.Initialize ();
Registrar = new DynamicRegistrar ();
if (DynamicRegistrationSupported)
Registrar = new DynamicRegistrar ();
RegisterDelegates (options);
Class.Initialize (options);
InitializePlatform (options);
@ -533,11 +543,14 @@ namespace ObjCRuntime {
return Registrar.ComputeSignature (method, isBlockSignature);
}
[BindingImpl (BindingImplOptions.Optimizable)]
public static void RegisterAssembly (Assembly a)
{
if (a == null)
throw new ArgumentNullException ("a");
if (!DynamicRegistrationSupported)
throw ErrorHelper.CreateError (8026, "Runtime.RegisterAssembly is not supported when the dynamic registrar has been linked away.");
#if MONOMAC
var attributes = a.GetCustomAttributes (typeof (RequiredFrameworkAttribute), false);
@ -1399,6 +1412,7 @@ namespace ObjCRuntime {
ConnectMethod (type, method, new ExportAttribute (selector.Name));
}
[BindingImpl (BindingImplOptions.Optimizable)]
public static void ConnectMethod (Type type, MethodInfo method, ExportAttribute export)
{
if (type == null)
@ -1410,6 +1424,9 @@ namespace ObjCRuntime {
if (export == null)
throw new ArgumentNullException ("export");
if (!DynamicRegistrationSupported)
throw ErrorHelper.CreateError (8026, "Runtime.ConnectMethod is not supported when the dynamic registrar has been linked away.");
Registrar.RegisterMethod (type, method, export);
}

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

@ -16,7 +16,11 @@ namespace ObjCRuntime {
*
* http://developer.apple.com/documentation/DeveloperTools/gcc-4.0.1/gcc/Type-encoding.html
*/
[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
public static Type ToManaged (string type) {
if (!Runtime.DynamicRegistrationSupported) // The call to Runtime.GetAssemblies further below requires the dynamic registrar.
throw ErrorHelper.CreateError (8026, "TypeConverter.ToManaged is not supported when the dynamic registrar has been linked away.");
switch (type[0]) {
case '@':
return typeof (IntPtr);

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

@ -30,13 +30,22 @@ namespace Xamarin.BindingTests
Assert.IsNotNull (IP1, "IP1");
// with a [Protocol] attribute
var IP1Attributes = IP1.GetCustomAttributes (typeof (ProtocolAttribute), false);
Assert.AreEqual (1, IP1Attributes.Length, "[Protocol] IP1");
var IP1Protocol = (ProtocolAttribute)IP1Attributes [0];
Assert.AreEqual ("P1", IP1Protocol.Name, "Name");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P1Wrapper");
Assert.IsNotNull (wrapperType, "P1_Wrapper");
Assert.AreEqual (wrapperType, IP1Protocol.WrapperType, "WrapperType");
if (Runtime.DynamicRegistrationSupported) {
Assert.AreEqual (1, IP1Attributes.Length, "[Protocol] IP1");
var IP1Protocol = (ProtocolAttribute)IP1Attributes [0];
Assert.AreEqual ("P1", IP1Protocol.Name, "Name");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P1Wrapper");
Assert.IsNotNull (wrapperType, "P1_Wrapper");
Assert.AreEqual (wrapperType, IP1Protocol.WrapperType, "WrapperType");
} else {
Assert.AreEqual (0, IP1Attributes.Length, "[Protocol] IP1");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P1Wrapper");
Assert.IsNotNull (wrapperType, "P1_Wrapper");
}
// but not the model
Assert.IsNull (bindingAssembly.GetType ("Bindings.Test.Protocol.P1"), "P1");
}
@ -53,14 +62,22 @@ namespace Xamarin.BindingTests
// with a [Protocol] attribute
var IP2Attributes = IP2.GetCustomAttributes (typeof (ProtocolAttribute), false);
Assert.AreEqual (1, IP2Attributes.Length, "[Protocol] IP2");
var IP2Protocol = (ProtocolAttribute)IP2Attributes [0];
Assert.AreEqual ("P2", IP2Protocol.Name, "Name");
if (Runtime.DynamicRegistrationSupported) {
Assert.AreEqual (1, IP2Attributes.Length, "[Protocol] IP2");
var IP2Protocol = (ProtocolAttribute)IP2Attributes [0];
Assert.AreEqual ("P2", IP2Protocol.Name, "Name");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P2Wrapper");
Assert.IsNotNull (wrapperType, "P2_Wrapper");
Assert.AreEqual (wrapperType, IP2Protocol.WrapperType, "WrapperType");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P2Wrapper");
Assert.IsNotNull (wrapperType, "P2_Wrapper");
Assert.AreEqual (wrapperType, IP2Protocol.WrapperType, "WrapperType");
} else {
Assert.AreEqual (0, IP2Attributes.Length, "[Protocol] IP2");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P2Wrapper");
Assert.IsNotNull (wrapperType, "P2_Wrapper");
}
// and a model-like class
var model = bindingAssembly.GetType ("Bindings.Test.Protocol.P2");
@ -81,14 +98,22 @@ namespace Xamarin.BindingTests
// with a [Protocol] attribute
var IP3Attributes = IP3.GetCustomAttributes (typeof (ProtocolAttribute), false);
Assert.AreEqual (1, IP3Attributes.Length, "[Protocol] IP3");
var IP3Protocol = (ProtocolAttribute)IP3Attributes [0];
Assert.AreEqual ("P3", IP3Protocol.Name, "Name");
if (Runtime.DynamicRegistrationSupported) {
Assert.AreEqual (1, IP3Attributes.Length, "[Protocol] IP3");
var IP3Protocol = (ProtocolAttribute)IP3Attributes [0];
Assert.AreEqual ("P3", IP3Protocol.Name, "Name");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P3Wrapper");
Assert.IsNotNull (wrapperType, "P3_Wrapper");
Assert.AreEqual (wrapperType, IP3Protocol.WrapperType, "WrapperType");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P3Wrapper");
Assert.IsNotNull (wrapperType, "P3_Wrapper");
Assert.AreEqual (wrapperType, IP3Protocol.WrapperType, "WrapperType");
} else {
Assert.AreEqual (0, IP3Attributes.Length, "[Protocol] IP3");
// and a wrapper type
var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P3Wrapper");
Assert.IsNotNull (wrapperType, "P3_Wrapper");
}
// and a model class
var model = bindingAssembly.GetType ("Bindings.Test.Protocol.P3");

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

@ -13,7 +13,7 @@ using Xamarin.Tests;
namespace Xamarin.ApiTest
{
[TestFixture]
public class BlockLiteral
public class ApiTest
{
[Test]
#if MONOTOUCH
@ -26,7 +26,11 @@ namespace Xamarin.ApiTest
#endif
public void AlwaysOptimizable (Profile profile)
{
// This test ensures that all methods that call BlockLiteral.SetupBlock[Impl] have an [BindingImpl (BindingImplOptions.Optimizable)] attribute.
// This test ensures that all methods that call optimizable methods have an [BindingImpl (BindingImplOptions.Optimizable)] attribute.
// Current optimizable methods:
// * BlockLiteral.SetupBlock
// * BlockLiteral.SetupBlockImpl
// * Runtime.get_DynamicRegistrationSupported
var dll = Configuration.GetBaseLibrary (profile);
var asm = AssemblyDefinition.ReadAssembly (dll);
@ -34,51 +38,71 @@ namespace Xamarin.ApiTest
var failures = new List<string> ();
foreach (var type in asm.MainModule.GetTypes ()) {
if (type.Name == "BlockLiteral")
continue;
foreach (var method in type.Methods) {
if (!method.HasBody)
continue;
var callsSetupBlock = false;
var mustBeOptimizable = false;
MethodReference mr = null;
foreach (var instr in method.Body.Instructions) {
var mr = instr.Operand as MethodReference;
mr = instr.Operand as MethodReference;
if (mr == null)
continue;
if (mr.DeclaringType.Name != "BlockLiteral")
continue;
if (mr.Name != "SetupBlock" && mr.Name != "SetupBlockUnsafe")
continue;
callsSetupBlock = true;
break;
}
if (!callsSetupBlock)
continue;
// This method calls BlockLiteral.SetupBlock. Ensure it has a [BindingImpl (BindingImplOptions.Optimizable)] attribute.
var isOptimizable = IsOptimizable (method);
if (!isOptimizable && (method.IsGetter || method.IsSetter)) {
var property = method.DeclaringType.Properties.FirstOrDefault ((v) => v.GetMethod == method || v.SetMethod == method);
isOptimizable = IsOptimizable (property);
}
foreach (var ca in method.CustomAttributes) {
if (ca.AttributeType.Name != "BindingImplAttribute")
continue;
var value = (int) ca.ConstructorArguments [0].Value;
if ((value & 0x2) == 0x2) { // BindingImplOptions.Optimizable = 2
isOptimizable = true;
switch (mr.DeclaringType.Name) {
case "BlockLiteral":
if (type == mr.DeclaringType)
continue; // Calls within BlockLiteral without the optimizable attribute is allowed
switch (mr.Name) {
case "SetupBlock":
case "SetupBlockUnsafe":
mustBeOptimizable = true;
break;
}
break;
case "Runtime":
switch (mr.Name) {
case "get_DynamicRegistrationSupported":
mustBeOptimizable = true;
break;
}
break;
}
if (mustBeOptimizable)
break;
}
if (!mustBeOptimizable)
continue;
// Ensure it has a [BindingImpl (BindingImplOptions.Optimizable)] attribute.
var isOptimizable = IsOptimizable (method);
if (!isOptimizable)
failures.Add ($"The method {method.FullName} calls BlockLiteral.SetupBlock[Unsafe], but it does not have a [BindingImpl (BindingImplOptions.Optimizable)] attribute.");
failures.Add ($"The method {method.FullName} calls {mr.FullName}, but it does not have a [BindingImpl (BindingImplOptions.Optimizable)] attribute.");
}
}
CollectionAssert.IsEmpty (failures, "All methods calling SetupBlock[Unsafe] must be optimizable");
if (failures.Count > 0)
Assert.Fail ($"All methods calling optimizable API must be optimizable\n\t{string.Join ("\n\t", failures)}");
}
bool IsOptimizable (ICustomAttributeProvider provider)
bool IsOptimizable (MethodDefinition method)
{
var isOptimizable = IsOptimizableProvider (method);
if (!isOptimizable && (method.IsGetter || method.IsSetter)) {
var property = method.DeclaringType.Properties.FirstOrDefault ((v) => v.GetMethod == method || v.SetMethod == method);
if (IsOptimizableProvider (property))
return true;
}
foreach (var ca in method.CustomAttributes) {
if (ca.AttributeType.Name != "BindingImplAttribute")
continue;
var value = (int) ca.ConstructorArguments [0].Value;
if ((value & 0x2) == 0x2) // BindingImplOptions.Optimizable = 2
return true;
}
return false;
}
bool IsOptimizableProvider (ICustomAttributeProvider provider)
{
foreach (var ca in provider.CustomAttributes) {
if (ca.AttributeType.Name != "BindingImplAttribute")

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

@ -106,6 +106,63 @@ class T {
}
}
#if __MACOS__
// XM doesn't support removing the dynamic registrar yet.
//[TestCase (Profile.macOSMobile)]
#else
[Test]
[TestCase (Profile.iOS)]
#endif
public void MX2107 (Profile profile)
{
using (var bundler = new BundlerTool ()) {
var code = @"
using System;
using Foundation;
using ObjCRuntime;
class T {
static void Main ()
{
TypeConverter.ToManaged (""@"");
Runtime.ConnectMethod (typeof (NSObject), typeof (T).GetMethod (""Main""), new Selector (""sel""));
Runtime.ConnectMethod (typeof (NSObject), typeof (T).GetMethod (""Main""), new ExportAttribute (""sel""));
Runtime.ConnectMethod (typeof (T).GetMethod (""Main""), new Selector (""sel""));
Runtime.RegisterAssembly (null);
BlockLiteral bl = default (BlockLiteral);
Action action = null;
bl.SetupBlock (action, action);
bl.SetupBlockUnsafe (action, action);
}
}
";
bundler.Profile = profile;
bundler.CreateTemporaryCacheDirectory ();
bundler.CreateTemporaryApp (profile, code: code, extraArg: "/debug:full");
bundler.Linker = LinkerOption.LinkSdk;
bundler.Registrar = RegistrarOption.Static;
bundler.Optimize = new string [] { "remove-dynamic-registrar" };
bundler.AssertExecute ();
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.TypeConverter.ToManaged (System.String)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, ObjCRuntime.Selector)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, Foundation.ExportAttribute)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.ConnectMethod (System.Reflection.MethodInfo, ObjCRuntime.Selector)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.RegisterAssembly (System.Reflection.Assembly)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.BlockLiteral.SetupBlock (System.Delegate, System.Delegate)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.BlockLiteral.SetupBlockUnsafe (System.Delegate, System.Delegate)'.");
bundler.AssertWarningCount (7);
// try again with link all, now the warnings about SetupBlock[Unsafe] should be gone
bundler.Linker = LinkerOption.LinkAll;
bundler.AssertExecute ();
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.TypeConverter.ToManaged (System.String)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, ObjCRuntime.Selector)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, Foundation.ExportAttribute)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.ConnectMethod (System.Reflection.MethodInfo, ObjCRuntime.Selector)'.");
bundler.AssertWarning (2107, "It's not safe to remove the dynamic registrar, because testApp references 'ObjCRuntime.Runtime.RegisterAssembly (System.Reflection.Assembly)'.");
bundler.AssertWarningCount (5);
}
}
[Test]
#if __MACOS__
[TestCase (Profile.macOSMobile)]

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

@ -140,17 +140,11 @@ namespace LinkAll.Attributes {
{
var klass = Type.GetType (NamespacePrefix + "ObjCRuntime.Runtime, " + AssemblyName);
Assert.NotNull (klass, "Runtime");
// Initialize, RegisterAssembly... are unconditionally preserved
// Initialize and a few other methods are unconditionally preserved
var method = klass.GetMethod ("Initialize", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull (method, "Initialize");
method = klass.GetMethod ("RegisterAssembly", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull (method, "RegisterAssembly");
method = klass.GetMethod ("RegisterNSObject", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof (NSObject), typeof (IntPtr) }, null);
Assert.NotNull (method, "RegisterNSObject");
method = klass.GetMethod ("GetBlockWrapperCreator", BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof (MethodInfo), typeof (int) }, null);
Assert.NotNull (method, "GetBlockWrapperCreator");
method = klass.GetMethod ("CreateBlockProxy", BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof (MethodInfo), typeof (IntPtr) }, null);
Assert.NotNull (method, "CreateBlockProxy");
}
[Test]

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

@ -25,7 +25,7 @@
<MtouchDebug>True</MtouchDebug>
<MtouchLink>Full</MtouchLink>
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchExtraArgs>--registrar=static --optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>--registrar=static --optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<MtouchArch>i386, x86_64</MtouchArch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -39,7 +39,7 @@
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchArch>i386, x86_64</MtouchArch>
<DefineConstants>LINKALL;;$(DefineConstants)</DefineConstants>
<MtouchExtraArgs>--optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>--optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
@ -55,7 +55,7 @@
<MtouchLink>Full</MtouchLink>
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<MtouchExtraArgs>-gcc_flags="-UhoItsB0rken" --optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>-gcc_flags="-UhoItsB0rken" --optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -72,7 +72,7 @@
<MtouchLink>Full</MtouchLink>
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchArch>ARMv7</MtouchArch>
<MtouchExtraArgs>-gcc_flags="-UhoItsB0rken" --optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>-gcc_flags="-UhoItsB0rken" --optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug64|iPhone' ">
@ -88,7 +88,7 @@
<MtouchLink>Full</MtouchLink>
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchArch>ARM64</MtouchArch>
<MtouchExtraArgs>-gcc_flags="-UhoItsB0rken" --optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>-gcc_flags="-UhoItsB0rken" --optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
@ -102,7 +102,7 @@
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchUseLlvm>True</MtouchUseLlvm>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<MtouchExtraArgs>--optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>--optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<DefineConstants>LINKALL;;$(DefineConstants)</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -117,7 +117,7 @@
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchUseLlvm>True</MtouchUseLlvm>
<MtouchArch>ARMv7</MtouchArch>
<MtouchExtraArgs>--optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>--optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<DefineConstants>LINKALL;;$(DefineConstants)</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -132,7 +132,7 @@
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchUseLlvm>True</MtouchUseLlvm>
<MtouchArch>ARM64</MtouchArch>
<MtouchExtraArgs>--optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>--optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<DefineConstants>LINKALL;;$(DefineConstants)</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -147,7 +147,7 @@
<MtouchI18n>mideast,other</MtouchI18n>
<MtouchUseLlvm>true</MtouchUseLlvm>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<MtouchExtraArgs>--bitcode:full --optimize=all</MtouchExtraArgs>
<MtouchExtraArgs>--bitcode:full --optimize=all,-remove-dynamic-registrar</MtouchExtraArgs>
<DefineConstants>LINKALL;;$(DefineConstants)</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

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

@ -842,7 +842,7 @@ namespace Xamarin.MMP.Tests
"<LinkMode>Full</LinkMode>",
};
var rv = TI.TestUnifiedExecutable (test, shouldFail: false);
rv.Messages.AssertWarning (132, $"Unknown optimization: '{opt}'. Valid optimizations are: remove-uithread-checks, dead-code-elimination, inline-isdirectbinding, inline-intptr-size, blockliteral-setupblock, register-protocols.");
rv.Messages.AssertWarning (132, $"Unknown optimization: '{opt}'. Valid optimizations are: remove-uithread-checks, dead-code-elimination, inline-isdirectbinding, inline-intptr-size, blockliteral-setupblock, register-protocols, inline-dynamic-registration-supported.");
rv.Messages.AssertErrorCount (0);
});
}

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

@ -39,6 +39,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void TestSetupBlock ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires the dynamic registrar to be available.");
using (var obj = new TestClass ()) {
TestClass.OnCallback = ((IntPtr blockArgument, IntPtr self, IntPtr argument) =>
{

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

@ -55,6 +55,75 @@ namespace MonoTouchFixtures.ObjCRuntime {
Assert.That (p, Is.Not.EqualTo (IntPtr.Zero), "Class");
}
[Test]
public void Ctor ()
{
Assert.DoesNotThrow (() => new Class (typeof (NSObject)), "NSObject");
if (Runtime.DynamicRegistrationSupported) {
Assert.AreEqual (IntPtr.Zero, new Class (typeof (string)).Handle, "string");
} else {
try {
new Class (typeof (string));
} catch (Exception e) {
Assert.AreEqual (typeof (RuntimeException), e.GetType (), "string exc");
Assert.AreEqual ("Can't register the class System.String when the dynamic registrar has been linked away.", e.Message, "exc message");
}
}
}
[Test]
public void GetHandle ()
{
Assert.AreNotEqual (IntPtr.Zero, Class.GetHandle (typeof (NSObject)), "NSObject");
if (Runtime.DynamicRegistrationSupported) {
Assert.AreEqual (IntPtr.Zero, Class.GetHandle (typeof (string)), "string 1");
} else {
try {
Class.GetHandle (typeof (string));
} catch (Exception e) {
Assert.AreEqual (typeof (RuntimeException), e.GetType (), "string exc");
Assert.AreEqual ("Can't register the class System.String when the dynamic registrar has been linked away.", e.Message, "exc message");
}
}
}
[Test]
public void Lookup ()
{
Assert.AreEqual (typeof (NSObject), Class.Lookup (new Class (typeof (NSObject))), "NSObject");
Assert.AreEqual (typeof (NSString), Class.Lookup (new Class (typeof (NSString))), "NSString");
Assert.AreNotEqual (typeof (NSObject), Class.Lookup (new Class (typeof (NSString))), "neq");
try {
Class.Lookup (new Class ("NSProxy"));
} catch (Exception e) {
Assert.AreEqual (typeof (RuntimeException), e.GetType (), "NSProxy exception");
if (Runtime.DynamicRegistrationSupported) {
Assert.AreEqual ("The ObjectiveC class 'NSProxy' could not be registered, it does not seem to derive from any known ObjectiveC class (including NSObject).", e.Message, "NSProxy exception message");
} else {
Assert.That (e.Message, Is.StringMatching ("Can't lookup the Objective-C class 0x.* w"), "NSProxy exception message 2");
}
}
Assert.Throws<ArgumentException> (() => new Class ("InexistentClass"), "inexistent");
// Private class which we've obviously not bound, but we've bound a super class.
// And yes, NSMutableString is the first public superclass of __NSCFConstantString.
Assert.AreEqual (typeof (NSMutableString), Class.Lookup (new Class ("__NSCFConstantString")), "private class");
}
[Test]
public void IsCustomType ()
{
using (var str = new DirtyType ())
str.MarkDirty ();
}
class DirtyType : NSObject
{
public void MarkDirty ()
{
base.MarkDirty ();
}
}
// Not sure what to do about this one, it doesn't compile with the static registrar (since linking fails)
#if DYNAMIC_REGISTRAR
[Test]

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

@ -82,6 +82,22 @@ namespace MonoTouchFixtures.ObjCRuntime {
}
}
[Test]
public void RegistrarRemoval ()
{
// define set by xharness when creating test variations.
// It's not safe to remove the dynamic registrar in monotouch-test (by design; some of the tested API makes it unsafe, and the linker correcty detects this),
// so the dynamic registrar will only be removed if manually requested.
// Also removal of the dynamic registrar is not supported in XM
#if OPTIMIZEALL && !__MACOS__
var shouldBeRemoved = true;
#else
var shouldBeRemoved = false;
#endif
Assert.AreEqual (shouldBeRemoved, typeof (NSObject).Assembly.GetType ("Registrar.Registrar") == null, "Registrar removal");
Assert.AreEqual (shouldBeRemoved, typeof (NSObject).Assembly.GetType ("Registrar.DynamicRegistrar") == null, "DynamicRegistrar removal");
}
#if !MONOMAC
[Test]
public void TestProperties ()
@ -268,6 +284,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void TestAction ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires the dynamic registrar to be available.");
using (var obj = new RegistrarTestClass ()) {
var sel = new Selector ("testAction:");
var block = new BlockLiteral ();
@ -1812,6 +1831,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void CustomAppDelegatePerformFetchTest ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires the dynamic registrar to be available.");
using (var obj = new CustomApplicationDelegate ()) {
BlockLiteral block = new BlockLiteral ();
var performed = false;
@ -2567,9 +2589,13 @@ namespace MonoTouchFixtures.ObjCRuntime {
var block = new BlockLiteral ();
var tramp = new DActionArity1V1 (SDActionArity1V1.Invoke);
Action<NSObject> del = (v) => { };
block.SetupBlock (tramp, del);
Assert.AreEqual ("v@:^v^v", GetBlockSignature (block), "a");
block.CleanupBlock ();
if (Runtime.DynamicRegistrationSupported) {
block.SetupBlock (tramp, del);
Assert.AreEqual ("v@:^v^v", GetBlockSignature (block), "a");
block.CleanupBlock ();
} else {
Assert.Throws<RuntimeException> (() => block.SetupBlock (tramp, del));
}
}
[Test]
@ -2578,9 +2604,15 @@ namespace MonoTouchFixtures.ObjCRuntime {
var block = new BlockLiteral ();
var tramp = new DActionArity1V2 (SDActionArity1V2.Invoke);
Action<NSObject> del = (v) => { };
block.SetupBlock (tramp, del);
Assert.AreEqual ("v@?@", GetBlockSignature (block), "a");
block.CleanupBlock ();
if (Runtime.DynamicRegistrationSupported) {
block.SetupBlock (tramp, del);
Assert.AreEqual ("v@?@", GetBlockSignature (block), "a");
block.CleanupBlock ();
} else {
// The linker is able to rewrite calls to BlockLiteral.SetupBlock to BlockLiteral.SetupBlockImpl (which works without the dynamic registrar),
// but that will only happen if the code is linked, and monotouch-test is only SdkLinked. Thus this code will throw an exception
Assert.Throws<RuntimeException> (() => block.SetupBlock (tramp, del));
}
}
}

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

@ -73,6 +73,8 @@ namespace MonoTouchFixtures.ObjCRuntime {
{
if (connectMethodTestDone)
Assert.Ignore ("This test can only be executed once, it modifies global state.");
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires support for dynamic registration.");
connectMethodTestDone = true;
// Bug 20013. This should not throw a KeyNotFoundException.
Runtime.ConnectMethod (typeof (ConnectMethodClass).GetMethod ("Method"), new Selector ("method"));
@ -356,6 +358,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void ConnectMethod ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires support for dynamic registration.");
var minfo = typeof (RuntimeTest).GetMethod ("ConnectMethod");
Assert.Throws<ArgumentNullException> (() => Runtime.ConnectMethod (null, new Selector ("")), "1");
Assert.Throws<ArgumentNullException> (() => Runtime.ConnectMethod (minfo, null), "2");
@ -371,6 +376,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void ConnectMethod1 ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires support for dynamic registration.");
if (connectMethod1Done)
Assert.Ignore ("This is a one-shot test. Restart to run again.");
connectMethod1Done = true;
@ -385,6 +393,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void ConnectMethod2 ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires support for dynamic registration.");
if (connectMethod2Done)
Assert.Ignore ("This is a one-shot test. Restart to run again.");
connectMethod2Done = true;
@ -407,6 +418,9 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Test]
public void ConnectMethod3 ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires support for dynamic registration.");
if (connectMethod3Done)
Assert.Ignore ("This is a one-shot test. Restart to run again.");
connectMethod3Done = true;

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

@ -61,6 +61,9 @@ namespace MonoTouchFixtures.Photos {
[Test]
public unsafe void FrameProcessingBlock2 ()
{
if (!Runtime.DynamicRegistrationSupported)
Assert.Ignore ("This test requires support for the dynamic registrar to setup the block");
var t = typeof (NSObject).Assembly.GetType ("ObjCRuntime.Trampolines/SDPHLivePhotoFrameProcessingBlock2");
Assert.NotNull (t, "SDPHLivePhotoFrameProcessingBlock2");

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

@ -1639,7 +1639,7 @@ public class TestApp {
mtouch.Linker = MTouchLinker.LinkSdk;
mtouch.Optimize = new string [] { "foo" };
mtouch.AssertExecute (MTouchAction.BuildSim, "build");
mtouch.AssertWarning (132, "Unknown optimization: 'foo'. Valid optimizations are: remove-uithread-checks, dead-code-elimination, inline-isdirectbinding, inline-intptr-size, inline-runtime-arch, blockliteral-setupblock, register-protocols.");
mtouch.AssertWarning (132, "Unknown optimization: 'foo'. Valid optimizations are: remove-uithread-checks, dead-code-elimination, inline-isdirectbinding, inline-intptr-size, inline-runtime-arch, blockliteral-setupblock, register-protocols, inline-dynamic-registration-supported, remove-dynamic-registrar.");
}
}
@ -3267,8 +3267,10 @@ public partial class NotificationService : UNNotificationServiceExtension
mtouch.AssertWarning (2003, "Option '--optimize=inline-intptr-size' will be ignored since linking is disabled");
mtouch.AssertWarning (2003, "Option '--optimize=inline-runtime-arch' will be ignored since linking is disabled");
mtouch.AssertWarning (2003, "Option '--optimize=blockliteral-setupblock' will be ignored since linking is disabled");
mtouch.AssertWarning (2003, "Option '--optimize=inline-dynamic-registration-supported' will be ignored since linking is disabled");
mtouch.AssertWarning (2003, "Option '--optimize=register-protocols' will be ignored since the static registrar is not enabled");
mtouch.AssertWarningCount (7);
mtouch.AssertWarning (2003, "Option '--optimize=remove-dynamic-registrar' will be ignored since the static registrar is not enabled");
mtouch.AssertWarningCount (9);
}
using (var mtouch = new MTouchTool ()) {

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

@ -176,7 +176,7 @@ namespace xharness
switch (test.TestName) {
case "monotouch-test":
yield return new TestData { Variation = "Release (all optimizations)", MTouchExtraArgs = "--registrar:static --optimize:all", Debug = false, Profiling = false };
yield return new TestData { Variation = "Release (all optimizations)", MTouchExtraArgs = "--registrar:static --optimize:all", Debug = false, Profiling = false, Defines = "OPTIMIZEALL" };
break;
}
break;
@ -185,7 +185,7 @@ namespace xharness
case "monotouch-test":
// The default is to run monotouch-test with the dynamic registrar (in the simulator), so that's already covered
yield return new TestData { Variation = "Debug (static registrar)", MTouchExtraArgs = "--registrar:static", Debug = true, Profiling = false };
yield return new TestData { Variation = "Release (all optimizations)", MTouchExtraArgs = "--registrar:static --optimize:all", Debug = false, Profiling = false, LinkMode = "Full" };
yield return new TestData { Variation = "Release (all optimizations)", MTouchExtraArgs = "--registrar:static --optimize:all", Debug = false, Profiling = false, LinkMode = "Full", Defines = "LINKALL;OPTIMIZEALL" };
break;
}
break;
@ -194,7 +194,7 @@ namespace xharness
switch (test.TestName) {
case "xammac tests":
if (test.ProjectConfiguration == "Release")
yield return new TestData { Variation = "Release (all optimizations)", MonoBundlingExtraArgs = "--registrar:static --optimize:all", Debug = false, LinkMode = "Full", Defines = "LINKALL" };
yield return new TestData { Variation = "Release (all optimizations)", MonoBundlingExtraArgs = "--registrar:static --optimize:all", Debug = false, LinkMode = "Full", Defines = "LINKALL;OPTIMIZEALL" };
break;
}
break;
@ -226,14 +226,7 @@ namespace xharness
var clone_task = Task.Run (async () => {
await task.BuildTask.InitialTask; // this is the project cloning above
await clone.CreateCopyAsync (task);
if (!string.IsNullOrEmpty (mtouch_extra_args))
clone.Xml.AddExtraMtouchArgs (mtouch_extra_args, task.ProjectPlatform, configuration);
if (!string.IsNullOrEmpty (bundling_extra_args))
clone.Xml.AddMonoBundlingExtraArgs (bundling_extra_args, task.ProjectPlatform, configuration);
if (!string.IsNullOrEmpty (link_mode))
clone.Xml.SetNode ("LinkMode", link_mode, task.ProjectPlatform, configuration);
if (!string.IsNullOrEmpty (defines))
clone.Xml.AddAdditionalDefines (defines, task.ProjectPlatform, configuration);
var isMac = false;
switch (task.Platform) {
case TestPlatform.Mac:
@ -245,6 +238,15 @@ namespace xharness
isMac = true;
break;
}
if (!string.IsNullOrEmpty (mtouch_extra_args))
clone.Xml.AddExtraMtouchArgs (mtouch_extra_args, task.ProjectPlatform, configuration);
if (!string.IsNullOrEmpty (bundling_extra_args))
clone.Xml.AddMonoBundlingExtraArgs (bundling_extra_args, task.ProjectPlatform, configuration);
if (!string.IsNullOrEmpty (link_mode))
clone.Xml.SetNode (isMac ? "LinkMode" : "MtouchLink", link_mode, task.ProjectPlatform, configuration);
if (!string.IsNullOrEmpty (defines))
clone.Xml.AddAdditionalDefines (defines, task.ProjectPlatform, configuration);
clone.Xml.SetNode (isMac ? "Profiling" : "MTouchProfiling", profiling ? "True" : "False", task.ProjectPlatform, configuration);
if (!debug && !isMac)

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

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

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

@ -136,10 +136,12 @@ namespace Xamarin.Bundler {
" inline-isdirectbinding: By default disabled, because it may (requires the linker), because . Tries to inline calls to NSObject.IsDirectBinding to load a constant value. Makes the app smaller, and slightly faster at runtime.\n" +
#endif
#if MONOTOUCH
" remove-dynamic-registrar: By default enabled when the static registrar is enabled. Removes the dynamic registrar (makes the app smaller).\n" +
" inline-runtime-arch: By default always enabled (requires the linker). Inlines calls to ObjCRuntime.Runtime.Arch to load a constant value. Makes the app smaller, and slightly faster at runtime.\n" +
#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,12 @@ namespace Xamarin.Bundler
#endif
"blockliteral-setupblock",
"register-protocols",
"inline-dynamic-registration-supported",
#if MONOTOUCH
"remove-dynamic-registrar",
#else
"", // dummy value to make indices match up between XM and XI
#endif
};
enum Opt
@ -64,6 +70,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 +93,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 +159,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))));
}
@ -177,8 +215,10 @@ namespace Xamarin.Bundler
}
if (opt == "all") {
for (int i = 0; i < values.Length; i++)
values [i] = enabled;
for (int i = 0; i < values.Length; i++) {
if (!string.IsNullOrEmpty (opt_names [i]))
values [i] = enabled;
}
} else {
var found = false;
for (int i = 0; i < values.Length; i++) {

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

@ -212,11 +212,7 @@ namespace Xamarin.Linker {
return true; // We've already marked this section of code
for (int i = start; i < end; i++) {
if (instructions [i].OpCode.Code != Code.Nop) {
// Not marking nop instructios makes it possible to remove catch clauses if
// the protected region is only nops (or empty).
reachable [i] = true;
}
reachable [i] = true;
var ins = instructions [i];
switch (ins.OpCode.FlowControl) {
@ -226,10 +222,31 @@ namespace Xamarin.Linker {
return MarkInstructions (method, instructions, reachable, instructions.IndexOf (br_target), end);
case FlowControl.Cond_Branch:
// Conditional instruction, we need to check if we can calculate a constant value for the condition
var cond_target = (Instruction) ins.Operand;
var cond_target = ins.Operand as Instruction;
bool? branch = null; // did we get a constant value for the condition, and if so, did we branch or not?
var cond_instruction_count = 0; // The number of instructions that compose the condition
if (ins.OpCode.Code == Code.Switch) {
// Treat all branches of the switch statement as reachable.
// FIXME: calculate the potential constant branch (currently there are no optimizable methods where the switch condition is constant, so this is not needed for now)
var targets = ins.Operand as Instruction [];
if (targets == null) {
Driver.Log (4, $"Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand);
return false;
}
foreach (var target in targets) {
// not constant, continue marking both this code sequence and the branched sequence
if (!MarkInstructions (method, instructions, reachable, instructions.IndexOf (target), end))
return false;
}
return MarkInstructions (method, instructions, reachable, instructions.IndexOf (ins.Next), end);
}
if (cond_target == null) {
Driver.Log (4, $"Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand);
return false;
}
switch (ins.OpCode.Code) {
case Code.Brtrue:
case Code.Brtrue_S: {
@ -400,21 +417,36 @@ namespace Xamarin.Linker {
return;
// Handle exception handlers specially, they do not follow normal code flow.
bool[] reachableExceptionHandlers = null;
if (caller.Body.HasExceptionHandlers) {
foreach (var eh in caller.Body.ExceptionHandlers) {
reachableExceptionHandlers = new bool [caller.Body.ExceptionHandlers.Count];
for (var e = 0; e < reachableExceptionHandlers.Length; e++) {
var eh = caller.Body.ExceptionHandlers [e];
// First check if the protected region is reachable
var startI = instructions.IndexOf (eh.TryStart);
var endI = instructions.IndexOf (eh.TryEnd);
for (int i = startI; i < endI; i++) {
if (reachable [i]) {
reachableExceptionHandlers [e] = true;
break;
}
}
// The protected code isn't reachable, none of the handlers will be executed
if (!reachableExceptionHandlers [e])
continue;
switch (eh.HandlerType) {
case ExceptionHandlerType.Catch:
// We don't need catch handlers if the protected region does not have any reachable instructions.
var startI = instructions.IndexOf (eh.TryStart);
var endI = instructions.IndexOf (eh.TryEnd);
var anyReachable = false;
// We don't need catch handlers the reachable instructions are all nops
var allNops = true;
for (int i = startI; i < endI; i++) {
if (reachable [i]) {
anyReachable = true;
if (instructions [i].OpCode.Code != Code.Nop) {
allNops = false;
break;
}
}
if (anyReachable) {
if (!allNops) {
if (!MarkInstructions (caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd)))
return;
}
@ -506,6 +538,15 @@ namespace Xamarin.Linker {
Nop (instructions [i]);
}
// Remove exception handlers
if (reachableExceptionHandlers != null) {
for (int i = reachableExceptionHandlers.Length - 1; i >= 0; i--) {
if (reachableExceptionHandlers [i])
continue;
caller.Body.ExceptionHandlers.RemoveAt (i);
}
}
// Remove unreachable instructions (nops) at the end, because the last instruction can only be ret/throw/backwards branch.
for (int i = last_reachable + 1; i < reachable.Length; i++)
instructions.RemoveAt (last_reachable + 1);
@ -551,6 +592,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 +631,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 +723,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)
@ -685,6 +758,9 @@ namespace Xamarin.Linker {
if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral"))
return 0;
if (caller.Name == "GetBlockForDelegate" && caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral"))
return 0; // BlockLiteral.GetBlockForDelegate contains a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about.
string signature = null;
try {
// We need to figure out the type of the first argument to the call to SetupBlock[Impl].

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

@ -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,134 @@ 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;
Driver.Log (4, "Optimization dynamic registrar removal: {0}", LinkContext.App.Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled");
}
}
protected override void MapType (TypeDefinition type)

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

@ -1130,6 +1130,9 @@ namespace Xamarin.Bundler {
else
sw.WriteLine ("\tsetenv (\"MONO_GC_PARAMS\", \"major=marksweep\", 1);");
if (IsUnified)
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 ");