Skip to content

Commit 87eecd4

Browse files
committed
Auto merge of #79261 - faern:deprecate-compare-and-swap, r=Amanieu
Deprecate atomic compare_and_swap method Finish implementing [RFC 1443](https://github.com/rust-lang/rfcs/blob/master/text/1443-extended-compare-and-swap.md) (rust-lang/rfcs#1443). It was decided to deprecate `compare_and_swap` [back in Rust 1.12 already](#31767 (comment)). I can't find any info about that decision being reverted. My understanding is just that it has been forgotten. If there has been a decision on keeping `compare_and_swap` then it's hard to find, and even if this PR does not go through it can act as a place where people can find out about the decision being reverted. Atomic operations are hard to understand, very hard. And it does not help that there are multiple similar methods to do compare and swap with. They are so similar that for a reader it might be hard to understand the difference. This PR aims to make that simpler by finally deprecating `compare_and_swap` which is essentially just a more limited version of `compare_exchange`. The documentation is also updated (according to the RFC text) to explain the differences a bit better. Even if we decide to not deprecate `compare_and_swap`. I still think the documentation for the atomic operations should be improved to better describe their differences and similarities. And the documentation can be written nicer than the PR currently proposes, but I wanted to start somewhere. Most of it is just copied from the RFC. The documentation for `compare_exchange` and `compare_exchange_weak` indeed describe how they work! The problem is that they are more complex and harder to understand than `compare_and_swap`. So for someone who does not fully grasp this they might fall back to using `compare_and_swap`. Making the documentation outline the similarities and differences might build a bridge for people so they can cross over to the more powerful and sometimes more efficient operations. The conversions I do to avoid the `std` internal deprecation errors are very straight forward `compare_and_swap -> compare_exchange` changes where the orderings are just using the mapping in the new documentation. Only in one place did I use `compare_exchange_weak`. This can probably be improved further. But the goal here was not for those operations to be perfect. Just to not get worse and to allow the deprecation to happen.
2 parents 28d73a3 + 454f3ed commit 87eecd4

File tree

17 files changed

+170
-56
lines changed

17 files changed

+170
-56
lines changed

library/core/src/sync/atomic.rs

+89-18
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,23 @@ impl AtomicBool {
464464
/// **Note:** This method is only available on platforms that support atomic
465465
/// operations on `u8`.
466466
///
467+
/// # Migrating to `compare_exchange` and `compare_exchange_weak`
468+
///
469+
/// `compare_and_swap` is equivalent to `compare_exchange` with the following mapping for
470+
/// memory orderings:
471+
///
472+
/// Original | Success | Failure
473+
/// -------- | ------- | -------
474+
/// Relaxed | Relaxed | Relaxed
475+
/// Acquire | Acquire | Acquire
476+
/// Release | Release | Relaxed
477+
/// AcqRel | AcqRel | Acquire
478+
/// SeqCst | SeqCst | SeqCst
479+
///
480+
/// `compare_exchange_weak` is allowed to fail spuriously even when the comparison succeeds,
481+
/// which allows the compiler to generate better assembly code when the compare and swap
482+
/// is used in a loop.
483+
///
467484
/// # Examples
468485
///
469486
/// ```
@@ -479,6 +496,10 @@ impl AtomicBool {
479496
/// ```
480497
#[inline]
481498
#[stable(feature = "rust1", since = "1.0.0")]
499+
#[rustc_deprecated(
500+
since = "1.50.0",
501+
reason = "Use `compare_exchange` or `compare_exchange_weak` instead"
502+
)]
482503
#[cfg(target_has_atomic = "8")]
483504
pub fn compare_and_swap(&self, current: bool, new: bool, order: Ordering) -> bool {
484505
match self.compare_exchange(current, new, order, strongest_failure_ordering(order)) {
@@ -493,9 +514,10 @@ impl AtomicBool {
493514
/// the previous value. On success this value is guaranteed to be equal to `current`.
494515
///
495516
/// `compare_exchange` takes two [`Ordering`] arguments to describe the memory
496-
/// ordering of this operation. The first describes the required ordering if the
497-
/// operation succeeds while the second describes the required ordering when the
498-
/// operation fails. Using [`Acquire`] as success ordering makes the store part
517+
/// ordering of this operation. `success` describes the required ordering for the
518+
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
519+
/// `failure` describes the required ordering for the load operation that takes place when
520+
/// the comparison fails. Using [`Acquire`] as success ordering makes the store part
499521
/// of this operation [`Relaxed`], and using [`Release`] makes the successful load
500522
/// [`Relaxed`]. The failure ordering can only be [`SeqCst`], [`Acquire`] or [`Relaxed`]
501523
/// and must be equivalent to or weaker than the success ordering.
@@ -525,6 +547,7 @@ impl AtomicBool {
525547
/// ```
526548
#[inline]
527549
#[stable(feature = "extended_compare_and_swap", since = "1.10.0")]
550+
#[doc(alias = "compare_and_swap")]
528551
#[cfg(target_has_atomic = "8")]
529552
pub fn compare_exchange(
530553
&self,
@@ -550,9 +573,10 @@ impl AtomicBool {
550573
/// previous value.
551574
///
552575
/// `compare_exchange_weak` takes two [`Ordering`] arguments to describe the memory
553-
/// ordering of this operation. The first describes the required ordering if the
554-
/// operation succeeds while the second describes the required ordering when the
555-
/// operation fails. Using [`Acquire`] as success ordering makes the store part
576+
/// ordering of this operation. `success` describes the required ordering for the
577+
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
578+
/// `failure` describes the required ordering for the load operation that takes place when
579+
/// the comparison fails. Using [`Acquire`] as success ordering makes the store part
556580
/// of this operation [`Relaxed`], and using [`Release`] makes the successful load
557581
/// [`Relaxed`]. The failure ordering can only be [`SeqCst`], [`Acquire`] or [`Relaxed`]
558582
/// and must be equivalent to or weaker than the success ordering.
@@ -578,6 +602,7 @@ impl AtomicBool {
578602
/// ```
579603
#[inline]
580604
#[stable(feature = "extended_compare_and_swap", since = "1.10.0")]
605+
#[doc(alias = "compare_and_swap")]
581606
#[cfg(target_has_atomic = "8")]
582607
pub fn compare_exchange_weak(
583608
&self,
@@ -1066,6 +1091,23 @@ impl<T> AtomicPtr<T> {
10661091
/// **Note:** This method is only available on platforms that support atomic
10671092
/// operations on pointers.
10681093
///
1094+
/// # Migrating to `compare_exchange` and `compare_exchange_weak`
1095+
///
1096+
/// `compare_and_swap` is equivalent to `compare_exchange` with the following mapping for
1097+
/// memory orderings:
1098+
///
1099+
/// Original | Success | Failure
1100+
/// -------- | ------- | -------
1101+
/// Relaxed | Relaxed | Relaxed
1102+
/// Acquire | Acquire | Acquire
1103+
/// Release | Release | Relaxed
1104+
/// AcqRel | AcqRel | Acquire
1105+
/// SeqCst | SeqCst | SeqCst
1106+
///
1107+
/// `compare_exchange_weak` is allowed to fail spuriously even when the comparison succeeds,
1108+
/// which allows the compiler to generate better assembly code when the compare and swap
1109+
/// is used in a loop.
1110+
///
10691111
/// # Examples
10701112
///
10711113
/// ```
@@ -1080,6 +1122,10 @@ impl<T> AtomicPtr<T> {
10801122
/// ```
10811123
#[inline]
10821124
#[stable(feature = "rust1", since = "1.0.0")]
1125+
#[rustc_deprecated(
1126+
since = "1.50.0",
1127+
reason = "Use `compare_exchange` or `compare_exchange_weak` instead"
1128+
)]
10831129
#[cfg(target_has_atomic = "ptr")]
10841130
pub fn compare_and_swap(&self, current: *mut T, new: *mut T, order: Ordering) -> *mut T {
10851131
match self.compare_exchange(current, new, order, strongest_failure_ordering(order)) {
@@ -1094,9 +1140,10 @@ impl<T> AtomicPtr<T> {
10941140
/// the previous value. On success this value is guaranteed to be equal to `current`.
10951141
///
10961142
/// `compare_exchange` takes two [`Ordering`] arguments to describe the memory
1097-
/// ordering of this operation. The first describes the required ordering if the
1098-
/// operation succeeds while the second describes the required ordering when the
1099-
/// operation fails. Using [`Acquire`] as success ordering makes the store part
1143+
/// ordering of this operation. `success` describes the required ordering for the
1144+
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
1145+
/// `failure` describes the required ordering for the load operation that takes place when
1146+
/// the comparison fails. Using [`Acquire`] as success ordering makes the store part
11001147
/// of this operation [`Relaxed`], and using [`Release`] makes the successful load
11011148
/// [`Relaxed`]. The failure ordering can only be [`SeqCst`], [`Acquire`] or [`Relaxed`]
11021149
/// and must be equivalent to or weaker than the success ordering.
@@ -1157,9 +1204,10 @@ impl<T> AtomicPtr<T> {
11571204
/// previous value.
11581205
///
11591206
/// `compare_exchange_weak` takes two [`Ordering`] arguments to describe the memory
1160-
/// ordering of this operation. The first describes the required ordering if the
1161-
/// operation succeeds while the second describes the required ordering when the
1162-
/// operation fails. Using [`Acquire`] as success ordering makes the store part
1207+
/// ordering of this operation. `success` describes the required ordering for the
1208+
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
1209+
/// `failure` describes the required ordering for the load operation that takes place when
1210+
/// the comparison fails. Using [`Acquire`] as success ordering makes the store part
11631211
/// of this operation [`Relaxed`], and using [`Release`] makes the successful load
11641212
/// [`Relaxed`]. The failure ordering can only be [`SeqCst`], [`Acquire`] or [`Relaxed`]
11651213
/// and must be equivalent to or weaker than the success ordering.
@@ -1604,6 +1652,23 @@ happens, and using [`Release`] makes the load part [`Relaxed`].
16041652
**Note**: This method is only available on platforms that support atomic
16051653
operations on [`", $s_int_type, "`](", $int_ref, ").
16061654
1655+
# Migrating to `compare_exchange` and `compare_exchange_weak`
1656+
1657+
`compare_and_swap` is equivalent to `compare_exchange` with the following mapping for
1658+
memory orderings:
1659+
1660+
Original | Success | Failure
1661+
-------- | ------- | -------
1662+
Relaxed | Relaxed | Relaxed
1663+
Acquire | Acquire | Acquire
1664+
Release | Release | Relaxed
1665+
AcqRel | AcqRel | Acquire
1666+
SeqCst | SeqCst | SeqCst
1667+
1668+
`compare_exchange_weak` is allowed to fail spuriously even when the comparison succeeds,
1669+
which allows the compiler to generate better assembly code when the compare and swap
1670+
is used in a loop.
1671+
16071672
# Examples
16081673
16091674
```
@@ -1619,6 +1684,10 @@ assert_eq!(some_var.load(Ordering::Relaxed), 10);
16191684
```"),
16201685
#[inline]
16211686
#[$stable]
1687+
#[rustc_deprecated(
1688+
since = "1.50.0",
1689+
reason = "Use `compare_exchange` or `compare_exchange_weak` instead")
1690+
]
16221691
#[$cfg_cas]
16231692
pub fn compare_and_swap(&self,
16241693
current: $int_type,
@@ -1643,9 +1712,10 @@ containing the previous value. On success this value is guaranteed to be equal t
16431712
`current`.
16441713
16451714
`compare_exchange` takes two [`Ordering`] arguments to describe the memory
1646-
ordering of this operation. The first describes the required ordering if the
1647-
operation succeeds while the second describes the required ordering when the
1648-
operation fails. Using [`Acquire`] as success ordering makes the store part
1715+
ordering of this operation. `success` describes the required ordering for the
1716+
read-modify-write operation that takes place if the comparison with `current` succeeds.
1717+
`failure` describes the required ordering for the load operation that takes place when
1718+
the comparison fails. Using [`Acquire`] as success ordering makes the store part
16491719
of this operation [`Relaxed`], and using [`Release`] makes the successful load
16501720
[`Relaxed`]. The failure ordering can only be [`SeqCst`], [`Acquire`] or [`Relaxed`]
16511721
and must be equivalent to or weaker than the success ordering.
@@ -1695,9 +1765,10 @@ platforms. The return value is a result indicating whether the new value was
16951765
written and containing the previous value.
16961766
16971767
`compare_exchange_weak` takes two [`Ordering`] arguments to describe the memory
1698-
ordering of this operation. The first describes the required ordering if the
1699-
operation succeeds while the second describes the required ordering when the
1700-
operation fails. Using [`Acquire`] as success ordering makes the store part
1768+
ordering of this operation. `success` describes the required ordering for the
1769+
read-modify-write operation that takes place if the comparison with `current` succeeds.
1770+
`failure` describes the required ordering for the load operation that takes place when
1771+
the comparison fails. Using [`Acquire`] as success ordering makes the store part
17011772
of this operation [`Relaxed`], and using [`Release`] makes the successful load
17021773
[`Relaxed`]. The failure ordering can only be [`SeqCst`], [`Acquire`] or [`Relaxed`]
17031774
and must be equivalent to or weaker than the success ordering.

library/core/tests/atomic.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use core::sync::atomic::*;
44
#[test]
55
fn bool_() {
66
let a = AtomicBool::new(false);
7-
assert_eq!(a.compare_and_swap(false, true, SeqCst), false);
8-
assert_eq!(a.compare_and_swap(false, true, SeqCst), true);
7+
assert_eq!(a.compare_exchange(false, true, SeqCst, SeqCst), Ok(false));
8+
assert_eq!(a.compare_exchange(false, true, SeqCst, SeqCst), Err(true));
99

1010
a.store(false, SeqCst);
11-
assert_eq!(a.compare_and_swap(false, true, SeqCst), false);
11+
assert_eq!(a.compare_exchange(false, true, SeqCst, SeqCst), Ok(false));
1212
}
1313

1414
#[test]

library/std/src/sync/mpsc/blocking.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ pub fn tokens() -> (WaitToken, SignalToken) {
3636

3737
impl SignalToken {
3838
pub fn signal(&self) -> bool {
39-
let wake = !self.inner.woken.compare_and_swap(false, true, Ordering::SeqCst);
39+
let wake = self
40+
.inner
41+
.woken
42+
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
43+
.is_ok();
4044
if wake {
4145
self.inner.thread.unpark();
4246
}

library/std/src/sync/mpsc/oneshot.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ impl<T> Packet<T> {
129129
let ptr = unsafe { signal_token.cast_to_usize() };
130130

131131
// race with senders to enter the blocking state
132-
if self.state.compare_and_swap(EMPTY, ptr, Ordering::SeqCst) == EMPTY {
132+
if self.state.compare_exchange(EMPTY, ptr, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
133133
if let Some(deadline) = deadline {
134134
let timed_out = !wait_token.wait_max_until(deadline);
135135
// Try to reset the state
@@ -161,7 +161,12 @@ impl<T> Packet<T> {
161161
// the state changes under our feet we'd rather just see that state
162162
// change.
163163
DATA => {
164-
self.state.compare_and_swap(DATA, EMPTY, Ordering::SeqCst);
164+
let _ = self.state.compare_exchange(
165+
DATA,
166+
EMPTY,
167+
Ordering::SeqCst,
168+
Ordering::SeqCst,
169+
);
165170
match (&mut *self.data.get()).take() {
166171
Some(data) => Ok(data),
167172
None => unreachable!(),
@@ -264,7 +269,10 @@ impl<T> Packet<T> {
264269

265270
// If we've got a blocked thread, then use an atomic to gain ownership
266271
// of it (may fail)
267-
ptr => self.state.compare_and_swap(ptr, EMPTY, Ordering::SeqCst),
272+
ptr => self
273+
.state
274+
.compare_exchange(ptr, EMPTY, Ordering::SeqCst, Ordering::SeqCst)
275+
.unwrap_or_else(|x| x),
268276
};
269277

270278
// Now that we've got ownership of our state, figure out what to do

library/std/src/sync/mpsc/shared.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,15 @@ impl<T> Packet<T> {
385385
self.port_dropped.store(true, Ordering::SeqCst);
386386
let mut steals = unsafe { *self.steals.get() };
387387
while {
388-
let cnt = self.cnt.compare_and_swap(steals, DISCONNECTED, Ordering::SeqCst);
389-
cnt != DISCONNECTED && cnt != steals
388+
match self.cnt.compare_exchange(
389+
steals,
390+
DISCONNECTED,
391+
Ordering::SeqCst,
392+
Ordering::SeqCst,
393+
) {
394+
Ok(_) => false,
395+
Err(old) => old != DISCONNECTED,
396+
}
390397
} {
391398
// See the discussion in 'try_recv' for why we yield
392399
// control of this thread.

library/std/src/sync/mpsc/stream.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -322,12 +322,15 @@ impl<T> Packet<T> {
322322
// (because there is a bounded number of senders).
323323
let mut steals = unsafe { *self.queue.consumer_addition().steals.get() };
324324
while {
325-
let cnt = self.queue.producer_addition().cnt.compare_and_swap(
325+
match self.queue.producer_addition().cnt.compare_exchange(
326326
steals,
327327
DISCONNECTED,
328328
Ordering::SeqCst,
329-
);
330-
cnt != DISCONNECTED && cnt != steals
329+
Ordering::SeqCst,
330+
) {
331+
Ok(_) => false,
332+
Err(old) => old != DISCONNECTED,
333+
}
331334
} {
332335
while self.queue.pop().is_some() {
333336
steals += 1;

library/std/src/sync/once.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
// must do so with Release ordering to make the result available.
6666
// - `wait` inserts `Waiter` nodes as a pointer in `state_and_queue`, and
6767
// needs to make the nodes available with Release ordering. The load in
68-
// its `compare_and_swap` can be Relaxed because it only has to compare
68+
// its `compare_exchange` can be Relaxed because it only has to compare
6969
// the atomic, not to read other data.
7070
// - `WaiterQueue::Drop` must see the `Waiter` nodes, so it must load
7171
// `state_and_queue` with Acquire ordering.
@@ -110,7 +110,7 @@ use crate::thread::{self, Thread};
110110
/// ```
111111
#[stable(feature = "rust1", since = "1.0.0")]
112112
pub struct Once {
113-
// `state_and_queue` is actually an a pointer to a `Waiter` with extra state
113+
// `state_and_queue` is actually a pointer to a `Waiter` with extra state
114114
// bits, so we add the `PhantomData` appropriately.
115115
state_and_queue: AtomicUsize,
116116
_marker: marker::PhantomData<*const Waiter>,
@@ -395,12 +395,13 @@ impl Once {
395395
}
396396
POISONED | INCOMPLETE => {
397397
// Try to register this thread as the one RUNNING.
398-
let old = self.state_and_queue.compare_and_swap(
398+
let exchange_result = self.state_and_queue.compare_exchange(
399399
state_and_queue,
400400
RUNNING,
401401
Ordering::Acquire,
402+
Ordering::Acquire,
402403
);
403-
if old != state_and_queue {
404+
if let Err(old) = exchange_result {
404405
state_and_queue = old;
405406
continue;
406407
}
@@ -452,8 +453,13 @@ fn wait(state_and_queue: &AtomicUsize, mut current_state: usize) {
452453

453454
// Try to slide in the node at the head of the linked list, making sure
454455
// that another thread didn't just replace the head of the linked list.
455-
let old = state_and_queue.compare_and_swap(current_state, me | RUNNING, Ordering::Release);
456-
if old != current_state {
456+
let exchange_result = state_and_queue.compare_exchange(
457+
current_state,
458+
me | RUNNING,
459+
Ordering::Release,
460+
Ordering::Relaxed,
461+
);
462+
if let Err(old) = exchange_result {
457463
current_state = old;
458464
continue;
459465
}

library/std/src/sys/sgx/abi/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ unsafe extern "C" fn tcs_init(secondary: bool) {
3636
}
3737

3838
// Try to atomically swap UNINIT with BUSY. The returned state can be:
39-
match RELOC_STATE.compare_and_swap(UNINIT, BUSY, Ordering::Acquire) {
39+
match RELOC_STATE.compare_exchange(UNINIT, BUSY, Ordering::Acquire, Ordering::Acquire) {
4040
// This thread just obtained the lock and other threads will observe BUSY
41-
UNINIT => {
41+
Ok(_) => {
4242
reloc::relocate_elf_rela();
4343
RELOC_STATE.store(DONE, Ordering::Release);
4444
}
4545
// We need to wait until the initialization is done.
46-
BUSY => {
46+
Err(BUSY) => {
4747
while RELOC_STATE.load(Ordering::Acquire) == BUSY {
4848
core::hint::spin_loop();
4949
}
5050
}
5151
// Initialization is done.
52-
DONE => {}
52+
Err(DONE) => {}
5353
_ => unreachable!(),
5454
}
5555
}

library/std/src/sys/sgx/waitqueue/spin_mutex.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl<T> SpinMutex<T> {
4242

4343
#[inline(always)]
4444
pub fn try_lock(&self) -> Option<SpinMutexGuard<'_, T>> {
45-
if !self.lock.compare_and_swap(false, true, Ordering::Acquire) {
45+
if self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Acquire).is_ok() {
4646
Some(SpinMutexGuard { mutex: self })
4747
} else {
4848
None

0 commit comments

Comments
 (0)