Skip to content

Commit

Permalink
Add Atomic::compare_exchange(_weak) and deprecate Atomic::compare_and…
Browse files Browse the repository at this point in the history
…_set(_weak)
  • Loading branch information
taiki-e committed Jan 1, 2021
1 parent 3f51a08 commit 4f0fcd5
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 42 deletions.
195 changes: 171 additions & 24 deletions crossbeam-epoch/src/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,21 @@ fn strongest_failure_ordering(ord: Ordering) -> Ordering {
}

/// The error returned on failed compare-and-set operation.
pub struct CompareAndSetError<'g, T: ?Sized + Pointable, P: Pointer<T>> {
// TODO: remove in the next major version.
pub type CompareAndSetError<'g, T, P> = CompareExchangeError<'g, T, P>;

/// The error returned on failed compare-and-swap operation.
pub struct CompareExchangeError<'g, T: ?Sized + Pointable, P: Pointer<T>> {
/// The value in the atomic pointer at the time of the failed operation.
pub current: Shared<'g, T>,

/// The new value, which the operation failed to store.
pub new: P,
}

impl<'g, T: 'g, P: Pointer<T> + fmt::Debug> fmt::Debug for CompareAndSetError<'g, T, P> {
impl<T, P: Pointer<T> + fmt::Debug> fmt::Debug for CompareExchangeError<'_, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CompareAndSetError")
f.debug_struct("CompareExchangeError")
.field("current", &self.current)
.field("new", &self.new)
.finish()
Expand All @@ -54,6 +58,7 @@ impl<'g, T: 'g, P: Pointer<T> + fmt::Debug> fmt::Debug for CompareAndSetError<'g
/// ordering is chosen.
/// 2. A pair of `Ordering`s. The first one is for the success case, while the second one is
/// for the failure case.
// TODO: remove in the next major version.
pub trait CompareAndSetOrdering {
/// The ordering of the operation when it succeeds.
fn success(&self) -> Ordering;
Expand Down Expand Up @@ -426,8 +431,14 @@ impl<T: ?Sized + Pointable> Atomic<T> {
/// pointer that was written is returned. On failure the actual current value and `new` are
/// returned.
///
/// This method takes a [`CompareAndSetOrdering`] argument which describes the memory
/// ordering of this operation.
/// This method takes two `Ordering` arguments to describe the memory
/// ordering of this operation. `success` describes the required ordering for the
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
/// `failure` describes the required ordering for the load operation that takes place when
/// the comparison fails. Using `Acquire` as success ordering makes the store part
/// of this operation `Relaxed`, and using `Release` makes the successful load
/// `Relaxed`. The failure ordering can only be `SeqCst`, `Acquire` or `Relaxed`
/// and must be equivalent to or weaker than the success ordering.
///
/// # Examples
///
Expand All @@ -439,32 +450,161 @@ impl<T: ?Sized + Pointable> Atomic<T> {
///
/// let guard = &epoch::pin();
/// let curr = a.load(SeqCst, guard);
/// let res1 = a.compare_and_set(curr, Shared::null(), SeqCst, guard);
/// let res2 = a.compare_and_set(curr, Owned::new(5678), SeqCst, guard);
/// let res1 = a.compare_exchange(curr, Shared::null(), SeqCst, SeqCst, guard);
/// let res2 = a.compare_exchange(curr, Owned::new(5678), SeqCst, SeqCst, guard);
/// ```
pub fn compare_and_set<'g, O, P>(
pub fn compare_exchange<'g, P>(
&self,
current: Shared<'_, T>,
new: P,
ord: O,
success: Ordering,
failure: Ordering,
_: &'g Guard,
) -> Result<Shared<'g, T>, CompareAndSetError<'g, T, P>>
) -> Result<Shared<'g, T>, CompareExchangeError<'g, T, P>>
where
O: CompareAndSetOrdering,
P: Pointer<T>,
{
let new = new.into_usize();
self.data
.compare_exchange(current.into_usize(), new, ord.success(), ord.failure())
.compare_exchange(current.into_usize(), new, success, failure)
.map(|_| unsafe { Shared::from_usize(new) })
.map_err(|current| unsafe {
CompareAndSetError {
CompareExchangeError {
current: Shared::from_usize(current),
new: P::from_usize(new),
}
})
}

/// Stores the pointer `new` (either `Shared` or `Owned`) into the atomic pointer if the current
/// value is the same as `current`. The tag is also taken into account, so two pointers to the
/// same object, but with different tags, will not be considered equal.
///
/// Unlike [`compare_exchange`], this method is allowed to spuriously fail even when comparison
/// succeeds, which can result in more efficient code on some platforms. The return value is a
/// result indicating whether the new pointer was written. On success the pointer that was
/// written is returned. On failure the actual current value and `new` are returned.
///
/// This method takes two `Ordering` arguments to describe the memory
/// ordering of this operation. `success` describes the required ordering for the
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
/// `failure` describes the required ordering for the load operation that takes place when
/// the comparison fails. Using `Acquire` as success ordering makes the store part
/// of this operation `Relaxed`, and using `Release` makes the successful load
/// `Relaxed`. The failure ordering can only be `SeqCst`, `Acquire` or `Relaxed`
/// and must be equivalent to or weaker than the success ordering.
///
/// [`compare_exchange`]: Atomic::compare_exchange
///
/// # Examples
///
/// ```
/// use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
/// use std::sync::atomic::Ordering::SeqCst;
///
/// let a = Atomic::new(1234);
/// let guard = &epoch::pin();
///
/// let mut new = Owned::new(5678);
/// let mut ptr = a.load(SeqCst, guard);
/// loop {
/// match a.compare_exchange_weak(ptr, new, SeqCst, SeqCst, guard) {
/// Ok(p) => {
/// ptr = p;
/// break;
/// }
/// Err(err) => {
/// ptr = err.current;
/// new = err.new;
/// }
/// }
/// }
///
/// let mut curr = a.load(SeqCst, guard);
/// loop {
/// match a.compare_exchange_weak(curr, Shared::null(), SeqCst, SeqCst, guard) {
/// Ok(_) => break,
/// Err(err) => curr = err.current,
/// }
/// }
/// ```
pub fn compare_exchange_weak<'g, P>(
&self,
current: Shared<'_, T>,
new: P,
success: Ordering,
failure: Ordering,
_: &'g Guard,
) -> Result<Shared<'g, T>, CompareExchangeError<'g, T, P>>
where
P: Pointer<T>,
{
let new = new.into_usize();
self.data
.compare_exchange_weak(current.into_usize(), new, success, failure)
.map(|_| unsafe { Shared::from_usize(new) })
.map_err(|current| unsafe {
CompareExchangeError {
current: Shared::from_usize(current),
new: P::from_usize(new),
}
})
}

/// Stores the pointer `new` (either `Shared` or `Owned`) into the atomic pointer if the current
/// value is the same as `current`. The tag is also taken into account, so two pointers to the
/// same object, but with different tags, will not be considered equal.
///
/// The return value is a result indicating whether the new pointer was written. On success the
/// pointer that was written is returned. On failure the actual current value and `new` are
/// returned.
///
/// This method takes a [`CompareAndSetOrdering`] argument which describes the memory
/// ordering of this operation.
///
/// # Migrating to `compare_exchange`
///
/// `compare_and_set` is equivalent to `compare_exchange` with the following mapping for
/// memory orderings:
///
/// Original | Success | Failure
/// -------- | ------- | -------
/// Relaxed | Relaxed | Relaxed
/// Acquire | Acquire | Acquire
/// Release | Release | Relaxed
/// AcqRel | AcqRel | Acquire
/// SeqCst | SeqCst | SeqCst
///
/// # Examples
///
/// ```
/// # #![allow(deprecated)]
/// use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
/// use std::sync::atomic::Ordering::SeqCst;
///
/// let a = Atomic::new(1234);
///
/// let guard = &epoch::pin();
/// let curr = a.load(SeqCst, guard);
/// let res1 = a.compare_and_set(curr, Shared::null(), SeqCst, guard);
/// let res2 = a.compare_and_set(curr, Owned::new(5678), SeqCst, guard);
/// ```
// TODO: remove in the next major version.
#[deprecated(note = "Use `compare_exchange` instead")]
pub fn compare_and_set<'g, O, P>(
&self,
current: Shared<'_, T>,
new: P,
ord: O,
guard: &'g Guard,
) -> Result<Shared<'g, T>, CompareAndSetError<'g, T, P>>
where
O: CompareAndSetOrdering,
P: Pointer<T>,
{
self.compare_exchange(current, new, ord.success(), ord.failure(), guard)
}

/// Stores the pointer `new` (either `Shared` or `Owned`) into the atomic pointer if the current
/// value is the same as `current`. The tag is also taken into account, so two pointers to the
/// same object, but with different tags, will not be considered equal.
Expand All @@ -479,9 +619,23 @@ impl<T: ?Sized + Pointable> Atomic<T> {
///
/// [`compare_and_set`]: Atomic::compare_and_set
///
/// # Migrating to `compare_exchange_weak`
///
/// `compare_and_set_weak` is equivalent to `compare_exchange_weak` with the following mapping for
/// memory orderings:
///
/// Original | Success | Failure
/// -------- | ------- | -------
/// Relaxed | Relaxed | Relaxed
/// Acquire | Acquire | Acquire
/// Release | Release | Relaxed
/// AcqRel | AcqRel | Acquire
/// SeqCst | SeqCst | SeqCst
///
/// # Examples
///
/// ```
/// # #![allow(deprecated)]
/// use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
/// use std::sync::atomic::Ordering::SeqCst;
///
Expand Down Expand Up @@ -511,27 +665,20 @@ impl<T: ?Sized + Pointable> Atomic<T> {
/// }
/// }
/// ```
// TODO: remove in the next major version.
#[deprecated(note = "Use `compare_exchange_weak` instead")]
pub fn compare_and_set_weak<'g, O, P>(
&self,
current: Shared<'_, T>,
new: P,
ord: O,
_: &'g Guard,
guard: &'g Guard,
) -> Result<Shared<'g, T>, CompareAndSetError<'g, T, P>>
where
O: CompareAndSetOrdering,
P: Pointer<T>,
{
let new = new.into_usize();
self.data
.compare_exchange_weak(current.into_usize(), new, ord.success(), ord.failure())
.map(|_| unsafe { Shared::from_usize(new) })
.map_err(|current| unsafe {
CompareAndSetError {
current: Shared::from_usize(current),
new: P::from_usize(new),
}
})
self.compare_exchange_weak(current, new, ord.success(), ord.failure(), guard)
}

/// Bitwise "and" with the current tag.
Expand Down
2 changes: 1 addition & 1 deletion crossbeam-epoch/src/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl AtomicEpoch {
/// The return value is a result indicating whether the new value was written and containing
/// the previous value. On success this value is guaranteed to be equal to `current`.
///
/// `compare_exchange` takes two `Ordering` arguments to describe the memory
/// This method takes two `Ordering` arguments to describe the memory
/// ordering of this operation. `success` describes the required ordering for the
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
/// `failure` describes the required ordering for the load operation that takes place when
Expand Down
5 changes: 4 additions & 1 deletion crossbeam-epoch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ cfg_if! {
mod internal;
mod sync;

pub use self::atomic::{Pointable, Atomic, CompareAndSetError, CompareAndSetOrdering, Owned, Pointer, Shared};
pub use self::atomic::{
Pointable, Atomic, CompareExchangeError, CompareAndSetError, CompareAndSetOrdering,
Owned, Pointer, Shared,
};
pub use self::collector::{Collector, LocalHandle};
pub use self::guard::{unprotected, Guard};
}
Expand Down
4 changes: 2 additions & 2 deletions crossbeam-epoch/src/sync/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl<T, C: IsElement<T>> List<T, C> {
// Set the Entry of the to-be-inserted element to point to the previous successor of
// `to`.
entry.next.store(next, Relaxed);
match to.compare_and_set_weak(next, entry_ptr, Release, guard) {
match to.compare_exchange_weak(next, entry_ptr, Release, Relaxed, guard) {
Ok(_) => break,
// We lost the race or weak CAS failed spuriously. Update the successor and try
// again.
Expand Down Expand Up @@ -250,7 +250,7 @@ impl<'g, T: 'g, C: IsElement<T>> Iterator for Iter<'g, T, C> {
// Try to unlink `curr` from the list, and get the new value of `self.pred`.
let succ = match self
.pred
.compare_and_set(self.curr, succ, Acquire, self.guard)
.compare_exchange(self.curr, succ, Acquire, Acquire, self.guard)
{
Ok(_) => {
// We succeeded in unlinking `curr`, so we have to schedule
Expand Down
22 changes: 15 additions & 7 deletions crossbeam-epoch/src/sync/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,21 @@ impl<T> Queue<T> {
let next = o.next.load(Acquire, guard);
if unsafe { next.as_ref().is_some() } {
// if not, try to "help" by moving the tail pointer forward
let _ = self.tail.compare_and_set(onto, next, Release, guard);
let _ = self
.tail
.compare_exchange(onto, next, Release, Relaxed, guard);
false
} else {
// looks like the actual tail; attempt to link in `n`
let result = o
.next
.compare_and_set(Shared::null(), new, Release, guard)
.compare_exchange(Shared::null(), new, Release, Relaxed, guard)
.is_ok();
if result {
// try to move the tail pointer forward
let _ = self.tail.compare_and_set(onto, new, Release, guard);
let _ = self
.tail
.compare_exchange(onto, new, Release, Relaxed, guard);
}
result
}
Expand Down Expand Up @@ -118,12 +122,14 @@ impl<T> Queue<T> {
match unsafe { next.as_ref() } {
Some(n) => unsafe {
self.head
.compare_and_set(head, next, Release, guard)
.compare_exchange(head, next, Release, Relaxed, guard)
.map(|_| {
let tail = self.tail.load(Relaxed, guard);
// Advance the tail so that we don't retire a pointer to a reachable node.
if head == tail {
let _ = self.tail.compare_and_set(tail, next, Release, guard);
let _ = self
.tail
.compare_exchange(tail, next, Release, Relaxed, guard);
}
guard.defer_destroy(head);
// TODO: Replace with MaybeUninit::read when api is stable
Expand All @@ -149,12 +155,14 @@ impl<T> Queue<T> {
match unsafe { next.as_ref() } {
Some(n) if condition(unsafe { &*n.data.as_ptr() }) => unsafe {
self.head
.compare_and_set(head, next, Release, guard)
.compare_exchange(head, next, Release, Relaxed, guard)
.map(|_| {
let tail = self.tail.load(Relaxed, guard);
// Advance the tail so that we don't retire a pointer to a reachable node.
if head == tail {
let _ = self.tail.compare_and_set(tail, next, Release, guard);
let _ = self
.tail
.compare_exchange(tail, next, Release, Relaxed, guard);
}
guard.defer_destroy(head);
Some(n.data.as_ptr().read())
Expand Down
7 changes: 5 additions & 2 deletions crossbeam-epoch/tests/loom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ fn treiber_stack() {
let head = self.head.load(Relaxed, &guard);
n.next.store(head, Relaxed);

match self.head.compare_and_set(head, n, Release, &guard) {
match self
.head
.compare_exchange(head, n, Release, Relaxed, &guard)
{
Ok(_) => break,
Err(e) => n = e.new,
}
Expand All @@ -102,7 +105,7 @@ fn treiber_stack() {

if self
.head
.compare_and_set(head, next, Relaxed, &guard)
.compare_exchange(head, next, Relaxed, Relaxed, &guard)
.is_ok()
{
unsafe {
Expand Down
Loading

0 comments on commit 4f0fcd5

Please sign in to comment.