[Pitch] Explicit Specialization

At the SIL level. The LLVM optimizer and backend are free to do whatever they want to those, as well.

2 Likes

Thanks for the feedback so far. I've updated the proposal PR with clarifications from the thread, diff here.

4 Likes

How does this interact with some generic parameter types? I believe the current underscored syntax prohibits the use of some in parameter position so you are able to name all type parameters. Is there a way to extend the syntax to allow handling opaque generic parameter types (perhaps only in simple cases like where they are the only parameter)? For example: @specialize(where: T == String) and @specialize(String)

We could special case something like this, but it doesn't seem desirable. If you want to refer to a placeholder in multiple places (whether in the signature, or in an attribute as well), you should give it a name.

8 Likes

huge +1!

Can we manually specify a different implementation for the specialized type?
Something like this:

extension Array where Element: BinaryInteger {
  @specialize(where Element == Int)
  func sum() -> Double {
    reduce(0) { $0 + Double($1) }
  }
}

extension [Int] {
  func sum() -> Double { 0 }
}

// or, in the form of a customized signature
extension Array where Element: BinaryInteger {
  @specialize(where Element == Int, superFastImpl)
  func sum() -> Double {
    reduce(0) { $0 + Double($1) }
  }
}

extension [Int] {
  func superFastImpl() -> Double { 0 }
}

That would entail taking all the complexity of Swift overload resolution and building it into the runtime. Just do double dispatch with a new protocol instead. For example see this previous discussion: Protocol extension, generics and dynamic dispatch confusion - #2 by Slava_Pestov

3 Likes

Thanks for your reply.

I understand the overload mechanism in Swift is based on static information in the calling context. But I'm confused why this "internal specialization" feature is related to "overload resolution".

As Ben illustrated in the pitch, a function marked with @specialize is semantically equal to run-time condition branches, can't we just allow the developer to specify one of the branches?

This code is modified from the draft text:

extension Sequence where Element: BinaryInteger {
  @specialize(where Self == [Int])
  @specialize(where Self == [Int8])
  func sum() -> Double {
    if Self.self == [Int].self {
      <execute specialization for [Int].sum()>   // <- for example, this branch can be developer specified
    } else if Self.self == [Int8].self {
      <execute specialization for [Int8].sum()> // <- this branch remains to be provided by the compiler
    } else {
      reduce(0) { $0 + Double($1) }
    }
  }
}

The code you just wrote works today, without @specialize. The only convenience that's missing is that Self will not be [Int] within the <execute specialization for [Int].sum()> block, but that "only" requires some casting. That would be a nice convenience to have, but it's largely orthogonal to this proposal.

2 Likes

We actually have this as an underscored standard library function today as _specialize.

1 Like

What is proposed here is only an optimization guide and has no effect on the outward semantics of the function (aside from making it go faster for certain generic argument values). It might be good to make that clear in the proposal up front.

10 Likes

What I want right now and seems to be missing (if I've read correctly) is the ability to specialize a function someone else wrote. Every time my colleague converts an Int64 to an Int32 in his debug builds there is a lazy protocol witness lookup which causes a 300x slowdown in a microbenchmark compared to release builds, which makes Swift look bad(!) I'd like to be able to say look, just insert this specialization in your code and that will disappear. I assume(?) a specialization would do that.

4 Likes

Yes, you should be able to do this. This is what this part of Future DIrections, that I probably buried in the Tooling section, was getting at:

The current implementation requires the attribute to be attached directly to the function being specialized. Tooling to produce specializations would benefit from an additional syntax that could be added in a separate file, or even into a separately compiled binary.

It's definitely a future direction though because it requires a dynamic lookup table of specializations, rather than the current extremely simple static switch emitted into the start of the unspecialized function.

4 Likes