Understanding @inlinable in Swift
By: Bruno Rocha
Tags: Mobile, Xcode, iOS, Swift, Programming

What Is the Purpose of @inlinable ?

One important aspect of most optimizations like inlining is that they are mostly done internally. While you can be confident that a module you’re developing will be properly optimized, things are a lot more complicated when we’re dealing with calls made from other modules.

Compiler optimizations happen because the compiler has the full picture of what’s being compiled, but when you’re building a framework, the compiler cannot possibly know how the importers are going to it. As a result, while the internal code of the framework will be optimized, the public interface will most likely end up intact.

A first thought may be “well, we could tell the compiler what sources composed that framework so that it could regain the full picture and apply more optimizations to the call sites.” But this gets complicated when you realize that the importer of the said framework is linking something that is already compiled. All this information on source files is gone, and you may not even have them in the first place if this is a third-party framework.

However, this is not an impossible problem. In fact, while compilers have different solutions for this problem, they mostly follow this same idea of supercharging a module’s public interface to contain additional information that the compiler can use at link time to further optimize the pieces of code where a framework is being used, which includes, but is not exclusive to, inlining.

In practice, though, you may notice that this could seriously get out of hand. If we start adding information from every single method to the public interface, not only will the framework massively jump in size but most of that would probably go to waste! As we don’t know how the framework is going to be used, doing so in a discriminatory manner would be a terrible mistake.

Instead of making you gamble, Swift instead lets you make this decision for yourself. Introduced in Swift 4.2, the @inlinable attribute allows you to enable cross-module inlining for a particular method. When this is done, the implementation of the method will be exposed as part of the module's public interface, allowing the compiler to further optimize calls made from different modules.

@inlinable func printPlusOne(_ num: Int) {

print("My number: \(num + 1)")

}

If the inlinable method happens to be calling other methods internally, the compiler will ask you to also expose these methods. This can be done either by also making them @inlinable or by marking them with @usableFromInline , which indicates that although the method is being used from an inlined method, it itself is not really supposed to be inlined.

The largest benefit of @inlinable lies in how much overhead certain methods might have. Even though the overhead of most methods can be negligible, some methods can be quite expensive, specifically if they contain generics and closures:

@inlinable public func allEqual<T>(_ seq: T) -> Bool where T : Sequence, T.Element : Equatable {

// ...

}

The compiler already applies multiple optimizations to prevent generics from being a problem in general, but as we mentioned, these don’t apply when the code is being called from a separate module. For cases like that, using @inlinable can give you interesting performance boosts at the cost of an increased framework binary size.