Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -182,7 +182,7 @@ sealed record UcoMethodData
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
/// so the ABI is correct when JNI dispatches the call.
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
/// Body: directly activates the target type using its generated activation ctor.
/// </summary>
sealed record UcoConstructorData
{
Expand All @@ -192,7 +192,7 @@ sealed record UcoConstructorData
public required string WrapperName { get; init; }

/// <summary>
/// Target type to pass to ActivateInstance.
/// Target type to activate in the generated wrapper.
/// </summary>
public required TypeRefData TargetType { get; init; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1;

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

/// <summary>
/// Emits a method body and definition with optional local variable declarations.
Expand All @@ -269,6 +269,11 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
/// <see cref="BlobBuilder"/> and must write the full <c>LOCAL_SIG</c> blob (header 0x07,
/// compressed count, then each variable type).
/// </param>
/// <param name="useBranches">
/// If true, creates a <see cref="ControlFlowBuilder"/> so the emitted IL can use
/// <see cref="InstructionEncoder.DefineLabel"/>, <see cref="InstructionEncoder.Branch"/>,
/// and <see cref="InstructionEncoder.MarkLabel"/>.
/// </param>
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
Action<BlobEncoder> encodeSig, Action<InstructionEncoder> emitIL,
Action<BlobBuilder>? encodeLocals)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
/// [UnmanagedCallersOnly]
/// public static void nctor_0_uco(IntPtr jnienv, IntPtr self)
/// =&gt; TrimmableNativeRegistration.ActivateInstance(self, typeof(Activity));
/// =&gt; new Activity(self, JniHandleOwnership.DoNotTransfer);
/// // or: var obj = (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity));
/// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer);
///
/// // Registers JNI native methods (ACWs only):
/// public void RegisterNatives(JniType jniType)
Expand Down Expand Up @@ -87,7 +89,6 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _jniObjectReferenceCtorRef;
MemberReferenceHandle _jniEnvDeleteRefRef;
MemberReferenceHandle _withinNewObjectScopeRef;
MemberReferenceHandle _activateInstanceRef;
MemberReferenceHandle _ucoAttrCtorRef;
BlobHandle _ucoAttrBlobHandle;
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
Expand Down Expand Up @@ -244,17 +245,6 @@ void EmitMemberReferences ()
rt => rt.Type ().Boolean (),
p => { }));

// TrimmableTypeMap.ActivateInstance(IntPtr, Type)
var trimmableTypeMapRef = _pe.Metadata.AddTypeReference (_pe.MonoAndroidRef,
_pe.Metadata.GetOrAddString ("Microsoft.Android.Runtime"), _pe.Metadata.GetOrAddString ("TrimmableTypeMap"));
_activateInstanceRef = _pe.AddMemberRef (trimmableTypeMapRef, "ActivateInstance",
sig => sig.MethodSignature ().Parameters (2,
rt => rt.Void (),
p => {
p.AddParameter ().Type ().IntPtr ();
p.AddParameter ().Type ().Type (_systemTypeRef, false);
}));

// JniNativeMethod..ctor(byte*, byte*, IntPtr)
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
Expand Down Expand Up @@ -354,6 +344,16 @@ void EmitTypeMapAssociationAttributeCtorRef ()

void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinitionHandle> wrapperHandles)
{
if (proxy.IsAcw) {
// RegisterNatives uses RVA-backed UTF-8 fields under <PrivateImplementationDetails>.
// Materialize those helper types before adding the proxy TypeDef, otherwise the
// later RegisterNatives method can be attached to the helper type instead.
foreach (var reg in proxy.NativeRegistrations) {
_pe.GetOrAddUtf8Field (reg.JniMethodName);
_pe.GetOrAddUtf8Field (reg.JniSignature);
}
}

var metadata = _pe.Metadata;
var typeDefHandle = metadata.AddTypeDefinition (
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
Expand All @@ -368,14 +368,15 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition
}

// .ctor
_pe.EmitBody (".ctor",
var ctorHandle = _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.OpCode (ILOpCode.Ret);
});
metadata.AddCustomAttribute (typeDefHandle, ctorHandle, _ucoAttrBlobHandle);

// CreateInstance
EmitCreateInstance (proxy);
Expand Down Expand Up @@ -704,10 +705,14 @@ MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco)

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

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

Expand All @@ -733,79 +738,88 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
}

MethodDefinitionHandle handle;
if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
var ctorRef = AddJavaInteropActivationCtorRef (
activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));

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

encoder.LoadArgument (1); // jniSelf
encoder.OpCode (ILOpCode.Ldtoken);
encoder.Token (userTypeRef);
encoder.Call (_getTypeFromHandleRef);
encoder.Call (_activateInstanceRef);

encoder.MarkLabel (skipLabel);
encoder.OpCode (ILOpCode.Ret);
},
encodeLocals: null,
useBranches: true);
} else if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
var ctorRef = AddJavaInteropActivationCtorRef (userTypeRef);

handle = _pe.EmitBody (uco.WrapperName,
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
encodeSig,
encoder => {
var skipLabel = encoder.DefineLabel ();
encoder.Call (_withinNewObjectScopeRef);
encoder.Branch (ILOpCode.Brtrue, skipLabel);
if (!activationCtor.IsOnLeafType) {
encoder.OpCode (ILOpCode.Ldtoken);
encoder.Token (targetTypeRef);
encoder.Call (_getTypeFromHandleRef);
encoder.Call (_getUninitializedObjectRef);
encoder.OpCode (ILOpCode.Castclass);
encoder.Token (targetTypeRef);
}

encoder.LoadLocalAddress (0);
encoder.LoadArgument (1); // self
encoder.Call (_jniObjectReferenceCtorRef);

encoder.LoadLocalAddress (0);
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
encoder.OpCode (ILOpCode.Newobj);
encoder.Token (ctorRef);
encoder.OpCode (ILOpCode.Pop);
if (activationCtor.IsOnLeafType) {
encoder.LoadLocalAddress (0);
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
encoder.OpCode (ILOpCode.Newobj);
encoder.Token (ctorRef);
encoder.OpCode (ILOpCode.Pop);
} else {
encoder.LoadLocalAddress (0);
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
encoder.Call (ctorRef);
}

encoder.MarkLabel (skipLabel);
encoder.OpCode (ILOpCode.Ret);
},
EncodeJniObjectReferenceLocal,
useBranches: true);
} else {
var ctorRef = AddActivationCtorRef (userTypeRef);
var ctorRef = AddActivationCtorRef (
activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));

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

encoder.LoadArgument (1); // self
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
encoder.OpCode (ILOpCode.Newobj);
encoder.Token (ctorRef);
encoder.OpCode (ILOpCode.Pop);
if (activationCtor.IsOnLeafType) {
encoder.LoadArgument (1); // self
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
encoder.OpCode (ILOpCode.Newobj);
encoder.Token (ctorRef);
encoder.OpCode (ILOpCode.Pop);
} else {
encoder.OpCode (ILOpCode.Ldtoken);
encoder.Token (targetTypeRef);
encoder.Call (_getTypeFromHandleRef);
encoder.Call (_getUninitializedObjectRef);
encoder.OpCode (ILOpCode.Castclass);
encoder.Token (targetTypeRef);

encoder.LoadArgument (1); // self
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
encoder.Call (ctorRef);
}

encoder.MarkLabel (skipLabel);
encoder.OpCode (ILOpCode.Ret);
},
encodeLocals: null,
useBranches: true);
}

AddUnmanagedCallersOnlyAttribute (handle);
return handle;
}
Expand Down
33 changes: 0 additions & 33 deletions src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,39 +147,6 @@ internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transf
return GetProxyForManagedType (type)?.GetContainerFactory ();
}

/// <summary>
/// Creates a managed peer instance for a Java object being constructed.
/// Called from generated UCO constructor wrappers (nctor_*_uco).
/// </summary>
internal static void ActivateInstance (IntPtr self, Type targetType)
{
var instance = s_instance;
if (instance is null) {
throw new InvalidOperationException ("TrimmableTypeMap has not been initialized.");
}

// Look up the proxy via JNI class name → TypeMap dictionary.
// We can't use targetType.GetCustomAttribute<JavaPeerProxy>() because the
// self-application attribute is on the proxy type, not the target type.
var selfRef = new JniObjectReference (self);
var jniClass = JniEnvironment.Types.GetObjectClass (selfRef);
var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass);
JniObjectReference.Dispose (ref jniClass);

if (className is null || !instance._typeMap.TryGetValue (className, out var proxyType)) {
throw new InvalidOperationException (
$"Failed to create peer for type '{targetType.FullName}' (jniClass='{className}'). " +
"Ensure the type has a generated proxy in the TypeMap assembly.");
}

var proxy = proxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
if (proxy is null || proxy.CreateInstance (self, JniHandleOwnership.DoNotTransfer) is null) {
throw new InvalidOperationException (
$"Failed to create peer for type '{targetType.FullName}'. " +
"Ensure the type has a generated proxy in the TypeMap assembly.");
}
}

[UnmanagedCallersOnly]
static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHandle)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer ()
}

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

Assert.Contains ("get_WithinNewObjectScope", memberNames);
Assert.Contains ("ActivateInstance", memberNames);
Assert.Contains ("GetUninitializedObject", memberNames);
Assert.DoesNotContain ("ActivateInstance", memberNames);
Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames);
}

Expand Down