Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
23a0642
Add SwiftRuntimeLibrary project
kotlarmilos Mar 5, 2024
abab981
Add arm64 CI job
kotlarmilos Mar 5, 2024
bd9b708
Use BCL managed types and enable nullable reference types
kotlarmilos Mar 5, 2024
62bcca6
Add Swift type database structure to README.md and add ISwiftObject i…
kotlarmilos Mar 6, 2024
e32d8ee
Add public access modifier to SwiftObject
kotlarmilos Mar 6, 2024
d213412
Remove type-specific information from the type database
kotlarmilos Mar 6, 2024
98d5e2f
Add bindings generator with a simple parser and emitter
kotlarmilos Mar 7, 2024
aae465e
Update docs and add HelloWorld sample
kotlarmilos Mar 7, 2024
306f45f
Update docs/README.md
kotlarmilos Mar 7, 2024
a33ce4f
Update src/SwiftRuntimeLibrary/src/TypeDatabase.cs
kotlarmilos Mar 7, 2024
7458019
Update src/SwiftRuntimeLibrary/src/TypeDatabase.cs
kotlarmilos Mar 7, 2024
49fcaf4
Update src/SwiftRuntimeLibrary/src/TypeDatabase.cs
kotlarmilos Mar 7, 2024
14e561f
Update src/SwiftRuntimeLibrary/src/TypeDatabase.cs
kotlarmilos Mar 7, 2024
3ca1f93
Fix exception message formatting
kotlarmilos Mar 7, 2024
8b9d806
Refactor Swift.Runtime namespace
kotlarmilos Mar 7, 2024
5cf39db
Refactor project naming: Swift.Runtime and Swift.Bindings
kotlarmilos Mar 7, 2024
2002932
Use deinit calls for Swift projections in C# by default
kotlarmilos Mar 8, 2024
aa55c63
Add support for UnsafeRawPointer
kotlarmilos Mar 18, 2024
be8a996
Merge branch 'feature/swift-bindings' of github.com:dotnet/runtimelab…
kotlarmilos Mar 19, 2024
f01d13e
Update dotnet version
kotlarmilos Mar 19, 2024
9264f83
Add support for UnsafeBufferPointer types
kotlarmilos Mar 20, 2024
396d395
Update Swift ABI parser to capture dependencies and C# mapped types
kotlarmilos Mar 20, 2024
51ec116
Update docs with UnsafeBufferPointer section
kotlarmilos Mar 20, 2024
03a5b09
Implement mutable pointers as generics in C#
kotlarmilos Mar 21, 2024
ba27924
Add P/Invoke bool test case
kotlarmilos Mar 21, 2024
8480dbe
Update pointers with implicit operators
kotlarmilos Mar 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,15 @@ Session.vim

# VS debug support files
launchSettings.json

# Swift output files
*.swiftinterface*
*.swiftdoc*
*.swiftmodule*
*.abi*
*.swiftsourceinfo*
*.dylib

# Testing artifacts
testing/
src/samples/**/*Bindings.cs
Comment on lines +194 to +204
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.

32 changes: 30 additions & 2 deletions SwiftBindings.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings", "src\SwiftBindings\src\SwiftBindings.csproj", "{B7977360-6671-4707-9A1C-1C29D5BE2674}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings", "src\Swift.Bindings\src\Swift.Bindings.csproj", "{B7977360-6671-4707-9A1C-1C29D5BE2674}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings.Tests", "src\SwiftBindings\tests\SwiftBindings.Tests.csproj", "{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftBindings.Tests", "src\Swift.Bindings\tests\Swift.Bindings.Tests.csproj", "{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftRuntime", "src\Swift.Runtime\src\Swift.Runtime.csproj", "{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftRuntime.Tests", "src\Swift.Runtime\tests\Swift.Runtime.Tests.csproj", "{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -44,5 +48,29 @@ Global
{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}.Release|x64.Build.0 = Release|Any CPU
{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}.Release|x86.ActiveCfg = Release|Any CPU
{CE81B6BD-CCCC-4223-9069-B28435A4A5C1}.Release|x86.Build.0 = Release|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x64.ActiveCfg = Debug|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x64.Build.0 = Debug|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x86.ActiveCfg = Debug|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Debug|x86.Build.0 = Debug|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|Any CPU.Build.0 = Release|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x64.ActiveCfg = Release|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x64.Build.0 = Release|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x86.ActiveCfg = Release|Any CPU
{8E9013BE-01BD-4F4C-8BF7-E8C71FA6608E}.Release|x86.Build.0 = Release|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x64.ActiveCfg = Debug|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x64.Build.0 = Debug|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x86.ActiveCfg = Debug|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Debug|x86.Build.0 = Debug|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|Any CPU.Build.0 = Release|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x64.ActiveCfg = Release|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x64.Build.0 = Release|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x86.ActiveCfg = Release|Any CPU
{61F74BC6-1CCA-49FA-B5B8-6C9EABC1D0AB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
108 changes: 66 additions & 42 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document provides a detailed overview of the .NET Swift interop tooling, fo

## 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.
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.

```
Description:
Expand Down Expand Up @@ -36,24 +36,57 @@ 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` | `void*` |
| `Swift.UnsafeMutableRawPointer` | `void*` |
| `Int` | `nint` |
| `UInt` | `nuint` |
| `Swift.Int64` | `Int64` |
| `Swift.UInt64` | `UInt64` |
| `Swift.Int32` | `Int32` |
| `Swift.UInt32` | `UInt32` |
| `Swift.Int16` | `Int16` |
| `Swift.UInt16` | `UInt16` |
| `Swift.Int8` | `SByte` |
| `Swift.UInt8` | `Byte` |
| `Int` | `IntPtr` |
| `UInt` | `UIntPtr`|
| `Bool` | `bool` |
| `Float` | `float` |
| `Double` | `double` |
| `Float` | `Single` |
| `Double` | `Double` |

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.

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.
<details>
The Swift type database is an XML-based file format used for describing primitive data types with the following structure:

```xml
<?xml version="1.0" encoding="utf-8"?>
<swifttypedatabase version="1.0">
<entities>
<!-- Individual entities describing Swift data types with C# projections -->
</entities>
</swifttypedatabase>
```
#### Elements

##### `entities`
- **Description:** Container for individual data type entities.
- **Child Elements:**
- `entity`: Represents a specific data type in Swift.
- **Attributes:**
- `managedNameSpace`: Specifies the managed namespace of the data type.
- `managedTypeName`: Specifies the managed type name of the data type.
- **Child Elements:**
- `typedeclaration`: Represents the declaration of the Swift type.
- **Attributes:**
- `kind`: Specifies the kind of type declaration.
- `name`: Specifies the name of the Swift type.
- `module`: Specifies the module of the Swift type.
</details>

### Pointers

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.

### Buffer pointers

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.

### Static and P/Invoke functions

Expand Down Expand Up @@ -101,42 +134,33 @@ namespace HelloLibraryBindings
}
```

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.
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.

## Functional outline

The tooling consists of the following components:
The tooling comprises 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.
- **Components:**
- `parser`: Parses a Swift library using ABI or Swiftinterface parser.
- `marshaller`: Marshals types between C# and Swift.
- `emitter`: Emits a C# bindings library using string-based or object model-based emitter.
- **SwiftRuntime**: Library providing projections of common Swift types. It contains a type database for common Swift types and implements Swift runtime constructs in C#.

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`.
1. Consume the Swift ABI file (`.abi.json`) and aggregate the public ABI using a parser that generates module declarations.
2. If needed, generate marshalling information for collected ABI.
3. Generate C# source code using an emitter and generated declarations.

### Public ABI aggregation
![Functional outline](functional-outline.svg)

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`.
### Parser

`ISwiftParser` interface:
```csharp
using System;
using SwiftReflector.SwiftXmlReflection;

namespace SwiftReflector.Parser
{
public interface ISwiftParser
{
public ModuleDeclaration GetModuleDeclaration(string filePath, ErrorHandling errors);
}
}
```
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.

### Module declaration
### Marshaller

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.
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#.

### Code generation
### Emitter

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.
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.
1 change: 1 addition & 0 deletions docs/functional-outline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 0 additions & 8 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Dependencies>
<ToolsetDependencies>
<ProductDependencies>
<!-- This VS redist package is used as a way to get a non-shipping version number
when using a custom runtime version. -->
<Dependency Name="VS.Redist.Common.NetCore.SharedFramework.x64.6.0" Version="6.0.0-preview.5.21226.5">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>ac82799250fe42c8ba2941aef487aca94ecf04cc</Sha>
</Dependency>
</ProductDependencies>
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="9.0.0-beta.24165.6">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>ace00d8719b8d1fdfd0cc05f71bb9af216338d27</Sha>
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
<XUnitVersion>2.4.1</XUnitVersion>
<XUnitRunnerVisualStudioVersion>2.4.3</XUnitRunnerVisualStudioVersion>
<!-- Set the custom NETCoreApp version -->
<MicrosoftNETCoreAppVersion>9.0.0-preview.2.24128.5</MicrosoftNETCoreAppVersion>
<MicrosoftNETCoreAppVersion>9.0.0-preview.3.24129.2</MicrosoftNETCoreAppVersion>
</PropertyGroup>
</Project>
27 changes: 22 additions & 5 deletions eng/pipelines/runtimelab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,33 @@ stages:
- stage: build
displayName: Build
jobs:
- template: /eng/pipelines/templates/build-job.yml
parameters:
osGroup: OSX
archType: x64
${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- template: /eng/pipelines/templates/build-job.yml
parameters:
osGroup: OSX
archType: x64
isOfficialBuild: true
runTests: false
pool:
vmImage: 'macOS-latest'

- ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}:
- template: /eng/pipelines/templates/build-job.yml
parameters:
osGroup: OSX
archType: arm64
runTests: true
pool:
vmImage: 'macOS-latest'

- template: /eng/pipelines/templates/build-job.yml
parameters:
osGroup: OSX
archType: x64
runTests: true
pool:
vmImage: 'macOS-latest'

# Publish and validation steps. Only run in official builds
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- template: \eng\common\templates\post-build\post-build.yml
Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"sdk": {
"version": "9.0.100-preview.1.24101.2",
"version": "9.0.100-preview.3.24153.2",
"allowPrerelease": true,
"rollForward": "major"
},
"tools": {
"dotnet": "9.0.100-preview.1.24101.2",
"dotnet": "9.0.100-preview.3.24153.2",
"runtimes": {
"dotnet": [
"$(MicrosoftNETCoreAppVersion)"
Expand Down
17 changes: 17 additions & 0 deletions src/Swift.Bindings/src/Emitter/BindingsGenerator.ICSharpEmitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace BindingsGeneration
{
/// <summary>
/// Represents an interface for emitting C# source code.
/// </summary>
public interface ICSharpEmitter
{
/// <summary>
/// Emits a C# module based on the module declaration.
/// </summary>
/// <param name="decl">The module declaration.</param>
public void EmitModule(ModuleDecl decl);
}
}
Loading