Skip to content

Commit

Permalink
[tools] Add a managed static registrar. Fixes dotnet#17324.
Browse files Browse the repository at this point in the history
  • Loading branch information
rolfbjarne committed May 5, 2023
1 parent d627980 commit b43ab76
Show file tree
Hide file tree
Showing 9 changed files with 1,717 additions and 35 deletions.
38 changes: 34 additions & 4 deletions src/ObjCRuntime/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down Expand Up @@ -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;

Expand Down
40 changes: 40 additions & 0 deletions src/ObjCRuntime/IManagedRegistrar.cs
Original file line number Diff line number Diff line change
@@ -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<RuntimeTypeHandle, RuntimeTypeHandle> type);
}
}

#endif // NET
24 changes: 15 additions & 9 deletions src/ObjCRuntime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
81 changes: 74 additions & 7 deletions tools/common/StaticRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4540,6 +4540,29 @@ public TypeDefinition GetInstantiableType (TypeDefinition td, List<Exception> 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
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -4903,6 +4947,7 @@ string GetSmartEnumToNSStringFunc (TypeReference managedType, TypeReference inpu
void GenerateConversionToManaged (TypeReference inputType, TypeReference outputType, AutoIndentStringBuilder sb, string descriptiveMethodName, ref List<Exception> 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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions tools/dotnet-linker/LinkerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit b43ab76

Please sign in to comment.