From b43ab76745ab154c5fea8eac23be8f6eb28d804e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 25 Jan 2023 16:23:12 +0100 Subject: [PATCH] [tools] Add a managed static registrar. Fixes #17324. Fixes https://github.com/xamarin/xamarin-macios/issues/17324. --- src/ObjCRuntime/Class.cs | 38 +- src/ObjCRuntime/IManagedRegistrar.cs | 40 + src/ObjCRuntime/Runtime.cs | 24 +- tools/common/StaticRegistrar.cs | 81 +- tools/dotnet-linker/LinkerConfiguration.cs | 3 + .../Steps/ManagedRegistrarLookupTablesStep.cs | 382 ++++++ .../Steps/ManagedRegistrarStep.cs | 1118 +++++++++++++++++ tools/mtouch/Errors.designer.cs | 46 +- tools/mtouch/Errors.resx | 20 +- 9 files changed, 1717 insertions(+), 35 deletions(-) create mode 100644 src/ObjCRuntime/IManagedRegistrar.cs diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index 1aa1bcb72204..0283f4e5ef49 100644 --- a/src/ObjCRuntime/Class.cs +++ b/src/ObjCRuntime/Class.cs @@ -254,8 +254,24 @@ 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) { + 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 { + 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,7 +468,9 @@ internal static unsafe int FindMapIndex (Runtime.MTClassMap* array, int lo, int switch (token & 0xFF000000) { case 0x02000000: // TypeDef Type type; - if (module is null) { + if (Runtime.IsManagedStaticRegistrar) { + type = RegistrarHelper.LookupRegisteredType (assembly, token & 0x00FFFFFF); + } else 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); @@ -462,6 +480,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 +598,16 @@ 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) { + 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 { + 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 a446baff05c0..b9eaa1dc5424 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -1816,14 +1816,20 @@ static void TryReleaseINativeObject (INativeObject? obj) return null; // Check if the static registrar knows about this protocol - unsafe { - var map = options->RegistrationMap; - if (map is not null) { - var token = Class.GetTokenReference (type, throw_exception: false); - if (token != INVALID_TOKEN_REF) { - var wrapper_token = xamarin_find_protocol_wrapper_type (token); - if (wrapper_token != INVALID_TOKEN_REF) - return Class.ResolveTypeTokenReference (wrapper_token); + if (IsManagedStaticRegistrar) { + var rv = RegistrarHelper.FindProtocolWrapperType (type); + if (rv is not null) + return rv; + } else { + unsafe { + var map = options->RegistrationMap; + if (map is not null) { + var token = Class.GetTokenReference (type, throw_exception: false); + if (token != INVALID_TOKEN_REF) { + var wrapper_token = xamarin_find_protocol_wrapper_type (token); + if (wrapper_token != INVALID_TOKEN_REF) + return Class.ResolveTypeTokenReference (wrapper_token); + } } } } @@ -2288,7 +2294,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 934d5ae07161..995808f54d10 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -4540,6 +4540,29 @@ public TypeDefinition GetInstantiableType (TypeDefinition td, List ex return nativeObjType; } + 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 @@ -4666,6 +4689,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]; @@ -4903,6 +4947,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; @@ -5092,25 +5137,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; @@ -5141,6 +5192,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); diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index fbeed58ce783..089ec04d81ce 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -70,6 +70,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 f7abadc4e42a..badb38617cf0 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -1,5 +1,20 @@ 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 @@ -26,8 +41,375 @@ 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; + + var annotation = DerivedLinkContext.Annotations.GetCustomAnnotation ("ManagedRegistrarStep", assembly); + var trampolineInfos = annotation as AssemblyTrampolineInfo; + if (trampolineInfos is null) + return; + + abr.SetCurrentAssembly (assembly); + + trampolineInfos.SetIds (); + CreateRegistrarType (trampolineInfos); + + abr.ClearCurrentAssembly (); + } + + void CreateRegistrarType (AssemblyTrampolineInfo infos) + { + 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)); + // registrarType.CustomAttributes.Add (abr.CreateDynamicallyAccessedMemberTypesAttribute (DynamicallyAccessedMemberTypes.Interfaces)); + abr.CurrentAssembly.MainModule.Types.Add (registrarType); + + infos.RegistrarType = registrarType; + // + // The callback methods themselves are all public, and thus accessible from anywhere inside the assembly even if the containing type is not public, as long as the containing type is not nested. + // However, if the containing type is nested inside another type, it gets complicated. + // + // We have two options: + // + // 1. Just change the visibility on the nested type to make it visible inside the assembly. + // 2. Add a method in the containing type (which has access to any directly nested private types) that can look up any unmanaged trampolines. + // If the containing type is also a private nested type, when we'd have to add another method in its containing type, and so on. + // + // The second option is more complicated to implement than the first, so we're doing the first option. If someone + // runs into any problems (there might be with reflection: looking up a type using the wrong visibility will fail to find that type). + // That said, there may be all sorts of problems with reflection (we're adding methods to types, any logic that depends on a type having a certain number of methods will fail for instance). + // + + var sorted = infos.OrderBy (v => v.Id).ToList (); + foreach (var md in sorted) { + var declType = md.Trampoline.DeclaringType; + while (declType.IsNested) { + if (declType.IsNestedPrivate) { + declType.IsNestedAssembly = true; + } else if (declType.IsNestedFamilyAndAssembly || declType.IsNestedFamily) { + declType.IsNestedFamilyOrAssembly = true; + } + declType = declType.DeclaringType; + } + } + + var defaultCtor = registrarType.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); + DerivedLinkContext.Annotations.Mark (defaultCtor); + + // Compute the list of types that we need to register + var types = new List<(TypeReference Reference, TypeDefinition Definition)> (); + types.AddRange (StaticRegistrar.Types.Select (v => { + var tr = v.Value.Type; + var td = tr.Resolve (); + return (tr, td); + })); + 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 ())); + } + types.RemoveAll (v => v.Definition.Module.Assembly != abr.CurrentAssembly); + types.RemoveAll (v => IsLinkedAway (v.Definition)); + foreach (var type in registrarType.Module.Types) { + if (IsLinkedAway (type)) + continue; + var wrapperType = StaticRegistrar.GetProtocolAttributeWrapperType (type); + if (wrapperType is null) + continue; + types.Add (new (wrapperType, wrapperType.Resolve ())); + } + for (var i = 0; i < types.Count; i++) + infos.RegisterType (types [i].Definition, (uint) i); + + GenerateLookupUnmanagedFunction (registrarType, sorted); + GenerateLookupType (infos, registrarType, types); + GenerateLookupTypeId (infos, registrarType, types); + GenerateRegisterWrapperTypes (registrarType); + + 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 ()); + } + } + + bool IsLinkedAway (TypeDefinition type) + { + return StaticRegistrar.IsTrimmed (type, Annotations); + } + + void GenerateLookupTypeId (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List<(TypeReference Reference, TypeDefinition Definition)> 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); + + // 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<(TypeReference Reference, TypeDefinition Definition)> 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); + + // switch (id) { + // case 0: return ; + // case 1: return ; + // } + + 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; + Console.WriteLine ($"Registering {td.FullName} => {i}"); + if (IsLinkedAway (td)) + throw new NotImplementedException ($"Linked away? {td.FullName}"); + } + + 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); + var git = new GenericInstanceType (abr.System_Collections_Generic_Dictionary2); + git.GenericArguments.Add (abr.System_RuntimeTypeHandle); + git.GenericArguments.Add (abr.System_RuntimeTypeHandle); + method.AddParameter ("type", git); + method.Overrides.Add (abr.IManagedRegistrar_RegisterWrapperTypes); + method.CreateBody (out var il); + + 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; + + var keyMarked = !IsLinkedAway (ct.Key.Resolve ()); + var wrapperTypeMarked = !IsLinkedAway (ct.Value.ProtocolWrapperType.Resolve ()); + if (!keyMarked && !wrapperTypeMarked) + continue; + if (keyMarked ^ wrapperTypeMarked) + throw new InvalidOperationException ($"Huh?"); + + 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) + { + Console.WriteLine ($"GenerateLookupMethods ({registrar_type.FullName}, {trampolineInfos.Count} items"); + + 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. + Console.WriteLine ($"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); + } + + MethodDefinition GenerateLookupMethods (TypeDefinition type, IList trampolineInfos, int methodsPerLevel, int level, int levels, int startIndex, int endIndex, out MethodDefinition method) + { + Console.WriteLine ($"GenerateLookupMethods ({type.FullName}, {trampolineInfos.Count} items, methodsPerLevel: {methodsPerLevel}, level: {level}, levels: {levels}, startIndex: {startIndex}, endIndex: {endIndex})"); + + if (startIndex > endIndex) + throw new InvalidOperationException ($"Huh 3? startIndex: {startIndex} endIndex: {endIndex}"); + + var startId = trampolineInfos [startIndex].Id; + var name = level == 1 ? "LookupUnmanagedFunctionImpl" : $"LookupUnmanagedFunction_{level}_{levels}__{startIndex}_{endIndex}__"; + method = type.AddMethod (name, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, abr.System_IntPtr); + method.ReturnType = abr.System_IntPtr; // shouldn't be necessary??? + 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. + var wrapLookup = true; + + var targetCount = endIndex - startIndex + 1; + var targets = new Instruction [targetCount]; + for (var i = 0; i < targets.Length; i++) { + var ti = trampolineInfos [startIndex + i]; + var md = ti.Trampoline; + var mr = abr.CurrentAssembly.MainModule.ImportReference (md); + if (wrapLookup) { + 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 new InvalidOperationException ($"Huh 2 -- {chunkSize}?"); + } + + var count = endIndex - startIndex + 1; + var chunks = (int) Math.Ceiling (count / (double) chunkSize); + var targets = new Instruction [chunks]; + + Console.WriteLine ($"GenerateLookupMethods ({type.FullName}, {trampolineInfos.Count} items, methodsPerLevel: {methodsPerLevel}, level: {level}, levels: {levels}, startIndex: {startIndex}, endIndex: {endIndex}) count: {count} chunks: {chunks} chunkSize: {chunkSize}"); + + var lookupMethods = new MethodDefinition [targets.Length]; + for (var i = 0; i < targets.Length; i++) { + 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 c12bd0e4b4c5..944ec38c23d2 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,15 +8,85 @@ 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 { + 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); + } + } + + public class AssemblyTrampolineInfo : List { + Dictionary registered_type_map = new (); + + public TypeDefinition? RegistrarType; + + public void RegisterType (TypeDefinition td, uint index) + { + registered_type_map.Add (td, index); + } + + public bool TryGetRegisteredTypeIndex (TypeDefinition td, out uint index) + { + return registered_type_map.TryGetValue (td, out index); + } + + public void SetIds () + { + for (var i = 0; i < Count; i++) + this [i].Id = i; + } + } + + 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; + List exceptions = new List (); + AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } + void AddException (Exception exception) + { + if (exceptions is null) + exceptions = new List (); + exceptions.Add (exception); + } protected override void TryProcess () { @@ -34,6 +105,16 @@ protected override void TryEndProcess () if (App.Registrar != RegistrarMode.ManagedStatic) return; + + if (exceptions is null) + return; + var warnings = exceptions.Where (v => (v as ProductException)?.Error == false).ToArray (); + if (warnings.Length == exceptions.Count) + return; + + if (exceptions.Count == 1) + throw exceptions [0]; + throw new AggregateException (exceptions); } protected override void TryProcessAssembly (AssemblyDefinition assembly) @@ -42,7 +123,1044 @@ 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); + + if (assembly == abr.PlatformAssembly) { + Annotations.Mark (abr.System_Diagnostics_CodeAnalysis_DynamicallyAccessedMemberTypes.Resolve ()); + } + + 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); + } + + var process = false; + + process |= IsNSObject (type); + process |= StaticRegistrar.GetCategoryAttribute (type) is not null; + + var registerAttribute = StaticRegistrar.GetRegisterAttribute (type); + if (registerAttribute is not null && registerAttribute.IsWrapper) + return modified; + + if (!process) + return modified; + + var methods_to_wrap = new HashSet (); + if (type.HasMethods) { + foreach (var method in type.Methods) + ProcessMethod (method, methods_to_wrap); + } + + if (type.HasProperties) { + foreach (var prop in type.Properties) { + ProcessProperty (prop, methods_to_wrap); + } + } + + foreach (var method in methods_to_wrap) { + try { + CreateUnmanagedCallersMethod (method, infos); + } catch (Exception e) { + Console.WriteLine (e); + AddException (ErrorHelper.CreateError (99, e, "Failed process {0}: {1}", method.FullName, e.Message)); + } + } + + return true; + } + + void ProcessMethod (MethodDefinition method, HashSet methods_to_wrap) + { + if (!(method.IsConstructor && !method.IsStatic)) { + var ea = StaticRegistrar.GetExportAttribute (method); + if (ea is null && !method.IsVirtual) + return; + } + + if (!StaticRegistrar.TryFindMethod (method, out _)) { + Console.WriteLine ("Could not find method {0}, so no generating trampoline.", GetMethodSignature (method)); + return; + } + + methods_to_wrap.Add (method); + } + + void ProcessProperty (PropertyDefinition property, HashSet methods_to_wrap) + { + var ea = StaticRegistrar.GetExportAttribute (property); + if (ea is null) + return; + + if (property.GetMethod is not null) + methods_to_wrap.Add (property.GetMethod); + + if (property.SetMethod is not null) + methods_to_wrap.Add (property.SetMethod); + } + + static string Sanitize (string str) + { + str = str.Replace ('.', '_'); + str = str.Replace ('/', '_'); + str = str.Replace ('`', '_'); + str = str.Replace ('<', '_'); + str = str.Replace ('>', '_'); + str = str.Replace ('$', '_'); + str = str.Replace ('@', '_'); + str = 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); + DerivedLinkContext.Annotations.Mark (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)); + + DerivedLinkContext.Annotations.Mark (callback); + DerivedLinkContext.Annotations.AddPreservedMethod (method, callback); + DerivedLinkContext.Annotations.AddPreservedMethod (callback, method); + + 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 (); + + // Generate code to return null/default value/void + if (returnVariable is not null) { + var returnType = returnVariable.VariableType!; + if (returnType.IsValueType) { + // return default() + il.Emit (OpCodes.Ldloca, returnVariable); + il.Emit (OpCodes.Initobj, returnType); + il.Emit (OpCodes.Ldloc, returnVariable); + } else { + il.Emit (OpCodes.Ldnull); + } + } + il.Emit (OpCodes.Ret); + } + + static string GetMethodSignature (MethodDefinition method) + { + return $"{method?.ReturnType?.FullName ?? "(null)"} {method?.DeclaringType?.FullName ?? "(null)"}::{method?.Name ?? "(null)"} ({string.Join (", ", method?.Parameters?.Select (v => v?.ParameterType?.FullName + " " + v?.Name) ?? Array.Empty ())})"; + } + + static string GetMethodSignatureWithSourceCode (MethodDefinition method) + { + var rv = GetMethodSignature (method); + if (method.HasBody && method.DebugInformation.HasSequencePoints) { + var seq = method.DebugInformation.SequencePoints [0]; + rv += " " + seq.Document.Url + ":" + seq.StartLine.ToString () + " "; + } + return rv; + } + + bool IsNSObject (TypeReference type) + { + if (type is ArrayType) + return false; + + if (type is ByReferenceType) + return false; + + if (type is PointerType) + return false; + + if (type is GenericParameter) + return false; + + return type.IsNSObject (DerivedLinkContext); + } + + BindAsAttribute? GetBindAsAttribute (MethodDefinition method, int parameter) + { + if (StaticRegistrar.IsPropertyAccessor (method, out var property)) { + return StaticRegistrar.GetBindAsAttribute (property); + } else { + return StaticRegistrar.GetBindAsAttribute (method, parameter); + } + } + + // This emits a conversion between the native and the managed representation of a parameter or return value, + // and returns the corresponding native type. The returned nativeType will (must) be a blittable type. + bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type, bool toManaged, int parameter, [NotNullWhen (true)] out TypeReference? nativeType, List postProcessing, VariableDefinition? selfVariable, bool isOutParameter = false, int nativeParameterIndex = -1, bool isDynamicInvoke = false) + { + nativeType = null; + + if (!(parameter == -1 && !method.IsStatic && method.DeclaringType == type)) { + var bindAsAttribute = GetBindAsAttribute (method, parameter); + if (bindAsAttribute is not null) { + if (toManaged) { + 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; + } + + void GenerateConversionToManaged (MethodDefinition method, ILProcessor il, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, int parameter, out TypeReference nativeCallerType) + { + // This is a mirror of the native method xamarin_generate_conversion_to_managed (for the dynamic registrar). + // It's also a mirror of the method ManagedRegistrarStep.GenerateConversionToManaged. + // These methods must be kept in sync. + var managedType = outputType; + var nativeType = inputType; + + var isManagedNullable = StaticRegistrar.IsNullable (managedType); + + var underlyingManagedType = managedType; + var underlyingNativeType = nativeType; + + var isManagedArray = StaticRegistrar.IsArray (managedType); + var isNativeArray = StaticRegistrar.IsArray (nativeType); + + nativeCallerType = 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). + // 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 a23b34a1af9a..27c7c3a6022e 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 /// . @@ -4257,5 +4265,23 @@ 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); + } + } + + /// + /// 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); + } + } } } diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 45687cea40f4..1cc24e8e60d5 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 @@ -2240,4 +2243,11 @@ The signature must be a non-empty string. + + 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}). +