From 57f1ce11564f9cfe951964ba4f9556c281bb3625 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH] [tools] Add a managed static registrar. Fixes #17324. Fixes https://github.com/xamarin/xamarin-macios/issues/17324. --- src/CoreFoundation/CFArray.cs | 12 +- src/Foundation/NSArray.cs | 25 + src/Foundation/NSObject2.cs | 8 + src/Makefile | 1 + src/ObjCRuntime/BindAs.cs | 253 ++ src/ObjCRuntime/Blocks.cs | 27 + src/ObjCRuntime/Class.cs | 26 +- src/ObjCRuntime/DynamicRegistrar.cs | 18 +- src/ObjCRuntime/Registrar.cs | 38 +- src/ObjCRuntime/RegistrarHelper.cs | 389 +++ src/ObjCRuntime/Runtime.cs | 163 +- src/frameworks.sources | 2 + tools/common/StaticRegistrar.cs | 358 ++- tools/dotnet-linker/LinkerConfiguration.cs | 26 + .../Steps/ConfigurationAwareStep.cs | 9 + .../Steps/ManagedRegistrarStep.cs | 2591 +++++++++++++++++ tools/linker/CoreOptimizeGeneratedCode.cs | 52 +- tools/mtouch/Errors.designer.cs | 28 +- tools/mtouch/Errors.resx | 13 +- 19 files changed, 3892 insertions(+), 147 deletions(-) create mode 100644 src/ObjCRuntime/BindAs.cs create mode 100644 src/ObjCRuntime/RegistrarHelper.cs diff --git a/src/CoreFoundation/CFArray.cs b/src/CoreFoundation/CFArray.cs index 6aa954ca868d..8f1937849a80 100644 --- a/src/CoreFoundation/CFArray.cs +++ b/src/CoreFoundation/CFArray.cs @@ -108,10 +108,10 @@ public NativeHandle GetValue (nint index) return CFArrayGetValueAtIndex (GetCheckedHandle (), index); } - internal static unsafe NativeHandle Create (params NativeHandle [] values) + internal static unsafe NativeHandle Create (params NativeHandle []? values) { if (values is null) - ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (values)); + return NativeHandle.Zero; fixed (NativeHandle* pv = values) { return CFArrayCreate (IntPtr.Zero, (IntPtr) pv, @@ -120,10 +120,10 @@ internal static unsafe NativeHandle Create (params NativeHandle [] values) } } - public static unsafe NativeHandle Create (params INativeObject [] values) + public static unsafe NativeHandle Create (params INativeObject []? values) { if (values is null) - ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (values)); + return NativeHandle.Zero; int c = values.Length; var _values = c <= 256 ? stackalloc IntPtr [c] : new IntPtr [c]; for (int i = 0; i < c; ++i) @@ -132,10 +132,10 @@ public static unsafe NativeHandle Create (params INativeObject [] values) return CFArrayCreate (IntPtr.Zero, (IntPtr) pv, c, kCFTypeArrayCallbacks_ptr); } - public static unsafe NativeHandle Create (params string [] values) + public static unsafe NativeHandle Create (params string []? values) { if (values is null) - ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (values)); + return NativeHandle.Zero; var c = values.Length; var _values = c <= 256 ? stackalloc IntPtr [c] : new IntPtr [c]; for (var i = 0; i < c; ++i) diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs index 63d3c9987e7f..be067b875b49 100644 --- a/src/Foundation/NSArray.cs +++ b/src/Foundation/NSArray.cs @@ -295,6 +295,19 @@ static public T [] ArrayFromHandle (NativeHandle handle) where T : class, INa return ret; } + static Array ArrayFromHandle (NativeHandle handle, Type elementType) + { + if (handle == NativeHandle.Zero) + return null; + + var c = (int) GetCount (handle); + var rv = Array.CreateInstance (elementType, c); + for (int i = 0; i < c; i++) { + rv.SetValue (UnsafeGetItem (handle, (nuint) i, elementType), i); + } + return rv; + } + static public T [] EnumsFromHandle (NativeHandle handle) where T : struct, IConvertible { if (handle == NativeHandle.Zero) @@ -395,6 +408,18 @@ static T UnsafeGetItem (NativeHandle handle, nuint index) where T : class, IN return Runtime.GetINativeObject (val, false); } + static object UnsafeGetItem (NativeHandle handle, nuint index, Type type) + { + var val = GetAtIndex (handle, index); + // A native code could return NSArray with NSNull.Null elements + // and they should be valid for things like T : NSDate so we handle + // them as just null values inside the array + if (val == NSNull.Null.Handle) + return null; + + return Runtime.GetINativeObject (val, false, type); + } + // can return an INativeObject or an NSObject public T GetItem (nuint index) where T : class, INativeObject { diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs index fd6d4905521c..f92c0492a2a5 100644 --- a/src/Foundation/NSObject2.cs +++ b/src/Foundation/NSObject2.cs @@ -231,6 +231,14 @@ public void Dispose () GC.SuppressFinalize (this); } + static T AllocateNSObject (NativeHandle handle, Flags flags) where T : NSObject + { + var obj = (T) RuntimeHelpers.GetUninitializedObject (typeof (T)); + obj.handle = handle; + obj.flags = flags; + return obj; + } + internal static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, Flags flags) { // This function is called from native code before any constructors have executed. diff --git a/src/Makefile b/src/Makefile index c33b676f42f5..5d99659a0997 100644 --- a/src/Makefile +++ b/src/Makefile @@ -54,6 +54,7 @@ DOTNET_REFERENCES = \ /r:$(DOTNET_BCL_DIR)/System.Console.dll \ /r:$(DOTNET_BCL_DIR)/System.Diagnostics.Debug.dll \ /r:$(DOTNET_BCL_DIR)/System.Diagnostics.Tools.dll \ + /r:$(DOTNET_BCL_DIR)/System.Diagnostics.StackTrace.dll \ /r:$(DOTNET_BCL_DIR)/System.Drawing.Primitives.dll \ /r:$(DOTNET_BCL_DIR)/System.IO.Compression.dll \ /r:$(DOTNET_BCL_DIR)/System.IO.FileSystem.dll \ diff --git a/src/ObjCRuntime/BindAs.cs b/src/ObjCRuntime/BindAs.cs new file mode 100644 index 000000000000..5b0ead94517b --- /dev/null +++ b/src/ObjCRuntime/BindAs.cs @@ -0,0 +1,253 @@ +// +// BindAs.cs: Helper code for BindAs support. +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2023 Microsoft Corp + +#if NET + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using CoreFoundation; +using CoreGraphics; +using Foundation; + +using Registrar; + +namespace ObjCRuntime { + static class BindAs { + // xamarin_convert_nsarray_to_managed_with_func + static T Identity (T obj) + { + return obj; + } + + internal unsafe static T[]? ConvertNSArrayToManagedArray (IntPtr nsarray, delegate* convert) where T: struct + { + if (nsarray == IntPtr.Zero) + return null; + + return ConvertNSArrayToManagedArray2 (nsarray, convert, &Identity); + } + + internal unsafe static IntPtr ConvertManagedArrayToNSArray (T[]? array, delegate* convert) where T: struct + { + if (array is null) + return IntPtr.Zero; + + return ConvertManagedArrayToNSArray2 (array, &Identity, convert); + } + + internal unsafe static T[]? ConvertNSArrayToManagedArray2 (IntPtr nsarray, delegate* convert1, delegate* convert2) where T: struct + { + if (nsarray == IntPtr.Zero) + return null; + + return NSArray.ArrayFromHandleFunc (nsarray, ( ptr) => convert2 (convert1 (ptr))); + } + + internal unsafe static IntPtr ConvertManagedArrayToNSArray2 (T[]? array, delegate* convert1, delegate* convert2) where T: struct + { + if (array is null) + return IntPtr.Zero; + + NSArray arr; + var count = array.Length; + if (count == 0) { + arr = new NSArray (); + } else { + var ptrs = new IntPtr [count]; + for (nint i = 0; i < count; i++) { + var item = convert2 (convert1 (array [i])); + if (item == IntPtr.Zero) + item = NSNull.Null.Handle; + ptrs [i] = item; + } + fixed (void* ptr = &ptrs[0]) { + arr = Runtime.GetNSObject (NSArray.FromObjects ((IntPtr) ptr, count))!; + } + } + + arr.DangerousRetain (); + arr.DangerousAutorelease (); + var rv = arr.Handle; + arr.Dispose (); + return rv; + } + + internal unsafe static T? CreateNullable (IntPtr handle, delegate* convert) where T: struct + { + if (handle == IntPtr.Zero) + return null; + return convert (handle); + } + + internal unsafe static T? CreateNullable2 (IntPtr handle, delegate* convert1, delegate* convert2) where T: struct + { + if (handle == IntPtr.Zero) + return null; + return convert2 (convert1 (handle)); + } + + // FIXME: create helper functions that don't need a managed wrapper. + internal static Foundation.NSRange xamarin_nsvalue_to_nsrange (IntPtr value) { if (value == IntPtr.Zero) return default (Foundation.NSRange); return Runtime.GetNSObject (value)?.RangeValue ?? default (Foundation.NSRange); } + internal static CoreGraphics.CGAffineTransform xamarin_nsvalue_to_cgaffinetransform (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGAffineTransform); return Runtime.GetNSObject (value)?.CGAffineTransformValue ?? default (CoreGraphics.CGAffineTransform); } + internal static CoreGraphics.CGPoint xamarin_nsvalue_to_cgpoint (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGPoint); return Runtime.GetNSObject (value)?.CGPointValue ?? default (CoreGraphics.CGPoint); } + internal static CoreGraphics.CGRect xamarin_nsvalue_to_cgrect (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGRect); return Runtime.GetNSObject (value)?.CGRectValue ?? default (CoreGraphics.CGRect); } + internal static CoreGraphics.CGSize xamarin_nsvalue_to_cgsize (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGSize); return Runtime.GetNSObject (value)?.CGSizeValue ?? default (CoreGraphics.CGSize); } +#if !__MACOS__ + internal static CoreGraphics.CGVector xamarin_nsvalue_to_cgvector (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGVector); return Runtime.GetNSObject (value)?.CGVectorValue ?? default (CoreGraphics.CGVector); } +#endif + internal static CoreAnimation.CATransform3D xamarin_nsvalue_to_catransform3d (IntPtr value) { if (value == IntPtr.Zero) return default (CoreAnimation.CATransform3D); return Runtime.GetNSObject (value)?.CATransform3DValue ?? default (CoreAnimation.CATransform3D); } + internal static CoreLocation.CLLocationCoordinate2D xamarin_nsvalue_to_cllocationcoordinate2d (IntPtr value) { if (value == IntPtr.Zero) return default (CoreLocation.CLLocationCoordinate2D); return Runtime.GetNSObject (value)?.CoordinateValue ?? default (CoreLocation.CLLocationCoordinate2D); } + internal static CoreMedia.CMTime xamarin_nsvalue_to_cmtime (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMTime); return Runtime.GetNSObject (value)?.CMTimeValue ?? default (CoreMedia.CMTime); } + internal static CoreMedia.CMTimeMapping xamarin_nsvalue_to_cmtimemapping (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMTimeMapping); return Runtime.GetNSObject (value)?.CMTimeMappingValue ?? default (CoreMedia.CMTimeMapping); } + internal static CoreMedia.CMTimeRange xamarin_nsvalue_to_cmtimerange (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMTimeRange); return Runtime.GetNSObject (value)?.CMTimeRangeValue ?? default (CoreMedia.CMTimeRange); } + internal static CoreMedia.CMVideoDimensions xamarin_nsvalue_to_cmvideodimensions (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMVideoDimensions); return Runtime.GetNSObject (value)?.CMVideoDimensionsValue ?? default (CoreMedia.CMVideoDimensions); } + internal static MapKit.MKCoordinateSpan xamarin_nsvalue_to_mkcoordinatespan (IntPtr value) { if (value == IntPtr.Zero) return default (MapKit.MKCoordinateSpan); return Runtime.GetNSObject (value)?.CoordinateSpanValue ?? default (MapKit.MKCoordinateSpan); } + internal static SceneKit.SCNMatrix4 xamarin_nsvalue_to_scnmatrix4 (IntPtr value) { if (value == IntPtr.Zero) return default (SceneKit.SCNMatrix4); return Runtime.GetNSObject (value)?.SCNMatrix4Value ?? default (SceneKit.SCNMatrix4); } + internal static SceneKit.SCNVector3 xamarin_nsvalue_to_scnvector3 (IntPtr value) { if (value == IntPtr.Zero) return default (SceneKit.SCNVector3); return Runtime.GetNSObject (value)?.Vector3Value ?? default (SceneKit.SCNVector3); } + internal static SceneKit.SCNVector4 xamarin_nsvalue_to_scnvector4 (IntPtr value) { if (value == IntPtr.Zero) return default (SceneKit.SCNVector4); return Runtime.GetNSObject (value)?.Vector4Value ?? default (SceneKit.SCNVector4); } +#if HAS_UIKIT + internal static UIKit.UIEdgeInsets xamarin_nsvalue_to_uiedgeinsets (IntPtr value) { if (value == IntPtr.Zero) return default (UIKit.UIEdgeInsets); return Runtime.GetNSObject (value)?.UIEdgeInsetsValue ?? default (UIKit.UIEdgeInsets); } + internal static UIKit.UIOffset xamarin_nsvalue_to_uioffset (IntPtr value) { if (value == IntPtr.Zero) return default (UIKit.UIOffset); return Runtime.GetNSObject (value)?.UIOffsetValue ?? default (UIKit.UIOffset); } + internal static UIKit.NSDirectionalEdgeInsets xamarin_nsvalue_to_nsdirectionaledgeinsets (IntPtr value) { if (value == IntPtr.Zero) return default (UIKit.NSDirectionalEdgeInsets); return Runtime.GetNSObject (value)?.DirectionalEdgeInsetsValue ?? default (UIKit.NSDirectionalEdgeInsets); } +#endif + + // FIXME: create helper functions that don't need a managed wrapper. + internal static IntPtr xamarin_nsrange_to_nsvalue (Foundation.NSRange value) { using var rv = NSValue.FromRange (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cgaffinetransform_to_nsvalue (CoreGraphics.CGAffineTransform value) { using var rv = NSValue.FromCGAffineTransform (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cgpoint_to_nsvalue (CoreGraphics.CGPoint value) { using var rv = NSValue.FromCGPoint (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cgrect_to_nsvalue (CoreGraphics.CGRect value) { using var rv = NSValue.FromCGRect (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cgsize_to_nsvalue (CoreGraphics.CGSize value) { using var rv = NSValue.FromCGSize (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#if !__MACOS__ + internal static IntPtr xamarin_cgvector_to_nsvalue (CoreGraphics.CGVector value) { using var rv = NSValue.FromCGVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#endif + internal static IntPtr xamarin_catransform3d_to_nsvalue (CoreAnimation.CATransform3D value) { using var rv = NSValue.FromCATransform3D (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cllocationcoordinate2d_to_nsvalue (CoreLocation.CLLocationCoordinate2D value) { using var rv = NSValue.FromMKCoordinate (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cmtime_to_nsvalue (CoreMedia.CMTime value) { using var rv = NSValue.FromCMTime (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cmtimemapping_to_nsvalue (CoreMedia.CMTimeMapping value) { using var rv = NSValue.FromCMTimeMapping (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cmtimerange_to_nsvalue (CoreMedia.CMTimeRange value) { using var rv = NSValue.FromCMTimeRange (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_cmvideodimensions_to_nsvalue (CoreMedia.CMVideoDimensions value) { using var rv = NSValue.FromCMVideoDimensions (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_mkcoordinatespan_to_nsvalue (MapKit.MKCoordinateSpan value) { using var rv = NSValue.FromMKCoordinateSpan (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_scnmatrix4_to_nsvalue (SceneKit.SCNMatrix4 value) { using var rv = NSValue.FromSCNMatrix4 (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_scnvector3_to_nsvalue (SceneKit.SCNVector3 value) { using var rv = NSValue.FromVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_scnvector4_to_nsvalue (SceneKit.SCNVector4 value) { using var rv = NSValue.FromVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#if HAS_UIKIT + internal static IntPtr xamarin_uiedgeinsets_to_nsvalue (UIKit.UIEdgeInsets value) { using var rv = NSValue.FromUIEdgeInsets (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_uioffset_to_nsvalue (UIKit.UIOffset value) { using var rv = NSValue.FromUIOffset (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + internal static IntPtr xamarin_nsdirectionaledgeinsets_to_nsvalue (UIKit.NSDirectionalEdgeInsets value) { using var rv = NSValue.FromDirectionalEdgeInsets (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#endif + + // FIXME: create helper functions that don't need a managed wrapper. + public static System.SByte xamarin_nsnumber_to_sbyte (IntPtr value) { if (value == IntPtr.Zero) return default (System.SByte); return Runtime.GetNSObject (value)?.SByteValue ?? default (System.SByte); } + public static System.Byte xamarin_nsnumber_to_byte (IntPtr value) { if (value == IntPtr.Zero) return default (System.Byte); return Runtime.GetNSObject (value)?.ByteValue ?? default (System.Byte); } + public static System.Int16 xamarin_nsnumber_to_short (IntPtr value) { if (value == IntPtr.Zero) return default (System.Int16); return Runtime.GetNSObject (value)?.Int16Value ?? default (System.Int16); } + public static System.UInt16 xamarin_nsnumber_to_ushort (IntPtr value) { if (value == IntPtr.Zero) return default (System.UInt16); return Runtime.GetNSObject (value)?.UInt16Value ?? default (System.UInt16); } + public static System.Int32 xamarin_nsnumber_to_int (IntPtr value) { if (value == IntPtr.Zero) return default (System.Int32); return Runtime.GetNSObject (value)?.Int32Value ?? default (System.Int32); } + public static System.UInt32 xamarin_nsnumber_to_uint (IntPtr value) { if (value == IntPtr.Zero) return default (System.UInt32); return Runtime.GetNSObject (value)?.UInt32Value ?? default (System.UInt32); } + public static System.Int64 xamarin_nsnumber_to_long (IntPtr value) { if (value == IntPtr.Zero) return default (System.Int64); return Runtime.GetNSObject (value)?.Int64Value ?? default (System.Int64); } + public static System.UInt64 xamarin_nsnumber_to_ulong (IntPtr value) { if (value == IntPtr.Zero) return default (System.UInt64); return Runtime.GetNSObject (value)?.UInt64Value ?? default (System.UInt64); } + public static nint xamarin_nsnumber_to_nint (IntPtr value) { if (value == IntPtr.Zero) return default (nint); return Runtime.GetNSObject (value)?.NIntValue ?? default (nint); } + public static nuint xamarin_nsnumber_to_nuint (IntPtr value) { if (value == IntPtr.Zero) return default (nuint); return Runtime.GetNSObject (value)?.NUIntValue ?? default (nuint); } + public static System.Single xamarin_nsnumber_to_float (IntPtr value) { if (value == IntPtr.Zero) return default (System.Single); return Runtime.GetNSObject (value)?.FloatValue ?? default (System.Single); } + public static System.Double xamarin_nsnumber_to_double (IntPtr value) { if (value == IntPtr.Zero) return default (System.Double); return Runtime.GetNSObject (value)?.DoubleValue ?? default (System.Double); } + public static System.Boolean xamarin_nsnumber_to_bool (IntPtr value) { if (value == IntPtr.Zero) return default (System.Boolean); return Runtime.GetNSObject (value)?.BoolValue ?? default (System.Boolean); } + public static nfloat xamarin_nsnumber_to_nfloat (IntPtr value) + { + if (value == IntPtr.Zero) + return default (nfloat); + var number = Runtime.GetNSObject (value); + if (number is null) + return default (nfloat); + if (IntPtr.Size == 4) + return (nfloat) number.FloatValue; + return (nfloat) number.DoubleValue; + } + + // FIXME: create helper functions that don't need a managed wrapper. + public static System.SByte? xamarin_nsnumber_to_nullable_sbyte (IntPtr value) { return Runtime.GetNSObject (value)?.SByteValue ?? null; } + public static System.Byte? xamarin_nsnumber_to_nullable_byte (IntPtr value) { return Runtime.GetNSObject (value)?.ByteValue ?? null; } + public static System.Int16? xamarin_nsnumber_to_nullable_short (IntPtr value) { return Runtime.GetNSObject (value)?.Int16Value ?? null; } + public static System.UInt16? xamarin_nsnumber_to_nullable_ushort (IntPtr value) { return Runtime.GetNSObject (value)?.UInt16Value ?? null; } + public static System.Int32? xamarin_nsnumber_to_nullable_int (IntPtr value) { return Runtime.GetNSObject (value)?.Int32Value ?? null; } + public static System.UInt32? xamarin_nsnumber_to_nullable_uint (IntPtr value) { return Runtime.GetNSObject (value)?.UInt32Value ?? null; } + public static System.Int64? xamarin_nsnumber_to_nullable_long (IntPtr value) { return Runtime.GetNSObject (value)?.Int64Value ?? null; } + public static System.UInt64? xamarin_nsnumber_to_nullable_ulong (IntPtr value) { return Runtime.GetNSObject (value)?.UInt64Value ?? null; } + public static nint? xamarin_nsnumber_to_nullable_nint (IntPtr value) { return Runtime.GetNSObject (value)?.NIntValue ?? null; } + public static nuint? xamarin_nsnumber_to_nullable_nuint (IntPtr value) { return Runtime.GetNSObject (value)?.NUIntValue ?? null; } + public static System.Single? xamarin_nsnumber_to_nullable_float (IntPtr value) { return Runtime.GetNSObject (value)?.FloatValue ?? null; } + public static System.Double? xamarin_nsnumber_to_nullable_double (IntPtr value) { return Runtime.GetNSObject (value)?.DoubleValue ?? null; } + public static System.Boolean? xamarin_nsnumber_to_nullable_bool (IntPtr value) { return Runtime.GetNSObject (value)?.BoolValue ?? null; } + public static nfloat? xamarin_nsnumber_to_nullable_nfloat (IntPtr value) + { + if (value == IntPtr.Zero) + return null; + var number = Runtime.GetNSObject (value); + if (number is null) + return null; + if (IntPtr.Size == 4) + return (nfloat) number.FloatValue; + return (nfloat) number.DoubleValue; + } + + // FIXME: create helper functions that don't need a managed wrapper. + public static IntPtr xamarin_sbyte_to_nsnumber (System.SByte value) { return NSNumber.FromSByte (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_byte_to_nsnumber (System.Byte value) { return NSNumber.FromByte (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_short_to_nsnumber (System.Int16 value) { return NSNumber.FromInt16 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_ushort_to_nsnumber (System.UInt16 value) { return NSNumber.FromUInt16 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_int_to_nsnumber (System.Int32 value) { return NSNumber.FromInt32 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_uint_to_nsnumber (System.UInt32 value) { return NSNumber.FromUInt32 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_long_to_nsnumber (System.Int64 value) { return NSNumber.FromInt64 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_ulong_to_nsnumber (System.UInt64 value) { return NSNumber.FromUInt64 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nint_to_nsnumber (nint value) { return NSNumber.FromNInt (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nuint_to_nsnumber (nuint value) { return NSNumber.FromNUInt (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_float_to_nsnumber (System.Single value) { return NSNumber.FromFloat (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_double_to_nsnumber (System.Double value) { return NSNumber.FromDouble (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_bool_to_nsnumber (System.Boolean value) { return NSNumber.FromBoolean (value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nfloat_to_nsnumber (nfloat value) + { + if (IntPtr.Size == 4) + return NSNumber.FromFloat ((float) value).DangerousRetain ().DangerousAutorelease ().Handle; + return NSNumber.FromDouble ((double) value).DangerousRetain ().DangerousAutorelease ().Handle; + } + + // FIXME: create helper functions that don't need a managed wrapper. + public static IntPtr xamarin_nullable_sbyte_to_nsnumber (System.SByte? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromSByte (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_byte_to_nsnumber (System.Byte? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromByte (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_short_to_nsnumber (System.Int16? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt16 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_ushort_to_nsnumber (System.UInt16? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt16 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_int_to_nsnumber (System.Int32? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt32 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_uint_to_nsnumber (System.UInt32? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt32 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_long_to_nsnumber (System.Int64? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt64 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_ulong_to_nsnumber (System.UInt64? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt64 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_nint_to_nsnumber (nint? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromNInt (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_nuint_to_nsnumber (nuint? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromNUInt (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_float_to_nsnumber (System.Single? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromFloat (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_double_to_nsnumber (System.Double? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromDouble (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_bool_to_nsnumber (System.Boolean? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromBoolean (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + public static IntPtr xamarin_nullable_nfloat_to_nsnumber (nfloat? value) + { + if (!value.HasValue) + return IntPtr.Zero; + if (IntPtr.Size == 4) + return NSNumber.FromFloat ((float) value.Value).DangerousRetain ().DangerousAutorelease ().Handle; + return NSNumber.FromDouble ((double) value.Value).DangerousRetain ().DangerousAutorelease ().Handle; + } + } +} + +#endif // NET diff --git a/src/ObjCRuntime/Blocks.cs b/src/ObjCRuntime/Blocks.cs index d6c017945127..37dc81e9d34d 100644 --- a/src/ObjCRuntime/Blocks.cs +++ b/src/ObjCRuntime/Blocks.cs @@ -456,6 +456,33 @@ unsafe static IntPtr GetBlockForFunctionPointer (MethodInfo delegateInvokeMethod } #endif // NET + [EditorBrowsable (EditorBrowsableState.Never)] + [BindingImpl (BindingImplOptions.Optimizable)] + static IntPtr CreateBlockForDelegate (Delegate @delegate, Delegate delegateProxyFieldValue, string /*?*/ signature) + { + if (@delegate is null) + ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (@delegate)); + + if (delegateProxyFieldValue is null) + ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (delegateProxyFieldValue)); + + // Note that we must create a heap-allocated block, so we + // start off by creating a stack-allocated block, and then + // call _Block_copy, which will create a heap-allocated block + // with the proper reference count. + using var block = new BlockLiteral (); + if (signature is null) { + if (Runtime.DynamicRegistrationSupported) { + block.SetupBlock (delegateProxyFieldValue, @delegate); + } else { + throw ErrorHelper.CreateError (8026, $"BlockLiteral.GetBlockForDelegate with a null signature is not supported when the dynamic registrar has been linked away (delegate type: {@delegate.GetType ().FullName})."); + } + } else { + block.SetupBlockImpl (delegateProxyFieldValue, @delegate, true, signature); + } + return _Block_copy (&block); + } + [BindingImpl (BindingImplOptions.Optimizable)] internal static IntPtr GetBlockForDelegate (MethodInfo minfo, object @delegate, uint token_ref, string signature) { diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index 40742d0be3d0..6949ad390cf6 100644 --- a/src/ObjCRuntime/Class.cs +++ b/src/ObjCRuntime/Class.cs @@ -399,7 +399,7 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int var assembly = ResolveAssembly (assembly_name); var module = ResolveModule (assembly, module_token); - return ResolveToken (module, token); + return ResolveToken (assembly, module, token); } internal static Type? ResolveTypeTokenReference (uint token_reference) @@ -442,21 +442,34 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int var assembly = ResolveAssembly (assembly_name); var module = ResolveModule (assembly, 0x1); - return ResolveToken (module, token | implicit_token_type); + return ResolveToken (assembly, module, token | implicit_token_type); } - static MemberInfo? ResolveToken (Module module, uint token) + static MemberInfo? ResolveToken (Assembly assembly, Module? module, uint token) { // Finally resolve the token. var token_type = token & 0xFF000000; switch (token & 0xFF000000) { case 0x02000000: // TypeDef - var type = module.ResolveType ((int) token); + Type type; + if (Runtime.IsManagedStaticRegistrar) { + type = RegistrarHelper.LookupRegisteredType (assembly, token & 0x00FFFFFF); + } else if (module is null) { + throw ErrorHelper.CreateError (9999, $"Could not find the module in the assembly {assembly}."); + } else { + type = module.ResolveType ((int) token); + } #if LOG_TYPELOAD Console.WriteLine ($"ResolveToken (0x{token:X}) => Type: {type.FullName}"); #endif return type; case 0x06000000: // Method + if (Runtime.IsManagedStaticRegistrar) + throw ErrorHelper.CreateError (9999, "Unable to resolve the metadata token 0x{0} when using the managed static registrar", token.ToString ("x")); // FIXME: error number + + if (module is null) + throw ErrorHelper.CreateError (9999, $"Could not find the module in the assembly {assembly}."); + var method = module.ResolveMethod ((int) token); #if LOG_TYPELOAD Console.WriteLine ($"ResolveToken (0x{token:X}) => Method: {method?.DeclaringType?.FullName}.{method.Name}"); @@ -467,8 +480,11 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int } } - static Module ResolveModule (Assembly assembly, uint token) + static Module? ResolveModule (Assembly assembly, uint token) { + if (token == Runtime.INVALID_TOKEN_REF) + return null; + foreach (var mod in assembly.GetModules ()) { if (mod.MetadataToken != token) continue; diff --git a/src/ObjCRuntime/DynamicRegistrar.cs b/src/ObjCRuntime/DynamicRegistrar.cs index a0891dab8289..ae4b2c34d955 100644 --- a/src/ObjCRuntime/DynamicRegistrar.cs +++ b/src/ObjCRuntime/DynamicRegistrar.cs @@ -219,12 +219,12 @@ protected override IEnumerable CollectTypes (Assembly assembly) return assembly.GetTypes (); } - protected override BindAsAttribute GetBindAsAttribute (PropertyInfo property) + public override BindAsAttribute GetBindAsAttribute (PropertyInfo property) { return property?.GetCustomAttribute (false); } - protected override BindAsAttribute GetBindAsAttribute (MethodBase method, int parameter_index) + public override BindAsAttribute GetBindAsAttribute (MethodBase method, int parameter_index) { ICustomAttributeProvider provider; @@ -343,7 +343,7 @@ protected override Type MakeByRef (Type type) return type.MakeByRefType (); } - protected override CategoryAttribute GetCategoryAttribute (Type type) + public override CategoryAttribute GetCategoryAttribute (Type type) { return SharedDynamic.GetOneAttribute (type); } @@ -374,7 +374,7 @@ protected override MethodBase GetBaseMethod (MethodBase method) return ((MethodInfo) method).GetBaseDefinition (); } - protected override Type GetElementType (Type type) + public override Type GetElementType (Type type) { return type.GetElementType (); } @@ -460,7 +460,7 @@ protected override string GetTypeFullName (Type type) return type.FullName; } - protected override bool VerifyIsConstrainedToNSObject (Type type, out Type constrained_type) + public override bool VerifyIsConstrainedToNSObject (Type type, out Type constrained_type) { constrained_type = null; @@ -523,7 +523,7 @@ protected override string GetAssemblyQualifiedName (Type type) return type.AssemblyQualifiedName; } - protected override bool HasReleaseAttribute (MethodBase method) + public override bool HasReleaseAttribute (MethodBase method) { var mi = method as MethodInfo; if (mi == null) @@ -539,7 +539,7 @@ public static bool HasThisAttributeImpl (MethodBase method) return mi.IsDefined (typeof (System.Runtime.CompilerServices.ExtensionAttribute), false); } - protected override bool HasThisAttribute (MethodBase method) + public override bool HasThisAttribute (MethodBase method) { return HasThisAttributeImpl (method); } @@ -554,7 +554,7 @@ protected override bool HasModelAttribute (Type type) return type.IsDefined (typeof (ModelAttribute), false); } - protected override bool IsArray (Type type, out int rank) + public override bool IsArray (Type type, out int rank) { if (!type.IsArray) { rank = 0; @@ -594,7 +594,7 @@ protected override bool IsDelegate (Type type) return type.IsSubclassOf (typeof (System.Delegate)); } - protected override bool IsNullable (Type type) + public override bool IsNullable (Type type) { if (!type.IsGenericType) return false; diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index 4f839a737af3..475d24a76ec7 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -1094,9 +1094,9 @@ protected virtual void OnRegisterCategory (ObjCType type, ref List ex protected abstract bool IsStatic (TField field); protected abstract bool IsStatic (TMethod method); protected abstract TType MakeByRef (TType type); - protected abstract bool HasThisAttribute (TMethod method); + public abstract bool HasThisAttribute (TMethod method); protected abstract bool IsConstructor (TMethod method); - protected abstract TType GetElementType (TType type); + public abstract TType GetElementType (TType type); protected abstract TType GetReturnType (TMethod method); protected abstract void GetNamespaceAndName (TType type, out string @namespace, out string name); protected abstract bool TryGetAttribute (TType type, string attributeNamespace, string attributeType, out object attribute); @@ -1104,23 +1104,23 @@ protected virtual void OnRegisterCategory (ObjCType type, ref List ex protected abstract ExportAttribute GetExportAttribute (TMethod method); // Return null if no attribute is found. Must check the base method (i.e. if method is overriding a method in a base class, must check the overridden method for the attribute). protected abstract Dictionary> PrepareMethodMapping (TType type); public abstract RegisterAttribute GetRegisterAttribute (TType type); // Return null if no attribute is found. Do not consider base types. - protected abstract CategoryAttribute GetCategoryAttribute (TType type); // Return null if no attribute is found. Do not consider base types. + public abstract CategoryAttribute GetCategoryAttribute (TType type); // Return null if no attribute is found. Do not consider base types. protected abstract ConnectAttribute GetConnectAttribute (TProperty property); // Return null if no attribute is found. Do not consider inherited properties. public abstract ProtocolAttribute GetProtocolAttribute (TType type); // Return null if no attribute is found. Do not consider base types. protected abstract IEnumerable GetProtocolMemberAttributes (TType type); // Return null if no attributes found. Do not consider base types. protected virtual Version GetSdkIntroducedVersion (TType obj, out string message) { message = null; return null; } // returns the sdk version when the type was introduced for the current platform (null if all supported versions) protected abstract Version GetSDKVersion (); protected abstract TType GetProtocolAttributeWrapperType (TType type); // Return null if no attribute is found. Do not consider base types. - protected abstract BindAsAttribute GetBindAsAttribute (TMethod method, int parameter_index); // If parameter_index = -1 then get the attribute for the return type. Return null if no attribute is found. Must consider base method. - protected abstract BindAsAttribute GetBindAsAttribute (TProperty property); + public abstract BindAsAttribute GetBindAsAttribute (TMethod method, int parameter_index); // If parameter_index = -1 then get the attribute for the return type. Return null if no attribute is found. Must consider base method. + public abstract BindAsAttribute GetBindAsAttribute (TProperty property); protected abstract IList GetAdoptsAttributes (TType type); public abstract TType GetNullableType (TType type); // For T? returns T. For T returns null. - protected abstract bool HasReleaseAttribute (TMethod method); // Returns true of the method's return type/value has a [Release] attribute. + public abstract bool HasReleaseAttribute (TMethod method); // Returns true of the method's return type/value has a [Release] attribute. protected abstract bool IsINativeObject (TType type); protected abstract bool IsValueType (TType type); - protected abstract bool IsArray (TType type, out int rank); + public abstract bool IsArray (TType type, out int rank); protected abstract bool IsEnum (TType type, out bool isNativeEnum); - protected abstract bool IsNullable (TType type); + public abstract bool IsNullable (TType type); protected abstract bool IsDelegate (TType type); protected abstract bool IsGenericType (TType type); protected abstract bool IsGenericMethod (TMethod method); @@ -1128,7 +1128,7 @@ protected virtual void OnRegisterCategory (ObjCType type, ref List ex protected abstract bool IsAbstract (TType type); protected abstract bool IsPointer (TType type); protected abstract TType GetGenericTypeDefinition (TType type); - protected abstract bool VerifyIsConstrainedToNSObject (TType type, out TType constrained_type); + public abstract bool VerifyIsConstrainedToNSObject (TType type, out TType constrained_type); protected abstract TType GetEnumUnderlyingType (TType type); protected abstract IEnumerable GetFields (TType type); // Must return all instance fields. May return static fields (they are filtered out automatically). protected abstract TType GetFieldType (TField field); @@ -1160,7 +1160,25 @@ public Registrar () { } - protected bool IsArray (TType type) + public bool IsPropertyAccessor (TMethod method, out TProperty property) + { + property = null; + + if (method is null) + return false; + + if (!method.IsSpecialName) + return false; + + var name = method.Name; + if (!name.StartsWith ("get_", StringComparison.Ordinal) && !name.StartsWith ("set_", StringComparison.Ordinal)) + return false; + + property = FindProperty (method.DeclaringType, name.Substring (4)); + return property is not null; + } + + public bool IsArray (TType type) { int rank; return IsArray (type, out rank); diff --git a/src/ObjCRuntime/RegistrarHelper.cs b/src/ObjCRuntime/RegistrarHelper.cs new file mode 100644 index 000000000000..2f2bbf7df202 --- /dev/null +++ b/src/ObjCRuntime/RegistrarHelper.cs @@ -0,0 +1,389 @@ +// +// BindAs.cs: Helper code for BindAs support. +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2023 Microsoft Corp + + +// #define TRACE + +#if NET + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using CoreFoundation; +using CoreGraphics; +using Foundation; + +using Registrar; + +namespace ObjCRuntime { + // The managed static registrar will make this interface public when needed. + interface IManagedRegistrar { + IntPtr LookupUnmanagedFunction (string? symbol, int id); + RuntimeTypeHandle LookupType (uint id); + void RegisterWrapperTypes (Dictionary type); + } + + class MapInfo { + public IManagedRegistrar Registrar; + public bool RegisteredWrapperTypes; + + public MapInfo (IManagedRegistrar registrar) + { + Registrar = registrar; + } + } + + // This class contains helper methods for the managed static registrar. + // The managed static registrar will make it public when needed. + static class RegistrarHelper { +#pragma warning disable 8618 + static Dictionary assembly_map; + static Dictionary wrapper_types; +#pragma warning restore 8618 + + + internal static void Initialize () + { + assembly_map = new Dictionary (Runtime.StringEqualityComparer); + wrapper_types = new Dictionary (Runtime.RuntimeTypeHandleEqualityComparer); + } + + unsafe static IntPtr GetBlockPointer (BlockLiteral block) + { + var rv = BlockLiteral._Block_copy (&block); + block.Dispose (); + return rv; + } + + static IntPtr GetBlockForDelegate (object @delegate, RuntimeMethodHandle method_handle) + { + var method = (MethodInfo) MethodBase.GetMethodFromHandle (method_handle)!; + return BlockLiteral.GetBlockForDelegate (method, @delegate, Runtime.INVALID_TOKEN_REF, null); + } + + + static MapInfo GetMapEntry (Assembly assembly) + { + return GetMapEntry (assembly.GetName ().Name!, assembly); + } + + static MapInfo GetMapEntry (IntPtr assembly) + { + return GetMapEntry (Marshal.PtrToStringAuto (assembly)!, null); + } + + static MapInfo GetMapEntry (string assemblyName, Assembly? assembly) + { + lock (assembly_map) { + if (!assembly_map.TryGetValue (assemblyName, out var mapEntry)) { + if (assembly is null) + assembly = GetAssembly (assemblyName); + var type = assembly.GetType ("ObjCRuntime.__Registrar__", false); + if (type is null) + throw ErrorHelper.CreateError (99, "Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'", assembly); + + var registrar = (IManagedRegistrar) Activator.CreateInstance (type)!; + mapEntry = new MapInfo (registrar); + assembly_map [assemblyName] = mapEntry; + } + return mapEntry; + } + } + + static Assembly GetAssembly (string assemblyName) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies ()) { + if (assembly.GetName ().Name == assemblyName) + return assembly; + } + + throw ErrorHelper.CreateError (99, "Could not find the assembly '{0}' in the current AppDomain", assemblyName); + } + + internal static Type? FindProtocolWrapperType (Type type) + { + var typeHandle = type.TypeHandle; + + lock (assembly_map) { + // First check if the type is already in our dictionary. + if (wrapper_types.TryGetValue (typeHandle, out var wrapperType)) + return Type.GetTypeFromHandle (wrapperType); + + // Not in our dictionary, get the map entry to see if we've already + // called RegisterWrapperTypes for this assembly, + var entry = GetMapEntry (type.Assembly); + if (!entry.RegisteredWrapperTypes) { + entry.Registrar.RegisterWrapperTypes (wrapper_types); + entry.RegisteredWrapperTypes = true; + } + + // Return whatever's in the dictionary now. + if (wrapper_types.TryGetValue (typeHandle, out wrapperType)) + return Type.GetTypeFromHandle (wrapperType); + } + + return null; + } + +#if TRACE + [ThreadStatic] + static Stopwatch? lookupWatch; +#endif + + internal static IntPtr LookupUnmanagedFunction (IntPtr assembly, string? symbol, int id) + { + IntPtr rv; + +#if TRACE + if (lookupWatch is null) + lookupWatch = new Stopwatch (); + + lookupWatch.Start (); + Console.WriteLine ("LookupUnmanagedFunction (0x{0} = {1}, 0x{2} = {3}, {4})", assembly.ToString ("x"), Marshal.PtrToStringAuto (assembly), symbol, symb, id); +#endif + + if (id == -1) { + rv = IntPtr.Zero; + } else if (assembly != IntPtr.Zero) { + rv = LookupUnmanagedFunctionInAssembly (assembly, symbol, id); + } else { + rv = LookupManagedFunctionImpl (id); + } + +#if TRACE + lookupWatch.Stop (); + + Console.WriteLine ("LookupUnmanagedFunction (0x{0} = {1}, {2}, {3}) => 0x{4} ElapsedMilliseconds: {5}", assembly.ToString ("x"), Marshal.PtrToStringAuto (assembly), symbol, id, rv.ToString ("x"), lookupWatch.ElapsedMilliseconds); +#endif + + if (rv != IntPtr.Zero) + return rv; + + throw ErrorHelper.CreateError (8001, "Unable to find the managed function with id {0} ({1})", id, symbol); + } + + static IntPtr LookupUnmanagedFunctionInAssembly (IntPtr assembly_name, string? symbol, int id) + { + var entry = GetMapEntry (assembly_name); + return entry.Registrar.LookupUnmanagedFunction (symbol, id); + } + + static IntPtr LookupManagedFunctionImpl (int id) + { + // The static registrar will modify this function as needed. + return IntPtr.Zero; + } + + internal static Type LookupRegisteredType (Assembly assembly, uint id) + { + var entry = GetMapEntry (assembly); + var handle = entry.Registrar.LookupType (id); + return Type.GetTypeFromHandle (handle)!; + } + + // helper functions for converting between native and managed objects + static NativeHandle ManagedArrayToNSArray (object array, bool retain) + { + if (array is null) + return NativeHandle.Zero; + + NSObject rv; + if (array is NSObject[] nsobjs) { + rv = NSArray.FromNSObjects (nsobjs); + } else if (array is INativeObject[] inativeobjs) { + rv = NSArray.FromNSObjects (inativeobjs); + } else { + throw new InvalidOperationException ($"Can't convert {array.GetType ()} to an NSArray."); // FIXME: better error + } + + if (retain) + return Runtime.RetainNSObject (rv); + return Runtime.RetainAndAutoreleaseNSObject (rv); + } + + unsafe static void NSArray_string_native_to_managed (IntPtr* ptr, ref string[]? value, ref string[]? copy) + { + if (ptr is not null) { + value = NSArray.StringArrayFromHandle (*ptr); + } else { + value = null; + } + copy = value; + } + + unsafe static void NSArray_string_managed_to_native (IntPtr* ptr, string[] value, string[] copy, bool isOut) + { + if (ptr is null) + return; + + // Note that we won't notice if individual array elements change, only if the array itself changes + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"NSArray_string_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + if (value is null) { +#if TRACE + Runtime.NSLog ($"NSArray_string_managed_to_native (0x{(*ptr).ToString ("x")}, null, !null)"); +#endif + *ptr = IntPtr.Zero; + return; + } + IntPtr rv = Runtime.RetainAndAutoreleaseNSObject (NSArray.FromStrings (value)); +#if TRACE + Runtime.NSLog ($"NSArray_string_managed_to_native (0x{(*ptr).ToString ("x")}, value != copy: {value?.Length} != {copy?.Length}): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + + unsafe static void NSArray_native_to_managed (IntPtr* ptr, ref T[]? value, ref T[]? copy) where T: class, INativeObject + { + if (ptr is not null) { + value = NSArray.ArrayFromHandle (*ptr); + } else { + value = null; + } + copy = value; + } + + unsafe static void NSArray_managed_to_native (IntPtr* ptr, T[] value, T[] copy, bool isOut) where T: class, INativeObject + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + // Note that we won't notice if individual array elements change, only if the array itself changes + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + if (value is null) { +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (0x{(*ptr).ToString ("x")}, null, !null)"); +#endif + *ptr = IntPtr.Zero; + return; + } + IntPtr rv = Runtime.RetainAndAutoreleaseNSObject (NSArray.FromNSObjects (value)); +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (0x{(*ptr).ToString ("x")}, value != copy: {value?.Length} != {copy?.Length}): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + + unsafe static void NSObject_native_to_managed (IntPtr* ptr, ref T? value, ref T? copy) where T: NSObject + { + if (ptr is not null) { + value = Runtime.GetNSObject (*ptr, owns: false); + } else { + value = null; + } + copy = value; + } + + unsafe static void NSObject_managed_to_native (IntPtr* ptr, NSObject value, NSObject copy, bool isOut) + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"NSObject_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"NSObject_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + IntPtr rv = Runtime.RetainAndAutoreleaseNSObject (value); +#if TRACE + Runtime.NSLog ($"NSObject_managed_to_native (0x{(*ptr).ToString ("x")}, ? != ?): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + + unsafe static void string_native_to_managed (NativeHandle *ptr, ref string? value, ref string? copy) + { + if (ptr is not null) { + value = CFString.FromHandle (*ptr); + } else { + value = null; + } + copy = value; + } + + unsafe static void string_managed_to_native (NativeHandle *ptr, string value, string copy, bool isOut) + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"string_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"string_managed_to_native (0x{(*ptr).ToString ()}, equal)"); +#endif + return; + } + var rv = CFString.CreateNative (value); +#if TRACE + Runtime.NSLog ($"string_managed_to_native (0x{(*ptr).ToString ()}, ? != ?): 0x{rv.ToString ()} => {value}"); +#endif + *ptr = rv; + } + + unsafe static void INativeObject_native_to_managed (IntPtr* ptr, ref T? value, ref T? copy, RuntimeTypeHandle implementationType) where T: class, INativeObject + { + if (ptr is not null) { + value = Runtime.GetINativeObject (*ptr, implementation: Type.GetTypeFromHandle (implementationType), forced_type: false, owns: false); + } else { + value = null; + } + copy = value; + } + + unsafe static void INativeObject_managed_to_native (IntPtr *ptr, INativeObject value, INativeObject copy, bool isOut) + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"INativeObject_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"INativeObject_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + IntPtr rv = value.GetHandle (); +#if TRACE + Runtime.NSLog ($"INativeObject_managed_to_native (0x{(*ptr).ToString ("x")}, ? != ?): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + } +} + +#endif // NET diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index cad3b6296d2e..83f3045cbc13 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -48,6 +48,8 @@ public partial class Runtime { internal static IntPtrEqualityComparer IntPtrEqualityComparer; internal static TypeEqualityComparer TypeEqualityComparer; + internal static StringEqualityComparer StringEqualityComparer; + internal static RuntimeTypeHandleEqualityComparer RuntimeTypeHandleEqualityComparer; internal static DynamicRegistrar Registrar; #pragma warning restore 8618 @@ -300,6 +302,8 @@ unsafe static void Initialize (InitializationOptions* options) IntPtrEqualityComparer = new IntPtrEqualityComparer (); TypeEqualityComparer = new TypeEqualityComparer (); + StringEqualityComparer = new StringEqualityComparer (); + RuntimeTypeHandleEqualityComparer = new RuntimeTypeHandleEqualityComparer (); Runtime.options = options; delegates = new List (); @@ -316,6 +320,8 @@ unsafe static void Initialize (InitializationOptions* options) Registrar = new DynamicRegistrar (); protocol_cache = new Dictionary> (IntPtrEqualityComparer); } + if (IsManagedStaticRegistrar) + RegistrarHelper.Initialize (); RegisterDelegates (options); Class.Initialize (options); #if !NET @@ -743,7 +749,12 @@ static void GetMethodForSelector (IntPtr cls, IntPtr sel, sbyte is_static, IntPt Registrar.GetMethodDescription (Class.Lookup (cls), sel, is_static != 0, desc); } - static sbyte HasNSObject (IntPtr ptr) + internal static bool HasNSObject (NativeHandle ptr) + { + return TryGetNSObject (ptr, evenInFinalizerQueue: false) is not null; + } + + internal static sbyte HasNSObject (IntPtr ptr) { var rv = TryGetNSObject (ptr, evenInFinalizerQueue: false) is not null; return (sbyte) (rv ? 1 : 0); @@ -775,10 +786,15 @@ static unsafe IntPtr GetGenericMethodFromToken (IntPtr obj, uint token_ref) return IntPtr.Zero; var nsobj = GetGCHandleTarget (obj) as NSObject; + return AllocGCHandle (FindClosedMethodForObject (nsobj, method)); + } + + static MethodInfo FindClosedMethodForObject (NSObject? nsobj, MethodBase method) + { if (nsobj is null) - throw ErrorHelper.CreateError (8023, $"An instance object is required to construct a closed generic method for the open generic method: {method.DeclaringType!.FullName}.{method.Name} (token reference: 0x{token_ref:X}). {Constants.PleaseFileBugReport}"); + throw ErrorHelper.CreateError (8023, $"An instance object is required to construct a closed generic method for the open generic method: {method.DeclaringType!.FullName}.{method.Name}. {Constants.PleaseFileBugReport}"); - return AllocGCHandle (FindClosedMethod (nsobj.GetType (), method)); + return FindClosedMethod (nsobj.GetType (), method); } static IntPtr TryGetOrConstructNSObjectWrapped (IntPtr ptr) @@ -1485,6 +1501,13 @@ static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, NSObject.Flag return null; } +#if NET + public static NSObject? GetNSObject (NativeHandle ptr) + { + return GetNSObject ((IntPtr) ptr, MissingCtorResolution.ThrowConstructor1NotFound); + } +#endif + public static NSObject? GetNSObject (IntPtr ptr) { return GetNSObject (ptr, MissingCtorResolution.ThrowConstructor1NotFound); @@ -1816,14 +1839,20 @@ static void TryReleaseINativeObject (INativeObject? obj) return null; // Check if the static registrar knows about this protocol - unsafe { - var map = options->RegistrationMap; - if (map is not null) { - var token = Class.GetTokenReference (type, throw_exception: false); - if (token != INVALID_TOKEN_REF) { - var wrapper_token = xamarin_find_protocol_wrapper_type (token); - if (wrapper_token != INVALID_TOKEN_REF) - return Class.ResolveTypeTokenReference (wrapper_token); + if (IsManagedStaticRegistrar) { + var rv = RegistrarHelper.FindProtocolWrapperType (type); + if (rv is not null) + return rv; + } else { + unsafe { + var map = options->RegistrationMap; + if (map is not null) { + var token = Class.GetTokenReference (type, throw_exception: false); + if (token != INVALID_TOKEN_REF) { + var wrapper_token = xamarin_find_protocol_wrapper_type (token); + if (wrapper_token != INVALID_TOKEN_REF) + return Class.ResolveTypeTokenReference (wrapper_token); + } } } } @@ -1991,10 +2020,47 @@ internal static string ToFourCCString (int value) static void RetainNativeObject (IntPtr gchandle) { var obj = GetGCHandleTarget (gchandle); - if (obj is NativeObject nobj) + RetainNativeObject ((INativeObject?) obj); + } + + // Retain the input if it's either an NSObject or a NativeObject. + static NativeHandle RetainNativeObject (INativeObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + if (obj is NSObject nsobj) + RetainNSObject (nsobj); + else if (obj is NativeObject nobj) nobj.Retain (); - else if (obj is NSObject nsobj) + return obj.GetHandle (); + } + + internal static NativeHandle RetainNSObject (NSObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + obj.DangerousRetain (); + return obj.GetHandle (); + } + + internal static NativeHandle RetainAndAutoreleaseNSObject (NSObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + obj.DangerousRetain (); + obj.DangerousAutorelease (); + return obj.GetHandle (); + } + + internal static NativeHandle RetainAndAutoreleaseNativeObject (INativeObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + if (obj is NSObject nsobj) { nsobj.DangerousRetain (); + nsobj.DangerousAutorelease (); + } + return obj.GetHandle (); } // Check if the input is an NSObject, and in that case retain it (and return true) @@ -2097,6 +2163,44 @@ internal static MethodInfo FindClosedMethod (Type closed_type, MethodBase open_m throw ErrorHelper.CreateError (8003, $"Failed to find the closed generic method '{open_method.Name}' on the type '{closed_type.FullName}'."); } + internal static MethodInfo FindClosedMethod (object instance, RuntimeTypeHandle open_type_handle, RuntimeMethodHandle open_method_handle) + { + var closed_type = instance.GetType ()!; + var open_type = Type.GetTypeFromHandle (open_type_handle)!; + Runtime.NSLog ($"FindClosedMethod ({instance.GetType ()}, 0x{open_type_handle.Value.ToString ("x")} = {open_type}, {open_method_handle} = 0x{open_method_handle.Value.ToString ("x")})"); + closed_type = FindClosedTypeInHierarchy (open_type, closed_type)!; + Runtime.NSLog ($"FindClosedMethod ({instance.GetType ()}, 0x{open_type_handle.Value.ToString ("x")} = {open_type}, {open_method_handle} = 0x{open_method_handle.Value.ToString ("x")}) closed_type: {closed_type}"); + var closedMethod = MethodBase.GetMethodFromHandle (open_method_handle, closed_type.TypeHandle)!; + Runtime.NSLog ($"FindClosedMethod ({instance.GetType ()}, 0x{open_type_handle.Value.ToString ("x")} = {open_type}, {open_method_handle} = 0x{open_method_handle.Value.ToString ("x")}) closed_type: {closed_type} closed method: {closedMethod}"); + return (MethodInfo) closedMethod; + } + + static Type? FindClosedTypeInHierarchy (Type open_type, Type? closed_type) + { + if (closed_type is null) + return null; + + var closed_type_definition = closed_type; + if (closed_type_definition.IsGenericType) + closed_type_definition = closed_type_definition.GetGenericTypeDefinition (); + if (closed_type_definition == open_type) + return closed_type; + return FindClosedTypeInHierarchy (open_type, closed_type.BaseType); + } + + internal static Type FindClosedParameterType (object instance, RuntimeTypeHandle open_type_handle, RuntimeMethodHandle open_method_handle, int parameter) + { + var closed_type = instance.GetType ()!; + var open_type = Type.GetTypeFromHandle (open_type_handle)!; + Runtime.NSLog ($"FindClosedParameterType ({instance.GetType ()}, 0x{open_type_handle.Value.ToString ("x")} = {open_type}, 0x{open_method_handle.Value.ToString ("x")}, {parameter})"); + closed_type = FindClosedTypeInHierarchy (open_type, closed_type)!; + Runtime.NSLog ($"FindClosedParameterType ({instance.GetType ()}, 0x{open_type_handle.Value.ToString ("x")} = {open_type}, 0x{open_method_handle.Value.ToString ("x")}, {parameter}) closed type: {closed_type}"); + var closedMethod = MethodBase.GetMethodFromHandle (open_method_handle, closed_type.TypeHandle)!; + Runtime.NSLog ($"FindClosedParameterType ({instance.GetType ()}, 0x{open_type_handle.Value.ToString ("x")} = {open_type}, 0x{open_method_handle.Value.ToString ("x")}, {parameter}) closed type: {closed_type} closed method: {closedMethod}"); + var parameters = closedMethod.GetParameters (); + return parameters [parameter].ParameterType.GetElementType ()!; // FIX NAMING + } + static void GCCollect () { GC.Collect (); @@ -2193,7 +2297,13 @@ static bool GetIsARM64CallingConvention () } // Allocate a GCHandle and return the IntPtr to it. - internal static IntPtr AllocGCHandle (object? value, GCHandleType type = GCHandleType.Normal) + internal static IntPtr AllocGCHandle (object? value) + { + return AllocGCHandle (value, GCHandleType.Normal); + } + + // Allocate a GCHandle and return the IntPtr to it. + internal static IntPtr AllocGCHandle (object? value, GCHandleType type) { return GCHandle.ToIntPtr (GCHandle.Alloc (value, type)); } @@ -2288,6 +2398,7 @@ static sbyte InvokeConformsToProtocol (IntPtr handle, IntPtr protocol) static IntPtr LookupUnmanagedFunction (IntPtr assembly, IntPtr symbol, int id) { + return RegistrarHelper.LookupUnmanagedFunction (assembly, Marshal.PtrToStringAuto (symbol), id); } } @@ -2315,6 +2426,30 @@ public int GetHashCode (Type? obj) } } + internal class StringEqualityComparer : IEqualityComparer { + public bool Equals (string? x, string? y) + { + return string.Equals (x, y, StringComparison.Ordinal); + } + public int GetHashCode (string? obj) + { + if (obj is null) + return 0; + return obj.GetHashCode (); + } + } + + internal class RuntimeTypeHandleEqualityComparer : IEqualityComparer { + public bool Equals (RuntimeTypeHandle x, RuntimeTypeHandle y) + { + return x.Equals (y); + } + public int GetHashCode (RuntimeTypeHandle obj) + { + return obj.GetHashCode (); + } + } + internal struct IntPtrTypeValueTuple : IEquatable { static readonly IEqualityComparer item1Comparer = Runtime.IntPtrEqualityComparer; static readonly IEqualityComparer item2Comparer = Runtime.TypeEqualityComparer; diff --git a/src/frameworks.sources b/src/frameworks.sources index 770018cda5c0..442d0e4ddba9 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -1946,6 +1946,7 @@ SHARED_SOURCES = \ ObjCRuntime/AdoptsAttribute.cs \ ObjCRuntime/BackingField.cs \ ObjCRuntime/BaseWrapper.cs \ + ObjCRuntime/BindAs.cs \ ObjCRuntime/BlockProxyAttribute.cs \ ObjCRuntime/BindingImplAttribute.cs \ ObjCRuntime/CategoryAttribute.cs \ @@ -1959,6 +1960,7 @@ SHARED_SOURCES = \ ObjCRuntime/ExceptionMode.cs \ ObjCRuntime/Method.cs \ ObjCRuntime/Registrar.cs \ + ObjCRuntime/RegistrarHelper.cs \ ObjCRuntime/ReleaseAttribute.cs \ ObjCRuntime/RequiredFrameworkAttribute.cs \ ObjCRuntime/Runtime.cs \ diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index be0cb5d72534..4a4bb7c11aa6 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -33,8 +33,7 @@ using Mono.Cecil; using Mono.Linker; using Mono.Tuner; -using System.Numerics; -using static System.Runtime.InteropServices.JavaScript.JSType; +using Xamarin.Tuner; namespace Registrar { /* @@ -461,7 +460,7 @@ public string ToObjCType (TypeReference type) return "void *"; } - public string ToObjCType (TypeDefinition type, bool delegateToBlockType = false) + public string ToObjCType (TypeDefinition type, bool delegateToBlockType = false, bool cSyntaxForBlocks =false) { switch (type.FullName) { case "System.IntPtr": return "void *"; @@ -496,7 +495,10 @@ public string ToObjCType (TypeDefinition type, bool delegateToBlockType = false) StringBuilder builder = new StringBuilder (); builder.Append (ToObjCType (invokeMethod.ReturnType)); - builder.Append (" (^)("); + builder.Append (" (^"); + if (cSyntaxForBlocks) + builder.Append ("%PARAMETERNAME%"); + builder.Append (")("); var argumentTypes = invokeMethod.Parameters.Select (param => ToObjCType (param.ParameterType)); builder.Append (string.Join (", ", argumentTypes)); @@ -528,6 +530,11 @@ public static bool IsDelegate (TypeDefinition type) } TypeDefinition ResolveType (TypeReference tr) + { + return ResolveType (LinkContext, tr); + } + + public static TypeDefinition ResolveType (Xamarin.Tuner.DerivedLinkContext context, TypeReference tr) { // The static registrar might sometimes deal with types that have been linked away // It's not always possible to call .Resolve () on types that have been linked away, @@ -538,29 +545,34 @@ TypeDefinition ResolveType (TypeReference tr) if (tr is ArrayType arrayType) { return arrayType.ElementType.Resolve (); } else if (tr is GenericInstanceType git) { - return ResolveType (git.ElementType); + return ResolveType (context, git.ElementType); } else { var td = tr.Resolve (); if (td == null) - td = LinkContext?.GetLinkedAwayType (tr, out _); + td = context?.GetLinkedAwayType (tr, out _); return td; } } public bool IsNativeObject (TypeReference tr) + { + return IsNativeObject (LinkContext, tr); + } + + public static bool IsNativeObject (Xamarin.Tuner.DerivedLinkContext context, TypeReference tr) { var gp = tr as GenericParameter; if (gp != null) { if (gp.HasConstraints) { foreach (var constraint in gp.Constraints) { - if (IsNativeObject (constraint.ConstraintType)) + if (IsNativeObject (context, constraint.ConstraintType)) return true; } } return false; } - var type = ResolveType (tr); + var type = ResolveType (context, tr); while (type != null) { if (type.HasInterfaces) { @@ -757,13 +769,13 @@ protected override int GetValueTypeSize (TypeReference type) return GetValueTypeSize (type.Resolve (), Is64Bits); } - protected override bool HasReleaseAttribute (MethodDefinition method) + public override bool HasReleaseAttribute (MethodDefinition method) { method = GetBaseMethodInTypeHierarchy (method); return HasAttribute (method.MethodReturnType, ObjCRuntime, StringConstants.ReleaseAttribute); } - protected override bool HasThisAttribute (MethodDefinition method) + public override bool HasThisAttribute (MethodDefinition method) { return HasAttribute (method, "System.Runtime.CompilerServices", "ExtensionAttribute"); } @@ -956,7 +968,7 @@ protected override bool IsStatic (PropertyDefinition property) return false; } - protected override TypeReference GetElementType (TypeReference type) + public override TypeReference GetElementType (TypeReference type) { var ts = type as TypeSpecification; if (ts != null) { @@ -1055,7 +1067,7 @@ bool IsNativeEnum (TypeDefinition td) return HasAttribute (td, ObjCRuntime, StringConstants.NativeAttribute); } - protected override bool IsNullable (TypeReference type) + public override bool IsNullable (TypeReference type) { return GetNullableType (type) != null; } @@ -1071,7 +1083,7 @@ protected override bool IsEnum (TypeReference tr, out bool isNativeEnum) return type.IsEnum; } - protected override bool IsArray (TypeReference type, out int rank) + public override bool IsArray (TypeReference type, out int rank) { var arrayType = type as ArrayType; if (arrayType == null) { @@ -1156,7 +1168,7 @@ protected override bool AreEqual (TypeReference a, TypeReference b) return TypeMatch (a, b); } - protected override bool VerifyIsConstrainedToNSObject (TypeReference type, out TypeReference constrained_type) + public override bool VerifyIsConstrainedToNSObject (TypeReference type, out TypeReference constrained_type) { constrained_type = null; @@ -1339,7 +1351,7 @@ public override RegisterAttribute GetRegisterAttribute (TypeReference type) return rv; } - protected override CategoryAttribute GetCategoryAttribute (TypeReference type) + public override CategoryAttribute GetCategoryAttribute (TypeReference type) { string name = null; @@ -1891,7 +1903,7 @@ static NativeNameAttribute CreateNativeNameAttribute (ICustomAttribute attrib, T } } - protected override BindAsAttribute GetBindAsAttribute (PropertyDefinition property) + public override BindAsAttribute GetBindAsAttribute (PropertyDefinition property) { if (property == null) return null; @@ -1904,7 +1916,7 @@ protected override BindAsAttribute GetBindAsAttribute (PropertyDefinition proper return CreateBindAsAttribute (attrib, property); } - protected override BindAsAttribute GetBindAsAttribute (MethodDefinition method, int parameter_index) + public override BindAsAttribute GetBindAsAttribute (MethodDefinition method, int parameter_index) { if (method == null) return null; @@ -2002,7 +2014,7 @@ ExportAttribute CreateExportAttribute (IMemberDefinition candidate) } // [Export] is not sealed anymore - so we cannot simply compare strings - ICustomAttribute GetExportAttribute (ICustomAttributeProvider candidate) + public static ICustomAttribute GetExportAttribute (ICustomAttributeProvider candidate) { if (!candidate.HasCustomAttributes) return null; @@ -2069,7 +2081,7 @@ static bool PropertyMatch (PropertyDefinition candidate, PropertyDefinition prop return true; } - MethodDefinition GetBaseMethodInTypeHierarchy (MethodDefinition method) + public MethodDefinition GetBaseMethodInTypeHierarchy (MethodDefinition method) { if (!IsOverride (method)) return method; @@ -2473,7 +2485,7 @@ string ToSimpleObjCParameterType (TypeReference type, string descriptiveMethodNa return ToObjCParameterType (type, descriptiveMethodName, exceptions, inMethod); } - string ToObjCParameterType (TypeReference type, string descriptiveMethodName, List exceptions, MemberReference inMethod, bool delegateToBlockType = false) + string ToObjCParameterType (TypeReference type, string descriptiveMethodName, List exceptions, MemberReference inMethod, bool delegateToBlockType = false, bool cSyntaxForBlocks = false) { GenericParameter gp = type as GenericParameter; if (gp != null) @@ -2595,7 +2607,7 @@ string ToObjCParameterType (TypeReference type, string descriptiveMethodName, Li } return CheckStructure (td, descriptiveMethodName, inMethod); } else { - return ToObjCType (td, delegateToBlockType: delegateToBlockType); + return ToObjCType (td, delegateToBlockType: delegateToBlockType, cSyntaxForBlocks: cSyntaxForBlocks); } } } @@ -2783,7 +2795,7 @@ class ProtocolInfo { public ObjCType Protocol; } - class SkippedType { + public class SkippedType { public TypeReference Skipped; public ObjCType Actual; public uint SkippedTokenReference; @@ -2796,6 +2808,10 @@ protected override void OnSkipType (TypeReference type, ObjCType registered_type skipped_types.Add (new SkippedType { Skipped = type, Actual = registered_type }); } + public List SkippedTypes { + get => skipped_types; + } + public string GetInitializationMethodName (string single_assembly) { if (!string.IsNullOrEmpty (single_assembly)) { @@ -2929,8 +2945,7 @@ void Specialize (AutoIndentStringBuilder sb, out string initialization_method) map_init.AppendLine ("__xamarin_class_map [{1}].handle = {0};", get_class, i++); } - - if (@class.IsProtocol && @class.ProtocolWrapperType != null) { + if (@class.IsProtocol && @class.ProtocolWrapperType != null && App.Registrar != RegistrarMode.ManagedStatic) { if (token_ref == INVALID_TOKEN_REF && !TryCreateTokenReference (@class.Type, TokenType.TypeDef, out token_ref, exceptions)) continue; if (!TryCreateTokenReference (@class.ProtocolWrapperType, TokenType.TypeDef, out var protocol_wrapper_type_ref, exceptions)) @@ -4030,9 +4045,6 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List setup_return.Indentation = indent; cleanup.Indentation = indent; - if (!TryCreateTokenReference (method.Method, TokenType.Method, out var token_ref, exceptions)) - return; - // A comment describing the managed signature if (trace) { nslog_start.Indentation = sb.Indentation; @@ -4122,6 +4134,113 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List nslog_start.AppendLine (");"); } +#if NET + if (LinkContext.App.Registrar == RegistrarMode.ManagedStatic) { + + // SpecializePrepareParameters (new AutoIndentStringBuilder (), method, num_arg, descriptiveMethodName, exceptions); + + var staticCall = false; + var supportDynamicAssemblyLoading = true; + var managedMethodNotFound = false; + if (!App.Configuration.UnmanagedCallersMap.TryGetValue (method.Method, out var pinvokeMethodInfo)) { + exceptions.Add (ErrorHelper.CreateWarning (99, "Could not find the managed callback for {0}", descriptiveMethodName)); + var types = App.Configuration.UnmanagedCallersMap.Keys.Where (v => v.DeclaringType.FullName == method.Method.DeclaringType.FullName); + pinvokeMethodInfo = new LinkerConfiguration.UnmanagedCallersEntry (name + "___FIXME___MANAGED_METHOD_NOT_FOUND", -1, method.Method); + managedMethodNotFound = true; + } + var pinvokeMethod = pinvokeMethodInfo.Name; + sb.AppendLine (); + if (!staticCall) + sb.Append ("typedef "); + + var callbackReturnType = ""; + var hasReturnType = true; + if (isCtor) { + callbackReturnType = "id"; + } else if (isVoid) { + callbackReturnType = "void"; + hasReturnType = false; + } else { + callbackReturnType = ToObjCParameterType (method.NativeReturnType, descriptiveMethodName, exceptions, method.Method); + } + + sb.Append (callbackReturnType); + + if (staticCall) { + sb.Append (pinvokeMethod); + } else { + sb.Append (" (*"); + sb.Append (pinvokeMethod); + sb.Append ("_function)"); + } + sb.Append (" (id self, SEL sel"); + var indexOffset = method.IsCategoryInstance ? 1 : 0; + for (var i = indexOffset; i < num_arg; i++) { + sb.Append (", "); + var parameterType = ToObjCParameterType (method.NativeParameters [i], method.DescriptiveMethodName, exceptions, method.Method, delegateToBlockType: true, cSyntaxForBlocks: true); + var containsBlock = parameterType.Contains ("%PARAMETERNAME%"); + parameterType = parameterType.Replace ("%PARAMETERNAME%", $"p{i - indexOffset}"); + sb.Append (parameterType); + if (!containsBlock) { + sb.Append (" "); + sb.AppendFormat ("p{0}", i - indexOffset); + } + } + if (isCtor) + sb.Append (", bool* call_super"); + sb.Append (", GCHandle* exception_gchandle"); + + if (method.IsVariadic) + sb.Append (", ..."); + sb.Append (");"); + + sb.WriteLine (); + sb.WriteLine (GetObjCSignature (method, exceptions)); + sb.WriteLine ("{"); + sb.WriteLine ("GCHandle exception_gchandle = INVALID_GCHANDLE;"); + if (isCtor) + sb.WriteLine ($"bool call_super = false;"); + if (hasReturnType) + sb.WriteLine ($"{callbackReturnType} rv = {{ 0 }};"); + + if (managedMethodNotFound) { + sb.WriteLine ($"NSLog (@\"Trying to call managed method that wasn't found at build time: {pinvokeMethod}\\n\");"); + sb.WriteLine ($"fprintf (stderr, \"Trying to call managed method that wasn't found at build time: {pinvokeMethod}\\n\");"); + } + if (!staticCall) { + sb.WriteLine ($"static {pinvokeMethod}_function {pinvokeMethod};"); + var assemblyName = supportDynamicAssemblyLoading ? "\"" + method.Method.Module.Assembly.Name.Name + "\"" : "NULL"; + sb.WriteLine ($"xamarin_registrar_dlsym ((void **) &{pinvokeMethod}, {assemblyName}, \"{pinvokeMethod}\", {pinvokeMethodInfo.Id});"); + } + if (hasReturnType) + sb.Write ("rv = "); + sb.Write (pinvokeMethod); + sb.Write (" (self, _cmd"); + for (var i = indexOffset; i < num_arg; i++) { + sb.AppendFormat (", p{0}", i - indexOffset); + } + if (isCtor) + sb.Write (", &call_super"); + sb.Write (", &exception_gchandle"); + sb.WriteLine (");"); + + sb.WriteLine ("xamarin_process_managed_exception_gchandle (exception_gchandle);"); + + if (isCtor) { + GenerateCallToSuperForConstructor (sb, method, exceptions); + } + + if (hasReturnType) + sb.WriteLine ("return rv;"); + + sb.WriteLine ("}"); + return; + } +#endif + + if (!TryCreateTokenReference (method.Method, TokenType.Method, out var token_ref, exceptions)) + return; + SpecializePrepareParameters (sb, method, num_arg, descriptiveMethodName, exceptions); // the actual invoke @@ -4393,7 +4512,30 @@ public TypeDefinition GetInstantiableType (TypeDefinition td, List ex return nativeObjType; } - TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) + public MethodDefinition GetCreateBlockMethod (TypeDefinition delegateProxyType) + { + if (!delegateProxyType.HasMethods) + return null; + + foreach (var method in delegateProxyType.Methods) { + if (method.Name != "CreateBlock") + continue; + if (!method.ReturnType.Is ("ObjCRuntime", "BlockLiteral")) + continue; + if (!method.HasParameters) + continue; + if (method.Parameters.Count != 1) + continue; + if (!IsDelegate (method.Parameters [0].ParameterType)) + continue; + + return method; + } + + return null; + } + + public TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) { // A mirror of this method is also implemented in BlockLiteral:GetDelegateProxyType // If this method is changed, that method will probably have to be updated too (tests!!!) @@ -4440,7 +4582,12 @@ TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) return null; } - MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) + // + // Returns a MethodInfo that represents the method that can be used to turn + // a the block in the given method at the given parameter into a strongly typed + // delegate + // + public MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) { // A mirror of this method is also implemented in Runtime:GetBlockWrapperCreator // If this method is changed, that method will probably have to be updated too (tests!!!) @@ -4514,6 +4661,27 @@ MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) return null; } + public bool TryFindType (TypeDefinition type, out ObjCType objcType) + { + return Types.TryGetValue (type, out objcType); + } + + public bool TryFindMethod (MethodDefinition method, out ObjCMethod objcMethod) + { + if (TryFindType (method.DeclaringType, out var type)) { + if (type.Methods is not null) { + foreach (var m in type.Methods) { + if ((object) m.Method == (object) method) { + objcMethod = m; + return true; + } + } + } + } + objcMethod = null; + return false; + } + MethodDefinition GetBlockProxyAttributeMethod (MethodDefinition method, int parameter) { var param = method.Parameters [parameter]; @@ -4621,7 +4789,7 @@ public bool MapProtocolMember (TypeDefinition t, MethodDefinition method, out Me return false; } - string GetManagedToNSNumberFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) + public string GetManagedToNSNumberFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) { var typeName = managedType.FullName; switch (typeName) { @@ -4649,7 +4817,7 @@ string GetManagedToNSNumberFunc (TypeReference managedType, TypeReference inputT } } - string GetNSNumberToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) + public string GetNSNumberToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) { var typeName = managedType.FullName; switch (typeName) { @@ -4679,7 +4847,7 @@ string GetNSNumberToManagedFunc (TypeReference managedType, TypeReference inputT } } - string GetNSValueToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) + public string GetNSValueToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) { var underlyingTypeName = managedType.FullName; @@ -4708,7 +4876,7 @@ string GetNSValueToManagedFunc (TypeReference managedType, TypeReference inputTy } } - string GetManagedToNSValueFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) + public string GetManagedToNSValueFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) { var underlyingTypeName = managedType.FullName; @@ -4751,6 +4919,7 @@ string GetSmartEnumToNSStringFunc (TypeReference managedType, TypeReference inpu void GenerateConversionToManaged (TypeReference inputType, TypeReference outputType, AutoIndentStringBuilder sb, string descriptiveMethodName, ref List exceptions, ObjCMethod method, string inputName, string outputName, string managedClassExpression, int parameter) { // This is a mirror of the native method xamarin_generate_conversion_to_managed (for the dynamic registrar). + // It's also a mirror of the method ManagedRegistrarStep.GenerateConversionToManaged. // These methods must be kept in sync. var managedType = outputType; var nativeType = inputType; @@ -4940,25 +5109,31 @@ public override bool Equals (object obj) bool TryCreateFullTokenReference (MemberReference member, out uint token_ref, out Exception exception) { - token_ref = (full_token_reference_count++ << 1) + 1; switch (member.MetadataToken.TokenType) { case TokenType.TypeDef: case TokenType.Method: break; // OK default: exception = ErrorHelper.CreateError (99, Errors.MX0099, $"unsupported tokentype ({member.MetadataToken.TokenType}) for {member.FullName}"); + token_ref = INVALID_TOKEN_REF; return false; } - var assemblyIndex = registered_assemblies.FindIndex (v => v.Assembly == member.Module.Assembly); - if (assemblyIndex == -1) { - exception = ErrorHelper.CreateError (99, Errors.MX0099, $"Could not find {member.Module.Assembly.Name.Name} in the list of registered assemblies when processing {member.FullName}:\n\t{string.Join ("\n\t", registered_assemblies.Select (v => v.Assembly.Name.Name))}"); - return false; - } - var assemblyName = registered_assemblies [assemblyIndex].Name; var moduleToken = member.Module.MetadataToken.ToUInt32 (); var moduleName = member.Module.Name; var memberToken = member.MetadataToken.ToUInt32 (); var memberName = member.FullName; + return WriteFullTokenReference (member.Module.Assembly, moduleToken, moduleName, memberToken, memberName, out token_ref, out exception); + } + + bool WriteFullTokenReference (AssemblyDefinition assembly, uint moduleToken, string moduleName, uint memberToken, string memberName, out uint token_ref, out Exception exception) + { + token_ref = (full_token_reference_count++ << 1) + 1; + var assemblyIndex = registered_assemblies.FindIndex (v => v.Assembly == assembly); + if (assemblyIndex == -1) { + exception = ErrorHelper.CreateError (99, Errors.MX0099, $"Could not find {assembly.Name.Name} in the list of registered assemblies when processing {memberName}:\n\t{string.Join ("\n\t", registered_assemblies.Select (v => v.Assembly.Name.Name))}"); + return false; + } + var assemblyName = registered_assemblies [assemblyIndex].Name; exception = null; full_token_references.Append ($"\t\t{{ /* #{full_token_reference_count} = 0x{token_ref:X} */ {assemblyIndex} /* {assemblyName} */, 0x{moduleToken:X} /* {moduleName} */, 0x{memberToken:X} /* {memberName} */ }},\n"); return true; @@ -4989,6 +5164,28 @@ bool TryCreateTokenReferenceUncached (MemberReference member, TokenType implied_ { var token = member.MetadataToken; +#if NET + if (App.Registrar == RegistrarMode.ManagedStatic) { + if (implied_type == TokenType.TypeDef && member is TypeDefinition td) { + if (App.Configuration.RegisteredTypesMap.TryGetValue (td, out var id)) { + id = id | (uint) TokenType.TypeDef; + return WriteFullTokenReference (member.Module.Assembly, INVALID_TOKEN_REF, member.Module.Name, id, member.FullName, out token_ref, out exception); + } + throw ErrorHelper.CreateError (99, $"Can't create a token reference to an unregistered type when using the managed static registrar: {member.FullName}"); + token_ref = INVALID_TOKEN_REF; + return false; + } + if (implied_type == TokenType.Method) { + throw ErrorHelper.CreateError (99, $"Can't create a token reference to a method when using the managed static registrar: {member.FullName}"); + token_ref = INVALID_TOKEN_REF; + return false; + } + throw ErrorHelper.CreateError (99, "Can't create a token reference to a token type {0} when using the managed static registrar.", implied_type.ToString ()); + token_ref = INVALID_TOKEN_REF; + return false; + } +#endif + /* We can't create small token references if we're in partial mode, because we may have multiple arrays of registered assemblies, and no way of saying which one we refer to with the assembly index */ if (IsSingleAssembly) return TryCreateFullTokenReference (member, out token_ref, out exception); @@ -5286,6 +5483,87 @@ protected override bool SkipRegisterAssembly (AssemblyDefinition assembly) return base.SkipRegisterAssembly (assembly); } + + // Find the value of the [UserDelegateType] attribute on the specified delegate + TypeReference GetUserDelegateType (TypeReference delegateType) + { + var delegateTypeDefinition = delegateType.Resolve (); + foreach (var attrib in delegateTypeDefinition.CustomAttributes) { + var attribType = attrib.AttributeType; + if (!attribType.Is (Namespaces.ObjCRuntime, "UserDelegateTypeAttribute")) + continue; + return attrib.ConstructorArguments [0].Value as TypeReference; + } + return null; + } + + MethodDefinition GetDelegateInvoke (TypeReference delegateType) + { + var td = delegateType.Resolve (); + foreach (var method in td.Methods) { + if (method.Name == "Invoke") + return method; + } + return null; + } + + MethodReference InflateMethod (TypeReference inflatedDeclaringType, MethodDefinition openMethod) + { + var git = inflatedDeclaringType as GenericInstanceType; + if (git == null) + return openMethod; + + var inflatedReturnType = TypeReferenceExtensions.InflateGenericType (git, openMethod.ReturnType); + var mr = new MethodReference (openMethod.Name, inflatedReturnType, git); + if (openMethod.HasParameters) { + for (int i = 0; i < openMethod.Parameters.Count; i++) { + var inflatedParameterType = TypeReferenceExtensions.InflateGenericType (git, openMethod.Parameters [i].ParameterType); + var p = new ParameterDefinition (openMethod.Parameters [i].Name, openMethod.Parameters [i].Attributes, inflatedParameterType); + mr.Parameters.Add (p); + } + } + return mr; + } + + public bool TryComputeBlockSignature (ICustomAttributeProvider codeLocation, TypeReference trampolineDelegateType, out Exception exception, out string signature) + { + signature = null; + exception = null; + try { + // Calculate the block signature. + var blockSignature = false; + MethodReference userMethod = null; + + // First look for any [UserDelegateType] attributes on the trampoline delegate type. + var userDelegateType = GetUserDelegateType (trampolineDelegateType); + if (userDelegateType != null) { + var userMethodDefinition = GetDelegateInvoke (userDelegateType); + userMethod = InflateMethod (userDelegateType, userMethodDefinition); + blockSignature = true; + } else { + // Couldn't find a [UserDelegateType] attribute, use the type of the actual trampoline instead. + var userMethodDefinition = GetDelegateInvoke (trampolineDelegateType); + userMethod = InflateMethod (trampolineDelegateType, userMethodDefinition); + blockSignature = false; + } + + // No luck finding the signature, so give up. + if (userMethod is null) { + exception = ErrorHelper.CreateError (App, 4187 /* Could not find a [UserDelegateType] attribute on the type '{0}'. */, codeLocation, Errors.MX4187, trampolineDelegateType.FullName); + return false; + } + + var parameters = new TypeReference [userMethod.Parameters.Count]; + for (int p = 0; p < parameters.Length; p++) + parameters [p] = userMethod.Parameters [p].ParameterType; + signature = LinkContext.Target.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); + return true; + } catch (Exception e) { + exception = ErrorHelper.CreateError (App, 4188 /* Unable to compute the block signature for the type '{0}': {1} */, e, codeLocation, Errors.MX4188, trampolineDelegateType.FullName, e.Message); + return false; + } + } + } // Replicate a few attribute types here, with TypeDefinition instead of Type diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 1cbd7ffc2e05..a3502987923c 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -7,6 +7,7 @@ using Mono.Cecil; using Mono.Linker; +using Mono.Linker.Steps; using Xamarin.Bundler; using Xamarin.Utils; @@ -57,6 +58,22 @@ public class LinkerConfiguration { Dictionary> msbuild_items = new Dictionary> (); + public class UnmanagedCallersEntry { + public string Name; + public int Id; + public MethodDefinition UnmanagedCallersMethod; + + public UnmanagedCallersEntry (string name, int id, MethodDefinition unmanagedCallersMethod) + { + Name = name; + Id = id; + UnmanagedCallersMethod = unmanagedCallersMethod; + } + } + + public Dictionary UnmanagedCallersMap = new (); + public Dictionary RegisteredTypesMap = new Dictionary (); + internal PInvokeWrapperGenerator PInvokeWrapperGenerationState; public static LinkerConfiguration GetInstance (LinkContext context, bool createIfNotFound = true) @@ -488,6 +505,15 @@ public static void Report (LinkContext context, IList exceptions) // ErrorHelper.Show will print our errors and warnings to stderr. ErrorHelper.Show (list); } + + public IEnumerable GetNonDeletedAssemblies (BaseStep step) + { + foreach (var assembly in Assemblies) { + if (step.Annotations.GetAction (assembly) == Mono.Linker.AssemblyAction.Delete) + continue; + yield return assembly; + } + } } } diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 54d984aeed9d..ed6a03e34598 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -4,6 +4,7 @@ using Mono.Cecil; using Mono.Linker.Steps; +using Xamarin.Tuner; using Xamarin.Bundler; @@ -13,6 +14,14 @@ public LinkerConfiguration Configuration { get { return LinkerConfiguration.GetInstance (Context); } } + public DerivedLinkContext DerivedLinkContext { + get { return Configuration.DerivedLinkContext; } + } + + public Application App { + get { return DerivedLinkContext.App; } + } + protected void Report (Exception exception) { LinkerConfiguration.Report (Context, exception); diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index cd93bbede2bb..6eef3961d2f7 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -7,6 +8,14 @@ using Xamarin.Bundler; using Xamarin.Utils; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using Registrar; +using System.Globalization; + #nullable enable namespace Xamarin.Linker { @@ -14,6 +23,1051 @@ public class ManagedRegistrarStep : ConfigurationAwareStep { protected override string Name { get; } = "ManagedRegistrar"; protected override int ErrorCode { get; } = 2430; + bool UsesUnmanagedMethodLookups { + get { + return true; // FIXME: false if AOT (and not interpreter?) + } + } + + List exceptions = new List (); + Application? app; + + AssemblyDefinition? current_assembly; + + AssemblyDefinition? corlib_assembly; + AssemblyDefinition? platform_assembly; + + AssemblyDefinition CurrentAssembly { + get { + if (current_assembly is null) + throw new InvalidOperationException ($"No current assembly!"); + return current_assembly; + } + } + + AssemblyDefinition CorlibAssembly { + get { + if (corlib_assembly is null) + throw new InvalidOperationException ($"No corlib assembly!"); + return corlib_assembly; + } + } + + AssemblyDefinition PlatformAssembly { + get { + if (platform_assembly is null) + throw new InvalidOperationException ($"No platform assembly!"); + return platform_assembly; + } + } + + Dictionary> type_map = new Dictionary> (); + Dictionary method_map = new Dictionary (); + + class TrampolineInfo + { + public MethodDefinition Trampoline; + public MethodDefinition Target; + public int Id; + + public TrampolineInfo (MethodDefinition trampoline, MethodDefinition target, int id) + { + this.Trampoline = trampoline; + this.Target = target; + this.Id = id; + } + } + + List current_trampoline_lists = new (); + Dictionary> trampoline_map = new (); + + // FIXME: mark the types and methods we use + TypeReference GetTypeReference (AssemblyDefinition assembly, string fullname, out TypeDefinition type, bool ensurePublic = false) + { + if (!type_map.TryGetValue (assembly, out var map)) + type_map.Add (assembly, map = new Dictionary ()); + + if (!map.TryGetValue (fullname, out var tuple)) { + var td = assembly.MainModule.Types.SingleOrDefault (v => v.FullName == fullname); + if (td is null) + throw new InvalidOperationException ($"Unable to find the type '{fullname}' in {assembly.Name.Name}"); + if (ensurePublic) + td.IsPublic = true; + var tr = CurrentAssembly.MainModule.ImportReference (td); + map [fullname] = tuple = new (td, tr); + } + + type = tuple.Item1; + return tuple.Item2; + } + + // FIXME: mark the types and methods we use + MethodReference GetMethodReference (AssemblyDefinition assembly, string fullname, string name) + { + GetTypeReference (assembly, fullname, out var td); + return GetMethodReference (assembly, td, name, fullname + "::" + name, null, out var _); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, string fullname, string name, Func? predicate) + { + GetTypeReference (assembly, fullname, out var td); + return GetMethodReference (assembly, td, name, fullname + "::" + name, predicate, out var _); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, string fullname, string name, Func? predicate, bool ensurePublic) + { + GetTypeReference (assembly, fullname, out var td); + return GetMethodReference (assembly, td, name, fullname + "::" + name, predicate, out var _, ensurePublic: ensurePublic); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, null, out var _); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, Func? predicate) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, predicate, out var _); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, Func? predicate, bool ensurePublic = true) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, predicate, out var _, ensurePublic: ensurePublic); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, Func? predicate, bool ensurePublic = true) + { + return GetMethodReference (assembly, tr, name, key, predicate, out var _, ensurePublic: ensurePublic); + } + + MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, Func? predicate, out MethodDefinition method, bool ensurePublic = true) + { + if (!method_map.TryGetValue (key, out var tuple)) { + var td = tr.Resolve (); + var md = td.Methods.SingleOrDefault (v => v.Name == name && (predicate is null || predicate (v))); + if (md is null) + throw new InvalidOperationException ($"Unable to find the method '{tr.FullName}::{name}' (for key '{key}') in {assembly.Name.Name}. Methods in type:\n\t{string.Join ("\n\t", td.Methods.Select (GetMethodSignature).OrderBy (v => v))}"); + + tuple.Item1 = md; + tuple.Item2 = CurrentAssembly.MainModule.ImportReference (md); + method_map.Add (key, tuple); + + if (ensurePublic) + md.IsPublic = true; + } + + method = tuple.Item1; + return tuple.Item2; + } + + TypeReference System_Byte { + get { + return GetTypeReference (CorlibAssembly, "System.Byte", out var _); + } + } + + TypeReference System_Exception { + get { + return GetTypeReference (CorlibAssembly, "System.Exception", out var _); + } + } + + TypeReference System_Int32 { + get { + return GetTypeReference (CorlibAssembly, "System.Int32", out var _); + } + } + + TypeReference System_UInt32 { + get { + return GetTypeReference (CorlibAssembly, "System.UInt32", out var _); + } + } + + TypeReference System_IntPtr { + get { + return GetTypeReference (CorlibAssembly, "System.IntPtr", out var _); + } + } + + TypeReference System_Nullable_1 { + get { + return GetTypeReference (CorlibAssembly, "System.Nullable`1", out var _); + } + } + + TypeReference System_Object { + get { + return GetTypeReference (CorlibAssembly, "System.Object", out var _); + } + } + + TypeReference System_String { + get { + return GetTypeReference (CorlibAssembly, "System.String", out var _); + } + } + + TypeReference System_Type { + get { + return GetTypeReference (CorlibAssembly, "System.Type", out var _); + } + } + + TypeReference System_Void { + get { + return GetTypeReference (CorlibAssembly, "System.Void", out var _); + } + } + + TypeReference System_RuntimeTypeHandle { + get { + return GetTypeReference (CorlibAssembly, "System.RuntimeTypeHandle", out var _); + } + } + + TypeReference System_Collections_Generic_Dictionary2 { + get { + return GetTypeReference (CorlibAssembly, "System.Collections.Generic.Dictionary`2", out var _); + } + } + + TypeReference System_Reflection_MethodBase { + get { + return GetTypeReference (CorlibAssembly, "System.Reflection.MethodBase", out var _); + } + } + + TypeReference System_Reflection_MethodInfo { + get { + return GetTypeReference (CorlibAssembly, "System.Reflection.MethodInfo", out var _); + } + } + + TypeReference Foundation_NSArray { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSArray", out var _); + } + } + + TypeReference Foundation_NSObject { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSObject", out var _); + } + } + + TypeReference Foundation_NSString { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSString", out var _); + } + } + + TypeReference ObjCRuntime_BindAs { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.BindAs", out var _); + } + } + + TypeReference ObjCRuntime_IManagedRegistrar { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.IManagedRegistrar", out var _, ensurePublic: true); + } + } + + TypeReference ObjCRuntime_RegistrarHelper { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.RegistrarHelper", out var _); + } + } + + TypeReference ObjCRuntime_Runtime { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.Runtime", out var _); + } + } + + TypeReference ObjCRuntime_NativeHandle { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.NativeHandle", out var _); + } + } + + TypeReference ObjCRuntime_BlockLiteral { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.BlockLiteral", out var _); + } + } + + TypeReference ObjCRuntime_NativeObjectExtensions { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.NativeObjectExtensions", out var _); + } + } + + MethodReference System_Object__ctor { + get { + return GetMethodReference (CorlibAssembly, "System.Object", ".ctor", (v) => v.IsDefaultConstructor ()); + } + } + + MethodReference Nullable_HasValue { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, "get_HasValue", (v) => + !v.IsStatic + && !v.HasParameters + && !v.HasGenericParameters); + } + } + + MethodReference Nullable_Value { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, "get_Value", (v) => + !v.IsStatic + && !v.HasParameters + && !v.HasGenericParameters); + } + } + + MethodReference Type_GetTypeFromHandle { + get { + return GetMethodReference (CorlibAssembly, System_Type, "GetTypeFromHandle", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "RuntimeTypeHandle") + && !v.HasGenericParameters); + } + } + + MethodReference Dictionary2_Add { + get { + return GetMethodReference (CorlibAssembly, System_Collections_Generic_Dictionary2, "Add", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && !v.HasGenericParameters); + } + } + + MethodReference MethodBase_Invoke { + get { + return GetMethodReference (CorlibAssembly, System_Reflection_MethodBase, "Invoke", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && v.Parameters [1].ParameterType is ArrayType at + && at.ElementType.Is ("System", "Object") + && !v.HasGenericParameters); + } + } + + MethodReference MethodBase_GetMethodFromHandle { + get { + return GetMethodReference (CorlibAssembly, System_Reflection_MethodBase, "GetMethodFromHandle", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "RuntimeMethodHandle") + && v.Parameters [1].ParameterType.Is ("System", "RuntimeTypeHandle") + && !v.HasGenericParameters); + } + } + + MethodReference NSObject_AllocateNSObject { + get { + return GetMethodReference (PlatformAssembly, Foundation_NSObject, "AllocateNSObject", nameof (NSObject_AllocateNSObject), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && v.Parameters [1].ParameterType.Is ("", "Flags") && v.Parameters [1].ParameterType.DeclaringType.Is ("Foundation", "NSObject") + && v.HasGenericParameters + && v.GenericParameters.Count == 1, + ensurePublic: true); + } + } + + MethodReference NSObject_DangerousRetain { + get { + return GetMethodReference (PlatformAssembly, Foundation_NSObject, "DangerousRetain", nameof (NSObject_DangerousRetain), (v) => + !v.IsStatic + && !v.HasParameters + && !v.HasGenericParameters); + } + } + + MethodReference NSObject_DangerousAutorelease { + get { + return GetMethodReference (PlatformAssembly, Foundation_NSObject, "DangerousAutorelease", nameof (NSObject_DangerousAutorelease), (v) => + !v.IsStatic + && !v.HasParameters + && !v.HasGenericParameters); + } + } + + MethodReference BindAs_ConvertNSArrayToManagedArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertNSArrayToManagedArray", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + // && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference BindAs_ConvertNSArrayToManagedArray2 { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertNSArrayToManagedArray2", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + // && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 2 + , ensurePublic: true); + } + } + + MethodReference BindAs_ConvertManagedArrayToNSArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertManagedArrayToNSArray", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType is ArrayType at + // && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference BindAs_ConvertManagedArrayToNSArray2 { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertManagedArrayToNSArray2", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is ArrayType at + // && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 2 + , ensurePublic: true); + } + } + + MethodReference BindAs_CreateNullable { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "CreateNullable", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + // && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + // && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference BindAs_CreateNullable2 { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "CreateNullable2", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + // && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + // && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 2 + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_NSArray_string_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_string_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType is ArrayType at1 && at1.ElementType.Is ("System", "String") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType is ArrayType at2 && at2.ElementType.Is ("System", "String") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_NSArray_string_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_string_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ArrayType at1 && at1.ElementType.Is ("System", "String") + && v.Parameters [2].ParameterType is ArrayType at2 && at2.ElementType.Is ("System", "String") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_NSArray_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType is ArrayType at1 && at1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType is ArrayType at2 && at2.ElementType.Is ("", "T") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_NSArray_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ArrayType at1 && at1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ArrayType at2 && at2.ElementType.Is ("", "T") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_NSObject_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSObject_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType.Is ("", "T") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_NSObject_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSObject_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("Foundation", "NSObject") + && v.Parameters [2].ParameterType.Is ("Foundation", "NSObject") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_string_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "string_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("ObjCRuntime", "NativeHandle") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType.Is ("System", "String") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType.Is ("System", "String") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_string_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "string_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("ObjCRuntime", "NativeHandle") + && v.Parameters [1].ParameterType.Is ("System", "String") + && v.Parameters [2].ParameterType.Is ("System", "String") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_INativeObject_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "INativeObject_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType.Is ("", "T") + && v.Parameters [3].ParameterType.Is ("System", "RuntimeTypeHandle") + && v.HasGenericParameters + && v.GenericParameters.Count == 1 + , ensurePublic: true); + } + } + + MethodReference RegistrarHelper_INativeObject_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "INativeObject_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("ObjCRuntime", "INativeObject") + && v.Parameters [2].ParameterType.Is ("ObjCRuntime", "INativeObject") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + + MethodReference IManagedRegistrar_LookupUnmanagedFunction { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "LookupUnmanagedFunction", (v) => + v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "String") + && v.Parameters [1].ParameterType.Is ("System", "Int32") + && !v.HasGenericParameters); + } + } + + + MethodReference IManagedRegistrar_LookupType { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "LookupType", (v) => + v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "UInt32") + && !v.HasGenericParameters); + } + } + + + MethodReference IManagedRegistrar_RegisterWrapperTypes { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "RegisterWrapperTypes", (v) => + v.HasParameters + && v.Parameters.Count == 1 + // && v.Parameters [0].ParameterType is GenericInstanceType git && git.GetElementType ().Is ("System.Collections.Generic", "Dictionary`2") + && !v.HasGenericParameters); + } + } + + MethodReference Runtime_AllocGCHandle { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "AllocGCHandle", nameof (Runtime_AllocGCHandle), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference Runtime_HasNSObject { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "HasNSObject", nameof (Runtime_HasNSObject), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference Runtime_GetNSObject__System_IntPtr { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "GetNSObject", nameof (Runtime_GetNSObject__System_IntPtr), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && !v.HasGenericParameters); + } + } + + MethodReference Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "GetNSObject", nameof (Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && v.Parameters [2].ParameterType.Is ("System", "RuntimeMethodHandle") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && v.HasGenericParameters + && v.GenericParameters.Count == 1, ensurePublic: true); + } + } + + MethodReference Runtime_GetNSObject_T___System_IntPtr { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "GetNSObject", nameof (Runtime_GetNSObject_T___System_IntPtr), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.HasGenericParameters + && v.GenericParameters.Count == 1, ensurePublic: true); + } + } + + MethodReference Runtime_GetNSObject__ObjCRuntime_NativeHandle { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "GetNSObject", nameof (Runtime_GetNSObject__ObjCRuntime_NativeHandle), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && !v.HasGenericParameters); + } + } + + MethodReference Runtime_GetINativeObject__IntPtr_Boolean_Type_Type { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "GetINativeObject", nameof (Runtime_GetINativeObject__IntPtr_Boolean_Type_Type), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("System", "Boolean") + && v.Parameters [2].ParameterType.Is ("System", "Type") + && v.Parameters [3].ParameterType.Is ("System", "Type") + && !v.HasGenericParameters, + ensurePublic: true); + } + } + + MethodReference BlockLiteral_CreateBlockForDelegate { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BlockLiteral, "CreateBlockForDelegate", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType.Is ("System", "Delegate") + && v.Parameters [1].ParameterType.Is ("System", "Delegate") + && v.Parameters [2].ParameterType.Is ("System", "String") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference RegistrarHelper_GetBlockForDelegate { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "GetBlockForDelegate", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && v.Parameters [1].ParameterType.Is ("System", "RuntimeMethodHandle") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference RegistrarHelper_GetBlockPointer { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "GetBlockPointer", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "BlockLiteral") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference BlockLiteral_Copy { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BlockLiteral, "Copy", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && !v.HasGenericParameters); + } + } + + MethodReference Runtime_ReleaseBlockWhenDelegateIsCollected { + get { + var rv = GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "ReleaseBlockWhenDelegateIsCollected", "ReleaseBlockWhenDelegateIsCollected", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("System", "Delegate") + && !v.HasGenericParameters, out var md); + md.IsPublic = true; + return rv; + } + } + + MethodReference Runtime_GetBlockWrapperCreator { + get { + var rv = GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "GetBlockWrapperCreator", nameof (Runtime_GetBlockWrapperCreator), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System.Reflection", "MethodInfo") + && v.Parameters [1].ParameterType.Is ("System", "Int32") + && !v.HasGenericParameters, out var md); + md.IsPublic = true; + return rv; + } + } + + MethodReference Runtime_CreateBlockProxy { + get { + var rv = GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "CreateBlockProxy", nameof (Runtime_CreateBlockProxy), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System.Reflection", "MethodInfo") + && v.Parameters [1].ParameterType.Is ("System", "IntPtr") + && !v.HasGenericParameters, out var md); + md.IsPublic = true; + return rv; + } + } + + MethodReference Runtime_TraceCaller { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "TraceCaller", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "String") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference Runtime_FindClosedMethod { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "FindClosedMethod", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && v.Parameters [1].ParameterType.Is ("System", "RuntimeTypeHandle") + && v.Parameters [2].ParameterType.Is ("System", "RuntimeMethodHandle") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference Runtime_FindClosedParameterType { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "FindClosedParameterType", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && v.Parameters [1].ParameterType.Is ("System", "RuntimeTypeHandle") + && v.Parameters [2].ParameterType.Is ("System", "RuntimeMethodHandle") + && v.Parameters [3].ParameterType.Is ("System", "Int32") + && !v.HasGenericParameters + , ensurePublic: true); + } + } + MethodReference CFString_FromHandle { + get { + return GetMethodReference (PlatformAssembly, "CoreFoundation.CFString", "FromHandle", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && !v.HasGenericParameters); + } + } + + MethodReference CFString_CreateNative { + get { + return GetMethodReference (PlatformAssembly, "CoreFoundation.CFString", "CreateNative", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "String") + && !v.HasGenericParameters); + } + } + + MethodReference CFArray_StringArrayFromHandle { + get { + return GetMethodReference (PlatformAssembly, "CoreFoundation.CFArray", "StringArrayFromHandle", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && !v.HasGenericParameters); + } + } + + MethodReference CFArray_Create { + get { + return GetMethodReference (PlatformAssembly, "CoreFoundation.CFArray", "Create", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType is ArrayType at + && at.GetElementType ().Is ("System", "String") + && !v.HasGenericParameters); + } + } + + MethodReference NSArray_ArrayFromHandle { + get { + return GetMethodReference (PlatformAssembly, "Foundation.NSArray", "ArrayFromHandle", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && v.Parameters [1].ParameterType.Is ("System", "Type") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference NSArray_ArrayFromHandle_1 { + get { + return GetMethodReference (PlatformAssembly, Foundation_NSArray, "ArrayFromHandle", "ArrayFromHandle`1", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + MethodReference RegistrarHelper_ManagedArrayToNSArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "ManagedArrayToNSArray", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && v.Parameters [1].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters, ensurePublic: true); + } + } + + MethodReference NativeObjectExtensions_GetHandle { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_NativeObjectExtensions, "GetHandle"); + } + } + + MethodReference NativeObject_op_Implicit_IntPtr { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_NativeHandle, "op_Implicit", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && v.ReturnType.Is ("System", "IntPtr") + && !v.HasGenericParameters + ); + } + } + + MethodReference Runtime_RetainNSObject { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "RetainNSObject", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("Foundation", "NSObject") + && !v.HasGenericParameters, + ensurePublic: true); + } + } + + MethodReference Runtime_RetainNativeObject { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "RetainNativeObject", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "INativeObject") + && !v.HasGenericParameters, + ensurePublic: true); + } + } + + MethodReference Runtime_RetainAndAutoreleaseNSObject { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "RetainAndAutoreleaseNSObject", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("Foundation", "NSObject") + && !v.HasGenericParameters, + ensurePublic: true); + } + } + + MethodReference Runtime_RetainAndAutoreleaseNativeObject { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "RetainAndAutoreleaseNativeObject", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "INativeObject") + && !v.HasGenericParameters, + ensurePublic: true); + } + } + + MethodReference UnmanagedCallersOnlyAttribute_Constructor { + get { + return GetMethodReference (CorlibAssembly, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute", ".ctor", (v) => v.IsDefaultConstructor ()); + } + } + + MethodReference Unsafe_AsRef { + get { + return GetMethodReference (CorlibAssembly, "System.Runtime.CompilerServices.Unsafe", "AsRef", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.IsPointer + && v.Parameters [0].ParameterType.GetElementType ().Is ("System", "Void") + && v.HasGenericParameters + ); + } + } + + MethodReference Exception_ctor_String { + get { + return GetMethodReference (CorlibAssembly, "System.Exception", ".ctor", (v) => + v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "String") + && !v.HasGenericParameters); + } + } + void AddException (Exception exception) + { + if (exceptions is null) + exceptions = new List (); + exceptions.Add (exception); + } protected override void TryProcess () { @@ -34,6 +1088,18 @@ protected override void TryEndProcess () if (app!.Registrar != RegistrarMode.ManagedStatic) return; + + RewriteRuntimeLookupManagedFunction (); + + if (exceptions is null) + return; + var warnings = exceptions.Where (v => (v as ProductException)?.Error == false).ToArray (); + if (warnings.Length == exceptions.Count) + return; + + if (exceptions.Count == 1) + throw exceptions [0]; + throw new AggregateException (exceptions); } protected override void TryProcessAssembly (AssemblyDefinition assembly) @@ -42,7 +1108,1532 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) if (app!.Registrar != RegistrarMode.ManagedStatic) return; + + if (Annotations.GetAction (assembly) == AssemblyAction.Delete) + return; + + // No SDK assemblies will have anything we need to register + if (Configuration.Profile.IsSdkAssembly (assembly)) + return; + + if (!assembly.MainModule.HasAssemblyReferences) + return; + + // In fact, unless an assembly references our platform assembly, then it won't have anything we need to register + if (!Configuration.Profile.IsProductAssembly (assembly) && !assembly.MainModule.AssemblyReferences.Any (v => Configuration.Profile.IsProductAssembly (v.Name))) + return; + + if (!assembly.MainModule.HasTypes) + return; + + if (corlib_assembly is null) + corlib_assembly = Configuration.Assemblies.Single (v => v.Name.Name == Driver.CorlibName); + + if (platform_assembly is null) + platform_assembly = Configuration.Assemblies.Single (Configuration.Profile.IsProductAssembly); + + current_assembly = assembly; + + current_trampoline_lists = new List (); + trampoline_map.Add (current_assembly, current_trampoline_lists); + + var modified = false; + foreach (var type in assembly.MainModule.Types) + modified |= ProcessType (type); + + // Make sure the linker saves any changes in the assembly. + if (modified) { + CreateRegistrarType (); + Save (assembly); + } + + type_map.Clear (); + method_map.Clear (); + current_assembly = null; } + void Save (AssemblyDefinition assembly) + { + var action = Context.Annotations.GetAction (assembly); + if (action == AssemblyAction.Copy) + Context.Annotations.SetAction (assembly, AssemblyAction.Save); + } + + void CreateRegistrarType () + { + var td = new TypeDefinition ("ObjCRuntime", "__Registrar__", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); + td.BaseType = System_Object; + td.Interfaces.Add (new InterfaceImplementation (ObjCRuntime_IManagedRegistrar)); + CurrentAssembly.MainModule.Types.Add (td); + + // + // The callback methods themselves are all public, and thus accessible from anywhere inside the assembly even if the containing type is not public, as long as the containing type is not nested. + // However, if the containing type is nested inside another type, it gets complicated. + // + // We have two options: + // + // 1. Just change the visibility on the nested type to make it visible inside the assembly. + // 2. Add a method in the containing type (which has access to any directly nested private types) that can look up any unmanaged trampolines. + // If the containing type is also a private nested type, when we'd have to add another method in its containing type, and so on. + // + // The second option is more complicated to implement than the first, so we're doing the first option. If someone + // runs into any problems (there might be with reflection: looking up a type using the wrong visibility will fail to find that type). + // That said, there may be all sorts of problems with reflection (we're adding methods to types, any logic that depends on a type having a certain number of methods will fail for instance). + // + + var sorted = current_trampoline_lists.OrderBy (v => v.Id).ToList (); + foreach (var md in sorted) { + var declType = md.Trampoline.DeclaringType; + while (declType.IsNested) { + if (declType.IsNestedPrivate) { + declType.IsNestedAssembly = true; + } else if (declType.IsNestedFamilyAndAssembly || declType.IsNestedFamily) { + declType.IsNestedFamilyOrAssembly = true; + } + declType = declType.DeclaringType; + } + } + + var defaultCtor = td.AddMethod (".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, System_Void); + defaultCtor.CreateBody (out var il); + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, System_Object__ctor); + il.Emit (OpCodes.Ret); + + GenerateLookupUnmanagedFunction (td, sorted); + GenerateLookupType (td); + GenerateRegisterWrapperTypes (td); + } + + void GenerateLookupType (TypeDefinition type) + { + var method = type.AddMethod ("LookupType", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, System_RuntimeTypeHandle); + method.AddParameter ("id", System_UInt32); + method.Overrides.Add (IManagedRegistrar_LookupType); + var body = method.CreateBody (out var il); + + // switch (id) { + // case 0: return ; + // case 1: return ; + // } + + var types = new List (); + types.AddRange (StaticRegistrar.Types.Select (v => v.Value.Type)); + foreach (var st in StaticRegistrar.SkippedTypes) { + if (!types.Contains (st.Skipped)) + types.Add (st.Skipped); + if (!types.Contains (st.Actual.Type)) + types.Add (st.Actual.Type); + } + types.RemoveAll (v => v.Module.Assembly != current_assembly); + var targets = new Instruction [types.Count]; + + for (var i = 0; i < targets.Length; i++) { + targets [i] = Instruction.Create (OpCodes.Ldtoken, types [i]); + var td = types [i].Resolve (); + Console.WriteLine ($"Registering {td.FullName} => {i}"); + Configuration.RegisteredTypesMap.Add (td, (uint) i); + } + + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Switch, targets); + for (var i = 0; i < targets.Length; i++) { + il.Append (targets [i]); + il.Emit (OpCodes.Ret); + } + + // return default (RuntimeTypeHandle) + var temporary = body.AddVariable (System_RuntimeTypeHandle); + il.Emit (OpCodes.Ldloca, temporary); + il.Emit (OpCodes.Initobj, System_RuntimeTypeHandle); + il.Emit (OpCodes.Ldloc, temporary); + il.Emit (OpCodes.Ret); + } + + void GenerateRegisterWrapperTypes (TypeDefinition type) + { + var method = type.AddMethod ("RegisterWrapperTypes", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, System_Void); + var git = new GenericInstanceType (System_Collections_Generic_Dictionary2); + git.GenericArguments.Add (System_RuntimeTypeHandle); + git.GenericArguments.Add (System_RuntimeTypeHandle); + method.AddParameter ("type", git); + method.Overrides.Add (IManagedRegistrar_RegisterWrapperTypes); + method.CreateBody (out var il); + + var addMethodReference = CreateMethodReferenceOnGenericType (System_Collections_Generic_Dictionary2, Dictionary2_Add, System_RuntimeTypeHandle, System_RuntimeTypeHandle); + var currentTypes = StaticRegistrar.Types.Where (v => v.Value.Type.Resolve ().Module.Assembly == current_assembly); + foreach (var ct in currentTypes) { + if (!ct.Value.IsProtocol) + continue; + if (ct.Value.ProtocolWrapperType is null) + continue; + + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Ldtoken, type.Module.ImportReference (ct.Key)); + il.Emit (OpCodes.Ldtoken, type.Module.ImportReference (ct.Value.ProtocolWrapperType)); + il.Emit (OpCodes.Call, addMethodReference); + } + + il.Emit (OpCodes.Ret); + } + + void GenerateLookupUnmanagedFunction (TypeDefinition type, IList trampolineInfos) + { + Console.WriteLine ($"GenerateLookupMethods ({type.FullName}, {trampolineInfos.Count} items"); + + MethodDefinition? lookupMethods = null; + if (trampolineInfos.Count > 0) { + // All the methods in a given assembly will have consecutive IDs (but might not start at 0). + if (trampolineInfos.First ().Id + trampolineInfos.Count - 1 != trampolineInfos.Last ().Id) + throw ErrorHelper.CreateError (99, "Invalid ID range"); + + const int methodsPerLevel = 10; + var levels = (int) Math.Ceiling (Math.Log (trampolineInfos.Count, methodsPerLevel)); + GenerateLookupMethods (type, trampolineInfos, methodsPerLevel, 1, levels, 0, trampolineInfos.Count - 1, out lookupMethods); + } + + var method = type.AddMethod ("LookupUnmanagedFunction", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, System_IntPtr); + method.AddParameter ("symbol", System_String); + method.AddParameter ("id", System_Int32); + method.Overrides.Add (IManagedRegistrar_LookupUnmanagedFunction); + method.CreateBody (out var il); + if (lookupMethods is null) { + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + } else { + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Ldarg_2); + il.Emit (OpCodes.Call, lookupMethods); + } + il.Emit (OpCodes.Ret); + } + + MethodDefinition GenerateLookupMethods (TypeDefinition type, IList trampolineInfos, int methodsPerLevel, int level, int levels, int startIndex, int endIndex, out MethodDefinition method) + { + Console.WriteLine ($"GenerateLookupMethods ({type.FullName}, {trampolineInfos.Count} items, methodsPerLevel: {methodsPerLevel}, level: {level}, levels: {levels}, startIndex: {startIndex}, endIndex: {endIndex})"); + + if (startIndex > endIndex) + throw new InvalidOperationException ($"Huh 3? startIndex: {startIndex} endIndex: {endIndex}"); + + var startId = trampolineInfos [startIndex].Id; + var name = level == 1 ? "LookupUnmanagedFunctionImpl" : $"LookupUnmanagedFunction_{level}_{levels}__{startIndex}_{endIndex}__"; + method = type.AddMethod (name, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, System_IntPtr); + method.ReturnType = System_IntPtr; // shouldn't be necessary??? + method.AddParameter ("symbol", System_String); + method.AddParameter ("id", System_Int32); + method.CreateBody (out var il); + + if (!UsesUnmanagedMethodLookups) { + // The app is AOT-compiled, and the generated registrar code will call the + // UnmanagedCallersOnly method directly using a native symbol instead of a dynamic lookup. + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + il.Emit (OpCodes.Ret); + return method; + } + + if (level == levels) { + // This is the leaf method where we do the actual lookup. + var wrapLookup = true; + + var targetCount = endIndex - startIndex + 1; + var targets = new Instruction [targetCount]; + for (var i = 0; i < targets.Length; i++) { + var ti = trampolineInfos [startIndex + i]; + var md = ti.Trampoline; + try { + var mr = CurrentAssembly.MainModule.ImportReference (md); + if (wrapLookup) { + var wrappedLookup = type.AddMethod (name + ti.Id, MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, System_IntPtr); + wrappedLookup.CreateBody (out var wrappedIl); + wrappedIl.Emit (OpCodes.Ldftn, mr); + wrappedIl.Emit (OpCodes.Ret); + + targets [i] = Instruction.Create (OpCodes.Call, wrappedLookup); + } else { + targets [i] = Instruction.Create (OpCodes.Ldftn, mr); + } + } catch (Exception e) { + var str = string.Format ("Failed to import reference {0}: {1}", GetMethodSignature (md), e.ToString ()); + AddException (ErrorHelper.CreateError (99, e, str)); + targets [i] = Instruction.Create (OpCodes.Ldstr, str); + } + } + + il.Emit (OpCodes.Ldarg_1); + if (startId != 0) { + il.Emit (OpCodes.Ldc_I4, startId); + il.Emit (OpCodes.Sub_Ovf_Un); + } + il.Emit (OpCodes.Switch, targets); + for (var k = 0; k < targetCount; k++) { + il.Append (targets [k]); + il.Emit (OpCodes.Ret); + } + } else { + // This is an intermediate method to not have too many ldftn instructions in a single method (it takes a long time to JIT). + var chunkSize = (int) Math.Pow (methodsPerLevel, levels - level); + + // Some validation + if (level == 1) { + if (chunkSize * methodsPerLevel < trampolineInfos.Count) + throw new InvalidOperationException ($"Huh 2 -- {chunkSize}?"); + } + + var count = endIndex - startIndex + 1; + var chunks = (int) Math.Ceiling (count / (double) chunkSize); + var targets = new Instruction [chunks]; + + Console.WriteLine ($"GenerateLookupMethods ({type.FullName}, {trampolineInfos.Count} items, methodsPerLevel: {methodsPerLevel}, level: {level}, levels: {levels}, startIndex: {startIndex}, endIndex: {endIndex}) count: {count} chunks: {chunks} chunkSize: {chunkSize}"); + + var lookupMethods = new MethodDefinition [targets.Length]; + for (var i = 0; i < targets.Length; i++) { + try { + var subStartIndex = startIndex + (chunkSize) * i; + var subEndIndex = subStartIndex + (chunkSize) - 1; + if (subEndIndex > endIndex) + subEndIndex = endIndex; + var md = GenerateLookupMethods (type, trampolineInfos, methodsPerLevel, level + 1, levels, subStartIndex, subEndIndex, out _); + lookupMethods [i] = md; + targets [i] = Instruction.Create (OpCodes.Ldarg_0); + } catch (Exception e) { + var str = string.Format ("Failed to generate nested lookup method: {0}", e.ToString ()); + AddException (ErrorHelper.CreateError (99, e, str)); + targets [i] = Instruction.Create (OpCodes.Ldstr, str); + } + } + + il.Emit (OpCodes.Ldarg_1); + if (startId != 0) { + il.Emit (OpCodes.Ldc_I4, startId); + il.Emit (OpCodes.Sub_Ovf_Un); + } + il.Emit (OpCodes.Ldc_I4, chunkSize); + il.Emit (OpCodes.Div); + il.Emit (OpCodes.Switch, targets); + for (var k = 0; k < targets.Length; k++) { + il.Append (targets [k]); // OpCodes.Ldarg_0 + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Call, lookupMethods [k]); + il.Emit (OpCodes.Ret); + } + } + + // no hit? this shouldn't happen + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + il.Emit (OpCodes.Ret); + + return method; + } + + void RewriteRuntimeLookupManagedFunction () + { + current_assembly = PlatformAssembly; + + var method = GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "LookupManagedFunctionImpl").Resolve (); + var table = Configuration.UnmanagedCallersMap.ToList ().OrderBy (v => v.Value.Id).ToList (); + + Console.WriteLine ($"Creating table for {table.Count} entries YAAY"); + + // Create second-level methods. + var lookupsPerMethod = 100; + var secondLevelMethodCount = (table.Count + lookupsPerMethod - 1) / lookupsPerMethod; + var secondLevelMethods = new MethodDefinition [secondLevelMethodCount]; + var indirectLookup = true; + for (var i = 0; i < secondLevelMethodCount; i++) { + var secondLevelMethod = method.DeclaringType.AddMethod ("LookupManagedFunctionImpl" + i.ToString (), MethodAttributes.Static | MethodAttributes.Private, method.ReturnType); + secondLevelMethod.AddParameter ("index", method.Parameters [0].ParameterType); + secondLevelMethods [i] = secondLevelMethod; + + var body = new MethodBody (secondLevelMethod); + secondLevelMethod.Body = body; + var il = body.GetILProcessor (); + il.Clear (); + + var secondLevelMethodLookupCount = i == secondLevelMethodCount - 1 ? table.Count % lookupsPerMethod : lookupsPerMethod; + var targets = new Instruction [secondLevelMethodLookupCount]; + for (var k = 0; k < secondLevelMethodLookupCount; k++) { + var index = i * lookupsPerMethod + k; + var md = table [index].Value.UnmanagedCallersMethod; + try { + var mr = PlatformAssembly.MainModule.ImportReference (md); + if (indirectLookup) { + var indirectMethod = method.DeclaringType.AddMethod (md.Name + "__indirect_lookup", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, secondLevelMethod.ReturnType); + var indirectIL = indirectMethod.Body.GetILProcessor (); + indirectIL.Emit (OpCodes.Ldftn, mr); + indirectIL.Emit (OpCodes.Ret); + targets [k] = Instruction.Create (OpCodes.Call, indirectMethod); + } else { + targets [k] = Instruction.Create (OpCodes.Ldftn, mr); + } + } catch (Exception e) { + var str = string.Format ("Failed to import reference {0}: {1}", GetMethodSignature (md), e.ToString ()); + AddException (ErrorHelper.CreateError (99, e, str)); + targets [k] = Instruction.Create (OpCodes.Ldstr, str); + } + } + + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Switch, targets); + for (var k = 0; k < secondLevelMethodLookupCount; k++) { + il.Append (targets [k]); + il.Emit (OpCodes.Ret); + } + + // no hit? this shouldn't happen + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + il.Emit (OpCodes.Ret); + } + + // Create first-level method + { + var body = new MethodBody (method); + method.Body = body; + var il = body.GetILProcessor (); + il.Clear (); + + var targets = new Instruction [secondLevelMethodCount]; + var returnStatement = Instruction.Create (OpCodes.Ret); + for (var i = 0; i < targets.Length; i++) { + targets [i] = Instruction.Create (OpCodes.Call, secondLevelMethods [i]); + } + + il.Emit (OpCodes.Ldarg_0); + if (lookupsPerMethod <= sbyte.MaxValue) { + il.Emit (OpCodes.Ldc_I4_S, (sbyte) lookupsPerMethod); + } else { + il.Emit (OpCodes.Ldc_I4, lookupsPerMethod); + } + il.Emit (OpCodes.Rem); + il.Emit (OpCodes.Ldarg_0); + if (lookupsPerMethod <= sbyte.MaxValue) { + il.Emit (OpCodes.Ldc_I4_S, (sbyte) lookupsPerMethod); + } else { + il.Emit (OpCodes.Ldc_I4, lookupsPerMethod); + } + il.Emit (OpCodes.Div); + il.Emit (OpCodes.Switch, targets); + for (var i = 0; i < targets.Length; i++) { + il.Append (targets [i]); + il.Emit (OpCodes.Ret); + il.Emit (OpCodes.Br, returnStatement); + } + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + il.Append (returnStatement); + } + + Save (PlatformAssembly); + current_assembly = null; + } + + bool ProcessType (TypeDefinition type) + { + var modified = false; + if (type.HasNestedTypes) { + foreach (var nested in type.NestedTypes) + modified |= ProcessType (nested); + } + + var process = false; + + process |= IsNSObject (type); + process |= StaticRegistrar.GetCategoryAttribute (type) is not null; + + var registerAttribute = StaticRegistrar.GetRegisterAttribute (type); + if (registerAttribute is not null && registerAttribute.IsWrapper) + return modified; + + if (!process) + return modified; + + var methods_to_wrap = new HashSet (); + if (type.HasMethods) { + foreach (var method in type.Methods) + ProcessMethod (method, methods_to_wrap); + } + + if (type.HasProperties) { + foreach (var prop in type.Properties) { + ProcessProperty (prop, methods_to_wrap); + } + } + + foreach (var method in methods_to_wrap) { + try { + CreateUnmanagedCallersMethod (method); + } catch (Exception e) { + Console.WriteLine (e); + AddException (ErrorHelper.CreateError (99, e, "Failed process {0}: {1}", method.FullName, e.Message)); + } + } + + return true; + } + + void ProcessMethod (MethodDefinition method, HashSet methods_to_wrap) + { + if (!(method.IsConstructor && !method.IsStatic)) { + var ea = StaticRegistrar.GetExportAttribute (method); + if (ea is null && !method.IsVirtual) + return; + } + + if (!StaticRegistrar.TryFindMethod (method, out _)) { + Console.WriteLine ("Could not find method {0}, so no generating trampoline.", GetMethodSignature (method)); + return; + } + + methods_to_wrap.Add (method); + } + + void ProcessProperty (PropertyDefinition property, HashSet methods_to_wrap) + { + var ea = StaticRegistrar.GetExportAttribute (property); + if (ea is null) + return; + + if (property.GetMethod is not null) + methods_to_wrap.Add (property.GetMethod); + + if (property.SetMethod is not null) + methods_to_wrap.Add (property.SetMethod); + } + + static string Sanitize (string str) + { + // 😐... + str = str.Replace ('.', '_'); + str = str.Replace ('/', '_'); + str = str.Replace ('\\', '_'); + str = str.Replace ('`', '_'); + str = str.Replace ('<', '_'); + str = str.Replace ('>', '_'); + str = str.Replace ('$', '_'); + str = str.Replace ('@', '_'); + return str; + } + + void Trace (ILProcessor il, string message) + { + var trace = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("MSR_TRACE")); + if (trace) { + il.Emit (OpCodes.Ldstr, message); + il.Emit (OpCodes.Call, Runtime_TraceCaller); + } + } + + int counter; + void CreateUnmanagedCallersMethod (MethodDefinition method) + { + var baseMethod = StaticRegistrar.GetBaseMethodInTypeHierarchy (method); + var placeholderType = System_IntPtr; + var initialExceptionCount = exceptions.Count; + ParameterDefinition? callSuperParameter = null; + VariableDefinition? returnVariable = null; + var leaveTryInstructions = new List (); + var isVoid = method.ReturnType.Is ("System", "Void"); + + var name = $"callback_{counter++}_{Sanitize (method.DeclaringType.FullName)}_{Sanitize (method.Name)}"; + + var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); + if (callbackType is null) { + callbackType = new TypeDefinition (string.Empty, "__Registrar_Callbacks__", TypeAttributes.NestedPrivate | TypeAttributes.Sealed | TypeAttributes.Class); + callbackType.BaseType = System_Object; + method.DeclaringType.NestedTypes.Add (callbackType); + } + + var callback = callbackType.AddMethod (name, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, placeholderType); + callback.CustomAttributes.Add (CreateUnmanagedCallersAttribute (name)); + var entry = new LinkerConfiguration.UnmanagedCallersEntry (name, Configuration.UnmanagedCallersMap.Count, callback); + Configuration.UnmanagedCallersMap.Add (method, entry); + current_trampoline_lists.Add (new TrampolineInfo (callback, method, entry.Id)); + + // FIXME + var t = method.DeclaringType; + while (t.IsNested) { + t.IsNestedPublic = true; + t = t.DeclaringType; + } + t.IsPublic = true; + // END FIXME + + var body = callback.CreateBody (out var il); + var placeholderInstruction = il.Create (OpCodes.Nop); + var placeholderNextInstruction = il.Create (OpCodes.Nop); + var postProcessing = new List (); + var categoryAttribute = StaticRegistrar.GetCategoryAttribute (method.DeclaringType); + var isCategory = categoryAttribute is not null; + var isInstanceCategory = isCategory && StaticRegistrar.HasThisAttribute (method); + var isGeneric = method.DeclaringType.HasGenericParameters; + VariableDefinition? selfVariable = null; + try { + Trace (il, $"ENTER"); + + if (method.IsConstructor) { + callback.AddParameter ("pobj", ObjCRuntime_NativeHandle); + } else { + callback.AddParameter ("pobj", System_IntPtr); + } + + if (!isVoid || method.IsConstructor) + returnVariable = body.AddVariable (placeholderType); + + if (isGeneric) { + if (method.IsStatic) + throw new NotImplementedException (); // probably an error? + + il.Emit (OpCodes.Ldtoken, method); + // il.Emit (OpCodes.Ldtoken, Console_WriteLine); // DUMMY METHOD + + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable); + + selfVariable = body.AddVariable (System_Object); + il.Emit (OpCodes.Stloc, selfVariable); + il.Emit (OpCodes.Ldloc, selfVariable); + // FIXME: throw if null + // FIXME: can only be NSObject + il.Emit (OpCodes.Ldtoken, method.DeclaringType); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, Runtime_FindClosedMethod); + } + + if (isInstanceCategory) { + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.Parameters [0].ParameterType, true, 0, out var nativeType, postProcessing, selfVariable); + } else if (method.IsStatic) { + // nothing to do + } else if (method.IsConstructor) { + callSuperParameter = new ParameterDefinition ("call_super", ParameterAttributes.None, new PointerType (System_Byte)); + var callAllocateNSObject = il.Create (OpCodes.Ldarg_0); + // if (Runtime.HasNSObject (p0)) { + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, Runtime_HasNSObject); + il.Emit (OpCodes.Brfalse, callAllocateNSObject); + // *call_super = 1; + il.Emit (OpCodes.Ldarg, callSuperParameter); + il.Emit (OpCodes.Ldc_I4_1); + il.Emit (OpCodes.Stind_I1); + // return rv; + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Stloc, returnVariable); + il.Emit (OpCodes.Leave, placeholderInstruction); + // } + leaveTryInstructions.Add (il.Body.Instructions.Last ()); + + var git = new GenericInstanceMethod (NSObject_AllocateNSObject); + git.GenericArguments.Add (method.DeclaringType); + il.Append (callAllocateNSObject); // ldarg_0 + il.Emit (OpCodes.Ldc_I4_2); // NSObject.Flags.NativeRef + 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 { + // instance method + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable); + //if (nativeType != callback.Parameters [0].ParameterType) + // AddException (ErrorHelper.CreateWarning (99, "Unexpected parameter type for the first parameter. Expected {0}, got {1}. Method: {2}", callback.Parameters [0].ParameterType.FullName, nativeType?.FullName, GetMethodSignatureWithSourceCode (method))); + } + + callback.AddParameter ("sel", System_IntPtr); + + var managedParameterCount = 0; + var nativeParameterOffset = isInstanceCategory ? 1 : 2; + var parameterStart = isInstanceCategory ? 1 : 0; + if (method.HasParameters) + managedParameterCount = method.Parameters.Count; + + if (isGeneric) { + il.Emit (OpCodes.Ldc_I4, managedParameterCount); + il.Emit (OpCodes.Newarr, System_Object); + } + + if (method.HasParameters) { + var isDynamicInvoke = isGeneric; + for (var p = parameterStart; p < managedParameterCount; p++) { + var nativeParameter = callback.AddParameter ($"p{p}", placeholderType); + var nativeParameterIndex = p + nativeParameterOffset; + var managedParameterType = method.Parameters [p].ParameterType; + var baseParameter = baseMethod.Parameters [p]; + var isOutParameter = IsOutParameter (method, p, baseParameter); + if (isDynamicInvoke && !isOutParameter) { + if (parameterStart != 0) + throw new NotImplementedException ("parameterStart"); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Ldc_I4, p); + } + if (!isOutParameter) { + il.EmitLoadArgument (nativeParameterIndex); + } + if (EmitConversion (method, il, managedParameterType, true, p, out var nativeType, postProcessing, selfVariable, isOutParameter, nativeParameterIndex, isDynamicInvoke)) { + nativeParameter.ParameterType = nativeType; + } else { + nativeParameter.ParameterType = placeholderType; + AddException (ErrorHelper.CreateError (99, "Unable to emit conversion for parameter {2} of type {0}. Method: {1}", method.Parameters [p].ParameterType, GetMethodSignatureWithSourceCode (method), p)); + } + if (isDynamicInvoke && !isOutParameter) { + if (managedParameterType.IsValueType) + il.Emit (OpCodes.Box, managedParameterType); + il.Emit (OpCodes.Stelem_Ref); + } + } + } + + if (callSuperParameter is not null) + callback.Parameters.Add (callSuperParameter); + + callback.AddParameter ("exception_gchandle", new PointerType (System_IntPtr)); + + if (isGeneric) { + il.Emit (OpCodes.Call, MethodBase_Invoke); + if (isVoid) { + il.Emit (OpCodes.Pop); + } else if (method.ReturnType.IsValueType) { + il.Emit (OpCodes.Unbox_Any, method.ReturnType); + } else { + // il.Emit (OpCodes.Castclass, method.ReturnType); + } + } else if (method.IsStatic) { + il.Emit (OpCodes.Call, method); + } else { + il.Emit (OpCodes.Callvirt, method); + } + + if (returnVariable is not null) { + if (EmitConversion (method, il, method.ReturnType, false, -1, out var nativeReturnType, postProcessing, selfVariable)) { + returnVariable.VariableType = nativeReturnType; + callback.ReturnType = nativeReturnType; + } else { + AddException (ErrorHelper.CreateError (99, "Unable to emit conversion for return value of type {0}. Method: {1}", method.ReturnType, GetMethodSignatureWithSourceCode (method))); + } + il.Emit (OpCodes.Stloc, returnVariable); + } else { + callback.ReturnType = System_Void; + } + + body.Instructions.AddRange (postProcessing); + + Trace (il, $"EXIT"); + + il.Emit (OpCodes.Leave, placeholderInstruction); + leaveTryInstructions.Add (il.Body.Instructions.Last ()); + + AddExceptionHandler (il, returnVariable, placeholderNextInstruction, out var eh, out var leaveEHInstruction); + + // Generate code to return null/default value/void + if (returnVariable is not null) { + var returnType = returnVariable.VariableType!; + if (returnType.IsValueType) { + // return default() + il.Emit (OpCodes.Ldloca, returnVariable); + il.Emit (OpCodes.Initobj, returnType); + il.Emit (OpCodes.Ldloc, returnVariable); + } else { + il.Emit (OpCodes.Ldnull); + } + } + il.Emit (OpCodes.Ret); + + // Generate code to return the return value + Instruction leaveTryInstructionOperand; + if (returnVariable is not null) { + il.Emit (OpCodes.Ldloc, returnVariable); + leaveTryInstructionOperand = il.Body.Instructions.Last (); + il.Emit (OpCodes.Ret); + } else { + // Here we can re-use the ret instruction from the previous block. + leaveTryInstructionOperand = il.Body.Instructions.Last (); + } + + // Replace any 'placeholderNextInstruction' operands with the actual next instruction. + foreach (var instr in body.Instructions) { + if (object.ReferenceEquals (instr.Operand, placeholderNextInstruction)) + instr.Operand = instr.Next; + } + + foreach (var instr in leaveTryInstructions) + instr.Operand = leaveTryInstructionOperand; + eh.HandlerEnd = (Instruction) leaveEHInstruction.Operand; + + if (exceptions.Count != initialExceptionCount) { + var newExceptions = exceptions.Skip (initialExceptionCount); + body.Instructions.Insert (0, il.Create (OpCodes.Ldstr, $"Conversion not implemented. Exceptions during process:\n\t{string.Join ("\n\t", newExceptions.Select (v => v.ToString ()))}")); + body.Instructions.Insert (1, il.Create (OpCodes.Newobj, Exception_ctor_String)); + body.Instructions.Insert (2, il.Create (OpCodes.Throw)); + while (body.Instructions [3] != eh.TryEnd) + body.Instructions.RemoveAt (3); + exceptions.RemoveRange (initialExceptionCount, exceptions.Count - initialExceptionCount); + } + } catch (Exception e) { + il.Emit (OpCodes.Ldstr, $"Exception occurred while creating trampoline: " + e); + throw; + } + } + + void AddExceptionHandler (ILProcessor il, VariableDefinition? returnVariable, Instruction placeholderNextInstruction, out ExceptionHandler eh, out Instruction leaveEHInstruction) + { + var body = il.Body; + var method = body.Method; + + // Exception handler + eh = new ExceptionHandler (ExceptionHandlerType.Catch); + eh.CatchType = System_Exception; + eh.TryStart = il.Body.Instructions [0]; + il.Body.ExceptionHandlers.Add (eh); + + var exceptionVariable = body.AddVariable (System_Exception); + il.Emit (OpCodes.Stloc, exceptionVariable); + eh.HandlerStart = il.Body.Instructions.Last (); + eh.TryEnd = eh.HandlerStart; + il.Emit (OpCodes.Ldarg, method.Parameters.Count - 1); + il.Emit (OpCodes.Ldloc, exceptionVariable); + il.Emit (OpCodes.Call, Runtime_AllocGCHandle); + il.Emit (OpCodes.Stind_I); + Trace (il, $"EXCEPTION"); + il.Emit (OpCodes.Leave, placeholderNextInstruction); + leaveEHInstruction = body.Instructions.Last (); + + // Generate code to return null/default value/void + if (returnVariable is not null) { + var returnType = returnVariable.VariableType!; + if (returnType.IsValueType) { + // return default() + il.Emit (OpCodes.Ldloca, returnVariable); + il.Emit (OpCodes.Initobj, returnType); + il.Emit (OpCodes.Ldloc, returnVariable); + } else { + il.Emit (OpCodes.Ldnull); + } + } + il.Emit (OpCodes.Ret); + } + + 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 ())})"; + } + + static string GetMethodSignatureWithSourceCode (MethodDefinition method) + { + var rv = GetMethodSignature (method); + if (method.HasBody && method.DebugInformation.HasSequencePoints) { + var seq = method.DebugInformation.SequencePoints [0]; + rv += " " + seq.Document.Url + ":" + seq.StartLine.ToString () + " "; + } + return rv; + } + + bool IsNSObject (TypeReference type) + { + if (type is ArrayType) + return false; + + if (type is ByReferenceType) + return false; + + if (type is PointerType) + return false; + + if (type is GenericParameter) + return false; + + return type.IsNSObject (DerivedLinkContext); + } + + BindAsAttribute? GetBindAsAttribute (MethodDefinition method, int parameter) + { + if (StaticRegistrar.IsPropertyAccessor (method, out var property)) { + return StaticRegistrar.GetBindAsAttribute (property); + } else { + return StaticRegistrar.GetBindAsAttribute (method, parameter); + } + } + + // This emits a conversion between the native and the managed representation of a parameter or return value, + // and returns the corresponding native type. The returned nativeType will (must) be a blittable type. + bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type, bool toManaged, int parameter, [NotNullWhen (true)] out TypeReference? nativeType, List postProcessing, VariableDefinition? selfVariable, bool isOutParameter = false, int nativeParameterIndex = -1, bool isDynamicInvoke = false) + { + nativeType = null; + + if (!(parameter == -1 && !method.IsStatic && method.DeclaringType == type)) { + var bindAsAttribute = GetBindAsAttribute (method, parameter); + if (bindAsAttribute is not null) { + if (toManaged) { + // if (parameter != -1) { + GenerateConversionToManaged (method, il, bindAsAttribute.OriginalType, type, "descriptiveMethodName", parameter, out nativeType); + return true; + // } + } else { + GenerateConversionToNative (method, il, type, bindAsAttribute.OriginalType, "descriptiveMethodName", out nativeType); + return true; + } + } + } + + if (type.Is ("System", "Void")) { + if (parameter == -1 && method.IsConstructor) { + if (toManaged) { + AddException (ErrorHelper.CreateError (99, "Don't know how (9) to convert ctor. Method: {0}", GetMethodSignatureWithSourceCode (method))); + } else { + il.Emit (OpCodes.Call, NativeObjectExtensions_GetHandle); + nativeType = ObjCRuntime_NativeHandle; + return true; + } + } + AddException (ErrorHelper.CreateError (99, "Can't convert System.Void. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (type.IsValueType) { + if (type.Is ("System", "Boolean")) { + if (toManaged) { + // nothing to do I think + } else { + // FIXME: verify if this sequence is really necessary. + var ldc_1 = il.Create (OpCodes.Ldc_I4_1); + var nop = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Brtrue_S, ldc_1); + il.Emit (OpCodes.Ldc_I4_0); + il.Emit (OpCodes.Br_S, nop); + il.Append (ldc_1); + il.Append (nop); + } + nativeType = System_Byte; + return true; + } + + // no conversion necessary if we're any other value type + nativeType = type; + return true; + } + + if (type is PointerType pt) { + var elementType = pt.ElementType; + if (!elementType.IsValueType) + AddException (ErrorHelper.CreateError (99, "Unexpected pointer type {0}: must be a value type. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); + // no conversion necessary either way + nativeType = type; + return true; + } + + if (type is ByReferenceType brt) { + if (toManaged) { + var elementType = brt.ElementType; + if (elementType is GenericParameter gp) { + if (!StaticRegistrar.VerifyIsConstrainedToNSObject (gp, out var constrained)) { + AddException (ErrorHelper.CreateError (99, "Incorrectly constrained generic parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + elementType = constrained; + } + + if (elementType.IsValueType) { + // call !!0& [System.Runtime]System.Runtime.CompilerServices.Unsafe::AsRef(void*) + var mr = new GenericInstanceMethod (CurrentAssembly.MainModule.ImportReference (Unsafe_AsRef)); + if (isOutParameter) + il.EmitLoadArgument (nativeParameterIndex); + mr.GenericArguments.Add (elementType); + il.Emit (OpCodes.Call, mr); + // reference types aren't blittable, so the managed signature must have be a pointer type + nativeType = new PointerType (elementType); + return true; + } + + MethodReference? native_to_managed = null; + MethodReference? managed_to_native = null; + Instruction? addBeforeNativeToManagedCall = null; + + if (elementType is ArrayType elementArrayType) { + // TODO: verify elementArrayType.ElementType? + if (elementArrayType.ElementType.Is ("System", "String")) { + native_to_managed = RegistrarHelper_NSArray_string_native_to_managed; + managed_to_native = RegistrarHelper_NSArray_string_managed_to_native; + } else { + native_to_managed = CreateGenericInstanceMethod (RegistrarHelper_NSArray_native_to_managed, elementArrayType.ElementType); + managed_to_native = CreateGenericInstanceMethod (RegistrarHelper_NSArray_managed_to_native, elementArrayType.ElementType); + } + nativeType = new PointerType (ObjCRuntime_NativeHandle); + } else if (elementType.Is ("System", "String")) { + native_to_managed = RegistrarHelper_string_native_to_managed; + managed_to_native = RegistrarHelper_string_managed_to_native; + nativeType = new PointerType (ObjCRuntime_NativeHandle); + } else if (elementType.IsNSObject (DerivedLinkContext)) { + native_to_managed = CreateGenericInstanceMethod (RegistrarHelper_NSObject_native_to_managed, elementType); + managed_to_native = RegistrarHelper_NSObject_managed_to_native; + nativeType = new PointerType (System_IntPtr); + } else if (StaticRegistrar.IsNativeObject (DerivedLinkContext, elementType)) { + var nativeObjType = StaticRegistrar.GetInstantiableType (type.Resolve (), exceptions, GetMethodSignature (method)); + addBeforeNativeToManagedCall = il.Create (OpCodes.Ldtoken, method.Module.ImportReference (nativeObjType)); // implementation type + native_to_managed = CreateGenericInstanceMethod (RegistrarHelper_INativeObject_native_to_managed, elementType); + managed_to_native = RegistrarHelper_INativeObject_managed_to_native; + nativeType = new PointerType (System_IntPtr); + } else { + AddException (ErrorHelper.CreateError (99, "Don't know how (4) to convert {0} between managed and native code. Method: {1}", type.FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (managed_to_native is not null && native_to_managed is not null) { + EnsureVisible (method, managed_to_native); + EnsureVisible (method, native_to_managed); + + var indirectVariable = il.Body.AddVariable (elementType); + // We store a copy of the value in a separate variable, to detect if it changes. + var copyIndirectVariable = il.Body.AddVariable (elementType); + + // We don't read the input for 'out' parameters, it might be garbage. + if (!isOutParameter) { + il.Emit (OpCodes.Ldloca, indirectVariable); + il.Emit (OpCodes.Ldloca, copyIndirectVariable); + if (addBeforeNativeToManagedCall is not null) + il.Append (addBeforeNativeToManagedCall); + il.Emit (OpCodes.Call, native_to_managed); + if (isDynamicInvoke) { + il.Emit (OpCodes.Ldloc, indirectVariable); + } else { + il.Emit (OpCodes.Ldloca, indirectVariable); + } + } else { + if (!isDynamicInvoke) + il.Emit (OpCodes.Ldloca, indirectVariable); + } + postProcessing.Add (il.CreateLoadArgument (nativeParameterIndex)); + postProcessing.Add (il.Create (OpCodes.Ldloc, indirectVariable)); + postProcessing.Add (il.Create (OpCodes.Ldloc, copyIndirectVariable)); + postProcessing.Add (il.Create (isOutParameter)); + postProcessing.Add (il.Create (OpCodes.Call, managed_to_native)); + return true; + } + } + + AddException (ErrorHelper.CreateError (99, "Don't know how (2) to convert {0} between managed and native code. Method: {1}", type.FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (isOutParameter) + throw new InvalidOperationException ($"Parameter must be ByReferenceType to be an out parameter"); + + if (type is ArrayType at) { + var elementType = at.GetElementType (); + if (elementType.Is ("System", "String")) { + il.Emit (OpCodes.Call, toManaged ? CFArray_StringArrayFromHandle : CFArray_Create); + nativeType = ObjCRuntime_NativeHandle; + return true; + } + + var isGenericParameter = false; + if (elementType is GenericParameter gp) { + if (!StaticRegistrar.VerifyIsConstrainedToNSObject (gp, out var constrained)) { + AddException (ErrorHelper.CreateError (99, "Incorrectly constrained generic parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + elementType = constrained; + isGenericParameter = true; + } + + var isNSObject = elementType.IsNSObject (DerivedLinkContext); + var isNativeObject = StaticRegistrar.IsNativeObject (elementType); + if (isNSObject || isNativeObject) { + if (toManaged) { + if (isGenericParameter) { + il.Emit (OpCodes.Ldloc, selfVariable); + il.Emit (OpCodes.Ldtoken, method.DeclaringType); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Ldc_I4, parameter); + il.Emit (OpCodes.Call, Runtime_FindClosedParameterType); + il.Emit (OpCodes.Call, NSArray_ArrayFromHandle); + } else { + var gim = new GenericInstanceMethod (NSArray_ArrayFromHandle_1); + gim.GenericArguments.Add (elementType); + il.Emit (OpCodes.Call, gim); + } + } else { + var retain = StaticRegistrar.HasReleaseAttribute (method); + il.Emit (retain ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + il.Emit (OpCodes.Call, RegistrarHelper_ManagedArrayToNSArray); + } + nativeType = ObjCRuntime_NativeHandle; + return true; + } + + AddException (ErrorHelper.CreateError (99, "Don't know how (3) to convert array element type {1} for array type {0} between managed and native code. Method: {2}", type.FullName, elementType.FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (IsNSObject (type)) { + if (toManaged) { + if (type is GenericParameter gp || type is GenericInstanceType || type.HasGenericParameters) { + il.Emit (OpCodes.Call, Runtime_GetNSObject__System_IntPtr); + } else { + // FIXME: argument semantics + il.Emit (OpCodes.Ldarg_1); // SEL + il.Emit (OpCodes.Ldtoken, method); + il.Emit (parameter == -1); // evenInFinalizerQueue + il.Emit (OpCodes.Call, CreateGenericInstanceMethod (Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool, type)); + var tmpVariable = il.Body.AddVariable (type); + il.Emit (OpCodes.Stloc, tmpVariable); + il.Emit (OpCodes.Ldloc, tmpVariable); + } + nativeType = System_IntPtr; + } else { + if (parameter == -1) { + var retain = StaticRegistrar.HasReleaseAttribute (method); + il.Emit (OpCodes.Dup); + if (retain) { + il.Emit (OpCodes.Call, Runtime_RetainNSObject); + } else { + il.Emit (OpCodes.Call, Runtime_RetainAndAutoreleaseNSObject); + } + } else { + il.Emit (OpCodes.Call, NativeObjectExtensions_GetHandle); + } + nativeType = ObjCRuntime_NativeHandle; + } + return true; + } + + if (StaticRegistrar.IsNativeObject (DerivedLinkContext, type)) { + if (toManaged) { + if (type is GenericParameter gp) { + // FIXME: check that gp is constrained to NSObject + // il.Emit (OpCodes.Ldarg_1); + // il.Emit (OpCodes.Ldtoken, method); + // il.Emit (OpCodes.Call, CreateGenericInstanceMethod (Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool, type)); + // il.Emit (OpCodes.Call, CreateGenericInstanceMethod (Runtime_GetNSObject_T___System_IntPtr, type)); + il.Emit (OpCodes.Call, Runtime_GetNSObject__System_IntPtr); + } else { + var nativeObjType = StaticRegistrar.GetInstantiableType (type.Resolve (), exceptions, GetMethodSignature (method)); + il.Emit (OpCodes.Ldc_I4_0); // false + il.Emit (OpCodes.Ldtoken, method.Module.ImportReference (type)); // target type + il.Emit (OpCodes.Call, Type_GetTypeFromHandle); + il.Emit (OpCodes.Ldtoken, method.Module.ImportReference (nativeObjType)); // implementation type + il.Emit (OpCodes.Call, Type_GetTypeFromHandle); + il.Emit (OpCodes.Call, Runtime_GetINativeObject__IntPtr_Boolean_Type_Type); + il.Emit (OpCodes.Castclass, type); + } + nativeType = System_IntPtr; + } else { + if (parameter == -1) { + var retain = StaticRegistrar.HasReleaseAttribute (method); + var isNSObject = IsNSObject (type); + if (retain) { + il.Emit (OpCodes.Call, isNSObject ? Runtime_RetainNSObject : Runtime_RetainNativeObject); + } else { + il.Emit (OpCodes.Call, isNSObject ? Runtime_RetainAndAutoreleaseNSObject : Runtime_RetainAndAutoreleaseNativeObject); + } + } else { + il.Emit (OpCodes.Call, NativeObjectExtensions_GetHandle); + } + nativeType = ObjCRuntime_NativeHandle; + } + return true; + } + + if (type.Is ("System", "String")) { + il.Emit (OpCodes.Call, toManaged ? CFString_FromHandle : CFString_CreateNative); + nativeType = ObjCRuntime_NativeHandle; + return true; + } + + if (StaticRegistrar.IsDelegate (type.Resolve ())) { + if (!StaticRegistrar.TryFindMethod (method, out var objcMethod)) { + AddException (ErrorHelper.CreateError (99, "Unable to find method {0}", GetMethodSignature (method))); + return false; + } + if (toManaged) { + var createMethod = StaticRegistrar.GetBlockWrapperCreator (objcMethod, parameter); + if (createMethod is null) { + AddException (ErrorHelper.CreateWarning (App, 4174 /* Unable to locate the block to delegate conversion method for the method {0}'s parameter #{1}. */, method, Errors.MT4174, method.FullName, parameter + 1)); + var tmpVariable = il.Body.AddVariable (System_IntPtr); + il.Emit (OpCodes.Stloc, tmpVariable); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, MethodBase_GetMethodFromHandle); + il.Emit (OpCodes.Castclass, System_Reflection_MethodInfo); + il.Emit (OpCodes.Ldc_I4, parameter); + il.Emit (OpCodes.Call, Runtime_GetBlockWrapperCreator); + il.Emit (OpCodes.Ldloc, tmpVariable); + il.Emit (OpCodes.Call, Runtime_CreateBlockProxy); + } else { + EnsureVisible (method, createMethod); + il.Emit (OpCodes.Call, BlockLiteral_Copy); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Call, method.Module.ImportReference (createMethod)); + il.Emit (OpCodes.Call, Runtime_ReleaseBlockWhenDelegateIsCollected); + } + } else { + FieldDefinition? delegateProxyField = null; + MethodDefinition? createBlockMethod = null; + + if (!DerivedLinkContext.StaticRegistrar.TryComputeBlockSignature (method, trampolineDelegateType: type, out var exception, out var signature)) { + AddException (ErrorHelper.CreateWarning (99, "Error while converting block/delegates: FIXME better error: {0}", exception.ToString ())); + } else { + var delegateProxyType = StaticRegistrar.GetDelegateProxyType (objcMethod); + if (delegateProxyType is null) { + exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method, Errors.MT4176 /* "Unable to locate the delegate to block conversion type for the return value of the method {0}." */, method.FullName)); + } else { + createBlockMethod = StaticRegistrar.GetCreateBlockMethod (delegateProxyType); + if (createBlockMethod is null) { + delegateProxyField = delegateProxyType.Fields.SingleOrDefault (v => v.Name == "Handler"); + if (delegateProxyField is null) { + AddException (ErrorHelper.CreateWarning (99, "No delegate proxy field on {0}", delegateProxyType.FullName)); // FIXME: better error message + } + } + } + } + + // the delegate is already on the stack + if (createBlockMethod is not null) { + EnsureVisible (method, createBlockMethod); + il.Emit (OpCodes.Call, method.Module.ImportReference (createBlockMethod)); + il.Emit (OpCodes.Call, RegistrarHelper_GetBlockPointer); + } else if (delegateProxyField is not null) { + EnsureVisible (method, delegateProxyField); + il.Emit (OpCodes.Ldsfld, method.Module.ImportReference (delegateProxyField)); + il.Emit (OpCodes.Ldstr, signature); + il.Emit (OpCodes.Call, BlockLiteral_CreateBlockForDelegate); + } else { + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, RegistrarHelper_GetBlockForDelegate); + } + } + nativeType = System_IntPtr; + return true; + } + + AddException (ErrorHelper.CreateError (99, "Don't know how (1) to convert {0} between managed and native code: {1}. Method: {2}", type.FullName, type.GetType ().FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + void EnsureVisible (MethodDefinition caller, FieldDefinition field) + { + field.IsPublic = true; + EnsureVisible (caller, field.DeclaringType); + } + + void EnsureVisible (MethodDefinition caller, TypeDefinition type) + { + if (type.IsNested) { + type.IsNestedPublic = true; + EnsureVisible (caller, type.DeclaringType); + } else { + type.IsPublic = true; + } + } + + void EnsureVisible (MethodDefinition caller, MethodReference method) + { + var md = method.Resolve (); + md.IsPublic = true; + EnsureVisible (caller, md.DeclaringType); + } + + bool IsOutParameter (MethodDefinition method, int parameter, ParameterDefinition baseParameter) + { + return method.Parameters [parameter].IsOut || baseParameter.IsOut; + } + + StaticRegistrar StaticRegistrar { + get { return DerivedLinkContext.StaticRegistrar; } + } + + CustomAttribute CreateUnmanagedCallersAttribute (string entryPoint) + { + var unmanagedCallersAttribute = new CustomAttribute (UnmanagedCallersOnlyAttribute_Constructor); + unmanagedCallersAttribute.Fields.Add (new CustomAttributeNamedArgument ("EntryPoint", new CustomAttributeArgument (System_String, entryPoint))); + return unmanagedCallersAttribute; + } + + void GenerateConversionToManaged (MethodDefinition method, ILProcessor il, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, int parameter, out TypeReference nativeCallerType) + { + // This is a mirror of the native method xamarin_generate_conversion_to_managed (for the dynamic registrar). + // It's also a mirror of the method ManagedRegistrarStep.GenerateConversionToManaged. + // These methods must be kept in sync. + var managedType = outputType; + var nativeType = inputType; + + var isManagedNullable = StaticRegistrar.IsNullable (managedType); + + var underlyingManagedType = managedType; + var underlyingNativeType = nativeType; + + var isManagedArray = StaticRegistrar.IsArray (managedType); + var isNativeArray = StaticRegistrar.IsArray (nativeType); + + nativeCallerType = System_IntPtr; + + if (isManagedArray != isNativeArray) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + + if (isManagedArray) { + if (isManagedNullable) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + underlyingNativeType = StaticRegistrar.GetElementType (nativeType); + underlyingManagedType = StaticRegistrar.GetElementType (managedType); + } else if (isManagedNullable) { + underlyingManagedType = StaticRegistrar.GetNullableType (managedType); + } + + string? func = null; + MethodReference? conversionFunction = null; + MethodReference? conversionFunction2 = null; + if (underlyingNativeType.Is ("Foundation", "NSNumber")) { + func = StaticRegistrar.GetNSNumberToManagedFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName, out var _); + } else if (underlyingNativeType.Is ("Foundation", "NSValue")) { + func = StaticRegistrar.GetNSValueToManagedFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName, out var _); + } else if (underlyingNativeType.Is ("Foundation", "NSString")) { + if (!StaticRegistrar.IsSmartEnum (underlyingManagedType, out var getConstantMethod, out var getValueMethod)) { + // method linked away!? this should already be verified + AddException (ErrorHelper.CreateError (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + return; + } + + var gim = new GenericInstanceMethod (Runtime_GetNSObject_T___System_IntPtr); + gim.GenericArguments.Add (Foundation_NSString); + conversionFunction = gim; + + conversionFunction2 = CurrentAssembly.MainModule.ImportReference (getValueMethod); + } else { + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + } + + if (func is not null) { + conversionFunction = GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, func, func, (v) => + v.IsStatic, out MethodDefinition conversionFunctionDefinition, ensurePublic: true); + EnsureVisible (method, conversionFunctionDefinition.DeclaringType); + } + + if (isManagedArray) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (BindAs_ConvertNSArrayToManagedArray2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (BindAs_ConvertNSArrayToManagedArray); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + nativeCallerType = System_IntPtr; + } else { + if (isManagedNullable) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (BindAs_CreateNullable2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (BindAs_CreateNullable); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + nativeCallerType = System_IntPtr; + } else { + il.Emit (OpCodes.Call, conversionFunction); + if (conversionFunction2 is not null) + il.Emit (OpCodes.Call, conversionFunction2); + nativeCallerType = System_IntPtr; + } + } + } + + void GenerateConversionToNative (MethodDefinition method, ILProcessor il, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out TypeReference nativeCallerType) + { + // This is a mirror of the native method xamarin_generate_conversion_to_native (for the dynamic registrar). + // These methods must be kept in sync. + var managedType = inputType; + var nativeType = outputType; + + var isManagedNullable = StaticRegistrar.IsNullable (managedType); + + var underlyingManagedType = managedType; + var underlyingNativeType = nativeType; + + var isManagedArray = StaticRegistrar.IsArray (managedType); + var isNativeArray = StaticRegistrar.IsArray (nativeType); + + nativeCallerType = System_IntPtr; + + if (isManagedArray != isNativeArray) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + + if (isManagedArray) { + if (isManagedNullable) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + underlyingNativeType = StaticRegistrar.GetElementType (nativeType); + underlyingManagedType = StaticRegistrar.GetElementType (managedType); + } else if (isManagedNullable) { + underlyingManagedType = StaticRegistrar.GetNullableType (managedType); + } + + string? func = null; + MethodReference? conversionFunction = null; + MethodReference? conversionFunction2 = null; + MethodReference? conversionFunction3 = null; + if (underlyingNativeType.Is ("Foundation", "NSNumber")) { + func = StaticRegistrar.GetManagedToNSNumberFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName); + } else if (underlyingNativeType.Is ("Foundation", "NSValue")) { + func = StaticRegistrar.GetManagedToNSValueFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName); + } else if (underlyingNativeType.Is ("Foundation", "NSString")) { + if (!StaticRegistrar.IsSmartEnum (underlyingManagedType, out var getConstantMethod, out var getValueMethod)) { + // method linked away!? this should already be verified + ErrorHelper.Show (ErrorHelper.CreateError (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + return; + } + + conversionFunction = CurrentAssembly.MainModule.ImportReference (getConstantMethod); + conversionFunction2 = NativeObjectExtensions_GetHandle; + conversionFunction3 = NativeObject_op_Implicit_IntPtr; + } else { + AddException (ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}")); + return; + } + + if (func is not null) { + conversionFunction = GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, func, func, (v) => + v.IsStatic, out MethodDefinition conversionFunctionDefinition, ensurePublic: true); + EnsureVisible (method, conversionFunctionDefinition.DeclaringType); + } + + if (isManagedArray) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (BindAs_ConvertManagedArrayToNSArray2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (BindAs_ConvertManagedArrayToNSArray); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + } else { + var tmpVariable = il.Body.AddVariable (managedType); + + var trueTarget = il.Create (OpCodes.Nop); + var endTarget = il.Create (OpCodes.Nop); + if (isManagedNullable) { + il.Emit (OpCodes.Stloc, tmpVariable); + il.Emit (OpCodes.Ldloca, tmpVariable); + var mr = CreateMethodReferenceOnGenericType (System_Nullable_1, Nullable_HasValue, underlyingManagedType); + il.Emit (OpCodes.Call, mr); + il.Emit (OpCodes.Brtrue, trueTarget); + il.Emit (OpCodes.Ldc_I4_0); + il.Emit (OpCodes.Conv_I); + il.Emit (OpCodes.Br, endTarget); + il.Append (trueTarget); + il.Emit (OpCodes.Ldloca, tmpVariable); + il.Emit (OpCodes.Call, CreateMethodReferenceOnGenericType (System_Nullable_1, Nullable_Value, underlyingManagedType)); + } + il.Emit (OpCodes.Call, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Call, conversionFunction2); + if (conversionFunction3 is not null) + il.Emit (OpCodes.Call, conversionFunction3); + } + if (isManagedNullable) + il.Append (endTarget); + } + } + + static GenericInstanceMethod CreateGenericInstanceMethod (MethodReference mr, params TypeReference [] genericTypeArguments) + { + var gim = new GenericInstanceMethod (mr); + gim.GenericArguments.AddRange (genericTypeArguments); + return gim; + } + + static MethodReference CreateMethodReferenceOnGenericType (TypeReference type, MethodReference mr, params TypeReference[] genericTypeArguments) + { + var git = new GenericInstanceType (type); + git.GenericArguments.AddRange (genericTypeArguments); + + var rv = new MethodReference (mr.Name, mr.ReturnType, git); + rv.HasThis = mr.HasThis; + rv.ExplicitThis = mr.ExplicitThis; + rv.CallingConvention = mr.CallingConvention; + rv.Parameters.AddRange (mr.Parameters); + return rv; + } + } +} + +static class Cecil_Extensions { + public static VariableDefinition AddVariable (this MethodBody self, TypeReference variableType) + { + var rv = new VariableDefinition (variableType); + self.Variables.Add (rv); + return rv; + } + + public static ParameterDefinition AddParameter (this MethodDefinition self, string name, TypeReference parameterType) + { + var rv = new ParameterDefinition (name, ParameterAttributes.None, parameterType); + self.Parameters.Add (rv); + return rv; + } + + public static MethodDefinition AddMethod (this TypeDefinition self, string name, MethodAttributes attributes, TypeReference returnType) + { + var rv = new MethodDefinition (name, attributes, returnType); + rv.DeclaringType = self; + self.Methods.Add (rv); + return rv; + } + + public static MethodBody CreateBody (this MethodDefinition self, out ILProcessor il) + { + var body = new MethodBody (self); + self.Body = body; + il = body.GetILProcessor (); + return body; + } + + public static void AddRange (this Mono.Collections.Generic.Collection self, IEnumerable? items) + { + if (items is null) + return; + + foreach (var item in items) { + self.Add (item); + } + } + + public static void EmitLoadArgument (this ILProcessor il, int argument) + { + il.Append (il.CreateLoadArgument (argument)); + } + public static Instruction CreateLoadArgument (this ILProcessor il, int argument) + { + switch (argument) { + case 0: + return il.Create (OpCodes.Ldarg_0); + case 1: + return il.Create (OpCodes.Ldarg_1); + case 2: + return il.Create (OpCodes.Ldarg_2); + case 3: + return il.Create (OpCodes.Ldarg_3); + default: + return il.Create (OpCodes.Ldarg, argument); + } + } + + public static Instruction Create (this ILProcessor il, bool value) + { + if (value) + return il.Create (OpCodes.Ldc_I4_1); + return il.Create (OpCodes.Ldc_I4_0); + } + + public static void Emit (this ILProcessor il, bool value) + { + il.Append (il.Create (value)); } } diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 2ecc709e1b2c..fbf42cb10dee 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -10,6 +10,7 @@ using Mono.Linker; using Mono.Tuner; using Xamarin.Bundler; +using Xamarin.Tuner; using MonoTouch.Tuner; #if NET @@ -986,33 +987,11 @@ int ProcessSetupBlock (MethodDefinition caller, Instruction ins) return 0; } - // Calculate the block signature. - var blockSignature = false; - MethodReference userMethod = null; - - // First look for any [UserDelegateType] attributes on the trampoline delegate type. - var userDelegateType = GetUserDelegateType (trampolineDelegateType); - if (userDelegateType != null) { - var userMethodDefinition = GetDelegateInvoke (userDelegateType); - userMethod = InflateMethod (userDelegateType, userMethodDefinition); - blockSignature = true; - } else { - // Couldn't find a [UserDelegateType] attribute, use the type of the actual trampoline instead. - var userMethodDefinition = GetDelegateInvoke (trampolineDelegateType); - userMethod = InflateMethod (trampolineDelegateType, userMethodDefinition); - blockSignature = false; - } - - // No luck finding the signature, so give up. - if (userMethod == null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2106, caller, ins, Errors.MM2106_C, caller, ins.Offset, trampolineDelegateType.FullName)); + if (!LinkContext.Target.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { + ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); return 0; - } - var parameters = new TypeReference [userMethod.Parameters.Count]; - for (int p = 0; p < parameters.Length; p++) - parameters [p] = userMethod.Parameters [p].ParameterType; - signature = LinkContext.Target.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); + } } catch (Exception e) { ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); return 0; @@ -1309,29 +1288,6 @@ TypeReference GetPushedType (MethodDefinition method, Instruction ins) } } - // Find the value of the [UserDelegateType] attribute on the specified delegate - TypeReference GetUserDelegateType (TypeReference delegateType) - { - var delegateTypeDefinition = delegateType.Resolve (); - foreach (var attrib in delegateTypeDefinition.CustomAttributes) { - var attribType = attrib.AttributeType; - if (!attribType.Is (Namespaces.ObjCRuntime, "UserDelegateTypeAttribute")) - continue; - return attrib.ConstructorArguments [0].Value as TypeReference; - } - return null; - } - - MethodDefinition GetDelegateInvoke (TypeReference delegateType) - { - var td = delegateType.Resolve (); - foreach (var method in td.Methods) { - if (method.Name == "Invoke") - return method; - } - return null; - } - MethodDefinition setupblock_def; MethodReference GetBlockSetupImpl (MethodDefinition caller, Instruction ins) { diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index a23b34a1af9a..e595969869dc 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -337,16 +337,6 @@ public static string MM2106_B { } } - /// - /// Looks up a localized string similar to Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1} because no [UserDelegateType] attribute could be found on {2}. - /// . - /// - public static string MM2106_C { - get { - return ResourceManager.GetString("MM2106_C", resourceCulture); - } - } - /// /// Looks up a localized string similar to Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1}: {2}. /// . @@ -4006,6 +3996,24 @@ public static string MX3007 { } } + /// + /// Looks up a localized string similar to Could not find a [UserDelegateType] attribute on the type '{0}'.. + /// + public static string MX4187 { + get { + return ResourceManager.GetString("MX4187", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to compute the block signature for the type '{0}': {1}. + /// + public static string MX4188 { + get { + return ResourceManager.GetString("MX4188", resourceCulture); + } + } + /// /// Looks up a localized string similar to The native linker failed to execute: {0}. Please file a bug report at https://github.com/xamarin/xamarin-macios/issues/new /// . diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 45687cea40f4..46e74f9891e7 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -1289,11 +1289,6 @@ - - Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1} because no [UserDelegateType] attribute could be found on {2}. - - - Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1}: {2}. @@ -1914,6 +1909,14 @@ The registrar found a non-optimal type `{0}`: the type does not have a constructor that takes two (ObjCRuntime.NativeHandle, bool) arguments. However, a constructor that takes two (System.IntPtr, bool) arguments was found (and will be used instead). It's highly recommended to change the signature of the (System.IntPtr, bool) constructor to be (ObjCRuntime.NativeHandle, bool). + + Could not find a [UserDelegateType] attribute on the type '{0}'. + + + + Unable to compute the block signature for the type '{0}': {1} + + Missing '{0}' compiler. Please install Xcode 'Command-Line Tools' component