[TrimableTypeMap] Reuse base UCO wrappers for inherited overrides#11466
[TrimableTypeMap] Reuse base UCO wrappers for inherited overrides#11466simonrozsival wants to merge 7 commits into
Conversation
Avoid emitting duplicate UnmanagedCallersOnly wrappers for inherited virtual override marshal methods when a compatible base wrapper is already emitted in the generated typemap assembly. Register derived Java native methods against the base wrapper target and rely on managed virtual dispatch from the base callback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR reduces codegen bloat in the trimmable typemap assembly by reusing a base type’s existing [UnmanagedCallersOnly] (UCO) wrapper for compatible inherited virtual overrides, so derived types’ RegisterNatives can point at the base wrapper instead of generating duplicate wrappers.
Changes:
- Update typemap model + emitter to track
RegisterNativeswrapper targets (type + method), enabling cross-proxy wrapper reuse. - Implement conservative wrapper-reuse logic in the model builder and remove now-redundant derived UCO wrappers.
- Add unit tests (model + assembly IL validation) and a new device integration test covering Java→native→managed dispatch via the shared base UCO path.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | Adds device test validating inherited override dispatch using the shared base UCO wrapper under CoreCLR + trimmable typemap. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs | Adds model-level tests for reuse, ordering, and fallback behavior. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs | Adds IL-level test ensuring derived RegisterNatives references the base UCO wrapper method. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs | Switches wrapper handle tracking to use wrapper targets and updates RegisterNatives emission to resolve by target. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs | Builds native registrations in a post-pass with wrapper reuse and removes reused derived wrappers. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs | Introduces UcoWrapperTargetData and stores wrapper targets in NativeRegistrationData. |
Ensure proxy types that own reused UCO wrapper targets are emitted before proxy types whose RegisterNatives methods reference those wrappers. This keeps inherited override reuse independent of model proxy ordering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
Cover an A : B : C chain where A overrides a method declared by C and A RegisterNatives reuses C's UCO wrapper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover an inheritance chain where both the root and intermediate types have same-signature callbacks, and a leaf override must reuse the intermediate UCO rather than the root UCO. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover hidden virtual slots that reuse the same JNI signature, including the missing intermediate proxy fallback path. This ensures generated UCO wrappers keep calling the recorded callback owner instead of accidentally reusing a root-base wrapper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clarify the callback member reference handle helper name and share inline method token scanning between call and ldftn assertions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot there is a bug somewhere, find it and fix it: |
Agent-Logs-Url: https://github.com/dotnet/android/sessions/80eb25cb-843f-4ce1-975d-406fac750920 Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
... Fixed in 70b9f8f. The device test was generating Java methods for app-defined |
Summary
Avoid generating duplicate
[UnmanagedCallersOnly]wrappers for inherited virtual override marshal methods in the trimmable typemap assembly.For compatible inherited overrides, derived
RegisterNativesentries now point at the base proxy UCO wrapper target. The base callback still doesObject.GetObject<BaseType> (...)and calls the managed virtual method, so managed virtual dispatch reaches the derived override without an extra generated UCO wrapper per derived type.The reuse envelope is intentionally conservative:
[Export]direct-dispatch methodsThe emitter now also orders generated proxy types by wrapper-target dependency before emitting method bodies. This keeps derived
RegisterNativesemission order-independent: a derived proxy can safely reference a base proxy UCO wrapper even when the derived proxy appears earlier in the input model.Generated shape
The Java surface still has one native method per generated Java type. The change is only the function pointer each generated proxy passes to
RegisterNatives:Before this change, the derived proxy registered
UcoOverrideDerived0.n_doWork0()against its own duplicate UCO wrapper:After this change, the derived proxy keeps the same Java native registration, but the method callback points at the base proxy's UCO wrapper:
The shared base wrapper still calls the base registered callback; that callback performs
Object.GetObject<BaseType> (...), then the managed virtual call dispatches to the derived override:Measurements
In a synthetic app-shaped typemap with one registered abstract base and 20 registered derived types overriding 12 virtual methods:
osx-arm64NativeAOT proxy executable: 1,136,000 B -> 1,102,752 B (-33,248 B, -2.93%)That works out to roughly 87 B saved in the CoreCLR typemap DLL and 139 B in the stripped NativeAOT proxy executable per removed derived-method UCO wrapper. These are directional estimates; actual app savings depend on metadata/native layout and alignment.
Tests
dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj --no-restore(556 passed, run with .NET SDK 10.0.101 from a session-local global.json)Added model/emitted-IL coverage for inherited override reuse, including the derived-before-base input-order case and intermediate callback-owner case, plus a focused CoreCLR/trimmable
MSBuildDeviceIntegrationtest for Java-to-native dispatch through the shared base UCO path. I could not run the device test locally because this worktree's repo-prep/device-test build is blocked by local tooling/generated-artifact issues (make preparefails under the local .NET 11 preview SDK; the device-test build then hits missingjavac,XABuildConfig.cs,Xamarin.Android.Tools.BootstrapTasks.dll, and manifestmerger Gradle failures).