-
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
Introduce CallConvSwift
to represent the Swift ABI calling convention
#64215
Comments
CallConvSwift
to represent the Swift ABI calling convention
What would be the differences between regular C x64/arm64 Apple calling conventions and the CallConvSwift calling conventions? (I have glanced over the documents at https://github.com/apple/swift/tree/main/docs/ABI and I do not see any obvious differences.) |
@jkotas the register usage is different AFAIK but I'm not an expert, I know because of @stephen-hawley who can give a lot of details. |
@jkotas There is definitely some overlap, but there are also novel semantics we would need to deal with if we were support the ABI in the runtime. For example, the error register would be something we would need to respect when calling. How we responded to a non-zero value could vary but we would need to respond in some way – fail fast, convert to .NET exception, etc. |
Agree, the error register looks special. Do all Swift methods set the error register or just some of them? Do we need additional APIs to configure how to deal with the errors returned in the error register? |
Will this handle generic types and especially with regards to passing protocol witness tables? |
Yay! Thanks for joining @stephen-hawley. I created this issue to lure someone who has far more knowledge about the Swift ABI to hopefully chime in. @jkotas brings up a great question about how we would handle the contents of the error register, this will require understanding our options. It would also be interesting to think can users suggest or take over that responsibility from the runtime.
This is another excellent question. If I understand correctly, Swift has a fair number of features that C# doesn't have obvious mappings for. I assume the passing protocol witness tables is one of them. The desire of this API would be to help core and performance sensitive APIs, for example the native UI stack supporting MAUI. If this couldn't be used to help improve the MAUI experience, then I think it would be less compelling, at least in the near term. I'd defer to your thoughts on how much of the Swift ABI we would need and if "all of it" is the answer, then the answer to the above question would need to be "yes" I think. Once we get the list of what we need to support our next step would be to ensure that we could represent it in C# and if not, we can start that ask. This issue is really about pushing the conversation and getting clarity in the open on needs both first party and from the community. |
With regards to the error register, if you have a method in swift that looks like this: public func foo () throws -> SomeType
{
} If the method throws it will set the error register to the error. If it does not then the error register is zero and the return value is valid. In Binding Tools for Swift, I work around this by writing an adapter to the method. For the example, the adapter looks like this: public func adapt_foo(return: UnsafeMutablePointer<SomeType, Error, Bool>)
{
do {
var return = try foo ()
setExceptionNotThrown(value:return, retval:retval);
} catch let err {
setExceptionThrown(err:err, retval: retval);
}
} In addition, how will this handle closures, which in addition to the calling conventions will also have a dedicated register (the I understand that you're looking at Maui, but I'm trying to get as close to 100% coverage to the swift language from C# as I can. Do you have plans for handling the ARC in value types? For example, since swift has semantics for what happens to an object when it gets copied and destroyed, I can't model value types as C# value types, so instead I model them as a class implementing I have so many questions. In a good way. |
With regards to protocol witness tables, these come into play in a couple of different contexts. public func foo<T:SomeProtocol>(a: Int) {
} This actually gets implemented as under the hood like this: public func foo<T>(T:SomeProtocol, tType: T.Self, pwt: ProtocolWitnessTableForSomeProtocolWithRespectToT) {
} In other words, for every generic type, there is an extra argument added in that is the swift equivalent of the C# Where this gets particularly interesting is when I need to model a class with a generic virtual method: open class SomeClass {
public init () { }
open func someMethod<T: SomeProtocol>(value: T) -> T {
}
} To model this, I write an adapter class that overrides each of the virtual methods, then vectors into C# through a table of function pointers that receive the arguments. The problem is that I need to pass in the type of T (easy, I can get it through public func protocolWitnessTableOf<T:SomeProtocol>(t: T) -> OpaquePointer {
return swiftAsmArg2(); // returns pwt
// void *swiftAsmArg2(void *arg0, void *arg1, void *arg2) { return arg2; }
} With regards to the task of calling virtual methods in swift, I'm torn on this. Virtual methods use a self pointer. I have to wrap any virtual method like this: public func virtual_someMethodOnFoo(this: Foo, arg0: SomeValueType) {
this.someMethod(arg0);
} The problem with this is that And if it helps, I have a lot of this already documented here. Feel free to reach out with questions. For sure I think we should get our stories straight in terms of how to ensure that marshalling value types works. For example, all my mappings of value types implement the interface public interface ISwiftValueType : IDisposable {
byte [] SwiftData { get; set; }
} And all swift class types implement the interface public interface ISwiftObject : IDisposable {
IntPtr SwiftObject { get; }
} It would be super nice if I could write a pinvoke for the previous example of a method wrapper as: [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("SwiftLibrary", EntryPoint = "export")]
internal static void virtual_someMethodOnFoo(ISwiftObject foo, ISwiftValueType value); // or maybe I use the real types here? |
The proposal wouldn't be about enabling marshalling support in the same way we support marshalling a value type to a C/C++ struct. This would be about ensuring we place arguments in the correct registers and following calling convention semantics. The actual marshalling logic would be source generated – see DllImport generator. However, if we couldn't source generate marshalling code that would be an indication the runtime needs to provide something, but only after ensuring there was no source generation solution. I think the best example of this would be the error register above. Are there others? |
Yes! That needs to happen. It is on my list, be prepared :-) |
After looking at the Swift calling convention docs again and in particular at the "library evolution" support in Swift 5+, I think we may need to introduce 2 types here depending on if we want to support the "non-stable" Swift ABI (which has some perf benefits). We'd likely need one type for the "stable/evolution" ABI and one for the "non-stable" ABI. The "stable" ABI would be best-used with dynamically linked libraries (like the system libraries), and the "non-stable" ABI would be best for libraries that users would link into their final apps (as they would not version separately from the managed code). |
Could you please share link to details? |
I'll try to find more info, but the basics are here: https://www.swift.org/blog/library-evolution/ The main differences are captured here in the differences between the
If a type is marked as There's also some cases around generics in Swift that allow reducing the number of indirections when using the "non-stable" ABI, mainly focused around being able to invoke specific specializations when types are known instead of always needing to go through the abstracted signature with all of the witness tables. The exhastive list of rules around the "stable/evolution" ABI vs the "non-stable" ABI are here: https://github.com/apple/swift/blob/main/docs/LibraryEvolution.rst I still haven't read through the whole thing yet, but sharing it now so everyone can see it. |
More information about the changes made in the Swift calling convention for the "stable" ABI are found here: https://github.com/apple/swift/blob/main/docs/ABIStabilityManifesto.md#calling-convention |
Looking at LLVM and the swift compiler, there are two calling conventions: one is used for regular swift calls, the other for async/await swift calls. The stable vs unstable ABI handling is at a higher layer. We can likely use a similar layering in our design and only have |
We probably don't need the unstable for now or even in the longer run in most cases. Adding support for the unstable would be premature for now, since my understanding the unstable is for the "static" linking scenarios (i.e., internal). As an interop scenario the priority and focus should be on targeting the stable ABI unless we have a concrete and business need scenario - not simply a "nice to have". |
I agree, we likely don't need the unsable ABI. In any case, LLVM uses the same calling convention for both ABIs (the ABI handling is above the callconv layer), so we shouldn't need to worry about that anyway in the workt to implement CallConvSwift. We likely do need to handle the "tail call" calling convention if we decide to support interop with Swift's async/await, but I think that's a larger feature that we don't need to do immediately. |
To be clear, we need to use the library evolution code. In determining the front-facing ABI, we needed reflection on the swift types and I had originally hacked the swift compiler to generate that for me. The problem is that we had to keep the reflection going lock-step with the compiler because Apple broke the serialized AST format routinely. With library evolution, they generate a I've been thinking over what would help me the most for the swift ABI to simplify my code and to improve the performance. The biggest one would be to have an attribute like
and the instance handle would get put into the swift "self" register. Previously, this wouldn't help since method dispatch is done through a vtable, but in library evolution, they create adapter thunks that do the vtable call for you. The win here is that as written today, my pinvokes with generate code that does worst case register juggling, this change will have 0 register juggling. Other things that would make the biggest wins: handling of swift errors - these also go in their own registers. If I could write a pinvoke that included and argument like this: Auto-splatting of tuples that are arguments - tuples as arguments in swift get flattened and passed as individual arguments. So if you do I'm sure I'll add more to this issue as I think of it. |
I think there's another criteria we can add: we shouldn't need to generate any wrapping code on the Swift side either. I also have a question about the scope for source generators: could source generators be required to know about the target CPU, or should the runtime hide differences between CPUs? Made-up example: say I have a composite value type of 4 intptrs - on CPU A that's passed in 4 different registers, while on CPU B that's passed as a pointer in a single register. A source generator could easily generate two different P/Invoke methods and call the right one at runtime with the arguments in the correct locations, but IMHO this is best handled by the runtime, even though it can technically be handled by a source generator. |
Yes, these details should be handled by the runtime. You example should not need two different P/Invoke methods. |
Should all the structs be |
I'll update them to be readonly. |
My vote would be to place them all under I prefer |
Looks good! I agree with Aaron, option 1 provides the complete context. I think projection tools would rely on managed pointers like At the runtime layer, our focus is on the Mono runtime support; let us know if you would like us to proceed with the |
These special values may or may not be passed in a (processor) register depending on the target ABI - see https://github.com/llvm/llvm-project/blob/a1b2ace137385388bf9bd7ea4b6df3ff298900f6/clang/lib/CodeGen/ABIInfo.h#L142C1-L143 . Calling it a register can be misleading since it won't always match reality. We have included
It was intentional change made during API review. #44659 (comment) says "We renamed TrackedNativeReferenceAttribute to ObjectiveCTrackedTypeAttribute". Should we follow the established pattern? |
I'm in favor of deferring to the Swift community and documentation rather than any implementation detail of LLVM. Compilers are notorious for naming things. See the two below from the Swift repo - we should chose one and reference that as the stated term. Perhaps looking at C++'s work in this area. From the manifesto, it is "Call Context Register" - https://github.com/apple/swift/blob/main/docs/ABIStabilityManifesto.md#call-context-register But from the calling convention doc it is simply "self" - https://github.com/apple/swift/blob/main/docs/ABI/CallingConvention.rst#self
I think that is appropriate. I will say it was considered such narrow, mostly backwards looking, support that prepending ObjectiveC wasn't too bad. Since Swift is forward looking perhaps erring on the side of brevity is warranted. |
The low-level C/C++ - Swift interop looks like this: https://godbolt.org/z/W7YhcqhrM . __attribute__((swiftcall)) void noop(__attribute__((swift_context)) void *unused, __attribute__((swift_error_result)) void **error)
{
*error = 0;
} They do not call it register. Also, notice that it is The original swift ABI manifesto calls it register to communicate the design idea. It is not up to date spec. It was not updated to match what was actually implemented.
The .NET Framework design guidelines say:
|
Yes, that would be my preference. |
To be fair these are arbitrary gcc/clang attributes. I'm good matching them but these are really specific to gcc/clang. |
Call it the self register – that’s easiest.
I think the reason why it’s called the “call context register” is that a similar convention is used in 3 different contexts:
1. Instance methods: CCR is self
2. Class methods: CCR is type metadata pointer which is effectively self for the class
3. Closures: CCR is the pointer to data bound at closure creation time and is therefore the actual context of the closure
This may sound like I’m actually advocating for using the most precise term, CCR, but no. CCR is a mouthful. Self is terse and is a fine candidate for a synonym. In BTfS, I call it ‘self’ in C# projections and ‘this’ in swift projections. Why? Because ‘self’ is pretty much never used in C# and has no special meaning (yet, but we can always hope) and ‘this’ is pretty much never used in swift and has no special meaning (yet, same). And while I could surround ‘self’ with backticks in swift, the Swift language guide says quite explicitly to avoid this: “However, avoid using keywords as names unless you have absolutely no choice”
Steve
From: Aaron Robinson ***@***.***>
Date: Monday, October 30, 2023 at 2:06 PM
To: dotnet/runtime ***@***.***>
Cc: Comment ***@***.***>
Subject: Re: [dotnet/runtime] Introduce `CallConvSwift` to represent the Swift ABI calling convention (Issue #64215)
Calling it a register can be misleading since it won't always match reality.
I'm in favor of deferring to the Swift community and documentation rather than any implementation detail of LLVM. Compilers are notorious for naming things. See the two below from the Swift repo - we should chose one and reference that as the stated term. Perhaps looking at C++'s work in this area<https://www.swift.org/documentation/cxx-interop>.
From the manifesto, it is "Call Context Register" - https://github.com/apple/swift/blob/main/docs/ABIStabilityManifesto.md#call-context-register
But from the calling convention doc it is simply "self" - https://github.com/apple/swift/blob/main/docs/ABI/CallingConvention.rst#self
—
Reply to this email directly, view it on GitHub<#64215 (comment)> or unsubscribe<https://github.com/notifications/unsubscribe-auth/ADT6WSQP2CTTMWN7ZOXSCT3YB7UDTBFKMF2HI4TJMJ2XIZLTSWBKK5TBNR2WLJDUOJ2WLJDOMFWWLO3UNBZGKYLEL5YGC4TUNFRWS4DBNZ2F6YLDORUXM2LUPGBKK5TBNR2WLJDUOJ2WLJDOMFWWLLTXMF2GG2C7MFRXI2LWNF2HTAVFOZQWY5LFUVUXG43VMWSG4YLNMWVXI2DSMVQWIX3UPFYGLAVFOZQWY5LFVIYTMNZVGM4DKNJQHCSG4YLNMWUWQYLTL5WGCYTFNSBKK5TBNR2WLKRRGY3TKMZYGU4DENVENZQW2ZNJNBQXGX3MMFRGK3FMON2WE2TFMN2F65DZOBS2YSLTON2WKQ3PNVWWK3TUUZ2G64DJMNZZJAVEOR4XAZNKOJSXA33TNF2G64TZUV3GC3DVMWUTEMJQG4YTMMBQGWBKI5DZOBS2K2LTON2WLJLWMFWHKZNKGEYTCMRZGQYDGNJSQKSHI6LQMWSWYYLCMVWKK5TBNR2WLKRRGY3TKMZYGU2TAOECUR2HS4DFUVWGCYTFNSSXMYLMOVS2UMJWG42TGOBVHAZDNJ3UOJUWOZ3FOKTGG4TFMF2GK>.
You are receiving this email because you commented on the thread.
Triage notifications on the go with GitHub Mobile for iOS<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675> or Android<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
I have included the additional details specifying the types to represent each of the special registers into this issue. Please review it and update it accordingly before marking it as |
I just realized that this issue is still marked as |
In order to push forward the runtime support for the Swift calling convention we need approval for this issue and #95065. Does anyone need to be explicitly informed? |
I have marked this issue as blocking. It should give it a priority for API review. Also, you can email @terrajobst to find out when it will be scheduled for API review. |
namespace System.Runtime.CompilerServices
{
public class CallConvSwift
{
public CallConvSwift() { }
}
}
namespace System.Runtime.InteropServices.Swift
{
public readonly unsafe struct SwiftSelf
{
public SwiftSelf(void* value) {
Value = value;
}
public void* Value { get; }
}
public readonly unsafe struct SwiftError
{
public SwiftError(void* value) {
Value = value;
}
public void* Value { get; }
}
} |
Background and Motivation
The Swift programming language has a different ABI, runtime environment, and object model, making it challenging to call from the .NET without the runtime support. Ideally, we want to avoid generating extra wrappers in the objection tools and attempt to directly call all kinds of Swift functions.
For interop developers of managed APIs wrapping native Swift API layers, reconciling the Swift ABI with the existing P/Invoke options is close to intractable. The proposal would be to add built-in support for the Swift ABI via P/Invokes and Reverse P/Invokes through
UnmanagedCallConvAttribute
. This would directly help out binding efforts in the MAUI space.Proposed API
According to the calling convention, the
self
context has dedicated registers, and it is always passed through them since it's heavily used. Methods calling other methods on the same object can share the self context.Here are cases when
self
context is passed via register:Error handling is also handled through registers, so the caller needs to check for
error
and throw if necessary. Implementing P/Invoke thunks should simplify registers juggling by using predefined set of registers in these scenarios.Based on the design doc we want to introduce types to represent each of the special registers.
Usage Examples
This API would be used in the P/Invoke and Reverse P/Invoke syntax as follows:
Using the
CallConv*
approach would also permit use in C#unmanaged
function pointers. This would follow the Swift ABI during dispatch. Documentation for the Swift ABI can be found here and its design philosophy here./cc @dotnet/jit-contrib @dotnet/interop-contrib @Redth
The text was updated successfully, but these errors were encountered: