Skip to content

Commit

Permalink
Merge pull request #2964 from kotlarmilos/swift-bindings/swift-array
Browse files Browse the repository at this point in the history
[Swift language feature] Implement Swift.Array support
  • Loading branch information
kotlarmilos authored Feb 4, 2025
2 parents ce200f0 + 9469c1c commit 54d46f6
Show file tree
Hide file tree
Showing 5 changed files with 540 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
// Licensed under the MIT License.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Swift;
using Swift;
using Swift.Runtime;
using Swift.Runtime.InteropServices;
using Swift.StructsTests;
using Xunit;


namespace BindingsGeneration.FunctionalTests
{
public class StructTests : IClassFixture<StructTests.TestFixture>
Expand Down Expand Up @@ -316,5 +322,38 @@ 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public struct StructBuilder
}
}

@frozen
@frozen
public struct StructWithThrowingInit
{
public var x: Int
Expand Down Expand Up @@ -224,11 +224,11 @@ public func createNonFrozenStruct(a: Int, b: Int) -> NonFrozenStruct

public struct TimerStruct {
let returnValue: Int32

public init(returnValue: Int32) {
self.returnValue = returnValue
}

public func waitFor(seconds: UInt64) async -> Int32 {
do {
try await Task.sleep(nanoseconds: seconds * 1_000_000_000)
Expand All @@ -248,3 +248,12 @@ public struct TimerStruct {
}
}

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

public func sumArray(array: Array<Int32>) -> Int32
{
return array.reduce(0, +)
}
304 changes: 304 additions & 0 deletions src/Swift.Runtime/src/Swift/SwiftArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Swift;
using Swift.Runtime;
using Swift.Runtime.InteropServices;

namespace Swift;

/// <summary>
/// Represents a Swift array payload.
///
/// The following diagram illustrates the hierarchy of a Swift array type.
/// The actual implementation may differ:
///
/// struct Array
/// +-----------------------------------------------------------------------+
/// | struct ArrayBuffer |
/// | +--------------------------------------------------------------+ |
/// | | struct BridgeStorage | |
/// | | +------------------------------------------------------+ | |
/// | | | var rawValue: IntPtr | | |
/// | | +------------------------------------------------------+ | |
/// | +--------------------------------------------------------------+ |
/// +-----------------------------------------------------------------------+
/// </summary>
public struct ArrayBuffer
{
public IntPtr storage;
}

/// <summary>
/// Represents a Swift collection protocol.
/// </summary>
public interface ISwiftCollection { }

/// <summary>
/// Represents a Swift array.
/// </summary>
/// <typeparam name="Element">The element type contained in the array.</typeparam>
public class SwiftArray<Element> : IDisposable, ISwiftObject
{
static nuint _payloadSize = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata().Size;

static nuint _elementSize = ElementTypeMetadata.Size;

private ArrayBuffer _buffer;

private static Dictionary<Type, string> _protocolConformanceSymbols;

static SwiftArray()
{
_protocolConformanceSymbols = new Dictionary<Type, string>
{
{ typeof(ISwiftCollection), "$sSayxGSlsMc" }
};
}

public unsafe void Dispose()
{
if (_buffer.storage != IntPtr.Zero)
{
Arc.Release(*(IntPtr*)_buffer.storage);
_buffer.storage = IntPtr.Zero;
GC.SuppressFinalize(this);
}
}

unsafe ~SwiftArray()
{
Arc.Release(*(IntPtr*)_buffer.storage);
_buffer.storage = IntPtr.Zero;
}

public static nuint PayloadSize => _payloadSize;

public ArrayBuffer Payload => _buffer;

public static nuint ElementSize => _elementSize;

static TypeMetadata ISwiftObject.GetTypeMetadata()
{
return TypeMetadata.Cache.GetOrAdd(typeof(SwiftArray<Element>), _ => SwiftArrayPInvokes.PInvoke_getMetadata(TypeMetadataRequest.Complete, ElementTypeMetadata));
}

static TypeMetadata ElementTypeMetadata
{
get => TypeMetadata.GetTypeMetadataOrThrow<Element>();
}

static ISwiftObject ISwiftObject.NewFromPayload(SwiftHandle handle)
{
return new SwiftArray<Element>(handle);
}

IntPtr ISwiftObject.MarshalToSwift(IntPtr swiftDest)
{
var metadata = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata();
unsafe
{
fixed (void* _payloadPtr = &_buffer)
{
metadata.ValueWitnessTable->InitializeWithCopy((void*)swiftDest, (void*)_payloadPtr, metadata);
}
}
return swiftDest;
}

/// <summary>
/// Gets the protocol conformance descriptor for the given type.
/// </summary>
/// <typeparam name="TProtocol"></typeparam>
/// <returns></returns>
static ProtocolConformanceDescriptor ISwiftObject.GetProtocolConformanceDescriptor<TProtocol>()
where TProtocol : class
{
if (!_protocolConformanceSymbols.TryGetValue(typeof(TProtocol), out var symbolName))
{
throw new SwiftRuntimeException($"Attempted to retrieve protocol conformance descriptor for type SwiftArray and protocol {typeof(TProtocol).Name}, but no conformance was found.");
}
return ProtocolConformanceDescriptor.LoadFromSymbol("/usr/lib/swift/libswiftCore.dylib", symbolName);
}

/// <summary>
/// Constructs a new SwiftArray from the given handle.
/// </summary>
unsafe SwiftArray(SwiftHandle handle)
{
// copy memory
_buffer = *(ArrayBuffer*)handle;

}

/// <summary>
/// Constructs a new empty SwiftArray.
/// </summary>
public SwiftArray()
{
_buffer = SwiftArrayPInvokes.Init(ElementTypeMetadata);
}

/// <summary>
/// Gets the number of elements in the array.
/// </summary>
public unsafe int Count
{
get
{
return (int)SwiftArrayPInvokes.Count(_buffer, ElementTypeMetadata);
}
}

/// <summary>
/// Appends the given element to the array.
/// </summary>
public unsafe void Append(Element item)
{
IntPtr payload = IntPtr.Zero;
var metadata = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata();
try
{
payload = (IntPtr)NativeMemory.Alloc(ElementSize);
SwiftMarshal.MarshalToSwift(item, payload);

fixed (void* bufferPtr = &_buffer)
{
SwiftArrayPInvokes.Append(payload, metadata, new SwiftSelf(bufferPtr));
}
}
finally
{
NativeMemory.Free((void*)payload);
}
}

/// <summary>
/// Inserts the given element at the given index.
/// </summary>
public unsafe void Insert(int index, Element item)
{
IntPtr payload = IntPtr.Zero;
var metadata = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata();
try
{
payload = (IntPtr)NativeMemory.Alloc(ElementSize);
SwiftMarshal.MarshalToSwift(item, payload);
fixed (void* bufferPtr = &_buffer)
{
SwiftArrayPInvokes.Insert(new SwiftHandle(payload), (nint)index, metadata, new SwiftSelf(bufferPtr));
}
}
finally
{
NativeMemory.Free((void*)payload);
}
}

/// <summary>
/// Removes the element at the given index.
/// </summary>
public unsafe void Remove(int index)
{
IntPtr payload = IntPtr.Zero;
var metadata = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata();
try
{
payload = (IntPtr)NativeMemory.Alloc(ElementSize);
SwiftMarshal.MarshalToSwift(index, payload);

fixed (void* bufferPtr = &_buffer)
{
SwiftArrayPInvokes.Remove(new SwiftIndirectResult((void*)payload), (nint)index, metadata, new SwiftSelf(bufferPtr));
}
}
finally
{
NativeMemory.Free((void*)payload);
}
}

/// <summary>
/// Removes all elements from the array.
/// </summary>
public unsafe void RemoveAll()
{
var metadata = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata();

fixed (void* bufferPtr = &_buffer)
{
SwiftArrayPInvokes.RemoveAll(1, metadata, new SwiftSelf(bufferPtr));
}
}

/// <summary>
/// Gets or sets the element at the given index.
/// </summary>
public unsafe Element this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();

IntPtr payload = (IntPtr)NativeMemory.Alloc(ElementSize);
SwiftArrayPInvokes.Get(new SwiftIndirectResult((void*)payload), (nint)index, _buffer, ElementTypeMetadata);
return SwiftMarshal.MarshalFromSwift<Element>(new SwiftHandle(payload));
}
set
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();

var metadata = SwiftObjectHelper<SwiftArray<Element>>.GetTypeMetadata();
IntPtr payload = (IntPtr)NativeMemory.Alloc(ElementSize);
SwiftMarshal.MarshalToSwift(value, payload);

fixed (void* bufferPtr = &_buffer)
{
SwiftArrayPInvokes.Set(new SwiftHandle(payload), (nint)index, metadata, new SwiftSelf(bufferPtr));
}
}
}
}

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

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

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSayxSicig")]
public static unsafe extern void Get(SwiftIndirectResult result, nint index, ArrayBuffer handle, TypeMetadata elementMetadata);

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSayxSicis")]
public static unsafe extern void Set(SwiftHandle value, nint index, TypeMetadata elementMetadata, SwiftSelf self);

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

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSa6appendyyxnF")]
public static unsafe extern void Append(IntPtr value, TypeMetadata metadata, SwiftSelf self);

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSa9removeAll15keepingCapacityySb_tF")]
public static unsafe extern void RemoveAll(byte keepCapacity, TypeMetadata metadata, SwiftSelf self);

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSa6remove2atxSi_tF")]
public static unsafe extern void Remove(SwiftIndirectResult result, nint index, TypeMetadata metadata, SwiftSelf self);

[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])]
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSa6insert_2atyxn_SitF")]
public static unsafe extern void Insert(SwiftHandle value, nint index, TypeMetadata metadata, SwiftSelf self);
}
Loading

0 comments on commit 54d46f6

Please sign in to comment.