Skip to content

[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert#10967

Open
simonrozsival wants to merge 9 commits intomainfrom
dev/simonrozsival/trimmable-typemap-runtime-pr
Open

[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert#10967
simonrozsival wants to merge 9 commits intomainfrom
dev/simonrozsival/trimmable-typemap-runtime-pr

Conversation

@simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Mar 18, 2026

Closes #10791

Summary

Adds the runtime-side support for the trimmable typemap: type resolution, peer creation, native method registration, and AOT-safe collection marshaling. All behind RuntimeFeature.TrimmableTypeMap (defaults to false).

New runtime types

  • JavaPeerProxy / JavaPeerProxy<T> — AOT-safe proxy attribute base. Generated proxy types extend this and provide CreateInstance() for peer creation and GetContainerFactory() for collection marshaling.
  • IAndroidCallableWrapperRegisterNatives(JniType) interface for ACW proxy types to register JNI native methods.
  • JavaPeerContainerFactory<T> — AOT-safe factories for arrays, lists, collections, and dictionaries without MakeGenericType().
  • TrimmableTypeMap — Central class owning the TypeMapping dictionary. Encapsulates all proxy attribute access: peer creation, invoker resolution, container factories, and native method registration bootstrap.
  • TrimmableTypeMapTypeManagerJniTypeManager subclass delegating type lookups to TrimmableTypeMap. RegisterNativeMembers throws UnreachableException (JCW static blocks handle registration).

Wiring

  • RuntimeFeature.TrimmableTypeMap feature switch with ILLink substitutions
  • JNIEnvInit (CoreCLR) and JavaInteropRuntime + JreRuntime (NativeAOT) create the new managers when the feature is on
  • JavaMarshalValueManager (renamed from ManagedValueManager) gets proxy-based peer creation in TryConstructPeer

RegisterNatives bootstrap

  • mono.android.Runtime.registerNatives(Class) Java native method added
  • TrimmableTypeMap.RegisterBootstrapNativeMethod() registers the JNI callback during init
  • When Java loads a JCW class, OnRegisterNatives resolves the proxy and calls IAndroidCallableWrapper.RegisterNatives() to bind UCO function pointers

AOT-safe JavaConvert

  • JavaConvert.GetJniHandleConverter() uses JavaPeerContainerFactory for IList<T>, ICollection<T>, IDictionary<K,V>
  • JNIEnv.ArrayCreateInstance() uses factory path

Generator update

  • Proxy types now extend JavaPeerProxy<T> (generic) — TargetType and GetContainerFactory() inherited from base
  • Generator references TrimmableTypeMap for ActivateInstance and RegisterMethod

Dependencies

Test coverage

Copy link
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 (CI pending)

Found 0 code issues. CI is still running (dotnet-android and Xamarin.Android-PR in progress). Review will need to be re-evaluated once CI completes.

Code review

  • ✅ No null-forgiving operator (!) usage
  • ✅ All new files have #nullable enable
  • ✅ Proper Mono style (tabs, space before () and [])
  • ✅ Namespace style consistent with existing files
  • ✅ Feature flag with ILLink substitutions follows established pattern
  • Interlocked.CompareExchange + Debug.Assert for single-instance safety in both TrimmableTypeMap and JavaMarshalValueManager
  • ✅ JNI callback delegate properly rooted in static field (s_onRegisterNatives)
  • OnRegisterNatives calls Environment.FailFast on error (unrecoverable state)
  • ✅ All TODO comments reference tracking issues (#10794 or java-interop #1391)
  • TypeMapException is sealed
  • TryGetType uses [NotNullWhen(true)] annotation
  • ✅ 255/255 generator tests pass
  • Mono.Android.dll builds locally

Architecture notes

  • Clean encapsulation: all proxy attribute access goes through TrimmableTypeMapJavaPeerProxy is not leaked to callers
  • JavaMarshalValueManager (renamed from ManagedValueManager) takes TrimmableTypeMap? in constructor — immutable, no settable property
  • TrimmableTypeMapTypeManager delegates all lookups to TrimmableTypeMap
  • RegisterNatives bootstrap: Java → managed callback → proxy → IAndroidCallableWrapper.RegisterNatives(JniType) → UCO function pointers
  • All behind RuntimeFeature.TrimmableTypeMap (default false) — zero impact when off

Review generated by android-reviewer from review guidelines.

Copy link
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.

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

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-runtime-pr branch from e27ad18 to d1e28a1 Compare March 19, 2026 08:10
@simonrozsival simonrozsival added the copilot `copilot-cli` or other AIs were used to author this label Mar 19, 2026
simonrozsival and others added 8 commits March 19, 2026 17:46
- JavaPeerProxy / JavaPeerProxy<T> — AOT-safe proxy attribute base
- IAndroidCallableWrapper — RegisterNatives(JniType) for ACW types
- JavaPeerContainerFactory<T> — AOT-safe array/list/collection/dict
- TypeMapException — error reporting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Proxy types now extend JavaPeerProxy<T> instead of JavaPeerProxy.
TargetType and GetContainerFactory() are inherited from the generic
base. Generator references TrimmableTypeMap for ActivateInstance and
RegisterMethod.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TrimmableTypeMap class with core typemap functionality:
TryGetType, TryCreatePeer, GetInvokerType, GetContainerFactory,
ActivateInstance.

Add TrimmableTypeMapTypeManager delegating to TrimmableTypeMap.

Rename ManagedValueManager to JavaMarshalValueManager. Add proxy-based
peer creation in TryConstructPeer via TrimmableTypeMap.TryCreatePeer.

Add RuntimeFeature.TrimmableTypeMap feature switch with ILLink
substitutions. Wire into JNIEnvInit (CoreCLR) and JavaInteropRuntime
+ JreRuntime (NativeAOT).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add registerNatives(Class) native method to mono.android.Runtime.java
so JCW static initializer blocks can trigger native method registration.

Add to TrimmableTypeMap:
- RegisterBootstrapNativeMethod() registers the JNI callback during init
- OnRegisterNatives() resolves the proxy and calls
  IAndroidCallableWrapper.RegisterNatives(JniType) to bind UCO ptrs
- RegisterMethod() helper for per-method registration (TODO: batch)

Wire RegisterBootstrapNativeMethod() call in JNIEnvInit after runtime
creation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When TrimmableTypeMap is available, use JavaPeerContainerFactory from
the proxy for IList<T>, ICollection<T>, IDictionary<K,V> marshaling
and array creation instead of MakeGenericType/Array.CreateInstance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ranks 1-3: direct new T[], T[][], T[][][] — fully AOT-safe.
Rank 4+: when dynamic code is supported (CoreCLR), falls back to
MakeArrayType + CreateInstanceFromArrayType. Throws on NativeAOT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add missing using Android.Runtime for JniHandleOwnership
- Add DynamicallyAccessedMembers annotations required by JavaList<T>,
  JavaCollection<T>, JavaDictionary<K,V> on factory type parameters
- Fix IJniNameProviderAttribute lookup (not an Attribute, use
  GetCustomAttributes instead of GetCustomAttribute<T>)
- Fix JniType constructor (takes string, not JniObjectReference)
- Restore #pragma warning disable IL3050 for Array.CreateInstance
  fallback path
- Suppress IL2073 on GetInvokerType (invoker types preserved by
  MarkJavaObjects trimmer step)

Build succeeds locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pass TrimmableTypeMap via constructor instead of settable property.
Use Interlocked.CompareExchange for single-instance safety. Keep
RegisterBootstrapNativeMethod as separate call (JNI runtime must be
initialized first).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-runtime-pr branch from d1e28a1 to b07f9b9 Compare March 19, 2026 16:46
Updated from macOS-7 CI build 13601673.

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

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

This file grew:

"lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": {
--      "Size": 633928
++      "Size": 691720
    },

It's probably OK, but I wonder what did it.

Comment on lines +1 to +14
#nullable enable

using System;

namespace Java.Interop
{
/// <summary>
/// Exception thrown when a type mapping operation fails at runtime.
/// </summary>
public sealed class TypeMapException : Exception
{
public TypeMapException (string message) : base (message) { }
public TypeMapException (string message, Exception innerException) : base (message, innerException) { }
}
Copy link
Member

Choose a reason for hiding this comment

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

If this doesn't add extra info to the exception, can we just use InvalidOperationException or similar?

And the message would talk about a typemap.

/// ACW types are .NET types that have a corresponding generated Java class
/// which calls back into .NET via JNI native methods.
/// </summary>
public interface IAndroidCallableWrapper
Copy link
Member

Choose a reason for hiding this comment

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

Can some of the new types in here be internal?

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.

[TrimmableTypeMap] Implement TypeManager and ValueManager and other Runtime code

3 participants