Skip to content

[TrimmableTypeMap] Fix app initialization and startup#11252

Open
simonrozsival wants to merge 21 commits intomainfrom
trimmable-typemap-startup-fixes
Open

[TrimmableTypeMap] Fix app initialization and startup#11252
simonrozsival wants to merge 21 commits intomainfrom
trimmable-typemap-startup-fixes

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented Apr 30, 2026

Summary

This fixes CoreCLR app startup with _AndroidTypeMapImplementation=trimmable while keeping the trimmable typemap path trim-safe:

  • initialize/register trimmable typemap data without relying on broad assembly roots
  • use the UTF-8 JniType overload for mono/android/Runtime
  • keep generated UCO native registration pregenerated with JniNativeMethod rows and direct ldftn function pointers
  • preserve activation/proxy lookup for trimmable typemaps without delegate-registration or reflection-activation fallbacks
  • package trimmable typemap assemblies for every CoreCLR ABI
  • keep test discovery working in the trimmable device-test lane without broad default RootMode=All roots
  • keep generator tests focused on metadata/exception-region shape instead of matching emitted IL call-token byte patterns

Follow-up PR for the non-trivial generated IL maxstack work: #11260.

Details

Runtime initialization

The runtime uses the UTF-8 JniType constructor for mono/android/Runtime, relying on the Java.Interop fix that makes the UTF-8 overload resolve through the app class loader correctly.

Trim-safe test roots

The CoreCLR trimmable device-test project no longer uses broad RootMode=All roots by default for test-discovery assemblies. It now uses RootMode=Visible for the test assemblies and has a validation target that rejects broad roots in the default trimmable mode.

Legacy broad test-discovery roots are still available behind RootAssembliesForTrimmableTestDiscovery=true if needed for comparison/debugging.

The StartupHook assembly is preserved narrowly via TrimmerRoots.xml instead of rooting a whole assembly set, and Android test project references disable transitive project references to avoid duplicate Java.Interop references.

Trimmable typemap packaging

_AddTrimmableTypeMapAssembliesToStore now batches typemap assemblies per ABI, so CoreCLR trimmable packages include the generated typemap DLLs for every target ABI instead of only the last evaluated ABI.

Test exclusions and coverage

The stale Java.Interop JniTypeSignature ManagedPeer exclusions were removed after validating the tests now pass in the CoreCLR trimmable device-test run.

The generator tests still verify metadata shape and exception-region structure, but no longer parse method-body IL bytes looking for specific call tokens. Runtime behavior is covered by the trimmable CoreCLR device-test lane.

Validation

  • ./dotnet-local.sh build src/Mono.Android/Mono.Android.csproj -v:minimal -nr:false
  • dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal
    • 430 total, 0 failed
  • ./dotnet-local.sh build -t:RunTestApp tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c Release -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false -p:AndroidPackageFormat=apk -p:RestoreConfigFile=/Users/simonrozsival/Projects/dotnet/android/NuGet.config -nr:false -v:minimal
    • 887 total, 0 errors, 0 failures
    • all 17 TrimmableTypeMapTypeManagerTests cases succeeded

simonrozsival and others added 3 commits April 30, 2026 10:28
Initialize typemap data before AndroidRuntime construction, then register the trimmable Runtime.registerNatives bridge after JniRuntime.Current is available.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit TypeMapAssemblyTargetAttribute<T> with per-assembly anchors in aggregate mode while preserving the shared anchor in merged mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep GenerateJavaPeer=false peers as direct typemap entries, suppress inherited activation constructors for them, and split target-type lookup from generated-proxy lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Apr 30, 2026
Copilot AI review requested due to automatic review settings April 30, 2026 08:33
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Apr 30, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adjusts the trimmable typemap startup sequence and typemap metadata generation so that managed typemap data is available before AndroidRuntime construction, while native registrations that require JniRuntime.Current happen after the runtime is set.

Changes:

  • Move trimmable typemap data initialization earlier in JNIEnvInit.Initialize() and register mono.android.Runtime.registerNatives(Class) after JniRuntime.SetCurrent().
  • Update root typemap generation to emit TypeMapAssemblyTargetAttribute<T> with per-assembly anchors in aggregate mode and a shared anchor in merged mode, with new metadata-level tests.
  • Refine scanning/model building and runtime lookup to treat GenerateJavaPeer=false peers as direct typemap entries, suppress inherited activation ctor resolution for them, and split “target type” vs “proxy type” lookup paths.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs Adds a Java.Interop-style activation ctor to support activation-ctor scanning scenarios in tests.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs Adds coverage ensuring GenerateJavaPeer=false peers do not inherit activation ctors.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs Ensures non-generated peers without activation ctors produce no proxy types/associations.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs Adds tests validating per-assembly vs shared anchor behavior by decoding attribute/type spec metadata.
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs Splits native registration from initialization and adds separate caches for target-type and proxy lookup.
src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs Splits target-type enumeration from proxy-type enumeration and centralizes alias entry traversal.
src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs Updates interface to expose separate target/proxy enumeration methods.
src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs Implements new interface shape across multiple universes.
src/Mono.Android/Android.Runtime/JNIEnvInit.cs Adjusts initialization ordering and registers typemap native bridge after JniRuntime.Current exists.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Suppresses inherited activation ctor discovery for IsFromJniTypeSignature && DoNotGenerateAcw.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs Emits TypeMapAssemblyTargetAttribute<T> using per-assembly anchors in aggregate mode.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs Extracts proxy-creation predicate to keep direct typemap entries for non-generated peers.

@simonrozsival simonrozsival changed the title Fix trimmable typemap startup [TrimmableTypeMap] Fix app initialization and startup Apr 30, 2026
simonrozsival and others added 16 commits April 30, 2026 11:08
Separate shared-universe and per-assembly-universe TypeMapAssemblyTargetAttribute emission paths for clarity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Limit the trimmable typemap scanner to Register/component peers for now and restore proxy-only runtime lookup semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the temporary NeedsProxy helper refactor and the extra blank line so this PR stays focused on functional changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exclude Java.Interop JniTypeSignature ManagedPeer tests that are outside the current trimmable typemap scope and add equivalent Android [Register]-based coverage for dispose, finalization, nested dispose, and generic holder activation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Android app assemblies do not have a managed entry point, so remove the SDK default EntryPoint trimmer root and root the app assembly with RootMode=All for CoreCLR trimmable typemap builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CoreCLRTrimmable is a test flavor, not an NUnit category. Since it runs on CoreCLR, keep the standard CoreCLRIgnore and NTLM exclusions while also excluding trimmable-specific categories.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move trimmable typemap assembly preparation out of _GenerateJavaStubs so packaging, compression, and register-attribute removal see the generated typemap assemblies even when Java stub generation is skipped.

Update CoreCLR typemap store handling to depend on the prepared typemap assembly item groups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture component attribute values needed by the trimmable typemap scanner, including content provider authorities, and normalize connector managed type names consistently.

Keep scanner coverage for the component and connector metadata paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Record invoker type associations on their generated proxies so trimmable typemap lookup can resolve invoker registered JNI names without generating separate proxy entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prefer pregenerated trimmable typemap JNI names in the type manager and walk base types for managed-only subclasses that do not have their own Register attribute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Register JNI natives through pregenerated JniNativeMethod entries and ldftn function pointers instead of generated delegate registration.

Generate UCO forwarders with the legacy marshal-method wrapper shape and keep inherited activation pregenerated with direct activation constructor calls.

Cover the direct registration, UCO wrapper, default UnmanagedCallersOnly, boolean ABI, and inherited activation IL shapes in generator tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore JniTypeSignature peer discovery and alias ownership after merging main, keep intentional trimmable exclusions for replaced ManagedPeer coverage, and update focused generator/runtime cleanup changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Track emitted IL stack depth in PEAssemblyBuilder instead of using a fixed maxstack of 32, and keep a minimum maxstack of 8 with safety padding.

Also keep CoreCLR trimmable test discovery trim-safe without broad assembly roots and validate MAUI CoreCLR trimmable startup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the computed maxstack generator changes from this startup-fixes branch so they can be reviewed in a separate PR based on this branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival and others added 2 commits May 1, 2026 13:19
Consolidate repeated trimmable feature-switch guards and desugar fallback assertions, and use nullable-aware string helpers in the typemap model builder.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the generator tests focused on metadata shape and exception-region structure, and rely on the trimmable CoreCLR device tests for runtime behavior instead of matching call tokens in emitted IL bytes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member Author

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

⚠️ Needs Changes pending CI. I didn't find any blocking code correctness issues in the final diff, but the internal Xamarin.Android-PR check is still in progress, so this isn't merge-ready yet.

Issue counts: ❌ 0, ⚠️ 0, 💡 1. The trimmable runtime/device coverage and removal of brittle IL token assertions are good improvements.

/// Returns all types mapped to a JNI name, resolving alias holders.
/// For non-alias entries this yields a single type. For alias groups
/// it follows each alias key and yields the surviving target types.
/// Returns all proxy types mapped to a JNI name, resolving alias holders.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 💡 Code organization — This comment now clarifies that the method returns generated proxy types rather than target types, but the method name is still the generic GetTypes. Since this interface is internal and all call sites are in this PR, consider renaming it to GetProxyTypes so the type-level distinction is visible at call sites too.

Rule: Method names must reflect behavior (Postmortem #4)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants