Skip to content

[NativeAOT] Make the trimmable typemap the default#11822

Draft
simonrozsival wants to merge 14 commits into
mainfrom
dev/simonrozsival/trimmable-typemap-default-nativeaot
Draft

[NativeAOT] Make the trimmable typemap the default#11822
simonrozsival wants to merge 14 commits into
mainfrom
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 fully landed in main via smaller PRs (#11799, #11801, #11802, and the other [TrimmableTypeMap] PRs), so this PR — now rebased directly on main — 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.

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

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.

Base automatically changed from dev/simonrozsival/trimmable-managers to main July 2, 2026 20:56
simonrozsival and others added 12 commits July 2, 2026 23:08
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>
…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>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-default-nativeaot branch 2 times, most recently from c916aee to adb00e4 Compare July 2, 2026 21:17
simonrozsival and others added 2 commits July 3, 2026 10:34
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>
Java.Interop.ManagedPeer is a reflection-based helper marked
[RequiresUnreferencedCode] ("Uses reflection to find constructors and
invoke them."). The trimmable type map generator emitted a proxy for it
(_TypeMap.Proxies.Java_Interop_ManagedPeer_Proxy) whose constructor
references ManagedPeer's constructors, producing two IL2026 trim
warnings, aggregated by ILC into:

  IL2104: Assembly '_Java.Interop.TypeMap' produced trim warnings

Because the default NativeAOT type map is now trimmable, this surfaced as
a real MSBuild warning and broke NativeAOT tests that assert no warnings
(e.g. BuildWithJavaToolOptions).

ManagedPeer is not supported by the trimmable type map: on the trimmable
path native registration goes through IAndroidCallableWrapper.RegisterNatives
and ManagedPeerNativeRegistration is disabled, so ManagedPeer is never
activated via the type map. Exclude it in the scanner so no proxy is
emitted.

Verified on an arm64 emulator: the HelloWorld NativeAOT (trimmable)
sample now builds with 0 warnings and still launches to MainActivity.

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