Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8e83f3f
Fix trimmable typemap startup ordering
simonrozsival Apr 30, 2026
1f7fd6f
Fix root typemap target anchors
simonrozsival Apr 30, 2026
e62ae8b
Handle non-generated JNI peers without proxies
simonrozsival Apr 30, 2026
3ee7d95
Split typemap target attribute emitters
simonrozsival Apr 30, 2026
555de15
Ignore JniTypeSignature in trimmable typemaps
simonrozsival Apr 30, 2026
99a15ac
Address trimmable typemap review feedback
simonrozsival Apr 30, 2026
2a44d9c
Add registered peer trimmable typemap coverage
simonrozsival Apr 30, 2026
c0c1a01
Fix CoreCLR trimmable linker root
simonrozsival Apr 30, 2026
4be3906
Apply CoreCLR exclusions to trimmable typemap tests
simonrozsival Apr 30, 2026
92675c8
Prepare trimmable typemap assemblies for packaging
simonrozsival May 1, 2026
d787990
Preserve trimmable component metadata
simonrozsival May 1, 2026
2321cbd
Associate invoker types with typemap proxies
simonrozsival May 1, 2026
92d7f13
Resolve JNI names from trimmable typemaps
simonrozsival May 1, 2026
55ad557
Emit raw trimmable UCO registrations
simonrozsival May 1, 2026
cbe9d93
Merge origin/main into trimmable typemap startup fixes
simonrozsival May 1, 2026
4356d3e
Complete trimmable typemap validation fixes
simonrozsival May 1, 2026
f66dbb6
Use utf-8 overload of JniType ctor
simonrozsival May 1, 2026
2bf7294
Compute typemap IL maxstack
simonrozsival May 1, 2026
5167e24
Move maxstack work to follow-up
simonrozsival May 1, 2026
bec48b8
Simplify trimmable typemap test code
simonrozsival May 1, 2026
d108b9c
Remove brittle typemap IL token assertions
simonrozsival May 1, 2026
7a7618f
Preserve startup hook in runtime tests
simonrozsival May 1, 2026
0bf8b07
Fix CoreCLR debug typemap duplicates
simonrozsival May 1, 2026
baf509c
Remove broad trimmable test roots
simonrozsival May 1, 2026
1295da7
Remove transitive reference suppression
simonrozsival May 1, 2026
5598f96
Remove trimmable test root validation target
simonrozsival May 2, 2026
6327757
Avoid standalone Java.Interop in runtime tests
simonrozsival May 2, 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 @@ -57,8 +57,9 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
ModuleName = moduleName,
};

// Invoker types are NOT emitted as separate proxies or TypeMap entries —
// they only appear as a TypeRef in the interface proxy's get_InvokerType property.
// Invoker types are NOT emitted as separate proxies or TypeMap entries.
// They are associated with their interface/abstract proxy so JniPeerMembers
// can resolve the invoker type's registered JNI name.
var invokerTypeNames = new HashSet<string> (
peers.Select (p => p.InvokerTypeName).OfType<string> (),
StringComparer.Ordinal);
Expand Down Expand Up @@ -138,10 +139,7 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
// Without this, the proxy type map is empty and CreatePeer fails for
// interface types like IIterator where targetType-based lookup is needed.
if (proxy != null) {
model.Associations.Add (new TypeMapAssociationData {
SourceTypeReference = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName),
AliasProxyTypeReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", assemblyName),
});
AddProxyAssociation (model, peer, proxy, assemblyName);
}
return;
}
Expand Down Expand Up @@ -174,6 +172,9 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
SourceTypeReference = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName),
AliasProxyTypeReference = holderRef,
});
if (proxy != null && peer.InvokerTypeName != null) {
AddProxyAssociation (model, peer.InvokerTypeName, peer.AssemblyName, proxy, assemblyName);
}
}

// Base JNI name entry → alias holder (self-referencing trim target, kept alive by associations)
Expand All @@ -198,6 +199,22 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
});
}

static void AddProxyAssociation (TypeMapAssemblyData model, JavaPeerInfo peer, JavaPeerProxyData proxy, string assemblyName)
{
AddProxyAssociation (model, peer.ManagedTypeName, peer.AssemblyName, proxy, assemblyName);
if (peer.InvokerTypeName != null) {
AddProxyAssociation (model, peer.InvokerTypeName, peer.AssemblyName, proxy, assemblyName);
}
}

static void AddProxyAssociation (TypeMapAssemblyData model, string managedTypeName, string sourceAssemblyName, JavaPeerProxyData proxy, string outputAssemblyName)
{
model.Associations.Add (new TypeMapAssociationData {
SourceTypeReference = AssemblyQualify (managedTypeName, sourceAssemblyName),
AliasProxyTypeReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", outputAssemblyName),
});
}

/// <summary>
/// Determines whether a type should use the unconditional (2-arg) TypeMap attribute.
/// Unconditional types are always preserved by the trimmer.
Expand Down Expand Up @@ -314,8 +331,8 @@ static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy)
WrapperName = $"n_{mm.JniName}_uco_{ucoIndex}",
CallbackMethodName = mm.NativeCallbackName,
CallbackType = new TypeRefData {
ManagedTypeName = !string.IsNullOrEmpty (mm.DeclaringTypeName) ? mm.DeclaringTypeName : peer.ManagedTypeName,
AssemblyName = !string.IsNullOrEmpty (mm.DeclaringAssemblyName) ? mm.DeclaringAssemblyName : peer.AssemblyName,
ManagedTypeName = !mm.DeclaringTypeName.IsNullOrEmpty () ? mm.DeclaringTypeName : peer.ManagedTypeName,
AssemblyName = !mm.DeclaringAssemblyName.IsNullOrEmpty () ? mm.DeclaringAssemblyName : peer.AssemblyName,
},
JniSignature = mm.JniSignature,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// </summary>
sealed class PEAssemblyBuilder
{
const int DefaultMaxStack = 32;

// Mono.Android strong name public key token (84e04ff9cfb79065)
static readonly byte [] MonoAndroidPublicKeyToken = { 0x84, 0xe0, 0x4f, 0xf9, 0xcf, 0xb7, 0x90, 0x65 };

Expand Down Expand Up @@ -307,7 +309,7 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
var bodyEncoder = new MethodBodyStreamEncoder (ILBuilder);
int bodyOffset = localSigHandle.IsNil
? bodyEncoder.AddMethodBody (encoder)
: bodyEncoder.AddMethodBody (encoder, maxStack: 8, localSigHandle, MethodBodyAttributes.InitLocals);
: bodyEncoder.AddMethodBody (encoder, maxStack: DefaultMaxStack, localSigHandle, MethodBodyAttributes.InitLocals);

return Metadata.AddMethodDefinition (
attrs, MethodImplAttributes.IL,
Expand Down Expand Up @@ -352,7 +354,7 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
var bodyEncoder = new MethodBodyStreamEncoder (ILBuilder);
int bodyOffset = localSigHandle.IsNil
? bodyEncoder.AddMethodBody (encoder)
: bodyEncoder.AddMethodBody (encoder, maxStack: 8, localSigHandle, MethodBodyAttributes.InitLocals);
: bodyEncoder.AddMethodBody (encoder, maxStack: DefaultMaxStack, localSigHandle, MethodBodyAttributes.InitLocals);

return Metadata.AddMethodDefinition (
attrs, MethodImplAttributes.IL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,13 @@ public void Generate (IReadOnlyList<string> perAssemblyTypeMapNames, bool useSha
MetadataTokens.MethodDefinitionHandle (pe.Metadata.GetRowCount (TableIndex.MethodDef) + 1));
}

// Emit [assembly: TypeMapAssemblyTargetAttribute<__TypeMapAnchor>("name")] for each per-assembly typemap
EmitAssemblyTargetAttributes (pe, anchorTypeHandle, perAssemblyTypeMapNames);
// Emit [assembly: TypeMapAssemblyTargetAttribute<T>("name")] for each per-assembly typemap.
// T must match the group type later passed to TypeMapping.GetOrCreate*TypeMapping<T>().
if (useSharedTypemapUniverse) {
EmitSharedUniverseAssemblyTargetAttributes (pe, anchorTypeHandle, perAssemblyTypeMapNames);
} else {
EmitPerAssemblyUniverseAssemblyTargetAttributes (pe, perAssemblyTypeMapNames);
}

// Emit [assembly: IgnoresAccessChecksTo("...")] so TypeMapLoader.Initialize() can access
// internal types (SingleUniverseTypeMap, AggregateTypeMap in Mono.Android,
Expand All @@ -126,23 +131,47 @@ public void Generate (IReadOnlyList<string> perAssemblyTypeMapNames, bool useSha
pe.WritePE (stream);
}

static void EmitAssemblyTargetAttributes (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList<string> perAssemblyTypeMapNames)
static void EmitSharedUniverseAssemblyTargetAttributes (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList<string> perAssemblyTypeMapNames)
{
var openAttrRef = pe.Metadata.AddTypeReference (pe.SystemRuntimeInteropServicesRef,
var openAttrRef = GetTypeMapAssemblyTargetAttributeRef (pe);
var ctorRef = GetTypeMapAssemblyTargetAttributeCtorRef (pe, openAttrRef, anchorTypeHandle);
foreach (var name in perAssemblyTypeMapNames) {
EmitAssemblyTargetAttribute (pe, ctorRef, name);
}
}

static void EmitPerAssemblyUniverseAssemblyTargetAttributes (PEAssemblyBuilder pe, IReadOnlyList<string> perAssemblyTypeMapNames)
{
var openAttrRef = GetTypeMapAssemblyTargetAttributeRef (pe);
foreach (var name in perAssemblyTypeMapNames) {
var asmRef = pe.FindOrAddAssemblyRef (name);
var perAssemblyAnchorRef = pe.Metadata.AddTypeReference (asmRef,
default, pe.Metadata.GetOrAddString ("__TypeMapAnchor"));
var ctorRef = GetTypeMapAssemblyTargetAttributeCtorRef (pe, openAttrRef, perAssemblyAnchorRef);
EmitAssemblyTargetAttribute (pe, ctorRef, name);
}
}

static TypeReferenceHandle GetTypeMapAssemblyTargetAttributeRef (PEAssemblyBuilder pe)
{
return pe.Metadata.AddTypeReference (pe.SystemRuntimeInteropServicesRef,
pe.Metadata.GetOrAddString ("System.Runtime.InteropServices"),
pe.Metadata.GetOrAddString ("TypeMapAssemblyTargetAttribute`1"));
}

static MemberReferenceHandle GetTypeMapAssemblyTargetAttributeCtorRef (PEAssemblyBuilder pe, EntityHandle openAttrRef, EntityHandle anchorTypeHandle)
{
var closedAttrTypeSpec = pe.MakeGenericTypeSpec (openAttrRef, anchorTypeHandle);

var ctorRef = pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
return pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1,
rt => rt.Void (),
p => p.AddParameter ().Type ().String ()));
}

foreach (var name in perAssemblyTypeMapNames) {
var blobHandle = pe.BuildAttributeBlob (blob => blob.WriteSerializedString (name));
pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorRef, blobHandle);
}
static void EmitAssemblyTargetAttribute (PEAssemblyBuilder pe, MemberReferenceHandle ctorRef, string name)
{
var blobHandle = pe.BuildAttributeBlob (blob => blob.WriteSerializedString (name));
pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorRef, blobHandle);
}

static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList<string> perAssemblyTypeMapNames, bool useSharedTypemapUniverse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// // JniName / TargetType / InvokerType are supplied by the base JavaPeerProxy constructor.
///
/// // UCO wrappers — [UnmanagedCallersOnly] entry points for JNI native methods (ACWs only):
/// [UnmanagedCallersOnly]
/// public static void n_OnCreate_uco_0(IntPtr jnienv, IntPtr self, IntPtr p0)
/// =&gt; Activity.n_OnCreate(jnienv, self, p0);
/// {
/// AndroidRuntimeInternal.WaitForBridgeProcessing();
/// try {
/// Activity.n_OnCreate(jnienv, self, p0);
/// } catch (Exception e) {
/// AndroidEnvironmentInternal.UnhandledException(e);
/// }
/// }
///
/// [UnmanagedCallersOnly]
/// public static void nctor_0_uco(IntPtr jnienv, IntPtr self)
Expand Down Expand Up @@ -93,6 +99,8 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _jniObjectReferenceCtorRef;
MemberReferenceHandle _jniEnvDeleteRefRef;
MemberReferenceHandle _shouldSkipActivationRef;
MemberReferenceHandle _waitForBridgeProcessingRef;
MemberReferenceHandle _androidEnvironmentUnhandledExceptionRef;
MemberReferenceHandle _ucoAttrCtorRef;
BlobHandle _ucoAttrBlobHandle;
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
Expand All @@ -106,6 +114,8 @@ sealed class TypeMapAssemblyEmitter
TypeReferenceHandle _jniTransitionRef;
TypeReferenceHandle _jniRuntimeRef;
TypeReferenceHandle _exceptionRef;
TypeReferenceHandle _androidRuntimeInternalRef;
TypeReferenceHandle _androidEnvironmentInternalRef;

MemberReferenceHandle _beginMarshalMethodRef;
MemberReferenceHandle _endMarshalMethodRef;
Expand Down Expand Up @@ -238,6 +248,11 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniRuntime"));
_exceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Exception"));
var monoAndroidRuntimeRef = _pe.AddAssemblyRef ("Mono.Android.Runtime", new Version (0, 0, 0, 0));
_androidRuntimeInternalRef = metadata.AddTypeReference (monoAndroidRuntimeRef,
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("AndroidRuntimeInternal"));
_androidEnvironmentInternalRef = metadata.AddTypeReference (monoAndroidRuntimeRef,
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("AndroidEnvironmentInternal"));

// ReadOnlySpan<JniNativeMethod> — TypeSpec for generic instantiation
_readOnlySpanOpenRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
Expand Down Expand Up @@ -310,6 +325,14 @@ void EmitMemberReferences ()
rt => rt.Type ().Boolean (),
p => { p.AddParameter ().Type ().IntPtr (); }));

_waitForBridgeProcessingRef = _pe.AddMemberRef (_androidRuntimeInternalRef, "WaitForBridgeProcessing",
sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }));

_androidEnvironmentUnhandledExceptionRef = _pe.AddMemberRef (_androidEnvironmentInternalRef, "UnhandledException",
sig => sig.MethodSignature ().Parameters (1,
rt => rt.Void (),
p => p.AddParameter ().Type ().Type (_exceptionRef, false)));

// JniNativeMethod..ctor(byte*, byte*, IntPtr)
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
Expand Down Expand Up @@ -351,7 +374,7 @@ void EmitMemberReferences ()
_ucoAttrCtorRef = _pe.AddMemberRef (ucoAttrTypeRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }));

// Pre-compute the UCO attribute blob — it's always the same 4 bytes (prolog + no named args)
// Legacy marshal-method UCO wrappers use the default unmanaged calling convention.
_ucoAttrBlobHandle = _pe.BuildAttributeBlob (b => { });

// JniEnvironment.BeginMarshalMethod(nint jnienv, out JniTransition, out JniRuntime?) -> bool
Expand Down Expand Up @@ -524,7 +547,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition

// UCO wrappers
foreach (var uco in proxy.UcoMethods) {
var handle = EmitUcoMethod (uco);
var handle = EmitUcoMethod (uco, proxy);
wrapperHandles [uco.WrapperName] = handle;
}

Expand All @@ -535,7 +558,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition

// RegisterNatives
if (proxy.IsAcw) {
EmitRegisterNatives (proxy.NativeRegistrations, wrapperHandles);
EmitRegisterNatives (proxy, wrapperHandles);
}
}

Expand Down Expand Up @@ -845,7 +868,7 @@ MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef)
}));
}

MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco)
MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy)
{
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
var returnKind = JniSignatureHelper.ParseReturnType (uco.JniSignature);
Expand Down Expand Up @@ -878,17 +901,59 @@ MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco)
var handle = _pe.EmitBody (uco.WrapperName,
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
encodeSig,
encoder => {
(encoder, cfb) => EmitUcoForwarderBody (encoder, cfb, returnKind, enc => {
for (int p = 0; p < paramCount; p++)
encoder.LoadArgument (p);
encoder.Call (callbackRef);
encoder.OpCode (ILOpCode.Ret);
});
enc.LoadArgument (p);
enc.Call (callbackRef);
}),
blob => EncodeUcoForwarderLegacyLocals (blob, returnKind));

AddUnmanagedCallersOnlyAttribute (handle);
return handle;
}

void EmitUcoForwarderBody (InstructionEncoder encoder, ControlFlowBuilder cfb, JniParamKind returnKind, Action<InstructionEncoder> emitCallback)
{
bool isVoid = returnKind == JniParamKind.Void;
var tryStart = encoder.DefineLabel ();
var catchStart = encoder.DefineLabel ();
var afterAll = encoder.DefineLabel ();

encoder.Call (_waitForBridgeProcessingRef);
encoder.MarkLabel (tryStart);
emitCallback (encoder);
if (!isVoid) {
encoder.StoreLocal (0);
}
encoder.Branch (ILOpCode.Leave, afterAll);

encoder.MarkLabel (catchStart);
encoder.StoreLocal (isVoid ? 0 : 1);
encoder.LoadLocal (isVoid ? 0 : 1);
encoder.Call (_androidEnvironmentUnhandledExceptionRef);
encoder.Branch (ILOpCode.Leave, afterAll);

encoder.MarkLabel (afterAll);
if (!isVoid) {
encoder.LoadLocal (0);
}
encoder.OpCode (ILOpCode.Ret);

cfb.AddCatchRegion (tryStart, catchStart, catchStart, afterAll, _exceptionRef);
}

void EncodeUcoForwarderLegacyLocals (BlobBuilder blob, JniParamKind returnKind)
{
bool isVoid = returnKind == JniParamKind.Void;
blob.WriteByte (0x07); // LOCAL_SIG
blob.WriteCompressedInteger (isVoid ? 1 : 2);
if (!isVoid) {
JniSignatureHelper.EncodeClrType (new SignatureTypeEncoder (blob), returnKind);
}
blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_exceptionRef));
}

MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxyData proxy)
{
var targetTypeRef = _pe.ResolveTypeRef (uco.TargetType);
Expand Down Expand Up @@ -1115,10 +1180,11 @@ void EncodeUcoConstructorLocals_JavaInterop (BlobBuilder blob)
blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef));
}

void EmitRegisterNatives (List<NativeRegistrationData> registrations,
void EmitRegisterNatives (JavaPeerProxyData proxy,
Dictionary<string, MethodDefinitionHandle> wrapperHandles)
{
// Filter to only registrations that have corresponding wrapper methods
var registrations = proxy.NativeRegistrations;
var validRegs = new List<(NativeRegistrationData Reg, MethodDefinitionHandle Wrapper)> (registrations.Count);
foreach (var reg in registrations) {
if (wrapperHandles.TryGetValue (reg.WrapperMethodName, out var wrapperHandle)) {
Expand Down
Loading