Skip to content

[TrimmableTypeMap][NativeAOT] Scan runtime host ACWs via a ref assembly#11982

Open
simonrozsival wants to merge 1 commit into
mainfrom
dev/simonrozsival/nativeaot-runtime-host-refasm
Open

[TrimmableTypeMap][NativeAOT] Scan runtime host ACWs via a ref assembly#11982
simonrozsival wants to merge 1 commit into
mainfrom
dev/simonrozsival/nativeaot-runtime-host-refasm

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Summary

Under the trimmable typemap on NativeAOT, the runtime host assembly Microsoft.Android.Runtime.NativeAOT — and therefore its only Java Callable Wrapper type, UncaughtExceptionMarshaler — was never scanned by the typemap generator. Its JCW, typemap entry, and acw-map entry were all missing, so R8 had nothing to keep and the ACW was absent from classes.dex, crashing the app at startup in JavaInteropRuntime.init (setDefaultUncaughtExceptionHandler).

Root cause

_GenerateTrimmableTypeMap runs only in the RID-independent outer build over @(ReferencePath), which contains just the compile closure (app + references + ref-pack framework assemblies). Microsoft.Android.Runtime.NativeAOT is a runtime-only assembly with no reference-assembly counterpart; it is resolved only in the per-RID inner build as a RID-specific runtime-pack asset (ResolveRuntimePackAssets requires a single RuntimeIdentifier). So it never enters the generator's input set.

Fix

Its managed metadata is RID-independent (byte-identical across RIDs), so:

  • Produce a reference assembly for Microsoft.Android.Runtime.NativeAOT (ProduceReferenceAssembly).
  • Ship it in the Microsoft.Android.Sdk pack under tools/typemap-refsnot in the Microsoft.Android.Ref targeting pack, because files under a targeting pack's ref/ folder must all be classified in its FrameworkList and would become universal framework references for every Android app. Shipping it in the SDK pack keeps it a build-time-only input for the trimmable NativeAOT path.
  • Feed it to the generator as an extra framework input in the outer build.
  • Classify its per-assembly typemap DLL (_Microsoft.Android.Runtime.NativeAOT.TypeMap) as framework for ILC, so it stays an IlcReference but is removed from the unmanaged-entrypoint roots (matching Mono.Android/Java.Interop); its JCW native methods register via the runtime registerNatives path.

This is a general fix for any runtime-only assembly with Java peers, not just UncaughtExceptionMarshaler.

Test

Adds Build_WithTrimmableTypeMap_KeepsNativeAotRuntimeHostAcws, which opts into the trimmable typemap on NativeAOT and asserts classes.dex retains the UncaughtExceptionMarshaler runtime ACW.

Notes

The trimmable typemap is opt-in on main (_AndroidTypeMapImplementation=trimmable); this fix is dormant otherwise. It is extracted from the "make trimmable the NativeAOT default" work so it can be reviewed and merged independently.

The trimmable typemap generator (_GenerateTrimmableTypeMap) runs only in the
RID-independent OUTER build over @(ReferencePath), which contains just the
compile closure (app + references + ref-pack framework assemblies). The NativeAOT
runtime host, Microsoft.Android.Runtime.NativeAOT, is a runtime-only assembly
resolved solely in the per-RID inner build (as a RID-specific runtime pack asset),
so it was never scanned. Its only Java Callable Wrapper type,
UncaughtExceptionMarshaler, therefore had no JCW, no typemap entry, and no acw-map
entry -> R8 had nothing to keep -> an on-device startup crash in
JavaInteropRuntime.init (setDefaultUncaughtExceptionHandler).

Runtime-pack resolution is inherently per-RID (ResolveRuntimePackAssets needs a
single RuntimeIdentifier), but the host assembly's managed metadata is
RID-independent (byte-identical across RIDs). So produce a reference assembly for
it and ship it in the Microsoft.Android.Sdk pack under tools/typemap-refs, then
feed it to the generator as an extra framework input in the outer build.

It is intentionally NOT placed in the Microsoft.Android.Ref targeting pack: files
under a targeting pack's ref/ folder must all be classified in its FrameworkList
and would become universal framework references for every Android app. Shipping it
in the SDK pack keeps it a build-time-only input for the trimmable NativeAOT path.

Its per-assembly typemap DLL (_Microsoft.Android.Runtime.NativeAOT.TypeMap) is
classified as framework for ILC so it stays an IlcReference but is removed from the
unmanaged-entrypoint roots, matching Mono.Android/Java.Interop; its JCW native
methods register via the runtime registerNatives path.

Adds Build_WithTrimmableTypeMap_KeepsNativeAotRuntimeHostAcws, which opts into the
trimmable typemap on NativeAOT and asserts classes.dex retains the
UncaughtExceptionMarshaler runtime ACW.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 4, 2026 20:36

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.

Pull request overview

This PR fixes a NativeAOT + trimmable typemap startup crash by ensuring the typemap generator scans the runtime-only Microsoft.Android.Runtime.NativeAOT assembly (via a shipped reference assembly), so its runtime ACW(s) (notably UncaughtExceptionMarshaler) are kept by R8 and appear in classes.dex.

Changes:

  • Add a new build test asserting UncaughtExceptionMarshaler is present in classes.dex for NativeAOT + trimmable typemap + R8.
  • Extend _GenerateTrimmableTypeMap inputs to include an extra framework assembly item list for runtime-only assemblies.
  • Produce + package a reference assembly for Microsoft.Android.Runtime.NativeAOT under tools/typemap-refs, and wire it into the NativeAOT trimmable typemap build.
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs Adds regression test verifying the runtime ACW class is retained in classes.dex.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets Feeds additional runtime-only framework assemblies into typemap generation inputs.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets Injects the NativeAOT runtime host ref assembly into typemap inputs; classifies its per-assembly typemap DLL as framework for ILC.
src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj Enables ProduceReferenceAssembly and copies the ref assembly into the SDK pack’s typemap-refs staging area.
build-tools/create-packs/Microsoft.Android.Sdk.proj Packages the NativeAOT runtime host reference assembly under tools/typemap-refs.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 2

Comment on lines +94 to +100
<PropertyGroup>
<_NativeAotRuntimeHostRefAssembly>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\tools\typemap-refs\Microsoft.Android.Runtime.NativeAOT.dll'))</_NativeAotRuntimeHostRefAssembly>
</PropertyGroup>
<ItemGroup>
<_AndroidTrimmableTypeMapExtraFrameworkAssembly Include="$(_NativeAotRuntimeHostRefAssembly)"
Condition=" Exists('$(_NativeAotRuntimeHostRefAssembly)') " />
</ItemGroup>
Comment on lines +101 to +103
<Warning Condition=" !Exists('$(_NativeAotRuntimeHostRefAssembly)') "
Text="The NativeAOT runtime host reference assembly was not found at '$(_NativeAotRuntimeHostRefAssembly)'; runtime Java Callable Wrappers (e.g. UncaughtExceptionMarshaler) may be missing from the typemap." />
</Target>
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