Any efficient erasure of a generic type?

I can find no way to capture the specialized implementation of a function or method on a generic type. For example, given

protocol Summable {
  func sum() -> Int 
}
extension Int: Summable {
  func sum() -> Int { self } 
}
let x: Summable = 3

Invoking x.sum() in a -O build leaves me in optimal code. If I add

extension Summable {
  static func sum1(_ me: UnsafeRawPointer) -> Int {
    me.assumingMemoryBound(to: Self.self).pointee.sum()
  }
}
let f = Int.sum1

I can invoke f on a pointer-to- Int, and it’s the same optimal code. If I really care about performance I can

let s: Summable.Type = Int.self

and invoke s.sum1 on a pointer-to-Int again ending up in optimal code. I could use any of these techniques to type-erase the Int without incurring an unreasonable performance hit. However, if we add:

extension Optional: Summable where Wrapped: BinaryInteger {
  func sum() -> Int { return Int(self ?? 0) }
}
let xPrime: Summable = Optional(3)
let fPrime = Int?.sum1
let sPrime: Summable.Type = Int?.self

I see no way to do the same thing for Int?. The code I end up in is full of swift_getAssociatedConformanceWitness, swift_allocObject and other performance barriers. Is there a way out?

Note: using @_specialize on a function declaration doesn't count! If I could use it in an expression context, (like where any of the Prime variables above was initialized) that would be awesome.

5 Likes

What is the exact code you have here? It sounds like it might just be a bug or missing case in the specializer, if you want to file a bug for it.

Thanks @Joe_Groff. I made a gist with a complete survey of the type erasure techniques I know about and filed SR-13221.

2 Likes

So you're going to run into at least a couple of known problems. One is that we currently never generate specialized witness tables for generic types, so if you type-erase Optional<X> via the protocol abstraction (using existentials, unspecialized generic functions, and so on), you're going to immediately fall back to the unspecialized implementation of Optional's implementations of the protocol. Second, without opaque value SIL, unspecialized code is going to suffer worse ARC optimization than specialized code, which is probably part of why you're seeing more ARC traffic.

However, I would expect that, if you were doing something like your example in your OP:

extension Summable {
  static func sum1(_ me: UnsafeRawPointer) -> Int {
    me.assumingMemoryBound(to: Self.self).pointee.sum()
  }
}
let f = Int.sum1

where, instead of capturing the protocol witness table, you're capturing individual methods on a specific concrete type, that in that case the complete concrete type we're invoking sum1 on ought to be known at compile time, we should still specialize sum1 in that case. I'm not sure why that wouldn't happen, as long as there isn't an inadvertent module boundary between the implementation of Optional: Summable and the code referencing the method.

I knew about that one; it's the upshot for general type erasure as a technique that's the problem.

Sorry, it does happen; I was mistaken previously and that was noted in the bug and the analysis I posted; I should have come back here and corrected the record. However, I also note that it's not really a suitable vehicle for generalized type erasure when there are multiple operations involved.

Terms of Service

Privacy Policy

Cookie Policy