[TrimmableTypeMap] Improve array handling#11238
Conversation
5b32352 to
956a999
Compare
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — Clean design, minor doc/style nits
Well-architected PR. The runtime fork (CoreCLR → Array.CreateInstance directly, NativeAOT → per-rank typemap + Array.CreateInstanceFromArrayType) is sound and avoids unnecessary typemap lookups on CoreCLR while remaining AOT-safe on NativeAOT.
What looks good:
- IL emission for both aggregate (Debug) and single-universe (Release) paths is correct — stack discipline, local variable encoding, and member reference blobs all check out.
- Thorough edge-case handling in
EmitArrayEntries: primitives skipped (dedicated JNI handlers exist), open generics rejected (typeof(T<>[])invalid), alias groups deferred. TryGetArrayType's unwrap loop correctly handles nested szarrays and rejects multi-dim arrays.- 14+ new generator unit tests cover the key scenarios including PE round-trip verification.
- Sentinel types in
ArrayMapAnchors.csmirror the established__TypeMapAnchorpattern nicely. MaxSupportedArrayRank = 8matches the 8 anchor types, with the MSBuild default of 3 for NativeAOT being practical for typical apps.
Minor nits (3 inline 💡):
| Sev | Category | File |
|---|---|---|
| 💡 | Formatting | Array.Empty<T>() → [] (2 instances) |
| 💡 | Documentation | EmitArrayEntries doc comment mentions [L<jni>;-shaped keys but keys are bare element names |
| 💡 | Documentation | Stale 2-arg Initialize references in comments |
No correctness, safety, or performance issues found.
Generated by Android PR Reviewer for issue #11238 · ● 9.8M
There was a problem hiding this comment.
Pull request overview
Refactors trimmable typemap array creation to avoid per-T factory IL bloat by routing array construction through JNIEnv.ArrayCreateInstance, using Array.CreateInstance when dynamic code is supported and a per-rank array typemap under NativeAOT.
Changes:
- Add generator support for per-rank array
TypeMapentries (with rank anchors) and plumbMaxArrayRankthrough the MSBuild task + targets. - Update the runtime typemap initialization and lookup to support array-type resolution under NativeAOT, and update
JNIEnv.ArrayCreateInstanceto use the new runtime fork. - Remove
JavaPeerContainerFactory<T>array creation APIs and update tests to cover the new generator/root-assembly behavior.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs | Adds unit tests validating array-entry model emission (ranks, keys, skip rules, blob round-trip). |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs | Adds tests for merged-universe root assembly generation when arrays are enabled. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs | Adds MaxArrayRank task input and forwards it into typemap generation. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets | Introduces $(_AndroidTrimmableTypeMapMaxArrayRank) defaults and passes it to the task. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Extends initialization to accept per-rank array dictionaries and adds TryGetArrayType lookup used by NativeAOT. |
| src/Mono.Android/Microsoft.Android.Runtime/ArrayMapAnchors.cs | Adds shared __ArrayMapRank{N} anchor types in Mono.Android for rank grouping. |
| src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs | Removes array creation APIs from the container factory surface (arrays move to JNIEnv). |
| src/Mono.Android/Android.Runtime/JNIEnv.cs | Implements the runtime fork for array creation (dynamic-code vs NativeAOT typemap). |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs | Adds MaxSupportedArrayRank validation and threads maxArrayRank through generation. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs | Passes maxArrayRank into ModelBuilder.Build. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs | Emits rank anchor refs and supports rank-anchored 3-arg TypeMap<TGroup> constructor emission. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs | Emits root-loader initialization that passes array rank dictionaries (or null) into TrimmableTypeMap.Initialize. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs | Emits per-rank array entries into the typemap model when enabled. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs | Adds MaxArrayRank and per-entry AnchorRank metadata for array mapping. |
|
@copilot fix this branch based on this CI build failure analysis: |
Improve CreatePeer under the trimmable typemap to match legacy JavaCast/JavaAs contracts: - Bad-cast disambiguation: distinguish incompatible Java types (return null → InvalidCastException) from missing typemap entries (ArgumentException) and generator gaps (NotSupportedException). - Closed-generic activation: when the proxy targets an open generic (e.g. JavaList<>), activate the closed targetType (e.g. JavaList<int>) via reflection using the (IntPtr, JniHandleOwnership) ctor. The [DynamicallyAccessedMembers(Constructors)] annotation on targetType guarantees the trimmer preserves the ctor metadata. - Type resolution: map IJavaPeerable/object/Exception to concrete peer types before proxy lookup, mirroring the legacy GetPeerType behavior. - TargetTypeMatches: restructure so open-generic proxies match only closed instantiations of their definition. - FindClass safety: catch ClassNotFoundException in TryGetProxyFromTargetType for types not present in the APK. - Don't synthesize activation from inherited ctors: match legacy GetConstructor() which doesn't find inherited constructors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add trimmable-typemap variant of GetThis.java that uses mono.android.Runtime.register instead of ManagedPeer.registerNativeMembers. Java.Interop-Tests.targets swaps variants based on $(_AndroidTypeMapImplementation). Re-enabled tests: - JavaCast_BadInterfaceCast (bad-cast disambiguation) - JavaCast_BaseToGenericWrapper (closed-generic activation) - JavaCast_CheckForManagedSubclasses (bad-cast disambiguation) - JavaCast_InvalidTypeCastThrows (bad-cast disambiguation) - JavaAs_Exceptions (inherited ctor fix) - DisposeAccessesThis (trimmable GetThis.java) - CreateGenericValue (ArrayRank scanner fix) Updated remaining exclusion comments with accurate root-cause descriptions. Removed resolved TODO comments from test files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For every non-aliased, non-generic peer (excluding JNI primitive-keyword
keys), the generator can now emit three speculative `TypeMap<T>` entries
keyed by the **element JNI name** and anchored to per-typemap-assembly
`__ArrayMapRank{1,2,3}` sentinel TypeDefs:
[assembly: TypeMap<__ArrayMapRank1>("java/lang/String", typeof(string[]), typeof(string[]))]
[assembly: TypeMap<__ArrayMapRank2>("java/lang/String", typeof(string[][]), typeof(string[][]))]
[assembly: TypeMap<__ArrayMapRank3>("java/lang/String", typeof(string[][][]), typeof(string[][][]))]
The trim target is the closed array type itself, so ILC's per-shape
conditional drops entries when the array shape is never constructed —
validated on disjoint-set NativeAOT tests for both reference and value
type element peers.
* `Generator/Model/TypeMapAssemblyData.cs`:
* `TypeMapAssemblyData.RankSentinels` (nullable `RankSentinelNames`)
— when set, the emitter generates the three sentinel TypeDefs.
* `TypeMapAttributeData.AnchorRank` (nullable int) — when set,
overrides the model-level default anchor with the rank-{value}
sentinel from the same assembly.
* `Generator/TypeMapAssemblyEmitter.cs`:
* `EmitRankSentinels` mirrors the existing `__TypeMapAnchor` pattern.
* Per-anchor `TypeMap<TGroup>` 3-arg ctor refs are now built and
cached lazily by anchor handle (`GetOrAddTypeMapAttr3ArgCtorRef`).
* `EmitTypeMapAttribute` resolves rank-anchored entries to the local
sentinel handle.
* `Generator/ModelBuilder.cs`:
* `Build` takes a new `bool emitArrayEntries` (default false).
When true, sets `RankSentinels` and routes each peer through
`EmitArrayEntries`, which produces the per-rank trio.
* Skip rules: open-generic peers, JNI primitive-keyword keys, alias
groups (would produce duplicate keys; deferred).
* `tests/.../TypeMapModelBuilderTests.cs`: 14 new tests covering the
default-off behavior, default sentinel names, per-rank-trio emission,
element-only key, closed-array trim target, conditional-only entries,
open-generic / alias / primitive-keyword skip rules, multiple-peer
isolation, and PE blob round-trip (sentinels emitted, attribute
blobs survive).
Tracking: #11234 Phase 2 (arrays-only, container types stay untouched).
Runtime wiring + MSBuild flag follow in subsequent commits.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`JNIEnv.ArrayCreateInstance` now branches on `RuntimeFeature.IsDynamicCodeSupported`: * CoreCLR / Mono (true) — `Array.CreateInstance(elementType, length)`. No typemap roundtrip; supports unlimited array rank. * NativeAOT (false) — typemap lookup → AOT-safe `Array.CreateInstanceFromArrayType`. Capped at the emitted ranks (1–3); miss throws `NotSupportedException` with diagnostic. The runtime fork lets us avoid emitting (and paying for) speculative array TypeMap entries on CoreCLR-only builds, where the runtime type loader can construct any `T[]` dynamically anyway. * `ITypeMapWithAliasing.TryGetArrayType(string jniElementTypeName, int rank, out Type? arrayType)` — new abstraction for the per-rank array dictionary lookup. `SingleUniverseTypeMap` carries three nullable `IReadOnlyDictionary<string, Type>?` fields (rank 1, 2, 3) populated at `TrimmableTypeMap.Initialize` time; `AggregateTypeMap` does first-wins iteration. * `TrimmableTypeMap.TryGetArrayType(Type elementType, out Type?)` — walks down `elementType.IsArray` / `GetElementType()` to find the leaf type and array depth, resolves the leaf JNI element name (primitive static dict OR `TryGetJniNameForManagedType` wrapped), and delegates the (jni, rank+1) lookup to the interface. * `TrimmableTypeMap.Initialize` gains 5-arg overloads (single + aggregate) accepting the per-rank dicts. Existing 2-arg overloads stay as wrappers passing null per-rank dicts so older generated assemblies keep working. * `RootTypeMapAssemblyGenerator`: the generated `TypeMapLoader.Initialize` IL now branches on the new `emitArrayEntries` flag. When true, it collects per-assembly `__ArrayMapRank{1,2,3}` sentinels via `TypeMapping.GetOrCreateExternalTypeMapping<__ArrayMapRank{N}>()` and passes the resulting dicts to the 5-arg `TrimmableTypeMap.Initialize`. Aggregate (Debug) path is fully implemented; merged-universe (Release) path throws at generation time with a clear message — wiring the shared-universe array sentinels is a small follow-up. * `GenerateTrimmableTypeMap` MSBuild task: new `EmitArrayEntries` property forwarded through `TrimmableTypeMapGenerator.Execute` and `RootTypeMapAssemblyGenerator.Generate`. SDK target sets it to `$(PublishAot)`. * `JavaPeerContainerFactory<T>.CreateArray` and `CreateHigherRankArray` deleted. Container methods (`CreateList`, `CreateCollection`, `CreateDictionary*`) stay untouched — those are tracked separately in #11234. Validation: * 445 / 445 generator unit tests pass. * Trimmable + CoreCLR `RunTestApp` lane on emulator: **917 total / 0 errors / 3 failures** (pre-existing `TryGetJniNameForManagedType_*`, called out as out-of-scope in #11225). No regression. * The NativeAOT branch path is gated on dotnet/runtime#126380 (ships in .NET 11 nightly preview.5+); validated with the playground repro separately. Tracking: #11234 Phase 2 (arrays only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* JNIEnv.ArrayCreateInstance:
* Drop the IL3050 #pragma — IsDynamicCodeSupported acts as a
[FeatureGuard] so the trimmer dead-codes the dynamic branch under
PublishAot without needing a manual suppression.
* Drop the 'Mono' mention from the comment — the trimmable typemap
is a CoreCLR-only feature.
* SingleUniverseTypeMap._arrayMapsByRank:
* Switched from a 1-indexed array of fixed length 4 (with [0] unused)
to a 0-indexed variable-length array (length determined by what the
generator emitted). TryGetArrayType uses (rank - 1) for indexing
and bounds-checks to the actual length.
* Variable rank count throughout:
* RankSentinelNames: replaced the fixed (Rank1, Rank2, Rank3) record
with a name list + Count, generated via CreateDefault(maxRank).
* TypeMapAssemblyEmitter: _rankAnchorHandles becomes a 0-indexed
variable-length array sized to model.RankSentinels.Count.
* ModelBuilder.Build: replaced 'bool emitArrayEntries' with
'int maxArrayRank' (0 = disabled). EmitArrayEntries takes the max
and loops 1..maxArrayRank instead of using a const.
* RootTypeMapAssemblyGenerator: 'int maxArrayRank' parameter; the
generated TypeMapLoader.Initialize now builds a single jagged
'IReadOnlyDictionary<string, Type>?[][]' (per-universe per-rank)
instead of fixed-rank-count locals. Rank loop is unrolled.
* GenerateTrimmableTypeMap MSBuild task: 'int MaxArrayRank' property.
* New $(_AndroidTrimmableTypeMapMaxArrayRank) MSBuild property —
defaults to 3 under PublishAot=true, 0 otherwise. Users can override
to support unlimited rank under NativeAOT (limited to what they're
willing to pay in attribute metadata).
* TrimmableTypeMap.Initialize:
* Single overload: '(typeMap, proxyMap, IReadOnlyDictionary<string,
Type>?[]? arrayMapsByRank)' — 0-indexed by (rank - 1).
* Aggregate overload: '(typeMaps[], proxyMaps[],
IReadOnlyDictionary<string, Type>?[]?[]? perUniverseArrayMaps)' —
jagged, indexed first by universe, then by (rank - 1).
* Existing 2-arg overloads retained as wrappers passing null.
* Tests: updated BuildModelWithArrays to take maxArrayRank (default 3).
Replaced 'RankSentinels.Rank{1,2,3}' assertions with Names[i] indexing.
Added Build_EmitArrayEntries_HonoursMaxArrayRank test that verifies
emission with maxArrayRank=1 and =5.
Validation:
* 446 / 446 generator unit tests pass (445 baseline + 1 new for the
MaxArrayRank parametrization).
* Trimmable + CoreCLR RunTestApp lane on emulator: 917 / 0 errors / 3
pre-existing failures. No regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tightens XML doc comments and inline notes added in this PR — keeps the intent but drops walls of explanation that were repeating what the code already says. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the NotSupportedException stub in RootTypeMapAssemblyGenerator
with a fully functional shared-universe + array entries IL emit path,
covering the Release build configuration when array typemap entries
are enabled (`_AndroidTrimmableTypeMapMaxArrayRank > 0`).
Each per-assembly typemap DLL still owns its own `__ArrayMapRank{N}`
sentinel TypeDefs, so each rank's entries are split across N per-asm
dicts. The new `CompositeStringTypeReadOnlyDictionary` wraps an array
of source dicts and routes `TryGetValue` first-hit, letting the existing
single-universe-with-arrays `TrimmableTypeMap.Initialize` overload
consume them as a single per-rank dict each.
Generated IL (shared + arrays):
var arrayMapsByRank = new IReadOnlyDictionary<string, Type>?[maxRank];
for each rank r:
var sources = new IReadOnlyDictionary<string, Type>?[asmCount];
sources[i] = TypeMapping.GetOrCreateExternalTypeMapping<PerAsm[i].__ArrayMapRank_r>();
arrayMapsByRank[r-1] = new CompositeStringTypeReadOnlyDictionary(sources);
TrimmableTypeMap.Initialize(
TypeMapping.GetOrCreateExternalTypeMapping<Java.Lang.Object>(),
TypeMapping.GetOrCreateProxyTypeMapping<Java.Lang.Object>(),
arrayMapsByRank);
Also extends `IgnoresAccessChecksTo` to cover per-asm DLLs in shared
mode when arrays are enabled (root needs to reach their internal sealed
`__ArrayMapRank{N}` types).
Adds 4 generator unit tests; all 450 tests pass. Validated default
Release CoreCLR trimmable lane: 917/0/3 (matches baseline).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes obvious redundant inline comments and trims a couple of doc summaries; behavior unchanged. 450/450 unit tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an IsSZArray guard inside the leaf walk so multi-dimensional arrays (e.g. byte[,], byte[,][]) bail out cleanly rather than walking through GetElementType() as if they were jagged szarray chains. JNI only supports single-dim zero-based arrays so any multi-dim element type reaching this method indicates a caller bug — return false rather than silently producing a misleading rank-N lookup. In practice this is unreachable from MCW-generated marshaling (JNI arrays are always szarrays) so behavior is unchanged for real callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces TypeMapAssemblyData.RankSentinels (a RankSentinelNames wrapper
class storing a list of derived-from-rank strings) with a plain int
MaxArrayRank field. The sentinel names are always `__ArrayMapRank{N}`
by convention — both the per-asm emitter and the runtime/root loader
hard-code that pattern — so the wrapper added no value beyond rank
storage.
Removes ~35 LOC and a public-ish helper class. 450/450 unit tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Folds the multi-source first-hit logic into SingleUniverseTypeMap by making its rank dict storage jagged (`[rank-1][source]`). The composite class was just a thin wrapper around a list of dicts that did first-hit TryGetValue — exactly what the type map's own array lookup needs to do. Native jagged storage avoids the wrapper allocation, removes one type, and eliminates a member-ref + newobj from the generated IL per rank. Initialize signature changes: the single-universe-with-arrays overload now takes `IReadOnlyDictionary<string,Type>?[]?[]?` (jagged) instead of `IReadOnlyDictionary<string,Type>?[]?`. The aggregate path wraps each universe's 1-source rank dicts into 1-element-inner-jagged at construction time so SingleUniverseTypeMap's storage shape is uniform. Mono.Android Release builds clean. 449/449 unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Defines `Microsoft.Android.Runtime.__ArrayMapRank1` ... `__ArrayMapRank8` as internal sealed types in Mono.Android. Per-asm typemap DLLs no longer emit their own rank sentinel TypeDefs — they reference the shared anchors instead. Per rank, every per-asm DLL contributes to the SAME TypeMap group, so `TypeMapping.GetOrCreateExternalTypeMapping<__ArrayMapRankN>()` returns ONE merged dict spanning all per-asm DLLs. Knock-on simplifications: - `SingleUniverseTypeMap` and `AggregateTypeMap` no longer hold rank dicts; the rank lookup table lives directly on `TrimmableTypeMap` (singleton) since it's logically global, not per-universe. - `ITypeMapWithAliasing.TryGetArrayType` removed — no longer needed. - `Initialize(typeMaps[], proxyMaps[], perUniverseArrayMaps[][])` collapses to `Initialize(typeMaps[], proxyMaps[], arrayMapsByRank[])` (1D, same shape as the single-universe overload). Both modes pass the SAME array-shape — the only thing that varies is whether the main typemap is single (shared) or N-element (aggregate). - Root assembly IL: shared and aggregate paths share an `EmitBuildArrayMapsByRank` helper that emits `maxArrayRank` calls to `GetOrCreateExternalTypeMapping<Mono.Android.__ArrayMapRankN>()` directly. Shared mode no longer needs IgnoresAccessChecksTo on per-asm DLLs. - The supported maximum is `MaxSupportedArrayRank = 8`. The generator throws `ArgumentOutOfRangeException` if `_AndroidTrimmableTypeMapMaxArrayRank` exceeds it (with a message pointing at the Mono.Android types to add for more). Net -82 LOC. Mono.Android Release builds clean. 449/449 unit tests pass. Device test (Release CoreCLR trimmable): 917 / 0 errors / 3 pre-existing failures — matches baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drops the unreachable elementType null check (param is non-nullable under #nullable enable), collapses the redundant rank/index arithmetic (rankIndex = elementDepth = rank - 1), and folds the JNI name resolution into a single ternary. Behavior unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Collapses 4 IL emit paths × 4 MemberRef helpers in the root assembly generator down to 2 of each. Both paths always emit the 3-arg `Initialize` (with arrayMapsByRank); when maxArrayRank=0 the new `EmitArrayMapsByRankOrNull` helper just emits `ldnull`. Also drops the matching no-array `Initialize` C# overloads from `TrimmableTypeMap` since nothing calls them — the generated root assembly always picks the 3-arg shape now. The internal callers can pass `arrayMapsByRank: null`. Net -157 LOC. 449/449 unit tests pass; Mono.Android Release builds clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. EmitTypeMapLoader: only build the Initialize MemberRef for the path actually taken (was building both shared and aggregate variants unconditionally — added a stray MemberRef row to every assembly). 2. ModelBuilder.Brackets: replace LINQ Enumerable.Repeat fallback for rank>=4 with a straightforward StringBuilder loop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drops a redundant `if (maxArrayRank > 0)` guard (the default is 0 already and the parameter is validated non-negative above) and inlines `moduleName`. Behavior unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The == default check on _rankAnchorHandles[i] was redundant — since EmitRankSentinels populates all slots up to MaxArrayRank, the length check is sufficient. The error message also referenced 'TypeDef' when we now emit TypeRefs (shared anchors live in Mono.Android). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore TrimmableTypeMap Initialize compatibility overloads, add primitive-leaf array fallback, and clarify array typemap documentation and diagnostics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rt.Single Agent-Logs-Url: https://github.com/dotnet/android/sessions/51b7a15a-d4ad-4309-98ee-1487a03f3b99 Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
682020a to
d7ab389
Compare
Summary
Improves array creation for the CoreCLR trimmable typemap path by removing the per-
Tarray factory methods fromJavaPeerContainerFactory<T>and routing array construction throughJNIEnv.ArrayCreateInstance.JNIEnv.ArrayCreateInstancenow uses a runtime fork:Array.CreateInstance(elementType, length)directly.Array.CreateInstanceFromArrayType(arrayType, length).This avoids per-peer array-creation IL bloat on CoreCLR while keeping an AOT-safe path for NativeAOT.
Tracking: #11234 Phase 2 arrays-only. Container types (
JavaList<T>,JavaCollection<T>,JavaDictionary<K,V>) remain out of scope.Generator changes
When
$(_AndroidTrimmableTypeMapMaxArrayRank)is greater than 0, the per-assembly typemap generator emits speculative arrayTypeMapentries for each non-aliased, non-generic, non-primitive-key peer:Keys are bare element JNI names. Rank is encoded by the shared
__ArrayMapRank{N}anchor group, so runtime lookup avoids constructing JNI array signature strings.The MSBuild default is:
3for NativeAOT (PublishAot=true), where dynamic code is unavailable and array creation uses the typemap path.0otherwise, where dynamic code can useArray.CreateInstancedirectly.Merged-universe and aggregate root typemap generation both support array entries. When no array entries are emitted, generated loaders pass
nullforarrayMapsByRank.Runtime changes
TrimmableTypeMap.Initializehas 3-arg overloads that accept per-rank array maps, and keeps the previous 2-arg overloads as compatibility wrappers that passnull.TrimmableTypeMap.TryGetArrayType(Type elementType, out Type? arrayType)walks jagged array element types to find the leaf type and rank, then uses the per-rank__ArrayMapRank{N}dictionary keyed by the leaf JNI name. Primitive leaf arrays intentionally do not require primitive-keyed rank-map entries; they fall back to constructing the next primitive array type directly.JNIEnv.ArrayCreateInstancereports missing non-primitive array mappings with the relevant__ArrayMapRank{N}group and_AndroidTrimmableTypeMapMaxArrayRankguidance.Validation
dotnet build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -v:minimal -nologo --no-restoredotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj --no-restore -v:minimal -nologo -consoleloggerparameters:NoSummary— 449 passeddotnet msbuild src/Mono.Android/Mono.Android.csproj /restore /p:DotNetToolPath=$(which dotnet) -v:minimal -nologo -consoleloggerparameters:NoSummary