-
Notifications
You must be signed in to change notification settings - Fork 533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable marshal methods support by default #7351
Conversation
The point of this commit is only to generate code and make sure it's valid as far as compiling and linking are concerned. The code has not been tested at run time as not all the infrastructure on the Xamarin.Android side is implemented yet. This is on purpose, to keep PRs smaller. The majority of this PR introduces various classes, enums and structures related to code generation. Support for various LLVM IR instructions is limited only to those we actually use and only to elements of those constructions that we use. As such, it's not a general purpose code generator which allows us to make some assumptions and take some shortcuts (without compromising correctness and validity of the generated code) Portions of the PR (the native type handling system) are to be treated as proof-of-concept as they are not as optimized (design wise) as they should be. The reason for this limitation is that it requires modifying the previous LLVM IR data generation code and it would contribute to this PR's already substantial size. The next PR in the series will take care of that rewrite as well as it will focus on implementing the runtime side of marshal methods, making it possible to actually run applications which use marshal methods. What this PR implements is the following: * LLVM IR * function and instruction attributes * function parameter (declaration/definition) and argument (runtime) handling * function variable (including parameters) handling, including unnamed local variables * support for native function signatures and pointers to functions * The ret, store, load, icmp, br, call and phi instructions * Marshal method generator * managed to JNI signature and symbol name translations
Implement missing runtime support so that applications are able to actually run. Also fix issues with marshal method overloads as well as add code to handle the case where two unrelated methods (from different types) end up using the same JNI symbol name: * `Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked * `System.Boolean Android.Views.ActionMode/ICallback::OnActionItemClicked(Android.Views.ActionMode,Android.Views.IMenuItem)` * `System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnActionItemClicked(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenuItem)`
* main: LEGO: Merge pull request 7221 LEGO: Merge pull request 7219 [Xamarin.Android.Build.Tasks] use `ToJniName(type, cache)` (dotnet#7211) [docs] Synchronize with MicrosoftDocs/xamarin-docs (dotnet#7208) [Mono.Android] Remove System.Linq usage (dotnet#7210) Bump to Android NDK r25 (dotnet#6764) Bump to mono/opentk@daa9b2d5 (dotnet#7192) [Mono.Android] Optional NTLMv2 support in AndroidMessageHandler (dotnet#6999) Bump to dotnet/installer@53587f9 7.0.100-rc.1.22374.1 (dotnet#7198)
* mm-codegen: LEGO: Merge pull request 7221 LEGO: Merge pull request 7219 [Xamarin.Android.Build.Tasks] use `ToJniName(type, cache)` (dotnet#7211) [docs] Synchronize with MicrosoftDocs/xamarin-docs (dotnet#7208) [Mono.Android] Remove System.Linq usage (dotnet#7210) Bump to Android NDK r25 (dotnet#6764) Bump to mono/opentk@daa9b2d5 (dotnet#7192) [Mono.Android] Optional NTLMv2 support in AndroidMessageHandler (dotnet#6999) Bump to dotnet/installer@53587f9 7.0.100-rc.1.22374.1 (dotnet#7198)
* main: Bump to dotnet/java-interop@a5756ca8. (dotnet#7226) Bump `$(AndroidNet6Version)` to 32.0.447 (dotnet#7224)
* mm-codegen: Bump to dotnet/java-interop@a5756ca8. (dotnet#7226) Bump `$(AndroidNet6Version)` to 32.0.447 (dotnet#7224)
Also, deal with duplicate native symbol names
* main: [Mono.Android] fix crash on startup with EnableLLVM (dotnet#7188) [ci] Add Android Designer test template (dotnet#7227)
* mm-codegen: [Mono.Android] fix crash on startup with EnableLLVM (dotnet#7188) [ci] Add Android Designer test template (dotnet#7227)
* main: [Localization] Import translated resx files (dotnet#7190)
* mm-codegen: [Localization] Import translated resx files (dotnet#7190)
Hit a snag with one of the callbacks using non-blittable arguments (`bool` in this case), but it seems to be the last (famous last words) obstacle preventing the app from starting. A handful of hacks are needed ATM, too.
* main: [build] update to latest JDKs (dotnet#7236)
* mm-codegen: [build] update to latest JDKs (dotnet#7236)
Unfortunately, 133 out of 182 methods are still registered dynamically, the reason being that they either return `bool` or take it as one of their parameters. `bool` isn't a blittable type and thus such methods cannot be used with `[UnregisteredCallersOnly]`. To move farther, we need to modify the generator to stop generating native callbacks (just them) with `bool`.
* main: Bump to dotnet/installer@8f63969 7.0.100-rc.1.22407.1 (dotnet#7217)
* mm-codegen: Bump to dotnet/installer@8f63969 7.0.100-rc.1.22407.1 (dotnet#7217)
* main: [ci] Upload test assemblies after signing (dotnet#7241)
* mm-codegen: [ci] Upload test assemblies after signing (dotnet#7241)
Beginnings of the generator
The app still crashes for some reason (appears something is not decorated with the `[UnmanagedCallersOnly]` attribute) but the IL code generation is complete and the app doesn't report any native/runtime linking errors. TBC next week
* main: Bump to xamarin/monodroid@210073e1 (dotnet#7272) [OneLoc] Localize Microsoft.Android.Templates (dotnet#7248) [README] Add links to XA 13.0 release installers (dotnet#7251) Bump to dotnet/installer@716bd17 7.0.100-rc.1.22409.23 (dotnet#7247) Bump manifest-merger from 30.2.1 to 30.2.2 (dotnet#7238)
* main: Localized file check-in by OneLocBuild Task: Build definition ID 11410: Build ID 6759133 (dotnet#7423) LEGO: Merge pull request 7422 Bump to 32.0.476 .NET 6 Android packages (dotnet#7415)
@@ -33,6 +33,7 @@ by projects that use the Microsoft.Android framework in .NET 6+. | |||
<ItemGroup> | |||
<_AndroidRefPackAssemblies Include="$(JavaInteropSourceDirectory)\bin\$(Configuration)-net7.0\ref\Java.Interop.dll" /> | |||
<_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.dll" /> | |||
<_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.Runtime.dll" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fear that I'm gonna be "bikeshedding" half the naming here -- and I am! -- but…
Mono.Android.Runtime
is not "future-facing". We may stick with Mono.Android.dll
"forever", but there are equal odds that we may do a Microsoft.Android.dll
name in the future (.NET 10?).
As such, we should choose names which are unlikely to need changing. Possible suggestions:
Microsoft.Android.Startup.dll
Microsoft.Android.Internals.dll
Microsoft.Android.Interop.dll
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather keep it Mono.Android.Runtime
for now, since it pairs nicely with Mono.Android
(and that was the reason I chose this name)
@@ -0,0 +1,18 @@ | |||
using System; | |||
|
|||
namespace Android.Runtime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…and more name bikeshedding: I think it was a historical mistake to use Android.Runtime
; we're lucky that Google never introduced an android.runtime
package.
I think Microsoft.Android.Internals
would be a good namespace, or just Microsoft.Android
, as all the types here are internal
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, I'd rather keep the Android.Runtime
namespace, because this assembly includes sources from Mono.Android
that are in this namespace. If we want to rename it, we should rename it all at once one day.
src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml
Outdated
Show resolved
Hide resolved
<method name="PropagateUncaughtException" /> | ||
</type> | ||
<type fullname="Android.Runtime.JNIEnvInit"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forget; does JNIEnvInit
need to remain in Mono.Android.dll
? Or can it be moved to the assembly containing AndroidRuntimeInternal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It remains there for classic Xamarin, it is moved to the new assembly for .NET
internal static void UnhandledException (Exception e) | ||
{ | ||
if (UnhandledExceptionHandler == null) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should abort
; if we hit here, either we're very early in startup -- which means aborts would be bad ;-) -- or something is Very Wrong™.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't abort explicitly here. Hitting it during startup is very unlikely, and if it's not properly initialized, the app will crash anyway, implicitly aborting.
|
||
namespace Android.Runtime | ||
{ | ||
static internal class JNIEnvInit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be moved to the other new assembly? (I think I asked this before, but forgot the answer.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is included in the new assembly for .NET, for classic it remains in Mono.Android, thus the source is here and the new assembly includes it with a relative path.
try { | ||
callback (jnienv, klazz); | ||
} catch (Exception e) when (_unhandled_exception (e)) { | ||
AndroidEnvironment.UnhandledException (e); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you update this file? Manually? Or via Visual Studio (for Mac?) and re-evaluating JNINativeWrapper.g..tt
?
My suspicion -- based on these whitespace changes -- is that you updated manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did update it manually.
Also need an "epic" commit message tying all the pieces together and explaining what it does and how it works; see also: The commit message should "revisit: e1af958, explain that the TODO item "Update |
* main: [Mono.Android] Improve generated API docs diff (dotnet#7427) Localized file check-in by OneLocBuild Task: Build definition ID 11410: Build ID 6765281 (dotnet#7430) LEGO: Merge pull request 7429 Bump to dotnet/java-interop@a0728ed5 (dotnet#7420)
It was a remnant from the attempt to move the `JNIEnvInit` class to a separate assembly.
Context: 5271f3e109a2242caa3bd7801dfad9c1683488d7
Context: e1af9587bb98d4c249bbc392ceccc2b53ffff155
Context: 186a9fcfac14ba15af1d8ad091dfa6612232fdcb
Context: 903ba37ce70d2840983774e1d6fb55f8002561e2
Context: a760281bb1c86baeedc54e3bb5333063bfcd48aa
Context: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration
Complete the LLVM Marshal Methods effort sketched out in e1af9587.
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.0 |
* main: Enable marshal methods support by default (dotnet#7351) Bump to dotnet/java-interop@e1ee4b1c, xamarin/android-api-docs@bc5443dc (dotnet#7358) [xaprepare] Support Gentoo Linux for OS detection (dotnet#7442) [Xamarin.Android.Build.Tasks] Avoid XA5207 for design-time builds (dotnet#7434) [Mono.Android] Improve generated API docs diff (dotnet#7427) Localized file check-in by OneLocBuild Task: Build definition ID 11410: Build ID 6765281 (dotnet#7430) LEGO: Merge pull request 7429 Bump to dotnet/java-interop@a0728ed5 (dotnet#7420)
* main: Enable marshal methods support by default (dotnet#7351) Bump to dotnet/java-interop@e1ee4b1c, xamarin/android-api-docs@bc5443dc (dotnet#7358)
Context: 5271f3e
Context: e1af958
Context: 186a9fc
Context: 903ba37
Context: a760281
Note: marshal methods are supported only in the .NET version of
Xamarin.Android, "classic" code does not support them.
This is the last commit in a long series (see the context links above)
which implements support for so called Marshal Methods - a mechanism
to register API callbacks with the Android Java runtime/VM that allow
us to implement Java method overrides in managed code. Java VM uses the
Java Native Interface (JNI) library and API to facilitate this for
native code developers (managed code is considered "native" in this
context) by allowing them to implement portions of Java classes in
language of their choice, so long as the language can call JNI
functions that "register" the the native implementations of Java class
member methods. Alternatively, the native implementations can be
placed in a shared library as a public symbol with name that follows
JNI requirements, so that the Java runtime can find the native
implementation of a method found in a certain Java class belonging to
a certain Java package.
Up until this commit,
Mono/Xamarin/.NET Android
has been using the"dynamic" registration method, which incurred a non-trivial amount of
overhead, especially when starting the application on device and made
both registration and execution of managed code unnecessarily slower.
With this commit we add a way to use the other, "static", mechanism
which works by generating native functions, correctly named for JNI to
be able to find them, at the application build time. This approach
allows us to completely remove runtime registration overhead at
application startup, since it's replaced by native symbol lookup
performed by Java whenever a native class method is called. Such
lookup is usually very fast, since the native loader in the Android
system keeps symbol tables in memory once the shared library is
loaded. Marshal methods support is enabled by default for
Release
builds and always disabled for
Debug
buillds. In order todisable it for
Release
builds, one needs to set the$(AndroidEnableMarshalMethods)
MSBuild property toFalse
.Instead of using the older native assembly generator (which only
supported generating native data sections), marshal methods use the
LLVM IR language, subsequently compiled into native machine code by
the LLVM IR compiler (
llc
). This ascertains that the generatednative code as well as object files and shared libraries adhere to all
the requirements of the target platform ABI and enables certain
optimizations.
During application build we scan for all the managed types that mirror
their Java counterparts and implement abstract or override virtual
methods. Based on this selection we generate Java code that includes
declarations of native Java methods as described above, for whose we
generate the marshal methods chunks that are subsequently invoked at
the application runtime. While scanning, a marshal method classifier
is used to analyze whether the candidate method conforms to a set of
requirements that allow us to register the given native method using
the "static" approach. If yes, such method is removed from the set of
dynamically registered methods and, ideally, at the end of build we
have no methods that require the dynamic approach. However, if we
do encounter such methods, they are registered using the "old"
dynamic mechanism without conflicts between it and the generated
marshal methods code.
Managed methods selected for "static" registration need to be slightly
transformed in order to make them directly invokable from native code
as well as wrapped in such way that allows us to capture and handle
any unhandled exceptions, should they be thrown during the method
execution. This is done by a marshal methods assembly rewriter, which
makes the following changes:
[UnmanagedCallersOnly]
attribute to each selectedmethod.
a special unhandled exception handler, whose task is to properly
propagate the exceptions so that they don't break neither Java nor
Managed call stacks.
value or parameters (currently we identify only
bool
), the aboveexception wrapper includes code that casts the non-blittable types
to/from the blittable types without losing any information.
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 app test results:
Maui Hello World app test results: