Skip to content

[SwiftBindings] Add array support#2948

Closed
stephen-hawley wants to merge 1 commit into
feature/swift-bindingsfrom
swift-array
Closed

[SwiftBindings] Add array support#2948
stephen-hawley wants to merge 1 commit into
feature/swift-bindingsfrom
swift-array

Conversation

@stephen-hawley
Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown

@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
Copy Markdown

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

@kotlarmilos
Copy link
Copy Markdown
Member

Closing this PR in favour of #2964.

@kotlarmilos kotlarmilos deleted the swift-array branch January 29, 2025 09:18
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