Commit 8bc7a3e
authored
Enable marshal methods support by default (#7351)
Context: 5271f3e
Context: e1af958
Context: 186a9fc
Context: 903ba37
Context: a760281
Context: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration
Complete the LLVM Marshal Methods effort sketched out in e1af958.
LLVM Marshal Methods are only supported in .NET Android, *not*
Xamarin.Android.
A *Marshal Method* is a JNI Callable C function (pointer) which has
[parameter types and return types which comply with the JNI ABI][0].
[`generator`][1] emits marshal methods as part of the binding, which
are turned into Delegate instances at runtime as part of
[Java Type Registration][2].
*LLVM Marshal Methods* turn this runtime operation -- looking up
`generator`-emitted marshal methods and registering those methods
with Java -- into a *build-time* operation, using LLVM-IR to generate
[JNI Native Method Names][3] which will then be contained within
`libxamarin-app.so`. LLVM Marshal Methods will also *remove* the
previous Reflection-based infrastructure from relevant types.
LLVM Marshal Methods are *enabled by default* for ***Release***
configuration builds in .NET 8, and disabled by default for Debug
builds. The new `$(AndroidEnableMarshalMethods)` MSBuild property
explicitly controls whether or not LLVM Marshal Methods are used.
LLVM Marshal Methods are *not* available in Classic Xamarin.Android.
~~ Build Phase: Scanning for Compatible Types ~~
During the application build, all `Java.Lang.Object` and
`Java.Lang.Throwable` subclasses are scanned as part of
[Java Callable Wrapper generation][4], looking for "un-bound"
(user-written) types which override `abstract` or `virtual`
methods, or implement interface members. This is done to emit
Java Callable Wrappers, Java code which "mirrors" the C# code with
an appropriate base class, interface implementation list, and
Java `native` method declarations for "virtual" member overrides.
This scanning process is updated for LLVM Marshal Methods to classify
each type to see if it requires the legacy Delegate-based
registration mechanism, as constructs such as
`[Java.Interop.ExportAttribute]` cannot (yet) be used with
LLVM Marshal Methods.
~~ Build Phase: Java Callable Wrapper Generation ~~
For example, given the C# type:
// C#
public partial class MainActivity : Activity {
protected override void OnCreate (Bundle? state) => …
}
Then the resulting Java Callable Wrapper *without* LLVM Marshal
Methods enabled will be:
// Java + No LLVM Marshal Methods
public /* partial */ class MainActivity extends Activity {
static {
String __md_methods =
"n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n";
mono.android.Runtime.register ("Example.MainActivity, ExampleAssembly", MainActivity.class, __md_methods);
}
public void onCreate (android.os.Bundle p0) {n_onCreate(p0);}
private native void n_onCreate (android.os.Bundle p0);
}
When LLVM Marshal Methods are enabled, the Java Callable Wrapper
has no static constructor, nor any call to `Runtime.register()`.
~~ Build Phase: Marshal Method Wrapper ~~
Consider the binding infrastructure code that `generator` emits for
`Android.App.Activity.OnCreate()`:
namespace Android.App {
public partial class Activity {
static Delegate? cb_onCreate_Landroid_os_Bundle_;
#pragma warning disable 0169
static Delegate GetOnCreate_Landroid_os_Bundle_Handler ()
{
if (cb_onCreate_Landroid_os_Bundle_ == null)
cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_);
return cb_onCreate_Landroid_os_Bundle_;
}
static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
var __this = global::Java.Lang.Object.GetObject<Android.App.Activity> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle> (native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
__this.OnCreate (savedInstanceState);
}
#pragma warning restore 0169
[Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")]
protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState)
{
const string __id = "onCreate.(Landroid/os/Bundle;)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle);
_members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
} finally {
global::System.GC.KeepAlive (savedInstanceState);
}
}
}
}
When LLVM Marshal Methods are enabled, the following IL
transformations are performed:
* The `static Delegate? cb_…` field is removed.
* The `static Delegate Get…Handler()` method is removed.
* A new `static … n_…_mm_wrapper()` method is added.
The `n_…_mm_wrapper()` method is responsible for exception marshaling
and for `bool` marshaling. The `n_…_mm_wrapper()` method has the
[`UnmanagedCallersOnlyAttribute`][5], and works by calling the
existing `n_…()` method:
namespace Android.App {
public partial class Activity {
// Added
[UnmanagedCallersOnly]
static void n_OnCreate_Landroid_os_Bundle__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
try {
n_OnCreate_Landroid_os_Bundle_ (jnienv, native__this, native_savedInstanceState);
}
catch (Exception __e) {
Android.Runtime.AndroidEnvironmentInternal.UnhandledException (__e);
}
}
}
}
~~ Build Phase: LLVM-IR Marshal Method Generation ~~
For each Java `native` method declaration contained in Java Callable
Wrappers which support LLVM Marshal Methods, LLVM-IR is used to
generate the JNI Native Method with the `Java_…` symbol name:
using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState);
static android_app_activity_on_create_bundle_fn android_app_activity_on_create_bundle = nullptr;
extern "C" JNIEXPORT void
JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) noexcept
{
if (android_app_activity_on_create_bundle == nullptr) {
get_function_pointer (
16, // mono image index; computed at build time
0, // class index; computed at build time
0x0600055B, // method token; computed at build time
reinterpret_cast<void*&>(android_app_activity_on_create_bundle) // target pointer
);
}
android_app_activity_on_create_bundle (env, klass, savedInstanceState);
}
~~ Other Changes ~~
The new `Android.Runtime.JNIEnvInit` type was split out of the
`Android.Runtime.JNIEnv` type to further reduce startup overhead, as
there are fewer fields to initialize.
The `Mono.Android.Runtime.dll` assembly is added because the
Marshal Method Wrapper needs to be able to invoke what *was*
`AndroidEnvironment.UnhandledException()`, *while also* updating
`Mono.Android.dll`! `Mono.Android.Runtime.dll` allows the marshal
method wrappers to reliably use
`Android.Runtime.AndroidEnvironmentInternal.UnhandledException()`,
which will *never* be changed by the marshal method wrapper
infrastructure.
~~ Results ~~
Marshal methods make application startup around 3.2% faster (the
bigger the app the more performance gains), with a bit room for
future improvements (by eliminating wrapper methods and other
optimizations):
[.NET Podcasts][6] app test results:
| Before | After | Δ | Notes |
| ------- | ------- | -------- | ---------------------------------------------- |
| 868.500 | 840.400 | -3.24% ✓ | preload disabled; 32-bit build; no compression |
| 863.700 | 837.600 | -3.02% ✓ | preload disabled; 64-bit build; no compression |
| 872.500 | 850.100 | -2.57% ✓ | preload enabled; 64-bit build |
| 877.000 | 854.800 | -2.53% ✓ | preload disabled; 64-bit build |
| 859.300 | 839.800 | -2.27% ✓ | preload enabled; 64-bit build; no compression |
| 871.700 | 853.100 | -2.13% ✓ | preload enabled; 32-bit build |
| 860.600 | 842.300 | -2.13% ✓ | preload enabled; 32-bit build; no compression |
| 869.500 | 852.500 | -1.96% ✓ | preload disabled; 32-bit build |
Maui Hello World app test results:
| Before | After | Δ | Notes |
| ------- | ------- | -------- | ---------------------------------------------- |
| 374.800 | 365.500 | -2.48% ✓ | preload disabled; 64-bit build |
| 374.100 | 365.600 | -2.27% ✓ | preload disabled; 32-bit build |
| 369.100 | 364.400 | -1.27% ✓ | preload enabled; 32-bit build |
| 364.300 | 360.600 | -1.02% ✓ | preload enabled; 32-bit build; no compression |
| 368.900 | 365.400 | -0.95% ✓ | preload enabled; 64-bit build |
| 362.500 | 359.400 | -0.86% ✓ | preload disabled; 32-bit build; no compression |
| 361.100 | 361.600 | +0.14% ✗ | preload enabled; 64-bit build; no compression |
| 359.200 | 368.000 | +2.39% ✗ | preload disabled; 64-bit build; no compression |
[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments
[1]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#generator
[2]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration
[3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names
[4]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-callable-wrapper-generator
[5]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-7.0
[6]: https://github.com/microsoft/dotnet-podcasts/tree/net7.01 parent 2c7d72d commit 8bc7a3e
File tree
79 files changed
+2585
-1098
lines changed- Documentation/guides
- building-apps
- internals
- build-tools
- automation/yaml-templates
- create-packs
- scripts
- src
- Microsoft.Android.Sdk.ILLink/PreserveLists
- Mono.Android.Runtime
- Android.Runtime
- Properties
- Mono.Android
- Android.App
- Android.Runtime
- Java.Interop
- Java.Lang
- java/mono/android
- Xamarin.Android.Build.Tasks
- Linker/PreserveLists
- MSBuild/Xamarin/Android
- Microsoft.Android.Sdk/targets
- Tasks
- Tests
- Xamarin.Android.Build.Tests
- Utilities
- Xamarin.ProjectTools/Resources/Base
- Utilities
- LlvmIrGenerator
- java-runtime
- java/mono/android/app
- monodroid
- jni
- tests/Mono.Android-Tests
- Java.Interop-Tests
- Runtime-Microsoft.Android.Sdk
- Xamarin.Android.RuntimeTests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
79 files changed
+2585
-1098
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | 30 | | |
35 | 31 | | |
36 | 32 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
424 | 424 | | |
425 | 425 | | |
426 | 426 | | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
427 | 436 | | |
428 | 437 | | |
429 | 438 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
| 95 | + | |
| 96 | + | |
95 | 97 | | |
96 | 98 | | |
97 | 99 | | |
| |||
296 | 298 | | |
297 | 299 | | |
298 | 300 | | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
299 | 307 | | |
300 | 308 | | |
301 | 309 | | |
| |||
456 | 464 | | |
457 | 465 | | |
458 | 466 | | |
| 467 | + | |
459 | 468 | | |
460 | 469 | | |
461 | 470 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
66 | 69 | | |
67 | 70 | | |
68 | 71 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
27 | 26 | | |
28 | 27 | | |
29 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
106 | | - | |
| 106 | + | |
107 | 107 | | |
108 | 108 | | |
109 | 109 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
0 commit comments