Skip to content

Commit

Permalink
[mono] Add runtime support for Swift calling convention on x64 and ar…
Browse files Browse the repository at this point in the history
…m64 (#94764)

This commit introduces basic support for the Swift calling convention in the mini and interpreter backends on x64 and arm64 architectures. When a C# function calls a Swift library, it is expected that the self context is stored in a dedicated register. Similarly, when a Swift function returns, the error is supposed to be placed in a specific register.

---------

Co-authored-by: Matous Kozak <matouskozak@seznam.cz>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Aaron Robinson <arobins@microsoft.com>
Co-authored-by: Jeremy Koritzinsky <jkoritzinsky@gmail.com>
Co-authored-by: Aleksey Kliger (λgeek) <akliger@gmail.com>
  • Loading branch information
6 people authored Feb 6, 2024
1 parent e13a23d commit be8cb2b
Show file tree
Hide file tree
Showing 34 changed files with 903 additions and 144 deletions.
2 changes: 1 addition & 1 deletion src/mono/mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,7 @@ ves_icall_RuntimeType_GetCallingConventionFromFunctionPointerInternal (MonoQCall
MonoType *type = type_handle.type;
g_assert (type->type == MONO_TYPE_FNPTR);
// FIXME: Once we address: https://github.com/dotnet/runtime/issues/90308 this should not be needed anymore
return GUINT_TO_INT8 (type->data.method->suppress_gc_transition ? MONO_CALL_UNMANAGED_MD : type->data.method->call_convention);
return GUINT_TO_INT8 (mono_method_signature_has_ext_callconv (type->data.method, MONO_EXT_CALLCONV_SUPPRESS_GC_TRANSITION) ? MONO_CALL_UNMANAGED_MD : type->data.method->call_convention);
}

MonoBoolean
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/metadata/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ inflate_generic_signature_checked (MonoImage *image, MonoMethodSignature *sig, M
res->explicit_this = sig->explicit_this;
res->call_convention = sig->call_convention;
res->pinvoke = sig->pinvoke;
res->ext_callconv = sig->ext_callconv;
res->generic_param_count = sig->generic_param_count;
res->sentinelpos = sig->sentinelpos;
res->has_type_parameters = is_open;
Expand Down
249 changes: 134 additions & 115 deletions src/mono/mono/metadata/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (suppress_gc_transition_attribute, "Sys
static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callers_only_attribute, "System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute")
static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callconv_attribute, "System.Runtime.InteropServices", "UnmanagedCallConvAttribute")

GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_error, "System.Runtime.InteropServices.Swift", "SwiftError")
GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_self, "System.Runtime.InteropServices.Swift", "SwiftSelf")

static gboolean type_is_blittable (MonoType *type);

static IlgenCallbacksToMono ilgenCallbacksToMono = {
Expand Down Expand Up @@ -3232,6 +3235,8 @@ mono_marshal_set_callconv_for_type(MonoType *type, MonoMethodSignature *csig, gb
csig->call_convention = MONO_CALL_FASTCALL;
else if (!strcmp (class_name, "CallConvThiscall"))
csig->call_convention = MONO_CALL_THISCALL;
else if (!strcmp (class_name, "CallConvSwift"))
csig->ext_callconv |= MONO_EXT_CALLCONV_SWIFTCALL;
else if (!strcmp (class_name, "CallConvSuppressGCTransition") && skip_gc_trans != NULL)
*skip_gc_trans = TRUE;
}
Expand Down Expand Up @@ -3358,8 +3363,10 @@ mono_marshal_set_signature_callconv_from_attribute(MonoMethodSignature *sig, Mon
sig->call_convention = MONO_CALL_THISCALL;
else if (!strcmp (name, "Fastcall"))
sig->call_convention = MONO_CALL_FASTCALL;
else if (!strcmp (name, "Swift"))
sig->ext_callconv |= MONO_EXT_CALLCONV_SWIFTCALL;
else if (!strcmp (name, "SuppressGCTransition"))
sig->suppress_gc_transition = 1;
sig->ext_callconv |= MONO_EXT_CALLCONV_SUPPRESS_GC_TRANSITION;
// TODO: Support CallConvMemberFunction?
// TODO: Support multiple calling convetions?
// - Switch MonoCallConvention enum values to powers of 2
Expand Down Expand Up @@ -3427,23 +3434,31 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions,
MonoMethodSignature *sig, *csig;
MonoMethodPInvoke *piinfo = (MonoMethodPInvoke *) method;
MonoMethodBuilder *mb;
MonoMarshalSpec **mspecs;
MonoMarshalSpec **mspecs = NULL;
MonoMethod *res;
GHashTable *cache;
gboolean pinvoke = FALSE;
gboolean skip_gc_trans = FALSE;
gboolean pinvoke_not_found = FALSE;
gpointer iter;
ERROR_DECL (emitted_error);
WrapperInfo *info;
MonoType *string_type;
GHashTable **cache_ptr;
ERROR_DECL (emitted_error);

g_assert (method != NULL);
g_assertf (mono_method_signature_internal (method)->pinvoke, "%s flags:%X iflags:%X param_count:%X",
method->name, method->flags, method->iflags, mono_method_signature_internal (method)->param_count);

GHashTable **cache_ptr;

MonoType *string_type = m_class_get_byval_arg (mono_defaults.string_class);
if (MONO_CLASS_IS_IMPORT (method->klass)) {
/* The COM code is not AOT compatible, it calls mono_custom_attrs_get_attr_checked () */
if (aot)
return method;
#ifndef DISABLE_COM
return mono_cominterop_get_native_wrapper (method);
#else
g_assert_not_reached ();
#endif
}

if (aot) {
if (check_exceptions)
Expand All @@ -3470,17 +3485,6 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions,
}
}

if (MONO_CLASS_IS_IMPORT (method->klass)) {
/* The COM code is not AOT compatible, it calls mono_custom_attrs_get_attr_checked () */
if (aot)
return method;
#ifndef DISABLE_COM
return mono_cominterop_get_native_wrapper (method);
#else
g_assert_not_reached ();
#endif
}

sig = mono_method_signature_internal (method);

if (!(method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) &&
Expand All @@ -3493,12 +3497,30 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions,
mono_error_set_generic_error (emitted_error, "System", "MissingMethodException", "Method contains unsupported native code");
else if (!aot)
mono_lookup_pinvoke_call_internal (method, emitted_error);
} else {
if (!aot || (method->klass == mono_defaults.string_class))
piinfo->addr = mono_lookup_internal_call (method);
} else if (!aot || (method->klass == mono_defaults.string_class)) {
piinfo->addr = mono_lookup_internal_call (method);
}
}

mb = mono_mb_new (method->klass, method->name, MONO_WRAPPER_MANAGED_TO_NATIVE);
mb->method->save_lmf = 1;

if (G_UNLIKELY (pinvoke && mono_method_has_unmanaged_callers_only_attribute (method))) {
/*
* In AOT mode and embedding scenarios, it is possible that the icall is not registered in the runtime doing the AOT compilation.
* Emit a wrapper that throws a NotSupportedException.
*/
get_marshal_cb ()->mb_emit_exception (mb, "System", "NotSupportedException", "Method canot be marked with both DllImportAttribute and UnmanagedCallersOnlyAttribute");
goto emit_exception_for_error;
} else if (!pinvoke && !piinfo->addr && !aot) {
/* if there's no code but the error isn't set, just use a fairly generic exception. */
if (is_ok (emitted_error))
mono_error_set_generic_error (emitted_error, "System", "MissingMethodException", "");
get_marshal_cb ()->mb_emit_exception_for_error (mb, emitted_error);
goto emit_exception_for_error;
}

string_type = m_class_get_byval_arg (mono_defaults.string_class);
/* hack - redirect certain string constructors to CreateString */
if (piinfo->addr == ves_icall_System_String_ctor_RedirectToCreateString) {
MonoMethod *m;
Expand Down Expand Up @@ -3555,133 +3577,130 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions,
return res;
}

mb = mono_mb_new (method->klass, method->name, MONO_WRAPPER_MANAGED_TO_NATIVE);

mb->method->save_lmf = 1;

if (G_UNLIKELY (pinvoke && mono_method_has_unmanaged_callers_only_attribute (method))) {
/* emit a wrapper that throws a NotSupportedException */
get_marshal_cb ()->mb_emit_exception (mb, "System", "NotSupportedException", "Method canot be marked with both DllImportAttribute and UnmanagedCallersOnlyAttribute");

info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
info->d.managed_to_native.method = method;

csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
csig->pinvoke = 0;
res = mono_mb_create_and_cache_full (cache, method, mb, csig,
csig->param_count + 16, info, NULL);
mono_mb_free (mb);

return res;
}

/*
* In AOT mode and embedding scenarios, it is possible that the icall is not
* registered in the runtime doing the AOT compilation.
*/
/* Handled at runtime */
pinvoke_not_found = !pinvoke && !piinfo->addr && !aot;
if (pinvoke_not_found) {
/* if there's no code but the error isn't set, just use a fairly generic exception. */
if (is_ok (emitted_error))
mono_error_set_generic_error (emitted_error, "System", "MissingMethodException", "");
get_marshal_cb ()->mb_emit_exception_for_error (mb, emitted_error);
mono_error_cleanup (emitted_error);

info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
info->d.managed_to_native.method = method;

csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
csig->pinvoke = 0;
res = mono_mb_create_and_cache_full (cache, method, mb, csig,
csig->param_count + 16, info, NULL);
mono_mb_free (mb);

return res;
}

/* internal calls: we simply push all arguments and call the method (no conversions) */
if (method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME)) {
if (sig->hasthis)
csig = mono_metadata_signature_dup_add_this (get_method_image (method), sig, method->klass);
else
csig = mono_metadata_signature_dup_full (get_method_image (method), sig);

//printf ("%s\n", mono_method_full_name (method, 1));

/* hack - string constructors returns a value */
if (method->string_ctor)
csig->ret = string_type;

get_marshal_cb ()->emit_native_icall_wrapper (mb, method, csig, check_exceptions, aot, piinfo);
} else {
g_assert(pinvoke);

info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
info->d.managed_to_native.method = method;
csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
mono_marshal_set_callconv_from_modopt (method, csig, FALSE);

csig = mono_metadata_signature_dup_full (get_method_image (method), csig);
csig->pinvoke = 0;
res = mono_mb_create_and_cache_full (cache, method, mb, csig, csig->param_count + 16,
info, NULL);
mspecs = g_new0 (MonoMarshalSpec*, sig->param_count + 1);
mono_method_get_marshal_info (method, mspecs);

mono_mb_free (mb);
return res;
}
if (mono_class_try_get_suppress_gc_transition_attribute_class ()) {
MonoCustomAttrInfo *cinfo;
ERROR_DECL (error);

g_assert (pinvoke);
cinfo = mono_custom_attrs_from_method_checked (method, error);
mono_error_assert_ok (error);
gboolean found = FALSE;
if (cinfo) {
for (int i = 0; i < cinfo->num_attrs; ++i) {
MonoClass *ctor_class = cinfo->attrs [i].ctor->klass;
if (ctor_class == mono_class_try_get_suppress_gc_transition_attribute_class ()) {
found = TRUE;
break;
}
}
}
if (found)
skip_gc_trans = TRUE;
if (cinfo && !cinfo->cached)
mono_custom_attrs_free (cinfo);
}

csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
mono_marshal_set_callconv_from_modopt (method, csig, FALSE);
if (csig->call_convention == MONO_CALL_DEFAULT) {
/* If the calling convention has not been set, check the UnmanagedCallConv attribute */
mono_marshal_set_callconv_from_unmanaged_callconv_attribute (method, csig, &skip_gc_trans);
}

mspecs = g_new0 (MonoMarshalSpec*, sig->param_count + 1);
mono_method_get_marshal_info (method, mspecs);
if (mono_method_signature_has_ext_callconv (csig, MONO_EXT_CALLCONV_SWIFTCALL)) {
MonoClass *swift_self = mono_class_try_get_swift_self_class ();
MonoClass *swift_error = mono_class_try_get_swift_error_class ();
MonoClass *swift_error_ptr = mono_class_create_ptr (m_class_get_this_arg (swift_error));
int swift_error_args = 0, swift_self_args = 0;
for (int i = 0; i < method->signature->param_count; ++i) {
MonoClass *param_klass = mono_class_from_mono_type_internal (method->signature->params [i]);
if (param_klass) {
if (param_klass == swift_error && !m_type_is_byref (method->signature->params [i])) {
swift_error_args = swift_self_args = 0;
mono_error_set_generic_error (emitted_error, "System", "InvalidProgramException", "SwiftError argument must be passed by reference.");
break;
} else if (param_klass == swift_error || param_klass == swift_error_ptr) {
swift_error_args++;
} else if (param_klass == swift_self) {
swift_self_args++;
} else if (!m_class_is_blittable (param_klass) && m_class_get_this_arg (param_klass)->type != MONO_TYPE_FNPTR) {
swift_error_args = swift_self_args = 0;
mono_error_set_generic_error (emitted_error, "System", "InvalidProgramException", "Passing non-primitive value types to a P/Invoke with the Swift calling convention is unsupported.");
break;
}
}
}

if (mono_class_try_get_suppress_gc_transition_attribute_class ()) {
MonoCustomAttrInfo *cinfo;
ERROR_DECL (error);
if (swift_self_args > 1 || swift_error_args > 1) {
mono_error_set_generic_error (emitted_error, "System", "InvalidProgramException", "Method signature contains multiple SwiftSelf or SwiftError arguments.");
}

cinfo = mono_custom_attrs_from_method_checked (method, error);
mono_error_assert_ok (error);
gboolean found = FALSE;
if (cinfo) {
for (int i = 0; i < cinfo->num_attrs; ++i) {
MonoClass *ctor_class = cinfo->attrs [i].ctor->klass;
if (ctor_class == mono_class_try_get_suppress_gc_transition_attribute_class ()) {
found = TRUE;
break;
}
if (!is_ok (emitted_error)) {
get_marshal_cb ()->mb_emit_exception_for_error (mb, emitted_error);
goto emit_exception_for_error;
}
}
if (found)
skip_gc_trans = TRUE;
if (cinfo && !cinfo->cached)
mono_custom_attrs_free (cinfo);
}

if (csig->call_convention == MONO_CALL_DEFAULT) {
/* If the calling convention has not been set, check the UnmanagedCallConv attribute */
mono_marshal_set_callconv_from_unmanaged_callconv_attribute (method, csig, &skip_gc_trans);
MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0;
flags |= check_exceptions ? EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS : (MonoNativeWrapperFlags)0;
flags |= skip_gc_trans ? EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS : (MonoNativeWrapperFlags)0;
flags |= runtime_marshalling_enabled (get_method_image (method)) ? EMIT_NATIVE_WRAPPER_RUNTIME_MARSHALLING_ENABLED : (MonoNativeWrapperFlags)0;

mono_marshal_emit_native_wrapper (get_method_image (mb->method), mb, csig, piinfo, mspecs, piinfo->addr, flags);
}

MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0;
flags |= check_exceptions ? EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS : (MonoNativeWrapperFlags)0;
flags |= skip_gc_trans ? EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS : (MonoNativeWrapperFlags)0;
flags |= runtime_marshalling_enabled (get_method_image (method)) ? EMIT_NATIVE_WRAPPER_RUNTIME_MARSHALLING_ENABLED : (MonoNativeWrapperFlags)0;
info = mono_wrapper_info_create (mb, method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME)
? WRAPPER_SUBTYPE_NONE
: WRAPPER_SUBTYPE_PINVOKE);

mono_marshal_emit_native_wrapper (get_method_image (mb->method), mb, csig, piinfo, mspecs, piinfo->addr, flags);
info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_PINVOKE);
info->d.managed_to_native.method = method;

if (method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME))
csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
csig->pinvoke = 0;
res = mono_mb_create_and_cache_full (cache, method, mb, csig, csig->param_count + 16,
info, NULL);
mono_mb_free (mb);

for (int i = sig->param_count; i >= 0; i--)
if (mspecs [i])
mono_metadata_free_marshal_spec (mspecs [i]);
g_free (mspecs);
if (mspecs) {
for (int i = sig->param_count; i >= 0; i--)
if (mspecs [i])
mono_metadata_free_marshal_spec (mspecs [i]);
g_free (mspecs);
}

return res;
goto leave;

emit_exception_for_error:
mono_error_cleanup (emitted_error);
info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
info->d.managed_to_native.method = method;

csig = mono_metadata_signature_dup_full (get_method_image (method), sig);
csig->pinvoke = 0;
res = mono_mb_create_and_cache_full (cache, method, mb, csig,
csig->param_count + 16, info, NULL);

leave:
mono_mb_free (mb);
return res;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/mono/mono/metadata/marshal.h
Original file line number Diff line number Diff line change
Expand Up @@ -771,4 +771,7 @@ mono_mb_create_and_cache_full (GHashTable *cache, gpointer key,
IlgenCallbacksToMono*
mono_marshal_get_mono_callbacks_for_ilgen (void);

GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_self)
GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_error)

#endif /* __MONO_MARSHAL_H__ */
13 changes: 12 additions & 1 deletion src/mono/mono/metadata/metadata-internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -656,11 +656,17 @@ struct _MonoMethodSignature {
unsigned int pinvoke : 1;
unsigned int is_inflated : 1;
unsigned int has_type_parameters : 1;
unsigned int suppress_gc_transition : 1;
unsigned int marshalling_disabled : 1;
uint8_t ext_callconv; // see MonoExtCallConv
MonoType *params [MONO_ZERO_LEN_ARRAY];
};

typedef enum {
MONO_EXT_CALLCONV_SUPPRESS_GC_TRANSITION = 0x01,
MONO_EXT_CALLCONV_SWIFTCALL = 0x02,
/// see MonoMethodSignature:ext_callconv - only 8 bits
} MonoExtCallConv;

/*
* AOT cache configuration loaded from config files.
* Doesn't really belong here.
Expand Down Expand Up @@ -1275,4 +1281,9 @@ mono_class_set_deferred_type_load_failure (MonoClass *klass, const char * fmt, .
gboolean
mono_class_set_type_load_failure (MonoClass *klass, const char * fmt, ...);

static inline gboolean
mono_method_signature_has_ext_callconv (MonoMethodSignature *sig, MonoExtCallConv flags) {
return (sig->ext_callconv & flags) != 0;
}

#endif /* __MONO_METADATA_INTERNALS_H__ */
Loading

0 comments on commit be8cb2b

Please sign in to comment.