This is something I’ve been waiting for for a long time and I’m glad this
is finally becoming a reality. This is a big step forward for anyone
interested in seeing Swift get core “almost-stdlib” libraries.
···
On Mon, Oct 2, 2017 at 3:31 PM, Slava Pestov via swift-evolution < swift-evolution@swift.org> wrote:
Hi all,
Here is a draft proposal that makes public a feature we’ve had for a
while. Let me know what you think!Cross-module inlining and specialization ("@inlinable")
- Proposal: SE-NNNN
- Authors: Slava Pestov <https://github.com/slavapestov>, Jordan Rose
<https://github.com/jrose-apple>
- Review Manager: TBD
- Status: *Initial pitch*
- Implementation: Already implemented as an underscored attribute
@_inlineableIntroduction
We propose introducing an @inlinable attribute which exports the body of
a function as part of a module's interface, making it available to the
optimizer when referenced from other modules.
MotivationOne of the top priorities of the Swift 5 release is a design and
implementation of *the Swift ABI*. This effort consists of three major
tasks:-
Finalizing the low-level function calling convention, layout of data
types, and various runtime data structures. The goal here is to maintain
compatibility across compiler versions, ensuring that we can continue to
make improvements to the Swift compiler without breaking binaries built
with an older version of the compiler.
-Implementing support for *library evolution*, or the ability to make
certain source-compatible changes, without breaking binary compatibility.
Examples of source-compatible changes we are considering include adding new
stored properties to structs and classes, removing private stored
properties from structs and classes, adding new public methods to a class,
or adding new protocol requirements that have a default implementation. The
goal here is to maintain compatibility across framework versions, ensuring
that framework authors can evolve their API without breaking binaries built
against an older version of the framework. For more information about the
resilience model, see the library evolution document
<https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst> in
the Swift repository.
-Stabilizing the API of the standard library. The goal here is to
ensure that the standard library can be deployed separately from client
binaries and frameworks, without forcing recompilation of existing code.All existing language features of Swift were designed with these goals in
mind. In particular, the implementation of generic types and functions
relies on runtime reified types to allow separate compilation and type
checking of generic code.Within the scope of a single module, the Swift compiler performs very
aggressive optimization, including full and partial specialization of
generic functions, inlining, and various forms of interprocedural analysis.On the other hand, across module boundaries, runtime generics introduce
unavoidable overhead, as reified type metadata must be passed between
functions, and various indirect access patterns must be used to manipulate
values of generic type. We believe that for most applications, this
overhead is negligible compared to the actual work performed by the code
itself.However, for some advanced use cases, and in particular for the standard
library, the overhead of runtime generics can dominate any useful work
performed by the library. Examples include the various algorithms defined
in protocol extensions of Sequence and Collection, for instance the mapmethod
of the Sequence protocol. Here the algorithm is very simple and spends
most of its time manipulating generic values and calling to a user-supplied
closure; specialization and inlining can completely eliminate the algorithm
of the higher-order function call and generate equivalent code to a
hand-written loop manipulating concrete types.We would like to annotate such functions with the @inlinable attribute.
This will make their bodies available to the optimizer when building client
code; on the other hand, calling such a function will cause it to be
emitted into the client binary, meaning that if a library were to change
the definition of such a function, only binaries built against the newer
version of library will use the new definition.
Proposed solutionThe @inlinable attribute causes the body of a function to be emitted as
part of the module interface. For example, a framework can define a rather
impractical implementation of an algorithm which returns true if all
elements of a sequence are equal or if the sequence is empty, and false
otherwise:@inlinable public func allEqual<T>(_ seq: T) -> Bool
where T : Sequence, T.Element : Equatable {
var iter = seq.makeIterator()
guard let first = iter.next() else { return true }func rec(_ iter: inout T.Iterator) -> Bool {
guard let next = iter.next() else { return true }
return next == first && rec(&iter)
}return rec(&iter)}
A client binary built against this framework can call allEqual() and
enjoy a possible performance improvement when built with optimizations
enabled, due to the elimination of abstraction overhead.On the other hand, once the framework author comes to their senses and
implements an iterative solution to replace the recursive algorithm defined
above, the client binary cannot make use of the more efficient
implementation until recompiled.
Detailed designThe new @inlinable attribute can only be applied to the following kinds
of declarations:- Functions and methods
- Subscripts
- Computed properties
- Initializers
- DeinitializersThe attribute can only be applied to public declarations. This is because
the attribute only has an effect when the declaration is used from outside
of the module. Within a module, the optimizer can always rely on the
function body being available.For similar reasons, the attribute cannot be applied to local
declarations, that is, declarations nested inside functions or statements.
However, local functions and closure expressions defined inside public
@inlinable functions are always implicitly @inlinable.When applied to subscripts or computed properties, the attribute applies
to the getter, setter, didSetand willSet, if present.The compiler will enforce certain restrictions on bodies of inlinable
declarations:-
inlinable declarations cannot define local types. This is because all
types have a unique identity in the Swift runtime, visible to the language
in the form of the == operator on metatype values. It is not clear
what it would mean if two different libraries inline the same local type
from a third library, with all three libraries linked together into the
same binary. This becomes even worse if two *different* versions of
the same inlinable function appear inside the same binary.
-inlinable declarations can only reference other public declarations.
This is because they can be emitted into the client binary, and are
therefore limited to referencing symbols that the client binary can
reference.*Note:* The restrictions enforced on the bodies of @inlinable declarations
are exactly those that we have in place on default argument expressions of
public functions in Swift 4.
Source compatibilityThe introduction of the @inlinable attribute is an additive change to the
language and has no impact on source compatibility.
Effect on ABI stabilityThe introduction of the @inlinable attribute does not change the ABI of
existing declarations. However, adding @inlinable to an existing
declaration changes ABI, because the declaration will no longer have a
public entry point in the generated library. Removing @inlinable from an
existing declaration does not change ABI, because it merely introduces a
new public symbol in the generated library.We have discussed adding a "versioned @inlinable" variant that preserves
the public entry point for older clients, while making the declaration
inlinable for newer clients. This will likely be a separate proposal and
discussion.
Effect on API resilienceBecause a declaration marked @inlinable is not part of the library ABI,
removing such a declaration is a binary-compatible, but source-incompatible
change.Any changes to the body of a declaration marked @inlinable should be
considered very carefully. As a general guideline, we feel that @inlinable makes
the most sense with "obviously correct" algorithms which manipulate other
data types abstractly through protocols, so that any future changes to an
@inlinable declaration are optimizations that do not change observed
behavior.
Comparison with other languagesThe closest language feature to the @inlinable attribute is found in C
and C++. In C and C++, the concept of a header file is similar to Swift's
binary swiftmodule files, except they are written by hand and not
generated by the compiler. Swift's public declarations are roughly
analogous to declarations whose prototypes appear in a header file.Header files mostly contain declarations without bodies, but can also
declare static inlinefunctions with bodies. Such functions are not part
of the binary interface of the library, and are instead emitted into client
code when referenced. As with @inlinable declarations, static inlinefunctions
can only reference other "public" declarations, that is, those that are
defined in other header files.
Alternatives consideredOne possible alterative would be to add a new compiler mode where *all* declarations
become implicitly @inlinable.However, such a compilation mode would not solve the problem of delivering
a stable ABI and standard library which can be deployed separately from
user code. We *don't want* all declaration bodies in the standard library
to be available to the optimizer when building user code.While such a feature might be useful for users who build private
frameworks that are always shipped together their application without
resilience concerns, we do not feel it aligns with our goals for ABI
stability, and at best it should be a separate discussion.For similar reasons, we do not feel that an "opt-out" attribute that can
be applied to declarations to mark them non-inlinable makes sense.We have also considered generalizing @inlinable to allow it to be applied
to entire blocks of declarations, for example at the level of an extension.
As we gain more experience with using this attribute in the standard
library we might decide this would be a useful addition, but we feel that
for now, it makes sense to focus on the case of a single inlinable
declaration instead. Any future generalizations can be introduced as
additive language features.We originally used the spelling @inlineable for the attribute. However,
we settled on @inlinable for consistency with the Decodable and Encodable protocols,
which are named as they are and not Decodeable and Encodeable.Finally, we have considered some alternate spellings for this attribute.
The name @inlinable is somewhat of a misnomer, because nothing about it
actually forces the compiler to inline the declaration; it might simply
generate a concrete specialization of it, or look at the body as part of an
interprocedural analysis, or completely ignore the body. We have considered
@alwaysEmitIntoClient as a more accurate, but awkward, spelling of the
attribute's behavior._______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution