[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert#10967
[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert#10967simonrozsival wants to merge 19 commits intomainfrom
Conversation
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
d1e28a1 to
b07f9b9
Compare
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.
a956a46 to
a8f3e62
Compare
a8f3e62 to
d2e4bb1
Compare
|
Re: the |
- 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>
Updated from macOS-7 CI build 13601673. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ypeNameFromClass - ActivateInstance: use JNI GetObjectClass + GetJniTypeNameFromClass to find proxy type via the TypeMap dictionary instead of targetType.GetCustomAttribute (the self-application attribute is on the proxy, not the target) - Pre-load per-assembly TypeMap DLLs via TypeMapAssemblyTarget attributes before GetOrCreateExternalTypeMapping (Android assembly store needs explicit probing) - Fix GetJniTypeNameFromInstance → GetJniTypeNameFromClass in OnRegisterNatives (nativeClassHandle is a jclass, not a jobject) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- JNIEnvInit: wrap RegisterBootstrapNativeMethod in explicit 'if (RuntimeFeature.TrimmableTypeMap)' guard instead of null-conditional operator, so the trimmer can eliminate it when the feature is disabled - TryCreatePeer: look up proxy via JNI class name from the TypeMap dictionary instead of type.GetCustomAttribute (the self-application attribute is on the proxy type, not the managed target type) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both RegisterAttribute and JniTypeSignatureAttribute implement IJniNameProviderAttribute, so a single GetCustomAttributes check covers both. Made the method internal+static so TrimmableTypeMapTypeManager.GetSimpleReferences can reuse it instead of duplicating the attribute lookup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…atives - Remove TypeMapException: use InvalidOperationException instead, with 'Typemap' prefix in messages (jonathanpeppers feedback) - OnRegisterNatives: replace silent returns with Environment.FailFast() since all error paths indicate runtime or generator bugs that should abort the app Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…switch The unconditional reference to TrimmableTypeMap.Instance in JNIEnv.ArrayCreateInstance prevented the linker from trimming the entire TrimmableTypeMap dependency chain (ConcurrentDictionary, Assembly.Load, GetCustomAttributesData, Marshal.GetDelegateFor- FunctionPointer, etc.), causing ~57 KB bloat in System.Private.CoreLib even when the feature is disabled. Wrap the access with RuntimeFeature.TrimmableTypeMap to match the pattern already used in JavaConvert.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add missing RegisterBootstrapNativeMethod call in NativeAOT path - Log caught exceptions in TrimmableTypeMap assembly pre-loading - Fix Mono style: space before [] in ConstructorArguments [0] - Remove trailing whitespace in JavaMarshalValueManager.TryConstructPeer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor native→managed initialization: - Initialize sets args->registerJniNativesFn (null in trimmable path) - Eliminates create_delegate call for RegisterJniNatives - Guard jnienv_register_jni_natives call site for null - Add Host::Java_mono_android_Runtime_registerNatives no-op stub for the trimmable path (managed code handles registration) - Move jnienv_register_jni_natives_fn typedef before struct This lets the trimmer cleanly remove RegisterJniNatives when the trimmable typemap is active, while preserving it for the legacy path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The apkdesc was updated before the feature guard fix in ArrayCreateInstance, so it contained bloated sizes from the unguarded TrimmableTypeMap reference. Reverting to main's baseline since the feature switch ensures all new code is trimmed when disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
76a490e to
aad2ddb
Compare
Follow-up PRs will handle the ILLink/ILC setup to ensure typemap assemblies are available without manual Assembly.Load() calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
450911d to
6f0a24f
Compare
The rebase conflict resolution incorrectly kept the PR's old submodule ref (fb0952fad) instead of main's (c14ba04). This PR should not modify the submodule — main already has the correct version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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— 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).Type resolution
GetProxyForManagedType(Type)resolves managed type → JNI name viaIJniNameProviderAttribute(shared interface for[Register]and[JniTypeSignature]) → TypeMap dictionary → proxy. Results cached inConcurrentDictionary.TryGetJniNameForTypeshared betweenTrimmableTypeMapandTrimmableTypeMapTypeManager.ActivateInstanceuses JNIGetObjectClass→GetJniTypeNameFromClass→ TypeMap lookup for constructor activation (proxy types have self-applied attribute, not the target type).Assembly pre-loading
TrimmableTypeMapconstructor scansTypeMapAssemblyTargetattributes from the entry assembly and callsAssembly.Loadfor each referenced assembly beforeGetOrCreateExternalTypeMapping.Native interop refactoring
RegisterJniNativespassed via init args —Initializesetsargs->registerJniNativesFn(null whenTrimmableTypeMap=true). Eliminates thecreate_delegatecall forRegisterJniNatives, letting the trimmer remove it cleanly in the trimmable path.registerNativesstub —Host::Java_mono_android_Runtime_registerNativesno-op for the trimmable path (managed code handles registration viaRegisterBootstrapNativeMethod).jnienv_register_jni_nativescall site.Initializeis now the singlecreate_delegateentry point from native code.RegisterNatives bootstrap
mono.android.Runtime.registerNatives(Class)Java native method addedTrimmableTypeMap.RegisterBootstrapNativeMethod()registers the JNI callback during init (behind explicitif (RuntimeFeature.TrimmableTypeMap)guard for trimmer compatibility)OnRegisterNativesresolves the proxy and callsIAndroidCallableWrapper.RegisterNatives()to bind UCO function pointersAOT-safe JavaConvert
JavaConvert.GetJniHandleConverter()usesJavaPeerContainerFactoryforIList<T>,ICollection<T>,IDictionary<TKey,TValue>JNIEnv.ArrayCreateInstance()uses factory pathWiring
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 inTryConstructPeerDependencies
Test coverage