-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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][swift-interop] Support for Swift frozen struct lowering in mini-JIT/AOT/interpreter #102143
[mono][swift-interop] Support for Swift frozen struct lowering in mini-JIT/AOT/interpreter #102143
Conversation
Tagging subscribers to this area: @steveisok, @lambdageek |
outside Swift struct lowering algorithm
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.
mostly lgtm. I'm not 100% sure about using memcpy on StackInfo
in the interpreter.
I think (for the JIT especially) we need to be careful about where we allocate the new signature.
It would be nice if we could avoid g_array_new
- can we have a faster path for, let's say, upto 16 arguments that uses a temporary stack allocation for the working array?
I think it might make sense to move the code for doing the lowering into a helper function in transform.c (and in method-to-ir.c) rather than doing everything inline in the giant switch statements. That might keep the code a bit cleaner and easier to skip when someone is reading and they're not interested in Swift
StackInfo *sp_old_params = (StackInfo*) mono_mempool_alloc (td->mempool, sizeof (StackInfo) * csignature->param_count); | ||
memcpy (sp_old_params, td->sp, sizeof (StackInfo) * csignature->param_count); | ||
|
||
GArray *new_params = g_array_new (FALSE, FALSE, sizeof (MonoType*)); |
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.
Can you use g_array_sized_new
with an initial size equal to the old param count (plus 4?). that would avoid resizing most of the time.
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.
Yes, I changed it to use the old param count since we are unsure ahead-of-time if any lowering will occur.
src/mono/mono/mini/method-to-ir.c
Outdated
// Create a new dummy signature with the lowered arguments | ||
MonoMethodSignature *swiftcall_signature = mono_metadata_signature_alloc (m_class_get_image (method->klass), new_param_count); | ||
for (uint32_t idx_param = 0; idx_param < new_param_count; ++idx_param) { | ||
swiftcall_signature->params [idx_param] = g_array_index (new_params, MonoType *, idx_param); | ||
} | ||
swiftcall_signature->ret = fsig->ret; | ||
swiftcall_signature->pinvoke = fsig->pinvoke; | ||
swiftcall_signature->ext_callconv = fsig->ext_callconv; | ||
|
||
fsig = swiftcall_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.
- Can we make a helper function and share it between the JIT and interp?
- I'm not sure
m_class_get_image (method->klass)
is where we want to allocate the signature. If the method belongs to a generic instance, we should use the mem manager of the generic instance (basically a mem manager that depends on the assemblies of the generic definition and the generic type params).
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.
Good point, I created a new helper function mono_metadata_signature_dup_new_params
insidemetadata.c
.
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.
Thanks for making changes verbose.
The helper function duplicates signature metadata and adds new parameters to the signature. + pre-allocating new_params to the old params size
Only for interpreter at the moment because mono_method_to_ir is more complicated due to NEW_ARGLOADA macros
Thank you for the feedback. I made several changes to improve the PR.
Done, removed the use of
Done, changed the allocation of new signature to use
We discussed that offline and agreed to stick with current dynamic allocation because we don't know the final number of parameters up front. In the future, we might change the process and collect the struct lowering information a little bit earlier (e.g. during marshalling runtime/src/mono/mono/metadata/marshal.c Lines 3698 to 3730 in 19467dc
Done for interpreter by creating |
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.
Changes to the lowering algorithm LGTM
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!
{ | ||
MonoMethodSignature *new_csignature; | ||
g_assert (!csignature->hasthis); // P/Invoke calls shouldn't contain 'this' | ||
|
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 argument moving around that happens here will break the fast offset allocator (where we allocate storage for vars immediately as we push it on the td->sp
stack). This appears however to be fine because we disable tiering for wrappers and I would imagine a swift call is only happening from a m2n wrapper. However I would assert here that td->optimized
with the comment that argument reordering here doesn't handle on the fly offset allocation and requires the full var offset allocator pass that is only ran for optimized code.
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.
Yes, we only need to perform the struct lowering for m2n (and later eventually n2m) wrappers. I added the assert with the comment you suggested.
/azp run runtime-extra-platforms |
Azure Pipelines successfully started running 1 pipeline(s). |
…i-JIT/AOT/interpreter (dotnet#102143) * refactor + bug fixes for swift struct marshalling * mini struct lowering * interpreter swift struct lowering * enable SwiftAbiStress on Mono
Description
This PR adds support for lowering of Swift
@frozen
structs in P/Invoke arguments. In short, in Swift calls,@frozen
structs can be passed as a sequence of up to 4 primitives using registers or stack (see Swift Calling Convention). The general algorithm was implemented in #99439 and this PR connects it to the Mono codegen backends. This PR doesn't handle returns of Swift@frozen
structs and reverse P/Invoke scenarios (tracked at #102077).Changes
The connection to codegen is done at Mono IR level. First, in
method-to-ir.c
for connection to JIT and AOT backends. Second, intransform.c
for interpreter. Both connection are similar.Before doing call to native Swift code, we explore the signature for any struct types. If struct encountered (not
SwiftSelf
orSwiftError
which are handled individually), we execute the lowering algorithm. Based on the result we have 2 outcomes:Structure can be lowered into a sequence of up to 4 primitives:
The structs argument in signature is replaced by the lowered sequence. The sequence elements are taken from the original structs at previously calculated offsets (struct base address + element offset). For detail how are the offsets calculated see
mono_marshal_get_swift_physical_lowering
.Structure must be passed by reference:
The structs argument in signature is replaced by a byref type that represents address of the structs.
Non-struct types are preserved. The modified signature is used just for the call to native Swift code (for non-P/Invoke calls original signature is used).
Lastly, refactoring and bug fixes for marshaling inside
mono_marshal_get_swift_physical_lowering
.MonoTypeEnum
toMonoType*
as this is the most common representation of types in the later Mono code flowMONO_ABI_SIZEOF (MonoObject)
)remaining_interval_size
for final lowering loopVerification
Currently, Mono CI only has coverage for JIT/interpreter but fullAOT verification was done locally. This PR passes an extended
SwiftAbiStressTest
of 5000 signature locally both on x64 and arm64 with JIT/interpreter/fullAOT backends.Alternatives
During preparation of this PR, several alternatives were explore and decided not to further proceed.
mini-<arch>.c
files). This option was discarded because Mini currently doesn't provide representation for value types that can be passed via registers and stack simultaneously. While handling it manually for this PR would be possibly, the final code was long and error prone. Additionally, the code would have to be duplicated for each architecture.marshal-lightweight.c
). This option was successfully implemented and provided a shared implementation for both Mini and interpreter. However, we decided to rather implement it at the Mono IR level. This has two benefits, firstly we get better control over the resulting assembly as we are closer to the Mono codegen. Secondly, we are able to keep interpreter code more independent for potential deployment of interpreter outside Mono.FYI: @jakobbotsch @amanasifkhalid @jkurdek @vitek-karas