Skip to content

[TrimmableTypeMap] Trim value-type dictionary instantiations - #12793

Open
simonrozsival wants to merge 1 commit into
mainfrom
simonrozsival-typemap-instantiation-reduction
Open

simonrozsival wants to merge 1 commit into
mainfrom
simonrozsival-typemap-instantiation-reduction

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Sep 15, 2026

Copy link
Copy Markdown
Member

Motivation

NativeAOT cannot create arbitrary closed generic types at runtime. Reference-type generic arguments can share a canonical __Canon template, but value-type arguments remain exact: JavaDictionary<int,long>, JavaDictionary<int,double>, and JavaDictionary<int?,long?> each require their own generated native code and runtime metadata.

The previous implementation supported Java-to-managed conversion for every primitive and nullable value/value dictionary shape through generic virtual dispatch:

ValueTypeFactory<TKey>.CreateDictionary(...)
    -> ValueTypeFactory<TValue>.CreateDictionaryWithKey<TKey>(...)
    -> new JavaDictionary<TKey,TValue>(...)

Because every one of the 18 supported primitive/nullable ValueTypeFactory<T> instantiations was reachable, NativeAOT's generic-virtual dependency analysis conservatively generated the complete 18 × 18 matrix. Consequently, every trimmable NativeAOT application paid for all 324 JavaDictionary<TKey,TValue> instantiations even when the application used no value/value dictionary—or only one combination. Size analysis showed JavaDictionary<TKey,TValue> contributing roughly 1.4 MB of the managed NativeAOT image.

The desired behavior is for each exact generic instantiation to follow normal application reachability: using IDictionary<int,long> should retain its converter and JavaDictionary<int,long> constructor, without retaining unrelated combinations.

Approach

This change replaces the generic-virtual cross-product with a handwritten conditional type-map universe:

  • TypeMap<JavaDictionary> entries associate each supported target shape with a small converter type and use the target shape as the trim target.
  • Both IDictionary<TKey,TValue> and concrete JavaDictionary<TKey,TValue> targets are represented.
  • Converter types are file-local, self-applied attributes. Attribute activation is NativeAOT-safe and roots the converter's exact direct new JavaDictionary<TKey,TValue>(...) call.
  • ValueTypeDictionaryFactory performs a string-keyed external type-map lookup. String keys are important because probing an unlisted closed generic through the proxy map requires its TypeHandle; NativeAOT may intentionally have no EEType for that unsupported shape.
  • The generated root typemap assembly points to Mono.Android for this universe. The map entries remain conditional, so unused converters and closed generic instantiations can still be removed.
  • Unsupported value types continue to use the existing non-generic JavaDictionary fallback.

The universe explicitly describes all 18 primitive and nullable value types in each key/value position. Unlike the previous GVM implementation, listing all supported combinations does not mean all combinations enter every application: the trimmer includes an entry only when its trim-target type is used.

APK size

An identical dotnet new maui app was published as an unsigned Release android-arm64 APK with the trimmable typemap. The baseline is this stack's exact main parent (8f7c4d4fa5).

Runtime Stack base This PR Change vs previous Change vs stack base
NativeAOT 14,180,177 B 13,366,327 B -813,850 B (-5.739%) -813,850 B (-5.739%)
CoreCLR 18,032,646 B 18,053,072 B +20,426 B (+0.113%) +20,426 B (+0.113%)

The NativeAOT app library itself decreased from 24,045,952 B to 21,440,264 B: -2,605,688 B (-10.84%) uncompressed. CoreCLR does not pay for NativeAOT generic code generation; its small increase is the retained typemap metadata/runtime machinery.

Validation

  • NativeAOT Android device suite: 706 passed, 11 skipped
  • CoreCLR trimmable Android device suite: 744 passed, 12 skipped
  • root typemap generator tests: 17 passed

Replace the NativeAOT generic-virtual cross-product with a conditional type-map converter universe for primitive and nullable value-type dictionaries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival marked this pull request as ready for review September 15, 2026 17:27
Copilot AI lite review requested due to automatic review settings September 15, 2026 17:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

2 participants