Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4ead1b1
[TrimmableTypeMap] Add runtime base types for generated proxies
simonrozsival Mar 18, 2026
70c7d67
[TrimmableTypeMap] Emit JavaPeerProxy<T> as generic base class
simonrozsival Mar 18, 2026
1644e27
[TrimmableTypeMap] Add TrimmableTypeMap, TypeManager, and wire managers
simonrozsival Mar 18, 2026
cf8b6f9
[TrimmableTypeMap] Add RegisterNatives bootstrap
simonrozsival Mar 18, 2026
404c3fd
[TrimmableTypeMap] AOT-safe JavaConvert and ArrayCreateInstance
simonrozsival Mar 18, 2026
53e3154
[TrimmableTypeMap] AOT-safe CreateArray with dynamic code fallback
simonrozsival Mar 18, 2026
69d9338
Fix build errors and trimming annotations
simonrozsival Mar 18, 2026
a970337
Refactor JavaMarshalValueManager to take TrimmableTypeMap in constructor
simonrozsival Mar 18, 2026
0035032
[tests] Update BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc
jonathanpeppers Mar 19, 2026
b225af8
Fix TrimmableTypeMap: ActivateInstance, assembly pre-loading, GetJniT…
simonrozsival Mar 20, 2026
dc338a2
Fix trimmer compatibility and TryCreatePeer proxy lookup
simonrozsival Mar 20, 2026
3d56f5f
Simplify TryGetJniNameForType to use IJniNameProviderAttribute
simonrozsival Mar 20, 2026
ce09b7f
Address PR feedback: remove TypeMapException, FailFast in OnRegisterN…
simonrozsival Mar 20, 2026
424df43
Guard TrimmableTypeMap reference in ArrayCreateInstance with feature …
simonrozsival Mar 20, 2026
e3f576f
Address code review findings
simonrozsival Mar 20, 2026
945b837
Pass RegisterJniNatives via init args, add registerNatives C++ stub
simonrozsival Mar 20, 2026
aad2ddb
Revert apkdesc to main baseline
simonrozsival Mar 21, 2026
6f0a24f
Remove assembly pre-loading from TrimmableTypeMap
simonrozsival Mar 21, 2026
57a747f
Fix external/Java.Interop submodule ref to match main
simonrozsival Mar 21, 2026
e598f28
Add missing registerNatives declaration to host headers
simonrozsival Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
var settings = new DiagnosticSettings ();
settings.AddDebugDotnetLog ();

var (typeManager, trimmableTypeMap) = CreateTypeManager ();

var options = new NativeAotRuntimeOptions {
EnvironmentPointer = jnienv,
ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global),
TypeManager = new ManagedTypeManager (),
ValueManager = ManagedValueManager.GetOrCreateInstance (),
TypeManager = typeManager,
ValueManager = new JavaMarshalValueManager (trimmableTypeMap),
UseMarshalMemberBuilder = false,
JniGlobalReferenceLogWriter = settings.GrefLog,
JniLocalReferenceLogWriter = settings.LrefLog,
Expand All @@ -75,6 +77,8 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
// Entry point into Mono.Android.dll. Log categories are initialized in JNI_OnLoad.
JNIEnvInit.InitializeJniRuntime (runtime, initArgs);

trimmableTypeMap?.RegisterBootstrapNativeMethod ();

transition = new JniTransition (jnienv);

var handler = Java.Lang.Thread.DefaultUncaughtExceptionHandler;
Expand All @@ -86,4 +90,14 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
}
transition.Dispose ();
}

static (JniRuntime.JniTypeManager, TrimmableTypeMap?) CreateTypeManager ()
{
if (RuntimeFeature.TrimmableTypeMap) {
var map = new TrimmableTypeMap ();
return (new TrimmableTypeMapTypeManager (map), map);
}

return (new ManagedTypeManager (), null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder)
throw new InvalidOperationException ($"Member `{nameof (NativeAotRuntimeOptions)}.{nameof (NativeAotRuntimeOptions.JvmLibraryPath)}` must be set.");

#if NET
builder.TypeManager ??= new ManagedTypeManager ();
builder.TypeManager ??= CreateDefaultTypeManager ();
#endif // NET

builder.ValueManager ??= ManagedValueManager.GetOrCreateInstance();
builder.ValueManager ??= JavaMarshalValueManager.Instance;
builder.ObjectReferenceManager ??= new Android.Runtime.AndroidObjectReferenceManager ();

if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)
Expand All @@ -75,6 +75,15 @@ internal protected JreRuntime (NativeAotRuntimeOptions builder)
{
}

static JniRuntime.JniTypeManager CreateDefaultTypeManager ()
{
if (RuntimeFeature.TrimmableTypeMap) {
return new TrimmableTypeMapTypeManager (new TrimmableTypeMap ());
}

return new ManagedTypeManager ();
}

public override string? GetCurrentManagedThreadName ()
{
return Thread.CurrentThread.Name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// [assembly: TypeMapAssociation(typeof(MyTextView), typeof(Android_Widget_TextView_Proxy))] // alias
///
/// // One proxy type per Java peer that needs activation or UCO wrappers:
/// public sealed class Activity_Proxy : JavaPeerProxy, IAndroidCallableWrapper // IAndroidCallableWrapper for ACWs only
/// public sealed class Activity_Proxy : JavaPeerProxy<Activity>, IAndroidCallableWrapper // IAndroidCallableWrapper for ACWs only
/// {
/// public Activity_Proxy() : base() { }
///
Expand All @@ -33,7 +33,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// // or: null; // no activation
/// // or: throw new NotSupportedException(...); // open generic
///
/// public override Type TargetType =&gt; typeof(Activity);
/// // TargetType and GetContainerFactory() are inherited from JavaPeerProxy<Activity>
/// public Type InvokerType =&gt; typeof(IOnClickListenerInvoker); // interfaces only
///
/// // UCO wrappers — [UnmanagedCallersOnly] entry points for JNI native methods (ACWs only):
Expand All @@ -43,13 +43,13 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
/// [UnmanagedCallersOnly]
/// public static void nctor_0_uco(IntPtr jnienv, IntPtr self)
/// =&gt; TrimmableNativeRegistration.ActivateInstance(self, typeof(Activity));
/// =&gt; TrimmableTypeMap.ActivateInstance(self, typeof(Activity));
///
/// // Registers JNI native methods (ACWs only):
/// public void RegisterNatives(JniType jniType)
/// {
/// TrimmableNativeRegistration.RegisterMethod(jniType, "n_OnCreate", "(Landroid/os/Bundle;)V", &amp;n_OnCreate_uco_0);
/// TrimmableNativeRegistration.RegisterMethod(jniType, "nctor_0", "()V", &amp;nctor_0_uco);
/// TrimmableTypeMap.RegisterMethod(jniType, "n_OnCreate", "(Landroid/os/Bundle;)V", &amp;n_OnCreate_uco_0);
/// TrimmableTypeMap.RegisterMethod(jniType, "nctor_0", "()V", &amp;nctor_0_uco);
/// }
/// }
///
Expand All @@ -75,11 +75,10 @@ sealed class TypeMapAssemblyEmitter
TypeReferenceHandle _systemTypeRef;
TypeReferenceHandle _runtimeTypeHandleRef;
TypeReferenceHandle _jniTypeRef;
TypeReferenceHandle _trimmableNativeRegistrationRef;
TypeReferenceHandle _trimmableTypeMapRef;
TypeReferenceHandle _notSupportedExceptionRef;
TypeReferenceHandle _runtimeHelpersRef;

MemberReferenceHandle _baseCtorRef;
MemberReferenceHandle _getTypeFromHandleRef;
MemberReferenceHandle _getUninitializedObjectRef;
MemberReferenceHandle _notSupportedExceptionCtorRef;
Expand Down Expand Up @@ -188,8 +187,8 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle"));
_jniTypeRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniType"));
_trimmableNativeRegistrationRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("TrimmableNativeRegistration"));
_trimmableTypeMapRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
metadata.GetOrAddString ("Microsoft.Android.Runtime"), metadata.GetOrAddString ("TrimmableTypeMap"));
_notSupportedExceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException"));
_runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
Expand All @@ -198,9 +197,6 @@ void EmitTypeReferences ()

void EmitMemberReferences ()
{
_baseCtorRef = _pe.AddMemberRef (_javaPeerProxyRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }));

_getTypeFromHandleRef = _pe.AddMemberRef (_systemTypeRef, "GetTypeFromHandle",
sig => sig.MethodSignature ().Parameters (1,
rt => rt.Type ().Type (_systemTypeRef, false),
Expand Down Expand Up @@ -232,15 +228,15 @@ void EmitMemberReferences ()
p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
}));

_activateInstanceRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "ActivateInstance",
_activateInstanceRef = _pe.AddMemberRef (_trimmableTypeMapRef, "ActivateInstance",
sig => sig.MethodSignature ().Parameters (2,
rt => rt.Void (),
p => {
p.AddParameter ().Type ().IntPtr ();
p.AddParameter ().Type ().Type (_systemTypeRef, false);
}));

_registerMethodRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "RegisterMethod",
_registerMethodRef = _pe.AddMemberRef (_trimmableTypeMapRef, "RegisterMethod",
sig => sig.MethodSignature ().Parameters (4,
rt => rt.Void (),
p => {
Expand Down Expand Up @@ -315,36 +311,41 @@ void EmitTypeMapAssociationAttributeCtorRef ()
void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinitionHandle> wrapperHandles)
{
var metadata = _pe.Metadata;

// Create JavaPeerProxy<TargetType> as the base class
var targetTypeRef = _pe.ResolveTypeRef (proxy.TargetType);
var genericBaseSpec = _pe.MakeGenericTypeSpec (_javaPeerProxyRef, targetTypeRef);

var typeDefHandle = metadata.AddTypeDefinition (
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
metadata.GetOrAddString (proxy.Namespace),
metadata.GetOrAddString (proxy.TypeName),
_javaPeerProxyRef,
genericBaseSpec,
MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1),
MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1));

if (proxy.IsAcw) {
metadata.AddInterfaceImplementation (typeDefHandle, _iAndroidCallableWrapperRef);
}

// .ctor
// .ctor — call JavaPeerProxy<T>..ctor()
var genericBaseCtorRef = _pe.AddMemberRef (genericBaseSpec, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }));
_pe.EmitBody (".ctor",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }),
encoder => {
encoder.OpCode (ILOpCode.Ldarg_0);
encoder.Call (_baseCtorRef);
encoder.Call (genericBaseCtorRef);
encoder.OpCode (ILOpCode.Ret);
});

// CreateInstance
EmitCreateInstance (proxy);

// get_TargetType
EmitTypeGetter ("get_TargetType", proxy.TargetType,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig);
// get_TargetType and GetContainerFactory() are inherited from JavaPeerProxy<T>

// get_InvokerType
// get_InvokerType — only for interfaces/abstract types
if (proxy.InvokerType != null) {
EmitTypeGetter ("get_InvokerType", proxy.InvokerType,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig);
Expand Down
16 changes: 11 additions & 5 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ public static partial class JNIEnv {

public static IntPtr Handle => JniEnvironment.EnvironmentPointer;

static Array ArrayCreateInstance (Type elementType, int length) =>
// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
// IL3050 disabled in source: if someone uses NativeAOT, they will get the warning.
#pragma warning disable IL3050
Array.CreateInstance (elementType, length);
static Array ArrayCreateInstance (Type elementType, int length)
{
if (RuntimeFeature.TrimmableTypeMap) {
var factory = TrimmableTypeMap.Instance?.GetContainerFactory (elementType);
if (factory != null)
return factory.CreateArray (length, 1);
}

#pragma warning disable IL3050 // Array.CreateInstance is not AOT-safe, but this is the legacy fallback path
return Array.CreateInstance (elementType, length);
#pragma warning restore IL3050
}

static Type MakeArrayType (Type type) =>
// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
Expand Down
21 changes: 18 additions & 3 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal struct JnienvInitializeArgs {
public IntPtr grefGCUserPeerable;
public bool managedMarshalMethodsLookupEnabled;
public IntPtr propagateUncaughtExceptionFn;
public IntPtr registerJniNativesFn;
}
#pragma warning restore 0649

Expand Down Expand Up @@ -130,16 +131,22 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)

BoundExceptionType = (BoundExceptionType)args->ioExceptionType;
JniRuntime.JniTypeManager typeManager;
JniRuntime.JniValueManager valueManager;
if (RuntimeFeature.ManagedTypeMap) {
JniRuntime.JniValueManager? valueManager = null;
TrimmableTypeMap? trimmableTypeMap = null;
if (RuntimeFeature.TrimmableTypeMap) {
trimmableTypeMap = new TrimmableTypeMap ();
typeManager = new TrimmableTypeMapTypeManager (trimmableTypeMap);
valueManager = new JavaMarshalValueManager (trimmableTypeMap);
} else if (RuntimeFeature.ManagedTypeMap) {
typeManager = new ManagedTypeManager ();
} else {
typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0);
}
if (RuntimeFeature.IsMonoRuntime) {
valueManager = new AndroidValueManager ();
} else if (RuntimeFeature.IsCoreClrRuntime) {
valueManager = ManagedValueManager.GetOrCreateInstance ();
// Note: this will be removed once trimmable typemap is the only supported option for CoreCLR runtime
valueManager ??= new JavaMarshalValueManager ();
} else {
throw new NotSupportedException ("Internal error: unknown runtime not supported");
}
Expand All @@ -152,6 +159,10 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
args->jniAddNativeMethodRegistrationAttributePresent != 0
);

if (RuntimeFeature.TrimmableTypeMap) {
trimmableTypeMap!.RegisterBootstrapNativeMethod ();
}

grefIGCUserPeer_class = args->grefIGCUserPeer;
grefGCUserPeerable_class = args->grefGCUserPeerable;

Expand All @@ -165,6 +176,10 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
}

args->propagateUncaughtExceptionFn = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, IntPtr, void>)&PropagateUncaughtException;

if (!RuntimeFeature.TrimmableTypeMap) {
args->registerJniNativesFn = (IntPtr)(delegate* unmanaged<IntPtr, int, IntPtr, IntPtr, int, void>)&RegisterJniNatives;
}
RunStartupHooksIfNeeded ();
SetSynchronizationContext ();
}
Expand Down
2 changes: 2 additions & 0 deletions src/Mono.Android/ILLink/ILLink.Substitutions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<type fullname="Microsoft.Android.Runtime.RuntimeFeature">
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="false" value="false" />
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="true" value="true" />
<method signature="System.Boolean get_TrimmableTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap" featurevalue="false" value="false" />
<method signature="System.Boolean get_TrimmableTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap" featurevalue="true" value="true" />
</type>
</assembly>
</linker>
21 changes: 21 additions & 0 deletions src/Mono.Android/Java.Interop/IAndroidCallableWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#nullable enable

using System;

namespace Java.Interop
{
/// <summary>
/// Interface for proxy types that represent Android Callable Wrappers (ACW).
/// 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
{
/// <summary>
/// Registers JNI native methods for this ACW type.
/// Called when the Java class is first loaded and needs its native methods bound.
/// </summary>
/// <param name="nativeClass">The JNI type for the Java class.</param>
void RegisterNatives (JniType nativeClass);
}
}
50 changes: 50 additions & 0 deletions src/Mono.Android/Java.Interop/JavaConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;

using Android.Runtime;
using Microsoft.Android.Runtime;

namespace Java.Interop {

Expand Down Expand Up @@ -79,6 +80,13 @@ params Type [] typeArguments
return converter;
if (target.IsArray)
return (h, t) => JNIEnv.GetArray (h, t, target.GetElementType ());

if (RuntimeFeature.TrimmableTypeMap) {
var factoryConverter = TryGetFactoryBasedConverter (target);
if (factoryConverter != null)
return factoryConverter;
}

if (target.IsGenericType && target.GetGenericTypeDefinition() == typeof (IDictionary<,>)) {
Type t = MakeGenericType (typeof (JavaDictionary<,>), target.GetGenericArguments ());
return GetJniHandleConverterForType (t);
Expand All @@ -101,6 +109,48 @@ params Type [] typeArguments
return null;
}

/// <summary>
/// AOT-safe converter using <see cref="JavaPeerContainerFactory"/> from the generated proxy.
/// Avoids <c>MakeGenericType()</c> by using the pre-typed factory from the proxy attribute.
/// </summary>
static Func<IntPtr, JniHandleOwnership, object?>? TryGetFactoryBasedConverter (Type target)
{
if (!target.IsGenericType)
return null;

var genericDef = target.GetGenericTypeDefinition ();
var typeArgs = target.GetGenericArguments ();

if (genericDef == typeof (IList<>) && typeArgs.Length == 1) {
var factory = TryGetContainerFactory (typeArgs [0]);
if (factory != null)
return (h, t) => factory.CreateList (h, t);
}

if (genericDef == typeof (ICollection<>) && typeArgs.Length == 1) {
var factory = TryGetContainerFactory (typeArgs [0]);
if (factory != null)
return (h, t) => factory.CreateCollection (h, t);
}

if (genericDef == typeof (IDictionary<,>) && typeArgs.Length == 2) {
var keyFactory = TryGetContainerFactory (typeArgs [0]);
var valueFactory = TryGetContainerFactory (typeArgs [1]);
if (keyFactory != null && valueFactory != null)
return (h, t) => valueFactory.CreateDictionary (keyFactory, h, t);
}

return null;
}

static JavaPeerContainerFactory? TryGetContainerFactory (Type elementType)
{
if (!typeof (IJavaPeerable).IsAssignableFrom (elementType))
return null;

return TrimmableTypeMap.Instance?.GetContainerFactory (elementType);
}

static Func<IntPtr, JniHandleOwnership, object> GetJniHandleConverterForType ([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t)
{
MethodInfo m = t.GetMethod ("FromJniHandle", BindingFlags.Static | BindingFlags.Public)!;
Expand Down
Loading