779 строки
25 KiB
Markdown
779 строки
25 KiB
Markdown
---
|
|
title: "Build optimizations"
|
|
description: "This document explains the various optimizations that are applied at build time for Xamarin.iOS and Xamarin.Mac apps."
|
|
ms.prod: xamarin
|
|
ms.assetid: 84B67E31-B217-443D-89E5-CFE1923CB14E
|
|
ms.technology: xamarin-cross-platform
|
|
author: bradumbaugh
|
|
ms.author: brumbaug
|
|
dateupdated: 04/16/2018
|
|
---
|
|
|
|
# Build optimizations
|
|
|
|
This document explains the various optimizations that are applied at build time for
|
|
Xamarin.iOS and Xamarin.Mac apps.
|
|
|
|
## Remove UIApplication.EnsureUIThread / NSApplication.EnsureUIThread
|
|
|
|
Removes calls to [UIApplication.EnsureUIThread][1] (for Xamarin.iOS) or
|
|
`NSApplication.EnsureUIThread` (for Xamarin.Mac).
|
|
|
|
This optimization will change the following type of code:
|
|
|
|
```csharp
|
|
public virtual void AddChildViewController (UIViewController childController)
|
|
{
|
|
global::UIKit.UIApplication.EnsureUIThread ();
|
|
// ...
|
|
}
|
|
```
|
|
|
|
into the following:
|
|
|
|
```csharp
|
|
public virtual void AddChildViewController (UIViewController childController)
|
|
{
|
|
// ...
|
|
}
|
|
```
|
|
|
|
This optimization requires the linker to be enabled, and is only applied to
|
|
methods with the `[BindingImpl (BindingImplOptions.Optimizable)]` attribute.
|
|
|
|
By default it's enabled for release builds.
|
|
|
|
The default behavior can be overridden by passing `--optimize=[+|-]remove-uithread-checks` to mtouch/mmp.
|
|
|
|
[1]: https://developer.xamarin.com/api/member/UIKit.UIApplication.EnsureUIThread/
|
|
|
|
## Inline IntPtr.Size
|
|
|
|
Inlines the constant value of `IntPtr.Size` according to the target platform.
|
|
|
|
This optimization will change the following type of code:
|
|
|
|
```csharp
|
|
if (IntPtr.Size == 8) {
|
|
Console.WriteLine ("64-bit platform");
|
|
} else {
|
|
Console.WriteLine ("32-bit platform");
|
|
}
|
|
```
|
|
|
|
into the following (when building for a 64-bit platform):
|
|
|
|
```csharp
|
|
if (8 == 8) {
|
|
Console.WriteLine ("64-bit platform");
|
|
} else {
|
|
Console.WriteLine ("32-bit platform");
|
|
}
|
|
```
|
|
|
|
This optimization requires the linker to be enabled, and is only applied to
|
|
methods with the `[BindingImpl (BindingImplOptions.Optimizable)]` attribute.
|
|
|
|
By default it's enabled if targeting a single architecture, or for the
|
|
platform assembly (**Xamarin.iOS.dll**, **Xamarin.TVOS.dll**,
|
|
**Xamarin.WatchOS.dll** or **Xamarin.Mac.dll**).
|
|
|
|
If targeting multiple architectures, this optimization will create different
|
|
assemblies for the 32-bit version and the 64-bit version of the app, and both
|
|
versions will have to be included in the app, effectively increasing the final
|
|
app size instead of decreasing it.
|
|
|
|
The default behavior can be overridden by passing `--optimize=[+|-]inline-intptr-size` to mtouch/mmp.
|
|
|
|
## Inline NSObject.IsDirectBinding
|
|
|
|
`NSObject.IsDirectBinding` is an instance property that determines whether a
|
|
particular instance is of a wrapper type or not (a wrapper type is a managed
|
|
type that maps to a native type; for instance the managed `UIKit.UIView` type
|
|
maps to the native `UIView` type - the opposite is a user type, in this case
|
|
`class MyUIView : UIKit.UIView` would be a user type).
|
|
|
|
It's necessary to know the value of `IsDirectBinding` when calling into
|
|
Objective-C, because the value determines which version of `objc_msgSend` to
|
|
use.
|
|
|
|
Given only the following code:
|
|
|
|
```csharp
|
|
class UIView : NSObject {
|
|
public virtual string SomeProperty {
|
|
get {
|
|
if (IsDirectBinding) {
|
|
return "true";
|
|
} else {
|
|
return "false"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class NSUrl : NSObject {
|
|
public virtual string SomeOtherProperty {
|
|
get {
|
|
if (IsDirectBinding) {
|
|
return "true";
|
|
} else {
|
|
return "false"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class MyUIView : UIView {
|
|
}
|
|
```
|
|
|
|
We can determine that in `UIView.SomeProperty` the value of
|
|
`IsDirectBinding` is not a constant and cannot be inlined:
|
|
|
|
```csharp
|
|
void uiView = new UIView ();
|
|
Console.WriteLine (uiView.SomeProperty); /* prints 'true' */
|
|
void myView = new MyUIView ();
|
|
Console.WriteLine (myView.SomeProperty); // prints 'false'
|
|
```
|
|
|
|
However, it's possible to look at the all the types in the app and determine
|
|
that there are no types that inherit from `NSUrl`, and it's thus safe to
|
|
inline the `IsDirectBinding` value to a constant `true`:
|
|
|
|
```csharp
|
|
void myURL = new NSUrl ();
|
|
Console.WriteLine (myURL.SomeOtherProperty); // prints 'true'
|
|
// There's no way to make SomeOtherProperty print anything but 'true', since there are no NSUrl subclasses.
|
|
```
|
|
|
|
In particular, this optimization will change the following type of code (this
|
|
is the binding code for `NSUrl.AbsoluteUrl`):
|
|
|
|
```csharp
|
|
if (IsDirectBinding) {
|
|
return Runtime.GetNSObject<NSUrl> (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, Selector.GetHandle ("absoluteURL")));
|
|
} else {
|
|
return Runtime.GetNSObject<NSUrl> (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("absoluteURL")));
|
|
}
|
|
```
|
|
|
|
into the following (when it can be determined that there are no subclasses of
|
|
`NSUrl` in the app):
|
|
|
|
```csharp
|
|
if (true) {
|
|
return Runtime.GetNSObject<NSUrl> (global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, Selector.GetHandle ("absoluteURL")));
|
|
} else {
|
|
return Runtime.GetNSObject<NSUrl> (global::ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("absoluteURL")));
|
|
}
|
|
```
|
|
|
|
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 for Xamarin.iOS, and always disabled by
|
|
default for Xamarin.Mac (because it's possible to dynamically load assemblies
|
|
in Xamarin.Mac, it's not possible to determine that a particular class is
|
|
never subclassed).
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]inline-isdirectbinding` to mtouch/mmp.
|
|
|
|
## Inline Runtime.Arch
|
|
|
|
This optimization will change the following type of code:
|
|
|
|
```csharp
|
|
if (Runtime.Arch == Arch.DEVICE) {
|
|
Console.WriteLine ("Running on device");
|
|
} else {
|
|
Console.WriteLine ("Running in the simulator");
|
|
}
|
|
```
|
|
|
|
into the following (when building for device):
|
|
|
|
```csharp
|
|
if (Arch.DEVICE == Arch.DEVICE) {
|
|
Console.WriteLine ("Running on device");
|
|
} else {
|
|
Console.WriteLine ("Running in the simulator");
|
|
}
|
|
```
|
|
|
|
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 for Xamarin.iOS (it's not available for
|
|
Xamarin.Mac).
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]inline-runtime-arch` to mtouch.
|
|
|
|
## Dead code elimination
|
|
|
|
This optimization will change the following type of code:
|
|
|
|
```csharp
|
|
if (true) {
|
|
Console.WriteLine ("Doing this");
|
|
} else {
|
|
Console.WriteLine ("Not doing this");
|
|
}
|
|
```
|
|
|
|
into:
|
|
|
|
```csharp
|
|
Console.WriteLine ("Doing this");
|
|
```
|
|
|
|
It will also evaluate constant comparisons, like this:
|
|
|
|
```csharp
|
|
if (8 == 8) {
|
|
Console.WriteLine ("Doing this");
|
|
} else {
|
|
Console.WriteLine ("Not doing this");
|
|
}
|
|
```
|
|
|
|
and determine that the expression `8 == 8` is a always true, and reduce it to:
|
|
|
|
```csharp
|
|
Console.WriteLine ("Doing this");
|
|
```
|
|
|
|
This is a powerful optimization when used together with the inlining
|
|
optimizations, because it can transform the following type of code (this is
|
|
the binding code for `NFCIso15693ReadMultipleBlocksConfiguration.Range`):
|
|
|
|
```csharp
|
|
NSRange ret;
|
|
if (IsDirectBinding) {
|
|
if (Runtime.Arch == Arch.DEVICE) {
|
|
if (IntPtr.Size == 8) {
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSend (this.Handle, Selector.GetHandle ("range"));
|
|
} else {
|
|
global::ObjCRuntime.Messaging.NSRange_objc_msgSend_stret (out ret, this.Handle, Selector.GetHandle ("range"));
|
|
}
|
|
} else if (IntPtr.Size == 8) {
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSend (this.Handle, Selector.GetHandle ("range"));
|
|
} else {
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSend (this.Handle, Selector.GetHandle ("range"));
|
|
}
|
|
} else {
|
|
if (Runtime.Arch == Arch.DEVICE) {
|
|
if (IntPtr.Size == 8) {
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("range"));
|
|
} else {
|
|
global::ObjCRuntime.Messaging.NSRange_objc_msgSendSuper_stret (out ret, this.SuperHandle, Selector.GetHandle ("range"));
|
|
}
|
|
} else if (IntPtr.Size == 8) {
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("range"));
|
|
} else {
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("range"));
|
|
}
|
|
}
|
|
return ret;
|
|
```
|
|
|
|
into this (when building for a 64-bit device, and when also able to
|
|
ensure there are no `NFCIso15693ReadMultipleBlocksConfiguration` subclasses
|
|
in the app):
|
|
|
|
```csharp
|
|
NSRange ret;
|
|
ret = global::ObjCRuntime.Messaging.NSRange_objc_msgSend (this.Handle, Selector.GetHandle ("range"));
|
|
return ret;
|
|
```
|
|
|
|
The AOT compiler is already able to do eliminate dead code like this, but this
|
|
optimization is done inside the linker, which means that the linker able to
|
|
see that there are multiple methods that are not used anymore, and may thus be
|
|
removed (unless used elsewhere):
|
|
|
|
* `global::ObjCRuntime.Messaging.NSRange_objc_msgSend_stret`
|
|
* `global::ObjCRuntime.Messaging.NSRange_objc_msgSendSuper`
|
|
* `global::ObjCRuntime.Messaging.NSRange_objc_msgSendSuper_stret`
|
|
|
|
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=[+|-]dead-code-elimination` to mtouch/mmp.
|
|
|
|
## Optimize calls to BlockLiteral.SetupBlock
|
|
|
|
The Xamarin.iOS/Mac runtime needs to know the block signature when creating an
|
|
Objective-C block for a managed delegate. This might be a fairly expensive
|
|
operation. This optimization will calculate the block signature at build time,
|
|
and modify the IL to call a `SetupBlock` method that takes the signature as an
|
|
argument instead. Doing this avoids the need for calculating the signature at
|
|
runtime.
|
|
|
|
Benchmarks show that this speeds up calling a block by a factor of 10 to 15.
|
|
|
|
It will transform the following [code](https://github.com/xamarin/xamarin-macios/blob/018f7153441d9d7e0f58e2046f39eeb46f1ff480/src/UIKit/UIAccessibility.cs#L198-L211):
|
|
|
|
```csharp
|
|
public static void RequestGuidedAccessSession (bool enable, Action<bool> completionHandler)
|
|
{
|
|
// ...
|
|
block_handler.SetupBlock (callback, completionHandler);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
into:
|
|
|
|
```csharp
|
|
public static void RequestGuidedAccessSession (bool enable, Action<bool> completionHandler)
|
|
{
|
|
// ...
|
|
block_handler.SetupBlockImpl (callback, completionHandler, true, "v@?B");
|
|
// ...
|
|
}
|
|
```
|
|
|
|
This optimization requires the linker to be enabled, and is only applied to
|
|
methods with the `[BindingImpl (BindingImplOptions.Optimizable)]` attribute.
|
|
|
|
It is enabled by default when using the static registrar (in Xamarin.iOS the
|
|
static registrar is enabled by default for device builds, and in Xamarin.Mac
|
|
the static registrar is enabled by default for release builds).
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]blockliteral-setupblock` to mtouch/mmp.
|
|
|
|
## Optimize support for protocols
|
|
|
|
The Xamarin.iOS/Mac runtime needs information about how managed types
|
|
implements Objective-C protocols. This information is stored in interfaces
|
|
(and attributes on these interfaces), which is not a very efficient format,
|
|
nor is it linker-friendly.
|
|
|
|
One example is that these interfaces store information about all protocol
|
|
members in a `[ProtocolMember]` attribute, which among other things contain
|
|
references to the parameter types of those members. This means that simply
|
|
implementing such an interface will make the linker preserve all types used in
|
|
that interface, even for optional members the app never calls or implements.
|
|
|
|
This optimization will make the static registrar store any required
|
|
information in an efficient format that uses little memory that's easy and
|
|
quick to find at runtime.
|
|
|
|
It will also teach the linker that it does not necessarily need to preserve
|
|
these interfaces, nor any of the related attributes.
|
|
|
|
This optimization requires both the linker and the static registrar to be
|
|
enabled.
|
|
|
|
On Xamarin.iOS this optimization is enabled by default when both the linker
|
|
and the static registrar are enabled.
|
|
|
|
On Xamarin.Mac this optimization is never enabled by default, because
|
|
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](#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.
|
|
|
|
## Precompute methods to create managed delegates for Objective-C blocks
|
|
|
|
When Objective-C calls a selector that takes a block as a parameter, and then
|
|
managed code has overriden that method, the Xamarin.iOS / Xamarin.Mac runtime
|
|
needs to create a delegate for that block.
|
|
|
|
The binding code generated by the binding generator will include a
|
|
`[BlockProxy]` attribute, which specifies the type with a `Create` method that
|
|
can do this.
|
|
|
|
Given the following Objective-C code:
|
|
|
|
```objc
|
|
@interface ObjCBlockTester : NSObject {
|
|
}
|
|
-(void) classCallback: (void (^)())completionHandler;
|
|
-(void) callClassCallback;
|
|
@end
|
|
@implementation ObjCBlockTester
|
|
-(void) classCallback: (void (^)())completionHandler
|
|
{
|
|
}
|
|
|
|
-(void) callClassCallback
|
|
{
|
|
[self classCallback: ^()
|
|
{
|
|
NSLog (@"called!");
|
|
}];
|
|
}
|
|
@end
|
|
```
|
|
|
|
and the following binding code:
|
|
|
|
```csharp
|
|
[BaseType (typeof (NSObject))]
|
|
interface ObjCBlockTester
|
|
{
|
|
[Export ("classCallback:")]
|
|
void ClassCallback (Action completionHandler);
|
|
}
|
|
```
|
|
|
|
the generator will produce:
|
|
|
|
```csharp
|
|
[Register("ObjCBlockTester", true)]
|
|
public unsafe partial class ObjCBlockTester : NSObject {
|
|
// unrelated code...
|
|
|
|
[Export ("callClassCallback")]
|
|
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
|
|
public virtual void CallClassCallback ()
|
|
{
|
|
if (IsDirectBinding) {
|
|
ApiDefinition.Messaging.void_objc_msgSend (this.Handle, Selector.GetHandle ("callClassCallback"));
|
|
} else {
|
|
ApiDefinition.Messaging.void_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("callClassCallback"));
|
|
}
|
|
}
|
|
|
|
[Export ("classCallback:")]
|
|
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
|
|
public unsafe virtual void ClassCallback ([BlockProxy (typeof (Trampolines.NIDActionArity1V0))] System.Action completionHandler)
|
|
{
|
|
// ...
|
|
|
|
}
|
|
}
|
|
|
|
static class Trampolines
|
|
{
|
|
[UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
|
|
[UserDelegateType (typeof (System.Action))]
|
|
internal delegate void DActionArity1V0 (IntPtr block);
|
|
|
|
static internal class SDActionArity1V0 {
|
|
static internal readonly DActionArity1V0 Handler = Invoke;
|
|
|
|
[MonoPInvokeCallback (typeof (DActionArity1V0))]
|
|
static unsafe void Invoke (IntPtr block) {
|
|
var descriptor = (BlockLiteral *) block;
|
|
var del = (System.Action) (descriptor->Target);
|
|
if (del != null)
|
|
del (obj);
|
|
}
|
|
}
|
|
|
|
internal class NIDActionArity1V0 {
|
|
IntPtr blockPtr;
|
|
DActionArity1V0 invoker;
|
|
|
|
[Preserve (Conditional=true)]
|
|
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
|
|
public unsafe NIDActionArity1V0 (BlockLiteral *block)
|
|
{
|
|
blockPtr = _Block_copy ((IntPtr) block);
|
|
invoker = block->GetDelegateForBlock<DActionArity1V0> ();
|
|
}
|
|
|
|
[Preserve (Conditional=true)]
|
|
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
|
|
~NIDActionArity1V0 ()
|
|
{
|
|
_Block_release (blockPtr);
|
|
}
|
|
|
|
[Preserve (Conditional=true)]
|
|
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
|
|
public unsafe static System.Action Create (IntPtr block)
|
|
{
|
|
if (block == IntPtr.Zero)
|
|
return null;
|
|
if (BlockLiteral.IsManagedBlock (block)) {
|
|
var existing_delegate = ((BlockLiteral *) block)->Target as System.Action;
|
|
if (existing_delegate != null)
|
|
return existing_delegate;
|
|
}
|
|
return new NIDActionArity1V0 ((BlockLiteral *) block).Invoke;
|
|
}
|
|
|
|
[Preserve (Conditional=true)]
|
|
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
|
|
unsafe void Invoke ()
|
|
{
|
|
invoker (blockPtr);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
When Objective-C calls `[ObjCBlockTester callClassCallback]`, the Xamarin.iOS
|
|
/ Xamarin.Mac runtime will look at the `[BlockProxy (typeof (Trampolines.NIDActionArity1V0))]`
|
|
attribute on the parameter. It will then look up the `Create` method on that type,
|
|
and call that method to create the delegate.
|
|
|
|
This optimization will find the `Create` method at build time, and the static
|
|
registrar will generate code that looks up the method at runtime using the
|
|
metadata tokens instead using the attribute and reflection (this is much
|
|
faster, and also allows the linker to remove the corresponding runtime code,
|
|
making the app smaller).
|
|
|
|
If mmp/mtouch is unable to find the `Create` method, then a MT4174/MM4174
|
|
warning will be shown, and the lookup will be performed at runtime instead.
|
|
The most probable cause is manually written binding code without the required
|
|
`[BlockProxy]` attributes.
|
|
|
|
This optimization requires the static registrar to be enabled.
|
|
|
|
It is always enabled by default (as long as the static registrar is enabled).
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]static-delegate-to-block-lookup` to mtouch/mmp.
|
|
|
|
## Remove unsupported IL for bitcode
|
|
|
|
Removes unsupported IL for bitcode, and replaces it with a `NotSupportedException`.
|
|
|
|
There are certain types of IL that Xamarin.iOS doesn't support when compiling
|
|
to bitcode. This optimization will replace the unsupported IL with an
|
|
`NotSupportedException`, and will emit a warning at build time.
|
|
|
|
This ensures that any unsupported IL will be detected at runtime even when not
|
|
compiling to bitcode (in particular it will mean that the behavior between
|
|
Debug and Release device builds is identical, since Debug builds do not
|
|
compile to bitcode, while Release builds do).
|
|
|
|
This optimization will change the following code:
|
|
|
|
```csharp
|
|
void Method ()
|
|
{
|
|
try {
|
|
throw new Exception ("FilterMe");
|
|
} catch (Exception e) when (e.Message == "FilterMe") {
|
|
Console.WriteLine ("filtered");
|
|
}
|
|
}
|
|
```
|
|
|
|
into the following:
|
|
|
|
```csharp
|
|
void Method ()
|
|
{
|
|
throw new NotSupportedException ("This method contains IL not supported when compiled to bitcode.");
|
|
}
|
|
```
|
|
|
|
This optimization does not require the linker to be enabled, it will process
|
|
all assemblies, even those not linked.
|
|
|
|
It is only applicable to watchOS, and then it's enabled by default when
|
|
building for device.
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]remove-unsupported-il-for-bitcode` to mtouch/mmp.
|
|
|
|
## Inline Runtime.IsARM64CallingConvention
|
|
|
|
Inlines the value of `Runtime.IsARM64CallingConvention` as determined at
|
|
build time.
|
|
|
|
It's usually possible to determine at build time if we'll be running on an
|
|
ARM64 cpu at runtime, and in that case we can inline the value of this
|
|
property to a constant `true` or `false` value.
|
|
|
|
This optimization will change the following type of code:
|
|
|
|
```csharp
|
|
if (Runtime.IsARM64CallingConvention) {
|
|
Console.WriteLine ("Running on ARM64");
|
|
} else {
|
|
Console.WriteLine ("Not running on ARM64");
|
|
}
|
|
```
|
|
|
|
into the following when running on ARM64:
|
|
|
|
```csharp
|
|
if (true) {
|
|
Console.WriteLine ("Running on ARM64");
|
|
} else {
|
|
Console.WriteLine ("Not running on ARM64");
|
|
}
|
|
```
|
|
|
|
or into the following when not running on ARM64:
|
|
|
|
```csharp
|
|
if (false) {
|
|
Console.WriteLine ("Running on ARM64");
|
|
} else {
|
|
Console.WriteLine ("Not running on ARM64");
|
|
}
|
|
|
|
```
|
|
|
|
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-is-arm64-calling-convention` to mtouch/mmp.
|
|
|
|
## Seal and Devirtualize
|
|
|
|
This optimization requires the linker to be enabled and is applied globally
|
|
on all code inside the application.
|
|
|
|
When the linker _knows_ all the code inside an application it can do global
|
|
optimization like:
|
|
* seal types (if not subclassed);
|
|
* mark method as final (if not overriden in subclasses); and
|
|
* devirtualize methods (if never overriden)
|
|
|
|
The changes allow the AOT compiler to apply further optimizations when
|
|
generating native code.
|
|
|
|
This optimization is not safe when dynamically loading code. As such it does
|
|
not exist for Xamarin.Mac. For Xamarin.iOS it is enabled, by default,
|
|
unless the interpreter is used.
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]seal-and-devirtualize` to `mtouch`.
|
|
|
|
## Static constructors for BeforeFieldInit removal
|
|
|
|
This optimization requires the linker to be enabled and is applied globally
|
|
on all code inside the application.
|
|
|
|
This optimization allows the linker not to mark every `.cctor` when a type
|
|
is preserved, e.g. whenever the class/static constructor `.cctor` is only
|
|
used for field initialization and those fields are not marked themselves
|
|
then it is possible to remove the `.cctor`.
|
|
|
|
This optimization is enabled, by default, on both Xamarin.iOS and
|
|
Xamarin.Mac. However it represent a change from older versions of the linker.
|
|
It is possible that some existing code depend on this side effect (i.e.
|
|
those `.cctor` not being removed). In such case the optimization can be
|
|
disabled until the correct linker annotations (e.g.
|
|
`[Preserve (Conditional=true)]`) are added.
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]cctor-beforefieldinit` to `mtouch` or `mmp`.
|
|
|
|
## Custom Attributes Removal
|
|
|
|
This optimization requires the linker to be enabled and is applied globally
|
|
on all assemblies inside the application.
|
|
|
|
This optimization removes a number of, rarely used, custom attributes from
|
|
assemblies. In turn this allows the linker to later remove the associated
|
|
code from the base class libraries (BCL). This help reduce both the
|
|
metadata and code size for your application.
|
|
|
|
This optimization is enabled, by default, on both Xamarin.iOS and
|
|
Xamarin.Mac. If some of the removed custom attributes are required for
|
|
your application you can disable this optimization, without totally
|
|
disabling the managed linker.
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]custom-attributes-removal` to `mtouch` or `mmp`.
|
|
|
|
## Force Rejected Types Removal
|
|
|
|
This optimization can be enabled when it's not possible to use the
|
|
managed linker (e.g. **Don't link**) or when the managed linker cannot
|
|
remove references to deprecated types that would cause an application
|
|
to be rejected by Apple.
|
|
|
|
References to the existing types will be renamed, e.g. `UIWebView` to
|
|
`DeprecatedWebView`, in every assemblies.
|
|
|
|
The type definition is also renamed (for validity) and all custom
|
|
attributes on the types and their members will be removed.
|
|
Code inside the members will be replaced with a
|
|
`throw new NotSupportedException ();`.
|
|
|
|
The default behavior can be overridden by passing
|
|
`--optimize=[+|-]force-rejected-types-removal` to `mtouch`.
|
|
|
|
The exact list of types might change over time and is best read directly from
|
|
the [source code](https://github.com/xamarin/xamarin-macios/blob/main/tools/linker/RemoveRejectedTypesStep.cs).
|