How can you see that a generic function was specialised, and what does it depend on?
Predict first
Is there a specialised copy of twice in the SIL of a Debug (-Onone) build?
@inline(never) func twice<T: Numeric>(_ x: T) -> T { x + x }
print(twice(2))
- Yes
- No
Predict first
And with -O?
@inline(never) func twice<T: Numeric>(_ x: T) -> T { x + x }
print(twice(2))
- Yes
- No
Mental model. A generic function is a recipe written for “any grain”. Specialising makes a laminated card that says “rice”, with all the grain-detection steps removed.
A generic function can be compiled once, working on any type through hidden metadata, or specialised: the optimiser copies the body for a known type (say Int), replacing every witness-table call with direct code, which can then be inlined and folded. Specialised functions are visible in SIL by their mangled names, which gain a Tg5 suffix (“generic specialisation”). In -Onone there are none: Debug builds run the generic code as written.
Specialisation needs two things: the optimiser must be on, and it must see the generic’s body at the call site that fixes the type. Within one WMO module that is automatic. Across a module boundary it is only possible if the function is @inlinable (or marked @_specialize(where T == Int) to emit a pre-specialised copy). A generic that is passed around as an existential, or whose concrete type is only known at runtime, stays unspecialised. That is why “generic code is as fast as hand-written code” is true in your module and often false across a binary-library boundary.
Specialisation is an optimiser pass, so an unoptimised build keeps the single generic version.
The optimiser sees the call twice(2), knows T == Int, and emits a specialised copy marked Tg5.
Common traps
- Benchmarking generics in a Debug build. Nothing is specialised, so the numbers say little about Release.
- Putting a hot generic in a separate library without
@inlinable. Clients get the slow, unspecialised version.
They might follow up with
- What does
Tg5mean? It is part of Swift’s symbol mangling for a generic specialisation. Demangle the full name to see the type. - Can the compiler specialise through a protocol existential? Sometimes: it can open the existential and specialise on the concrete type when it can prove it.
Related questions
- 5.3.2 How does an unspecialised generic function know the concrete type, and what does that cost?
- 5.2.3 How does the inliner decide, and what do @inline(never), @inline(__always) and @inlinable do?
- 1.4.7 What is generic specialization? When does it not happen?
How does an unspecialised generic function know the concrete type… →