Skip to content

Commit 028dbf9

Browse files

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Formally defining `consuming` and `nonconsuming` argument type modifiers
2+
3+
* Proposal: [SE-NNNN](NNNN-consuming-nonconsuming.md)
4+
* Authors: [Michael Gottesman](https://github.com/gottesmm), [Andrew Trick](https://github.com/atrick)
5+
* Review Manager: TBD
6+
* Status: **Awaiting implementation**
7+
* Pitch v1: [https://github.com/gottesmm/swift-evolution/blob/consuming-nonconsuming-pitch-v1/proposals/000b-consuming-nonconsuming.md](https://github.com/gottesmm/swift-evolution/blob/consuming-nonconsuming-pitch-v1/proposals/000b-consuming-nonconsuming.md)
8+
9+
<!--
10+
*During the review process, add the following fields as needed:*
11+
12+
* Implementation: [apple/swift#NNNNN](https://github.com/apple/swift/pull/NNNNN) or [apple/swift-evolution-staging#NNNNN](https://github.com/apple/swift-evolution-staging/pull/NNNNN)
13+
* Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/)
14+
* Bugs: [SR-NNNN](https://bugs.swift.org/browse/SR-NNNN), [SR-MMMM](https://bugs.swift.org/browse/SR-MMMM)
15+
* Previous Revision: [1](https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md)
16+
* Previous Proposal: [SE-XXXX](XXXX-filename.md)
17+
-->
18+
19+
## Introduction
20+
21+
By default the Swift compiler uses simple heuristics to determine whether a
22+
function takes ownership of its arguments. In some cases, these heuristics
23+
result in compiled code that forces the caller or callee to insert unnecessary
24+
copies and destroys. We propose new `consuming` and `nonconsuming` argument type
25+
modifiers to allow developers to override said compiler heuristics and
26+
explicitly chose the convention used by the compiler when writing performance
27+
sensitive code.
28+
29+
Pitch thread: [https://forums.swift.org/t/pitch-formally-defining-consuming-and-nonconsuming-argument-type-modifiers](https://forums.swift.org/t/pitch-formally-defining-consuming-and-nonconsuming-argument-type-modifiers)
30+
31+
Swift-evolution thread: [Discussion thread topic for that proposal](https://forums.swift.org/)
32+
33+
## Motivation
34+
35+
In Swift, all non-trivial function arguments use one of two conventions that
36+
specify if a caller or callee function is responsible for managing the
37+
argument's lifetime. The two conventions are:
38+
39+
* **`consuming`**. The caller function is transferring ownership of a value to
40+
the callee function. The callee then becomes responsible for managing the
41+
lifetime of the value. Semantically, this is implemented by requiring the
42+
caller to emit an unbalanced retain upon the value that then must be balanced
43+
by a consuming operation in the callee. This unbalanced retain causes such an
44+
operation to be called "passing the argument at +1".
45+
46+
* **`nonconsuming`**. The caller function is lending ownership of a value to the
47+
callee. The callee does not own the value and must retain the value to consume
48+
it (e.x.: passing as a consuming argument). A reference counted `nonconsuming`
49+
argument is called a "+0 argument" since it is passed without emitting an
50+
unbalanced retain (in contrast to a "+1 argument") and all retain/release pairs
51+
are properly balanced locally within the caller/callee rather than over the
52+
call boundary.
53+
54+
By default Swift chooses which convention to use based upon the function type of
55+
the callee as well as the position of the argument in the callee's argument
56+
list. Specifically:
57+
58+
1. If a callee is an initializer, then an argument is always passed as
59+
`consuming`.
60+
61+
2. If a callee is a setter, then an argument is passed as `consuming` if the
62+
argument is a non-self argument.
63+
64+
3. Otherwise regardless of the callee type, an argument is always passed as
65+
`nonconsuming`.
66+
67+
Over all, these defaults been found to work well, but in performance sensitive
68+
situations an API designer may need to customize these defaults to eliminate
69+
unnecessary copies and destroys. Despite that need, today there does not exist
70+
source stable Swift syntax for customizing those defaults.
71+
72+
## Motivating Examples
73+
74+
Despite the lack of such source stable syntax, to support the stdlib, the
75+
compiler has for some time provided underscored, source unstable keywords that
76+
allowed stdlib authors to override the default conventions:
77+
78+
1. `__shared`. This is equivalent to `nonconsuming`.
79+
2. `__owned`. This is equivalent to `consuming`.
80+
3. `__consuming`. This is used to have methods take self as a `consuming` argument.
81+
82+
Here are some examples of situations where developers have found it necessary to
83+
use these underscored attributes to eliminate overhead caused by using the
84+
default conventions:
85+
86+
* Passing a non-consuming argument to an initializer or setter if one is going
87+
to consume a value derived from the argument instead of the argument itself.
88+
89+
1. [String initializer for Substring](https://github.com/apple/swift/blob/09507f59cf36e83ebc2d1d1ab85cba8f4fc2e87c/stdlib/public/core/Substring.swift#L22). This API uses the underscored API `__shared` since semantically the author's want to create a String that is a copy of the substring. Since the Substring itself is not being consumed, without __shared we would have additional ref count traffic.
90+
```swift
91+
extension String {
92+
/// Creates a new string from the given substring.
93+
///
94+
/// - Parameter substring: A substring to convert to a standalone `String`
95+
/// instance.
96+
///
97+
/// - Complexity: O(*n*), where *n* is the length of `substring`.
98+
@inlinable
99+
public init(_ substring: __shared Substring) {
100+
self = String._fromSubstring(substring)
101+
}
102+
}
103+
```
104+
2. Initializing a cryptographic algorithm state by accumulating over a collection. Example: [ChaCha]( https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/benchmark/single-source/ChaCha.swift#L59). In this case, the ChaCha state is initialized using the contents of the collection "key" rather than "key" itself. NOTE: One thing to keep in mind with this example is that the optimizer completely inlines away the iterator so even though we use an iterator here. This results in the optimizer eliminating all of the ARC traffic from the usage of the CollectionOf32BitLittleEndianIntegers.makeIterator() causing the only remaining ARC traffic to be related to key being passed as an argument. Hence if we did not use shared, we would have an unnecessary release in init.
105+
```swift
106+
init<Key: Collection, Nonce: Collection>(key: __shared Key, nonce: Nonce, counter: UInt32) where Key.Element == UInt8, Nonce.Element == UInt8 {
107+
/* snip */
108+
var keyIterator = CollectionOf32BitLittleEndianIntegers(key).makeIterator()
109+
self._state.4 = keyIterator.next()!
110+
self._state.5 = keyIterator.next()!
111+
self._state.6 = keyIterator.next()!
112+
/* snip */
113+
}
114+
```
115+
* Passing a consuming argument to a normal function or method that isn't a
116+
setter but acts like a setter.
117+
1. Implementing append on a collection. Example: [Array.append(_:)](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/Array.swift#L1167). In this example, we want to forward the element directly into memory without inserting a retain, so we must use the underscored attribute `__owned` to change the default convention to be consuming.
118+
```swift
119+
public mutating func append(_ newElement: __owned Element) {
120+
// Separating uniqueness check and capacity check allows hoisting the
121+
// uniqueness check out of a loop.
122+
_makeUniqueAndReserveCapacityIfNotUnique()
123+
let oldCount = _buffer.mutableCount
124+
_reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
125+
_appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
126+
_endMutation()
127+
}
128+
```
129+
2. Bridging APIs. Example: [_bridgeAnythingNonVerbatimToObjectiveC()](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/BridgeObjectiveC.swift#L216). In this case, we want to consume the object into its bridged representation so we do not have to copy when bridging.
130+
```swift
131+
func _bridgeAnythingNonVerbatimToObjectiveC<T>(_ x: __owned T) -> AnyObject
132+
```
133+
* Consuming self when calling a method that is not an initializer.
134+
1. Creating an iterator for a collection. Example: [Collection.makeIterator()](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/Collection.swift#L1008). The iterator needs to have a reference to self so to reduce ARC traffic, we pass self into makeIterator at +1.
135+
```swift
136+
extension Collection where Iterator == IndexingIterator<Self> {
137+
/// Returns an iterator over the elements of the collection.
138+
@inlinable
139+
public __consuming func makeIterator() -> IndexingIterator<Self> {
140+
return IndexingIterator(_elements: self)
141+
}
142+
}
143+
```
144+
2. Sequence based algorithms that use iterators. Example: [Sequence.filter()](https://github.com/apple/swift/blob/324cccd18e9297b3cea9fc88d1ce80a0debe657e/stdlib/public/core/Sequence.swift#L678). In this case since we are using makeIterator, we need self to be __consuming.
145+
```swift
146+
@inlinable
147+
public __consuming func filter(
148+
_ isIncluded: (Element) throws -> Bool
149+
) rethrows -> [Element] {
150+
var result = ContiguousArray<Element>()
151+
152+
var iterator = self.makeIterator()
153+
154+
while let element = iterator.next() {
155+
if try isIncluded(element) {
156+
result.append(element)
157+
}
158+
}
159+
160+
return Array(result)
161+
}
162+
```
163+
164+
In all of the above cases, by using underscored attributes, authors changed the
165+
default convention since it introduced extra copy/destroys.
166+
167+
## Proposed solution
168+
169+
As mentioned in the previous section, the compiler already internally supports
170+
these semantics in the guise of underscored, source unstable keywords `__owned`,
171+
`__shared` and for self the keyword `__consuming`. We propose that we:
172+
173+
1. Add two new keywords to the language: `consuming` and `nonconsuming`.
174+
175+
2. Make `consuming` a synonym for `__consuming` when using `__consuming` to make
176+
self a +1 argument.
177+
178+
3. On non-self arguments, make `consuming` a synonym for `__owned` and
179+
`nonconsuming` a synonym for `__shared`.
180+
181+
## Detailed design
182+
183+
We propose formally modifying the Swift grammar as follows:
184+
185+
```
186+
// consuming, nonconsuming for parameters
187+
- type-annotation → : attributes? inout? type
188+
+ type-annotation → : attributes? type-modifiers? type
189+
+ type-modifiers → : type-modifier type-modifier*
190+
+ type-modifier → : inout
191+
+ → : consuming
192+
+ → : nonconsuming
193+
+
194+
195+
// consuming for self
196+
+ declaration-modifier → : consuming
197+
```
198+
199+
The only work that is required is to add support to the compiler for accepting
200+
the new spellings mentioned (`consuming` and `nonconsuming`) for the underscored
201+
variants of those keywords.
202+
203+
## Source compatibility
204+
205+
Since we are just adding new spellings for things that already exist in the
206+
compiler, this is additive and there isn't any source compatibility impact.
207+
208+
## Effect on ABI stability
209+
210+
This will not effect the ABI of any existing language features since all uses
211+
that already use `__owned`, `__shared`, and `__consuming` will work just as
212+
before. Applying `consuming`, `nonconsuming` to function arguments will result
213+
in ABI break to existing functions if the specified convention does not match
214+
the default convention.
215+
216+
## Effect on API resilience
217+
218+
Changing a argument from `consuming` to `nonconsuming` or vice versa is an
219+
ABI-breaking change. Adding an annotation that matches the default convention
220+
does not change the ABI.
221+
222+
## Alternatives considered
223+
224+
We could reuse `owned` and `shared` and just remove the underscores. This was
225+
viewed as confusing since `shared` is used in other contexts since `shared` can
226+
mean a rust like "shared borrow" which is a much stronger condition than
227+
`nonconsuming` is. Additionally, since we already will be using `consuming` to
228+
handle +1 for self, for consistency it makes sense to also rename `owned` to
229+
`consuming`.
230+
231+
## Acknowledgments
232+
233+
Thanks to Robert Widmann for the original underscored implementation of
234+
`__owned` and `__shared`: [https://forums.swift.org/t/ownership-annotations/11276](https://forums.swift.org/t/ownership-annotations/11276).

0 commit comments

Comments
 (0)