Skip to content

[Mono.Android] Load JNI remapping from binary assets - #13023

Closed
simonrozsival wants to merge 6 commits into
simonrozsival-move-r8-remapping-tasksfrom
simonrozsival-binary-jni-remapping-transport
Closed

simonrozsival wants to merge 6 commits into
simonrozsival-move-r8-remapping-tasksfrom
simonrozsival-binary-jni-remapping-transport

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Part of #12940. This is one binary transport layer above #12844, targeting simonrozsival-move-r8-remapping-tasks, not main. Native stack #12850 membership/order remains [12846,12847,12848,12692,12844,13023]. Only this session's transport branch/PR changes; lower branches, PR bases and stack metadata are untouched.

Replace canonical replacement XML's final transport: XML -> deterministic, versioned, pointer-free XAJR v1 Android asset -> managed lookup, instead of LLVM tables/native objects. Preserve XML merge precedence/diagnostics, type/reverse-type/method/field mappings, optional descriptors, instance-to-static forwarding and opt-in R8 policies/filtering. CoreCLR shares bytes under each RID's logical asset path; NativeAOT serializes separately per RID after the one final R8 pass, filtering from the actual first-publish ILC object.

A small Java reader and pointer-plus-length native handoff supply the asset. The parser validates a private snapshot independently of lookup state. Only the build-time RuntimeFeature.JniRemapping call-site guard initializes/pins JniRemappingLookup, so disabled lookup types/caches are trimmable. Temporary native bytes are freed through monodroid_free in finally, including failures. No-map applications still read/validate a valid 64-byte empty asset; malformed or missing bytes fail explicitly. The Java byte-array read and two subsequent copies remain; native-asset/memory-mapped alternatives have only been discussed, not implemented here.

Remove only remapping-specific emitters, .ll/.o inputs, native tables and post-R8 relinking/evaluation hooks. Track actual NativeAOT first-publish inputs before IncrementalClean; serialize after R8 but before the clean manifest snapshot. Preserve current NDK/linker routing, Java/crypto bootstrap, remaining LLVM consumers, assembly-store/ELF formats, app DSO and unrelated runtime state. No new public switch or native fallback.

Current stack snapshot

Base: 6eeb61813986ea4f8ee1ea8c371c6b7595a505dd (#12844), directly based on policy #12692 5e47c4b825b3e0a4b11ab0c3b41455cdbde81843. Head: b02f5c3ed80ad8e4b2bdc0e90a7ec586a93127dd.

The latest parent update adds the explicit-primary-RID CoreCLR review case and main's Java.Interop test migration. All five transport commits replayed without conflict; git range-diff reports = for every commit. This ancestry-only push used an explicit lease against freshly verified previous transport head d9f0873c72ce1026b2196ddc56044dc01272f9e3. Backups preserve prior owned heads.

Earlier current-main conflict resolutions retain reduced logging initialization ABI, retained/scoped typemap and DGML retirement, shared native logging startup and merged assembly-store runtime/compression state. Narrow JNI asset-reader keep rules are wired into both current typemap templates and the legacy template. No retired NativeAOT ProGuard template, compression LLVM emitter or runtime-state table is reintroduced. The parent's runtime-remapping opt-out from typemap no-minification is unchanged. Final assembly collection uses _ResolveAssemblies without late ProGuard generation.

All review/CI fixes remain: atomic asset publication with failed-write retry coverage; validation regardless of the lookup switch; project/import invalidation when the last mapping is removed; FileWrites/Clean ordering; nullable merge-output directory handling; and feature-gated lookup initialization. Size-reference conflicts retained newer main values rather than restoring old snapshots; thresholds remain unchanged.

Latest review-case validation

./dotnet-local.sh build tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj -c Release -m:2 -nodeReuse:false -v minimal
./dotnet-local.sh test bin/TestRelease/MSBuildDeviceIntegration/net10.0/MSBuildDeviceIntegration.dll --filter 'FullyQualifiedName~MultiRidUsesOneR8Mapping&Name~CoreCLR&Name~True' --logger 'trx;LogFileName=sync6eeb-coreclr-primary-rid.trx' --results-directory bin/TestRelease/TransportResults -- NUnit.NumberOfTestWorkers=1

Build succeeded (33 existing generated-code/duplicate-source warnings, 0 errors). Exactly one requested case ran and passed: MultiRidUsesOneR8Mapping(CoreCLR,true), no skips. It built both RIDs with an explicit primary RID, verified one R8 pass, zero R8/native-link work on no-op, and removal of generated remapping XML/binary files on Clean. The parent owner independently verified the result. Production transport patches are unchanged; earlier source/runtime results below are retained with their actual source attribution.

Earlier source/runtime evidence

Own-source SDK/task/runtime/native-host/test outputs were rebuilt; immutable distributions seeded the private SDK/toolchain, not donor/PoC task or runtime outputs. Concurrency was capped at two MSBuild nodes/two Ninja jobs. Device validation used only private jni-remap-sync3d712-api35 / emulator-5584 and isolated ADB port 5041. No physical A16 or shared emulator was used. Named helpers were stopped/removed afterward; useful logs/results remain.

Commands used the private SDK first on PATH, absolute DOTNET_ROOT pointing to bin/Release/dotnet, DOTNET_ROLL_FORWARD=Major, and DOTNET_CLI_USE_MSBUILD_SERVER=0. Size checks also used privately installed CI-pinned apkdiff 0.0.17. Device commands additionally used ADB_SERVER_SOCKET=tcp:localhost:5041, ANDROID_ADB_SERVER_PORT=5041, ANDROID_SERIAL=emulator-5584 and ADB_TARGET='-s emulator-5584'.

Exact command / source Result
make all CONFIGURATION=Release MSBUILD_ARGS='-m:2' on preceding 66fb parent plus transport Passed, 0 warnings/errors; rebuilt assembly-store-aware SDK/runtime/native/task/test outputs.
bin/Release/dotnet/dotnet build src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Release -m:2 -v minimal --no-incremental -p:TreatWarningsAsErrors=true Passed, 0 warnings/errors; forced strict shared-parser/modern-test compilation.
bin/Release/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Release --no-build -v minimal -p:TreatWarningsAsErrors=true --filter 'FullyQualifiedName~GenerateR8JniRemappingTests|FullyQualifiedName~GenerateJniRemappingAssetTests|FullyQualifiedName~CompressionMetadataTests' Passed: 90 tests, no skips. Binary/schema/lookup/writer failure-retry and inherited R8/compression metadata.
./dotnet-local.sh test bin/TestRelease/MSBuildDeviceIntegration/net10.0/MSBuildDeviceIntegration.dll --filter 'FullyQualifiedName~DeployToDevice|FullyQualifiedName~MultiRidUsesOneR8Mapping' --logger 'trx;LogFileName=sync66fb-startup-multirid.trx' --results-directory bin/TestRelease/TransportResults -- NUnit.NumberOfTestWorkers=1 Passed: 6/6, no skips, including main's assembly-store state. CoreCLR Debug/Release and NativeAOT no-map startup; three multi-RID single-R8/no-op/Clean cases, ILC/native-link no-op timestamps and explicit NativeAOT primary RID.
./dotnet-local.sh test bin/TestRelease/net10.0/Xamarin.Android.Build.Tests.dll --filter 'FullyQualifiedName~BuildReleaseArm64|FullyQualifiedName~JniRemappingAssetSurvivesIncrementalBuild|FullyQualifiedName~NoChangeBuildKeepsDynamicJniRegistrationDisabled|FullyQualifiedName~CheckR8MetadataFilesExist|FullyQualifiedName~MamJsonToXmlTests|FullyQualifiedName~MergeRemapXmlTests|FullyQualifiedName~R8Obfuscation' --logger 'trx;LogFileName=sync66fb-host.trx' --results-directory bin/TestRelease/TransportResults -- NUnit.NumberOfTestWorkers=2 No failures: 27 passed plus 4 release-size cases inconclusive/NotExecuted due size decreases only. Actual lookup-type absence assertions execute before size checks. Packaging, mapped/empty RID bytes, last-map removal, no unnecessary native rebuild, Clean, XML/MAM and policy cases passed.
./dotnet-local.sh test bin/TestRelease/MSBuildDeviceIntegration/net10.0/MSBuildDeviceIntegration.dll --filter 'FullyQualifiedName~UnchangedProguardRulesDoNotRerunR8|FullyQualifiedName~R8RuntimeRemappingTests' --logger 'trx;LogFileName=sync0057-targeted-parent.trx' --results-directory bin/TestRelease/TransportResults -- NUnit.NumberOfTestWorkers=1 on corrected f330/0057 product before the assembly-store-only main merge Passed: 4/4, no skips. Both updated retained/scoped and original runtime-remapping ProGuard cases, and BOTH complete CoreCLR/NativeAOT mapped-runtime scenarios. Type/reverse/constructor/method/field/peer-return, filtering, first R8=1/no-op R8=0, recovery, changed maps without ILC/native relink and opt-off assertions retained. Lower owner independently verified these results; the final assembly-store-aware startup/multi-RID set above was rerun afterward.

Reduced pointer-plus-length initialization offsets/alignment were cross-compiled for arm64/arm32/x64/x86 after logging-field retirement (96-/56-byte structs). That ABI is unchanged in subsequent parent updates; these checks are not 32-bit runtime execution proof.

Live user XML/MAM/native malformed-byte/cached-pointer GC MTP tests passed 19/19 on the preceding 3d712 product, not rerun after the parent-only updates. Exact commands:

./dotnet-local.sh build -t:Install -c Release tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -m:2 -nodeReuse:false -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -p:AndroidPackageFormats=apk -p:AndroidTypeMapImplementation=trimmable -p:IncludeCategories=JniRemapping%2cIntune -p:_AndroidIsAssignableFromCheck=false -p:AdbTarget='-s emulator-5584'
# From tests/Mono.Android-Tests/Mono.Android-Tests:
../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj --no-build -c Release --device emulator-5584 --report-trx --results-directory ../../../bin/TestRelease/TransportSync3d712RuntimeResults -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -p:AndroidPackageFormats=apk -p:AndroidTypeMapImplementation=trimmable -p:IncludeCategories=JniRemapping%2cIntune -p:_AndroidIsAssignableFromCheck=false -p:AdbTarget='-s emulator-5584'

Historical first publication: full modern suite 377 passed/5 native-tool-fixture skips, NativeAOT Intune/MAM MTP one passed. Those entire suites were not rerun here. Earlier size snapshots passed all four cases before main's size decreases; current size-only inconclusive outcomes are not claimed as passes.

Runtime coverage is API 35 arm64 with 4-KiB pages; 32-bit runtime, 16-KiB devices and Windows runtime execution were not tested. Fresh CI results are not yet verified. The separate Windows directory-move file-lock failure is not changed or claimed fixed; no manual CI retries or issue mutations were performed.


Pull Request
title and
description
should follow the
commit-messages.md workflow documentation, and in particular should include:

  • Useful description of why the change is necessary.
  • Links to issues fixed
  • Unit tests

Copilot AI balanced review requested due to automatic review settings October 6, 2026 13:36
@simonrozsival
simonrozsival added this pull request to stack #12850 October 6, 2026 13:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Incremental invalidation, failed-write recovery, asset validation, and cleanup tracking need correction.

Review effort: Balanced
Findings: 1 High severity · 3 Medium severity

Open (4)
What changed in this PR

Replaces JNI-remapping native tables with XAJR binary assets, advancing #12940 on top of #12844 while avoiding native relinking when mappings change.

Changes:

  • Adds deterministic asset generation, validation, and managed lookup.
  • Updates CoreCLR/NativeAOT startup and per-RID packaging; removes remapping-specific LLVM machinery.
  • Expands runtime, packaging, and incremental-build tests and documents XA4331.
File Description
tests/​MSBuildDeviceIntegration/​Tests/​R8RuntimeRemappingTests.cs Tests asset packaging and updates without relinking.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​JniRemappingLookupTests.cs Tests binary lookup and pointer lifetime.
src/​Xamarin.Android.Build.Tasks/​Xamarin.Android.Common.targets Replaces native remapping generation with asset packaging.
src/​Xamarin.Android.Build.Tasks/​Utilities/​JniRemappingNativeCodeGenerator.cs Removes LLVM table emitter.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​Tasks/​GenerateJniRemappingNativeCodeTests.cs Removes obsolete emitter tests.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​PackagingTest.cs Checks per-RID AAB assets.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​IncrementalBuildTest.cs Checks asset regeneration and cleanup.
src/​Xamarin.Android.Build.Tasks/​Tasks/​PrepareAbiItems.cs Removes remapping source mode.
src/​Xamarin.Android.Build.Tasks/​Tasks/​GenerateJniRemappingNativeCode.cs Removes native generation task.
src/​Xamarin.Android.Build.Tasks/​Resources/​proguard_xamarin.cfg Preserves Java asset reader.
src/​Xamarin.Android.Build.Tasks/​Resources/​proguard_trimmable_nativeaot.cfg Preserves NativeAOT asset reader.
src/​Xamarin.Android.Build.Tasks/​Resources/​NativeAotRuntimeProvider.java Initializes asset access.
src/​Xamarin.Android.Build.Tasks/​Properties/​Resources.resx Adds XA4331 diagnostic.
src/​Xamarin.Android.Build.Tasks/​Properties/​Resources.Designer.cs Exposes XA4331 resource.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.TypeMap.Trimmable.targets Removes relocated remapping import.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets Collects linked assemblies without rescheduling ProGuard.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.R8JniRemapping.targets Generates and collects binary assets.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.NativeAOT.targets Removes late linking and exposes publish metadata.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.NativeAOT.R8.targets Deletes post-R8 relinking machinery.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.NativeAOT.After.targets Removes obsolete relinking import.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.After.targets Imports shared remapping targets.
src/​native/​nativeaot/​host/​jni-remapping-data-stub.cc Removes empty native descriptor.
src/​native/​nativeaot/​host/​host.cc Loads asset during startup.
src/​native/​nativeaot/​host/​CMakeLists.txt Removes descriptor stub source.
src/​native/​common/​include/​runtime-base/​jni-remapping-asset.hh Adds shared JNI asset loader.
src/​native/​common/​include/​managed-interface.hh Adds pointer-and-length handoff.
src/​native/​clr/​xamarin-app-stub/​application_dso_stub.cc Removes native remapping tables.
src/​native/​clr/​include/​xamarin-app.hh Removes obsolete table declarations.
src/​native/​clr/​host/​host.cc Loads CoreCLR remapping asset.
src/​Mono.Android/​Mono.Android.csproj Includes managed asset reader.
src/​Mono.Android/​Microsoft.Android.Runtime/​JniRemappingLookup.cs Uses validated, pinned asset storage.
src/​Mono.Android/​Microsoft.Android.Runtime/​JniRemappingAsset.cs Implements XAJR validation and lookup.
src/​Mono.Android/​Android.Runtime/​JNIEnvInit.cs Initializes lookup and frees temporary bytes.
src/​Microsoft.Android.Build.Tasks/​Utilities/​JniRemapping/​JniRemappingXmlReader.cs Reads existing replacement XML.
src/​Microsoft.Android.Build.Tasks/​Utilities/​JniRemapping/​JniRemappingAssetWriter.cs Serializes deterministic XAJR data.
src/​Microsoft.Android.Build.Tasks/​Tests/​Microsoft.Android.Build.Tasks.Tests/​Tasks/​GenerateR8JniRemappingTests.cs Checks R8 mappings through binary lookup.
src/​Microsoft.Android.Build.Tasks/​Tests/​Microsoft.Android.Build.Tasks.Tests/​Tasks/​GenerateJniRemappingAssetTests.cs Tests serialization, validation, and lifetime.
src/​Microsoft.Android.Build.Tasks/​Tests/​Microsoft.Android.Build.Tasks.Tests/​Microsoft.Android.Build.Tasks.Tests.csproj Updates test dependencies.
src/​Microsoft.Android.Build.Tasks/​Tasks/​GenerateJniRemappingAsset.cs Adds binary generation task.
src/​Microsoft.Android.Build.Tasks/​Microsoft.Android.Build.Tasks.csproj Shares runtime parser with build tasks.
src/​java-runtime/​java/​net/​dot/​android/​JniRemappingAsset.java Reads per-RID assets.
src/​java-runtime/​java/​mono/​android/​clr/​MonoPackageManager.java Initializes CoreCLR asset access.
Documentation/​docs-mobile/​TOC.yml Adds XA4331 navigation.
Documentation/​docs-mobile/​messages/​xa4331.md Documents generation failures.
Documentation/​docs-mobile/​messages/​index.md Indexes XA4331.
Documentation/​docs-mobile/​building-apps/​build-properties.md Documents binary remapping transport.
Files not reviewed (1)
  • src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs: Generated file

@simonrozsival
simonrozsival force-pushed the simonrozsival-binary-jni-remapping-transport branch from 8d1678b to d0dd43c Compare October 6, 2026 19:30
@simonrozsival
simonrozsival force-pushed the simonrozsival-binary-jni-remapping-transport branch from de2160c to d9f0873 Compare October 7, 2026 09:10
@simonrozsival
simonrozsival force-pushed the simonrozsival-binary-jni-remapping-transport branch from d9f0873 to b02f5c3 Compare October 7, 2026 09:32
simonrozsival and others added 6 commits October 7, 2026 18:25
Replace only the final JNI remapping transport with deterministic XAJR assets.  Preserve canonical replacement XML, optional descriptors, reverse types, field mappings, and R8 retention policies.

Package shared CoreCLR bytes and post-ILC per-RID NativeAOT assets after R8.  Validate and pin private managed bytes before exposing stable JNI pointers; release the temporary native handoff.  Retire JNI remapping LLVM tables, object inputs, and map-dependent native relinking without changing other code generation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Materialize the actual first-publish native files in outer FileWrites before IncrementalClean.  Keep the ILC object used for RID-specific binary filtering and the already-linked library without re-evaluating or relinking after R8.

Adapt the migrated descriptor-distinct field integration test to assert real binary lookup behavior.  Both full CoreCLR/NativeAOT R8 runtime cases and critical host packaging/incremental/Clean checks pass on the reconstructed parent.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Publish assets only after successful writes, validate startup bytes even with lookups disabled, and invalidate assets when project mapping inputs are removed. Generate final remapping outputs after R8 but before IncrementalClean records FileWrites.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fix CS8604 when MergeRemapXml is linked into the modern task tests and warnings are treated as errors. Support filename-only outputs without attempting to create an empty directory, and cover that case in the existing merge task tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Validate native asset bytes outside the lookup type, then initialize lookup state only behind the build-time feature switch. Preserve malformed-data rejection and pinned-pointer lifetime without rooting disabled lookup caches.

Refresh only the affected Release APK size entries for the intentional binary asset, Java/native loader and validation code, retaining all existing trimming and size thresholds.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Package the validated XAJR payload as a single read-only data symbol in per-ABI libandroid_runtime_blobs.so. Bound its fixed header and declared size against the retained ELF mapping before handing its pointer to managed lookup. Remove Java staging, full-payload copying and startup table scans; retain full producer validation and bounded UTF-8/NUL checks on visited records. Preserve post-R8 packaging, NativeAOT filtering, forwarding signatures, incremental/Clean and opt-off trimming. Include loader, semantic, allocation and atomic-output tests and refresh only the four affected CoreCLR size references without changing thresholds.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival force-pushed the simonrozsival-binary-jni-remapping-transport branch from b02f5c3 to e89ae0f Compare October 7, 2026 17:02
An error occurred while trying to automatically change base from simonrozsival-move-r8-remapping-tasks to main October 10, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants