@inlinable is really about visibility--it makes the function body part of the the Swift interface for the module, rather than in internal detail. Quoting the introduction of SE-0193:
@inlinable attribute exports the body of a function as part of a module's interface, making it available to the optimizer when referenced from other modules.
@inline(__always) is a compiler directive that does not change function visibility at all; it simply tells the compiler to ignore inlining heuristics and always¹ inline the function. In particular, note that a function that is
@inline(__always), but not
@inlinable, will not be available for inlining outside of its module, because the function body that would be inlined is not available.
It's unfortunate that Swift inherited the C and C++ legacy of conflating these two orthogonal semantic notions (whether or not a function body is part of the API vs whether or not a function call should be generated) under the single term "inline", but, well, it did.
Note that adding or removing
@inlinable changes the API of a module. Adding or removing
@inline(__always) does not.
@inline(__always) can be beneficial for performance (especially in micro-benchmarks), but it can also have catastrophic downstream effects in macro performance due to code-size increase. We may need finer-grained semantics to be available (e.g. "always inline this when monomorphized, otherwise never") in order to reason about this, or we may simply need better heuristics for the compiler, or both, but @Michael_Ilseman has found some cases where aggressive use of
@inline(__always) was killing performance due to code size blowup, so we definitely don't want to simply stamp it on everything. @Andrew_Trick has sketched out proposals for finer-grained attributes in the past, and I hope that he'll get some time to work on it more.
¹ well, mostly. There are some weird corner-cases here, which Andy has also discussed fixing, but this is a good enough definition for the purposes of this comment.