[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert#10967
Open
simonrozsival wants to merge 9 commits intomainfrom
Open
[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert#10967simonrozsival wants to merge 9 commits intomainfrom
simonrozsival wants to merge 9 commits intomainfrom
Conversation
simonrozsival
commented
Mar 18, 2026
Member
Author
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 AI Review Summary
Verdict:
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.Assertfor single-instance safety in bothTrimmableTypeMapandJavaMarshalValueManager - ✅ JNI callback delegate properly rooted in static field (
s_onRegisterNatives) - ✅
OnRegisterNativescallsEnvironment.FailFaston error (unrecoverable state) - ✅ All TODO comments reference tracking issues (#10794 or java-interop #1391)
- ✅
TypeMapExceptionis sealed - ✅
TryGetTypeuses[NotNullWhen(true)]annotation - ✅ 255/255 generator tests pass
- ✅
Mono.Android.dllbuilds locally
Architecture notes
- Clean encapsulation: all proxy attribute access goes through
TrimmableTypeMap—JavaPeerProxyis not leaked to callers JavaMarshalValueManager(renamed fromManagedValueManager) takesTrimmableTypeMap?in constructor — immutable, no settable propertyTrimmableTypeMapTypeManagerdelegates all lookups toTrimmableTypeMap- RegisterNatives bootstrap: Java → managed callback → proxy →
IAndroidCallableWrapper.RegisterNatives(JniType)→ UCO function pointers - All behind
RuntimeFeature.TrimmableTypeMap(defaultfalse) — zero impact when off
Review generated by android-reviewer from review guidelines.
e27ad18 to
d1e28a1
Compare
- 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>
d1e28a1 to
b07f9b9
Compare
Updated from macOS-7 CI build 13601673. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
jonathanpeppers
left a comment
There was a problem hiding this comment.
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) { } | ||
| } |
Member
There was a problem hiding this comment.
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 |
Member
There was a problem hiding this comment.
Can some of the new types in here be internal?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 tofalse).New runtime types
JavaPeerProxy/JavaPeerProxy<T>— AOT-safe proxy attribute base. Generated proxy types extend this and provideCreateInstance()for peer creation andGetContainerFactory()for collection marshaling.IAndroidCallableWrapper—RegisterNatives(JniType)interface for ACW proxy types to register JNI native methods.JavaPeerContainerFactory<T>— AOT-safe factories for arrays, lists, collections, and dictionaries withoutMakeGenericType().TrimmableTypeMap— Central class owning theTypeMappingdictionary. Encapsulates all proxy attribute access: peer creation, invoker resolution, container factories, and native method registration bootstrap.TrimmableTypeMapTypeManager—JniTypeManagersubclass delegating type lookups toTrimmableTypeMap.RegisterNativeMembersthrowsUnreachableException(JCW static blocks handle registration).Wiring
RuntimeFeature.TrimmableTypeMapfeature switch with ILLink substitutionsJNIEnvInit(CoreCLR) andJavaInteropRuntime+JreRuntime(NativeAOT) create the new managers when the feature is onJavaMarshalValueManager(renamed fromManagedValueManager) gets proxy-based peer creation inTryConstructPeerRegisterNatives bootstrap
mono.android.Runtime.registerNatives(Class)Java native method addedTrimmableTypeMap.RegisterBootstrapNativeMethod()registers the JNI callback during initOnRegisterNativesresolves the proxy and callsIAndroidCallableWrapper.RegisterNatives()to bind UCO function pointersAOT-safe JavaConvert
JavaConvert.GetJniHandleConverter()usesJavaPeerContainerFactoryforIList<T>,ICollection<T>,IDictionary<K,V>JNIEnv.ArrayCreateInstance()uses factory pathGenerator update
JavaPeerProxy<T>(generic) —TargetTypeandGetContainerFactory()inherited from baseTrimmableTypeMapforActivateInstanceandRegisterMethodDependencies
Test coverage
Mono.Android.dllbuilds locally