[swift-evolution] Pitch: Cross-module inlining and specialization
Xiaodi Wu
xiaodi.wu at gmail.com
Mon Oct 2 15:44:04 CDT 2017
Very much looking forward to this. Any possibility of rolling in some
version (ha) of @_versioned so that @inlinable functions can reference
internal declarations?
On Mon, Oct 2, 2017 at 3:31 PM, Slava Pestov via swift-evolution <
swift-evolution at 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
> @_inlineable
>
> Introduction
>
> 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.
> Motivation
>
> One 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 solution
>
> The @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 design
>
> The new @inlinable attribute can only be applied to the following kinds
> of declarations:
>
> - Functions and methods
> - Subscripts
> - Computed properties
> - Initializers
> - Deinitializers
>
> The 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 compatibility
>
> The introduction of the @inlinable attribute is an additive change to the
> language and has no impact on source compatibility.
> Effect on ABI stability
>
> The 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 resilience
>
> Because 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 languages
>
> The 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 considered
>
> One 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171002/7e256f4b/attachment.html>
More information about the swift-evolution
mailing list