Skip to content

[NativeAOT] Make the trimmable typemap the default#11822

Draft
simonrozsival wants to merge 15 commits into
dev/simonrozsival/trimmable-managersfrom
dev/simonrozsival/trimmable-typemap-default-nativeaot
Draft

[NativeAOT] Make the trimmable typemap the default#11822
simonrozsival wants to merge 15 commits into
dev/simonrozsival/trimmable-managersfrom
dev/simonrozsival/trimmable-typemap-default-nativeaot

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 30, 2026

Copy link
Copy Markdown
Member

Goal

Make the trimmable typemap the default typemap for NativeAOT.

Supersedes #11617. That PR bundled the reflection-free TrimmableTypeMapType/ValueManager runtime work and the NativeAOT default flip into one large change. The runtime/manager foundation has since landed in smaller PRs (this PR is based on dev/simonrozsival/trimmable-managers, #11801), so this PR is the focused remainder: flip the NativeAOT default to trimmable and adjust the tests accordingly.

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.

Base branch: this PR targets dev/simonrozsival/trimmable-managers (#11801) and should merge after it.

Change map

Core enablement

  • Microsoft.Android.Sdk.NativeAOT.targets — default _AndroidTypeMapImplementation managedtrimmable.
  • Xamarin.Android.Common.targets — run the post-ILLink AssemblyModifierPipeline for NativeAOT+trimmable (split out _GetAfterILLinkAdditionalStepsInputs); 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; depend on ILC's SetupProperties ($(IlcDynamicBuildPropertyDependencies)) on NativeAOT so the runtime-pack framework assemblies are resolved before the typemap is generated.
  • JNIEnvInit / JreRuntime — the reflection-backed managers (ManagedTypeManager, AndroidTypeManager, AndroidValueManager, JavaMarshalValueManager) are wrapped in IL2026/IL3050-suppressed local helpers so Mono.Android and the NativeAOT runtime host build clean under trimming. NativeAOT keeps falling back to ManagedTypeManager / JavaMarshalValueManager when the trimmable type map is not used.

Tests

  • BaseTestIgnoreNativeAotLinkedAssemblyChecks / IgnoreOnNativeAot helpers.
  • Skip guards for NativeAOT cases that inspect illink's obj/<config>/<rid>/linked/ output (ILC doesn't produce it): LinkerTests (×5), BuildTest2.BuildReleaseArm64, IncrementalBuildTest (×3). BuildTest2.NativeAOT is deleted (it verified the legacy linked/ ManagedTypeMapping, which ILC no longer produces).
  • Warning-clean updates — with the trimmable default, NativeAOT no longer emits the reflection-manager IL3050/IL3053 warnings, so BuildHasNoWarnings, XASdkTests, and XA4310 now assert no warnings.
  • ManifestTest.ExportedErrorMessage asserts the coded AMM0000 error without the exact manifest line.
  • BuildWithLibraryTests.ProjectDependencies is skipped on NativeAOT (the trimmable typemap trims JCWs for library types that are never instantiated).
  • DotNetBuild expects mapping.txt for NativeAOT release.
  • Mono.Android-Tests defines a TrimmableTypeMapUnsupported excluded category for on-device runs.

Local validation

  • make all → 0 errors / 0 warnings.
  • Build_WithTrimmableTypeMap_Succeeds (NativeAOT) → pass.
  • Microsoft.Android.Sdk.TrimmableTypeMap.Tests → 597 pass.
  • Each host-test change re-run on NativeAOT to confirm pass/skip (BuildHasNoWarnings, XA4310, ManifestTest, PreserveIX509TrustManagerSubclasses, etc.).

Still to validate in CI

  • On-device Mono.Android.NET-Tests (NativeAOT) and any apkdesc/size baseline updates — these need the full CI matrix and are deferred to this PR's CI run.

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

Research note: legacy resource-designer fix is intentionally NOT run on trimmable NativeAOT

The non-trimmable NativeAOT path runs FixLegacyResourceDesignerStep before ILC (the
_PreTrimmingFixLegacyDesigner* targets in Microsoft.Android.Sdk.TypeMap.LlvmIr.targets). The
trimmable path does not import those targets, so on trimmable NativeAOT this step does not run — and
this PR keeps it that way (_AndroidRunNativeCompileDependsOn for trimmable depends only on
NativeCompile). That choice was validated with a small experiment (library + app, IL decompiled):

  • Modern designer-assembly libraries (the default, AndroidUseDesignerAssembly=true): the step is a no-op.
    Library code compiles to call _Microsoft.Android.Resource.Designer.Resource/Layout::get_<id>() (not
    ldsfld), and FixLegacyResourceDesignerStep only rewrites ldsfld. The optimizer then inlines that
    getter to the correct final aapt2 id as a constant (verified: linked values 0x7F040000 / 0x7F030000
    matched the merged R.txt). So for the common case, running or not running the step makes no
    measurable difference
    .
  • Legacy libraries (AndroidUseDesignerAssembly=false, e.g. old SkiaSharp) emit ldsfld against a
    retained Resource class
    whose static fields are populated at runtime by
    Android.Runtime.ResourceIdManager.UpdateIdValues(). Resource ids therefore still resolve correctly at
    runtime without the rewrite; what the step additionally provides is the XA8000 unresolved-resource
    diagnostic and the ability to trim the designer class (a size win).

Decision: for trimmable NativeAOT we accept forgoing the XA8000 diagnostic / size win for legacy
AndroidUseDesignerAssembly=false libraries, rather than porting the legacy step. Modern libraries — the
overwhelming majority — are unaffected. Porting the step to the trimmable NativeAOT path can be revisited
separately if legacy-AAR support on NativeAOT turns out to need it.

simonrozsival and others added 7 commits June 30, 2026 16:11
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>
Making the trimmable type map the default removed the reflection-backed manager
IL3050/IL2026 warnings on NativeAOT (the managers are now suppressed/trimmable-only),
so the NativeAOT build produces zero warnings. Replace the IL3050 warning-count
assertion with AssertHasNoWarnings (). Verified locally: BuildHasNoWarnings
(True,*,NativeAOT) apk+aab now pass.

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>
With the trimmable typemap default, NativeAOT no longer produces the reflection-manager
IL3050 warnings, so the 'Mono.Android produced AOT analysis warnings' IL3053 aggregate
is gone. Assert the build has no warnings for all runtimes. Verified: XA4310
(apk/aab, NativeAOT) pass.

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>
simonrozsival and others added 3 commits June 30, 2026 16:48
Per review feedback, NativeAOT should keep falling back to ManagedTypeManager (type
manager) and JavaMarshalValueManager (value manager) when the trimmable type map is
not used, rather than throwing. The MSBuild hard error in Common.targets remains the
guard that prevents NativeAOT + non-trimmable builds.

The reflection-backed managers are wrapped in IL2026/IL3050-suppressed local helpers so
Mono.Android and the NativeAOT runtime host still build clean under trimming; the
trimmable-first branch dead-codes the fallback in app builds, so NativeAOT remains
warning-free (verified: BuildHasNoWarnings NativeAOT apk+aab pass).

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>
@dotnet dotnet deleted a comment from azure-pipelines Bot Jun 30, 2026
@simonrozsival

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

simonrozsival and others added 4 commits June 30, 2026 18:32
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>
The merge of dev/simonrozsival/trimmable-managers left two identical
CreateAndroidValueManager local functions in JNIEnvInit.CreateValueManager,
causing CS0128. Remove the redundant second definition.

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>
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.

1 participant