[runtime] Extend JNI remapping for R8 - #12817
Closed
simonrozsival wants to merge 19 commits into
Closed
simonrozsival wants to merge 19 commits into
simonrozsival wants to merge 19 commits into
Conversation
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical native layout compatibility and moderate JNI owner-retention issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (2)
What changed in this PR
Extends the reusable JNI remapping runtime contract for R8, including reverse type, field, descriptor, and inherited-member remapping.
Changes:
- Expands native remapping tables and XML generation.
- Integrates remapping into trimmable type maps and Java.Interop lookups.
- Adds runtime, generator, and Java fixture tests.
| File | Reviewed change |
|---|---|
src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs |
Generates expanded remapping tables. Nit: use NullableExtensions.IsNullOrEmpty() at both reported locations. |
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateJniRemappingNativeCodeTests.cs |
Tests generated LLVM remapping data. |
src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs |
Parses reverse, method, and field remapping entries. |
src/native/common/include/managed-interface.hh |
Extends the native remapping layout. Critical bug: checked-in MonoVM/CoreCLR stubs still use the old layout. |
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs |
Applies forward type remapping during casts. |
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs |
Adds reverse type and field lookup support. |
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs |
Supports remapped activation and registration. Moderate performance issue: reverse results are repeatedly allocated instead of cached. |
src/Mono.Android/Microsoft.Android.Runtime/JniRemappingLookup.cs |
Implements native remapping table lookups. |
external/Java.Interop/tests/Java.Interop-Tests/java/net/dot/jni/test/FieldRemapRenamedBase.java |
Adds renamed-field test fixture. |
external/Java.Interop/tests/Java.Interop-Tests/java/net/dot/jni/test/FieldRemapDerived.java |
Adds derived-field test fixture. |
external/Java.Interop/tests/Java.Interop-Tests/java/net/dot/jni/test/FieldRemapBase.java |
Adds base-field test fixture. |
external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs |
Tests field, inheritance, and descriptor remapping. |
external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs |
Configures remapping test data. |
external/Java.Interop/tests/Java.Interop-Tests/Java.Interop-Tests.csproj |
Includes Java test fixtures. |
external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt |
Records new public APIs. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniType.cs |
Adds non-throwing field lookup helpers. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs |
Adds default field-remapping behavior. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs |
Defines field-remapping APIs. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs |
Adds inherited static-method fallback. Moderate bug: preserve the target declaring type. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs |
Adds static-field remapping. Moderate bug: retain the target owner for static access. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs |
Applies method and constructor remapping. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs |
Applies instance-field remapping. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs |
Adds shared remapping and base-member helpers. |
> AI-generated fix. Produced by the `nightly-fix-finder` agentic workflow. ### Problem `JavaSourceUtils` emitted a hardcoded, uncoded error when an `@(Reference)` item was neither a Java source directory nor a `.jar` or `.aar` file. This customer-facing diagnostic bypassed the build task resource system and could not be localized. ### Fix Added localized error `XA1037` to the English resources and checked-in accessor, and updated `JavaSourceUtils` to emit the coded resource while preserving the message, argument, severity, and control flow. Added the corresponding XA1037 message documentation, index entry, and table-of-contents entry. ### Validation - `AndroidSdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-sdk" AndroidNdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-ndk" ./build.sh Prepare` — passed - `GRADLE_USER_HOME=/tmp/gh-aw/agent/gradle AndroidSdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-sdk" AndroidNdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-ndk" ./dotnet-local.sh build src/AndroidBuildConfig/AndroidBuildConfig.csproj --no-restore -m:1` — passed - `GRADLE_USER_HOME=/tmp/gh-aw/agent/gradle AndroidSdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-sdk" AndroidNdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-ndk" ./dotnet-local.sh build external/Java.Interop/src/Java.Interop.Tools.Maven/Java.Interop.Tools.Maven.csproj --no-restore -m:1` — passed - `GRADLE_USER_HOME=/tmp/gh-aw/agent/gradle AndroidSdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-sdk" AndroidNdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-ndk" ./dotnet-local.sh build src/Xamarin.Android.Tools.Aidl/Xamarin.Android.Tools.Aidl.csproj --no-restore -m:1` — passed - `GRADLE_USER_HOME=/tmp/gh-aw/agent/gradle AndroidSdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-sdk" AndroidNdkDirectory="$GITHUB_WORKSPACE/bin/Debug/android-ndk" ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj --no-restore -p:BuildProjectReferences=false -m:1` — passed ### Fix-finder metadata - Script: `08-string-literal-error-messages` - Score: `26/30` (actionability: `9`, safety: `9`, scope: `8`) > Generated by [Nightly Fix Finder](https://github.com/dotnet/android/actions/runs/35045607028) · copilot · gpt56 · 105.2 AIC · ⌖ 27.2 AIC · ⊞ 23.3K · [◷](https://github.com/search?q=repo%3Adotnet%2Fandroid+%22gh-aw-workflow-id%3A+nightly-fix-finder%22&type=pullrequests) <!-- gh-aw-agentic-workflow: Nightly Fix Finder, engine: copilot, model: gpt-5.6-sol, id: 35045607028, workflow_id: nightly-fix-finder, run: https://github.com/dotnet/android/actions/runs/35045607028 --> <!-- gh-aw-workflow-id: nightly-fix-finder --> <!-- gh-aw-workflow-call-id: dotnet/android/nightly-fix-finder -->
simonrozsival
force-pushed
the
simonrozsival-extend-jni-remapping-contract
branch
from
September 18, 2026 11:04
7e79ab4 to
01b9dc0
Compare
Context: #12354 Context: #12409 Context: #7479 Mono's TypeManager already caches peer activation constructors, but Microsoft.Android.Runtime.JavaMarshalValueManager.TryConstructPeer() still repeats reflection lookup for CoreCLR. Add the same cache pattern privately to JavaMarshalValueManager without adding a dependency on the legacy TypeManager implementation. Preserve XA-first lookup, JI fallback, missing-constructor results, constructor matching, existing peer identity, exception behavior, JNI reference ownership, and constructor-preservation annotations. Add focused coverage for cached/concurrent resolution and actual CoreCLR activation, including constructor failures and reference ownership. The trimmed Release CoreCLR device run passes all 13 new cases and 3 existing ReflectionCreateProxy cases. A Pixel 7 benchmark of warm TryConstructPeer activation and disposal improves from 3161.501 ns to 2915.512 ns (7.78% less time), with managed allocations reduced from 248 B to 144 B per operation. Separate matched Jetchat and MAUI sample-content startup checks detect no statistically clear regression; they do not establish a startup speedup. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bumps com.android.tools:r8 from 9.4.17 to 9.4.24. --- updated-dependencies: - dependency-name: com.android.tools:r8 dependency-version: 9.4.24 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Motivation The existing Intune/MAM JNI method-remapping path stores target type and method names as stable NUL-terminated UTF-8 strings in generated native data. The old path materialized those names as managed UTF-16 strings and then encoded them back to UTF-8 for `FindClass` and `GetMethodID`. That round trip is unnecessary and becomes more important as remapping is reused by larger consumers such as R8. ## Approach - Add pointer-backed target type, method-name, and method-signature values to `ReplacementMethodInfo`. - Keep pointer and string representations independent so reading a compatibility string property does not implicitly decode native memory. - Add `JniType` lookup paths for pointer/pointer and mixed pointer/span member names and signatures. - Let `JniPeerMembers` retain a replacement type pointer and use it directly for `FindClass` and later member-remapping lookups. - Keep native pointers in `JniMethodInfo` Debug metadata and decode them only if `Name`, `Signature`, or `ToString()` is explicitly requested. - Retain the existing string/span paths for custom `JniTypeManager` implementations. - Document UTF-8 encoding, NUL termination, ownership, and lifetime requirements for every pointer API. The successful generated-remapping path therefore passes the pregenerated UTF-8 type, method name, and optional signature directly to JNI without copying them or converting them to a managed string. This PR is method-only and does not add R8, field remapping, reverse-type mapping, inherited-member fallback, or NativeAOT remapping support. ## Relationship to other PRs - This PR is based directly on `main` and provides the Java.Interop representation and JNI lookup primitives. - #12796 supplies the generated pointers, performs managed table search, and removes the native remapping P/Invokes. - #12692 can build on this stack to add the R8-specific field, reverse-type, inherited-member, and NativeAOT pieces. ## Validation - Java.Interop Debug build. - Java.Interop `JniPeerMembersTests`: 12 passed, 1 skipped. - The JVM fixture exercises stable unmanaged UTF-8 type/name/signature storage, signature fallback, instance/static lookup, and instance-to-static remapping.
Emit the existing Intune remapping tables as a self-describing data block, pass it through JNIEnv initialization, and share UTF-8 binary lookup across MonoVM and CoreCLR without per-call P/Invokes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use System.Text.Ascii for the common UTF-8/UTF-16 comparison path and a streaming Rune fallback for non-ASCII names. Keep replacement types and exact method signatures in generated native memory, avoid source metadata strings, and cache generator UTF-8 sort keys. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise allocation-free UTF-8/UTF-16 comparison for a non-ASCII replacement type and method name through the CoreCLR device remapping path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Compare UTF-8 and UTF-16 ASCII names in vector-sized chunks so binary-search ordering rescans at most one chunk after Ascii.Equals reports a mismatch. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The native assembler populates the JniRemappingData table pointers during code generation, so suppress CS0649 for those two fields just like the existing generated methods pointer. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Update the Xamarin.Forms CoreCLR R8 Release APK description from the matching failed CI test attachment. macOS and Windows produced the same package size; their assembly stores differed by only 16 bytes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the generator UTF-8 sort-key cache when computing serialized string lengths so each distinct remapping string is encoded only once. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use Encoding.UTF8.GetByteCount for serialized lengths so signatures and other non-sort values are not retained in the sort-key cache. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Override the combined replacement-type lookup in both Android type managers so generated UTF-8 table misses do not fall through into a second compatibility string search. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add target descriptors, field remapping, and renamed-owner fallback while preserving existing custom type-manager defaults. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Emit reverse type, field, and target descriptor tables and consume them in Microsoft.Android.Runtime and the trimmable type map. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep legacy native stubs layout-compatible, retain redirected static member owners, and use the nullable string helpers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Qualify System.Math in the shared test fixture and cache reverse JNI type lookup results used by activation paths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Android cannot install JavaVMFixture's custom type manager, so keep the synthetic remapping tests on the desktop Java.Interop test path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove duplicated method-index entries carried through the lower-layer generator optimization so each native table is emitted once. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
simonrozsival
force-pushed
the
simonrozsival-extend-jni-remapping-contract
branch
from
September 18, 2026 19:13
01b9dc0 to
00cfd3b
Compare
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Extend the reusable JNI remapping contract shared by runtime consumers:
JniTypeManagerand Intune/MAM defaultsDepends on #12796.
Related to #12814 and #12535.
Scope
This is only the reusable contract/runtime layer. It intentionally defers:
Tests
Java.Interop-Tests:JniPeerMembersTests— 26 passed, 1 skippedXamarin.Android.Build.Tests:GenerateJniRemappingNativeCodeTests— 6 passedJniRemappingDatalayout compile-time assertions