[NativeAOT] Use XAJavaInterop1 codegen target and unify GC bridge#12133
[NativeAOT] Use XAJavaInterop1 codegen target and unify GC bridge#12133simonrozsival wants to merge 2 commits into
Conversation
Switch NativeAOT builds from the JavaInterop1 code-generation target to
XAJavaInterop1, matching CoreCLR and MonoVM. As a result, the generated
Java Callable Wrappers now implement `mono.android.IGCUserPeer` instead
of `net.dot.jni.GCUserPeerable`, so the GC bridge processing becomes
identical for CoreCLR and NativeAOT.
* Remove the NativeAOT-specific `_AndroidJcwCodegenTarget=JavaInterop1`
default so NativeAOT falls through to the shared `XAJavaInterop1`
default in `Microsoft.Android.Sdk.DefaultProperties.targets`.
* Delete the NativeAOT-only `BridgeProcessing` override that called the
`GCUserPeerable.jiAddManagedReference`/`jiClearManagedReferences`
methods. NativeAOT already compiles the CoreCLR bridge sources and
already runs `BridgeProcessingShared::initialize_on_runtime_init`
(via `GCBridge::initialize_on_runtime_init`), which caches the
`mono.android.IGCUserPeer`/`GCUserPeer` classes. Falling through to
that shared path makes the native GC bridge identical to CoreCLR.
Dropping the now-unused JavaInterop1 codegen + runtime support
(ManagedPeer, JavaProxyObject/JavaProxyThrowable, GCUserPeerable) is a
follow-up.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8d949279-d51c-4ce0-baf2-0b1d8e919928
Now that both CoreCLR and NativeAOT use the same IGCUserPeer-based GC
bridge, the `maybe_call_gc_user_peerable_*` extension points always
return `false` and no host needs to override them. Remove the abstract
base / derived split:
* Merge `BridgeProcessingShared` into a single concrete
`BridgeProcessing` class (delete `bridge-processing-shared.hh`).
* Drop the two pure-virtual `maybe_call_gc_user_peerable_*` hooks and
the CoreCLR no-op overrides, and inline the IGCUserPeer path
directly in `add_reference`/`clear_references`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8d949279-d51c-4ce0-baf2-0b1d8e919928
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 Skeptical completeness review — this migration looks incomplete; NativeAOT apps will likely fail at runtime.
Verdict: ⚠️ Needs Changes
The two committed pieces are correct and clean:
- ✅ Codegen flip to
XAJavaInterop1. - ✅ GC bridge unification (NativeAOT now uses the shared
IGCUserPeerpath; collapsingBridgeProcessingShared→BridgeProcessingis a nice cleanup).
But flipping the JCW codegen target changes what the generated Java Callable Wrappers call at runtime, and NativeAOT doesn't implement the new registration entry point.
The missing piece: native-method registration
Under XAJavaInterop1, each JCW's static initializer emits mono.android.Runtime.register("<asm-qualified-name>", X.class, methods) (CallableWrapperType.cs:277-289). That is a native method on mono.android.Runtime (src/java-runtime/java/mono/android/Runtime.java).
- CoreCLR/MonoVM implement it:
Java_mono_android_Runtime_register(src/native/clr/host/host-jni.cc:23→host.cc:488-507) forwards to the managedRegisterJniNativescallback stored inregisterJniNativesFn, which is set only in the CoreCLR/MonoJNIEnvInit.Initializepath (JNIEnvInit.cs:156). - NativeAOT implements none of this:
src/native/nativeaot/host/host-jni.cconly exposesJNI_OnLoad/OnInit— noregister.JNIEnvInit.InitializeNativeAotRuntimenever setsargs->registerJniNativesFn(JNIEnvInit.cs:104-118).src/native/nativeaot/libnaot-android.map.txtexports nothing (global:is empty).
Today NativeAOT (JavaInterop1) registers natives via net.dot.jni.ManagedPeer.registerNativeMembers — the very class we intend to stop using. After this flip, JCWs stop calling ManagedPeer and start calling mono.android.Runtime.register, which is unresolved on NativeAOT → UnsatisfiedLinkError when the first JCW class loads.
What still needs to be done
- Provide a NativeAOT implementation of
mono.android.Runtime.register(e.g. an[UnmanagedCallersOnly(EntryPoint="Java_mono_android_Runtime_register")]export, or dynamicRegisterNatives) that routes toRegisterJniNatives. - Set
registerJniNativesFninInitializeNativeAotRuntime(currently only the CoreCLR/Mono path sets it). - Confirm
mono.android.TypeManager.Activate/n_activate(used by XAJavaInterop1 ctors,CallableWrapperConstructor.cs:48-55) is actually resolvable on NativeAOT — same registration question. - Sanity-check any other
mono.android.Runtime/TypeManagernatives the JCWs/runtime now touch, and export them in the NativeAOT version script if name-based JNI linking is used.
CI caveat
The green Android Tools Tests lane only covers MSBuild/tooling. There is likely no NativeAOT device/runtime lane in PR CI, so a green-ish CI here does not validate that apps run. This needs the local device test (make all + the NativeAOT sample / Mono.Android tests) once the registration path exists.
Good news: ManagedPeer/GCUserPeerable/JavaProxyObject removal genuinely does stay a follow-up — but only after the registration path above is in place.
|
🤖 Follow-up / resolution to my registration concern above. The missing
Action taken: documented the dependency in the PR description and kept this PR as draft. It must be rebased on Net: no code change required in this PR for registration — the inline concern is resolved by the #12121 dependency, not by adding a managed-typemap registration path. |
Summary
Switches NativeAOT builds from the JavaInterop1 code-generation target to XAJavaInterop1, matching CoreCLR and MonoVM.
Because XAJavaInterop1 Java Callable Wrappers implement
mono.android.IGCUserPeer(instead ofnet.dot.jni.GCUserPeerable), the GC bridge processing becomes identical for CoreCLR and NativeAOT.Changes
_AndroidJcwCodegenTarget=JavaInterop1default. NativeAOT now falls through to the sharedXAJavaInterop1default inMicrosoft.Android.Sdk.DefaultProperties.targets.BridgeProcessingoverride that calledGCUserPeerable.jiAddManagedReference/jiClearManagedReferences. NativeAOT already compiles the CoreCLR bridge sources and already runsBridgeProcessingShared::initialize_on_runtime_init(viaGCBridge::initialize_on_runtime_init), which caches themono.android.IGCUserPeer/GCUserPeerclasses. Falling through to that shared path makes the native GC bridge identical to CoreCLR.IGCUserPeerpath, themaybe_call_gc_user_peerable_*virtual hooks are always no-ops, soBridgeProcessingSharedis collapsed into a single concreteBridgeProcessing.Why this depends on the trimmable typemap (#12121)
Flipping the JCW codegen target changes how the generated Java Callable Wrappers register their native methods at runtime — their static initializers now call into
mono.android.Runtimeinstead ofnet.dot.jni.ManagedPeer. The registration mechanism that backs this differs by typemap:managedtypemap (old NativeAOT default): JCWs would rely onJava_mono_android_Runtime_register, a name-linked native symbol implemented only in the CoreCLR/MonoVM hosts (src/native/clr/host/host-jni.cc,host.cc) and wired throughregisterJniNativesFn(set only inJNIEnvInit.Initialize). NativeAOT implements none of this (src/native/nativeaot/host/host-jni.cchas noregister,InitializeNativeAotRuntimenever setsregisterJniNativesFn, andlibnaot-android.map.txtexports nothing) →UnsatisfiedLinkError.trimmabletypemap (NativeAOT default after [NativeAOT] Make the trimmable typemap the default #12121): Provides its own AOT-safe registration.TrimmableTypeMap.RegisterNativeMethods()dynamically registersmono.android.Runtime.registerNatives(Class)viaJniEnvironment.Types.RegisterNativespointing at the managed[UnmanagedCallersOnly] OnRegisterNatives(src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs:133-149,561-587) — no name-linked JNI symbol required.TrimmableTypeMapTypeManager.RegisterNativeMemberseven asserts that natives are registered by the JCW static initializers.So this migration only works when NativeAOT is on the trimmable typemap. #12121 makes that the default; this PR builds on it.
Follow-up (not in this PR)
Once this migration is verified on the trimmable typemap, drop the now-unused JavaInterop1 codegen + runtime support:
ManagedPeer,JavaProxyObject/JavaProxyThrowable,net.dot.jni.GCUserPeerable, and related plumbing.Verification
Draft until #12121 merges and this branch is rebased on
main. Then:make all+ NativeAOT sample / Mono.Android device tests on the trimmable typemap.🤖 Generated with GitHub Copilot CLI