Skip to content

Commit 34936c0

Browse files
committed
Merge branch 'extended_compare_and_swap' of https://github.com/Amanieu/rfcs
2 parents b80356a + 9638c35 commit 34936c0

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
- Feature Name: extended_compare_and_swap
2+
- Start Date: 2016-1-5
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Rust currently provides a `compare_and_swap` method on atomic types, but this method only exposes a subset of the functionality of the C++11 equivalents [`compare_exchange_strong` and `compare_exchange_weak`](http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange):
10+
11+
- `compare_and_swap` maps to the C++11 `compare_exchange_strong`, but there is no Rust equivalent for `compare_exchange_weak`. The latter is allowed to fail spuriously even when the comparison succeeds, which allows the compiler to generate better assembly code when the compare and swap is used in a loop.
12+
13+
- `compare_and_swap` only has a single memory ordering parameter, whereas the C++11 versions have two: the first describes the memory ordering when the operation succeeds while the second one describes the memory ordering on failure.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
While all of these variants are identical on x86, they can allow more efficient code to be generated on architectures such as ARM:
19+
20+
- On ARM, the strong variant of compare and swap is compiled into an `LDREX` / `STREX` loop which restarts the compare and swap when a spurious failure is detected. This is unnecessary for many lock-free algorithms since the compare and swap is usually already inside a loop and a spurious failure is often caused by another thread modifying the atomic concurrently, which will probably cause the compare and swap to fail anyways.
21+
22+
- When Rust lowers `compare_and_swap` to LLVM, it uses the same memory ordering type for success and failure, which on ARM adds extra memory barrier instructions to the failure path. Most lock-free algorithms which make use of compare and swap in a loop only need relaxed ordering on failure since the operation is going to be restarted anyways.
23+
24+
# Detailed design
25+
[design]: #detailed-design
26+
27+
Since `compare_and_swap` is stable, we can't simply add a second memory ordering parameter to it. This RFC proposes deprecating the `compare_and_swap` function and replacing it with `compare_exchange` and `compare_exchange_weak`, which match the names of the equivalent C++11 functions (with the `_strong` suffix removed).
28+
29+
## `compare_exchange`
30+
31+
A new method is instead added to atomic types:
32+
33+
```rust
34+
fn compare_exchange(&self, current: T, new: T, success: Ordering, failure: Ordering) -> T;
35+
```
36+
37+
The restrictions on the failure ordering are the same as C++11: only `SeqCst`, `Acquire` and `Relaxed` are allowed and it must be equal or weaker than the success ordering. Passing an invalid memory ordering will result in a panic, although this can often be optimized away since the ordering is usually statically known.
38+
39+
The documentation for the original `compare_and_swap` is updated to say that it is equivalent to `compare_exchange` with the following mapping for memory orders:
40+
41+
Original | Success | Failure
42+
-------- | ------- | -------
43+
Relaxed | Relaxed | Relaxed
44+
Acquire | Acquire | Acquire
45+
Release | Release | Relaxed
46+
AcqRel | AcqRel | Acquire
47+
SeqCst | SeqCst | SeqCst
48+
49+
## `compare_exchange_weak`
50+
51+
A new method is instead added to atomic types:
52+
53+
```rust
54+
fn compare_exchange_weak(&self, current: T, new: T, success: Ordering, failure: Ordering) -> (T, bool);
55+
```
56+
57+
`compare_exchange` does not need to return a success flag because it can be inferred by checking if the returned value is equal to the expected one. This is not possible for `compare_exchange_weak` because it is allowed to fail spuriously, which means that it could fail to perform the swap even though the returned value is equal to the expected one.
58+
59+
A lock free algorithm using a loop would use the returned bool to determine whether to break out of the loop, and if not, use the returned value for the next iteration of the loop.
60+
61+
## Intrinsics
62+
63+
These are the existing intrinsics used to implement `compare_and_swap`:
64+
65+
```rust
66+
pub fn atomic_cxchg<T>(dst: *mut T, old: T, src: T) -> T;
67+
pub fn atomic_cxchg_acq<T>(dst: *mut T, old: T, src: T) -> T;
68+
pub fn atomic_cxchg_rel<T>(dst: *mut T, old: T, src: T) -> T;
69+
pub fn atomic_cxchg_acqrel<T>(dst: *mut T, old: T, src: T) -> T;
70+
pub fn atomic_cxchg_relaxed<T>(dst: *mut T, old: T, src: T) -> T;
71+
```
72+
73+
The following intrinsics need to be added to support relaxed memory orderings on failure:
74+
75+
```rust
76+
pub fn atomic_cxchg_acqrel_failrelaxed<T>(dst: *mut T, old: T, src: T) -> T;
77+
pub fn atomic_cxchg_failacq<T>(dst: *mut T, old: T, src: T) -> T;
78+
pub fn atomic_cxchg_failrelaxed<T>(dst: *mut T, old: T, src: T) -> T;
79+
pub fn atomic_cxchg_acq_failrelaxed<T>(dst: *mut T, old: T, src: T) -> T;
80+
```
81+
82+
The following intrinsics need to be added to support `compare_exchange_weak`:
83+
84+
```rust
85+
pub fn atomic_cxchg_weak<T>(dst: *mut T, old: T, src: T) -> (T, bool);
86+
pub fn atomic_cxchg_weak_acq<T>(dst: *mut T, old: T, src: T) -> (T, bool);
87+
pub fn atomic_cxchg_weak_rel<T>(dst: *mut T, old: T, src: T) -> (T, bool);
88+
pub fn atomic_cxchg_weak_acqrel<T>(dst: *mut T, old: T, src: T) -> (T, bool);
89+
pub fn atomic_cxchg_weak_relaxed<T>(dst: *mut T, old: T, src: T) -> (T, bool);
90+
pub fn atomic_cxchg_weak_acqrel_failrelaxed<T>(dst: *mut T, old: T, src: T) -> (T, bool);
91+
pub fn atomic_cxchg_weak_failacq<T>(dst: *mut T, old: T, src: T) -> (T, bool);
92+
pub fn atomic_cxchg_weak_failrelaxed<T>(dst: *mut T, old: T, src: T) -> (T, bool);
93+
pub fn atomic_cxchg_weak_acq_failrelaxed<T>(dst: *mut T, old: T, src: T) -> (T, bool);
94+
```
95+
96+
# Drawbacks
97+
[drawbacks]: #drawbacks
98+
99+
Ideally support for failure memory ordering would be added by simply adding an extra parameter to the existing `compare_and_swap` function. However this is not possible because `compare_and_swap` is stable.
100+
101+
This RFC proposes deprecating a stable function, which may not be desirable.
102+
103+
# Alternatives
104+
[alternatives]: #alternatives
105+
106+
One alternative for supporting failure orderings is to add new enum variants to `Ordering` instead of adding new methods with two ordering parameters. The following variants would need to be added: `AcquireFailRelaxed`, `AcqRelFailRelaxed`, `SeqCstFailRelaxed`, `SeqCstFailAcquire`. The downside is that the names are quite ugly and are only valid for `compare_and_swap`, not other atomic operations. It is also a breaking change to a stable enum.
107+
108+
Another alternative is to not deprecate `compare_and_swap` and instead add `compare_and_swap_explicit`, `compare_and_swap_weak` and `compare_and_swap_weak_explicit`. However the distiniction between the explicit and non-explicit isn't very clear and can lead to some confusion.
109+
110+
Not doing anything is also a possible option, but this will cause Rust to generate worse code for some lock-free algorithms.
111+
112+
# Unresolved questions
113+
[unresolved]: #unresolved-questions
114+
115+
None

0 commit comments

Comments
 (0)