diff --git a/CHANGELOG.md b/CHANGELOG.md index 53466ef6..d8cd5252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `cargo test` can now run on non-`x86` hosts +### Added + +- Added `OldestOrdered` iterator for `HistoryBuffer` + +### Changed + +- `atomic-polyfill` is now enabled and used for `cas` atomic emulation on `riscv` targets + ## [v0.7.9] - 2021-12-16 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index c2d006e3..4c8e90ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,12 @@ scoped_threadpool = "0.1.8" [target.thumbv6m-none-eabi.dependencies] atomic-polyfill = { version = "0.1.2", optional = true } +[target.riscv32i-unknown-none-elf.dependencies] +atomic-polyfill = { version = "0.1.4", optional = true } + +[target.riscv32imc-unknown-none-elf.dependencies] +atomic-polyfill = { version = "0.1.4", optional = true } + [dependencies] hash32 = "0.2.1" @@ -58,3 +64,6 @@ version = "0.1" [dependencies.defmt] version = ">=0.2.0,<0.4" optional = true + +[package.metadata.docs.rs] +all-features = true diff --git a/build.rs b/build.rs index 094b6cd2..0840d6fb 100644 --- a/build.rs +++ b/build.rs @@ -21,10 +21,19 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-cfg=armv7a"); } - // built-in targets with no atomic / CAS support as of nightly-2019-12-17 + // built-in targets with no atomic / CAS support as of nightly-2022-01-13 + // AND not supported by the atomic-polyfill crate // see the `no-atomics.sh` / `no-cas.sh` script sitting next to this file match &target[..] { - "msp430-none-elf" | "riscv32i-unknown-none-elf" | "riscv32imc-unknown-none-elf" => {} + "avr-unknown-gnu-atmega328" + | "bpfeb-unknown-none" + | "bpfel-unknown-none" + | "msp430-none-elf" + // | "riscv32i-unknown-none-elf" // supported by atomic-polyfill + // | "riscv32imc-unknown-none-elf" // supported by atomic-polyfill + | "thumbv4t-none-eabi" + // | "thumbv6m-none-eabi" // supported by atomic-polyfill + => {} _ => { println!("cargo:rustc-cfg=has_cas"); @@ -32,12 +41,30 @@ fn main() -> Result<(), Box> { }; match &target[..] { - "msp430-none-elf" | "riscv32i-unknown-none-elf" | "riscv32imc-unknown-none-elf" => {} + "avr-unknown-gnu-atmega328" + | "msp430-none-elf" + // | "riscv32i-unknown-none-elf" // supported by atomic-polyfill + // | "riscv32imc-unknown-none-elf" // supported by atomic-polyfill + => {} _ => { println!("cargo:rustc-cfg=has_atomics"); } }; + // Let the code know if it should use atomic-polyfill or not, and what aspects + // of polyfill it requires + match &target[..] { + "riscv32i-unknown-none-elf" | "riscv32imc-unknown-none-elf" => { + println!("cargo:rustc-cfg=full_atomic_polyfill"); + println!("cargo:rustc-cfg=cas_atomic_polyfill"); + } + + "thumbv6m-none-eabi" => { + println!("cargo:rustc-cfg=cas_atomic_polyfill"); + } + _ => {} + } + Ok(()) } diff --git a/src/histbuf.rs b/src/histbuf.rs index 7104e797..2bd52ec0 100644 --- a/src/histbuf.rs +++ b/src/histbuf.rs @@ -181,6 +181,38 @@ impl HistoryBuffer { pub fn as_slice(&self) -> &[T] { unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.len()) } } + + /// Returns an iterator for iterating over the buffer from oldest to newest. + /// + /// # Examples + /// + /// ``` + /// use heapless::HistoryBuffer; + /// + /// let mut buffer: HistoryBuffer = HistoryBuffer::new(); + /// buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]); + /// let expected = [1, 2, 3, 4, 5, 6]; + /// for (x, y) in buffer.oldest_ordered().zip(expected.iter()) { + /// assert_eq!(x, y) + /// } + /// + /// ``` + pub fn oldest_ordered<'a>(&'a self) -> OldestOrdered<'a, T, N> { + if self.filled { + OldestOrdered { + buf: self, + cur: self.write_at, + wrapped: false, + } + } else { + // special case: act like we wrapped already to handle empty buffer. + OldestOrdered { + buf: self, + cur: 0, + wrapped: true, + } + } + } } impl Extend for HistoryBuffer { @@ -247,9 +279,38 @@ impl Default for HistoryBuffer { } } +/// An iterator on the underlying buffer ordered from oldest data to newest +#[derive(Clone)] +pub struct OldestOrdered<'a, T, const N: usize> { + buf: &'a HistoryBuffer, + cur: usize, + wrapped: bool, +} + +impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + if self.cur == self.buf.len() && self.buf.filled { + // roll-over + self.cur = 0; + self.wrapped = true; + } + + if self.cur == self.buf.write_at && self.wrapped { + return None; + } + + let item = &self.buf[self.cur]; + self.cur += 1; + Some(item) + } +} + #[cfg(test)] mod tests { use crate::HistoryBuffer; + use core::fmt::Debug; #[test] fn new() { @@ -314,4 +375,59 @@ mod tests { assert_eq!(x.as_slice(), [5, 2, 3, 4]); } + + #[test] + fn ordered() { + // test on an empty buffer + let buffer: HistoryBuffer = HistoryBuffer::new(); + let mut iter = buffer.oldest_ordered(); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + + // test on a un-filled buffer + let mut buffer: HistoryBuffer = HistoryBuffer::new(); + buffer.extend([1, 2, 3]); + assert_eq!(buffer.len(), 3); + assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3]); + + // test on a filled buffer + let mut buffer: HistoryBuffer = HistoryBuffer::new(); + buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]); + assert_eq!(buffer.len(), 6); + assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3, 4, 5, 6]); + + // comprehensive test all cases + for n in 0..50 { + const N: usize = 7; + let mut buffer: HistoryBuffer = HistoryBuffer::new(); + buffer.extend(0..n); + assert_eq_iter( + buffer.oldest_ordered().copied(), + n.saturating_sub(N as u8)..n, + ); + } + } + + /// Compares two iterators item by item, making sure they stop at the same time. + fn assert_eq_iter( + a: impl IntoIterator, + b: impl IntoIterator, + ) { + let mut a = a.into_iter(); + let mut b = b.into_iter(); + + let mut i = 0; + loop { + let a_item = a.next(); + let b_item = b.next(); + + assert_eq!(a_item, b_item, "{}", i); + + i += 1; + + if b_item.is_none() { + break; + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index fe976f10..a8b52a3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ pub use binary_heap::BinaryHeap; pub use deque::Deque; -pub use histbuf::HistoryBuffer; +pub use histbuf::{HistoryBuffer, OldestOrdered}; pub use indexmap::{Bucket, FnvIndexMap, IndexMap, Pos}; pub use indexset::{FnvIndexSet, IndexSet}; pub use linear_map::LinearMap; diff --git a/src/mpmc.rs b/src/mpmc.rs index 0fbf7ce3..91b33311 100644 --- a/src/mpmc.rs +++ b/src/mpmc.rs @@ -1,6 +1,7 @@ //! A fixed capacity Multiple-Producer Multiple-Consumer (MPMC) lock-free queue //! -//! NOTE: This module is not available on targets that do *not* support CAS operations, e.g. ARMv6-M +//! NOTE: This module is not available on targets that do *not* support CAS operations and are not +//! emulated by the [`atomic_polyfill`] crate (e.g., MSP430). //! //! # Example //! @@ -73,8 +74,10 @@ //! //! # Portability //! -//! This module is not exposed to architectures that lack the instructions to implement CAS loops. -//! Those architectures include ARMv6-M (`thumbv6m-none-eabi`) and MSP430 (`msp430-none-elf`). +//! This module requires CAS atomic instructions which are not available on all architectures +//! (e.g. ARMv6-M (`thumbv6m-none-eabi`) and MSP430 (`msp430-none-elf`)). These atomics can be emulated +//! however with [`atomic_polyfill`], which is enabled with the `cas` feature and is enabled by default +//! for `thumbv6m-none-eabi` and `riscv32` targets. MSP430 is currently not supported by [`atomic_polyfill`]. //! //! # References //! @@ -84,18 +87,18 @@ use core::{cell::UnsafeCell, mem::MaybeUninit}; -#[cfg(all(feature = "mpmc_large", not(armv6m)))] +#[cfg(all(feature = "mpmc_large", not(cas_atomic_polyfill)))] type AtomicTargetSize = core::sync::atomic::AtomicUsize; -#[cfg(all(feature = "mpmc_large", armv6m))] +#[cfg(all(feature = "mpmc_large", cas_atomic_polyfill))] type AtomicTargetSize = atomic_polyfill::AtomicUsize; -#[cfg(all(not(feature = "mpmc_large"), not(armv6m)))] +#[cfg(all(not(feature = "mpmc_large"), not(cas_atomic_polyfill)))] type AtomicTargetSize = core::sync::atomic::AtomicU8; -#[cfg(all(not(feature = "mpmc_large"), armv6m))] +#[cfg(all(not(feature = "mpmc_large"), cas_atomic_polyfill))] type AtomicTargetSize = atomic_polyfill::AtomicU8; -#[cfg(not(armv6m))] +#[cfg(not(cas_atomic_polyfill))] type Ordering = core::sync::atomic::Ordering; -#[cfg(armv6m)] +#[cfg(cas_atomic_polyfill)] type Ordering = atomic_polyfill::Ordering; #[cfg(feature = "mpmc_large")] diff --git a/src/pool/llsc.rs b/src/pool/llsc.rs index 83081521..33f65557 100644 --- a/src/pool/llsc.rs +++ b/src/pool/llsc.rs @@ -3,10 +3,10 @@ pub use core::ptr::NonNull as Ptr; use core::{cell::UnsafeCell, ptr}; -#[cfg(armv6m)] +#[cfg(cas_atomic_polyfill)] use atomic_polyfill::{AtomicPtr, Ordering}; -#[cfg(not(armv6m))] +#[cfg(not(cas_atomic_polyfill))] use core::sync::atomic::{AtomicPtr, Ordering}; /// Unfortunate implementation detail required to use the diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 15ee5430..65a9c451 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -1,6 +1,7 @@ //! A heap-less, interrupt-safe, lock-free memory pool (\*) //! -//! NOTE: This module is not available on targets that do *not* support CAS operations, e.g. ARMv6-M +//! NOTE: This module is not available on targets that do *not* support CAS operations and are not +//! emulated by the [`atomic_polyfill`] crate (e.g., MSP430). //! //! (\*) Currently, the implementation is only lock-free *and* `Sync` on ARMv6, ARMv7-{A,R,M} & ARMv8-M //! devices @@ -59,8 +60,10 @@ //! on the target architecture (see section on ['Soundness'](#soundness) for more information). For //! this reason, `Pool` only implements `Sync` when compiling for some ARM cores. //! -//! Also note that ARMv6-M architecture lacks the primitives for CAS loops so this module does *not* -//! exist for `thumbv6m-none-eabi`. +//! This module requires CAS atomic instructions which are not available on all architectures +//! (e.g. ARMv6-M (`thumbv6m-none-eabi`) and MSP430 (`msp430-none-elf`)). These atomics can be emulated +//! however with [`atomic_polyfill`], which is enabled with the `cas` feature and is enabled by default +//! for `thumbv6m-none-eabi` and `riscv32` targets. MSP430 is currently not supported by [`atomic_polyfill`]. //! //! # Soundness //! diff --git a/src/pool/singleton/arc.rs b/src/pool/singleton/arc.rs index d110817e..a83519d8 100644 --- a/src/pool/singleton/arc.rs +++ b/src/pool/singleton/arc.rs @@ -81,10 +81,10 @@ use core::{ sync::atomic, }; -#[cfg(armv6m)] +#[cfg(cas_atomic_polyfill)] use atomic_polyfill::{AtomicUsize, Ordering}; -#[cfg(not(armv6m))] +#[cfg(not(cas_atomic_polyfill))] use core::sync::atomic::{AtomicUsize, Ordering}; use crate::pool::{self, stack::Ptr, Node}; diff --git a/src/spsc.rs b/src/spsc.rs index 2c0a6b44..38990d56 100644 --- a/src/spsc.rs +++ b/src/spsc.rs @@ -2,8 +2,8 @@ //! //! Implementation based on //! -//! NOTE: This module is not available on targets that do *not* support atomic loads, e.g. RISC-V -//! cores w/o the A (Atomic) extension +//! NOTE: This module is not available on targets that do *not* support atomic loads and are not +//! supported by [`atomic_polyfill`]. (e.g., MSP430). //! //! # Examples //! @@ -84,13 +84,12 @@ //! - The numbers reported correspond to the successful path (i.e. `Some` is returned by `dequeue` //! and `Ok` is returned by `enqueue`). -use core::{ - cell::UnsafeCell, - fmt, hash, - mem::MaybeUninit, - ptr, - sync::atomic::{AtomicUsize, Ordering}, -}; +use core::{cell::UnsafeCell, fmt, hash, mem::MaybeUninit, ptr}; + +#[cfg(full_atomic_polyfill)] +use atomic_polyfill::{AtomicUsize, Ordering}; +#[cfg(not(full_atomic_polyfill))] +use core::sync::atomic::{AtomicUsize, Ordering}; /// A statically allocated single producer single consumer queue with a capacity of `N - 1` elements ///