[TrimmableTypeMap] Fix String and nullable array proxies#12017
Open
simonrozsival wants to merge 11 commits into
Open
[TrimmableTypeMap] Fix String and nullable array proxies#12017simonrozsival wants to merge 11 commits into
simonrozsival wants to merge 11 commits into
Conversation
…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>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves the trimmable typemap’s array-proxy support by adding missing built-in proxies (notably System.String and boxed Nullable<T>), normalizing runtime array-proxy keys for closed generics, and introducing separate rank caps for reference vs primitive array proxies to reduce NativeAOT build-time/size costs.
Changes:
- Generate array proxy entries/associations for
System.Stringand boxedNullable<T>(including updated unit tests validating metadata emission and signature decoding). - Add a separate reference-array rank property (
MaxReferenceArrayRank/_AndroidTrimmableTypeMapMaxReferenceArrayRank) and plumb it through the MSBuild task + generator. - Normalize runtime managed-type keys for closed generics (e.g.,
Nullable<bool>) so runtime lookups match generator-emitted keys; relaxJavaPeerProxyctor visibility for generated proxy assemblies.
Show a summary per file
| File | Description |
|---|---|
| Configuration.OperatingSystem.props | Adds a host-specific props file (appears to be a local artifact). |
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs | Removes a reference-array proxy runtime test (coverage gap). |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs | Expands generator tests for built-in String/Nullable proxies and split-rank behavior. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs | Adds MaxReferenceArrayRank task input and passes it to the generator. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets | Introduces default _AndroidTrimmableTypeMapMaxReferenceArrayRank and passes it to the task. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets | Plumbs MaxReferenceArrayRank for the post-trim generator pass. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Normalizes managed-type keys for closed generics to match generated typemap keys. |
| src/Mono.Android/Java.Interop/JavaPeerProxy.cs | Makes the base constructor protected so generated typemap proxy assemblies can derive from it. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs | Adds maxReferenceArrayRank parameter and uses max rank for the root anchor matrix. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs | Passes split-rank settings into ModelBuilder. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs | Emits built-in proxies for System.String and boxed Nullable<T>; caps scanned-peer arrays by reference rank. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs | Adds signature-to-type coverage for boxed Nullable<T> reference arrays. |
Copilot's findings
- Files reviewed: 12/12 changed files
- Comments generated: 6
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>
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.
Summary
Independent trimmable typemap array-proxy fixes extracted from #11822, so the NativeAOT default-flip PR can stay focused.
System.Stringand boxedNullable<T>value types, including Java.InteropGetTypecoverage for boxed nullable arrays._AndroidTrimmableTypeMapMaxArrayRank(3 under AOT), while reference-like arrays use_AndroidTrimmableTypeMapMaxReferenceArrayRank(1 under AOT) to avoid generating/compiling large rank-2/3 reference-array closures by default.JavaPeerProxy's base constructor accessible to generated typemap proxy assemblies.Testing
dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj→ 613 passedNotes
Extracted from #11822. This does not change the NativeAOT default; it only fixes the opt-in trimmable typemap array behavior.
Issue references
Contributes to #10933 and #10793.