diff --git a/src/lib.rs b/src/lib.rs index 53e58ecdc55087d50cd17e2225076ae378332882..e8f83cbd160859606a248ba5ae1451b1ea7924f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,11 +144,10 @@ //! is_send::<Vec<NotSend, [NotSend; 4]>>(); //! ``` -#![cfg_attr(not(target_has_atomic = "ptr"), feature(asm))] -#![cfg_attr(target_has_atomic = "ptr", feature(const_atomic_usize_new))] #![deny(missing_docs)] -#![feature(cfg_target_has_atomic)] #![feature(const_fn)] +#![feature(const_unsafe_cell_new)] +#![feature(core_intrinsics)] #![feature(shared)] #![feature(unsize)] #![no_std] diff --git a/src/ring_buffer/mod.rs b/src/ring_buffer/mod.rs index 321af205b1e39bf405875fafe416d57e354ab908..37471944ada87a142f1e7d17aaaf53bae2c3a442 100644 --- a/src/ring_buffer/mod.rs +++ b/src/ring_buffer/mod.rs @@ -1,9 +1,8 @@ //! Ring buffer +use core::cell::UnsafeCell; use core::marker::{PhantomData, Unsize}; -use core::ptr; -#[cfg(target_has_atomic = "ptr")] -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::{intrinsics, ptr}; use untagged_option::UntaggedOption; @@ -13,6 +12,36 @@ pub use self::spsc::{Consumer, Producer}; mod spsc; +// AtomicUsize with no CAS operations that works on targets that have "no atomic support" according +// to their specification +struct AtomicUsize { + v: UnsafeCell<usize>, +} + +impl AtomicUsize { + pub const fn new(v: usize) -> AtomicUsize { + AtomicUsize { + v: UnsafeCell::new(v), + } + } + + pub fn get_mut(&mut self) -> &mut usize { + unsafe { &mut *self.v.get() } + } + + pub fn load_acquire(&self) -> usize { + unsafe { intrinsics::atomic_load_acq(self.v.get()) } + } + + pub fn load_relaxed(&self) -> usize { + unsafe { intrinsics::atomic_load_relaxed(self.v.get()) } + } + + pub fn store_release(&self, val: usize) { + unsafe { intrinsics::atomic_store_rel(self.v.get(), val) } + } +} + /// An statically allocated ring buffer backed by an array `A` pub struct RingBuffer<T, A> where @@ -22,12 +51,10 @@ where _marker: PhantomData<[T]>, // this is from where we dequeue items - #[cfg(target_has_atomic = "ptr")] head: AtomicUsize, - #[cfg(not(target_has_atomic = "ptr"))] head: usize, + head: AtomicUsize, // this is where we enqueue new items - #[cfg(target_has_atomic = "ptr")] tail: AtomicUsize, - #[cfg(not(target_has_atomic = "ptr"))] tail: usize, + tail: AtomicUsize, buffer: UntaggedOption<A>, } @@ -42,14 +69,8 @@ where RingBuffer { _marker: PhantomData, buffer: UntaggedOption::none(), - #[cfg(target_has_atomic = "ptr")] head: AtomicUsize::new(0), - #[cfg(not(target_has_atomic = "ptr"))] - head: 0, - #[cfg(target_has_atomic = "ptr")] tail: AtomicUsize::new(0), - #[cfg(not(target_has_atomic = "ptr"))] - tail: 0, } } @@ -63,15 +84,8 @@ where pub fn dequeue(&mut self) -> Option<T> { let n = self.capacity() + 1; - #[cfg(target_has_atomic = "ptr")] let head = self.head.get_mut(); - #[cfg(not(target_has_atomic = "ptr"))] - let head = &mut self.head; - - #[cfg(target_has_atomic = "ptr")] let tail = self.tail.get_mut(); - #[cfg(not(target_has_atomic = "ptr"))] - let tail = &mut self.tail; let buffer: &[T] = unsafe { self.buffer.as_ref() }; @@ -90,15 +104,8 @@ where pub fn enqueue(&mut self, item: T) -> Result<(), BufferFullError> { let n = self.capacity() + 1; - #[cfg(target_has_atomic = "ptr")] let head = self.head.get_mut(); - #[cfg(not(target_has_atomic = "ptr"))] - let head = &mut self.head; - - #[cfg(target_has_atomic = "ptr")] let tail = self.tail.get_mut(); - #[cfg(not(target_has_atomic = "ptr"))] - let tail = &mut self.tail; let buffer: &mut [T] = unsafe { self.buffer.as_mut() }; @@ -116,15 +123,8 @@ where /// Returns the number of elements in the queue pub fn len(&self) -> usize { - #[cfg(target_has_atomic = "ptr")] - let head = self.head.load(Ordering::Relaxed); - #[cfg(not(target_has_atomic = "ptr"))] - let head = self.head; - - #[cfg(target_has_atomic = "ptr")] - let tail = self.tail.load(Ordering::Relaxed); - #[cfg(not(target_has_atomic = "ptr"))] - let tail = self.tail; + let head = self.head.load_relaxed(); + let tail = self.tail.load_relaxed(); if head > tail { head - tail @@ -221,10 +221,7 @@ where fn next(&mut self) -> Option<&'a T> { if self.index < self.len { - #[cfg(not(target_has_atomic = "ptr"))] - let head = self.rb.head; - #[cfg(target_has_atomic = "ptr")] - let head = self.rb.head.load(Ordering::Relaxed); + let head = self.rb.head.load_relaxed(); let buffer: &[T] = unsafe { self.rb.buffer.as_ref() }; let ptr = buffer.as_ptr(); @@ -246,10 +243,7 @@ where fn next(&mut self) -> Option<&'a mut T> { if self.index < self.len { - #[cfg(not(target_has_atomic = "ptr"))] - let head = self.rb.head; - #[cfg(target_has_atomic = "ptr")] - let head = self.rb.head.load(Ordering::Relaxed); + let head = self.rb.head.load_relaxed(); let capacity = self.rb.capacity() + 1; let buffer: &mut [T] = unsafe { self.rb.buffer.as_mut() }; diff --git a/src/ring_buffer/spsc.rs b/src/ring_buffer/spsc.rs index 6ea265ff55acbf6a88c47b7dd9d6719b787e721a..0c384977615b34796b2c0653f7f8f2ffa6fd79cc 100644 --- a/src/ring_buffer/spsc.rs +++ b/src/ring_buffer/spsc.rs @@ -1,30 +1,14 @@ use core::ptr::{self, Shared}; use core::marker::Unsize; -#[cfg(target_has_atomic = "ptr")] -use core::sync::atomic::Ordering; use BufferFullError; use ring_buffer::RingBuffer; -// Compiler barrier -#[cfg(not(target_has_atomic = "ptr"))] -macro_rules! barrier { - () => { - unsafe { asm!("" ::: "memory") } - } -} - impl<T, A> RingBuffer<T, A> where A: Unsize<[T]>, { /// Splits a statically allocated ring buffer into producer and consumer end points - /// - /// **Warning** the current single producer single consumer implementation only supports - /// multi-core systems where `cfg(target_has_atomic = "ptr")` holds for all the cores. For - /// example, a dual core system where one core is Cortex-M0 core and the other is Cortex-M3 core - /// is not supported because Cortex-M0 (`thumbv6m-none-eabi`) doesn't satisfy - /// `cfg(target_has_atomic = "ptr")`. All single core systems are supported. pub fn split(&'static mut self) -> (Producer<T, A>, Consumer<T, A>) { ( Producer { @@ -52,44 +36,17 @@ where A: Unsize<[T]>, { /// Returns the item in the front of the queue, or `None` if the queue is empty - #[cfg(target_has_atomic = "ptr")] pub fn dequeue(&mut self) -> Option<T> { let rb = unsafe { self.rb.as_ref() }; - let tail = rb.tail.load(Ordering::Relaxed); - let head = rb.head.load(Ordering::Acquire); - let n = rb.capacity() + 1; let buffer: &[T] = unsafe { rb.buffer.as_ref() }; + let tail = rb.tail.load_relaxed(); + let head = rb.head.load_acquire(); if head != tail { let item = unsafe { ptr::read(buffer.get_unchecked(head)) }; - rb.head.store((head + 1) % n, Ordering::Release); - Some(item) - } else { - None - } - } - - /// Returns the item in the front of the queue, or `None` if the queue is empty - #[cfg(not(target_has_atomic = "ptr"))] - pub fn dequeue(&mut self) -> Option<T> { - let rb = unsafe { self.rb.as_mut() }; - - let n = rb.capacity() + 1; - let buffer: &[T] = unsafe { rb.buffer.as_ref() }; - - // NOTE(volatile) the value of `tail` can change at any time in the execution context of the - // consumer so we inform this to the compiler using a volatile load - if rb.head != unsafe { ptr::read_volatile(&rb.tail) } { - let item = unsafe { ptr::read(buffer.get_unchecked(rb.head)) }; - - // NOTE(barrier!) this ensures that the compiler won't place the instructions to read - // the data *before* the instructions to increment the `head` pointer -- note that this - // won't be enough on architectures that allow out of order execution - barrier!(); - - rb.head = (rb.head + 1) % n; + rb.head.store_release((head + 1) % n); Some(item) } else { None @@ -121,50 +78,20 @@ where /// Adds an `item` to the end of the queue /// /// Returns `BufferFullError` if the queue is full - #[cfg(target_has_atomic = "ptr")] pub fn enqueue(&mut self, item: T) -> Result<(), BufferFullError> { let rb = unsafe { self.rb.as_mut() }; - let head = rb.head.load(Ordering::Relaxed); - let tail = rb.tail.load(Ordering::Acquire); - let n = rb.capacity() + 1; - let next_tail = (tail + 1) % n; - let buffer: &mut [T] = unsafe { rb.buffer.as_mut() }; + let head = rb.head.load_relaxed(); + let tail = rb.tail.load_acquire(); + let next_tail = (tail + 1) % n; if next_tail != head { // NOTE(ptr::write) the memory slot that we are about to write to is uninitialized. We // use `ptr::write` to avoid running `T`'s destructor on the uninitialized memory unsafe { ptr::write(buffer.get_unchecked_mut(tail), item) } - rb.tail.store(next_tail, Ordering::Release); - Ok(()) - } else { - Err(BufferFullError) - } - } - - /// Adds an `item` to the end of the queue - /// - /// Returns `BufferFullError` if the queue is full - #[cfg(not(target_has_atomic = "ptr"))] - pub fn enqueue(&mut self, item: T) -> Result<(), BufferFullError> { - let rb = unsafe { self.rb.as_mut() }; - - let n = rb.capacity() + 1; - let buffer: &mut [T] = unsafe { rb.buffer.as_mut() }; - - let next_tail = (rb.tail + 1) % n; - // NOTE(volatile) the value of `head` can change at any time in the execution context of the - // producer so we inform this to the compiler using a volatile load - if next_tail != unsafe { ptr::read_volatile(&rb.head) } { - // NOTE(ptr::write) see the other `enqueue` implementation above for details - unsafe { ptr::write(buffer.get_unchecked_mut(rb.tail), item) } - - // NOTE(barrier!) see the NOTE(barrier!) above - barrier!(); - - rb.tail = next_tail; + rb.tail.store_release(next_tail); Ok(()) } else { Err(BufferFullError)