Skip to content

Commit c19872c

Browse files
Inline activation ctor in UCO wrappers
- UCO constructors directly call activation ctor (no ActivateInstance indirection) - WithinNewObjectScope guard prevents double peer creation - No-op UCO for open generic type definitions - ControlFlowBuilder support in PEAssemblyBuilder - Remove TrimmableNativeRegistration wrapper and ActivateInstance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cfbf866 commit c19872c

5 files changed

Lines changed: 77 additions & 90 deletions

File tree

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ sealed record UcoMethodData
182182
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
183183
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
184184
/// so the ABI is correct when JNI dispatches the call.
185-
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
185+
/// Body: directly activates the target type using its generated activation ctor.
186186
/// </summary>
187187
sealed record UcoConstructorData
188188
{
@@ -192,7 +192,7 @@ sealed record UcoConstructorData
192192
public required string WrapperName { get; init; }
193193

194194
/// <summary>
195-
/// Target type to pass to ActivateInstance.
195+
/// Target type to activate in the generated wrapper.
196196
/// </summary>
197197
public required TypeRefData TargetType { get; init; }
198198

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
239239
int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1;
240240

241241
var handle = Metadata.AddTypeDefinition (
242-
TypeAttributes.NestedPrivate | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
242+
TypeAttributes.NestedAssembly | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
243243
default,
244244
Metadata.GetOrAddString ($"__utf8_{size}"),
245245
Metadata.AddTypeReference (SystemRuntimeRef,
@@ -259,7 +259,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
259259
/// </summary>
260260
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
261261
Action<BlobEncoder> encodeSig, Action<InstructionEncoder> emitIL)
262-
=> EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null);
262+
=> EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null, useBranches: false);
263263

264264
/// <summary>
265265
/// Emits a method body and definition with optional local variable declarations.
@@ -269,6 +269,11 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
269269
/// <see cref="BlobBuilder"/> and must write the full <c>LOCAL_SIG</c> blob (header 0x07,
270270
/// compressed count, then each variable type).
271271
/// </param>
272+
/// <param name="useBranches">
273+
/// If true, creates a <see cref="ControlFlowBuilder"/> so the emitted IL can use
274+
/// <see cref="InstructionEncoder.DefineLabel"/>, <see cref="InstructionEncoder.Branch"/>,
275+
/// and <see cref="InstructionEncoder.MarkLabel"/>.
276+
/// </param>
272277
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
273278
Action<BlobEncoder> encodeSig, Action<InstructionEncoder> emitIL,
274279
Action<BlobBuilder>? encodeLocals)

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
4343
///
4444
/// [UnmanagedCallersOnly]
4545
/// public static void nctor_0_uco(IntPtr jnienv, IntPtr self)
46-
/// =&gt; TrimmableNativeRegistration.ActivateInstance(self, typeof(Activity));
46+
/// =&gt; new Activity(self, JniHandleOwnership.DoNotTransfer);
47+
/// // or: var obj = (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity));
48+
/// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer);
4749
///
4850
/// // Registers JNI native methods (ACWs only):
4951
/// public void RegisterNatives(JniType jniType)
@@ -87,7 +89,6 @@ sealed class TypeMapAssemblyEmitter
8789
MemberReferenceHandle _jniObjectReferenceCtorRef;
8890
MemberReferenceHandle _jniEnvDeleteRefRef;
8991
MemberReferenceHandle _withinNewObjectScopeRef;
90-
MemberReferenceHandle _activateInstanceRef;
9192
MemberReferenceHandle _ucoAttrCtorRef;
9293
BlobHandle _ucoAttrBlobHandle;
9394
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
@@ -244,17 +245,6 @@ void EmitMemberReferences ()
244245
rt => rt.Type ().Boolean (),
245246
p => { }));
246247

247-
// TrimmableTypeMap.ActivateInstance(IntPtr, Type)
248-
var trimmableTypeMapRef = _pe.Metadata.AddTypeReference (_pe.MonoAndroidRef,
249-
_pe.Metadata.GetOrAddString ("Microsoft.Android.Runtime"), _pe.Metadata.GetOrAddString ("TrimmableTypeMap"));
250-
_activateInstanceRef = _pe.AddMemberRef (trimmableTypeMapRef, "ActivateInstance",
251-
sig => sig.MethodSignature ().Parameters (2,
252-
rt => rt.Void (),
253-
p => {
254-
p.AddParameter ().Type ().IntPtr ();
255-
p.AddParameter ().Type ().Type (_systemTypeRef, false);
256-
}));
257-
258248
// JniNativeMethod..ctor(byte*, byte*, IntPtr)
259249
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
260250
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
@@ -354,6 +344,16 @@ void EmitTypeMapAssociationAttributeCtorRef ()
354344

355345
void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinitionHandle> wrapperHandles)
356346
{
347+
if (proxy.IsAcw) {
348+
// RegisterNatives uses RVA-backed UTF-8 fields under <PrivateImplementationDetails>.
349+
// Materialize those helper types before adding the proxy TypeDef, otherwise the
350+
// later RegisterNatives method can be attached to the helper type instead.
351+
foreach (var reg in proxy.NativeRegistrations) {
352+
_pe.GetOrAddUtf8Field (reg.JniMethodName);
353+
_pe.GetOrAddUtf8Field (reg.JniSignature);
354+
}
355+
}
356+
357357
var metadata = _pe.Metadata;
358358
var typeDefHandle = metadata.AddTypeDefinition (
359359
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
@@ -368,14 +368,15 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition
368368
}
369369

370370
// .ctor
371-
_pe.EmitBody (".ctor",
371+
var ctorHandle = _pe.EmitBody (".ctor",
372372
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
373373
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }),
374374
encoder => {
375375
encoder.OpCode (ILOpCode.Ldarg_0);
376376
encoder.Call (_baseCtorRef);
377377
encoder.OpCode (ILOpCode.Ret);
378378
});
379+
metadata.AddCustomAttribute (typeDefHandle, ctorHandle, _ucoAttrBlobHandle);
379380

380381
// CreateInstance
381382
EmitCreateInstance (proxy);
@@ -704,10 +705,14 @@ MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco)
704705

705706
MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxyData proxy)
706707
{
707-
var userTypeRef = _pe.ResolveTypeRef (uco.TargetType);
708+
var targetTypeRef = _pe.ResolveTypeRef (uco.TargetType);
708709
var activationCtor = proxy.ActivationCtor ?? throw new InvalidOperationException (
709710
$"UCO constructor wrapper requires an activation ctor for '{uco.TargetType.ManagedTypeName}'");
710711

712+
// UCO constructor wrappers must match the JNI native method signature exactly.
713+
// Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters
714+
// are not forwarded because we create the managed peer using the
715+
// activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor.
711716
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
712717
int paramCount = 2 + jniParams.Count;
713718

@@ -733,79 +738,88 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
733738
}
734739

735740
MethodDefinitionHandle handle;
741+
if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
742+
var ctorRef = AddJavaInteropActivationCtorRef (
743+
activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));
736744

737-
// For non-leaf activation, keep the WithinNewObjectScope guard but route back
738-
// through the generated proxy activation path instead of a runtime reflection helper.
739-
if (!activationCtor.IsOnLeafType) {
740745
handle = _pe.EmitBody (uco.WrapperName,
741746
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
742747
encodeSig,
743748
encoder => {
749+
// Skip activation if the object is being created from managed code
750+
// (e.g., JNIEnv.StartCreateInstance / JNIEnv.NewObject).
744751
var skipLabel = encoder.DefineLabel ();
745752
encoder.Call (_withinNewObjectScopeRef);
746753
encoder.Branch (ILOpCode.Brtrue, skipLabel);
747754

748-
encoder.LoadArgument (1); // jniSelf
749-
encoder.OpCode (ILOpCode.Ldtoken);
750-
encoder.Token (userTypeRef);
751-
encoder.Call (_getTypeFromHandleRef);
752-
encoder.Call (_activateInstanceRef);
753-
754-
encoder.MarkLabel (skipLabel);
755-
encoder.OpCode (ILOpCode.Ret);
756-
},
757-
encodeLocals: null,
758-
useBranches: true);
759-
} else if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
760-
var ctorRef = AddJavaInteropActivationCtorRef (userTypeRef);
761-
762-
handle = _pe.EmitBody (uco.WrapperName,
763-
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
764-
encodeSig,
765-
encoder => {
766-
var skipLabel = encoder.DefineLabel ();
767-
encoder.Call (_withinNewObjectScopeRef);
768-
encoder.Branch (ILOpCode.Brtrue, skipLabel);
755+
if (!activationCtor.IsOnLeafType) {
756+
encoder.OpCode (ILOpCode.Ldtoken);
757+
encoder.Token (targetTypeRef);
758+
encoder.Call (_getTypeFromHandleRef);
759+
encoder.Call (_getUninitializedObjectRef);
760+
encoder.OpCode (ILOpCode.Castclass);
761+
encoder.Token (targetTypeRef);
762+
}
769763

770764
encoder.LoadLocalAddress (0);
771765
encoder.LoadArgument (1); // self
772766
encoder.Call (_jniObjectReferenceCtorRef);
773767

774-
encoder.LoadLocalAddress (0);
775-
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
776-
encoder.OpCode (ILOpCode.Newobj);
777-
encoder.Token (ctorRef);
778-
encoder.OpCode (ILOpCode.Pop);
768+
if (activationCtor.IsOnLeafType) {
769+
encoder.LoadLocalAddress (0);
770+
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
771+
encoder.OpCode (ILOpCode.Newobj);
772+
encoder.Token (ctorRef);
773+
encoder.OpCode (ILOpCode.Pop);
774+
} else {
775+
encoder.LoadLocalAddress (0);
776+
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
777+
encoder.Call (ctorRef);
778+
}
779779

780780
encoder.MarkLabel (skipLabel);
781781
encoder.OpCode (ILOpCode.Ret);
782782
},
783783
EncodeJniObjectReferenceLocal,
784784
useBranches: true);
785785
} else {
786-
var ctorRef = AddActivationCtorRef (userTypeRef);
786+
var ctorRef = AddActivationCtorRef (
787+
activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));
787788

788789
handle = _pe.EmitBody (uco.WrapperName,
789790
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
790791
encodeSig,
791792
encoder => {
793+
// Skip activation if the object is being created from managed code
792794
var skipLabel = encoder.DefineLabel ();
793795
encoder.Call (_withinNewObjectScopeRef);
794796
encoder.Branch (ILOpCode.Brtrue, skipLabel);
795797

796-
encoder.LoadArgument (1); // self
797-
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
798-
encoder.OpCode (ILOpCode.Newobj);
799-
encoder.Token (ctorRef);
800-
encoder.OpCode (ILOpCode.Pop);
798+
if (activationCtor.IsOnLeafType) {
799+
encoder.LoadArgument (1); // self
800+
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
801+
encoder.OpCode (ILOpCode.Newobj);
802+
encoder.Token (ctorRef);
803+
encoder.OpCode (ILOpCode.Pop);
804+
} else {
805+
encoder.OpCode (ILOpCode.Ldtoken);
806+
encoder.Token (targetTypeRef);
807+
encoder.Call (_getTypeFromHandleRef);
808+
encoder.Call (_getUninitializedObjectRef);
809+
encoder.OpCode (ILOpCode.Castclass);
810+
encoder.Token (targetTypeRef);
811+
812+
encoder.LoadArgument (1); // self
813+
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
814+
encoder.Call (ctorRef);
815+
}
801816

802817
encoder.MarkLabel (skipLabel);
803818
encoder.OpCode (ILOpCode.Ret);
804819
},
805820
encodeLocals: null,
806821
useBranches: true);
807822
}
808-
809823
AddUnmanagedCallersOnlyAttribute (handle);
810824
return handle;
811825
}

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

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -147,39 +147,6 @@ internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transf
147147
return GetProxyForManagedType (type)?.GetContainerFactory ();
148148
}
149149

150-
/// <summary>
151-
/// Creates a managed peer instance for a Java object being constructed.
152-
/// Called from generated UCO constructor wrappers (nctor_*_uco).
153-
/// </summary>
154-
internal static void ActivateInstance (IntPtr self, Type targetType)
155-
{
156-
var instance = s_instance;
157-
if (instance is null) {
158-
throw new InvalidOperationException ("TrimmableTypeMap has not been initialized.");
159-
}
160-
161-
// Look up the proxy via JNI class name → TypeMap dictionary.
162-
// We can't use targetType.GetCustomAttribute<JavaPeerProxy>() because the
163-
// self-application attribute is on the proxy type, not the target type.
164-
var selfRef = new JniObjectReference (self);
165-
var jniClass = JniEnvironment.Types.GetObjectClass (selfRef);
166-
var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass);
167-
JniObjectReference.Dispose (ref jniClass);
168-
169-
if (className is null || !instance._typeMap.TryGetValue (className, out var proxyType)) {
170-
throw new InvalidOperationException (
171-
$"Failed to create peer for type '{targetType.FullName}' (jniClass='{className}'). " +
172-
"Ensure the type has a generated proxy in the TypeMap assembly.");
173-
}
174-
175-
var proxy = proxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
176-
if (proxy is null || proxy.CreateInstance (self, JniHandleOwnership.DoNotTransfer) is null) {
177-
throw new InvalidOperationException (
178-
$"Failed to create peer for type '{targetType.FullName}'. " +
179-
"Ensure the type has a generated proxy in the TypeMap assembly.");
180-
}
181-
}
182-
183150
[UnmanagedCallersOnly]
184151
static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHandle)
185152
{

tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer ()
189189
}
190190

191191
[Fact]
192-
public void Generate_InheritedCtor_UcoUsesGuardWithoutReflectionFallback ()
192+
public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation ()
193193
{
194194
var peers = ScanFixtures ();
195195
var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity");
@@ -202,7 +202,8 @@ public void Generate_InheritedCtor_UcoUsesGuardWithoutReflectionFallback ()
202202
var memberNames = GetMemberRefNames (reader);
203203

204204
Assert.Contains ("get_WithinNewObjectScope", memberNames);
205-
Assert.Contains ("ActivateInstance", memberNames);
205+
Assert.Contains ("GetUninitializedObject", memberNames);
206+
Assert.DoesNotContain ("ActivateInstance", memberNames);
206207
Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames);
207208
}
208209

0 commit comments

Comments
 (0)