SE-0229 — SIMD Vectors

allSatisfy isn't a brilliant name because it doesn't take a closure, but we don't have a Collection function named allEqual (it would probably be worth adding, though).

I don't mind if they are or not, as long as they are relatively well-contained. As you note, the standard library is deliberately small, and this proposal has a massive surface area as-is. I don't think that's good for progressive disclosure.

Also, I would just point out that I don't mean "esoteric" in any disparaging sense. I mean it in the sense of "intended for or likely to be understood by only a small number of people with a specialized knowledge or interest".

What do you mean by this? The vector comparison does produce an aggregate - a Mask, and the function is called with/on the mask to collapse it to scalar, which is the thing you actually test in an if/guard statement.

I would also note that Any is also the name of a rather important type in Swift. We also have the Sequence/sequence(...) functions, but those are actually related to one another, while Any and any(...) are not.

If this spelling as a free function is so incredibly important, I think that counts in favour of it being part of some separate module - again, in the interest of progressive disclosure.

What I’m saying is that the collection methods are used like this:

c.contains { $0 == 1 }

While the vector functions, if they were methods, would be used like this:

(v .== [1,1,1,1]).contains()

This makes contains and allSatisfy read unnaturally; taking a Boolean parameter sorta kinda helps with that, but we shouldn’t add a parameter and complicate a simple operation purely for aesthetics. It also creates awkward precedence conflicts which force most uses to parenthesize the test.

The mask is technically an aggregate, but mask-producing expressions don’t read like aggregates in code. We shouldn’t try to shoehorn them into that shape.

2 Likes

I might suggest…

extension Bool {
  public static func all<M: SIMDMask> (_ mask: M) -> Bool {
    return mask._all()
  }
}

…to allow the fluent spelling:

if .all(x < y) {
  print("In every lane, x is less than y")
}
5 Likes

How about just making them a computed var (or func) on the mask protocol?

(v .== [1,1,1,1]).all

Then they are nicely contained and it is only an extra character from

all(v .== [1,1,1,1])

If all() is enough of a term of art that it will make sense, then certainly people will equally be able to understand the meaning of the first line...

3 Likes

You'd have to do that anyway if passing the comparison as an argument to a function.

+1 on the proposal. I would prefer SIMD-based naming (e.g. Float.SIMDVector4) since that is the primary purpose of the types and I don't see it as the default vector type to use (compared to a future hypothetical Vector). I would not prefer using generic type syntax since to me these types are not for generic programming but fairly use-case and hardware specific.

One thing I would like to know more about is what kind of assembly we can expect to be generated using these types, including guidelines on how to use them most effectively.

1 Like

Also a way to load and store from unsafe pointers seems like it would be useful.

I don't know if anyone has mentioned this, but there's potential confusion between .< and ..< that I think could be significant.

What about going with a postfix dot, much as we use postfix & for modulus operators? That also emphasizes the primary part of the semantics, which is the comparison and not the pointwise-ness.

5 Likes

there is potential confusion between = and == too. plus, they don’t even return the same type.

Also i don’t think postfix . is allowed

2 Likes

The fact that there's a problem with = and == is not relevant. That's not changing; the question is whether to add a new problem.

I'm not sure whether this potential confusion matters, but I am pointing it out so the community can consider it. I don't yet have an opinion.

As for the postfix dot idea not working, you are correct.

Postfix . is a special case in the grammar for operators: Operators can include a dot, but only if they begin with a dot.

From TSPL > Operators:

You can also define custom operators that begin with a dot ( . ). These operators can contain additional dots. For example, .+. is treated as a single operator. If an operator doesn’t begin with a dot, it can’t contain a dot elsewhere. For example, +.+ is treated as the + operator followed by the .+ operator.

dot-operator-head → .

dot-operator-character → . | operator-character

dot-operator-characters → dot-operator-character opt

My notes say say we added compiler support for this special case in commit 0bfacde2420937bf.

I mentioned this in the discussion, but not the review thread. I believe it is possible with the right protocol structure to have the Vector4<T> syntax be extensible to non-SIMD types (without hardware acceleration for non-SIMD types, of course).

It basically requires having separate protocols for the storage part and @beccadax's "vectorizable" types.

A couple of people had mentioned being bothered by only being able to stick certain values in the generic slot, and this would remove that restriction (as long as the type can support the vector operations in code).

1 Like

I don’t think a generic parameter carries any implication that it can accept any arbitrary type (e.g. ‘Vector4< String>‘). It is quite common for generic type parameters to have constraints.

Wrappers like CGFloat are a little problematic and would probably require a level of indirection to access the underlying, vectorisable value. But I agree - with the right design, I think it’s possible.

It would be a bit easier if we had “sealed” protocols in the language. It’s badly needed anyway.

4 Likes

Review Update

The core team met and discussed the feedback received so far, and has made the following recommendations:

Intention to accept

The core team feels this is an important addition to the language that will open up SIMD programming to a wide audience in an approachable way.

The core team also made the following decisions for when the proposal is accepted:

  • There were many requests for additional math operations. These are certainly important, but can be left to later proposals once this foundational proposal has landed.
  • The initializers for VectorN from an Array should instead be generic from any Sequence.
  • The . prefix should be used on all mask-producing operators:
    • In case of .==, this helps disambiguate between the Bool and Mask-returning forms
    • In case of .& and .| this helps resolve the precedence problem of & and the inconsistency of a non-short-circuiting &&
    • While it doesn't have similar motivations, for consistency .< & co should also have a leading dot.
  • Pointwise arithmetic operations will not have a leading dot.
  • To avoid confusion with Collection semantics and future types like matrices, count should be renamed elementCount
  • The element properties x, y, z and w will be available on vectors of up to 4, along with the common named swizzles even, odd, high and low.
    • The general swizzle operation init(gathering:at:) feels unsatisfactory, and so should be deferred for a later proposal after more thought.
    • Brute-forcing all possible swizzles on the smaller vectors as properties was also ruled out.

Prototype of "generic"-style vectors

The majority of the feedback received during the thread was regarding the alternate "generic" spelling: Vector3<Int8> rather than Int8.Vector3.

It is still unclear which is the better form. However, in order to better make the decision, the core team has asked the proposal author to implement a prototype showing the alternate form. Reviewers will then be able to try out either form in order to help make the decision. (since @scanon is on vacation this week, @moiseev is kindly helping out with this prototype)

In addition, the proposal should be revised to spell out more explicitly some of the details, for example, of what masks are and the role they play.

We will hold the review open pending that prototype, and I'll post again when it's available.

21 Likes

Review Update 2

Hi – just to let everyone know: the updated proposal should be ready early next week.

In the mean-time the core team discussed some of the remaining questions. In order to land the ABI-relevant parts of the change soon, the team decided to defer the any and all parts of the proposal to a separate review, similar to the generalized swizzling options, as these do not affect the ABI so can be introduced additively later.

7 Likes

Review Update 3 – Resuming following changes

Hi everyone – thanks for your patience. The proposal and implementation have been updated, and the review will now resume through Friday, November 9th.

An updated copy of the proposal can now be found here, and diffs from previous are here.

To summarize how the proposal has changed:

  • The primary working types are now spelled like Vector3<T> instead of the earlier T.Vector3.
  • Initializers from any Sequence with the right element type are now provided.
  • All mask operations are .-prefixed
  • count has been renamed elementCount
  • The general swizzle / shuffle / permute operation init(gathering: at:) has been removed. We intend to restore it in a later proposal with a better name.
  • Users can make VectorN<T> available for arbitrary types T by conforming T to a new SIMDVectorizable protocol, which has very basic requirements.
  • The any and all and min, max, and clamp free functions have been removed. We intend to re-introduce this functionality (possibly with different bindings) in a follow-on proposal.
  • The IntegerVector and FloatingPointVector protocols have been removed and replaced with conditional conformances.

The text of the proposal now goes into detail about how the new spelling works and how users can add support for new types.

8 Likes

Small bikeshed: I wonder if we can rename SIMDVectorizable to simply Vectorizable. It might help with discovery and draws some parallels with the Vector name rather than SIMDVector.

This is looking really good, but I can’t see the definition of the SIMDMaskVector protocol, just its extension? Sorry if I’m being dumb!

This link should take you to the declaration: [DNM] SIMD, take 2 by stephentyrone · Pull Request #20344 · apple/swift · GitHub

(But it looks like most of the implementation is in extensions.)

Thanks @krilnon!