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

[Swift bindings] Add Swift code generation doc #2816

Open
wants to merge 10 commits into
base: feature/swift-bindings
Choose a base branch
from

Conversation

kotlarmilos
Copy link
Member

Description

This document:

  • Justifies the need for Swift wrappers and presents the tradeoffs
  • Identifies specific parts of the ABI that require wrappers and explains the rationale
  • Defines the priorities based on selected frameworks

Swift code shipping and trimming will be discussed separately.

@@ -0,0 +1,70 @@
# Swift code generation

Certain parts of the Swift ABI are unsable, such as metadata, async context, dynamic dispatch thunks, and cannot be directly projected into .NET without Swift wrappers. These wrappers provide the flexibility to encapsulate unstable ABI parts into a stable calling convention.
Copy link
Member

Choose a reason for hiding this comment

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

How can Apple use these in Apple OS APIs when they are unstable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Referring to runtime-generated features, such as vtable in the case of generics. Additionally, this would require reverse-engineering async and closure contexts.

It's likely we'll encounter cases that are hard to represent directly in C#. Even if we manage to do so, the maintenance cost would be high. Swift wrappers can address this issue by leveraging the Swift compiler/runtime to bridge the gap.

Copy link
Member

@jkotas jkotas Dec 2, 2024

Choose a reason for hiding this comment

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

I do not think "unstable ABI" is the right word to describe this. Unstable ABI is expected to break from version to version. OS APIs that apps depend on cannot have unstable ABI - it holds independent on .NET.

I would call this "ABI details that are too complex to project to .NET". Does this suggestion describe the problem well?

Copy link
Member

Choose a reason for hiding this comment

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

Alternative: "poorly documented ABI details that are too complex to project to .NET".

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, sounds good.

## Async
Priority: High (required for StoreKit2 and SwiftUI)

Async context in Swift is unstable and require thin wrappers for synhronous invocation. The wrapper will convert async functions into synchronous calls by using a Task as an async worker and a DispatchSemaphore to block until the operation completes. This approach encapsulates the unstable Swift async context, exposing only the stable wrapper interface to .NET.
Copy link
Member

Choose a reason for hiding this comment

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

I would expect that async Swift APIs are going to be exposed as async methods in C#.

"Sync-over-async" is a well-known antipattern that results in poor performance. (It is possible that it is not a real problem for the set of Swift APIs that we are looking at the moment.)

Copy link
Member Author

Choose a reason for hiding this comment

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

We want to offer a solution for async in StoreKit2: https://developer.apple.com/documentation/storekit/product/3851116-products

"Sync-over-async" is a well-known antipattern that results in poor performance.

We would be able to support callconv requirements for the Swift async. I am concerned about the context the layout is unknown/unstable. To resolve this, we could use a think Swift wrapper to represent the context, but project it as IAsyncStateMachine in C#.

I will explore options and update this proposal accordingly.

Copy link
Member

Choose a reason for hiding this comment

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

We just need to be able to create a C# Task on top of the underlying swift. There are many ways to do that on the C# side - we just need to find what we can get from Swift (for example, being able to register a callback which is called when the async operation is finished would be enough to build a Task out of it on the C# side).

src/docs/swift-code-generation.md Outdated Show resolved Hide resolved
src/docs/swift-code-generation.md Outdated Show resolved Hide resolved
src/docs/swift-code-generation.md Outdated Show resolved Hide resolved
## Async
Priority: High (required for StoreKit2 and SwiftUI)

Async context in Swift is unknown and require thin wrappers for synchronous invocation. The wrapper will convert async functions into synchronous calls by using a Task as an async worker and a DispatchSemaphore to block until the operation completes. This approach encapsulates the unknown Swift async context, exposing only the stable wrapper interface to .NET.
Copy link
Member

Choose a reason for hiding this comment

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

Async context in Swift is unknown

I am not sure what you mean by "unknown". This should use a better word.

src/docs/swift-code-generation.md Outdated Show resolved Hide resolved
When called from C#, the wrapper will invoke the superclass implementation in Swift. When called from Swift, the wrapper will use a simulated vtable. This simulated vtable calls into C#, retrieves a GCHandle for the actual C# class, and invokes its implementation, which in turn calls the Swift superclass implementation.

For types with generics, a vtable is generated for each specialization of the type. Additionally, due to limitations in how C# handles unmanaged callbacks, it is not possible to have an unmanaged callback within a generic class. As a result, separate unmanaged and managed vtables are required.

Choose a reason for hiding this comment

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

It's worth noting that there are very, very few uses of classes in idiomatic Swift (as opposed to Swift written to be compatible with ObjC) and as a result, binding classes can be considered, necessary but a lower priority.


### Protocols

The goal is to project C# class in Swift that implements a protocol. Otherwise, a dispatch thunk is generated and invoked to allow for changes.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand this section's wording. I presume we are talking only about the situations when we project the protocol into c#, and then we create a new type on the c# side which implements the said protocol. And then we:

  1. Allow the type to be passed into Swift whenever protocol argument is expected. Swift should then route all the calls back into C# using the simulated vtable.
  2. Allow C# to treat the type as if Swift did not exists (C# call to the the interface method should correctly go to C# implementation of this method)

Do I get this right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the text is missing here. Let me update this section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-SwiftBindings Swift bindings for .NET
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants