Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SwiftBindings] Add array support #2948

Open
wants to merge 1 commit into
base: feature/swift-bindings
Choose a base branch
from

Conversation

stephen-hawley
Copy link

This pull request adds a binding for Swift.Array.

This binding demonstrates a fundamental difference between the design of core types in Swift vs. .NET.
Swift uses consistent "copy on write" semantics for pretty much everything. This means that if you write this in Swift:

var a1:[Int] = [1, 2, 3]
var a2:[Int] = a1
a2[0] = 17
print(a1[0])
print(a2[0])

you will see

1
17

This is clearly not what a C# developer would expect and as such this binding doesn't reflect that. Any operation that mutates an instance of the array will effectively throw away the old instance which isn't precisely ideal, but here we are.

This is another example of a generic binding but this is different from SwiftOptional in that there are two types of methods: non-mutating (ie, normal methods) and mutating methods. These have very different calling conventions.

For non-mutating methods, the instance gets passed in as the first argument (not using the self register) and the type metadata of the generic parameter gets passed in.
For mutating methods, a pointer to a copy of the instance gets passed in in the self register and the type metadata of the array gets passed in.

Like many Swift primitives, this type is...unusual. I think it's technically a value type (it sure is acting like one), but under the hood the actual implementation may be one of several types. As a default, it looks like it's an NSArray for most cases.

Question: should I include an IDisposable implementation here or do we want to put that into a common base class?

@stephen-hawley stephen-hawley added the area-SwiftBindings Swift bindings for .NET label Jan 21, 2025
Copy link
Member

@matouskozak matouskozak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on https://developer.apple.com/documentation/swift/array, array is a @frozen generic struct. Do we plan to remove this projection code (SwiftArray.cs) when the tooling is able to successfully project any @frozen generic struct or is the plan that selected Swift types have special projections like this?

I'm just curious about the long-term plan. I think it makes sense for these "often-used" types to have special projections if they need to be handled with caution to satisfy both Swift constraints and .NET developers.

/// Represents a Swift array
/// </summary>
/// <typeparam name="T">the element type</typeparam>
public sealed class SwiftArray<T> : ISwiftObject, IList<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How memory is handled? Who owns the memory?

@kotlarmilos
Copy link
Member

Question: should I include an IDisposable implementation here or do we want to put that into a common base class?

I wouldn't include it unless it's actually used. Let's review the UX at some point (added to the plan).

@kotlarmilos
Copy link
Member

kotlarmilos commented Jan 22, 2025

Could you add the manual bindings to the type database xml?

/// Represents a Swift array
/// </summary>
/// <typeparam name="T">the element type</typeparam>
public sealed class SwiftArray<T> : ISwiftObject, IList<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array conforms to a bunch of protocols in Swift. Assuming that we would like to have protocol constraints projected as generic constraints on c# side, how are we gonna make this work?

E.g. to call https://developer.apple.com/documentation/storekit/product/products(for:) with SwiftArray we will need to be able to somehow model Collection<T> conformance

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g. to call https://developer.apple.com/documentation/storekit/product/products(for:) with SwiftArray we will need to be able to somehow model Collection conformance

Do we need to model Collection<T> for this use-case? Is this requirement applied to the method where the conformance is needed? Is the conformance already available in the dynamic library?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it depends on how we want the projection to look like.

The pattern used by `products(for:) calls for something like this (I skip async)

public Product products<T> (T identifiers) where T : ICollection<Swift.String>

I guess we could not map ICollection into c#, and have

public Product products<T> (T identifiers)

but then we would have to hardcode some stuff:

  • assumption that anything which goes into the function is valid
  • we would still somehow need to obtain the protocol conformance descriptor and PWT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assumption that anything which goes into the function is valid

I would assume the input is valid until focus on protocols support.

we would still somehow need to obtain the protocol conformance descriptor and PWT

Since the protocol's mangled name and generic type are known at runtime, can we retrieve the conformance using Swift runtime API?

/// <summary>
/// Constructs a new SwiftArray from the given handle to a Swift array
/// </summary>
public SwiftArray(SwiftHandle handle)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be public?


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

@jkurdek jkurdek Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a bit less confusing order would be SwiftHandle handle, nint index, TypeMetadata elementMetadata, SwiftSelf self. Current version will compile anyway as self will go into register and be removed from parameter list before the call, but I think it would be nice to stay consistent with how this looks in llvm-ir where self is the last parameter. (we enforce that for SwiftSelf<T> as well) Also applies to others.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For non-mutating methods, the instance gets passed in as the first argument (not using the self register) and the type metadata of the generic parameter gets passed in.

The mutating functions look as expected, but the non-mutating are truly interesting. Her a pointer to the copy is technically the second argument. This is something that generated projections would get terribly wrong. Is there any common pattern to the functions which behave like this on other types? Or each case requires manual investigation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is "funcSelfKind": "NonMutating" property in the ABI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-SwiftBindings Swift bindings for .NET
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants