From 8149f46f4f56bcc9b8a9461b39c4d1be2fdd89b6 Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Tue, 2 Sep 2025 14:28:43 -0700 Subject: [PATCH 1/5] Inline always proposal --- proposals/NNNN-inline-always.md | 348 ++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 proposals/NNNN-inline-always.md diff --git a/proposals/NNNN-inline-always.md b/proposals/NNNN-inline-always.md new file mode 100644 index 0000000000..3b2cf8dd0f --- /dev/null +++ b/proposals/NNNN-inline-always.md @@ -0,0 +1,348 @@ +# `@inline(always)` attribute + +* Proposal: [SE-NNNN](NNNN-inline-always.md) +* Authors: [Arnold Schwaighofer](https://github.com/aschwaighofer) +* Implementation: [swiftlang/swift#84178](https://github.com/swiftlang/swift/pull/84178) + +## Introduction + +The Swift compiler performs an optimization that expands the body of a function +into the caller called inlining. Inlining exposes the code in the callee to the +code in the caller. After inlining, the Swift compiler has more context to +optimize the code across caller and callee leading to better optimization in +many cases. Inlining can increase code size. To avoid unnecessary code size +increases, the Swift compiler uses heuristics (properties of the code) to +determine whether to perform inlining. Sometimes these heuristics tell the +compiler not to inline a function even though it would be beneficial to do so. +The proposed attribute `@inline(always)` instructs the compiler to always inline +the annotated function into the caller giving the author explicit control over +the optimization. + +## Motivation + +Inlining a function referenced by a function call enables the optimizer to see +across function call boundaries. This can enable further optimization. The +decision whether to inline a function is driven by compiler heuristics that +depend on the shape of the code and can vary between compiler versions. + +In the following example the decision to inline might depend on the number of +instructions in `callee` and on detecting that the call to callee is frequently +executed because it is surrounded by a loop. Inlining this case would be +beneficial because the compiler is able to eliminate a store to a stack slot in +the `caller` after inlining the `callee` because the function's `inout` calling +convention ABI that requires an address no longer applies and further +optimizations are enabled by the caller's function's context. + +```swift +func callee(_ result: inout SomeValue, _ cond: Bool) { + result = SomeValue() + if cond { + // many lines of code ... + } +} + +func caller() { + var cond: Bool = false + var x : SomeValue = SomeValue() + for i in 0 ..< 1 { + callee(&x, cond) + } +} + +func callerAfterInlining(_ cond: Bool { + var x : SomeValue = SomeValue() + var cond: Bool = false + for i in 0 ..< 1 { + // Inlined `callee()`: + // Can keep `SomeValue()` in registers because no longer + // passed as an `inout` argument. + x = SomeValue() // Can hoist `x` out of the loop and perform constant + // propagation. + if cond { // Can remove the code under the conditional because it is + // known not to execute. + // many lines of code ... + } + } +} +``` + +The heuristic might fail to detect that code is frequently executed (surrounding +loop structures might be several calls up in the call chain) or the number of +instructions in the callee might be to large for the heuristic to decide that +inlining is beneficial. +Heuristics might change between compiler versions either directly or indirectly +because some properties of the internal representation of the optimized code +changes. +To give code authors reliable control over the inlining process we propose to +add an `@inline(always)` function attribute. + +This optimization control should instruct the compiler to inline the referenced +function or emit an error when it is not possible to do so. + +```swift +@inline(always) +func callee(_ result: inout SomeValue, _ cond: Bool) { + result = SomeValue() + if cond { + // many lines of code ... + } +} +``` + +## Proposed solution + +We desire for the attribute to function as an optimization control. That means +that the proposed `@inline(always)` attribute should emit an error if inlining +cannot be guaranteed in all optimization modes. The value of the function at a +call site can might determined dynamically at runtime. In such cases the +compiler cannot determine a call site which function is applied without doing +global analysis. In these cases we don't guarantee inlining even if the dynamic +value of the applied function was annotated with `@inline(always)`. +We only guarantee inlining if the annotated function is directly referenced and +not derived by some function value computation such as method lookup or function +value (closure) formation and diagnose errors if this guarantee cannot be +upheld. + +A sufficiently clever optimizer might be able to derive the dynamic value at the +call site, in such cases the optimizer shall respect the optimization control +and perform inlining. + +```swift +protocol SomeProtocol { + func mightBeOverriden() +} + +class C : SomeProtocol{ + @inline(always) + func mightBeOverriden() { + } +} + +@inline(always) +func callee() { +} + +func applyFunctionValues(_ funValue: () -> (), c: C, p: SomeProtocol) { + funValue() // function value, not guaranteed + c.mightBeOverriden() // dynamic method lookup, not guaranteed + p.mightBeOverriden() // dynamic method lookup, not guaranteed + callee() // directly referenced, guaranteed +} + +func caller() { + applyFunctionValue(callee, C()) +} + +caller() +``` + +Code authors shall be able to rely on that if a function is marked with +`@inline(always)` and directly referenced from any context (within or outside of +the defining module) that the function can be inlined or an error is emitted. + + +## Detailed design + +We want to diagnose an error if a directly referenced function is marked with +`@inline(always)` and cannot be inlined. What are the cases where this might not +be possible? + +### Interaction with `@inlinable` + +Function bodies of functions referenceable outside of the defining module are +only available to the outside module if the definition is marked `@inlinable`. + +Therefore, a function marked with `@inline(always)` must be marked `@inlinable` +if it has `open`, `public`, or `package` level access. + +```swift +@inline(always) // error: a public function marked @inline(always) must be marked @inlinable +public func callee() { +} +``` + +### Interaction with `@usableFromInline` + +A `public` `@inlinable` function can reference a function with `internal` access +if it is either `@inlinable` (see above) or `@usableFromInline`. `@usableFromInline` +ensures that there is a public entry point to the `internal` level function but +does not ensure that the body of the function is available to external +modules. Therefore, it is an error to combine `@inline(always)` with a +`@usableFromInline` function as we cannot guaranteed that the function can +always be inlined. + +```swift +@inline(always) // error: an internal function marked with `@inline(always)` and + `@usableFromInline` could be referenced from an + `@inlinable` function and must be marked inlinable +@usableFromInline +internal func callee() {} + +@inlinable +public func caller() { + callee() // could not inline callee into external module +} +``` + +### Module internal access levels + +It is okay to mark `internal`, `private` and `fileprivate` function declarations +with `@inline(always)` in cases other than the ones mention above without the +`@inlinable` attribute as they can only be referenced from within the module. + + +```swift +public func caller() { + callee() +} + +@inline(always) // okay because caller would force either `@inlinable` or + // `@usableFromInline` if it was marked @inlinable itself +internal func callee() { +} + + +@inline(always) // okay can only referenced from within the module +private func callee2() { +} +``` + +#### Infinite recursion during inlining + +We will diagnose if inlining cannot happen due to calls within a +[strongly connected component](https://en.wikipedia.org/wiki/Strongly_connected_component) +marked with `@inline(always)` as errors. + +```swift +@inline(always) +func callee() { + ... + if cond2 { + caller() + } +} + +@inline(always) +func caller() { + ... + if cond { + callee() + } +} +``` + +### Dynamic function values + +As outlined earlier the attribute does not guarantee inlining or diagnose the +failure to inline when the function value is dynamic at a call site: a function +value is applied, or the function value is obtained via class method lookup or +protocol lookup. + +```swift +@inline(always) +func callee() {} +func useFunctionValue() { + let f = callee + ... + f() // function value use, not guaranteed to be inlined +} + +class SomeClass : SomeProto{ + @inline(always) + func nonFinalMethod() {} + + @inline(always) + func method() {} +} + +protocol SomeProto { + func method() +} + + +func dynamicMethodLookup() { + let c = SomeClass() + ... + c.nonFinalMethod() // method lookup, not guaranteed to be inlined + + let p: SomeProto = SomeClass() + p.method() // method lookup, not guaranteed to be inlined +} + +class A { + func finalInSub() {} + final func finalMethod() {} +} +class B : A { + overrided final func finalInSub() {} +} + +func noMethodLookup() { + let a = A() + a.finalMethod() // no method lookup, guaranteed to be inlined + + let b = B() + b.finalInSubClass() // no method lookup, guaranteed to be inlined +} +``` + + +## Source compatibility + +This proposal is additive. Existing code has not used the attribute. It has no +impact on existing code. Existing references to functions in libraries that are +now marked with `@inline(always)` will continue to compile successfully with the +added effect that functions will get inlined (that could have happened with +changes to inlining heuristic). + +## ABI compatibility + +The addition of the attribute has no effect on ABI compatibility. + +## Implications on adoption + +This feature can be freely adopted and un-adopted in source +code with no deployment constraints and without affecting source or ABI +compatibility. + +## Future directions + +`@inline(always)` can be too restrictive in cases where inlining is only +required within a module. For such cases we can introduce an `@inline(module)` +attribute in the future. + + +```swift +@inlinable +public caller() { + if coldPath { + callee() + } +} + +public otherCaller() { + if hotPath { + callee() + } +} + +@inline(module) +@usableFromInline +internal func callee() { +} +``` + +## Alternatives considered + +We could treat `@inline(always)` as an optimization hint that does not need to +be enforced or applied at all optimization levels similar to how the existing +`@inline(__always)` attribute functions and not emit errors if it cannot be +guaranteed to be uphold when the function is directly referenced. +This would deliver less predictable optimization behavior in cases where authors +overlooked requirements for inlining to happen such as not marking a public +function as `@inlinable`. + + +## Acknowledgments + +TODO: .... From ca06ee3cf2805b6553cd16c9aefce6b3d4ffd619 Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Tue, 16 Sep 2025 12:40:10 -0700 Subject: [PATCH 2/5] Update proposal to state that @inline(always) should imply @inlinable on public'ish declarations --- proposals/NNNN-inline-always.md | 57 +++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/proposals/NNNN-inline-always.md b/proposals/NNNN-inline-always.md index 3b2cf8dd0f..80f5fe2bc1 100644 --- a/proposals/NNNN-inline-always.md +++ b/proposals/NNNN-inline-always.md @@ -3,6 +3,7 @@ * Proposal: [SE-NNNN](NNNN-inline-always.md) * Authors: [Arnold Schwaighofer](https://github.com/aschwaighofer) * Implementation: [swiftlang/swift#84178](https://github.com/swiftlang/swift/pull/84178) +* Pitch thread: https://forums.swift.org/t/pitch-inline-always-attribute/82040 ## Introduction @@ -149,18 +150,55 @@ be possible? ### Interaction with `@inlinable` -Function bodies of functions referenceable outside of the defining module are -only available to the outside module if the definition is marked `@inlinable`. - -Therefore, a function marked with `@inline(always)` must be marked `@inlinable` -if it has `open`, `public`, or `package` level access. +`@inlinable` and `@_alwaysEmitIntoClient` make the function body available to +clients (callers in other modules) in library evolution mode. `@inlinable` makes +the body of the function available to the client and causes an ABI entry point +in the vending module to vended. `@_alwaysEmitIntoClient` makes the body of the +function available for clients but does not cause emission of an ABI entry +point. Functions with `open`, `public`, or `package` level access cause emission +of an ABI entry point for clients to call but in the absence of aforementioned +attributes do not make the body available to the client. + +`@inline(always)` intention is to be able to guarantee that inlining will happen +for any caller inside or outside the defining module therefore it makes sense to +require the use some form of "inline-ability" attribute with them. This +attribute could be required to be explicitly stated. And for it to be an error +when the attribute is omitted. ```swift +@inline(always) +@inlinable // or @_alwaysEmitIntoClient +public func caller() { ... } + @inline(always) // error: a public function marked @inline(always) must be marked @inlinable public func callee() { } ``` +Alternatively, the attribute could be implicitly implied by the usage of +`@inline(always)`. In this proposal, we take the position that it should be +implied to avoid the redundancy of spelling this out. The intention of +`@inline(always)` is for it to inline in all contexts. Instead of an error in the +absence of the attribute we should imply "inline-ability". The question is what +should we default to? + +`@_alwaysEmitIntoClient`'s semantics seems preferable for new functions. We +intend for the function to be always inlined, why should there be an ABI entry +point? + +`@inlinable` semantics allows for annotating existing functions with +`@inline(always)` without breaking ABI compatibility. `@inlinable` keeps an +entry point in the vending module for older code that assumed the existence of +an entry point. + +This proposals takes the position to give `@inline(always)` the semantics of +`@inlineable` and provide an alternative spelling for the case when we desire +`@_alwaysEmitIntoClient` semantics: `@inline(only)`. + +For access levels equal and lower than `internal` `@inlinable` should not be +implied. + + ### Interaction with `@usableFromInline` A `public` `@inlinable` function can reference a function with `internal` access @@ -297,7 +335,9 @@ changes to inlining heuristic). ## ABI compatibility -The addition of the attribute has no effect on ABI compatibility. +The addition of the attribute has no effect on ABI compatibility. We chose to +imply `@inlinable` for `public` (et al.) declarations which will continue to +emit an entry point for existing binary clients. ## Implications on adoption @@ -342,6 +382,11 @@ This would deliver less predictable optimization behavior in cases where authors overlooked requirements for inlining to happen such as not marking a public function as `@inlinable`. +With respect to `@inlinable` an initial draft of the proposal suggested to +require spelling the `@inlinable` attribute on `public` declarations or an error +would be displayed. The argument was that this would ensure that authors would +be aware of the additional semantics implied by the attribute: the body is +exposed. ## Acknowledgments From b18ea77cc3feba5d8336d70606fdd31e9c4b12da Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Thu, 25 Sep 2025 10:51:05 -0700 Subject: [PATCH 3/5] Address feedback --- proposals/NNNN-inline-always.md | 414 ++++++++++++++++++++------------ 1 file changed, 263 insertions(+), 151 deletions(-) diff --git a/proposals/NNNN-inline-always.md b/proposals/NNNN-inline-always.md index 80f5fe2bc1..97a423ad65 100644 --- a/proposals/NNNN-inline-always.md +++ b/proposals/NNNN-inline-always.md @@ -93,77 +93,271 @@ func callee(_ result: inout SomeValue, _ cond: Bool) { ## Proposed solution We desire for the attribute to function as an optimization control. That means -that the proposed `@inline(always)` attribute should emit an error if inlining -cannot be guaranteed in all optimization modes. The value of the function at a -call site can might determined dynamically at runtime. In such cases the -compiler cannot determine a call site which function is applied without doing -global analysis. In these cases we don't guarantee inlining even if the dynamic -value of the applied function was annotated with `@inline(always)`. -We only guarantee inlining if the annotated function is directly referenced and -not derived by some function value computation such as method lookup or function -value (closure) formation and diagnose errors if this guarantee cannot be -upheld. - -A sufficiently clever optimizer might be able to derive the dynamic value at the -call site, in such cases the optimizer shall respect the optimization control -and perform inlining. +that the proposed `@inline(always)` attribute should emit an error diagnostic if +inlining is not possible in all optimization modes. However, this gets +complicated by the fact that the value of the function at a call site might be +determined dynamically at runtime: + +- Calls through first class function values + ```swift + @inline(always) f() {...} + + func a() { + let fv = f + fv() + } + ``` +- Calls through protocol values and protocol constraint generic types + ```swift + protocol P { + func method() + } + struct S : P { + @inline(always) + func method() {...} + } + func a(_ t: T) { + t.method() + let p : P = S() + p.method() + } + ``` +- Calls through class instance values and the method referenced is not `final` + ```swift + class C { + @inline(always) + func method() {...} + } + func a(c: C) { + c.method() + } + ``` + +In such cases, the compiler cannot determine at a call site which function is +applied without doing non-local analysis: either dataflow, or class hiarchy +analysis. +These cases are in contrast to when the called function can statically be +determined purely by looking at the call site, we refer to this set as direct +function references in the following: + +- Calls to free standing functions +- Calls to methods of `actor`, `struct`, `enum` type +- Calls to final methods of `class` type, and type (`static/class`) methods of + `class` type + +Therefore, in cases where the value of the function at a usage site is +dynamically derived we don't emit an error even if the dynamic value of the +applied function was annotated with `@inline(always)`. We only emit an error if +the annotated function is directly referenced and something would cause it to be +not inlined or if some property at the declaration site of the function would +make it not possible in the common case. + +Listing the different scenarios that can occur for a function marked with +`@inline(always)`: + +1. A function can definitely be inlined at the use site: direct function + references barring recursion cycles +2. A function can never be always inlined at a use site and we diagnose an + error: cycles in `@inline(always)` functions calling each other and all + references are direct. +3. A function can not be inlined reliably and we diagnose an error at the + declaration site: non-final method declaration +4. A function can not be inlined and we don't diagnose an error: calls through + first class function values, protocol values, and protocol constraint generic + types. + +### Direct function references + +Calls to freestanding functions, methods of `enum`, `struct`, `actor` types, +final methods of `class` types, and type methods of `class` types don't +dynamically dispatch to different implementations. Calls to such methods can +always be inlined barring the recursion limitation (see later). (case 1) ```swift -protocol SomeProtocol { - func mightBeOverriden() +struct S { + @inline(always) + final func method() {} } -class C : SomeProtocol{ - @inline(always) - func mightBeOverriden() { - } +func f() { + let s: S = ... + s.method() // can definitely be inlined +} + +class C { + @inline(always) + final func finalMethod() {} + + @inline(always) + class func method() {} +} + +class Sub : C {} + +func f2() { + let c: C = ... + c.finalMethod() // can definitely be inlined + let c2: Sub = .. + c2.finalMethod() // can definitely be inlined + C.method() // can definitely be inlined } @inline(always) -func callee() { +func freestanding() {} + +func f3() { + freestanding() // can definitely be inlined +} + +``` + +### Non final class methods + +Swift performs dynamic dispatch for non-final methods of classes based on the +dynamic receiver type of the class instance value at a use site. Inferring the +value of that dynamic computation at compile time is not possible in many cases +and the success of inlining cannot be ensured. We treat a non-final method +declaration with `@inline(always)` as an declaration site error because we +assume that the intention of the attribute is that the method will be inlined in +most cases and this cannot be guaranteed (case 3). + +```swift +class C { + @inline(always) // error: non-final method marked @inline(always) + func method() {} } -func applyFunctionValues(_ funValue: () -> (), c: C, p: SomeProtocol) { - funValue() // function value, not guaranteed - c.mightBeOverriden() // dynamic method lookup, not guaranteed - p.mightBeOverriden() // dynamic method lookup, not guaranteed - callee() // directly referenced, guaranteed +class C2 : C { + @inline(always) // error: non-final method marked @inline(always) + override func method() {} } +func f(c: C) { + c.method() // dynamic type of c might be C or C2, could not ensure success + // of inlining in general +} +``` + +### Recursion + +Repeatedly inlining `@inline(always)` functions calling each other would lead to +an infinite cycle of inlining. We can never follow the `@inline(always)` +semantics and diagnose an error (case 2). + +```swift +@inline(always) +func callee() { + ... + if cond2 { + caller() // error: caller is marked @inline(always) and would create an + // inlining cycle + } +} + +@inline(always) func caller() { - applyFunctionValue(callee, C()) + ... + if cond { + callee() + } } +``` -caller() +### First class function values + +Swift allows for functions as first class objects. They can be assigned to +variables and passed as arguments. The reference function of a function value +cannot be reliably be determined at the usage and is therefore not diagnosed as +an error (case 4). + +```swift +@inline(always) +func callee() {} + +func use(_ f: () -> ()) { + f() +} +func useFunctionValue() { + let f = callee + ... + f() // function value use, may be inlined but not diagnosed if not + use(callee) // function value use, may be inlined in `use()` but not diagnosed + // if not +} ``` -Code authors shall be able to rely on that if a function is marked with -`@inline(always)` and directly referenced from any context (within or outside of -the defining module) that the function can be inlined or an error is emitted. +### Protocol methods +Protocol constraint or protocol typed values require a dynamic computation to +determine the eventual method called. Inferring the value of the eventual method +called at compile time is not possible in general and the success of inlining +cannot be ensured. We don't diagnose a usage site error if the underlying method +is marked with `@inline(always)` (case 4) -## Detailed design +```swift +protocol P { + func method() +} +struct S : P { + @inline(always) + func method() {} +} +final class C : P { + @inline(always) + func method() {} +} + +@inline(always) +func generic (_ t: T) { + t.method() +} + +func f() { + let p: P = S() + p.method() // might not get inlined, not diagnosed + generic(S()) // might not get inlined, not diagnosed + let p2: P = C() + p2.method() // might not get inlined, not diagnosed + generic(C()) // might not get inlined, not diagnosed +} +``` + +### Optimization control as optimization hint + +A clever optimizer might be able to derive the dynamic value at the +call site, in such cases the optimizer shall respect the optimization control +and perform inlining. + +In the following example the functions will be inlined when build with higher +optimization levels than `-Onone`. + +```swift +@inline(always) +func binaryOp(_ left: T, _ right: T, _ op: (T, T) -> T) -> T { + op(left, right) +} + +@inline(always) +func add(_ left: Int, _ right: Int) -> Int { left + right } + +print(binaryOp(5, 10, add)) +print(binaryOp(5, 10) { add($0, $1) }) +``` -We want to diagnose an error if a directly referenced function is marked with -`@inline(always)` and cannot be inlined. What are the cases where this might not -be possible? ### Interaction with `@inlinable` -`@inlinable` and `@_alwaysEmitIntoClient` make the function body available to -clients (callers in other modules) in library evolution mode. `@inlinable` makes -the body of the function available to the client and causes an ABI entry point -in the vending module to vended. `@_alwaysEmitIntoClient` makes the body of the -function available for clients but does not cause emission of an ABI entry -point. Functions with `open`, `public`, or `package` level access cause emission -of an ABI entry point for clients to call but in the absence of aforementioned -attributes do not make the body available to the client. +`@inlinable` makes the function body available to clients (callers in other +modules) in library evolution mode. Functions with `open`, `public`, or +`package` level access cause emission of an ABI entry point for clients to call +but in the absence of aforementioned attributes do not make the body available +to the client. `@inline(always)` intention is to be able to guarantee that inlining will happen for any caller inside or outside the defining module therefore it makes sense to -require the use some form of "inline-ability" attribute with them. This -attribute could be required to be explicitly stated. And for it to be an error -when the attribute is omitted. +require the use of "@inlinable" attribute with them. This attribute could be +required to be explicitly stated. And for it to be an error when the attribute +is omitted. ```swift @inline(always) @@ -176,28 +370,23 @@ public func callee() { ``` Alternatively, the attribute could be implicitly implied by the usage of -`@inline(always)`. In this proposal, we take the position that it should be -implied to avoid the redundancy of spelling this out. The intention of -`@inline(always)` is for it to inline in all contexts. Instead of an error in the -absence of the attribute we should imply "inline-ability". The question is what -should we default to? - -`@_alwaysEmitIntoClient`'s semantics seems preferable for new functions. We -intend for the function to be always inlined, why should there be an ABI entry -point? +`@inline(always)`. We take the position that it should be implied to avoid the +redundancy of spelling it out. -`@inlinable` semantics allows for annotating existing functions with -`@inline(always)` without breaking ABI compatibility. `@inlinable` keeps an -entry point in the vending module for older code that assumed the existence of -an entry point. +For access levels equal and lower than `internal` `@inlinable` is not implied. -This proposals takes the position to give `@inline(always)` the semantics of -`@inlineable` and provide an alternative spelling for the case when we desire -`@_alwaysEmitIntoClient` semantics: `@inline(only)`. +As a consequence all the rules that apply to `@inlinable` also apply to +`public`/`open`/`package` declarations marked with `@inline(always). -For access levels equal and lower than `internal` `@inlinable` should not be -implied. +```swift +internal func g() { ... } +@inline(always) +public func inlinableImplied() { + g() // error: global function 'g()' is internal and cannot be referenced from an + '@inlinable' function +} +``` ### Interaction with `@usableFromInline` @@ -206,7 +395,7 @@ if it is either `@inlinable` (see above) or `@usableFromInline`. `@usableFromInl ensures that there is a public entry point to the `internal` level function but does not ensure that the body of the function is available to external modules. Therefore, it is an error to combine `@inline(always)` with a -`@usableFromInline` function as we cannot guaranteed that the function can +`@usableFromInline` function as we cannot guarantee that the function can always be inlined. ```swift @@ -224,9 +413,11 @@ public func caller() { ### Module internal access levels -It is okay to mark `internal`, `private` and `fileprivate` function declarations -with `@inline(always)` in cases other than the ones mention above without the -`@inlinable` attribute as they can only be referenced from within the module. +To mark `internal`, `private` and `fileprivate` function declarations +with `@inline(always)` does not imply the `@inlinable` attribute's semantics. +They can only be referenced from within the module. `internal` declarations can +be marked with `@inlinable` if this is required by the presence of other +`@inlinable` (or public `@inline(always)`) functions that reference them. ```swift @@ -245,86 +436,6 @@ private func callee2() { } ``` -#### Infinite recursion during inlining - -We will diagnose if inlining cannot happen due to calls within a -[strongly connected component](https://en.wikipedia.org/wiki/Strongly_connected_component) -marked with `@inline(always)` as errors. - -```swift -@inline(always) -func callee() { - ... - if cond2 { - caller() - } -} - -@inline(always) -func caller() { - ... - if cond { - callee() - } -} -``` - -### Dynamic function values - -As outlined earlier the attribute does not guarantee inlining or diagnose the -failure to inline when the function value is dynamic at a call site: a function -value is applied, or the function value is obtained via class method lookup or -protocol lookup. - -```swift -@inline(always) -func callee() {} -func useFunctionValue() { - let f = callee - ... - f() // function value use, not guaranteed to be inlined -} - -class SomeClass : SomeProto{ - @inline(always) - func nonFinalMethod() {} - - @inline(always) - func method() {} -} - -protocol SomeProto { - func method() -} - - -func dynamicMethodLookup() { - let c = SomeClass() - ... - c.nonFinalMethod() // method lookup, not guaranteed to be inlined - - let p: SomeProto = SomeClass() - p.method() // method lookup, not guaranteed to be inlined -} - -class A { - func finalInSub() {} - final func finalMethod() {} -} -class B : A { - overrided final func finalInSub() {} -} - -func noMethodLookup() { - let a = A() - a.finalMethod() // no method lookup, guaranteed to be inlined - - let b = B() - b.finalInSubClass() // no method lookup, guaranteed to be inlined -} -``` - - ## Source compatibility This proposal is additive. Existing code has not used the attribute. It has no @@ -384,9 +495,10 @@ function as `@inlinable`. With respect to `@inlinable` an initial draft of the proposal suggested to require spelling the `@inlinable` attribute on `public` declarations or an error -would be displayed. The argument was that this would ensure that authors would -be aware of the additional semantics implied by the attribute: the body is -exposed. +would be displayed. The argument was made that this would ensure that authors +would be aware of the additional semantics implied by the attribute: the body is +exposed. This was juxtaposed by the argument that spelling both `@inlinable` and +`@inline(always)` is redundant. ## Acknowledgments From 122a32e0983966b13c2eec7338d7e88af0d7b2ea Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Wed, 1 Oct 2025 08:24:21 -0700 Subject: [PATCH 4/5] Add Acknowledgments --- proposals/NNNN-inline-always.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proposals/NNNN-inline-always.md b/proposals/NNNN-inline-always.md index 97a423ad65..1c8fa26f58 100644 --- a/proposals/NNNN-inline-always.md +++ b/proposals/NNNN-inline-always.md @@ -502,4 +502,8 @@ exposed. This was juxtaposed by the argument that spelling both `@inlinable` and ## Acknowledgments -TODO: .... +Thanks to [Jordan Rose](https://forums.swift.org/t/optimization-controls-and-optimization-hints/81612/7) for pointing out that inlining can't be always guaranteed, specifically the case of closures. +Thanks to [Xiaodi Wu](https://forums.swift.org/t/pitch-inline-always-attribute/82040/7) for proposing inferring `@inlinable`. +Thanks to [Tony Allevato](https://github.com/swiftlang/swift-evolution/pull/2958#discussion_r2379238582) for suggesting to error on on non-final methods and +providing editing feedback. +Thanks to [Doug Gregor](https://github.com/DougGregor), [Joe Groff](https://github.com/jckarter), [Tim Kientzle](https://github.com/tbkka), and [Allan Shortlidge](https://github.com/tshortli) for discussions related to the feature. From b1ff577c3e36ac4629696b5b9eeabd6d35c37002 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 2 Oct 2025 09:22:39 -0400 Subject: [PATCH 5/5] Assign SE-0496. --- proposals/{NNNN-inline-always.md => 0496-inline-always.md} | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename proposals/{NNNN-inline-always.md => 0496-inline-always.md} (98%) diff --git a/proposals/NNNN-inline-always.md b/proposals/0496-inline-always.md similarity index 98% rename from proposals/NNNN-inline-always.md rename to proposals/0496-inline-always.md index 1c8fa26f58..94922c087f 100644 --- a/proposals/NNNN-inline-always.md +++ b/proposals/0496-inline-always.md @@ -1,9 +1,11 @@ # `@inline(always)` attribute -* Proposal: [SE-NNNN](NNNN-inline-always.md) +* Proposal: [SE-0496](0496-inline-always.md) * Authors: [Arnold Schwaighofer](https://github.com/aschwaighofer) +* Review Manager: [Tony Allevato](https://github.com/allevato) +* Status: **Active review (October 2–16, 2025)** * Implementation: [swiftlang/swift#84178](https://github.com/swiftlang/swift/pull/84178) -* Pitch thread: https://forums.swift.org/t/pitch-inline-always-attribute/82040 +* Review: ((pitch)[https://forums.swift.org/t/pitch-inline-always-attribute/82040]) ## Introduction