Skip to content

Commit

Permalink
Rollup merge of rust-lang#48658 - llogiq:no-more-cas, r=kennytm
Browse files Browse the repository at this point in the history
Add a generic CAS loop to std::sync::Atomic*

This adds two new methods to both `AtomicIsize` and `AtomicUsize` with optimized safe compare-and-set loops, so users will no longer need to write their own, except in *very* strange circumstances.

`update_and_fetch` will apply the function and return its result, whereas `fetch_and_update` will apply the function and return the previous value.

This solves rust-lang#48384 with `x.update_and_fetch(|x| x.max(y))`. It also relates to rust-lang#48655 (which I misuse as tracking issue for now)..

*note* This *might* need a crater run because the functions could clash with third party extension traits.
  • Loading branch information
kennytm committed Apr 5, 2018
2 parents 4bf76d6 + 0f5e419 commit 920249a
Showing 1 changed file with 185 additions and 0 deletions.
185 changes: 185 additions & 0 deletions src/libcore/sync/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,7 @@ macro_rules! atomic_int {
$stable_nand:meta,
$s_int_type:expr, $int_ref:expr,
$extra_feature:expr,
$min_fn:ident, $max_fn:ident,
$int_type:ident $atomic_type:ident $atomic_init:ident) => {
/// An integer type which can be safely shared between threads.
///
Expand Down Expand Up @@ -1421,6 +1422,128 @@ assert_eq!(foo.load(Ordering::SeqCst), 0b011110);
unsafe { atomic_xor(self.v.get(), val, order) }
}
}

doc_comment! {
concat!("Fetches the value, and applies a function to it that returns an optional
new value. Returns a `Result` (`Ok(_)` if the function returned `Some(_)`, else `Err(_)`) of the
previous value.
Note: This may call the function multiple times if the value has been changed from other threads in
the meantime, as long as the function returns `Some(_)`, but the function will have been applied
but once to the stored value.
# Examples
```rust
#![feature(no_more_cas)]
", $extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};
let x = ", stringify!($atomic_type), "::new(7);
assert_eq!(x.fetch_update(|_| None, Ordering::SeqCst, Ordering::SeqCst), Err(7));
assert_eq!(x.fetch_update(|x| Some(x + 1), Ordering::SeqCst, Ordering::SeqCst), Ok(7));
assert_eq!(x.fetch_update(|x| Some(x + 1), Ordering::SeqCst, Ordering::SeqCst), Ok(8));
assert_eq!(x.load(Ordering::SeqCst), 9);
```"),
#[inline]
#[unstable(feature = "no_more_cas",
reason = "no more CAS loops in user code",
issue = "48655")]
pub fn fetch_update<F>(&self,
mut f: F,
fetch_order: Ordering,
set_order: Ordering) -> Result<$int_type, $int_type>
where F: FnMut($int_type) -> Option<$int_type> {
let mut prev = self.load(fetch_order);
while let Some(next) = f(prev) {
match self.compare_exchange_weak(prev, next, set_order, fetch_order) {
x @ Ok(_) => return x,
Err(next_prev) => prev = next_prev
}
}
Err(prev)
}
}

doc_comment! {
concat!("Maximum with the current value.
Finds the maximum of the current value and the argument `val`, and
sets the new value to the result.
Returns the previous value.
# Examples
```
#![feature(atomic_min_max)]
", $extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};
let foo = ", stringify!($atomic_type), "::new(23);
assert_eq!(foo.fetch_max(42, Ordering::SeqCst), 23);
assert_eq!(foo.load(Ordering::SeqCst), 42);
```
If you want to obtain the maximum value in one step, you can use the following:
```
#![feature(atomic_min_max)]
", $extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};
let foo = ", stringify!($atomic_type), "::new(23);
let bar = 42;
let max_foo = foo.fetch_max(bar, Ordering::SeqCst).max(bar);
assert!(max_foo == 42);
```"),
#[inline]
#[unstable(feature = "atomic_min_max",
reason = "easier and faster min/max than writing manual CAS loop",
issue = "48655")]
pub fn fetch_max(&self, val: $int_type, order: Ordering) -> $int_type {
unsafe { $max_fn(self.v.get(), val, order) }
}
}

doc_comment! {
concat!("Minimum with the current value.
Finds the minimum of the current value and the argument `val`, and
sets the new value to the result.
Returns the previous value.
# Examples
```
#![feature(atomic_min_max)]
", $extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};
let foo = ", stringify!($atomic_type), "::new(23);
assert_eq!(foo.fetch_min(42, Ordering::Relaxed), 23);
assert_eq!(foo.load(Ordering::Relaxed), 23);
assert_eq!(foo.fetch_min(22, Ordering::Relaxed), 23);
assert_eq!(foo.load(Ordering::Relaxed), 22);
```
If you want to obtain the minimum value in one step, you can use the following:
```
#![feature(atomic_min_max)]
", $extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};
let foo = ", stringify!($atomic_type), "::new(23);
let bar = 12;
let min_foo = foo.fetch_min(bar, Ordering::SeqCst).min(bar);
assert_eq!(min_foo, 12);
```"),
#[inline]
#[unstable(feature = "atomic_min_max",
reason = "easier and faster min/max than writing manual CAS loop",
issue = "48655")]
pub fn fetch_min(&self, val: $int_type, order: Ordering) -> $int_type {
unsafe { $min_fn(self.v.get(), val, order) }
}
}

}
}
}
Expand All @@ -1435,6 +1558,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"i8", "../../../std/primitive.i8.html",
"#![feature(integer_atomics)]\n\n",
atomic_min, atomic_max,
i8 AtomicI8 ATOMIC_I8_INIT
}
#[cfg(target_has_atomic = "8")]
Expand All @@ -1447,6 +1571,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"u8", "../../../std/primitive.u8.html",
"#![feature(integer_atomics)]\n\n",
atomic_umin, atomic_umax,
u8 AtomicU8 ATOMIC_U8_INIT
}
#[cfg(target_has_atomic = "16")]
Expand All @@ -1459,6 +1584,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"i16", "../../../std/primitive.i16.html",
"#![feature(integer_atomics)]\n\n",
atomic_min, atomic_max,
i16 AtomicI16 ATOMIC_I16_INIT
}
#[cfg(target_has_atomic = "16")]
Expand All @@ -1471,6 +1597,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"u16", "../../../std/primitive.u16.html",
"#![feature(integer_atomics)]\n\n",
atomic_umin, atomic_umax,
u16 AtomicU16 ATOMIC_U16_INIT
}
#[cfg(target_has_atomic = "32")]
Expand All @@ -1483,6 +1610,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"i32", "../../../std/primitive.i32.html",
"#![feature(integer_atomics)]\n\n",
atomic_min, atomic_max,
i32 AtomicI32 ATOMIC_I32_INIT
}
#[cfg(target_has_atomic = "32")]
Expand All @@ -1495,6 +1623,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"u32", "../../../std/primitive.u32.html",
"#![feature(integer_atomics)]\n\n",
atomic_umin, atomic_umax,
u32 AtomicU32 ATOMIC_U32_INIT
}
#[cfg(target_has_atomic = "64")]
Expand All @@ -1507,6 +1636,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"i64", "../../../std/primitive.i64.html",
"#![feature(integer_atomics)]\n\n",
atomic_min, atomic_max,
i64 AtomicI64 ATOMIC_I64_INIT
}
#[cfg(target_has_atomic = "64")]
Expand All @@ -1519,6 +1649,7 @@ atomic_int! {
unstable(feature = "atomic_nand", issue = "13226"),
"u64", "../../../std/primitive.u64.html",
"#![feature(integer_atomics)]\n\n",
atomic_umin, atomic_umax,
u64 AtomicU64 ATOMIC_U64_INIT
}
#[cfg(target_has_atomic = "ptr")]
Expand All @@ -1531,6 +1662,7 @@ atomic_int!{
unstable(feature = "atomic_nand", issue = "13226"),
"isize", "../../../std/primitive.isize.html",
"",
atomic_min, atomic_max,
isize AtomicIsize ATOMIC_ISIZE_INIT
}
#[cfg(target_has_atomic = "ptr")]
Expand All @@ -1543,6 +1675,7 @@ atomic_int!{
unstable(feature = "atomic_nand", issue = "13226"),
"usize", "../../../std/primitive.usize.html",
"",
atomic_umin, atomic_umax,
usize AtomicUsize ATOMIC_USIZE_INIT
}

Expand Down Expand Up @@ -1720,6 +1853,58 @@ unsafe fn atomic_xor<T>(dst: *mut T, val: T, order: Ordering) -> T {
}
}

/// returns the max value (signed comparison)
#[inline]
unsafe fn atomic_max<T>(dst: *mut T, val: T, order: Ordering) -> T {
match order {
Acquire => intrinsics::atomic_max_acq(dst, val),
Release => intrinsics::atomic_max_rel(dst, val),
AcqRel => intrinsics::atomic_max_acqrel(dst, val),
Relaxed => intrinsics::atomic_max_relaxed(dst, val),
SeqCst => intrinsics::atomic_max(dst, val),
__Nonexhaustive => panic!("invalid memory ordering"),
}
}

/// returns the min value (signed comparison)
#[inline]
unsafe fn atomic_min<T>(dst: *mut T, val: T, order: Ordering) -> T {
match order {
Acquire => intrinsics::atomic_min_acq(dst, val),
Release => intrinsics::atomic_min_rel(dst, val),
AcqRel => intrinsics::atomic_min_acqrel(dst, val),
Relaxed => intrinsics::atomic_min_relaxed(dst, val),
SeqCst => intrinsics::atomic_min(dst, val),
__Nonexhaustive => panic!("invalid memory ordering"),
}
}

/// returns the max value (signed comparison)
#[inline]
unsafe fn atomic_umax<T>(dst: *mut T, val: T, order: Ordering) -> T {
match order {
Acquire => intrinsics::atomic_umax_acq(dst, val),
Release => intrinsics::atomic_umax_rel(dst, val),
AcqRel => intrinsics::atomic_umax_acqrel(dst, val),
Relaxed => intrinsics::atomic_umax_relaxed(dst, val),
SeqCst => intrinsics::atomic_umax(dst, val),
__Nonexhaustive => panic!("invalid memory ordering"),
}
}

/// returns the min value (signed comparison)
#[inline]
unsafe fn atomic_umin<T>(dst: *mut T, val: T, order: Ordering) -> T {
match order {
Acquire => intrinsics::atomic_umin_acq(dst, val),
Release => intrinsics::atomic_umin_rel(dst, val),
AcqRel => intrinsics::atomic_umin_acqrel(dst, val),
Relaxed => intrinsics::atomic_umin_relaxed(dst, val),
SeqCst => intrinsics::atomic_umin(dst, val),
__Nonexhaustive => panic!("invalid memory ordering"),
}
}

/// An atomic fence.
///
/// Depending on the specified order, a fence prevents the compiler and CPU from
Expand Down

0 comments on commit 920249a

Please sign in to comment.