[NativeAOT] Make the trimmable typemap the default#11822
Draft
simonrozsival wants to merge 56 commits into
Draft
[NativeAOT] Make the trimmable typemap the default#11822simonrozsival wants to merge 56 commits into
simonrozsival wants to merge 56 commits into
Conversation
Member
Author
|
/azp run |
|
Azure Pipelines could not run because the pipeline triggers exclude this branch/path. |
c916aee to
adb00e4
Compare
This was referenced Jul 4, 2026
cb26eab to
f813b47
Compare
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>
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>
…ID inner build path _ResolveAssemblies always dispatches a per-RID inner build with AppendRuntimeIdentifierToOutputPath=true, so ILC writes each *.scan.dgml.xml under the RID-nested obj/<cfg>/<rid>/native/ path -- for both a single explicit RuntimeIdentifier and a RuntimeIdentifiers list. _CollectTrimmableNativeAotDgmlFiles runs in the outer build, whose NativeIntermediateOutputPath has no RID segment. The single-RuntimeIdentifier branch collected from that flat path (obj/<cfg>/native/), which never exists, so _GenerateTrimmableTypeMapProguardConfiguration failed with XA4321 across every single-RID NativeAOT build (e.g. IncrementalBuildDifferentDevice and the other MockPrimaryCpuAbi tests). Reconstruct the RID-nested path for both the single-RuntimeIdentifier and the RuntimeIdentifiers cases (they now share one item expression); the flat NativeIntermediateOutputPath remains only as the defensive no-RID fallback. 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>
…ath shapes) The previous XA4321 fix assumed ILC's *.scan.dgml.xml always lives at the RID-nested $(IntermediateOutputPath)<rid>/native/. That is only true when the outer $(IntermediateOutputPath) has no RID segment (a $(RuntimeIdentifiers) list, or a RID assigned late by _GetPrimaryCpuAbi). For a single explicit $(RuntimeIdentifier) set early, the SDK already appended the RID to the outer path, so reconstructing <rid>/native/ produced a doubled RID (obj/Release/android-arm64/android-arm64/native/) and XA4321. Emit both candidate paths ($(NativeIntermediateOutputPath) and the RID-nested form), Exists()-filtered, and consume whichever ILC actually produced. This is correct for single-RID (either output-path shape), multi-RID, and no-RID. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…L is absent ILC only emits the scan graph (*.scan.dgml.xml) when its scanner phase runs (optimized/Release builds). Unoptimized NativeAOT builds - e.g. a Debug configuration, as produced when a solution is built without an explicit Release configuration (AllProjectsHaveSameOutputDirectory) - emit only the codegen graph (*.codegen.dgml.xml). The trimmable proguard-keep generator then failed with XA4319 "No NativeAOT DGML files were provided". The codegen graph carries the same "Type metadata: [...]" nodes the generator reads, so collect it at the same candidate locations and use it only when no scan graph was found (the scan graph is smaller, hence preferred). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…JavaObject In an alias group (multiple managed types mapping to one JNI name), ModelBuilder sorted the peers purely by ordinal managed type name for deterministic proxy naming. For java/lang/Object that put Java.Interop.JavaObject ([JniTypeSignature], "I..." ) before Java.Lang.Object ([Register], "L..."), so the runtime's GetTypeForSimpleReference - which returns the first (index [0]) target type for a JNI name - resolved java/lang/Object to Java.Interop.JavaObject instead of the canonical Java.Lang.Object. This broke java_lang_Object_Is_Java_Lang_Object on the trimmable typemap (surfaced on NativeAOT once the app boots). Order the canonical [Register] MCW binding first: [JniTypeSignature]-only peers (IsFromJniTypeSignature) sort after [Register] peers, with ties broken by ordinal managed type name to keep proxy naming deterministic. This mirrors the ownership rule already used by MergeCrossAssemblyAliases and matches the legacy typemap. Adds Build_AliasGroup_RegisterPeerSortsBeforeJniTypeSignaturePeer. Verified against a real generated _Mono.Android.TypeMap.dll: java/lang/Object[0] now maps to Java_Lang_Object_Proxy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The trimmable typemap's alias [0] is the canonical java->managed type returned by GetTypeForSimpleReference. Match the native runtime's rule exactly: NativeTypeMappingData (feeding clr_typemap_java_to_managed / monovm_typemap_java_to_managed) builds the java->managed map by processing the Mono.Android module first, then all others, keeping the first managed type to claim a Java name (first-writer-wins). So java/lang/Object must resolve to Java.Lang.Object (Mono.Android), not Java.Interop.JavaObject (Java.Interop). Order alias peers Mono.Android-assembly-first (ordinal managed-name tiebreak) instead of the earlier [Register]-vs-[JniTypeSignature] heuristic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… path
Accessing a resource id under the trimmable type map (the NativeAOT default) threw at runtime:
System.TypeInitializationException
---> System.IO.FileNotFoundException: _Microsoft.Android.Resource.Designer
Root cause: the _Microsoft.Android.Resource.Designer assembly IS added to the publish set
(_AddResourceDesignerToPublishFiles marks it PostprocessAssembly=true, so the SDK adds it as an
IlcReference), but it is left trimmable (IsTrimmable=true). Resource ids are resolved via
reflection at runtime (Android.Runtime.ResourceIdManager -> ResourceDesignerAttribute), which
the trimmer cannot follow, so ILC trims the whole designer assembly away and any reflection-only
resource-id access (e.g. an NUnit [TestCaseSource] static field) fails to load it.
The non-trimmable (LlvmIr) path avoids this because PreTrimmingFixLegacyDesigner rewrites legacy
field loads and, more importantly, it roots all publish assemblies for ILC. That rewrite is a
no-op for modern assemblies (whose Resource already derives from the designer), so it is not the
right fix here.
Instead, simply keep the designer assembly: add it to TrimmerRootAssembly on the trimmable path.
TrimmerRootAssembly is honored by both ILLink (CoreCLR) and ILC (NativeAOT, emitted as --root:);
ILC's TrimMode-based rooting can't be used because _AndroidComputeIlcCompileInputs clears
@(_IlcManagedInputAssemblies). No IL rewriting is required.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
449e2a1 rooted _Microsoft.Android.Resource.Designer via TrimmerRootAssembly, but on the NativeAOT path ILC only loads assemblies passed to it as references and the designer is NOT in @(IlcReference) (it is only a compile-time reference). Rooting an assembly ILC never loaded fails: ilc: error : Failed to load assembly '_Microsoft.Android.Resource.Designer' which broke every NativeAOT app build. Add the designer to @(IlcReference) as well as @(TrimmerRootAssembly), and drive both off @(ReferencePath) filtered to the designer so nothing is referenced/rooted when the designer assembly was not generated (no resources / AndroidGenerateResourceDesigner=false). @(IlcReference) is ignored by ILLink, so the CoreCLR trimmable path is unaffected and just gets the root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…default off) Instead of keeping/rooting the _Microsoft.Android.Resource.Designer assembly for the trimmer/ILC, rewrite every 'call ..._Microsoft.Android.Resource.Designer.Resource/<Type>::get_<X>()' in the resolved (non-main) managed assemblies into an inline 'ldc.i4 <id>' literal (int[] styleable getters become inline array construction), using the final post-aapt2 R.txt. Once all references are literals the designer assembly is unreferenced and the trimmer / ILC drops it entirely -- no reflection, no shipped designer assembly, nothing to root. New task InlineResourceDesignerConstants (Cecil, keyed off R.txt) + shared wiring in Xamarin.Android.Resource.Designer.targets (collect PostprocessAssembly assemblies, rewrite to obj/.../inlinedesigner, swap ResolvedFileToPublish). Trimmer-agnostic: both ILLink (@(ManagedAssemblyToLink)) and ILC (@(IlcReference)) consume the swapped items; the only per-runtime piece is the NativeAOT ordering hook (_InlineResourceDesignerConstantsUpdateItems before NativeCompile). Gated behind $(_AndroidInlineResourceDesignerConstants) (default false) while it bakes on CI; when enabled it disables _RootResourceDesignerForTrimmableTypeMap (they are mutually exclusive). Unit tests cover the scalar + styleable-array rewrite and the no-op case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (cherry picked from commit 6fe4dd4)
The main app assembly was being skipped, but it calls the designer property getters for *library* resources (e.g. SomeLibrary.Resource.Drawable.foo); those calls were left referencing the designer, so with rooting disabled the designer was still trimmed away and NinePatchTests hit FileNotFoundException: _Microsoft.Android.Resource.Designer. - InlineResourceDesignerConstants no longer skips the main assembly (only framework assemblies). - ILC compiles the main assembly from @(IntermediateAssembly)/@(IlcCompileInput), not @(ResolvedFileToPublish), so the ResolvedFileToPublish swap didn't reach it. Redirect IlcCompileInput to the rewritten inlinedesigner copy on the trimmable path. - Dropped the now-unused TargetName task parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (cherry picked from commit 79c0efd)
…mmable) Make the inline-constants path the default for the trimmable type map, replacing _RootResourceDesignerForTrimmableTypeMap (mutually exclusive): rewrite the app + library designer getter calls to literals so the _Microsoft.Android.Resource.Designer assembly is dropped entirely, instead of rooting/keeping it. Scoped to the trimmable path ($(_AndroidTypeMapImplementation) == trimmable); MonoVM/llvm-ir is unaffected. Set $(_AndroidInlineResourceDesignerConstants)=false to fall back to rooting the whole designer assembly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The inline-constants approach (rewrite designer property-getter calls to literals so the designer assembly can be trimmed) failed end-to-end: even with the main assembly rewritten and IlcCompileInput redirected, a designer reference survived and ILC still dropped the assembly, reproducing the NinePatchTests FileNotFoundException: _Microsoft.Android.Resource.Designer. Revert to the proven reference+root fix (e0ea458): add the designer to @(IlcReference) and @(TrimmerRootAssembly) so ILC loads and keeps it whole. The inline experiment is preserved on the scratch branch / PR #11996 for a future follow-up. This reverts commits 4f07afb, ce96ffd, and 4d72376. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…path The trimmable manifest generator (ManifestGenerator) already had the library manifest merge logic (MergeLibraryManifests + FixupNameElements + placeholder substitution), but it was never fed the extracted library (.aar) AndroidManifest.xml files, so on the legacy manifest-merger path the app manifest was missing library-declared permissions/activities/providers. The manifestmerger.jar path was unaffected (it merges downstream in _ManifestMerger). Wire @(ExtractedManifestDocuments) through: _GenerateTrimmableTypeMap (depends on _GetLibraryImports; adds the docs to Inputs) -> GenerateTrimmableTypeMap.MergedManifestDocuments -> ManifestConfig.LibraryManifests -> ManifestGenerator.LibraryManifests. Also surface merge failures via a new XA4302 logger warning (LogLibraryManifestMergeWarning), matching the legacy ManifestDocument path. Fixes ManifestTest.MergeLibraryManifest on the trimmable/NativeAOT path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pping
GetTypes ("[Ljava/lang/String;") dropped the System.String array family
(System.String[], JavaObjectArray<string>, JavaArray<string>) on NativeAOT.
The runtime enumerates array element types via GetTypesForSimpleReference,
which injects System.String for java/lang/String through
GetBuiltInTypeForSimpleReference. But System.String is neither a scanned Java
peer (EmitArrayEntriesForPeer) nor a primitive (EmitPrimitiveArrayEntries), so
no array proxy was generated for it. On NativeAOT the array path relies on the
pre-generated proxy map (TryGetArrayProxy), so the System.String element type
yielded nothing. (CoreCLR fabricates the types dynamically, so it was fine.)
Emit a System.String array proxy alongside the primitives in the
_Java.Interop.TypeMap assembly, using the reference-array family (Primitive is
null). The proxy map key "System.String, System.Runtime" matches
TrimmableTypeMap.TryGetManagedTypeKey (typeof (string)).
Fixes the System.String portion of Java.InteropTests.JniTypeManagerTests.GetType
on the trimmable/NativeAOT path. Boxed-nullable element types (bool?/int?/...
via java/lang/Boolean etc.) have the same shape and will be covered/verified by
follow-up test coverage.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
JniTypeManagerTests.GetType covered primitive keyword arrays ([Z..[D) and the System.String reference array ([Ljava/lang/String;), but not the boxed nullable counterparts. Add assertions for [Ljava/lang/Boolean;, [Ljava/lang/Byte;, [Ljava/lang/Character;, [Ljava/lang/Short;, [Ljava/lang/Integer;, [Ljava/lang/Long;, [Ljava/lang/Float;, [Ljava/lang/Double;. Each boxed reference maps to Nullable<T> (JniBuiltinSimpleReferenceToType / GetBuiltInTypeForSimpleReference), and the non-keyword array path yields JavaObjectArray<T?> and T?[] (no JavaArray<T>/JavaPrimitiveArray<T>). The expected types match the default ReflectionJniTypeManager behavior. These pass on the default typemap. On the trimmable typemap they exercise the same array-proxy gap just fixed for System.String; if the boxed element types lack pre-generated array proxies on NativeAOT they will fail, driving the follow-up implementation fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetTypes ("[Ljava/lang/Boolean;") and the other boxed value-type array
signatures dropped the Nullable<T> array family (bool?[], JavaObjectArray<bool?>)
on NativeAOT. Like System.String, Nullable<T> is a built-in reference mapping
injected at runtime by GetBuiltInTypeForSimpleReference, so it is neither a
scanned Java peer nor a primitive and had no pre-generated array proxy.
Unlike String, the proxy map key is the tricky part: at runtime
TryGetManagedTypeKey (typeof (bool?)) keyed on Type.FullName, whose type
argument carries the full versioned assembly-qualified name
(System.Nullable`1[[System.Boolean, System.Private.CoreLib, Version=...]]),
which the generator can't stably reproduce.
Normalize the generic key on both sides:
* Runtime (BuildManagedTypeKey): build closed-generic keys from the open
definition's FullName plus each type argument's normalized key (simple
assembly name, no Version/Culture/PublicKeyToken), and treat Nullable<T> as
a System.Runtime type. typeof (bool?) -> "System.Nullable`1[[System.Boolean,
System.Runtime]], System.Runtime".
* Generator (ModelBuilder): emit reference-array proxies for the eight
Nullable<primitive> types that have a boxed java/lang mapping, keyed with
the same normalized string.
Normalizing (rather than emitting version-qualified duplicates) keeps a single
canonical proxy per logical type.
Fixes the boxed-nullable portion of JniTypeManagerTests.GetType on trimmable
NativeAOT (added in the preceding test-coverage commit).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The trimmable typemap generates array-proxy types (JavaObjectArray<T>, T[], nested closures) up to a configurable rank so that array types can be resolved without Array.CreateInstance under NativeAOT. Emitting these uniformly at rank 3 caused a NativeAOT ILC type explosion: on an AppCompat app ILC compiled/preinitialized ~129,320 types and IlcCompile took 333s, which pushed the heaviest CI apps past the 30-min DefaultBuildTimeOut. Reference-type array proxies (scanned Java peers, System.String, boxed Nullable<T>) are now capped at a separate, lower rank (_AndroidTrimmableTypeMapMaxReferenceArrayRank, default 1 for NativeAOT) while primitive arrays keep the higher rank (default 3). Jagged reference arrays (Foo[][]) are rare, and each nesting level multiplies the JavaObjectArray<T> closure ILC must process, so this trims the bulk of the cost while preserving the common Foo[] and int[][][] cases. The anchor matrix (model.MaxArrayRank) stays uniform at max(primitive, reference) across all typemap assemblies so RootTypeMapAssemblyGenerator's rectangular [assembly][rank] sentinel layout is unchanged; only the emitted entries per rank differ. Measured on the same AppCompat NativeAOT app: ILC types 129,320 -> 22,673 (5.7x fewer) IlcCompile 333s -> 91s (3.65x faster) TypeMap DLLs 19.7MB -> 9.7MB (~2x smaller) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ment
The split-rank change capped all reference-type array proxies at rank 1,
which regressed multidimensional string arrays: GetTypes("[[Ljava/lang/String;")
no longer yielded System.String[][] / JavaObjectArray<JavaObjectArray<string>>.
That expectation is baseline Java.Interop behavior (JniTypeManagerTests.GetType,
since #517) that the managed/llvm-ir/reflection type maps all satisfy, so the
trimmable type map must satisfy it too now that it is the default.
System.String is a single, fixed built-in element type (injected at runtime by
GetBuiltInTypeForSimpleReference), not part of the scanned-peer population that
drives the NativeAOT ILC type explosion. Emit its array proxies up to the
primitive maxArrayRank instead of maxReferenceArrayRank, exactly like the
keyword primitives. The cost is negligible: on the AppCompat NativeAOT app ILC
types went from 22,673 -> 22,717 (+44) and IlcCompile stayed ~60-90s (vs rank-3's
129,320 types / 333s).
Scanned Java peers stay capped at maxReferenceArrayRank (1); boxed Nullable<T>
built-ins likewise stay at rank 1 (no baseline coverage requires multidim boxed
arrays).
Also drop TryGetArrayProxy_ObjectLeaf_ReturnsAllRankTypes: its additionalRank:2
assertions expected Object[][] etc., which is exactly the reference-rank-2 case
that split-rank intentionally no longer generates.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lements Follow-up to promoting System.String: the boxed Nullable<T> value types (java/lang/Boolean, java/lang/Integer, ...) are built-in element mappings just like System.String and the keyword primitives — a small, fixed set that is not part of the scanned-peer population driving the NativeAOT ILC type explosion. Emit their array proxies up to maxArrayRank instead of maxReferenceArrayRank so all built-in element types are treated uniformly and multidimensional boxed arrays (int?[][], "[[Ljava/lang/Integer;") resolve. Only scanned Java peers remain capped at maxReferenceArrayRank (1). Cost stays negligible on the AppCompat NativeAOT app: ILC types 22,717 -> 23,101 (+384) and IlcCompile ~56s (vs rank-3's 129,320 types / 333s). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generated typemap proxy types can derive directly from the non-generic JavaPeerProxy base when they need to represent interfaces or open generic definitions. Make the base constructor protected so those generated proxies can call it from typemap assemblies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
a3e9e32 to
2095221
Compare
This was referenced Jul 8, 2026
Keep the RegisterNatives coverage enabled under NativeAOT by switching the affected tests from JniNativeMethodRegistration[] delegate marshaling to the blittable JniNativeMethod span overload with UnmanagedCallersOnly function pointers. This avoids the dynamic-code requirement while preserving the single-method and many-method registration coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore object-leaf array proxy coverage for rank 1 and split the rank-2 reference-array assertions into an ignored test until higher-rank reference array proxy generation is revisited. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the accidental host-specific Configuration.OperatingSystem.props file from the PR. Clarify that MaxReferenceArrayRank applies only to scanned Java peer arrays, while built-in primitives, System.String, and boxed Nullable<T> use MaxArrayRank. Drop the unused maxReferenceArrayRank parameter from EmitPrimitiveArrayEntries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add _AndroidTrimmableTypeMapMaxReferenceArrayRank to the build property cache so changing the reference-array rank invalidates _GenerateTrimmableTypeMap just like the primitive/built-in rank does. Add a regression test for the property-cache invalidation and clarify the TypeMapAssemblyGenerator parameter docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid null-forgiving PE blob access, keep layout emission activity-only, and trim explanatory comments while preserving legacy placeholder behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep Jon's requested Debug.Assert without the redundant runtime null guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Track the Java trimming toggle in the proguard configuration incremental inputs. Sort user Java keep rules and cover the disabled-with-DGML case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tighten BuildProguardEnabledProject so the NativeAOT case checks the expected proguard/proguard_project_references.cfg path directly instead of recursively searching the intermediate directory. The ACW keep rules intentionally live in references.cfg on the trimmable NativeAOT path because R8 owns proguard_project_primary.cfg for app/user-Java keeps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Under the trimmable typemap on NativeAOT, the runtime host assembly Microsoft.Android.Runtime.NativeAOT - and its only Java Callable Wrapper type, UncaughtExceptionMarshaler - was never scanned by the typemap generator, so its JCW/typemap/acw-map entries were missing, R8 had nothing to keep, and the app crashed at startup in JavaInteropRuntime.init (setDefaultUncaughtExceptionHandler). Root cause: _GenerateTrimmableTypeMap runs in the RID-independent OUTER build over @(ReferencePath), the compile closure, which omits runtime-only assemblies (no ref-pack counterpart) that are only pulled from the RID-specific runtime pack in the per-RID inner build. Runtime-pack resolution (ResolveRuntimePackAssets) requires a single RuntimeIdentifier. Fix - discover them automatically from the SDK's own resolution (no hard-coded assembly names, no per-assembly reference-assembly plumbing): * _ResolveRuntimeOnlyAssembliesForTypeMap (AssemblyResolution.targets): a resolve-only sibling of _ComputeFilesToPublishForRuntimeIdentifiers that stops after ResolveReferences (no ComputeFilesToPublish/ILLink/ILC/AOT) and returns the Android runtime-pack managed assemblies (Microsoft.Android.Runtime.*) with no @(ReferencePath) counterpart (matched on Filename, so Mono.Android/Java.Interop are not double-scanned; the .NET/BCL runtime pack is excluded). * _ResolveRuntimeOnlyAssembliesForTrimmableTypeMap + _AddRuntimeOnlyAssembliesToTrimmableTypeMap (NativeAOT.targets): before _ResolveAssemblies spawns the per-RID ILC builds, run that target once via the MSBuild task for the first RID (managed metadata is RID-independent) and cache the result. The nested resolve is gated on Inputs/Outputs so it does not re-run on incremental no-op builds; an always-run loader reads the cached list into the generator's extra-framework input. ILC framework classification is generalized: any runtime-pack managed assembly's per-assembly typemap DLL is treated as framework (IlcReference but not an unmanaged-entrypoint root), matching Mono.Android/Java.Interop. 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>
When BuildProguardEnabledProject runs NativeAOT without an explicit RuntimeIdentifier, the SDK builds the default RuntimeIdentifiers and writes the generated proguard_project_references.cfg under each RID-nested intermediate path. Check those exact files instead of the flat outer intermediate path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Make the trimmable typemap the default typemap for NativeAOT.
Supersedes #11617. That PR bundled the reflection-free
TrimmableTypeMapType/ValueManagerruntime work and the NativeAOT default flip into one large change. The runtime/manager foundation has since fully landed inmainvia smaller PRs (see Already-landed spin-off PRs below), so this PR — now rebased directly onmain— is the focused remainder: flip the NativeAOT default totrimmableand adjust the tests accordingly.Contributes to #10794, #11012, #8724.
Already-landed spin-off PRs
The original effort was split into smaller child/spin-off PRs that have already landed in
main; this PR assumes those pieces as baseline rather than asking reviewers to re-review them here:TrimmableTypeMapType/ValueManager), [TrimmableTypeMap] Match n_* callback boolean/char to each binding's real signature #11802 (binding-version-awaren_*callback boolean/char signatures), [TrimmableTypeMap] Remove InvokerType from JavaPeerProxy #11971 (removeInvokerTypefromJavaPeerProxy), [Mono.Android] Root Mono.Android.Export from the [Export]/[ExportField] loader, not the attributes #11804 (rootMono.Android.Exportfrom the loader).JavaPeerProxyfor interface proxies), [TrimmableTypeMap] Generate the typemap before the NativeAOT inner build so the IDE Compile→Sign flow works #11789 (generate typemap before the NativeAOT inner build), [TrimmableTypeMap] Honor AndroidApplicationJavaClass for the JCW base and manifest #11794 (honorAndroidApplicationJavaClass), [TrimmableTypeMap] Complete trimmable manifest generation parity #11796 (manifest generation parity), [TrimmableTypeMap] Fix trimmable primitive and alias arrays #11985 (primitive/alias arrays).AndroidJavaSourceunder R8), [TrimmableTypeMap] Merge library .aar manifests on the legacy merger path #12000 (merge library.aarmanifests on the legacy merger path), [TrimmableTypeMap] Bump WorkManager test's AndroidX to post-KMP-split versions #12002 (WorkManager AndroidX post-KMP-split versions).Change map
Core enablement
Microsoft.Android.Sdk.NativeAOT.targets— default_AndroidTypeMapImplementationmanaged→trimmable.Xamarin.Android.Common.targets— run the post-ILLinkAssemblyModifierPipelinefor NativeAOT+trimmable (split out_GetAfterILLinkAdditionalStepsInputs); skip the project proguard config for NativeAOT+trimmable. (All gated ontrimmable, somanaged/llvm-irNativeAOT is unchanged.)Microsoft.Android.Sdk.TypeMap.Trimmable.targets— disableManagedPeerNativeRegistrationfor trimmable; depend on ILC'sSetupProperties($(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 toManagedTypeManager/JavaMarshalValueManagerwhen the trimmable type map is not used.Tests
BaseTest—IgnoreNativeAotLinkedAssemblyChecks/IgnoreOnNativeAothelpers.obj/<config>/<rid>/linked/output (ILC doesn't produce it):LinkerTests(×5),BuildTest2.BuildReleaseArm64,IncrementalBuildTest(×3).BuildTest2.NativeAOTis deleted (it verified the legacylinked/ManagedTypeMapping, which ILC no longer produces).BuildHasNoWarnings,XASdkTests, andXA4310now assert no warnings.ManifestTest.ExportedErrorMessageasserts the codedAMM0000error without the exact manifest line.BuildWithLibraryTests.ProjectDependenciesis skipped on NativeAOT (the trimmable typemap trims JCWs for library types that are never instantiated).DotNetBuildexpectsmapping.txtfor NativeAOT release.Mono.Android-Testsdefines aTrimmableTypeMapUnsupportedexcluded 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.Still to validate in CI
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
FixLegacyResourceDesignerStepbefore ILC (the_PreTrimmingFixLegacyDesigner*targets inMicrosoft.Android.Sdk.TypeMap.LlvmIr.targets). Thetrimmable path does not import those targets, so on trimmable NativeAOT this step does not run — and
this PR keeps it that way (
_AndroidRunNativeCompileDependsOnfor trimmable depends only onNativeCompile). That choice was validated with a small experiment (library + app, IL decompiled):AndroidUseDesignerAssembly=true): the step is a no-op.Library code compiles to
call _Microsoft.Android.Resource.Designer.Resource/Layout::get_<id>()(notldsfld), andFixLegacyResourceDesignerSteponly rewritesldsfld. The optimizer then inlines thatgetter to the correct final aapt2 id as a constant (verified: linked values
0x7F040000/0x7F030000matched the merged
R.txt). So for the common case, running or not running the step makes nomeasurable difference.
AndroidUseDesignerAssembly=false, e.g. old SkiaSharp) emitldsfldagainst aretained
Resourceclass whose static fields are populated at runtime byAndroid.Runtime.ResourceIdManager.UpdateIdValues(). Resource ids therefore still resolve correctly atruntime 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=falselibraries, rather than porting the legacy step. Modern libraries — theoverwhelming 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.
Issue references
Contributes to #10788 and #10793.