From baa52c84752a41ba06e53c7405a8aa2786074573 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Sun, 21 Feb 2016 13:27:20 +0000 Subject: [PATCH 1/5] Add a generic Atomic type --- text/0000-generic_atomic.md | 112 ++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 text/0000-generic_atomic.md diff --git a/text/0000-generic_atomic.md b/text/0000-generic_atomic.md new file mode 100644 index 00000000000..f49f829d982 --- /dev/null +++ b/text/0000-generic_atomic.md @@ -0,0 +1,112 @@ +- Feature Name: generic_atomic +- Start Date: 21-02-2016 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC proposes adding a generic `Atomic` type which can accept any `T: Copy`. The actual set of types that are accepted is restricted based on what the target supports: if `T` is too big then compilation will fail with an error indicating that atomics of the required size are not supported. The actual atomic API is the same as the existing one, so there is nothing new there. However this will require compiler support for an `AtomicWrapper` type which is used internally. + +```rust +#[atomic_wrapper] +struct AtomicWrapper(T); + +pub struct Atomic { + val: UnsafeCell> +} + +impl Atomic { + pub const fn new(val: T) -> Atomic; + pub fn load(&self, order: Ordering) -> T; + pub fn store(&self, val: T, order: Ordering); + pub fn swap(&self, val: T, order: Ordering) -> T; + pub fn compare_exchange(self, current: T, new: T, success: Ordering, failure: Ordering) -> T; + pub fn compare_exchange_weak(self, current: T, new: T, success: Ordering, failure: Ordering) -> (T, bool); +} + +impl Atomic { + pub fn fetch_and(&self, val: bool, order: Ordering) -> bool; + pub fn fetch_nand(&self, val: bool, order: Ordering) -> bool; + pub fn fetch_or(&self, val: bool, order: Ordering) -> bool; + pub fn fetch_xor(&self, val: bool, order: Ordering) -> bool; +} + +impl Atomic { // And other integer types. i64/u64 only if supported by target. + pub fn fetch_add(&self, val: i8, order: Ordering) -> i8; + pub fn fetch_sub(&self, val: i8, order: Ordering) -> i8; + pub fn fetch_and(&self, val: i8, order: Ordering) -> i8; + pub fn fetch_or(&self, val: i8, order: Ordering) -> i8; + pub fn fetch_xor(&self, val: i8, order: Ordering) -> i8; +} +``` + +# Motivation +[motivation]: #motivation + +Why are we doing this? What use cases does it support? What is the expected outcome? + +# Detailed design +[design]: #detailed-design + +## `Atomic` + +This is fairly straightforward: `load`, `store`, `swap`, `compare_exchange` and `compare_exchange_weak` are implemented for all atomic types. `bool` and integer types have additional `fetch_*` functions, which match those in `AtomicBool`, `AtomicIsize` and `AtomicUsize`. + +The only complication is that `compare_exchange` does a bitwise comparison, which may fail due to differences in the padding bytes of `T`. This is solved with an intrinsic that explicitly clears the padding bytes of a value using a mask. We have to do this ourselves rather than relying on the user because Rust struct layouts are imlpementation-defined. + +## `AtomicWrapper` + +This is an implementation detail which requires special compiler support. It has two functions: + +- Rounds the size and alignment of `T` up to the next power of two. + +- Gives an error message if `T` is too big for the current target's atomic operations. + +Any extra padding added by `AtomicWrapper` will be cleared before it is used in an atomic operation. + +## Target support + +One problem is that it is hard for a user to determine if a certain type `T` can be placed inside an `Atomic`. After a quick survey of the LLVM and Clang code, architectures can be classified into 3 categories: + +- The architecture does not support any form of atomics (mainly microcontroller architectures). +- The architecture supports all atomic operations for integers from i8 to iN (where N is the architecture word/pointer size). +- The architecture supports all atomic operations for integers from i8 to i(N*2). + +A new target cfg is added: `target_has_atomic`. It will have multiple values, one for each atomic size supported by the target. For example: + +```rust +#[cfg(target_has_atomic = "128")] +static ATOMIC: Atomic<(u64, u64)> = Atomic::new((0, 0)); +#[cfg(not(target_has_atomic = "128"))] +static ATOMIC: Mutex<(u64, u64)> = Mutex::new((0, 0)); + +#[cfg(target_has_atomic = "64")] +static COUNTER: Atomic = Atomic::new(0); +#[cfg(not(target_has_atomic = "64"))] +static COUTNER: Atomic = Atomic::new(0); +``` + +In addition to this, we will guarantee that atomics with sizes less than or equal to `usize` will always be available. This is reasonable since `AtomicIsize`, `AtomicUsize` and `AtomicPtr` are always available as well. However it may limit our portability to some microcontroller architectures. + + +# Drawbacks +[drawbacks]: #drawbacks + +`AtomicWrapper` relies on compiler magic to work. + +Having certain atomic types get enabled/disable based on the target isn't very nice, but it's unavoidable. + +`Atomic` will have a size of 1, unlike `AtomicBool` which uses a `usize` internally. This may cause confusion. + +# Alternatives +[alternatives]: #alternatives + +Rather than generating a compiler error, unsupported atomic types could be translated into calls to external functions in `compiler-rt`, like C++'s `std::atomic`. However these functions use locks to implement atomics, which makes them unsuitable for some situations like communicating with a signal handler. + +Several other designs have been suggested [here](https://internals.rust-lang.org/t/pre-rfc-extended-atomic-types/3068). + +# Unresolved questions +[unresolved]: #unresolved-questions + +Should we also rename `swap` to `exchange` while we're at it? It's more consistent with the new `compare_exchange` functions. From 5d01650cd7533acb6f839172813fa5b9b5ed31cd Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Sun, 21 Feb 2016 14:10:56 +0000 Subject: [PATCH 2/5] Add motivation --- text/0000-generic_atomic.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-generic_atomic.md b/text/0000-generic_atomic.md index f49f829d982..202ca7eee32 100644 --- a/text/0000-generic_atomic.md +++ b/text/0000-generic_atomic.md @@ -44,7 +44,11 @@ impl Atomic { // And other integer types. i64/u64 only if supported by targe # Motivation [motivation]: #motivation -Why are we doing this? What use cases does it support? What is the expected outcome? +Many lock-free algorithms require a two-value `compare_exchange`, which is effectively twice the size of a `usize`. This would be implemented by atomically swapping a struct containing two members. + +Another use case is to support Linux's futex API. This API is based on atomic `i32` variables, which currently aren't available on x86_64 because `AtomicIsize` is 64-bit. + +Finally, many people coming from C++ will expect a generic atomic type like `std::atomic`. # Detailed design [design]: #detailed-design From 96fe9d529682bcbf2850d9820d3fd313da7edb3a Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 23 Feb 2016 20:12:46 +0000 Subject: [PATCH 3/5] Rename swap to exchange --- text/0000-generic_atomic.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-generic_atomic.md b/text/0000-generic_atomic.md index 202ca7eee32..4707262737c 100644 --- a/text/0000-generic_atomic.md +++ b/text/0000-generic_atomic.md @@ -20,7 +20,7 @@ impl Atomic { pub const fn new(val: T) -> Atomic; pub fn load(&self, order: Ordering) -> T; pub fn store(&self, val: T, order: Ordering); - pub fn swap(&self, val: T, order: Ordering) -> T; + pub fn exchange(&self, val: T, order: Ordering) -> T; pub fn compare_exchange(self, current: T, new: T, success: Ordering, failure: Ordering) -> T; pub fn compare_exchange_weak(self, current: T, new: T, success: Ordering, failure: Ordering) -> (T, bool); } @@ -55,7 +55,7 @@ Finally, many people coming from C++ will expect a generic atomic type like `std ## `Atomic` -This is fairly straightforward: `load`, `store`, `swap`, `compare_exchange` and `compare_exchange_weak` are implemented for all atomic types. `bool` and integer types have additional `fetch_*` functions, which match those in `AtomicBool`, `AtomicIsize` and `AtomicUsize`. +This is fairly straightforward: `load`, `store`, `exchange`, `compare_exchange` and `compare_exchange_weak` are implemented for all atomic types. `bool` and integer types have additional `fetch_*` functions, which match those in `AtomicBool`, `AtomicIsize` and `AtomicUsize`. The only complication is that `compare_exchange` does a bitwise comparison, which may fail due to differences in the padding bytes of `T`. This is solved with an intrinsic that explicitly clears the padding bytes of a value using a mask. We have to do this ourselves rather than relying on the user because Rust struct layouts are imlpementation-defined. @@ -113,4 +113,4 @@ Several other designs have been suggested [here](https://internals.rust-lang.org # Unresolved questions [unresolved]: #unresolved-questions -Should we also rename `swap` to `exchange` while we're at it? It's more consistent with the new `compare_exchange` functions. +None From 4313582dfc236ec5a59981ef2c17b4411f295529 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 23 Feb 2016 20:14:16 +0000 Subject: [PATCH 4/5] Add unresolved question of dealing with architectures with no atomics --- text/0000-generic_atomic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-generic_atomic.md b/text/0000-generic_atomic.md index 4707262737c..f86acd627d0 100644 --- a/text/0000-generic_atomic.md +++ b/text/0000-generic_atomic.md @@ -113,4 +113,4 @@ Several other designs have been suggested [here](https://internals.rust-lang.org # Unresolved questions [unresolved]: #unresolved-questions -None +How should we deal with architectures that don't support native atomic operations (for example, ARMv5)? Should we disallow atomics entirely? Should we silently fall back to lock-based implementations? From ae53e07c427a66d1609b7112964c0d3f98d2428d Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 7 Mar 2016 17:23:18 -0800 Subject: [PATCH 5/5] Add a note about emulating smaller atomics using compare_exchange --- text/0000-generic_atomic.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/0000-generic_atomic.md b/text/0000-generic_atomic.md index f86acd627d0..a06e77cc661 100644 --- a/text/0000-generic_atomic.md +++ b/text/0000-generic_atomic.md @@ -91,8 +91,7 @@ static COUNTER: Atomic = Atomic::new(0); static COUTNER: Atomic = Atomic::new(0); ``` -In addition to this, we will guarantee that atomics with sizes less than or equal to `usize` will always be available. This is reasonable since `AtomicIsize`, `AtomicUsize` and `AtomicPtr` are always available as well. However it may limit our portability to some microcontroller architectures. - +Note that it is not necessary for an architecture to natively support atomic operations for all sizes (`i8`, `i16`, etc) as long as it is able to perform a `compare_exchange` operation with a larger size. All smaller operations can be emulated using that. For example a byte atomic can be emulated by using a `compare_exchange` loop that only modifies a single byte of the value. This is actually how LLVM implements byte-level atomics on MIPS, which only supports word-sized atomics native. Note that the out-of-bounds read is fine here because atomics are aligned and will never cross a page boundary. Since this transformation is performed transparently by LLVM, we do not need to do any extra work to support this. # Drawbacks [drawbacks]: #drawbacks