-
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
Introduce SwiftSelf<T>
and SwiftIndirectResult
to represent Swift structs and enums in C#
#100543
Comments
Tagging subscribers to this area: @dotnet/interop-contrib |
This confused me. What you mean is the example with the
|
That would free the memory held by the |
This proposal does not address any memory management concerns. The
The |
swift trivia - it is impossible for a swift Just so I understand - It might be useful to have an actual void * return value on that constructor so that the difference between a normal constructor and a failable constructor is minor. example: public struct WashingMachine {
// a substantial payload
public init?(String variety) {
if (!GlobalInventory.HasVariety (variety)
return nil
// initialize the payload
}
} In this case you would want to be able to write something like this: _payload = Marshal.AllocHGlobal(_payloadSize).ToPointer();
var result = PIfunc_init((SwiftString)"Bosch", new SwiftIndirectResult(payload));
if (_payload != result) {
Marshal.FreeHGlobal(new IntPtr(_payload));
_payload = null;
// handle however we feel best.
} |
It represents a value that is stored in the return buffer and it will be handled in a similar way to SwiftSelf. The position in the signature doesn't matter, but for simplicity, we may decide to impose certain positional limitations.
Can we utilize SwiftError in this case? _payload = Marshal.AllocHGlobal(_payloadSize).ToPointer();
var swifterror = new SwiftError();
var result = PIfunc_init((SwiftString)"Bosch", new SwiftIndirectResult(payload), ref swifterror);
if (swifterror.Value != null) {
Marshal.FreeHGlobal(new IntPtr(_payload));
_payload = null;
// handle however we feel best.
} Edit: The above can work only for throwing constructor, not failable constructor. |
From what I see from some examples, it looks like failable initializers are modelled as functions that return optional values in Swift. The projection tooling will need to model it similarly. https://godbolt.org/z/3jhn7vqsT I think we should make it illegal to have anything but a |
Ok. Does this imply that functions returning optional value types always pass results by reference, even though they fit into the lowering sequence? |
No -- take a look at the |
Sorry, I am not following. Do we have a pattern for determining if a struct is lowered or not in such cases? The tooling should know when to utilize |
I'm not sure how optionals are represented internally in Swift and what the ABI implications of optionals are. It does not seem to be documented in any of the Swift ABI related documents that I can find. It appears from the Godbolt link that a Swift optional essentially is a frozen type similar to @frozen
public struct Optional<T>
{
public let value : T;
public let hasValue : Int8;
} and also that a frozen struct that has member fields that aren't frozen is itself not considered frozen (makes sense). So when we see |
I don't think it will be generally possible to represent this with a generic I'm not sure what the general plans around Swift generics are, but as it stands I do not see any reasonable way to represent them within the runtimes due to this layout algorithm difference. Any ideas on this issue @jkoritzinsky? |
This is probably not the place to bring up the details of generics, but suffice it to say that I was quite successful in projecting swift generic types into C#. If you keep the payload opaque and use accessors for properties, it's very straightforward.
|
I think the primary complications arise when you have various combinations of frozen structs where we are designing things to avoid the overhead of dynamic allocations. For example, how do we represent the type @frozen
public struct S
{
public let a : Int?;
public let b : Int8;
}; in C#? The straightforward translation is public struct S
{
public SwiftOptional<nint> a;
public sbyte b;
} but I would not expect this to result in the same type layout. I would expect the Swift frozen struct to have |
Again, I think this is beyond the scope of this discussion. The frozen struct example you give, because it contains a non-trivial enum, is not directly representable in C#, but it will still have accessor functions (swift writes these for every var/let). |
I don't think this is beyond the scope of this discussion. It has very direct impact on whether we need to reevaluate the entire design around support for frozen types. We discussed in dotnet/designs#313 how enums will be represented and whether they can be represented as C# structs. Passing
My understanding is that the ABI was frozen in Swift 5 and that we can rely on these details not to change. Otherwise why are we trying to support frozen types at all? |
The ABI was frozen in Swift 5. We're only supporting frozen types because we must (there's no way to avoid the layout requirements). Even if the layout algorithm is not documented today, it's locked-in and stable enough for us to depend on, at least for frozen types. We may end up being the ones to add the documentation to Swift though if Apple hasn't already done so (not my preference). It may be worth introducing a mechanism in .NET to say "don't add padding up to alignment at the end of the type" that we could use for Swift types. With non-generics it's easy enough, but it looks like with generics it may be too difficult to do with the tools we have today. |
Seems like we should be able to add the public readonly unsafe struct SwiftSelf<T> where T: unmanaged |
I have marked this proposal as ready for review since it may take some time to get an API review slot. Meanwhile, we still need to resolve the issue of lowering generic frozen structs. We will reevaluate the design regarding support for frozen structs and update the existing support/this proposal if needed. |
namespace System.Runtime.InteropServices.Swift
{
public readonly struct SwiftSelf<T> where T: unmanaged
{
public SwiftSelf<T>(T value)
{
Value = value;
}
public T Value { get; }
}
public readonly unsafe struct SwiftIndirectResult
{
public SwiftIndirectResult(void* value)
{
Value = value;
}
public void* Value { get; }
}
} |
Background and Motivation
The Swift programming language handles the lifetime of structs and enums differently than .NET. When mapping Swift structs and enums to C# in library evolution mode,
frozen
Swift structs are directly mapped to C# structs with identical field layouts, whilenon-frozen
Swift structs are mapped to C# classes. To support the projection tooling (#95638) for handling these cases, we need to introduce additional runtime support related to passing and returning structs.The proposal is to update the built-in support for the Swift ABI and introduce a new Swift-specific type result buffer access mechanism.
Proposed API
According to the Swift calling convention, structs are lowered into a sequence of primitives. If the number of primitives is 4 or less, the struct is passed by value via registers. Otherwise, it is passed by reference in the call context register. The proposal would be to change
SwiftSelf
to be generic so that a user can passSwiftSelf<T>
for a frozen struct type T which then is either enregistered into multiple registers or passed by reference in the call context register.Additionally, for Swift functions returning non-frozen structs that are projected into C# classes, it is necessary to load return buffer with a memory address. The proposal would be to add a new Swift type
SwiftIndirectResult
that would provide access to the return buffer register.Based on the this, we want to update
SwiftSelf
to handle frozen structs and add a new Swift type to handle returning non-frozen structs.Usage Examples
This API would be used by the projection tooling. The
SwiftSelf<T>
is needed when calling non-mutating instance methods on frozen structs to ensure the correct passing of the value type.The
SwiftIndirectResult
is required to enable the tooling to invoke functions that return non-frozen structs. The runtime will load the return buffer prior to the call based on theSwiftIndirectResult
value. For simplicity, the class does not implement theIDisposable
interface.Please review the proposal and suggest any updates or changes.
/cc: @dotnet/jit-contrib @dotnet/interop-contrib
The text was updated successfully, but these errors were encountered: