Skip to content

[NativeAOT] Make the trimmable typemap the default#12121

Merged
simonrozsival merged 19 commits into
mainfrom
dev/simonrozsival/trimmable-typemap-default-nativeaot-2
Jul 16, 2026
Merged

[NativeAOT] Make the trimmable typemap the default#12121
simonrozsival merged 19 commits into
mainfrom
dev/simonrozsival/trimmable-typemap-default-nativeaot-2

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jul 15, 2026

Copy link
Copy Markdown
Member

Goal

Make the trimmable typemap the default typemap for NativeAOT.

Supersedes #11822. That PR was based directly on main; this one is instead stacked on #12030 (dev/simonrozsival/android-trimmable-revisit-arrays-generic-collections), which reworks the AOT-safe array and collection factories. Rebasing onto #12030 let me drop the array-proxy / boxed-nullable / System.String-array-element commits (now handled by #12030's MakeArrayType/MakeGenericType factories) plus other work that has since landed on that branch, leaving just the focused NativeAOT default flip and its test adjustments.

Scope: for now this only changes the default. The existing managed/llvm-ir configurations remain reachable on NativeAOT (the runtime keeps its ManagedTypeManager / JavaMarshalValueManager fallbacks). Removing the non-trimmable NativeAOT paths and adding a hard error will be done in a separate PR.

Contributes to #10794, #11012, #8724, #10788, #10793.

Change map

Core enablement

  • Microsoft.Android.Sdk.NativeAOT.targets — default _AndroidTypeMapImplementation managedtrimmable.
  • Xamarin.Android.Common.targets — run the post-ILLink AssemblyModifierPipeline for NativeAOT+trimmable; skip the project proguard config for NativeAOT+trimmable. (All gated on trimmable, so managed/llvm-ir NativeAOT is unchanged.)
  • Microsoft.Android.Sdk.TypeMap.Trimmable.targets — disable ManagedPeerNativeRegistration for trimmable.
  • Pass generated ACW keep rules to R8 on the trimmable path; keep _PreTrimmingFixLegacyDesignerUpdateItems off the trimmable path.

Tests

  • BaseTestIgnoreNativeAotLinkedAssemblyChecks / IgnoreOnNativeAot helpers.
  • Skip guards for NativeAOT cases that inspect illink's linked/ output (LinkerTests, IncrementalBuildTest, BuildTest2); delete the obsolete BuildTest2.NativeAOT typemap test.
  • Warning-clean updates now that the trimmable default no longer emits the reflection-manager IL3050/IL3053 warnings.
  • DotNetBuild expects mapping.txt for NativeAOT release; add a NativeAOT regression test for R8-kept runtime ACWs.
  • The XA4212 custom-IJavaObject diagnostic is skipped on the NativeAOT (trimmable) path here for simplicity; the root-cause fix that emits it from the trimmable generator moved to follow-up [TrimmableTypeMap] Emit XA4212 for custom IJavaObject types on the trimmable path #12132.

Local validation

  • Microsoft.Android.Sdk.TrimmableTypeMap.Tests614 pass.
  • Full device/CI matrix + apkdesc/size baselines deferred to this PR's CI run.

Follow-up


Draft until CI confirms the full matrix (device tests + baselines).

@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-default-nativeaot-2 branch from 58fb2b9 to 3d0d593 Compare July 15, 2026 15:58
Base automatically changed from dev/simonrozsival/android-trimmable-revisit-arrays-generic-collections to main July 15, 2026 20:11
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-default-nativeaot-2 branch 2 times, most recently from 04f2a51 to 9ee49e1 Compare July 15, 2026 21:11
simonrozsival and others added 12 commits July 15, 2026 23:19
NativeAOT now defaults to and requires _AndroidTypeMapImplementation=trimmable:

- Microsoft.Android.Sdk.NativeAOT.targets: default managed -> trimmable; always
  run _PreTrimmingFixLegacyDesignerUpdateItems before NativeCompile.
- Xamarin.Android.Common.targets: error if NativeAOT is used with a non-trimmable
  typemap; run the post-ILLink AssemblyModifierPipeline for NativeAOT+trimmable
  (split out _GetAfterILLinkAdditionalStepsInputs); skip the project proguard config
  for NativeAOT+trimmable.
- Microsoft.Android.Sdk.TypeMap.Trimmable.targets: disable ManagedPeerNativeRegistration
  for trimmable; depend on IlcDynamicBuildPropertyDependencies on NativeAOT.
- JNIEnvInit / JreRuntime: NativeAOT now throws if the trimmable type map is not used,
  and the reflection-backed managers are wrapped in IL2026-suppressed helpers so
  Mono.Android and the NativeAOT runtime host build clean under trimming.

Test infrastructure for follow-up NativeAOT triage:
- BaseTest: IgnoreNativeAotLinkedAssemblyChecks / IgnoreOnNativeAot helpers.
- Mono.Android-Tests: add a TrimmableTypeMapUnsupported excluded category.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mmable path

_PreTrimmingFixLegacyDesignerUpdateItems is only defined in the LlvmIr typemap
targets, which are not imported for trimmable builds. Referencing it from
_AndroidRunNativeCompileDependsOn unconditionally broke NativeAOT (now trimmable by
default) with MSB4057. Restore the per-typemap condition so the trimmable path only
depends on NativeCompile.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NativeAOT trims with ILC and does not produce illink's obj/<config>/<rid>/linked/
directory, so tests that inspect linked assemblies (or assert the obsolete
PreserveAttribute IL6001 warning, or use the unsupported 'Lowercase' package naming
policy) cannot run as-is on NativeAOT. Guard them with the BaseTest helpers:

- LinkerTests: AndroidAddKeepAlives, AndroidUseNegotiateAuthentication,
  PreserveIX509TrustManagerSubclasses, PreserveServices (linked/ inspection),
  WarnWithReferenceToPreserveAttribute (IL6001).
- BuildTest2: NativeAOT (linked/Mono.Android.dll inspection), BuildReleaseArm64.
- IncrementalBuildTest: AppProjectTargetsDoNotBreak, LinkAssembliesNoShrink (linked/),
  ChangePackageNamingPolicy ('Lowercase' policy unsupported on trimmable).

Verified locally: PreserveIX509TrustManagerSubclasses(NativeAOT) now reports Skipped
instead of DirectoryNotFoundException, while the CoreCLR case still passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ault

- XASdkTests: NativeAOT is now warning-clean (the reflection-manager IL3050/IL3053
  warnings are gone), so assert no warnings instead of one.
- ManifestTest.ExportedErrorMessage: the trimmable manifest generator orders merged
  components differently, so assert the coded AMM0000 error for NativeAOT without the
  exact manifest line/column (verified: NativeAOT case passes).
- BuildWithLibraryTests.ProjectDependencies: the trimmable typemap trims Java Callable
  Wrappers for library types that are never instantiated, so the unused LibraryB JCWs
  are absent from classes.dex by design; skip the NativeAOT case (verified locally:
  the scrc64-named JCW .class files are generated but trimmed out of the dex).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NativeAOT release builds emit a proguard mapping.txt in the output directory
(confirmed in local NativeAOT build outputs), so add it to the expected file list for
the NativeAOT release case of DotNetBuild.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…chable

Drop the hard error that required _AndroidTypeMapImplementation=trimmable on NativeAOT.
For now this PR only flips the NativeAOT default to trimmable while keeping the existing
managed/llvm-ir configurations reachable (the runtime keeps its ManagedTypeManager /
JavaMarshalValueManager fallbacks). Removing the non-trimmable NativeAOT paths and
re-introducing the error will be done in a separate PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This test inspected illink's linked/Mono.Android.dll and the legacy ManagedTypeMapping
class to verify the managed type-map. With the trimmable typemap now the NativeAOT
default, ILC produces neither that linked/ output nor the ManagedTypeMapping type, so the
test no longer applies. Remove it rather than leaving a permanently-ignored test; a
DGML-based type-map check for NativeAOT can be added as a follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The test asserts the build fails with XA8000 for SkiaSharp's unresolved
@styleable/SKCanvasView, which relies on FixLegacyResourceDesignerStep. That legacy
resource-designer step is intentionally not run on the trimmable typemap path (the
NativeAOT default), so the diagnostic isn't emitted and the NativeAOT case no longer
applies. Skip it on NativeAOT via the IgnoreOnNativeAot helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
R8 shrinks bound library Java types (JavaSourceJarTest, JavaSourceTestExtension) out
of classes.dex on the trimmable typemap path because the proguard keep config is
incomplete on NativeAOT, so the class-presence assertions fail. Skip the NativeAOT case
via IgnoreOnNativeAot until the underlying proguard-keep bug is fixed.

Tracked by #11774.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The trimmable typemap generates additional Java Callable Wrappers that
trip XA0102 lint warnings. Ignore on NativeAOT until #11774
is resolved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The trimmable NativeAOT path generates its ProGuard/R8 ACW keep rules
from the ILC DGML into proguard_project_references.cfg
(_ProguardProjectConfiguration) via GenerateNativeAotProguardConfiguration,
and deliberately leaves R8's proguard_project_primary.cfg empty so that
references.cfg is the sole source of ACW keeps.

However _CalculateProguardConfigurationFiles excluded
_ProguardProjectConfiguration for NativeAOT+trimmable, so R8 received no
ACW keep rules and tree-shook every JCW/ACW (e.g. UncaughtExceptionMarshaler)
out of classes.dex. The app then crashed at startup in
JavaInteropRuntime.init with:

  java.lang.ClassNotFoundException: scrc64...UncaughtExceptionMarshaler

Drop the trimmable exclusion so the generated keep rules reach R8.

Verified on an arm64 emulator with CheckJNI enabled: the HelloWorld
NativeAOT (trimmable) sample now retains the ACW JCWs in classes.dex and
launches to MainActivity without crashing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds NativeAotKeepsRuntimeAcwJavaTypesUnderR8, which builds a Release NativeAOT
app with r8 and asserts that classes.dex still contains the runtime
UncaughtExceptionMarshaler ACW kept by the generated NativeAOT ProGuard rules.
If those keep rules are missing, R8 tree-shakes the runtime JCWs and the app
crashes at startup inside JavaInteropRuntime.init.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-default-nativeaot-2 branch from 9ee49e1 to 0eb136a Compare July 15, 2026 21:21
@simonrozsival
simonrozsival marked this pull request as ready for review July 15, 2026 21:41
Copilot AI review requested due to automatic review settings July 15, 2026 21:41

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 switches NativeAOT builds to use the trimmable typemap by default, aligning the NativeAOT pipeline with the trimmable typemap runtime model and updating build/test infrastructure to account for NativeAOT/ILC differences vs ILLink-based flows.

Changes:

  • Flip NativeAOT default _AndroidTypeMapImplementation from managedtrimmable.
  • Configure trimmable typemap runtimeconfig to disable Java.Interop.RuntimeFeature.ManagedPeerNativeRegistration.
  • Update/skip/replace several tests that relied on ILLink linked/ outputs or legacy behavior; add a regression test ensuring runtime ACW/JCW Java types are kept under R8 for NativeAOT.
Show a summary per file
File Description
tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs Skips a SkiaSharp device test on NativeAOT due to legacy designer-fix behavior differences on the trimmable path.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs Adds helper skip methods for NativeAOT-specific limitations (no ILLink linked/ output; other fundamental behavior differences).
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs Skips tests that inspect ILLink linked/ outputs (or ILLink-only warnings) when running under NativeAOT.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs Adjusts AMM0000 assertion for NativeAOT to avoid line-number coupling due to manifest layout differences.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs Skips/guards NativeAOT cases where behavior is intentionally unsupported (e.g., AndroidPackageNamingPolicy=Lowercase with trimmable typemap).
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildWithLibraryTests.cs Skips a dependency/DEX assertion on NativeAOT due to trimmable typemap trimming unused JCWs.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs Removes an obsolete NativeAOT typemap test based on ILLink linked/ output; adds an R8 keep-rule regression test for runtime ACWs on NativeAOT.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs Updates expected build outputs for NativeAOT Release to include mapping.txt; skips a lint test on NativeAOT due to known trimmable-JCW lint warnings.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs Skips a binding + AndroidJavaSource scenario on NativeAOT due to missing R8 keeps (tracked issue).
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets Sets Java.Interop.RuntimeFeature.ManagedPeerNativeRegistration=false for trimmable typemap runs.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets Makes trimmable the default _AndroidTypeMapImplementation for NativeAOT builds.

Copilot's findings

  • Files reviewed: 11/11 changed files
  • Comments generated: 1

simonrozsival and others added 2 commits July 16, 2026 09:42
The XA4212 "custom IJavaObject not supported" diagnostic is produced by the
Cecil-based XAJavaTypeScanner, which runs on the managed typemap path (via
AssemblyModifierPipeline in _RunAfterILLinkAdditionalSteps) and the llvm-ir path
(which passes ErrorOnCustomJavaObject to its generator).

Now that the trimmable typemap is the NativeAOT default, the type scan happens in
the System.Reflection.Metadata-based TrimmableTypeMapGenerator, which does not yet
perform the custom-IJavaObject check, so XA4212 is never raised and the build
wrongly succeeds. Skip the NativeAOT case with a documented follow-up until the
diagnostic is added to the trimmable generator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 94f450c7-781d-4019-a56c-fae1c456b9a7
…veAOT

This test verifies the _RunAfterILLinkAdditionalSteps target and its afterlink/
output. That target is intentionally skipped on the trimmable typemap path
(Condition "... '$(_AndroidTypeMapImplementation)' != 'trimmable'"), because the
trimmable path generates the typemap at compile time in _GenerateTrimmableTypeMap.

The test is unchanged from main and passed only because NativeAOT used to default
to the managed typemap. Now that trimmable is the NativeAOT default, the target is
skipped and no afterlink/ directory is produced, so skip the NativeAOT case (same
pattern as other NativeAOT test skips in this PR).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 94f450c7-781d-4019-a56c-fae1c456b9a7
simonrozsival and others added 5 commits July 16, 2026 16:50
The trimmable typemap defaulted _AndroidTrimmableTypeMapMaxArrayRank to 3
whenever dynamic code was unavailable (NativeAOT / PublishAot), which emits
per-element-type __ArrayMapRank{N} array-proxy types. Those proxies are dead
code at runtime: array and collection creation now goes through the AOT-safe
SafeArrayFactory / ValueTypeFactory (added in #12030), and
TrimmableTypeMap.TryGetArrayProxy has no callers.

Generating them only bloats NativeAOT ILC compilation. On multi-ABI apps this
pushed ILC past the test-harness build cap, timing out NativeAOT lanes
(Check9PatchFilesAreProcessed, WorkManager, BundleToolTests) that passed
quickly (5-13 min) before this default became NativeAOT.

Default MaxArrayRank to 0 on every runtime (still overridable via
$(_AndroidTrimmableTypeMapMaxArrayRank)). This is a subset of the codegen
removal in #12126; the per-type JavaPeerContainerFactory<T> bloat is not
addressed here and still needs #12126.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: da26833c-c9ea-41a6-b8d7-7a87a60ce8fc
…mmable-typemap-default-nativeaot-2

# Conflicts:
#	src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
Ensure additional JCW directories contribute concrete Java files to _CompileJava inputs instead of a literal wildcard path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 187b207a-083b-461e-9071-e9aab61c9d07
Pass both the standard generated Java source directory and compile-in-place JCW directories to Android lint so manifest classes are resolved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 187b207a-083b-461e-9071-e9aab61c9d07
Update CustomApplicationClassAndMultiDex to inspect NativeAOT generated Java in typemap/java while retaining the legacy CoreCLR path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 187b207a-083b-461e-9071-e9aab61c9d07
@simonrozsival
simonrozsival enabled auto-merge (squash) July 16, 2026 19:50
@simonrozsival
simonrozsival disabled auto-merge July 16, 2026 19:50
@simonrozsival
simonrozsival enabled auto-merge (squash) July 16, 2026 19:50
@simonrozsival
simonrozsival merged commit 77706d7 into main Jul 16, 2026
44 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/trimmable-typemap-default-nativeaot-2 branch July 16, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants