mirror of
https://github.com/rust-embedded/heapless.git
synced 2025-09-27 12:30:35 +00:00
Merge branch 'master' into allow-non-x86-hosts
This commit is contained in:
commit
5cf74504da
@ -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
|
||||
|
@ -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
|
||||
|
33
build.rs
33
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(())
|
||||
}
|
||||
|
116
src/histbuf.rs
116
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
21
src/mpmc.rs
21
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")]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
//!
|
||||
|
@ -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};
|
||||
|
17
src/spsc.rs
17
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
|
||||
///
|
||||
|
Loading…
x
Reference in New Issue
Block a user