summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Tyler Holmes <TylerDanielHolmes@gmail.com> 2022-01-20 09:59:22 -0800
committerGravatar GitHub <noreply@github.com> 2022-01-20 09:59:22 -0800
commit5cf74504da42bb06497a7466e8c554c725361504 (patch)
treef87fedb4b642467fb78a6c62a000c8eba4141b86
parentdb3e2b98edc4dbedda30bb5dfe2ed2497484ff05 (diff)
parent70cecc414749bcc06fc477bda3cbf83ada667c8e (diff)
downloadheapless-5cf74504da42bb06497a7466e8c554c725361504.tar.gz
heapless-5cf74504da42bb06497a7466e8c554c725361504.tar.zst
heapless-5cf74504da42bb06497a7466e8c554c725361504.zip
Merge branch 'master' into allow-non-x86-hosts
Diffstat (limited to '')
-rw-r--r--CHANGELOG.md8
-rw-r--r--Cargo.toml9
-rw-r--r--build.rs33
-rw-r--r--src/histbuf.rs116
-rw-r--r--src/lib.rs2
-rw-r--r--src/mpmc.rs21
-rw-r--r--src/pool/llsc.rs4
-rw-r--r--src/pool/mod.rs9
-rw-r--r--src/pool/singleton/arc.rs4
-rw-r--r--src/spsc.rs17
10 files changed, 194 insertions, 29 deletions
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<dyn Error>> {
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<dyn Error>> {
};
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<T, const N: usize> HistoryBuffer<T, N> {
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<u8, 6> = 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<T, const N: usize> Extend<T> for HistoryBuffer<T, N> {
@@ -247,9 +279,38 @@ impl<T, const N: usize> Default for HistoryBuffer<T, N> {
}
}
+/// 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<T, N>,
+ 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<u8, 6> = 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<u8, 6> = 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<u8, 6> = 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<u8, N> = 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<I: Eq + Debug>(
+ a: impl IntoIterator<Item = I>,
+ b: impl IntoIterator<Item = I>,
+ ) {
+ 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 <https://www.codeproject.com/Articles/43510/Lock-Free-Single-Producer-Single-Consumer-Circular>
//!
-//! 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
///