Skip to content

Commit df0349a

Browse files
authored
Merge pull request #2525 from dotnet/swift-bindings/runtime-library
Swift bindings generator with simple parser and emitter
2 parents 1da679f + 8480dbe commit df0349a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+7166
-96
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,15 @@ Session.vim
190190

191191
# VS debug support files
192192
launchSettings.json
193+
194+
# Swift output files
195+
*.swiftinterface*
196+
*.swiftdoc*
197+
*.swiftmodule*
198+
*.abi*
199+
*.swiftsourceinfo*
200+
*.dylib
201+
202+
# Testing artifacts
203+
testing/
204+
src/samples/**/*Bindings.cs

SwiftBindings.sln

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
44
VisualStudioVersion = 15.0.26124.0
55
MinimumVisualStudioVersion = 15.0.26124.0
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings", "src\SwiftBindings\src\SwiftBindings.csproj", "{B7977360-6671-4707-9A1C-1C29D5BE2674}"
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings", "src\Swift.Bindings\src\Swift.Bindings.csproj", "{B7977360-6671-4707-9A1C-1C29D5BE2674}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings.Tests", "src\SwiftBindings\tests\SwiftBindings.Tests.csproj", "{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}"
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings.Tests", "src\Swift.Bindings\tests\Swift.Bindings.Tests.csproj", "{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftRuntime", "src\Swift.Runtime\src\Swift.Runtime.csproj", "{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftRuntime.Tests", "src\Swift.Runtime\tests\Swift.Runtime.Tests.csproj", "{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}"
913
EndProject
1014
Global
1115
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -44,5 +48,29 @@ Global
4448
{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}.Release|x64.Build.0 = Release|Any CPU
4549
{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}.Release|x86.ActiveCfg = Release|Any CPU
4650
{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}.Release|x86.Build.0 = Release|Any CPU
51+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|Any CPU.Build.0 = Debug|Any CPU
53+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x64.ActiveCfg = Debug|Any CPU
54+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x64.Build.0 = Debug|Any CPU
55+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x86.ActiveCfg = Debug|Any CPU
56+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x86.Build.0 = Debug|Any CPU
57+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|Any CPU.ActiveCfg = Release|Any CPU
58+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|Any CPU.Build.0 = Release|Any CPU
59+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x64.ActiveCfg = Release|Any CPU
60+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x64.Build.0 = Release|Any CPU
61+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x86.ActiveCfg = Release|Any CPU
62+
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x86.Build.0 = Release|Any CPU
63+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
65+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x64.ActiveCfg = Debug|Any CPU
66+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x64.Build.0 = Debug|Any CPU
67+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x86.ActiveCfg = Debug|Any CPU
68+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x86.Build.0 = Debug|Any CPU
69+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
70+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|Any CPU.Build.0 = Release|Any CPU
71+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x64.ActiveCfg = Release|Any CPU
72+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x64.Build.0 = Release|Any CPU
73+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x86.ActiveCfg = Release|Any CPU
74+
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x86.Build.0 = Release|Any CPU
4775
EndGlobalSection
4876
EndGlobal

docs/README.md

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This document provides a detailed overview of the .NET Swift interop tooling, fo
44

55
## Usage
66

7-
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.
7+
The tooling consumes Swift ABI files, which are generated from `.swiftinterface` files 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.
88

99
```
1010
Description:
@@ -36,24 +36,57 @@ The table below lists the Swift types and their corresponding C# types.
3636

3737
| Swift Type | C# Type |
3838
| ------------------------------- | -------- |
39-
| `Swift.Int64` | `long` |
40-
| `Swift.UInt64` | `ulong` |
41-
| `Swift.Int32` | `int` |
42-
| `Swift.UInt32` | `uint` |
43-
| `Swift.Int16` | `short` |
44-
| `Swift.UInt16` | `ushort` |
45-
| `Swift.Int8` | `sbyte` |
46-
| `Swift.UInt8` | `byte` |
47-
| `Swift.UnsafeRawPointer` | `void*` |
48-
| `Swift.UnsafeMutableRawPointer` | `void*` |
49-
| `Int` | `nint` |
50-
| `UInt` | `nuint` |
39+
| `Swift.Int64` | `Int64` |
40+
| `Swift.UInt64` | `UInt64` |
41+
| `Swift.Int32` | `Int32` |
42+
| `Swift.UInt32` | `UInt32` |
43+
| `Swift.Int16` | `Int16` |
44+
| `Swift.UInt16` | `UInt16` |
45+
| `Swift.Int8` | `SByte` |
46+
| `Swift.UInt8` | `Byte` |
47+
| `Int` | `IntPtr` |
48+
| `UInt` | `UIntPtr`|
5149
| `Bool` | `bool` |
52-
| `Float` | `float` |
53-
| `Double` | `double` |
50+
| `Float` | `Single` |
51+
| `Double` | `Double` |
5452

53+
All C# types mentioned are blittable except for `bool`. To facilitate `P/Invoke`, a lightweight wrapper might be 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.
5554

56-
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.
55+
<details>
56+
The Swift type database is an XML-based file format used for describing primitive data types with the following structure:
57+
58+
```xml
59+
<?xml version="1.0" encoding="utf-8"?>
60+
<swifttypedatabase version="1.0">
61+
<entities>
62+
<!-- Individual entities describing Swift data types with C# projections -->
63+
</entities>
64+
</swifttypedatabase>
65+
```
66+
#### Elements
67+
68+
##### `entities`
69+
- **Description:** Container for individual data type entities.
70+
- **Child Elements:**
71+
- `entity`: Represents a specific data type in Swift.
72+
- **Attributes:**
73+
- `managedNameSpace`: Specifies the managed namespace of the data type.
74+
- `managedTypeName`: Specifies the managed type name of the data type.
75+
- **Child Elements:**
76+
- `typedeclaration`: Represents the declaration of the Swift type.
77+
- **Attributes:**
78+
- `kind`: Specifies the kind of type declaration.
79+
- `name`: Specifies the name of the Swift type.
80+
- `module`: Specifies the module of the Swift type.
81+
</details>
82+
83+
### Pointers
84+
85+
Swift provides unsafe pointer types as non-owning views into memory: `UnsafePointer`, `UnsafeMutablePointer`, `UnsafeRawPointer`, and `UnsafeMutableRawPointer`. They are implemented as frozen structs in Swift and are projected as structs into C#. The runtime implements Swift structure lowering algorithm, enabling these structs to be passed correctly. They are defined with `_rawValue` property with surfaced `Pointee` property. Mutable pointers are projected as generic types to address method overload issues.
86+
87+
### Buffer pointers
88+
89+
Swift provides buffer pointer types as non-owning views into memory: `UnsafeBufferPointer`, `UnsafeMutableBufferPointer`, `UnsafeRawBufferPointer`, and `UnsafeMutableRawBufferPointer`. They are implemented as frozen structs in Swift and are projected as structs into C#. The runtime implements Swift structure lowering algorithm, enabling these structs to be passed correctly. Typed buffer pointers are defined with `_position` and `_count` properties, while raw buffer pointers are defined with `_position` and `_end`. Surfaced properties on C# side are `BaseAddress` and `Count`. Mutable buffer pointers are projected as generic types to address method overload issues.. Since these buffer pointers do not allocate or own the memory they point to, memory management is not encapsulated within the structs.
5790

5891
### Static and P/Invoke functions
5992

@@ -101,42 +134,33 @@ namespace HelloLibraryBindings
101134
}
102135
```
103136

104-
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.
137+
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. The C# wrapper method is generated only when marshalling is required. If marshalling is not needed, only a P/Invoke declaration is generated.
105138

106139
## Functional outline
107140

108-
The tooling consists of the following components:
141+
The tooling comprises the following components:
109142
- **SwiftBindings**: Command-line interface that orchestrates the tooling workflow.
110-
- **SwiftReflector**: A library provides support for public ABI aggregation. It contains implementation of module declarations and the type system.
111-
- **SwiftRuntimeLibrary**: Library that provides runtime marshaling support and projections of common Swift types.
112-
- **SyntaxDynamo**: Library that provides an API for generating C# source code.
143+
- **Components:**
144+
- `parser`: Parses a Swift library using ABI or Swiftinterface parser.
145+
- `marshaller`: Marshals types between C# and Swift.
146+
- `emitter`: Emits a C# bindings library using string-based or object model-based emitter.
147+
- **SwiftRuntime**: Library providing projections of common Swift types. It contains a type database for common Swift types and implements Swift runtime constructs in C#.
113148

114149
The general workflow for generating C# bindings from Swift code is as follows:
115-
1. Consume the Swift ABI file (`.abi.json`) and aggregate the public ABI using `ISwiftParser`, which generates a `ModuleDeclaration`.
116-
2. Generate C# source code based on the `ModuleDeclaration`.
150+
1. Consume the Swift ABI file (`.abi.json`) and aggregate the public ABI using a parser that generates module declarations.
151+
2. If needed, generate marshalling information for collected ABI.
152+
3. Generate C# source code using an emitter and generated declarations.
117153

118-
### Public ABI aggregation
154+
![Functional outline](functional-outline.svg)
119155

120-
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`.
156+
### Parser
121157

122-
`ISwiftParser` interface:
123-
```csharp
124-
using System;
125-
using SwiftReflector.SwiftXmlReflection;
126-
127-
namespace SwiftReflector.Parser
128-
{
129-
public interface ISwiftParser
130-
{
131-
public ModuleDeclaration GetModuleDeclaration(string filePath, ErrorHandling errors);
132-
}
133-
}
134-
```
158+
The aggregation of the public ABI is done through the `ISwiftParser` interface. This interface defines the layout for concrete implementations responsible for parsing and collecting ABI information. Two implementations exist: Swift ABI parser and Swift interface parser. The Swift ABI parser aggregates ABI information based on an ABI json file. The Swift interface parser is designed to handle `.swiftinterface` files. The `.swiftinterface` file doesn't contain mangled names, and the parser should consume the dynamic library (`.dylib`) to generate declarations. Currently, the tooling only implements the Swift ABI parser.
135159

136-
### Module declaration
160+
### Marshaller
137161

138-
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.
162+
Ideally, marshalling logic should be done between parsing and emitting if possible. The `ModuleDecl`, `MethodDecl`, and `TypeDecl` represents model definition of collected and marshalled Swift ABI that should be projectable into C#.
139163

140-
### Code generation
164+
### Emitter
141165

142-
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.
166+
Two different strategies are available for emitting: using an object model or a string-based approach. The object model, like Roslyn API, represents a full set of C# language. Currently, the tooling only implements the string-based emitter.

docs/functional-outline.svg

Lines changed: 1 addition & 0 deletions
Loading

eng/Version.Details.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Dependencies>
33
<ToolsetDependencies>
4-
<ProductDependencies>
5-
<!-- This VS redist package is used as a way to get a non-shipping version number
6-
when using a custom runtime version. -->
7-
<Dependency Name="VS.Redist.Common.NetCore.SharedFramework.x64.6.0" Version="6.0.0-preview.5.21226.5">
8-
<Uri>https://github.com/dotnet/runtime</Uri>
9-
<Sha>ac82799250fe42c8ba2941aef487aca94ecf04cc</Sha>
10-
</Dependency>
11-
</ProductDependencies>
124
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="9.0.0-beta.24165.6">
135
<Uri>https://github.com/dotnet/arcade</Uri>
146
<Sha>ace00d8719b8d1fdfd0cc05f71bb9af216338d27</Sha>

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
<XUnitVersion>2.4.1</XUnitVersion>
1717
<XUnitRunnerVisualStudioVersion>2.4.3</XUnitRunnerVisualStudioVersion>
1818
<!-- Set the custom NETCoreApp version -->
19-
<MicrosoftNETCoreAppVersion>9.0.0-preview.2.24128.5</MicrosoftNETCoreAppVersion>
19+
<MicrosoftNETCoreAppVersion>9.0.0-preview.3.24129.2</MicrosoftNETCoreAppVersion>
2020
</PropertyGroup>
2121
</Project>

eng/pipelines/runtimelab.yml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,33 @@ stages:
4949
- stage: build
5050
displayName: Build
5151
jobs:
52-
- template: /eng/pipelines/templates/build-job.yml
53-
parameters:
54-
osGroup: OSX
55-
archType: x64
56-
${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
52+
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
53+
- template: /eng/pipelines/templates/build-job.yml
54+
parameters:
55+
osGroup: OSX
56+
archType: x64
5757
isOfficialBuild: true
5858
runTests: false
5959
pool:
6060
vmImage: 'macOS-latest'
6161

62+
- ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}:
63+
- template: /eng/pipelines/templates/build-job.yml
64+
parameters:
65+
osGroup: OSX
66+
archType: arm64
67+
runTests: true
68+
pool:
69+
vmImage: 'macOS-latest'
70+
71+
- template: /eng/pipelines/templates/build-job.yml
72+
parameters:
73+
osGroup: OSX
74+
archType: x64
75+
runTests: true
76+
pool:
77+
vmImage: 'macOS-latest'
78+
6279
# Publish and validation steps. Only run in official builds
6380
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
6481
- template: \eng\common\templates\post-build\post-build.yml

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"sdk": {
3-
"version": "9.0.100-preview.1.24101.2",
3+
"version": "9.0.100-preview.3.24153.2",
44
"allowPrerelease": true,
55
"rollForward": "major"
66
},
77
"tools": {
8-
"dotnet": "9.0.100-preview.1.24101.2",
8+
"dotnet": "9.0.100-preview.3.24153.2",
99
"runtimes": {
1010
"dotnet": [
1111
"$(MicrosoftNETCoreAppVersion)"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace BindingsGeneration
5+
{
6+
/// <summary>
7+
/// Represents an interface for emitting C# source code.
8+
/// </summary>
9+
public interface ICSharpEmitter
10+
{
11+
/// <summary>
12+
/// Emits a C# module based on the module declaration.
13+
/// </summary>
14+
/// <param name="decl">The module declaration.</param>
15+
public void EmitModule(ModuleDecl decl);
16+
}
17+
}

0 commit comments

Comments
 (0)