Skip to content

Commit 48e7379

Browse files
committed
Address Karoy's editorial comments
add an if back in
1 parent e21556a commit 48e7379

File tree

1 file changed

+35
-17
lines changed

1 file changed

+35
-17
lines changed

proposals/0410-atomics.md

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ extension Optional: AtomicValue where Wrapped: AtomicOptionalWrappable {...}
306306
* `UnsafeRawBufferPointer`
307307
* `UnsafeMutableRawBufferPointer`
308308

309-
`Duration` does not conform to `AtomicValue` whatsoever on 32 bit platforms.
309+
This proposal does not conform `Duration` to `AtomicValue` on any currently supported 32 bit platform. (Not even those where quad-word atomics are technically available, like arm64_32.)
310310

311311
#### Optional Atomics
312312

@@ -455,7 +455,7 @@ extension RawRepresentable where Self: AtomicOptionalWrappable, RawValue: Atomic
455455
}
456456
```
457457

458-
Similar to the enum example, we can model types whose raw value type is a pointer for example and use their optional value in atomic operations:
458+
For example, we can use this to add atomic operations over optionals of types whose raw value is a pointer:
459459

460460
```swift
461461
struct MyPointer: RawRepresentable, AtomicOptionalWrappable {
@@ -468,7 +468,7 @@ struct MyPointer: RawRepresentable, AtomicOptionalWrappable {
468468

469469
let myAtomicPointer = Atomic<MyPointer?>(nil)
470470
...
471-
myAtomicPointer.compareExchange(
471+
if myAtomicPointer.compareExchange(
472472
expected: nil,
473473
desired: MyPointer(rawValue: somePointer),
474474
ordering: .relaxed
@@ -479,7 +479,7 @@ myAtomicPointer.compareExchange(
479479
myAtomicPointer.store(nil, ordering: .releasing)
480480
```
481481

482-
(This get you an `AtomicValue` conformance for free as well because `AtomicOptionalWrappable: AtomicValue` , so non-optional `Atomic<MyPointer>` will work just as fine as well)
482+
(This gets you an `AtomicValue` conformance for free as well because `AtomicOptionalWrappable` refines `AtomicValue`. So this also allows non-optional use with `Atomic<MyPointer>`.)
483483

484484
### Atomic Storage Types
485485

@@ -531,7 +531,7 @@ This works by going through `Int`'s `AtomicValue` conformance and converting our
531531

532532
In their current single-word form, atomic pointer and reference types are susceptible to a class of race condition called the *ABA problem*. A freshly allocated object often happens to be placed at the same memory location as a recently deallocated one. Therefore, two successive `load`s of a simple atomic pointer may return the exact same value, even though the pointer may have received an arbitrary number of updates between the two loads, and the pointee may have been completely replaced. This can be a subtle, but deadly source of race conditions in naive implementations of many concurrent data structures.
533533

534-
While the single-word atomic primitives introduced in this document are already useful for some applications, it would be helpful to also provide a set of additional atomic operations that operate on two consecutive `Int`-sized values in the same transaction. All currently supported architectures provide direct hardware support for such "double-word" atomic operations.
534+
While the single-word atomic primitives introduced in this document are already useful for some applications, it would be helpful to also provide a set of additional atomic operations that operate on two consecutive `Int`-sized values in the same transaction. All currently supported architectures provide direct hardware support for such double-word atomic operations.
535535

536536
We propose a new separate type that provides an abstraction over the layout of what a double-word is for a platform.
537537

@@ -562,7 +562,9 @@ extension WordPair: AtomicValue {
562562

563563
For example, the second word can be used to augment atomic values with a version counter (sometimes called a "stamp" or a "tag"), which can help resolve the ABA problem by allowing code to reliably verify if a value remained unchanged between two successive loads.
564564

565-
Note that not all CPUs support double-word atomic operations and for that reason this type's conformance to `AtomicValue` is not always available. Platforms that do not have this support must not make the conformance to `AtomicValue` available on `WordPair` for use. Perhaps a future direction for this is something akin to `#if hasDoubleWideAtomics` to conditionally compile against whether this conformance type is available (or perhaps some `#if hasConformance(WordPair: AtomicValue)`).
565+
Note that not all CPUs support double-word atomic operations and so if Swift starts supporting such processors, this type's conformance to `AtomicValue` may not always be available. Platforms that cannot support double-word atomics must not make `WordPair`'s `AtomicValue` conformance available for use.
566+
567+
(If this becomes a real concern, a future proposal could introduce something like a `#if hasDoubleWordAtomics` compile-time condition to let code adapt to more limited environments. However, this is deferred until Swift actually starts supporting such platforms.)
566568

567569
### The Atomic type
568570

@@ -814,7 +816,7 @@ Most CPU architectures provide dedicated atomic instructions for certain integer
814816
| `min(_: Value, ordering: AtomicUpdateOrdering)` | `(oldValue: Value, newValue: Value)` | `a = Swift.min(a, b)` |
815817
| `max(_: Value, ordering: AtomicUpdateOrdering)` | `(oldValue: Value, newValue: Value)` | `a = Swift.max(a, b)` |
816818

817-
All operations are also marked as `@discardableResult` in the case where one doesn't care about the old value or new value. The compiler can't optimize the atomic operation away if the return value isn't used. The `add` and `subtract` operations explicitly check for overflow and will trap at runtime if one occurs. This is unchecked in `-Ounchecked` builds.
819+
All operations are also marked as `@discardableResult` in the case where one doesn't care about the old value or new value. The `add` and `subtract` operations explicitly check for overflow and will trap at runtime if one occurs, except in `-Ounchecked` builds.
818820

819821
While we require all atomic operations to be free of locks, we don't require wait-freedom. Therefore, on architectures that don't provide direct hardware support for some or all of these operations, we still require them to be implemented using `compareExchange` loops like the one for `wrappingAdd` above.
820822

@@ -1050,16 +1052,18 @@ class Counter {
10501052
}
10511053
```
10521054

1053-
By declaring this atomic value as a `var`, we've opted into Swift's dynamic exclusivity checking for this property, so all reads are checking whether or not it's currently being written to. This inherently means the atomic value is no longer, atomic.
1055+
By declaring this variable as a `var`, we opt into Swift's dynamic exclusivity checking for this property, so all non-exclusive accesses incur a runtime check to see if there is an active exclusive (e.g. mutating) access. This inherently means that atomic operations through such a variable will incur undesirable runtime overhead -- they are no longer purely atomic. (Even if the check never actually triggers a trap.)
10541056

1055-
To prevent users from accidently falling into this trap, `Atomic` (and `AtomicLazyReference`) will be special types who cannot be put into `var`s whatsoever. If a `var` has an explicit or inferred type of `Atomic` the compiler will present the user with an **error**.
1057+
To prevent users from accidentally falling into this trap, `Atomic` (and `AtomicLazyReference`) will not support `var` bindings. It is a compile-time error to have a `var` that has an explicit or inferred type of `Atomic`.
10561058

10571059
```swift
10581060
// error: variable of type 'Atomic<Int>' must be declared with a 'let'
10591061
var myAtomic = Atomic<Int>(123)
10601062
```
10611063

1062-
By making this a compiler error users will not have an unexpected dynamic exclusivity check inserted on atomic accesses. This means global variables, local variables, and stored properties cannot have a `var _: Atomic`. Similarly, you cannot declare a computed property that returns an `Atomic`. If one truly needs a computed property for whatever reason, you can workaround this by declaring the initial value as the return value for the computed property and use that to initialize an atomic:
1064+
By making this a compiler error, we can safely assume that atomic accesses will never incur an unexpected dynamic exclusivity check. It is forbidden to create mutable variables of type `struct Atomic`.
1065+
1066+
Similarly, it is an error to declare a computed property that returns an `Atomic`, as its getter would need to create and return a new instance each time it is accessed. Instead, you can return the actual value that would be the initial value for the atomic:
10631067

10641068
```swift
10651069
var computedInt: Int {
@@ -1069,14 +1073,26 @@ var computedInt: Int {
10691073
let myAtomic = Atomic<Int>(computedInt)
10701074
```
10711075

1072-
Along the same vein, these types must never be passed as `inout` as that declares that the callee has exclusive access to the atomic which as we said, makes the value no longer atomic. We will also be making this an **error**. One must pass `Atomic` as either `borrowing` or `consuming` (passing as consuming means you also have exclusive access to the atomic, but at the time you're consuming the value you don't care for the atomicity of the value anymore).
1076+
Alternatively, you can choose to convert the property to a function. This makes it clear that a new instance is being returned every time the function is called:
1077+
1078+
```swift
1079+
func makeAnAtomic() -> Atomic<Int> {
1080+
Atomic<Int>(123)
1081+
}
1082+
1083+
let myAtomic = makeAnAtomic()
1084+
```
1085+
1086+
1087+
1088+
In the same vein, these types must never be passed as `inout` parameters as that declares that the callee has exclusive access to the atomic, which would make the access no longer atomic. Attemping to create an `inout` binding for an atomic variable is also a compile-time error. Parameters that are used to pass `Atomic` values must either be `borrowing` or `consuming`. (Passing a variable as `consuming` is also an exclusive access, but it's destroying the original variable, so we no longer need to care for its atomicity.)
10731089

10741090
```swift
10751091
// error: parameter of type 'Atomic<Int>' must be declared as either 'borrowing' or 'consuming'
10761092
func passAtomic(_: inout Atomic<Int>)
10771093
```
10781094

1079-
Those rules are the ones most likely to surface when working with `Atomic`, but there is another caveat that you cannot declare `mutating` methods on this type for all the same reasons we've talked about above. If one were to extend `Atomic` with their own method:
1095+
Mutating methods on atomic types are also forbidden, as they introduce `inout` bindings on `self`. For example, trying to extend `Atomic` with our own `mutating` method results in a compile-time error:
10801096

10811097
```swift
10821098
extension Atomic {
@@ -1087,7 +1103,7 @@ extension Atomic {
10871103
}
10881104
```
10891105

1090-
These conditions for `Atomic` and `AtomicLazyReference` are important to prevent users from accidently introducing dynamic exclusivity for these low-level performance sensitive concurrency primitives.
1106+
These conditions for `Atomic` and `AtomicLazyReference` are important to prevent users from accidentally introducing dynamic exclusivity for these low-level performance sensitive concurrency primitives.
10911107

10921108
### Interaction with Swift Concurrency
10931109

@@ -1157,9 +1173,9 @@ func version3() async {
11571173
}
11581174
```
11591175

1160-
Considering these factors, we can safely say that `extension Atomic: Sendable where Value: Sendable {}`. All of the places where one can store a value an `Atomic`, is either as a global, as a class ivar, or as a local. For globals and class ivars, everyone agrees that they exist at a single location and will never try to perform atomic operations by moving the value to some local. As explained above, we can safely reason about local atomic values in async contexts and all of the places where we care about preserving atomicity will do the right thing for us, either by just using the thread's stack frame for allocation or by promoting it to some async coroutine's stack frame on the heap.
1176+
Variables of type `struct Atomic` are always located at a single, stable memory location, no matter its nature (be that a stored property in a class type or a noncopyable struct, an associated value in a noncopyable enum, a local variable that got promoted to the heap through a closure capture, or any other kind of variable.)
11611177

1162-
In addition to `Atomic` being `Sendable where Value: Sendable`, we can also make the same conformance for `AtomicLazyReference: Sendable where Instance: Sendable`.
1178+
Considering these factors, we can safely declare that `struct Atomic` is `Sendable` whenever its value is `Sendable`. By analogue reasoning, `struct AtomicLazyReference` is declared `Sendable` whenever its instance is `Sendable`.
11631179

11641180
## Detailed Design
11651181

@@ -1785,11 +1801,13 @@ Previous revisions of this proposal named this type `DoubleWord`. This is a good
17851801

17861802
### A different name for the `Synchronization` module
17871803

1788-
In the initial [return for revision notes](https://forums.swift.org/t/returned-for-revision-se-0410-atomics/68522), the language steering group suggested the name of this module to be `Atomics` as a strawman. I think this name is far too restrictive because it prevents other similar low-level concurrency primitives or somewhat related features like volatile loads/stores from sharing a module. It would also be extremely source breaking for folks that upgrade their Swift SDK to a version that may include this proposed new module while depending on the existing [swift-atomics](https://github.com/apple/swift-atomics) whose module is also named `Atomics`. We shouldn't be afraid of source breaks with potential conflicting module names when introducing a new module to the standard libraries. However, we have a known package using this name who is widely used and would be very source breaking for those using the atomics package.
1804+
In its [notes returning the initial version of this proposal for revision](https://forums.swift.org/t/returned-for-revision-se-0410-atomics/68522), the Swift Language Steering Group suggested the strawman name `Atomics` as a for this module. I think this name is far too restrictive because it prevents other similar low-level concurrency primitives or somewhat related features like volatile loads/stores from sharing a module. It would also be extremely source breaking for folks that upgrade their Swift SDK to a version that may include this proposed new module while depending on the existing [swift-atomics](https://github.com/apple/swift-atomics) whose module is also named `Atomics`.
1805+
1806+
We shouldn't be afraid of conflicting module names causing spurious source breaks when introducing a new module to the standard libraries; however, in this case, the direct precursor is prominently using this name, and reusing the same module name would cause significant breakage. We expect this package will need to remain in active use for a number of years, as it will be able to provide reimplementations of the constructs proposed here without the ABI availability constraints that come with Standard Library additions.
17891807

17901808
### Rename `AtomicValue` to `AtomicWrappable`
17911809

1792-
Another review note from the [revision notes](https://forums.swift.org/t/returned-for-revision-se-0410-atomics/68522) was the naming of the `AtomicValue` protocol. By API design guidelines it probably makes more sense to name this `AtomicWrappable` with our current design, however this proposal proposes `AtomicValue` because we feel the precedent set by the [swift-atomics](https://github.com/apple/swift-atomics) package might make it easier for folks to migrate their existing atomic code to the standard library's new atomic facilties as well as be more familiar with the API in general.
1810+
Another review note from the [revision notes](https://forums.swift.org/t/returned-for-revision-se-0410-atomics/68522) was the naming of the `AtomicValue` protocol. By API design guidelines it probably makes more sense to name this `AtomicWrappable` with our current design, however this proposal proposes `AtomicValue` because we feel the precedent set by the [swift-atomics](https://github.com/apple/swift-atomics) package will make it easier for folks to migrate their existing atomic code to the standard library's new atomic facilities.
17931811

17941812
## References
17951813

0 commit comments

Comments
 (0)