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 generator with simple parser and emitter #2525

Merged
merged 26 commits into from
Mar 25, 2024

Conversation

kotlarmilos
Copy link
Member

@kotlarmilos kotlarmilos commented Mar 5, 2024

Description

This PR introduces a toolset for generating C# bindings from Swift interface files. This is the MVP aiming to support the generation of C# bindings for CryptoKit dev templates.

The tooling consists of the following components:

  • SwiftBindings: A command-line interface with Swift ABI parser, marshalling types, and string-based C# emitter.
  • SwiftRuntimeLibrary: A library providing projections of common Swift types.

The parser and emitter are modular and implement ISwiftParser and ICSharpEmitter interfaces. The Swift ABI parser can be replaced with a swiftinterface parser, while the string-based emitter can be replaced with an object model-based emitter.

Workflow:

  1. Parse Swift ABI files to aggregate public ABI.
  2. Generate marshalling information if needed.
  3. Emit C# source code using a string-based emitter.

The unit tests verify the XML layout of the type database and various P/Invoke cases. Additionally, CryptoKit dev templates with unsafe (buffer) pointers are implemented.

The SwiftRuntimeLibrary provides runtime marshaling support and projections of common Swift types.
src/SwiftRuntimeLibrary/src/ISwiftObject.cs Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/SwiftRuntimeLibrary.csproj Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/SwiftCore.xml Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/SwiftCore.xml Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/SwiftCore.xml Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/SwiftCore.xml Outdated Show resolved Hide resolved
docs/README.md Outdated Show resolved Hide resolved
The bindings generator includes a parser, marshaller, and emitter. The parser generates module declarations by consuming an ABI JSON file. The marshaller facilitates type marshalling between C# and Swift, while the emitter is a string-based IndentedTextWriter that outputs C# source code.
@kotlarmilos kotlarmilos changed the title Add SwiftRuntimeLibrary project Swift bindings generator with simple parser and emitter Mar 7, 2024
@kotlarmilos
Copy link
Member Author

This PR has been updated with a bindings generator that includes a simple parser and emitter (refer to the updated description and docs). Please note that this is an MVP for CryptoKit dev templates.

docs/README.md Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/TypeDatabase.cs Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/TypeDatabase.cs Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/TypeDatabase.cs Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/TypeDatabase.cs Outdated Show resolved Hide resolved
src/SwiftRuntimeLibrary/src/TypeDatabase.cs Outdated Show resolved Hide resolved
kotlarmilos and others added 2 commits March 7, 2024 12:09
Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
This commit introduces support for the UnsafeRawPointer type. It also adds a test case for the existing CryptoKit bindings.
Copy link
Member

@matouskozak matouskozak left a comment

Choose a reason for hiding this comment

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

Good job! Looking good and much easier to read through than the original big PR. I put some comments but they are more questions about the future so no need to address them in this PR.

src/Swift.Runtime/src/TypeDatabase.cs Outdated Show resolved Hide resolved
src/Swift.Bindings/src/Model/MethodDecl.cs Outdated Show resolved Hide resolved
src/Swift.Runtime/src/ISwiftObject.cs Outdated Show resolved Hide resolved
This commit introduces support for the UnsafeBufferPointer types. It also adds a test case for the CryptoKit bindings.
@kotlarmilos
Copy link
Member Author

This PR includes projection tooling skeleton and implements CryptoKit dev templates with unsafe (buffer) pointers. We will move forward with surfacing https://developer.apple.com/documentation/cryptokit/chachapoly enum and https://developer.apple.com/documentation/cryptokit/symmetrickey struct in a subsequent PR.

Please review this PR and provide feedback.

Comment on lines +194 to +204
# Swift output files
*.swiftinterface*
*.swiftdoc*
*.swiftmodule*
*.abi*
*.swiftsourceinfo*
*.dylib

# Testing artifacts
testing/
src/samples/**/*Bindings.cs
Copy link
Member

Choose a reason for hiding this comment

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

These should all go under artifacts/. In the current experiment form, this seems okay, but prior to merging into dotnet/runtime, all outputs should be under artifacts/.

Copy link
Member

Choose a reason for hiding this comment

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

Oops. Sorry, I thought we were working in a dotnet/runtime fork. Never mind.

/// <summary>
/// Represents a method declaration.
/// </summary>
public sealed record MethodDecl
Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT Mar 20, 2024

Choose a reason for hiding this comment

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

Using records in public APIs hasn't been confirmed as yet. This is likely okay for the current state. Just be aware that if this is going to be publicly consumable, the API review team may push back.

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

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

LGTM

docs/README.md Outdated
@@ -44,6 +44,8 @@ The table below lists the Swift types and their corresponding C# types.
| `Swift.UInt16` | `ushort` |
| `Swift.Int8` | `sbyte` |
| `Swift.UInt8` | `byte` |
| `Swift.UnsafePointer` | `void*` |
Copy link
Member

Choose a reason for hiding this comment

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

I know we touched on this before - it does not feel right to drop the type information for the strongly typed pointers. Strong typing is an extra level of protection against programming bugs that we are giving away here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, as we discussed, a thin layer would be useful here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Types for unsafe pointers have been added. Mutable pointers are projected as structs with generics in C# to address methods overload issues and enable future extensibility.

public unsafe readonly struct UnsafeBufferPointer
{
private readonly void* _Position;
public readonly nint Count;
Copy link
Member

@jkotas jkotas Mar 21, 2024

Choose a reason for hiding this comment

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

Swift does not seem to have fields as first-class concept. Their docs only talk about properties: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/

I am wondering whether mapping stored properties to fields in C# is conceptually correct.

Is it a breaking change in Swift to change a stored property to a computed property in non-frozen type? I would expect that it is not a breaking change - can you confirm that?

If computed properties and stored properties are interchangeable in Swift, we should map both to properties in C#. Ie make this public nint Count { get; }; or private nint _count; public nint Count => _count;.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. This is not a breaking change in Swift. I confirmed it by consuming a library with struct that evolved from

public struct MyType {
    public var myProperty: Int = 0
    public init() {}
}

to

public struct MyType {
    private var _myProperty: Int = 0
    public var myProperty: Int {
        get { _myProperty }
        set { _myProperty = newValue }
    }

    public init() {}
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Buffer pointers are updated with private readonly nint _count; public nint Count => _count;.

Choose a reason for hiding this comment

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

IIRC, if a property is stored, you still get getters and setter generated by the compiler. Since BTfS doesn't deal with struct lowering, it maps properties (stored or otherwise) as C# properties.

This commit implements mutable pointers as generics in C# and update CryptoKit test cases. The parser is updated to process Swift generic types and projects them into C# generic types.
_count = count;
}

public void* BaseAddress => _position;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public void* BaseAddress => _position;
public T* BaseAddress => _position;

_count = count;
}

public void* BaseAddress => _position;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public void* BaseAddress => _position;
public T* BaseAddress => _position;

Copy link
Member

Choose a reason for hiding this comment

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

All void* in the strongly typed wrappers should be T*.

public unsafe readonly struct UnsafePointer<T>
{
private readonly void* _rawValue;
public UnsafePointer(void* _rawValue)
Copy link
Member

Choose a reason for hiding this comment

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

The argument name should not have underscore. Also, should the argument name match the property name that it corresponds to UnsafePointer(void* poin_rawValue)?

// <summary>
// Represents Swift UnsafePointer in C#.
// </summary>
public unsafe readonly struct UnsafePointer<T>
Copy link
Member

Choose a reason for hiding this comment

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

I think it would make sense to have implicit conversions to/from T* for these types.

Copy link
Member

Choose a reason for hiding this comment

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

(And to/from void* for the non-generic variant.)

{
private readonly void* _position;
private readonly void* _end;
public UnsafeRawBufferPointer(void* start, nint count)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public UnsafeRawBufferPointer(void* start, nint count)
public UnsafeRawBufferPointer(void* baseAddress, nint count)

Match property name?

@kotlarmilos kotlarmilos merged commit df0349a into feature/swift-bindings Mar 25, 2024
6 checks passed
@kotlarmilos kotlarmilos deleted the swift-bindings/runtime-library branch March 25, 2024 09:23
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.

6 participants