Skip to content

Commit

Permalink
[Swift language feature] Implement Swift.String wrapper in C# (#2983)
Browse files Browse the repository at this point in the history
* Implement Swift.String in C#

* Update src/Swift.Runtime/src/Swift/SwiftString.cs

Co-authored-by: Vitek Karas <[email protected]>

* Remove unnecessary GCHandle

* Add PInvoke prefix

* Add more tests

* Use function pointer for callback to marshall string

* Fix formatting

* Update src/Swift.Runtime/src/Swift/SwiftString.cs

Co-authored-by: Jan Kotas <[email protected]>

* Update src/Swift.Runtime/src/Swift/SwiftString.cs

Co-authored-by: Jan Kotas <[email protected]>

* Use ref type for the ToStringCallbackContext to take unmanaged pointer

* Fix formatting

---------

Co-authored-by: Vitek Karas <[email protected]>
Co-authored-by: Jan Kotas <[email protected]>
  • Loading branch information
3 people authored Feb 12, 2025
1 parent 0a268a0 commit 4364aa8
Show file tree
Hide file tree
Showing 12 changed files with 460 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Swift;
using BindingsGeneration.Tests;
using Swift;
using Swift.Runtime;
using Swift.Runtime.InteropServices;
using Xunit;

using Bindings = Swift.RuntimeTests;


namespace BindingsGeneration.FunctionalTests
{
public class RuntimeTests : IClassFixture<RuntimeTests.TestFixture>
{
private readonly TestFixture _fixture;

public RuntimeTests(TestFixture fixture)
{
_fixture = fixture;
}

public class TestFixture
{
static TestFixture()
{
InitializeResources();
}

private static void InitializeResources()
{
// Initialize
}
}

[Fact]
public void SmokeTestArray()
{
var array = GetArray(3);
Assert.Equal(3, array.Count);
Assert.Equal(0, array[0]);
Assert.Equal(1, array[1]);
Assert.Equal(2, array[2]);
Assert.Equal(3, SumArray(array));
}

[Fact]
public void TestEmptyArray()
{
var array = GetArray(0);
Assert.Equal(0, array.Count);
Assert.Equal(0, SumArray(array));
}

[Fact]
public void TestOneElementArray()
{
var array = GetArray(1);
Assert.Equal(1, array.Count);
Assert.Equal(0, array[0]);
Assert.Equal(0, SumArray(array));
}

[Fact]
public void TestBigArray()
{
var array = GetArray(10000);
Assert.Equal(10000, array.Count);
var sum = 0;
for (int i = 0; i < 10000; i++)
{
Assert.Equal(i, array[i]);
sum += i;
}
Assert.Equal(sum, SumArray(array));
}

// TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970
private static unsafe SwiftArray<int> GetArray(int count)
{
ArrayBuffer buffer = PInvoke_GetArray(count);
return SwiftMarshal.MarshalFromSwift<SwiftArray<int>>((SwiftHandle)new IntPtr(&buffer));
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8getArray5countSays5Int32VGAE_tF")]
private static extern ArrayBuffer PInvoke_GetArray(int count);

private static unsafe int SumArray(SwiftArray<int> array)
{
ArrayBuffer buffer = new ArrayBuffer();
SwiftMarshal.MarshalToSwift<SwiftArray<int>>(array, new IntPtr(&buffer));
return PInvoke_SumArray(buffer);
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8sumArray5arrays5Int32VSayAEG_tF")]
private static extern int PInvoke_SumArray(ArrayBuffer array);

[Fact]
public void SmokeTestSet()
{
var set = GetSet(3);
Assert.Equal(3, set.Count);
Assert.Equal(3, SumSet(set));
}

[Fact]
public void TestEmptySet()
{
var set = GetSet(0);
Assert.Equal(0, set.Count);
Assert.Equal(0, SumSet(set));
}

[Fact]
public void TestOneElementSet()
{
var set = GetSet(1);
Assert.Equal(1, set.Count);
Assert.Equal(0, SumSet(set));
}

[Fact]
public void TestBigSet()
{
var set = GetSet(10000);
Assert.Equal(10000, set.Count);
Assert.Equal(49995000, SumSet(set));

}

// TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970
private static unsafe SwiftSet<SwiftIntMock> GetSet(int count)
{
Variant variant = PInvoke_GetSet(count);
return SwiftMarshal.MarshalFromSwift<SwiftSet<SwiftIntMock>>((SwiftHandle)new IntPtr(&variant));
}

private static unsafe int SumSet(SwiftSet<SwiftIntMock> set)
{
Variant variant = new Variant();
SwiftMarshal.MarshalToSwift<SwiftSet<SwiftIntMock>>(set, new IntPtr(&variant));
return PInvoke_SumSet(variant);
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8getArray5countSays5Int32VGAE_tF")]
private static extern Variant PInvoke_GetSet(int count);

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8sumArray5arrays5Int32VSayAEG_tF")]
private static extern int PInvoke_SumSet(Variant set);

[Fact]
public void SmokeTestString()
{
var str = Bindings.RuntimeTests.getString(3);
Assert.Equal(3, str.Length);
Assert.Equal("aaa", str.ToString());
}

[Fact]
public void TestEmptyString()
{
var str = Bindings.RuntimeTests.getString(0);
Assert.Equal(0, str.Length);
Assert.Equal(string.Empty, str.ToString());
Assert.Equal(str.Length, Bindings.RuntimeTests.verifyString(str));
}

[Fact]
public void TestOneElementString()
{
var str = Bindings.RuntimeTests.getString(1);
Assert.Equal(1, str.Length);
Assert.Equal("a", str.ToString());
Assert.Equal(str.Length, Bindings.RuntimeTests.verifyString(str));
}

[Fact]
public void TestBigString()
{
var str = Bindings.RuntimeTests.getString(10000);
Assert.Equal(10000, str.Length);
Assert.Equal(new string('a', 10000), str.ToString());
Assert.Equal(str.Length, Bindings.RuntimeTests.verifyString(str));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import Foundation

public func getArray(count: Int32) -> Array<Int32> {
var array = Array<Int32>()
for i in 0..<count {
array.append(i)
}
return array
}

public func sumArray(array: Array<Int32>) -> Int32
{
return array.reduce(0, +)
}

public func getString(count: Int32) -> String {
return String(repeating: "a", count: Int(count))
}

public func verifyString(str: String) -> Int32 {
let count = str.count
return str.allSatisfy { $0 == "a" } ? Int32(count) : -1
}

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Swift;
using Swift.Runtime;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,75 +323,5 @@ public async Task TestAsyncStruct()
stopwatch.Stop();
Assert.True(Math.Abs(stopwatch.Elapsed.TotalSeconds - seconds) <= 1);
}

[Fact]
public void TestSwiftArray()
{
var array = GetArray(42, 17);
Assert.Equal(2, array.Count);
Assert.Equal(42, array[0]);
Assert.Equal(17, array[1]);
int sum = SumArray(array);
Assert.Equal(42 + 17, sum);
}

// TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970
private static unsafe SwiftArray<int> GetArray(int a, int b)
{
ArrayBuffer buffer = PInvoke_GetArray(a, b);
return SwiftMarshal.MarshalFromSwift<SwiftArray<int>>((SwiftHandle)new IntPtr(&buffer));
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8getArray1a1bSays5Int32VGAF_AFtF")]
private static extern ArrayBuffer PInvoke_GetArray(int a, int b);

private static unsafe int SumArray(SwiftArray<int> array)
{
ArrayBuffer buffer = new ArrayBuffer();
SwiftMarshal.MarshalToSwift<SwiftArray<int>>(array, new IntPtr(&buffer));
return PInvoke_SumArray(buffer);
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8sumArray5arrays5Int32VSayAEG_tF")]
private static extern int PInvoke_SumArray(ArrayBuffer array);

[Fact]
public void TestSwiftSet()
{
var set = GetSet(42, 17);
Assert.Equal(2, set.Count);
int sum = SumSet(set);
Assert.Equal(42 + 17, sum);

set = new SwiftSet<SwiftIntMock>();
sum = SumSet(set);
Assert.Equal(0, sum);
}

// TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970
private static unsafe SwiftSet<SwiftIntMock> GetSet(int a, int b)
{
Variant variant = PInvoke_GetSet(a, b);
return SwiftMarshal.MarshalFromSwift<SwiftSet<SwiftIntMock>>((SwiftHandle)new IntPtr(&variant));
}

private static unsafe int SumSet(SwiftSet<SwiftIntMock> set)
{
Variant variant = new Variant();
SwiftMarshal.MarshalToSwift<SwiftSet<SwiftIntMock>>(set, new IntPtr(&variant));
return PInvoke_SumSet(variant);
}

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8getArray1a1bSays5Int32VGAF_AFtF")]
private static extern Variant PInvoke_GetSet(int a, int b);

[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]
[DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8sumArray5arrays5Int32VSayAEG_tF")]
private static extern int PInvoke_SumSet(Variant array);


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,3 @@ public struct TimerStruct {
try? await Task.sleep(nanoseconds: 5_000_000_000)
}
}

public func getArray(a: Int32, b: Int32) -> Array<Int32>
{
return [a, b]
}

public func sumArray(array: Array<Int32>) -> Int32
{
return array.reduce(0, +)
}
10 changes: 10 additions & 0 deletions src/Swift.Runtime/src/Swift/Data.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <summary>
/// Represents Foundation.Data type.
/// </summary>

// TODO: https://github.com/dotnet/runtimelab/issues/2992
public struct Data
{
private long _flags;
private IntPtr _object;
}
3 changes: 3 additions & 0 deletions src/Swift.Runtime/src/Swift/FoundationDatabase.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,8 @@
<entity managedNameSpace="Swift" managedTypeName="UnsafeMutableBufferPointer&lt;System.Byte&gt;">
<typedeclaration kind="struct" name="UnsafeMutableBufferPointer&lt;Swift.UInt8&gt;" module="Swift" mangledName="sSr" frozen="true" blittable="true"/>
</entity>
<entity managedNameSpace="Swift" managedTypeName="SwiftString">
<typedeclaration kind="struct" name="String" module="Swift" mangledName="sSS" frozen="true" blittable="true"/>
</entity>
</entities>
</swifttypedatabase>
9 changes: 7 additions & 2 deletions src/Swift.Runtime/src/Swift/SwiftArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public unsafe void Dispose()
{
if (_buffer.storage != IntPtr.Zero)
{
Arc.Release(*(IntPtr*)_buffer.storage);
// TODO: https://github.com/dotnet/runtimelab/issues/2851
Arc.Release(_buffer.storage);
_buffer.storage = IntPtr.Zero;
GC.SuppressFinalize(this);
}
Expand All @@ -75,7 +76,8 @@ public unsafe void Dispose()
{
if (_buffer.storage != IntPtr.Zero)
{
Arc.Release(*(IntPtr*)_buffer.storage);
// TODO: https://github.com/dotnet/runtimelab/issues/2851
Arc.Release(_buffer.storage);
_buffer.storage = IntPtr.Zero;
}
}
Expand Down Expand Up @@ -272,9 +274,11 @@ public unsafe Element this[int index]

internal static class SwiftArrayPInvokes
{
[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSaMa")]
public static extern TypeMetadata PInvoke_getMetadata(TypeMetadataRequest request, TypeMetadata typeMetadata);

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sS2ayxGycfC")]
public static extern ArrayBuffer Init(TypeMetadata typeMetadata);

Expand All @@ -286,6 +290,7 @@ internal static class SwiftArrayPInvokes
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSayxSicis")]
public static unsafe extern void Set(SwiftHandle value, nint index, TypeMetadata elementMetadata, SwiftSelf self);

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSa5countSivg")]
public static extern nint Count(ArrayBuffer handle, TypeMetadata elementMetadata);

Expand Down
Loading

0 comments on commit 4364aa8

Please sign in to comment.