-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[mono] Add runtime support for Swift calling convention on x64 and arm64 #94764
Conversation
…/kotlarmilos/runtime into poc/swift-interop-direct-pinvoke
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.
/azp run runtime-extra-platforms |
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this 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:
- I'm not sure why the backend looks for both
SwiftError
andSwiftError*
arguments. - Should we prevent these pinvoke wrappers from being inlined?
src/mono/mono/metadata/marshal.c
Outdated
} 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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
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)) { |
There was a problem hiding this comment.
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*
?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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*
|
||
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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Co-authored-by: Aleksey Kliger (λgeek) <akliger@gmail.com>
Co-authored-by: Aleksey Kliger (λgeek) <akliger@gmail.com>
src/mono/mono/mini/mini-arm64.c
Outdated
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/mono/mono/mini/mini-amd64.c
Outdated
guint32 old_gr = gr; | ||
if (gr >= PARAM_REGS) | ||
gr--; | ||
add_valuetype (sig, ainfo, ptype, FALSE, &gr, &fr, &stack_size); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/mono/mono/mini/mini-amd64.c
Outdated
@@ -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)) { |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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!
src/mono/mono/mini/mini-arm64.c
Outdated
@@ -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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall
…ilos/runtime into poc/swift-interop-direct-pinvoke
@dotnet/jit-contrib Please take a look at the runtime tests before the merge. |
runtime tests LGTM, too |
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.