From d5ba6aedd8aade730ba378e8fe64d4c00d7a4531 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 8 Feb 2024 18:02:25 +0100 Subject: [PATCH 01/11] Add initial documentation skeleton --- docs/README.md | 202 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 5709668e65ee..51b2919692f8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,201 @@ -# Documentation +# .NET Swift interop tooling documentation -This document outlines the functional design of the tooling and projections between Swift and .NET. \ No newline at end of file +This document provides a detailed overview of the .NET Swift interop tooling, focusing on the projections between Swift and .NET, and the functional design of the tooling. + +## Projections + +This section outlines the mappings between Swift and .NET types, and describes how the tool generates C# bindings for a Swift library. For types with similar semantics across Swift and .NET, direct interop is possible and bindings are generated. For types without direct projection, additional Swift wrappers are required and it is the user's responsibility to generate these wrappers. At this stage, the tool is designed to avoid generating any Swift code if possible to avoid complexity of maintaining different Swift compiler and SDK versions. The tool should only generate bindings for Swift types that are part of the stable ABI and don't evolve. + +### Primitive types + +The table below lists the Swift types and their corresponding C# types. + +| Swift Type | C# Type | +| ------------------------------- | -------- | +| `Swift.Int64` | `long` | +| `Swift.UInt64` | `ulong` | +| `Swift.Int32` | `int` | +| `Swift.UInt32` | `uint` | +| `Swift.Int16` | `short` | +| `Swift.UInt16` | `ushort` | +| `Swift.Int8` | `sbyte` | +| `Swift.UInt8` | `byte` | +| `Swift.UnsafeRawPointer` | `IntPtr` | +| `Swift.UnsafeMutableRawPointer` | `IntPtr` | +| `Int` | `nint` | +| `UInt` | `nuint` | +| `Bool` | `bool` | +| `Float` | `float` | +| `Double` | `double` | + +Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mappingcan fit within the underlying calling convention as these types are below the size limit for being passed by reference. + +### Structs + +Swift structs are projected into C# as `IDisposable` classes, implementing `ISwiftStruct`, which inherit from `ISwiftNominalType` and `ISwiftDisposable`. This approach is chosen due to the semantic differences in lifecycle management between Swift and C# types. Bindings are generated with `SwiftStructAttribute` that inherits `SwiftNominalTypeAttribute`. This attribute contains information about the Swift type. + +Given the following Swift struct declaration: +```swift + public struct BarInt { + public var X:Int; + public init(x:Int) { + X = x; + } + } +``` + +The projection tooling will generate the following C#, with function bodies left empty for simplicity: +```csharp + using System; + using System.Runtime.InteropServices; + using SwiftRuntimeLibrary; + using SwiftRuntimeLibrary.SwiftMarshal; + + namespace NewClassCompilerTests + { + [SwiftStruct("libNewClassCompilerTests.dylib", + "$s21NewClassCompilerTests6BarIntVMn", + "$s21NewClassCompilerTests6BarIntVN", "")] + public class BarInt : ISwiftStruct + { + public BarInt(nint x) + { + } + internal BarInt(SwiftNominalCtorArgument unused) + { + } + public static SwiftMetatype GetSwiftMetatype() + { + } + public void Dispose() + { + } + void Dispose(bool disposing) + { + } + ~BarInt() + { + } + public byte[] SwiftData + { + get; set; + } + public nint X + { + get { } + set { } + } + } + } +``` + +There is a payload `SwiftData` along with two constructors. The first maps onto the `init` method inside the swift class. The second is an internal constructor that gets used to define an uninitialized type. This constructor gets used by the marshaler when a type needs to be allocated before it gets used, for example, as a return value because Swift semantics don’t allow to explicitly have variables in an uninitialized state. + +All properties bound in C# access the value through accessor functions. If a Swift property or a Swift method mutates the value, the contents of the C# `SwiftData` property will get changed as well. + +### Enums + +Swift enums are projected into C# as `IDisposable` classes, implementing `ISwiftEnum`, in similar manner as structs. + +Given this Swift enum: +```swift + public enum FooECTIA { + case a(Int) + case b(Double) + } +``` + +The projection tooling will generate the following C#, with function bodies left empty for simplicity: +```csharp + using System; + using System.Runtime.InteropServices; + using SwiftRuntimeLibrary; + using SwiftRuntimeLibrary.SwiftMarshal; + + namespace NewClassCompilerTests + { + public enum FooECTIACases + { + A, B + } + [SwiftEnumType("libNewClassCompilerTests.dylib", + "$s21NewClassCompilerTests8FooECTIAOMn", + "$s21NewClassCompilerTests8FooECTIAON", "")] + public class FooECTIA : ISwiftEnum + { + public void Dispose() + { + } + void Dispose(bool disposing) + { + } + ~FooECTIA() + { + } + public static FooECTIA NewA(nint value0) + { + } + public static FooECTIA NewB(double value0) + { + } + public byte[] SwiftData + { + get; set; + } + public nint ValueA + { + get { } + } + public double ValueB + { + get { } + } + public FooECTIACases Case + { + get { } + } + } + } +``` + +### Scaling and trimming + +The bindings should be organized into namespaces to mitigate scalability issues. Additionally, generated bindings should be trim-compatible. + +This subsection will be expanded during the design review process. + +### Unsupported types + +This subsection describes cases when direct interop is not possible and will be expanded during the design review process. This is a subset of features that require Swift wrappers provided by users: +- Protocols and classes with virtual methods +- Closures +- Passing a struct by value in more registers than P/Invoke will allow +- Exporting .NET into Swift + +## Functional outline + +The tooling consists of the following components: +- **CLIInterface**: A command-line interface that orchestrates the tooling workflow. +- **SwiftReflector**: A component that aggregates public API definitions from Swift modules. +- **Dynamo**: A component that provides API for C# code generation. +- **SwiftRuntimeLibrary**: A component that provides runtime marshaling support and common Swift type projections. + +The `SwiftRuntimeLibrary` This component is a library that provides basic support for Swift interop that the generated code builds on. It includes bindings for common built-in types like arrays and dictionaries. It provides a single source of truth for Swift types so that they can be exposed across an assembly boundary. + +### Workflow + +The general workflow for generating C# bindings from Swift code is as follows: + +1. Process the Swift library interface (`.swiftinterface`) with the SwiftReflector and generate `TLDefinition` with mappings between entry points and mangled names. +2. Aggregate a public API based on `.swiftinterface` with the SwiftReflector. +3. Generate surce code for C# bindings with Dynamo and compile the code into a managed library. + +### Shipping + +This subsection will be expanded during the design review process. + +### User expirience + +The tooling perhaps doesn't need to surface all APIs, but only those that are used. Users should be able to indicate a subset of APIs that should be bound. + +This subsection will be expanded during the design review process. From 7a80ed83932b138c5c27fb03d08f9b9cd7abcfc8 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 13:30:42 +0100 Subject: [PATCH 02/11] Update docs/README.md Co-authored-by: Aaron Robinson --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 51b2919692f8..c92a0c45b52e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,7 +28,7 @@ The table below lists the Swift types and their corresponding C# types. | `Float` | `float` | | `Double` | `double` | -Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mappingcan fit within the underlying calling convention as these types are below the size limit for being passed by reference. +Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mapping can fit within the underlying calling convention as these types are below the size limit for being passed by reference. ### Structs From 1d23cbfbd74136cb78479e998491f6b48e851de9 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 13:42:41 +0100 Subject: [PATCH 03/11] Update docs/README.md Co-authored-by: Vitek Karas <10670590+vitek-karas@users.noreply.github.com> --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index c92a0c45b52e..0100132957af 100644 --- a/docs/README.md +++ b/docs/README.md @@ -188,7 +188,7 @@ The general workflow for generating C# bindings from Swift code is as follows: 1. Process the Swift library interface (`.swiftinterface`) with the SwiftReflector and generate `TLDefinition` with mappings between entry points and mangled names. 2. Aggregate a public API based on `.swiftinterface` with the SwiftReflector. -3. Generate surce code for C# bindings with Dynamo and compile the code into a managed library. +3. Generate source code for C# bindings with Dynamo and compile the code into a managed library. ### Shipping From 29a506747d3f1d7b2b10a1cb8a469af85d0d982b Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 14:20:28 +0100 Subject: [PATCH 04/11] Update primitive types mapping --- docs/README.md | 169 ++++++++++++++++++++++--------------------------- 1 file changed, 77 insertions(+), 92 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0100132957af..54515a06febc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # .NET Swift interop tooling documentation -This document provides a detailed overview of the .NET Swift interop tooling, focusing on the projections between Swift and .NET, and the functional design of the tooling. +This document provides a detailed overview of the .NET Swift interop tooling, focusing on the projections between Swift and .NET, and the functional design of the tooling. The projection tooling is intended for use with C# and any other .NET language is beyond its scope. ## Projections @@ -20,15 +20,16 @@ The table below lists the Swift types and their corresponding C# types. | `Swift.UInt16` | `ushort` | | `Swift.Int8` | `sbyte` | | `Swift.UInt8` | `byte` | -| `Swift.UnsafeRawPointer` | `IntPtr` | -| `Swift.UnsafeMutableRawPointer` | `IntPtr` | +| `Swift.UnsafeRawPointer` | `void*` | +| `Swift.UnsafeMutableRawPointer` | `void*` | | `Int` | `nint` | | `UInt` | `nuint` | | `Bool` | `bool` | | `Float` | `float` | | `Double` | `double` | -Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mapping can fit within the underlying calling convention as these types are below the size limit for being passed by reference. + +All C# types mentioned are blittable except for `bool`. To facilitate `P/Invoke`, a lightweight wrapper is required to convert `bool` to `byte`. Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mapping can fit within the underlying calling convention as these types are below the size limit for being passed by reference. ### Structs @@ -46,47 +47,39 @@ Given the following Swift struct declaration: The projection tooling will generate the following C#, with function bodies left empty for simplicity: ```csharp - using System; - using System.Runtime.InteropServices; - using SwiftRuntimeLibrary; - using SwiftRuntimeLibrary.SwiftMarshal; - - namespace NewClassCompilerTests - { - [SwiftStruct("libNewClassCompilerTests.dylib", - "$s21NewClassCompilerTests6BarIntVMn", - "$s21NewClassCompilerTests6BarIntVN", "")] - public class BarInt : ISwiftStruct - { - public BarInt(nint x) - { - } - internal BarInt(SwiftNominalCtorArgument unused) - { - } - public static SwiftMetatype GetSwiftMetatype() - { - } - public void Dispose() - { - } - void Dispose(bool disposing) - { - } - ~BarInt() - { - } - public byte[] SwiftData - { - get; set; - } - public nint X - { - get { } - set { } - } - } +[SwiftStruct("libNewClassCompilerTests.dylib", + "$s21NewClassCompilerTests6BarIntVMn", + "$s21NewClassCompilerTests6BarIntVN", "")] +public class BarInt : ISwiftStruct +{ + public BarInt(nint x) + { + } + internal BarInt(SwiftNominalCtorArgument unused) + { + } + public static SwiftMetatype GetSwiftMetatype() + { + } + public void Dispose() + { } + void Dispose(bool disposing) + { + } + ~BarInt() + { + } + public byte[] SwiftData + { + get; set; + } + public nint X + { + get { } + set { } + } +} ``` There is a payload `SwiftData` along with two constructors. The first maps onto the `init` method inside the swift class. The second is an internal constructor that gets used to define an uninitialized type. This constructor gets used by the marshaler when a type needs to be allocated before it gets used, for example, as a return value because Swift semantics don’t allow to explicitly have variables in an uninitialized state. @@ -107,55 +100,47 @@ Given this Swift enum: The projection tooling will generate the following C#, with function bodies left empty for simplicity: ```csharp - using System; - using System.Runtime.InteropServices; - using SwiftRuntimeLibrary; - using SwiftRuntimeLibrary.SwiftMarshal; - - namespace NewClassCompilerTests - { - public enum FooECTIACases - { - A, B - } - [SwiftEnumType("libNewClassCompilerTests.dylib", - "$s21NewClassCompilerTests8FooECTIAOMn", - "$s21NewClassCompilerTests8FooECTIAON", "")] - public class FooECTIA : ISwiftEnum - { - public void Dispose() - { - } - void Dispose(bool disposing) - { - } - ~FooECTIA() - { - } - public static FooECTIA NewA(nint value0) - { - } - public static FooECTIA NewB(double value0) - { - } - public byte[] SwiftData - { - get; set; - } - public nint ValueA - { - get { } - } - public double ValueB - { - get { } - } - public FooECTIACases Case - { - get { } - } - } +public enum FooECTIACases +{ + A, B +} +[SwiftEnumType("libNewClassCompilerTests.dylib", + "$s21NewClassCompilerTests8FooECTIAOMn", + "$s21NewClassCompilerTests8FooECTIAON", "")] +public class FooECTIA : ISwiftEnum +{ + public void Dispose() + { + } + void Dispose(bool disposing) + { + } + ~FooECTIA() + { + } + public static FooECTIA NewA(nint value0) + { + } + public static FooECTIA NewB(double value0) + { + } + public byte[] SwiftData + { + get; set; + } + public nint ValueA + { + get { } + } + public double ValueB + { + get { } + } + public FooECTIACases Case + { + get { } } +} ``` ### Scaling and trimming From aafe0173df07af5b9605af9a1f725dcad2544501 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 15:10:58 +0100 Subject: [PATCH 05/11] Add Swift runtime library section --- docs/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 54515a06febc..00561bc595a9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,9 +31,33 @@ The table below lists the Swift types and their corresponding C# types. All C# types mentioned are blittable except for `bool`. To facilitate `P/Invoke`, a lightweight wrapper is required to convert `bool` to `byte`. Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mapping can fit within the underlying calling convention as these types are below the size limit for being passed by reference. +### Swift runtime library + +The tooling provides an implementation of runtime Swift types in C#. The code snippet below illustrates the general mechanism by which the types are projected. + +```csharp +namespace SwiftRuntimeLibrary { + public interface ISwiftValueType : IDisposable { + byte [] SwiftData { get; set; } + } + + public interface ISwiftObject : IDisposable { + IntPtr SwiftObject { get; } + } + + public interface ISwiftEnum : ISwiftValueType { + } + + public interface ISwiftStruct : ISwiftValueType { + } +} +``` + +This subsection will be expanded during the design review process. + ### Structs -Swift structs are projected into C# as `IDisposable` classes, implementing `ISwiftStruct`, which inherit from `ISwiftNominalType` and `ISwiftDisposable`. This approach is chosen due to the semantic differences in lifecycle management between Swift and C# types. Bindings are generated with `SwiftStructAttribute` that inherits `SwiftNominalTypeAttribute`. This attribute contains information about the Swift type. +Swift structs are projected into C# as `IDisposable` classes, implementing `ISwiftStruct`, which inherit from `ISwiftValueType` and `ISwiftDisposable`. This approach is chosen due to the semantic differences in lifecycle management between Swift and C# types. Bindings are generated with `SwiftStructAttribute` that inherits `SwiftValueTypeAttribute`. This attribute contains information about the Swift type. Given the following Swift struct declaration: ```swift From f4dca472c854946dcbea21f3eadd1090afaf2808 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 15:57:28 +0100 Subject: [PATCH 06/11] Update projected methods to follow naming convention --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 00561bc595a9..27224ea53965 100644 --- a/docs/README.md +++ b/docs/README.md @@ -142,10 +142,10 @@ public class FooECTIA : ISwiftEnum ~FooECTIA() { } - public static FooECTIA NewA(nint value0) + public static FooECTIA CreateA(nint value0) { } - public static FooECTIA NewB(double value0) + public static FooECTIA CreateB(double value0) { } public byte[] SwiftData From 1605d311bdbf6612eb276b14791b0237f961e5ab Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 16:27:57 +0100 Subject: [PATCH 07/11] Remove SwiftStruct and SwiftEnumType attributes --- docs/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index 27224ea53965..fa868e4e7e66 100644 --- a/docs/README.md +++ b/docs/README.md @@ -71,9 +71,6 @@ Given the following Swift struct declaration: The projection tooling will generate the following C#, with function bodies left empty for simplicity: ```csharp -[SwiftStruct("libNewClassCompilerTests.dylib", - "$s21NewClassCompilerTests6BarIntVMn", - "$s21NewClassCompilerTests6BarIntVN", "")] public class BarInt : ISwiftStruct { public BarInt(nint x) @@ -128,9 +125,7 @@ public enum FooECTIACases { A, B } -[SwiftEnumType("libNewClassCompilerTests.dylib", - "$s21NewClassCompilerTests8FooECTIAOMn", - "$s21NewClassCompilerTests8FooECTIAON", "")] + public class FooECTIA : ISwiftEnum { public void Dispose() From c8110eec1b49af07ad54874b69a793f2d6efa82d Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 16:34:47 +0100 Subject: [PATCH 08/11] Update docs/README.md Co-authored-by: Rolf Bjarne Kvinge --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index fa868e4e7e66..08305c83ec00 100644 --- a/docs/README.md +++ b/docs/README.md @@ -198,7 +198,7 @@ The general workflow for generating C# bindings from Swift code is as follows: This subsection will be expanded during the design review process. -### User expirience +### User experience The tooling perhaps doesn't need to surface all APIs, but only those that are used. Users should be able to indicate a subset of APIs that should be bound. From b8841fd68dfc04ab1fa19c78c5764e7d8908292c Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 9 Feb 2024 16:34:55 +0100 Subject: [PATCH 09/11] Update docs/README.md Co-authored-by: Rolf Bjarne Kvinge --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 08305c83ec00..8363ca53c35f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -41,7 +41,7 @@ namespace SwiftRuntimeLibrary { byte [] SwiftData { get; set; } } - public interface ISwiftObject : IDisposable { + public interface ISwiftObject : IDisposable { IntPtr SwiftObject { get; } } From 39eb1bd5e30d04c7bbd5c80278c6df97eb2f3ca6 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 22 Feb 2024 13:18:13 +0100 Subject: [PATCH 10/11] Update documentation to reflect changes in #2517 --- docs/README.md | 215 +++++++++++++++++-------------------------------- 1 file changed, 76 insertions(+), 139 deletions(-) diff --git a/docs/README.md b/docs/README.md index 8363ca53c35f..34db635c9429 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,10 +2,34 @@ This document provides a detailed overview of the .NET Swift interop tooling, focusing on the projections between Swift and .NET, and the functional design of the tooling. The projection tooling is intended for use with C# and any other .NET language is beyond its scope. +## Usage + +The tooling consumes Swift interface files to generate C# bindings. Currently, the tool processes a Swift ABI file, which is generated from a `.swiftinterface` file by the Swift compiler. This ABI file contains a json representation of the abstract syntax tree of the `.swiftinterface` file. The `.swiftinterface` and `.abi.json` files are generated by executing the `swiftc` command with the `-emit-module-interface` option. In upcoming improvements, the ABI aggregator will transition to directly parsing the `.swiftinterface` file and, eventually, a dynamic library to facilitate type-strong parsing. + +``` +Description: + Swift bindings generator. + +Usage: + SwiftBindings [options] + +Options: + -a, --swiftabi (REQUIRED) Path to the Swift ABI file. + -o, --output (REQUIRED) Output directory for generated bindings. + -v, --verbose Prints information about work in process. + -h, --help Display a help message. + --version Show version information + -?, -h, --help Show help and usage information +``` + +It is possible to specify multiple Swift ABI files for processing. If a unsupported type is encountered, the tooling will ignore it and generate C# source code for known syntax. + ## Projections This section outlines the mappings between Swift and .NET types, and describes how the tool generates C# bindings for a Swift library. For types with similar semantics across Swift and .NET, direct interop is possible and bindings are generated. For types without direct projection, additional Swift wrappers are required and it is the user's responsibility to generate these wrappers. At this stage, the tool is designed to avoid generating any Swift code if possible to avoid complexity of maintaining different Swift compiler and SDK versions. The tool should only generate bindings for Swift types that are part of the stable ABI and don't evolve. +This section will be expanded as more support is introduced. + ### Primitive types The table below lists the Swift types and their corresponding C# types. @@ -31,175 +55,88 @@ The table below lists the Swift types and their corresponding C# types. All C# types mentioned are blittable except for `bool`. To facilitate `P/Invoke`, a lightweight wrapper is required to convert `bool` to `byte`. Swift primitive types are implemented as frozen structs that conform to Swift-specific lowering processes handled by the runtime. However, such mapping can fit within the underlying calling convention as these types are below the size limit for being passed by reference. -### Swift runtime library - -The tooling provides an implementation of runtime Swift types in C#. The code snippet below illustrates the general mechanism by which the types are projected. - -```csharp -namespace SwiftRuntimeLibrary { - public interface ISwiftValueType : IDisposable { - byte [] SwiftData { get; set; } - } +### Static and P/Invoke functions - public interface ISwiftObject : IDisposable { - IntPtr SwiftObject { get; } - } +Each provided module is projected into a separate assembly. The assembly contains a namespace named "Bindings" and includes a class named "". For each public function, a `P/Invoke` signature and a corresponding method are generated. Below is an example to illustrate this process. - public interface ISwiftEnum : ISwiftValueType { - } - - public interface ISwiftStruct : ISwiftValueType { - } +Swift library example: +```swift +public func sayHello() { + print("Hello world") } ``` -This subsection will be expanded during the design review process. - -### Structs - -Swift structs are projected into C# as `IDisposable` classes, implementing `ISwiftStruct`, which inherit from `ISwiftValueType` and `ISwiftDisposable`. This approach is chosen due to the semantic differences in lifecycle management between Swift and C# types. Bindings are generated with `SwiftStructAttribute` that inherits `SwiftValueTypeAttribute`. This attribute contains information about the Swift type. +User's code in C#: +```csharp +using System; +using HelloLibraryBindings; -Given the following Swift struct declaration: -```swift - public struct BarInt { - public var X:Int; - public init(x:Int) { - X = x; +namespace HelloWorld +{ + public class Program + { + public static void Main(string[] args) + { + HelloLibrary.sayHello(); } } +} ``` -The projection tooling will generate the following C#, with function bodies left empty for simplicity: +Generated bindings in C#: ```csharp -public class BarInt : ISwiftStruct +using System; +using System.Runtime.InteropServices; +namespace HelloLibraryBindings { - public BarInt(nint x) + public class HelloLibrary { - } - internal BarInt(SwiftNominalCtorArgument unused) - { - } - public static SwiftMetatype GetSwiftMetatype() - { - } - public void Dispose() - { - } - void Dispose(bool disposing) - { - } - ~BarInt() - { - } - public byte[] SwiftData - { - get; set; - } - public nint X - { - get { } - set { } + [DllImport("libHelloLibrary.dylib", EntryPoint = "$s12HelloLibrary03sayA0yyF")] + internal static extern void PIfunc_sayHello(); + public static void sayHello() + { + PIfunc_sayHello(); + } } } ``` -There is a payload `SwiftData` along with two constructors. The first maps onto the `init` method inside the swift class. The second is an internal constructor that gets used to define an uninitialized type. This constructor gets used by the marshaler when a type needs to be allocated before it gets used, for example, as a return value because Swift semantics don’t allow to explicitly have variables in an uninitialized state. +In the example, the user's code references the `HelloLibraryBindings` namespace and invokes a static method that has the same name as the Swift function. When the Swift function returns a type, the C# wrapper method also returns type, with additional processing if required. + +## Functional outline -All properties bound in C# access the value through accessor functions. If a Swift property or a Swift method mutates the value, the contents of the C# `SwiftData` property will get changed as well. +The tooling consists of the following components: +- **SwiftBindings**: Command-line interface that orchestrates the tooling workflow. +- **SwiftReflector**: A library provides support for public ABI aggregation. It contains implementation of module declarations and the type system. +- **SwiftRuntimeLibrary**: Library that provides runtime marshaling support and projections of common Swift types. +- **SyntaxDynamo**: Library that provides an API for generating C# source code. -### Enums +The general workflow for generating C# bindings from Swift code is as follows: +1. Consume the Swift ABI file (`.abi.json`) and aggregate the public ABI using `ISwiftParser`, which generates a `ModuleDeclaration`. +2. Generate C# source code based on the `ModuleDeclaration`. -Swift enums are projected into C# as `IDisposable` classes, implementing `ISwiftEnum`, in similar manner as structs. +### Public ABI aggregation -Given this Swift enum: -```swift - public enum FooECTIA { - case a(Int) - case b(Double) - } -``` +The aggregation of the public ABI is managed through the `ISwiftParser` interface. This interface defines the layout for concrete implementations that are responsible for parsing and aggregating API information. There are two implementations: Swift ABI parser and Swift interface parser. The Swift ABI parser aggregates API information based on an ABI json file and doesn't contain metadata information from types. The Swift interface parser is designed to handle `.swiftinterface` file, which contains more information. The `.swiftinterface` file doesn't contain managled names and the parser consumes the dynamic library (`.dylib`) alongside to generate the `ModuleDeclaration`. -The projection tooling will generate the following C#, with function bodies left empty for simplicity: +`ISwiftParser` interface: ```csharp -public enum FooECTIACases -{ - A, B -} +using System; +using SwiftReflector.SwiftXmlReflection; -public class FooECTIA : ISwiftEnum +namespace SwiftReflector.Parser { - public void Dispose() - { - } - void Dispose(bool disposing) - { - } - ~FooECTIA() + public interface ISwiftParser { - } - public static FooECTIA CreateA(nint value0) - { - } - public static FooECTIA CreateB(double value0) - { - } - public byte[] SwiftData - { - get; set; - } - public nint ValueA - { - get { } - } - public double ValueB - { - get { } - } - public FooECTIACases Case - { - get { } + public ModuleDeclaration GetModuleDeclaration(string filePath, ErrorHandling errors); } } ``` -### Scaling and trimming - -The bindings should be organized into namespaces to mitigate scalability issues. Additionally, generated bindings should be trim-compatible. - -This subsection will be expanded during the design review process. - -### Unsupported types - -This subsection describes cases when direct interop is not possible and will be expanded during the design review process. This is a subset of features that require Swift wrappers provided by users: -- Protocols and classes with virtual methods -- Closures -- Passing a struct by value in more registers than P/Invoke will allow -- Exporting .NET into Swift - -## Functional outline - -The tooling consists of the following components: -- **CLIInterface**: A command-line interface that orchestrates the tooling workflow. -- **SwiftReflector**: A component that aggregates public API definitions from Swift modules. -- **Dynamo**: A component that provides API for C# code generation. -- **SwiftRuntimeLibrary**: A component that provides runtime marshaling support and common Swift type projections. - -The `SwiftRuntimeLibrary` This component is a library that provides basic support for Swift interop that the generated code builds on. It includes bindings for common built-in types like arrays and dictionaries. It provides a single source of truth for Swift types so that they can be exposed across an assembly boundary. - -### Workflow - -The general workflow for generating C# bindings from Swift code is as follows: - -1. Process the Swift library interface (`.swiftinterface`) with the SwiftReflector and generate `TLDefinition` with mappings between entry points and mangled names. -2. Aggregate a public API based on `.swiftinterface` with the SwiftReflector. -3. Generate source code for C# bindings with Dynamo and compile the code into a managed library. - -### Shipping - -This subsection will be expanded during the design review process. +### Module declaration -### User experience +The `ModuleDeclaration` serves as a integration point for public ABI aggregation and code generation phases. It contains ABI information for a module, including classes, structs, enums, protocols, functions, properties, extensions, and operators. -The tooling perhaps doesn't need to surface all APIs, but only those that are used. Users should be able to indicate a subset of APIs that should be bound. +### Code generation -This subsection will be expanded during the design review process. +Code generation involves a set compilers that convert `ModuleDeclaration` into C# source code. The `BindingsCompiler` and other specific compilers (i.e. `FunctionCompiler`) facilitating the generation of public-facing APIs using `SyntaxDynamo` library. From d81ec5ff2cf13cf768c5bb032c80a20731012346 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 22 Feb 2024 15:37:38 +0100 Subject: [PATCH 11/11] Fix formatting --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 34db635c9429..3ab8f57377c3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ All C# types mentioned are blittable except for `bool`. To facilitate `P/Invoke` ### Static and P/Invoke functions -Each provided module is projected into a separate assembly. The assembly contains a namespace named "Bindings" and includes a class named "". For each public function, a `P/Invoke` signature and a corresponding method are generated. Below is an example to illustrate this process. +Each provided module is projected into a separate assembly. The assembly contains a namespace named `Bindings` and includes a class named ``. For each public function, a `P/Invoke` signature and a corresponding method are generated. Below is an example to illustrate this process. Swift library example: ```swift