Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSR: Generate additional constructors to reduce usage of reflection #18529

Merged
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
14 changes: 6 additions & 8 deletions src/Foundation/NSObject2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,14 @@ public void Dispose ()
GC.SuppressFinalize (this);
}

static T AllocateNSObject<T> (IntPtr handle) where T : NSObject
{
var obj = (T) RuntimeHelpers.GetUninitializedObject (typeof (T));
obj.handle = handle;
obj.flags = Flags.NativeRef;
return obj;
}

internal static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, Flags flags)
{
#if NET
if (Runtime.IsManagedStaticRegistrar) {
throw new System.Diagnostics.UnreachableException ();
}
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
#endif

// This function is called from native code before any constructors have executed.
var type = (Type) Runtime.GetGCHandleTarget (type_gchandle);
try {
Expand Down
55 changes: 44 additions & 11 deletions tools/dotnet-linker/AppBundleRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public AssemblyDefinition PlatformAssembly {

Dictionary<AssemblyDefinition, Dictionary<string, (TypeDefinition, TypeReference)>> type_map = new ();
Dictionary<string, (MethodDefinition, MethodReference)> method_map = new ();
Dictionary<string, (FieldDefinition, FieldReference)> field_map = new ();

public AppBundleRewriter (LinkerConfiguration configuration)
{
Expand Down Expand Up @@ -176,6 +177,23 @@ static string GetMethodSignature (MethodDefinition method)
return $"{method?.ReturnType?.FullName ?? "(null)"} {method?.DeclaringType?.FullName ?? "(null)"}::{method?.Name ?? "(null)"} ({string.Join (", ", method?.Parameters?.Select (v => v?.ParameterType?.FullName + " " + v?.Name) ?? Array.Empty<string> ())})";
}

public FieldReference GetFieldReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, out FieldDefinition field)
{
if (!field_map.TryGetValue (key, out var tuple)) {
var td = tr.Resolve ();
var fd = td.Fields.SingleOrDefault (v => v.Name == name);
if (fd is null)
throw new InvalidOperationException ($"Unable to find the field '{tr.FullName}::{name}' (for key '{key}') in {assembly.Name.Name}. Fields in type:\n\t{string.Join ("\n\t", td.Fields.Select (f => f.Name).OrderBy (v => v))}");

tuple.Item1 = fd;
tuple.Item2 = CurrentAssembly.MainModule.ImportReference (fd);
field_map.Add (key, tuple);
}

field = tuple.Item1;
return tuple.Item2;
}

/* Types */

public TypeReference System_Boolean {
Expand Down Expand Up @@ -267,6 +285,12 @@ public TypeReference System_Void {
}
}

public TypeReference System_ValueType {
get {
return GetTypeReference (CorlibAssembly, "System.ValueType", out var _);
}
}

public TypeReference System_Collections_Generic_Dictionary2 {
get {
return GetTypeReference (CorlibAssembly, "System.Collections.Generic.Dictionary`2", out var _);
Expand Down Expand Up @@ -321,6 +345,26 @@ public TypeReference Foundation_NSObject {
}
}

public FieldReference Foundation_NSObject_HandleField {
get {
return GetFieldReference (PlatformAssembly, Foundation_NSObject, "handle", "Foundation.NSObject::handle", out var _);
}
}

#if NET
public MethodReference Foundation_NSObject_FlagsSetterMethod {
get {
return GetMethodReference (PlatformAssembly, Foundation_NSObject, "set_flags", "Foundation.NSObject::set_flags", predicate: null, out var _);
}
}
#else
public FieldReference Foundation_NSObject_FlagsField {
get {
return GetFieldReference (PlatformAssembly, Foundation_NSObject, "flags", "Foundation.NSObject::flags", out var _);
}
}
#endif

public TypeReference ObjCRuntime_BindAs {
get {
return GetTypeReference (PlatformAssembly, "ObjCRuntime.BindAs", out var _);
Expand Down Expand Up @@ -449,17 +493,6 @@ public MethodReference MethodBase_GetMethodFromHandle__RuntimeMethodHandle {
}
}

public MethodReference NSObject_AllocateNSObject {
get {
return GetMethodReference (PlatformAssembly,
Foundation_NSObject, "AllocateNSObject",
nameof (NSObject_AllocateNSObject),
isStatic: true,
genericParameterCount: 1,
System_IntPtr);
}
}

public MethodReference BindAs_ConvertNSArrayToManagedArray {
get {
return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertNSArrayToManagedArray", (v) =>
Expand Down
8 changes: 8 additions & 0 deletions tools/dotnet-linker/CecilExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ public static MethodDefinition AddMethod (this TypeDefinition self, string name,
return rv;
}

public static FieldDefinition AddField (this TypeDefinition self, string name, FieldAttributes attributes, TypeReference type)
{
var rv = new FieldDefinition (name, attributes, type);
rv.DeclaringType = self;
self.Fields.Add (rv);
return rv;
}

public static MethodBody CreateBody (this MethodDefinition self, out ILProcessor il)
{
var body = new MethodBody (self);
Expand Down
93 changes: 87 additions & 6 deletions tools/dotnet-linker/Steps/ManagedRegistrarStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ public void EmitCallToExportedMethod (MethodDefinition method, MethodDefinition
var placeholderType = abr.System_IntPtr;
ParameterDefinition? callSuperParameter = null;
VariableDefinition? returnVariable = null;
MethodReference? ctor = null;
var leaveTryInstructions = new List<Instruction> ();
var isVoid = method.ReturnType.Is ("System", "Void");

Expand All @@ -425,6 +426,7 @@ public void EmitCallToExportedMethod (MethodDefinition method, MethodDefinition
// and later on remove everything after this instruction. Maybe at a later point I'll figure out a way to make the code emission conditional without
// littering the logic with conditional statements.
Instruction? skipEverythingAfter = null;

if (isInstanceCategory) {
il.Emit (OpCodes.Ldarg_0);
EmitConversion (method, il, method.Parameters [0].ParameterType, true, 0, out var nativeType, postProcessing);
Expand Down Expand Up @@ -459,12 +461,32 @@ public void EmitCallToExportedMethod (MethodDefinition method, MethodDefinition
// We're throwing an exception, so there's no need for any more code.
skipEverythingAfter = il.Body.Instructions.Last ();
} else {
il.Emit (OpCodes.Ldarg_0);
// Whenever there's an NSObject constructor that we call from a registrar callback, we need to create
// a separate constructor that will first set the `handle` and `flags` values of the NSObject before
// calling the original constructor. Here's an example of the code we generate:
//
// // The original constructor:
// public .ctor (T0 p0, T1 p1, ...) { /* ... */ }
//
// // The generated constructor with pre-initialization:
// public .ctor (T0 p0, T1 p1, ..., IntPtr nativeHandle, IManagedRegistrar dummy) {
// this.handle = (NativeHandle)nativeHandle;
// this.flags = 2; // Flags.NativeRef == 2
// this..ctor (p0, p1, ...);
// }
//
// - This code can't be expressed in C# and it can only be expressed directly in IL.
// - The reason we need to do this is because the base NSObject parameterless constructor
// would allocate a new Objective-C object if `handle` is a zero pointer.
// - The `IManagedRegistrar` dummy parameter is used only to make sure that the signature
// is unique and there aren't any conflicts. The IManagedRegistrar type is internal and
// we only make it public through a custom linker step.

ctor = CloneConstructorWithNativeHandle (method);
method.DeclaringType.Methods.Add (ctor.Resolve ());

il.Emit (OpCodes.Nop);
postLeaveBranch.Operand = il.Body.Instructions.Last ();
var git = new GenericInstanceMethod (abr.NSObject_AllocateNSObject);
git.GenericArguments.Add (method.DeclaringType);
il.Emit (OpCodes.Call, git);
il.Emit (OpCodes.Dup); // this is for the call to ObjCRuntime.NativeObjectExtensions::GetHandle after the call to the constructor
}
} else if (isGeneric) {
// this is a proxy method and we can simply use `this` without any conversion
Expand Down Expand Up @@ -509,7 +531,13 @@ public void EmitCallToExportedMethod (MethodDefinition method, MethodDefinition

callback.AddParameter ("exception_gchandle", new PointerType (abr.System_IntPtr));

if (isGeneric && !method.IsConstructor) {
if (ctor is not null) {
// in addition to the params of the original ctor we pass also the native handle and a null
// value for the dummy (de-duplication) parameter
il.Emit (OpCodes.Ldarg_0);
il.Emit (OpCodes.Ldnull);
il.Emit (OpCodes.Newobj, ctor);
} else if (isGeneric && !method.IsConstructor) {
var targetMethod = method.DeclaringType.CreateMethodReferenceOnGenericType (method, method.DeclaringType.GenericParameters.ToArray ());
il.Emit (OpCodes.Call, targetMethod);
} else if (method.IsStatic) {
Expand Down Expand Up @@ -1270,5 +1298,58 @@ void GenerateConversionToNative (MethodDefinition method, ILProcessor il, TypeRe
il.Append (endTarget);
}
}

MethodDefinition CloneConstructorWithNativeHandle (MethodDefinition ctor)
{
var clonedCtor = new MethodDefinition (ctor.Name, ctor.Attributes, ctor.ReturnType);
clonedCtor.IsPublic = false;

// clone the original parameters firsts
foreach (var parameter in ctor.Parameters) {
clonedCtor.AddParameter (parameter.Name, parameter.ParameterType);
}

// add a native handle param + a dummy parameter that we know for a fact won't be used anywhere
// to make the signature of the new constructor unique
var handleParameter = clonedCtor.AddParameter ("nativeHandle", abr.System_IntPtr);
var dummyParameter = clonedCtor.AddParameter ("dummy", abr.ObjCRuntime_IManagedRegistrar);

var body = clonedCtor.CreateBody (out var il);

// ensure visible
abr.Foundation_NSObject_HandleField.Resolve ().IsFamily = true;
#if NET
abr.Foundation_NSObject_FlagsSetterMethod.Resolve ().IsFamily = true;
#else
abr.Foundation_NSObject_FlagsField.Resolve ().IsFamily = true;
#endif

// store the handle and flags first
il.Emit (OpCodes.Ldarg_0);
il.Emit (OpCodes.Ldarg, handleParameter);
#if NET
il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_NativeHandle);
#endif
il.Emit (OpCodes.Stfld, abr.CurrentAssembly.MainModule.ImportReference (abr.Foundation_NSObject_HandleField));

il.Emit (OpCodes.Ldarg_0);
il.Emit (OpCodes.Ldc_I4_2); // Flags.NativeRef == 2
#if NET
il.Emit (OpCodes.Call, abr.Foundation_NSObject_FlagsSetterMethod);
#else
il.Emit (OpCodes.Stfld, abr.Foundation_NSObject_FlagsField);
#endif

// call the original constructor with all of the original parameters
il.Emit (OpCodes.Ldarg_0);
foreach (var parameter in clonedCtor.Parameters.SkipLast (2)) {
il.Emit (OpCodes.Ldarg, parameter);
}

il.Emit (OpCodes.Call, ctor);
il.Emit (OpCodes.Ret);

return clonedCtor;
}
}
}
Loading