Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Android.Build.Tasks, monodroid] LLVM Marshal Methods Infra (d…
…otnet#7004) Context: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration Introduce low-level "plumbing" for future JNI marshal method work. A JNI marshal method is a [JNI-callable][0] function pointer provided to [`JNIEnv::RegisterNatives()`][1]. Currently, JNI marshal methods are provided via the interaction between `generator` and `JNINativeWrapper.CreateDelegate()`: * `generator` emits the "actual" JNI-callable method. * `JNINativeWrapper.CreateDelegate()` uses System.Reflection.Emit to *wrap* the `generator`-emitted for exception marshaling. (Though see also 32cff43.) JNI marshal methods are needed for all Java-to-C# transitions. Consider the virtual `Activity.OnCreate()` method: partial class Activity { static Delegate? cb_onCreate_Landroid_os_Bundle_; 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); } // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='onCreate' and count(parameter)=1 and parameter[1][@type='android.os.Bundle']]" [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) => … } `Activity.n_OnCreate_Landroid_os_Bundle_()` is the JNI marshal method, responsible for marshaling parameters from JNI values into C# types, forwarding the method invocation to `Activity.OnCreate()`, and (if necessary) marshaling the return value back to JNI. `Activity.GetOnCreate_Landroid_os_Bundle_Handler()` is part of the type registration infrastructure, providing a `Delegate` instance to `RegisterNativeMembers .RegisterNativeMembers()`, which is eventually passed to `JNIEnv::RegisterNatives()`. While this works, it's not incredibly performant: unless using one of the optimized delegate types (32cff43 et. al), System.Reflection.Emit is used to create a wrapper around the marshal method, which is something we've wanted to avoid doing for years. Thus, the idea: since we're *already* bundling a native toolchain and using LLVM-IR to produce `libxamarin-app.so` (b21cbf9, 5271f3e), what if we emitted [Java Native Method Names][2] and *skipped* all the done as part of `Runtime.register()` and `JNIEnv.RegisterJniNatives()`? Given: class MyActivity : Activity { protected override void OnCreate(Bundle? state) => … } During the build, `libxamarin-app.so` would contain the function: JNIEXPORT void JNICALL Java_crc…_MyActivity_n_1onCreate (JNIEnv *env, jobject self, jobject state); During App runtime, the `Runtime.register()` invocation present in [Java Callable Wrappers][3] would either be omitted or would be a no-op, and Android/JNI would instead resolve `MyActivity.n_onCreate()` as `Java_crc…_MyActivity_n_1onCreate()`. Many of the specifics are still being investigated, and this feature will be spread across various areas. We call this effort "LLVM Marshal Methods". First, prepare the way. Update `Xamarin.Android.Build.Tasks.dll` and `src/monodroid` to introduce support for generating JNI marshal methods into `libxamarin-app.so`. Most of the added code is *disabled and hidden* behind `#if ENABLE_MARSHAL_METHODS`. ~~ TODO ~~ Other pieces, in no particular order: * Update [`Java.Interop.Tools.JavaCallableWrappers`][4] so that static constructors aren't needed when LLVM Marshal Methods are used. * Update [`generator`][5] so that *two* sets of marshal methods are emitted: the current set e.g. `Activity.n_OnCreate_Landroid_os_Bundle_()`, and an "overload" set which has [`UnmanagedCallersOnlyAttribute`][6]. LLVM Marshal Methods will be able to directly call these "unmanaged marshal methods" without the overhead of `mono_runtime_invoke()`; see also f48b97c. * Finish the LLVM code generator so that LLVM Marshal Methods are emitted into `libxamarin-app.so`. * Update the linker so that much of the earlier marshal method infrastructure is removed in Release apps. When LLVM Marshal Methods are used, there is no need for `Activity.cb_onCreate_Landroid_os_Bundle_`, `Actvitiy.GetOnCreate_Landroid_os_Bundle_Handler()`, or the `Activity.n_OnCreate_Landroid_os_Bundle_()` without `[UnmanagedCallersOnly]`. Meanwhile, we cannot remove the existing infrastructure, as the current System.Reflection.Emit-oriented code is needed for faster app builds, a desirable feature of Debug configuration builds. LLVM Marshal Methods will be a Release configuration-only feature. [0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives [2]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names [3]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration [4]: https://github.com/xamarin/java.interop/tree/main/src/Java.Interop.Tools.JavaCallableWrappers [5]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#generator [6]: https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0
- Loading branch information