Pitch: Cross-module inlining and specialization

I find the following points in the proposal interesting:

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

...

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.

If your company name is not Apple, looking at how your code base using Swift is typically going to be deployed - it’s very often going to end up in a iOS or macOS app bundle. Those bundles are deployed as one unit.
Now, in our code base (I suspect similar to many other users of swift), we have various frameworks that are not frameworks for the purpose of providing a stable ABI but rather:
* A reusable unit with a somewhat stable API, that can be shared between apps and with the wider open source community
* A mechanism for namespacing

Now I understand that this use-case is deferred for a later separate discussion, but my point here is that the name and the semantics of this attribute should be somewhat “forward-compatilble” with this use-case. “ inlinable” does not sound appropriate, because we don’t want to “inline” (in the C/C++ meaning) declarations into each usage site.
Instead we want to compile the annotated parts of -all linked modules- as one unit. Basically, for those parts, the module name would just function like a C++ namespace - an input to the symbol name mangling, and then the whole thing could be whole-module-optimized together.

This touches upon another comment someone made previously in this discussion - that access level and compiler visibility should be separate concepts. Because not just public methods, also private methods should be subject to this.

Jonas

Now I understand that this use-case is deferred for a later separate discussion, but my point here is that the name and the semantics of this attribute should be somewhat “forward-compatilble” with this use-case. “ inlinable” does not sound appropriate, because we don’t want to “inline” (in the C/C++ meaning) declarations into each usage site.
Instead we want to compile the annotated parts of -all linked modules- as one unit. Basically, for those parts, the module name would just function like a C++ namespace - an input to the symbol name mangling, and then the whole thing could be whole-module-optimized together.

Yeah, @inlinable does not actually force any kind of inlining to be performed — it declared that the SIL for the function body should be serialized as part of the module.

This touches upon another comment someone made previously in this discussion - that access level and compiler visibility should be separate concepts. Because not just public methods, also private methods should be subject to this.

The undocumented @_versioned attribute is currently used to make something visible to the compiler without making it visible in the language. It sounds like there’s some interest in documenting this attribute too — can someone suggest a better name than @_versioned? If we converge on a design here I can incorporate that into the proposal, relaxing the restriction that @inlinable functions can only reference other public functions.

Slava

···

On Oct 3, 2017, at 9:14 PM, Jonas B via swift-evolution <swift-evolution@swift.org> wrote:

It’s not totally clear to me what @_versioned is supposed to do. Well, it’s kind of clear that if something less-than-public in module A is declared @_versioned then it’s visible to the compiler when compiling module B (which imports module A). But does @_versioned imply @inlineable? If not, what’s the use case for declaring something @_versioned but not @inlineable? Giving some more information to the optimiser without introducing ABI fragility? Why not always do that then?

···

On 4 Oct 2017, at 13:36, Slava Pestov <spestov@apple.com> wrote:

On Oct 3, 2017, at 9:14 PM, Jonas B via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Now I understand that this use-case is deferred for a later separate discussion, but my point here is that the name and the semantics of this attribute should be somewhat “forward-compatilble” with this use-case. “ inlinable” does not sound appropriate, because we don’t want to “inline” (in the C/C++ meaning) declarations into each usage site.
Instead we want to compile the annotated parts of -all linked modules- as one unit. Basically, for those parts, the module name would just function like a C++ namespace - an input to the symbol name mangling, and then the whole thing could be whole-module-optimized together.

Yeah, @inlinable does not actually force any kind of inlining to be performed — it declared that the SIL for the function body should be serialized as part of the module.

This touches upon another comment someone made previously in this discussion - that access level and compiler visibility should be separate concepts. Because not just public methods, also private methods should be subject to this.

The undocumented @_versioned attribute is currently used to make something visible to the compiler without making it visible in the language. It sounds like there’s some interest in documenting this attribute too — can someone suggest a better name than @_versioned? If we converge on a design here I can incorporate that into the proposal, relaxing the restriction that @inlinable functions can only reference other public functions.

Slava

@_versioned makes a symbol visible externally without making it visible from the language. There is no requirement that a @_versioned thing is @inlinable. It is used when you want to reference an internal function from an inlinable function. Eg,

internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // error!

···

On Oct 3, 2017, at 10:32 PM, Jonas B <bobergj@gmail.com> wrote:

On 4 Oct 2017, at 13:36, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Oct 3, 2017, at 9:14 PM, Jonas B via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Now I understand that this use-case is deferred for a later separate discussion, but my point here is that the name and the semantics of this attribute should be somewhat “forward-compatilble” with this use-case. “ inlinable” does not sound appropriate, because we don’t want to “inline” (in the C/C++ meaning) declarations into each usage site.
Instead we want to compile the annotated parts of -all linked modules- as one unit. Basically, for those parts, the module name would just function like a C++ namespace - an input to the symbol name mangling, and then the whole thing could be whole-module-optimized together.

Yeah, @inlinable does not actually force any kind of inlining to be performed — it declared that the SIL for the function body should be serialized as part of the module.

This touches upon another comment someone made previously in this discussion - that access level and compiler visibility should be separate concepts. Because not just public methods, also private methods should be subject to this.

The undocumented @_versioned attribute is currently used to make something visible to the compiler without making it visible in the language. It sounds like there’s some interest in documenting this attribute too — can someone suggest a better name than @_versioned? If we converge on a design here I can incorporate that into the proposal, relaxing the restriction that @inlinable functions can only reference other public functions.

Slava

It’s not totally clear to me what @_versioned is supposed to do. Well, it’s kind of clear that if something less-than-public in module A is declared @_versioned then it’s visible to the compiler when compiling module B (which imports module A). But does @_versioned imply @inlineable? If not, what’s the use case for declaring something @_versioned but not @inlineable? Giving some more information to the optimiser without introducing ABI fragility? Why not always do that then?

@_versioned internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // OK

Slava

@nonABI internal func myImplDetail() { }
@nonABI public func myPublicFunction() { myImplDetail() } // OK

Anyway, for my use case mentioned earlier (shipping a release version of my app bundle), that doesn’t really matter. I’d just like a compiler switch that made the whole module not having an ABI, essentially making all all methods and types @inlinable and @_versioned, using the terminology in your example.

My other observation is that no matter how great the ergonomics, and no matter the naming of these attributes, very few people outside the compiler team is going to be able to successfully ship a versioned library without the “Checking Binary Compatibility” tool mentioned in swift/LibraryEvolution.rst at main · apple/swift · GitHub.

/Jonas

···

On 4 Oct 2017, at 14:33, Slava Pestov <spestov@apple.com> wrote:

@_versioned makes a symbol visible externally without making it visible from the language. There is no requirement that a @_versioned thing is @inlinable. It is used when you want to reference an internal function from an inlinable function. Eg,

internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // error!

@_versioned internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // OK

Slava

From my language user point of view it would be more understandable if that was written with a single keyword, eg:

@_versioned makes a symbol visible externally without making it visible from the language. There is no requirement that a @_versioned thing is @inlinable. It is used when you want to reference an internal function from an inlinable function. Eg,

internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // error!

@_versioned internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // OK

Slava

From my language user point of view it would be more understandable if that was written with a single keyword, eg:
@nonABI internal func myImplDetail() { }
@nonABI public func myPublicFunction() { myImplDetail() } // OK

The two attributes are different though. myImplDetail() is _not_ inlined into client code here, but myPublicFunction() is. So they need different attributes to express this intent.

Anyway, for my use case mentioned earlier (shipping a release version of my app bundle), that doesn’t really matter. I’d just like a compiler switch that made the whole module not having an ABI, essentially making all all methods and types @inlinable and @_versioned, using the terminology in your example.

Yes, that would be a nice feature to have but it is outside the scope of this proposal. First, we want to tackle shipping resilient modules — then we can discuss generalizing the notion of a “resilience domain” to encompass multiple modules. I realize the latter is more useful to third party developers though, and I apologize that in this particular case our priorities are at odds…

My other observation is that no matter how great the ergonomics, and no matter the naming of these attributes, very few people outside the compiler team is going to be able to successfully ship a versioned library without the “Checking Binary Compatibility” tool mentioned in swift/LibraryEvolution.rst at main · apple/swift · GitHub.

I agree — even for members of the compiler team this tool would be very useful, both as a way of encoding our assumptions for sanity-checking, and making sure we don’t make a silly mistake with a future update to the standard library.

Slava

···

On Oct 3, 2017, at 11:05 PM, Jonas B <bobergj@gmail.com> wrote:

On 4 Oct 2017, at 14:33, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

/Jonas

Anyway, for my use case mentioned earlier (shipping a release version of my app bundle), that doesn’t really matter. I’d just like a compiler switch that made the whole module not having an ABI, essentially making all all methods and types @inlinable and @_versioned, using the terminology in your example.

Yes, that would be a nice feature to have but it is outside the scope of this proposal. First, we want to tackle shipping resilient modules — then we can discuss generalizing the notion of a “resilience domain” to encompass multiple modules. I realize the latter is more useful to third party developers though, and I apologize that in this particular case our priorities are at odds…

If a framework is only ever used by a single client in the app bundle, as it is in the majority of cases, I think the objective should be to link it statically. The dynamic linking overhead is useless in this case. Would this allow the compiler to optimize across module boundaries? (Are the current capabilities of Xcode 9 in this regard described anywhere?)

Elia Cereda

···

Il giorno 04 ott 2017, alle ore 08:09, Slava Pestov via swift-evolution <swift-evolution@swift.org> ha scritto:

On Oct 3, 2017, at 11:05 PM, Jonas B <bobergj@gmail.com <mailto:bobergj@gmail.com>> wrote:

On 4 Oct 2017, at 14:33, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

@_versioned makes a symbol visible externally without making it visible from the language. There is no requirement that a @_versioned thing is @inlinable. It is used when you want to reference an internal function from an inlinable function. Eg,

internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // error!

@_versioned internal func myImplDetail() { … }

@inlinable public func myPublicFunction() { myImplDetail() } // OK

Slava

From my language user point of view it would be more understandable if that was written with a single keyword, eg:
@nonABI internal func myImplDetail() { }
@nonABI public func myPublicFunction() { myImplDetail() } // OK

The two attributes are different though. myImplDetail() is _not_ inlined into client code here, but myPublicFunction() is. So they need different attributes to express this intent.

Anyway, for my use case mentioned earlier (shipping a release version of my app bundle), that doesn’t really matter. I’d just like a compiler switch that made the whole module not having an ABI, essentially making all all methods and types @inlinable and @_versioned, using the terminology in your example.

Yes, that would be a nice feature to have but it is outside the scope of this proposal. First, we want to tackle shipping resilient modules — then we can discuss generalizing the notion of a “resilience domain” to encompass multiple modules. I realize the latter is more useful to third party developers though, and I apologize that in this particular case our priorities are at odds…

My other observation is that no matter how great the ergonomics, and no matter the naming of these attributes, very few people outside the compiler team is going to be able to successfully ship a versioned library without the “Checking Binary Compatibility” tool mentioned in swift/LibraryEvolution.rst at main · apple/swift · GitHub.

I agree — even for members of the compiler team this tool would be very useful, both as a way of encoding our assumptions for sanity-checking, and making sure we don’t make a silly mistake with a future update to the standard library.

Slava

/Jonas

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution