Skip to content

[TrimmableTypeMap] Fix UCO constructor activations#11094

Merged
simonrozsival merged 5 commits intomainfrom
dev/simonrozsival/fix-uco-activation-parity
Apr 10, 2026
Merged

[TrimmableTypeMap] Fix UCO constructor activations#11094
simonrozsival merged 5 commits intomainfrom
dev/simonrozsival/fix-uco-activation-parity

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented Apr 9, 2026

Summary

This PR fixes the UCO constructor activation parity regression for trimmable typemaps in one review.

What it does

  • keep the JniEnvironment.WithinNewObjectScope guard in generated nctor_*_uco wrappers to avoid double peer creation from managed construction paths
  • remove the reflection-based ActivatePeerFromJavaConstructor() path entirely
  • inline activation directly in the generated wrappers, removing the TrimmableTypeMap.ActivateInstance() indirection
  • preserve the fully generated, non-reflective activation flow for both leaf and inherited activation ctors
  • keep the minimal generator plumbing needed by the emitted IL (PEAssemblyBuilder.EmitBody(..., useBranches: true)) and the RegisterNatives UTF-8 helper materialization

Non-goals

  • no reflection
  • no runtime fallback behavior
  • no unrelated trimmable typemap follow-up work

Notes

Refactor UCO constructor wrappers to match legacy ManagedPeer.Construct
activation behavior:

- Add WithinNewObjectScope guard: skip activation when the object is
  being created from managed code (JNIEnv.StartCreateInstance/NewObject),
  preventing double peer creation and GC crashes.

- For non-leaf types (activation ctor on a base type), call
  ActivatePeerFromJavaConstructor at runtime instead of inline IL.
  This finds the matching managed ctor for the JNI signature (e.g.,
  parameterless for "()V") and invokes it via ActivatePeer, preserving
  user constructor side effects like field initialization.

- For leaf types, inline the activation ctor call directly in the UCO
  with the WithinNewObjectScope guard.

- Emit no-op UCO for open generic type definitions (type params unknown).

- Add ControlFlowBuilder support to PEAssemblyBuilder.EmitBody for
  branch instructions (useBranches parameter).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- UCO constructors directly call activation ctor (no ActivateInstance indirection)
- WithinNewObjectScope guard prevents double peer creation
- No-op UCO for open generic type definitions
- ControlFlowBuilder support in PEAssemblyBuilder
- Remove TrimmableNativeRegistration wrapper and ActivateInstance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival marked this pull request as ready for review April 9, 2026 16:10
Copilot AI review requested due to automatic review settings April 9, 2026 16:10
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Fix UCO constructor activation parity with legacy [TrimmableTypeMap] Fix UCO constructor activations Apr 9, 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 updates the trimmable typemap generator/runtime path to match legacy UCO constructor activation behavior by guarding against double peer creation and inlining the activation IL in generated nctor_*_uco wrappers (removing the previous runtime helper entrypoint).

Changes:

  • Inline managed peer activation directly in generated UCO constructor wrappers, including a JniEnvironment.WithinNewObjectScope guard.
  • Remove TrimmableTypeMap.ActivateInstance() from the runtime path.
  • Extend PEAssemblyBuilder.EmitBody(...) to optionally enable branch/label emission (via ControlFlowBuilder) and add a generator test for the new behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs Adds a regression test asserting UCO constructors reference WithinNewObjectScope + inlined activation APIs and do not reference removed helpers.
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs Removes the now-unused ActivateInstance runtime helper.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs Switches UCO constructor wrapper emission to guarded, inlined activation and adjusts supporting references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs Adds a useBranches option to allow emitting branching IL via ControlFlowBuilder; adjusts UTF-8 helper nested type visibility.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs Updates model documentation to reflect the new non-helper activation behavior.

Comment on lines +377 to +379
encoder.OpCode (ILOpCode.Ret);
});
metadata.AddCustomAttribute (typeDefHandle, ctorHandle, _ucoAttrBlobHandle);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This adds a custom attribute to the proxy type using ctorHandle (the proxy’s own .ctor MethodDef) as the attribute constructor. That produces invalid metadata (the ctor handle must point to the attribute’s ctor, e.g. _ucoAttrCtorRef), and UnmanagedCallersOnlyAttribute shouldn’t be applied to the proxy type/instance ctor anyway. Remove this AddCustomAttribute call (UCO should only be applied via AddUnmanagedCallersOnlyAttribute() on the generated wrapper methods).

Copilot uses AI. Check for mistakes.
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.

🤖 AI Review Summary

Verdict: ⚠️ Needs Changes

Found 1 follow-up item:

  • 💡 Testing: the new UTF-8 helper pre-materialization is a subtle metadata-ordering workaround and would benefit from more targeted regression coverage (src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs:348).

The main UCO activation change looks consistent with the intended non-reflective design. Public CI is green, but the internal Xamarin.Android-PR pipeline is still queued, so this is not ready for LGTM yet.


Review generated by android-reviewer from review guidelines.

void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinitionHandle> wrapperHandles)
{
if (proxy.IsAcw) {
// RegisterNatives uses RVA-backed UTF-8 fields under <PrivateImplementationDetails>.
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.

🤖 💡 Testing — this eager GetOrAddUtf8Field() loop is fixing a very subtle metadata-ordering problem in the generated assembly. Could we strengthen Generate_AcwProxy_HasRegisterNativesAndUcoMethods() to assert that RegisterNatives is owned by the proxy type, not just present somewhere in MethodDefinitions? That would lock in the exact behavior this workaround depends on and make future cleanup safer.

{Rule: Missing regression tests for subtle generator fixes}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival merged commit 2d453fc into main Apr 10, 2026
6 checks passed
@simonrozsival simonrozsival deleted the dev/simonrozsival/fix-uco-activation-parity branch April 10, 2026 07:34
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