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

[mono] Add runtime support for Swift calling convention on x64 and arm64 #94764

Merged
merged 156 commits into from
Feb 6, 2024

Conversation

kotlarmilos
Copy link
Member

@kotlarmilos kotlarmilos commented Nov 15, 2023

Description

This PR 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.

In the method prolog, the self pointer is transferred to a specific register, and in the method epilog, the error register is moved to the stack and returned as an out argument. LLVM support will be addressed in a subsequent PR since the Mono runtime currently doesn't compile native wrappers using LLVM on Apple platforms.

Contributes to #94081 and #64215.

The Swift error argument is not present in the P/Invoke signature, but the context register value can be changed by Swift. Therefore, it must be preserved during the P/Invoke call by updating the used_int_regs.
@matouskozak
Copy link
Member

/azp run runtime-extra-platforms

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@lambdageek lambdageek left a comment

Choose a reason for hiding this comment

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

lgtm. some minor cleanup nits. and two questions:

  1. I'm not sure why the backend looks for both SwiftError and SwiftError* arguments.
  2. Should we prevent these pinvoke wrappers from being inlined?

src/mono/mono/metadata/marshal.c Show resolved Hide resolved
src/mono/mono/metadata/marshal.c Outdated Show resolved Hide resolved
Comment on lines 3641 to 3642
} else if (!m_class_is_enumtype (param_klass) && !m_class_is_primitive (param_klass) && m_class_get_this_arg (param_klass)->type != MONO_TYPE_FNPTR) {
swift_error_args = swift_self_args = 0;
Copy link
Member

Choose a reason for hiding this comment

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

are blittable structs ok to pass to swift?

can we use type_is_blittable ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Blittable struct types should be allowed.

src/mono/mono/metadata/marshal.h Outdated Show resolved Hide resolved
MonoClass *swift_error_ptr = mono_class_create_ptr (m_class_get_this_arg (swift_error));
for (guint i = 0; i < sig->param_count + sig->hasthis; i++) {
MonoClass *klass = mono_class_from_mono_type_internal (sig->params [i]);
if (klass && (klass == swift_error || klass == swift_error_ptr)) {
Copy link
Member

Choose a reason for hiding this comment

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

Why does this look for both SwiftError and SwiftError* ?

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea is to support passing by reference using the ref keyword. However, it is not supported for function pointers, so it is necessary to support SwiftError* as well.

add_param (cinfo, ainfo, sig->params [pindex], FALSE);
ainfo->reg = ARMREG_R20;
continue;
} else if (klass == swift_error || klass == swift_error_ptr) {
Copy link
Member

Choose a reason for hiding this comment

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

same as x64 - why do we look for both SwiftError and SwiftError*

src/mono/mono/mini/mini.c Outdated Show resolved Hide resolved

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)) {
Copy link
Member

Choose a reason for hiding this comment

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

@kotlarmilos @vargaz Do we need to prevent these m2n swiftcall wrappers from being inlined? I think there's logic in the mini-arm64 and mini-amd64 backends that depends on the pinvoke bit of the signature.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is the inlining performed before or after the lowering process? Do the method prolog and epilog get inlined as well?

ArgSwiftError is handled differently in the method prolog, which could be affected when it is inlined. Also, there is a potential problem related to context registers clobbering when wrappers are inlined.

Copy link
Contributor

Choose a reason for hiding this comment

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

These wrappers are not inlined right now.

kotlarmilos and others added 3 commits January 31, 2024 12:18
Co-authored-by: Aleksey Kliger (λgeek) <akliger@gmail.com>
Co-authored-by: Aleksey Kliger (λgeek) <akliger@gmail.com>
MonoClass *klass = mono_class_from_mono_type_internal (sig->params [pindex]);
if (klass == swift_self && sig->pinvoke) {
cinfo->gr--;
add_param (cinfo, ainfo, sig->params [pindex], FALSE);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should call add_param in pinvoke cases. We know that the arg will always be passed in the specific register and I think we should initialize ainfo explicitly.

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea was to reuse code and avoid maintaining changes in multiple places. However, as you suggested, it could be as simple as calculating the size of the payload.

guint32 old_gr = gr;
if (gr >= PARAM_REGS)
gr--;
add_valuetype (sig, ainfo, ptype, FALSE, &gr, &fr, &stack_size);
Copy link
Member

Choose a reason for hiding this comment

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

This looks way more confusing that on arm64 case. We should probably just initialize ainfo explicitly as arg in ireg.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, same as in arm64 case.

@@ -1249,6 +1280,21 @@ mono_arch_set_native_call_context_args (CallContext *ccontext, gpointer frame, M
else
storage = arg_get_storage (ccontext, ainfo);

#ifdef MONO_ARCH_HAVE_SWIFTCALL
if (mono_method_signature_has_ext_callconv (sig, MONO_EXT_CALLCONV_SWIFTCALL)) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we even need all this code ? Looks like for swift self, we should have something like arg in ireg with the context reg specified in ainfo->reg. So arg get storage should already handle it. For swift error similarly the storage is returned, why additional comparison is required ? I think all this code could just be: if (ainfo == ArgSwiftError) *(gpointer*)storage = 0;

Copy link
Member Author

@kotlarmilos kotlarmilos Feb 2, 2024

Choose a reason for hiding this comment

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

You are right. The ctx args are already set properly and should be handled without additional logic. Thanks for the heads up!

@@ -2010,6 +2034,21 @@ mono_arch_set_native_call_context_args (CallContext *ccontext, gpointer frame, M
else
storage = arg_get_storage (ccontext, ainfo);

#ifdef MONO_ARCH_HAVE_SWIFTCALL
if (mono_method_signature_has_ext_callconv (sig, MONO_EXT_CALLCONV_SWIFTCALL)) {
Copy link
Member

Choose a reason for hiding this comment

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

As in amd64 case, I think all this code can be removed. Here we would need to make arg_get_storage correctly return the storage for context regs, since the CallContext doesn't contain all the registers as on amd64.

Copy link
Member

@BrzVlad BrzVlad left a comment

Choose a reason for hiding this comment

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

LGTM overall

@kotlarmilos
Copy link
Member Author

@dotnet/jit-contrib Please take a look at the runtime tests before the merge.

@amanasifkhalid
Copy link
Member

runtime tests LGTM, too

@kotlarmilos kotlarmilos merged commit be8cb2b into dotnet:main Feb 6, 2024
163 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Mar 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants