Skip to content

Commit c0bb477

Browse files
Fix trimmer size regression: remove TrimmableTypeMap field from ValueManager
Two changes to prevent TrimmableTypeMap types from leaking into MonoVM builds: 1. Add ILLink substitutions for IsMonoRuntime and IsCoreClrRuntime. Without these, the trimmer can't eliminate runtime-specific branches (e.g. the CoreCLR JavaMarshalValueManager path stays in MonoVM builds). 2. Remove TrimmableTypeMap? _typeMap field from JavaMarshalValueManager. Access via TrimmableTypeMap.Instance singleton instead. The field reference was rooting the entire TrimmableTypeMap type graph even when the feature switch was false. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3d4ec93 commit c0bb477

File tree

6 files changed

+34
-36
lines changed

6 files changed

+34
-36
lines changed

src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
6161
var settings = new DiagnosticSettings ();
6262
settings.AddDebugDotnetLog ();
6363

64-
var (typeManager, trimmableTypeMap) = CreateTypeManager ();
64+
var typeManager = CreateTypeManager ();
6565

6666
var options = new NativeAotRuntimeOptions {
6767
EnvironmentPointer = jnienv,
6868
ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global),
6969
TypeManager = typeManager,
70-
ValueManager = new JavaMarshalValueManager (trimmableTypeMap),
70+
ValueManager = new JavaMarshalValueManager (),
7171
UseMarshalMemberBuilder = false,
7272
JniGlobalReferenceLogWriter = settings.GrefLog,
7373
JniLocalReferenceLogWriter = settings.LrefLog,
@@ -89,13 +89,12 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
8989
transition.Dispose ();
9090
}
9191

92-
static (JniRuntime.JniTypeManager, TrimmableTypeMap?) CreateTypeManager ()
92+
static JniRuntime.JniTypeManager CreateTypeManager ()
9393
{
9494
if (RuntimeFeature.TrimmableTypeMap) {
95-
var map = new TrimmableTypeMap ();
96-
return (new TrimmableTypeMapTypeManager (map), map);
95+
return new TrimmableTypeMapTypeManager ();
9796
}
9897

99-
return (new ManagedTypeManager (), null);
98+
return new ManagedTypeManager ();
10099
}
101100
}

src/Mono.Android/Android.Runtime/JNIEnvInit.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,9 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
135135
BoundExceptionType = (BoundExceptionType)args->ioExceptionType;
136136
JniRuntime.JniTypeManager typeManager;
137137
JniRuntime.JniValueManager? valueManager = null;
138-
TrimmableTypeMap? trimmableTypeMap = null;
139138
if (RuntimeFeature.TrimmableTypeMap) {
140-
trimmableTypeMap = new TrimmableTypeMap ();
141-
typeManager = new TrimmableTypeMapTypeManager (trimmableTypeMap);
142-
valueManager = new JavaMarshalValueManager (trimmableTypeMap);
139+
typeManager = new TrimmableTypeMapTypeManager ();
140+
valueManager = new JavaMarshalValueManager ();
143141
} else if (RuntimeFeature.ManagedTypeMap) {
144142
typeManager = new ManagedTypeManager ();
145143
} else {
@@ -163,7 +161,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
163161
);
164162

165163
if (RuntimeFeature.TrimmableTypeMap) {
166-
trimmableTypeMap?.RegisterBootstrapNativeMethod ();
164+
TrimmableTypeMap.Initialize ();
167165
}
168166

169167
grefIGCUserPeer_class = args->grefIGCUserPeer;

src/Mono.Android/ILLink/ILLink.Substitutions.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="true" value="true" />
66
</type>
77
<type fullname="Microsoft.Android.Runtime.RuntimeFeature">
8+
<method signature="System.Boolean get_IsMonoRuntime()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.IsMonoRuntime" featurevalue="false" value="false" />
9+
<method signature="System.Boolean get_IsMonoRuntime()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.IsMonoRuntime" featurevalue="true" value="true" />
10+
<method signature="System.Boolean get_IsCoreClrRuntime()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.IsCoreClrRuntime" featurevalue="false" value="false" />
11+
<method signature="System.Boolean get_IsCoreClrRuntime()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.IsCoreClrRuntime" featurevalue="true" value="true" />
812
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="false" value="false" />
913
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="true" value="true" />
1014
<method signature="System.Boolean get_TrimmableTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap" featurevalue="false" value="false" />

src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,8 @@ class JavaMarshalValueManager : JniRuntime.JniValueManager
3434
public static JavaMarshalValueManager Instance =>
3535
s_instance ?? throw new InvalidOperationException ("JavaMarshalValueManager has not been initialized. Call the constructor first.");
3636

37-
readonly TrimmableTypeMap? _typeMap;
38-
39-
unsafe internal JavaMarshalValueManager (TrimmableTypeMap? typeMap = null)
37+
unsafe internal JavaMarshalValueManager ()
4038
{
41-
_typeMap = typeMap;
42-
4339
var previous = Interlocked.CompareExchange (ref s_instance, this, null);
4440
Debug.Assert (previous is null, "JavaMarshalValueManager must only be created once.");
4541

@@ -512,8 +508,9 @@ void ProcessContext (HandleContext* context)
512508
Type? targetType)
513509
{
514510
if (RuntimeFeature.TrimmableTypeMap) {
515-
if (_typeMap is not null && targetType is not null) {
516-
var proxy = _typeMap.GetProxyForManagedType (targetType);
511+
var typeMap = TrimmableTypeMap.Instance;
512+
if (typeMap is not null && targetType is not null) {
513+
var proxy = typeMap.GetProxyForManagedType (targetType);
517514
if (proxy is not null) {
518515
var peer = proxy.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer);
519516
if (peer is not null) {

src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,36 @@ class TrimmableTypeMap
2222
{
2323
static TrimmableTypeMap? s_instance;
2424

25-
internal static TrimmableTypeMap? Instance => s_instance;
25+
internal static TrimmableTypeMap Instance =>
26+
s_instance ?? throw new InvalidOperationException (
27+
"TrimmableTypeMap has not been initialized. Ensure RuntimeFeature.TrimmableTypeMap is enabled and the JNI runtime is initialized.");
2628

2729
readonly IReadOnlyDictionary<string, Type> _typeMap;
2830
readonly ConcurrentDictionary<Type, JavaPeerProxy?> _proxyCache = new ();
2931

30-
internal TrimmableTypeMap ()
32+
TrimmableTypeMap ()
3133
{
32-
// The runtime's GetOrCreateExternalTypeMapping handles assembly resolution
33-
// automatically via the System.Runtime.InteropServices.TypeMappingEntryAssembly
34-
// config property (set in Trimmable.targets). It loads the entry assembly
35-
// (_Microsoft.Android.TypeMaps), walks its TypeMapAssemblyTargetAttribute
36-
// attributes, and recursively loads each referenced assembly.
3734
_typeMap = TypeMapping.GetOrCreateExternalTypeMapping<Java.Lang.Object> ();
35+
}
3836

39-
var previous = Interlocked.CompareExchange (ref s_instance, this, null);
37+
/// <summary>
38+
/// Initializes the singleton instance and registers the bootstrap JNI native method.
39+
/// Must be called after the JNI runtime is initialized and before any JCW class is loaded.
40+
/// </summary>
41+
internal static void Initialize ()
42+
{
43+
var instance = new TrimmableTypeMap ();
44+
var previous = Interlocked.CompareExchange (ref s_instance, instance, null);
4045
Debug.Assert (previous is null, "TrimmableTypeMap must only be created once.");
46+
47+
instance.RegisterBootstrapNativeMethod ();
4148
}
4249

4350
/// <summary>
4451
/// Registers the <c>mono.android.Runtime.registerNatives</c> JNI native method.
4552
/// Must be called after the JNI runtime is initialized and before any JCW class is loaded.
4653
/// </summary>
47-
internal void RegisterBootstrapNativeMethod ()
54+
void RegisterBootstrapNativeMethod ()
4855
{
4956
using var runtimeClass = new JniType ("mono/android/Runtime");
5057
JniEnvironment.Types.RegisterNatives (

src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,13 @@ namespace Microsoft.Android.Runtime;
1515
/// </summary>
1616
class TrimmableTypeMapTypeManager : JniRuntime.JniTypeManager
1717
{
18-
readonly TrimmableTypeMap _map;
19-
20-
internal TrimmableTypeMapTypeManager (TrimmableTypeMap map)
21-
{
22-
_map = map;
23-
}
24-
2518
protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
2619
{
2720
foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) {
2821
yield return t;
2922
}
3023

31-
if (_map.TryGetType (jniSimpleReference, out var type)) {
24+
if (TrimmableTypeMap.Instance.TryGetType (jniSimpleReference, out var type)) {
3225
yield return type;
3326
}
3427
}
@@ -59,7 +52,7 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
5952
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
6053
Type type)
6154
{
62-
var invokerType = _map.GetInvokerType (type);
55+
var invokerType = TrimmableTypeMap.Instance.GetInvokerType (type);
6356
if (invokerType != null) {
6457
return invokerType;
6558
}

0 commit comments

Comments
 (0)