Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

MSR: Create instances of NSObjects and INativeObjects without using reflection #18519

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ git-clean-all:
@git submodule foreach -q --recursive 'git clean -xffdq && git reset --hard -q'
@for dir in $(DEPENDENCY_DIRECTORIES); do if test -d $(CURDIR)/$$dir; then echo "Cleaning $$dir" && cd $(CURDIR)/$$dir && git clean -xffdq && git reset --hard -q && git submodule foreach -q --recursive 'git clean -xffdq'; else echo "Skipped $$dir (does not exist)"; fi; done

@if [ -n "$(ENABLE_XAMARIN)" ] || [ -n "$(ENABLE_DOTNET)"]; then \
@if [ -n "$(ENABLE_XAMARIN)" ] || [ -n "$(ENABLE_DOTNET)" ]; then \
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
CONFIGURE_FLAGS=""; \
if [ -n "$(ENABLE_XAMARIN)" ]; then \
echo "Xamarin-specific build has been re-enabled"; \
Expand Down
12 changes: 12 additions & 0 deletions src/Foundation/NSObject2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ public class NSObjectFlag {
}
#endif

#if NET
public interface INSObjectFactory {
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
// The method will be implemented via custom linker step if the managed static registrar is used
// for NSObject subclasses which have an (NativeHandle) or (IntPtr) constructor.
[MethodImpl(MethodImplOptions.NoInlining)]
virtual static NSObject ConstructNSObject (NativeHandle handle) => null;
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
}
#endif

#if NET && !COREBUILD
[ObjectiveCTrackedType]
[SupportedOSPlatform ("ios")]
Expand All @@ -81,6 +90,9 @@ public partial class NSObject : INativeObject
#if !COREBUILD
, IEquatable<NSObject>
, IDisposable
#endif
#if NET
, INSObjectFactory
#endif
{
#if !COREBUILD
Expand Down
5 changes: 4 additions & 1 deletion src/ObjCRuntime/IManagedRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//
// Copyright 2023 Microsoft Corp


#if NET

#nullable enable
Expand Down Expand Up @@ -34,6 +33,10 @@ interface IManagedRegistrar {
// 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);
// Create an instance of a managed NSObject subclass for an existing Objective-C object.
INativeObject? ConstructNSObject (RuntimeTypeHandle typeHandle, NativeHandle nativeHandle);
// Create an instance of a managed NSObject subclass for an existing Objective-C object.
INativeObject? ConstructINativeObject (RuntimeTypeHandle typeHandle, NativeHandle nativeHandle, bool owns);
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/ObjCRuntime/INativeObject.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable

using System;
using System.Runtime.CompilerServices;
using Foundation;

#if !NET
Expand All @@ -15,6 +16,13 @@ NativeHandle Handle {
get;
}
#endif

#if NET
// The method will be implemented via custom linker step if the managed static registrar is used
// for classes which have an (NativeHandle, bool) or (IntPtr, bool) constructor.
[MethodImpl(MethodImplOptions.NoInlining)]
static virtual INativeObject? ConstructINativeObject (NativeHandle handle, bool owns) => null;
rolfbjarne marked this conversation as resolved.
Show resolved Hide resolved
#endif
}

#if !COREBUILD
Expand Down
16 changes: 16 additions & 0 deletions src/ObjCRuntime/RegistrarHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,22 @@ internal static uint LookupRegisteredTypeId (Type type)
return entry.Registrar.LookupTypeId (type.TypeHandle);
}

internal static T? ConstructNSObject<T> (Type type, NativeHandle nativeHandle)
where T : class, INativeObject
{
if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry))
return null;
return (T?) entry.Registrar.ConstructNSObject (type.TypeHandle, nativeHandle);
}

internal static T? ConstructINativeObject<T> (Type type, NativeHandle nativeHandle, bool owns)
where T : class, INativeObject
{
if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry))
return null;
return (T?) entry.Registrar.ConstructINativeObject (type.TypeHandle, nativeHandle, owns);
}

// helper functions for converting between native and managed objects
static NativeHandle ManagedArrayToNSArray (object array, bool retain)
{
Expand Down
120 changes: 111 additions & 9 deletions src/ObjCRuntime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1304,19 +1304,46 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
return ConstructNSObject<T> (ptr, typeof (T), MissingCtorResolution.ThrowConstructor1NotFound);
}

// The generic argument T is only used to cast the return value.
// The 'selector' and 'method' arguments are only used in error messages.
static T? ConstructNSObject<T> (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : class, INativeObject
static T? ConstructNSObject<T> (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : NSObject
{
return ConstructNSObject<T> (ptr, type, missingCtorResolution, IntPtr.Zero, default (RuntimeMethodHandle));
}

// The generic argument T is only used to cast the return value.
// The 'selector' and 'method' arguments are only used in error messages.
static T? ConstructNSObject<T> (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
static T? ConstructNSObject<T> (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle)
where T : NSObject
#if NET
, INSObjectFactory
#endif
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
{
if (type is null)
throw new ArgumentNullException (nameof (type));
#if NET
if (Runtime.IsManagedStaticRegistrar) {
T? instance = default;
var nativeHandle = new NativeHandle (ptr);

if (typeof (T) != typeof (NSObject)
&& (typeof (T) == type || typeof (T).IsGenericType)
&& !(typeof (T).IsInterface || typeof (T).IsAbstract)) {
instance = ConstructNSObjectViaFactoryMethod (nativeHandle);
}

// If we couldn't create an instance of T through the factory method, we'll use the lookup table.
// This table can't instantiate generic types though.
if (!type.IsGenericType) {
instance ??= RegistrarHelper.ConstructNSObject<T> (type, nativeHandle);
}

if (instance is null) {
MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle);
rolfbjarne marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

return instance;
}
#endif

var ctor = GetIntPtrConstructor (type);

Expand All @@ -1337,6 +1364,22 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
#endif

return (T) ctor.Invoke (ctorArguments);

#if NET
static T? ConstructNSObjectViaFactoryMethod (NativeHandle handle)
{
NSObject? obj = T.ConstructNSObject (handle);
if (obj is T instance) {
return instance;
}

// if the factory method returns a NSObject subclass but we don't expect that specific type,
// we need to dispose it and return null anyway
obj?.Dispose ();

return null;
}
#endif
}

// The generic argument T is only used to cast the return value.
Expand All @@ -1348,6 +1391,50 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
if (type.IsByRef)
type = type.GetElementType ()!;

#if NET
if (Runtime.IsManagedStaticRegistrar) {
var nativeHandle = new NativeHandle (ptr);
T? instance = null;

// - The factory method on T is only useful if we know the exact generic type (it's not INativeObject)
// and we're not just falling back to the INativeObject/NSObject factory method (which would throw
// an exception anyway).
// - If `T` is different from `type`, we'd rather use the lookup table to create the instance of the
// more specialized `type` than "just" T unless it's a generic type. We can't create instances of generic
// types through the lookup table.
// - It doesn't make sense to create an instance of an interface or an abstract class.
if (typeof (T) != typeof (INativeObject)
&& typeof (T) != typeof (NSObject)
&& (typeof (T) == type || typeof (T).IsGenericType)
&& !(typeof (T).IsInterface || typeof (T).IsAbstract))
{
instance = ConstructINativeObjectViaFactoryMethod (nativeHandle, owns);
}

// if the factory didn't work and the type isn't generic, we can try the lookup table
if (instance is null && !type.IsGenericType) {
// if type is an NSObject, we prefer the NSObject lookup table
if (type != typeof (NSObject) && type.IsSubclassOf (typeof (NSObject))) {
instance = (T?)(INativeObject?) RegistrarHelper.ConstructNSObject<T> (type, nativeHandle);
if (instance is not null && owns) {
Runtime.TryReleaseINativeObject (instance);
}
}

if (instance is null && type != typeof (INativeObject)) {
instance = RegistrarHelper.ConstructINativeObject<T> (type, nativeHandle, owns);
}
}

if (instance is null) {
MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle);
return null;
}

return instance;
}
#endif

var ctor = GetIntPtr_BoolConstructor (type);

if (ctor is null) {
Expand All @@ -1368,6 +1455,25 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
ctorArguments [1] = owns;

return (T?) ctor.Invoke (ctorArguments);

#if NET
// This is a workaround for a compiler issue.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to leave me curious what this compiler issue is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is outdated. I was running into an issue where the compiler would crash when T.ConstructINativeObject or T.ConstructNSObject was called in the parent method but it was probably caused by something else (or it was in combination with some invalid IL that I generated during earlier stages of development). I don't observe the same crash now that I rebuilt it with "direct" calls to those methods. I'll get rid of the comment and I think I'll also remove the local function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I've been able to find the issue that led me to move the code into a separate function. Without T.ConstructINativeObject(...) being called from different function than Runtime.ConstructINativeObject<T>, for example calling CNContactFormatter.GetDescriptorForRequiredKeys (CNContactFormatterStyle.PhoneticFullName) causes a crashes with:

2023-06-30 17:11:06.854 MySingleView[90723:11572593] error: * Assertion at /Users/runner/work/1/s/src/mono/mono/mini/mini-generic-sharing.c:2283, condition `m_class_get_vtable (info->klass)' not met

=================================================================
        Native Crash Reporting
=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

=================================================================
        Native stacktrace:
=================================================================
        0x10362265c - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : mono_dump_native_crash_info
        0x1035e1578 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : mono_handle_native_crash
        0x1038001a0 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : sigabrt_signal_handler.cold.1
        0x103621f10 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : mono_runtime_setup_stat_profiler
        0x18cd26a24 - /usr/lib/system/libsystem_platform.dylib : _sigtramp
        0x18ccf7c28 - /usr/lib/system/libsystem_pthread.dylib : pthread_kill
        0x18cc05ae8 - /usr/lib/system/libsystem_c.dylib : abort
        0x102e39bf0 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libxamarin-dotnet.dylib : _ZL14print_callbackPKci
        0x10365e4b8 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : monoeg_g_logv_nofree
        0x10365e570 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : monoeg_assertion_message
        0x10365e5b4 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : mono_assertion_message_unreachable
        0x1035eaa8c - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : instantiate_info
        0x1035e9900 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : mini_instantiate_gshared_info
        0x103595248 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MonoBundle/libmonosgen-2.0.dylib : mini_init_method_rgctx
        0x1026c2acc - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MacOS/MySingleView : ObjCRuntime_Runtime_ConstructINativeObject_T_REF_intptr_bool_System_Type_ObjCRuntime_Runtime_MissingCtorResolution_intptr_System_RuntimeMethodHandle
        0x1026c5788 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MacOS/MySingleView : ObjCRuntime_Runtime_GetINativeObject_T_REF_intptr_bool_System_Type_bool_intptr_System_RuntimeMethodHandle
        0x1026c4860 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MacOS/MySingleView : ObjCRuntime_Runtime_GetINativeObject_T_REF_intptr_bool_System_Type_bool
        0x1026c47e0 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MacOS/MySingleView : ObjCRuntime_Runtime_GetINativeObject_T_REF_intptr_bool_bool
        0x1026c4768 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MacOS/MySingleView : ObjCRuntime_Runtime_GetINativeObject_T_REF_intptr_bool
        0x1026b6448 - ./xamarin-macios/tests/dotnet/MySingleView/bin/Release/net7.0-maccatalyst/maccatalyst-arm64/MySingleView.app/Contents/MacOS/MySingleView : Contacts_CNContactFormatter_GetDescriptorForRequiredKeys_Contacts_CNContactFormatterStyle
        ...

The comment is still outdated and I'll try to change the wording of the workaround description.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you file an issue for this? Seems like an issue in the AOT compiler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done that yet but I will create a follow-up compiler issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static T? ConstructINativeObjectViaFactoryMethod (NativeHandle nativeHandle, bool owns)
{
INativeObject? obj = T.ConstructINativeObject (nativeHandle, owns);
if (obj is T instance) {
return instance;
}

// if the factory method returns an INativeObject but we don't expect that specific type,
// we need to release it and return null anyway
if (obj is not null) {
Runtime.TryReleaseINativeObject(obj);
}
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved

return null;
}
#endif
}

static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, NSObject.Flags flags)
Expand Down Expand Up @@ -1747,7 +1853,7 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ
// native objects and NSObject instances.
throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName}).");
}
return ConstructNSObject<INativeObject> (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle);
return (INativeObject?) ConstructNSObject<NSObject> (ptr, implementation!, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle);
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
}

return ConstructINativeObject<INativeObject> (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle);
Expand Down Expand Up @@ -1804,10 +1910,6 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ
// native objects and NSObject instances.
throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName}).");
}
var rv = (T?) ConstructNSObject<T> (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound);
if (owns)
TryReleaseINativeObject (rv);
return rv;
}

return ConstructINativeObject<T> (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle);
Expand Down
3 changes: 3 additions & 0 deletions tools/common/StaticRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,9 @@ protected override bool ContainsPlatformReference (AssemblyDefinition assembly)
}

protected override IEnumerable<TypeReference> CollectTypes (AssemblyDefinition assembly)
=> GetAllTypes (assembly);

internal static IEnumerable<TypeReference> GetAllTypes (AssemblyDefinition assembly)
{
var queue = new Queue<TypeDefinition> ();

Expand Down
58 changes: 58 additions & 0 deletions tools/dotnet-linker/AppBundleRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,12 @@ public TypeReference ObjCRuntime_IManagedRegistrar {
}
}

public TypeReference Foundation_INSObjectFactory {
get {
return GetTypeReference (PlatformAssembly, "Foundation.INSObjectFactory", out var _);
}
}

public TypeReference ObjCRuntime_INativeObject {
get {
return GetTypeReference (PlatformAssembly, "ObjCRuntime.INativeObject", out var _);
Expand Down Expand Up @@ -716,6 +722,49 @@ public MethodReference IManagedRegistrar_LookupTypeId {
}
}

public MethodReference IManagedRegistrar_ConstructNSObject {
get {
return GetMethodReference (PlatformAssembly,
ObjCRuntime_IManagedRegistrar, "ConstructNSObject",
isStatic: false,
System_RuntimeTypeHandle,
ObjCRuntime_NativeHandle);
}
}

public MethodReference INSObjectFactory_ConstructNSObject {
get {
return GetMethodReference (PlatformAssembly,
Foundation_INSObjectFactory, "ConstructNSObject",
nameof (INSObjectFactory_ConstructNSObject),
isStatic: true,
ObjCRuntime_NativeHandle);
}
}

public MethodReference IManagedRegistrar_ConstructINativeObject {
get {
return GetMethodReference (PlatformAssembly,
ObjCRuntime_IManagedRegistrar, "ConstructINativeObject",
nameof (IManagedRegistrar_ConstructINativeObject),
isStatic: false,
System_RuntimeTypeHandle,
ObjCRuntime_NativeHandle,
System_Boolean);
}
}

public MethodReference INativeObject_ConstructINativeObject {
get {
return GetMethodReference (PlatformAssembly,
ObjCRuntime_INativeObject, "ConstructINativeObject",
nameof (INativeObject_ConstructINativeObject),
isStatic: true,
ObjCRuntime_NativeHandle,
System_Boolean);
}
}

public MethodReference IManagedRegistrar_RegisterWrapperTypes {
get {
return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "RegisterWrapperTypes", (v) =>
Expand Down Expand Up @@ -1058,6 +1107,15 @@ public MethodReference Runtime_RetainAndAutoreleaseNativeObject {
}
}

public MethodReference Runtime_TryReleaseINativeObject {
get {
return GetMethodReference (PlatformAssembly,
ObjCRuntime_Runtime, "TryReleaseINativeObject",
isStatic: true,
ObjCRuntime_INativeObject);
}
}

public MethodReference UnmanagedCallersOnlyAttribute_Constructor {
get {
return GetMethodReference (CorlibAssembly, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute", ".ctor", (v) => v.IsDefaultConstructor ());
Expand Down
Loading