Skip to content

Commit 5cf7450

Browse files
authored
Merge branch 'master' into allow-non-x86-hosts
2 parents db3e2b9 + 70cecc4 commit 5cf7450

File tree

10 files changed

+194
-29
lines changed

10 files changed

+194
-29
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111

1212
- `cargo test` can now run on non-`x86` hosts
1313

14+
### Added
15+
16+
- Added `OldestOrdered` iterator for `HistoryBuffer`
17+
18+
### Changed
19+
20+
- `atomic-polyfill` is now enabled and used for `cas` atomic emulation on `riscv` targets
21+
1422
## [v0.7.9] - 2021-12-16
1523

1624
### Fixed

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ scoped_threadpool = "0.1.8"
3333
[target.thumbv6m-none-eabi.dependencies]
3434
atomic-polyfill = { version = "0.1.2", optional = true }
3535

36+
[target.riscv32i-unknown-none-elf.dependencies]
37+
atomic-polyfill = { version = "0.1.4", optional = true }
38+
39+
[target.riscv32imc-unknown-none-elf.dependencies]
40+
atomic-polyfill = { version = "0.1.4", optional = true }
41+
3642
[dependencies]
3743
hash32 = "0.2.1"
3844

@@ -58,3 +64,6 @@ version = "0.1"
5864
[dependencies.defmt]
5965
version = ">=0.2.0,<0.4"
6066
optional = true
67+
68+
[package.metadata.docs.rs]
69+
all-features = true

build.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,50 @@ fn main() -> Result<(), Box<dyn Error>> {
2121
println!("cargo:rustc-cfg=armv7a");
2222
}
2323

24-
// built-in targets with no atomic / CAS support as of nightly-2019-12-17
24+
// built-in targets with no atomic / CAS support as of nightly-2022-01-13
25+
// AND not supported by the atomic-polyfill crate
2526
// see the `no-atomics.sh` / `no-cas.sh` script sitting next to this file
2627
match &target[..] {
27-
"msp430-none-elf" | "riscv32i-unknown-none-elf" | "riscv32imc-unknown-none-elf" => {}
28+
"avr-unknown-gnu-atmega328"
29+
| "bpfeb-unknown-none"
30+
| "bpfel-unknown-none"
31+
| "msp430-none-elf"
32+
// | "riscv32i-unknown-none-elf" // supported by atomic-polyfill
33+
// | "riscv32imc-unknown-none-elf" // supported by atomic-polyfill
34+
| "thumbv4t-none-eabi"
35+
// | "thumbv6m-none-eabi" // supported by atomic-polyfill
36+
=> {}
2837

2938
_ => {
3039
println!("cargo:rustc-cfg=has_cas");
3140
}
3241
};
3342

3443
match &target[..] {
35-
"msp430-none-elf" | "riscv32i-unknown-none-elf" | "riscv32imc-unknown-none-elf" => {}
44+
"avr-unknown-gnu-atmega328"
45+
| "msp430-none-elf"
46+
// | "riscv32i-unknown-none-elf" // supported by atomic-polyfill
47+
// | "riscv32imc-unknown-none-elf" // supported by atomic-polyfill
48+
=> {}
3649

3750
_ => {
3851
println!("cargo:rustc-cfg=has_atomics");
3952
}
4053
};
4154

55+
// Let the code know if it should use atomic-polyfill or not, and what aspects
56+
// of polyfill it requires
57+
match &target[..] {
58+
"riscv32i-unknown-none-elf" | "riscv32imc-unknown-none-elf" => {
59+
println!("cargo:rustc-cfg=full_atomic_polyfill");
60+
println!("cargo:rustc-cfg=cas_atomic_polyfill");
61+
}
62+
63+
"thumbv6m-none-eabi" => {
64+
println!("cargo:rustc-cfg=cas_atomic_polyfill");
65+
}
66+
_ => {}
67+
}
68+
4269
Ok(())
4370
}

src/histbuf.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,38 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
181181
pub fn as_slice(&self) -> &[T] {
182182
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.len()) }
183183
}
184+
185+
/// Returns an iterator for iterating over the buffer from oldest to newest.
186+
///
187+
/// # Examples
188+
///
189+
/// ```
190+
/// use heapless::HistoryBuffer;
191+
///
192+
/// let mut buffer: HistoryBuffer<u8, 6> = HistoryBuffer::new();
193+
/// buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]);
194+
/// let expected = [1, 2, 3, 4, 5, 6];
195+
/// for (x, y) in buffer.oldest_ordered().zip(expected.iter()) {
196+
/// assert_eq!(x, y)
197+
/// }
198+
///
199+
/// ```
200+
pub fn oldest_ordered<'a>(&'a self) -> OldestOrdered<'a, T, N> {
201+
if self.filled {
202+
OldestOrdered {
203+
buf: self,
204+
cur: self.write_at,
205+
wrapped: false,
206+
}
207+
} else {
208+
// special case: act like we wrapped already to handle empty buffer.
209+
OldestOrdered {
210+
buf: self,
211+
cur: 0,
212+
wrapped: true,
213+
}
214+
}
215+
}
184216
}
185217

186218
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> {
247279
}
248280
}
249281

282+
/// An iterator on the underlying buffer ordered from oldest data to newest
283+
#[derive(Clone)]
284+
pub struct OldestOrdered<'a, T, const N: usize> {
285+
buf: &'a HistoryBuffer<T, N>,
286+
cur: usize,
287+
wrapped: bool,
288+
}
289+
290+
impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> {
291+
type Item = &'a T;
292+
293+
fn next(&mut self) -> Option<&'a T> {
294+
if self.cur == self.buf.len() && self.buf.filled {
295+
// roll-over
296+
self.cur = 0;
297+
self.wrapped = true;
298+
}
299+
300+
if self.cur == self.buf.write_at && self.wrapped {
301+
return None;
302+
}
303+
304+
let item = &self.buf[self.cur];
305+
self.cur += 1;
306+
Some(item)
307+
}
308+
}
309+
250310
#[cfg(test)]
251311
mod tests {
252312
use crate::HistoryBuffer;
313+
use core::fmt::Debug;
253314

254315
#[test]
255316
fn new() {
@@ -314,4 +375,59 @@ mod tests {
314375

315376
assert_eq!(x.as_slice(), [5, 2, 3, 4]);
316377
}
378+
379+
#[test]
380+
fn ordered() {
381+
// test on an empty buffer
382+
let buffer: HistoryBuffer<u8, 6> = HistoryBuffer::new();
383+
let mut iter = buffer.oldest_ordered();
384+
assert_eq!(iter.next(), None);
385+
assert_eq!(iter.next(), None);
386+
387+
// test on a un-filled buffer
388+
let mut buffer: HistoryBuffer<u8, 6> = HistoryBuffer::new();
389+
buffer.extend([1, 2, 3]);
390+
assert_eq!(buffer.len(), 3);
391+
assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3]);
392+
393+
// test on a filled buffer
394+
let mut buffer: HistoryBuffer<u8, 6> = HistoryBuffer::new();
395+
buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]);
396+
assert_eq!(buffer.len(), 6);
397+
assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3, 4, 5, 6]);
398+
399+
// comprehensive test all cases
400+
for n in 0..50 {
401+
const N: usize = 7;
402+
let mut buffer: HistoryBuffer<u8, N> = HistoryBuffer::new();
403+
buffer.extend(0..n);
404+
assert_eq_iter(
405+
buffer.oldest_ordered().copied(),
406+
n.saturating_sub(N as u8)..n,
407+
);
408+
}
409+
}
410+
411+
/// Compares two iterators item by item, making sure they stop at the same time.
412+
fn assert_eq_iter<I: Eq + Debug>(
413+
a: impl IntoIterator<Item = I>,
414+
b: impl IntoIterator<Item = I>,
415+
) {
416+
let mut a = a.into_iter();
417+
let mut b = b.into_iter();
418+
419+
let mut i = 0;
420+
loop {
421+
let a_item = a.next();
422+
let b_item = b.next();
423+
424+
assert_eq!(a_item, b_item, "{}", i);
425+
426+
i += 1;
427+
428+
if b_item.is_none() {
429+
break;
430+
}
431+
}
432+
}
317433
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676

7777
pub use binary_heap::BinaryHeap;
7878
pub use deque::Deque;
79-
pub use histbuf::HistoryBuffer;
79+
pub use histbuf::{HistoryBuffer, OldestOrdered};
8080
pub use indexmap::{Bucket, FnvIndexMap, IndexMap, Pos};
8181
pub use indexset::{FnvIndexSet, IndexSet};
8282
pub use linear_map::LinearMap;

src/mpmc.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! A fixed capacity Multiple-Producer Multiple-Consumer (MPMC) lock-free queue
22
//!
3-
//! NOTE: This module is not available on targets that do *not* support CAS operations, e.g. ARMv6-M
3+
//! NOTE: This module is not available on targets that do *not* support CAS operations and are not
4+
//! emulated by the [`atomic_polyfill`] crate (e.g., MSP430).
45
//!
56
//! # Example
67
//!
@@ -73,8 +74,10 @@
7374
//!
7475
//! # Portability
7576
//!
76-
//! This module is not exposed to architectures that lack the instructions to implement CAS loops.
77-
//! Those architectures include ARMv6-M (`thumbv6m-none-eabi`) and MSP430 (`msp430-none-elf`).
77+
//! This module requires CAS atomic instructions which are not available on all architectures
78+
//! (e.g. ARMv6-M (`thumbv6m-none-eabi`) and MSP430 (`msp430-none-elf`)). These atomics can be emulated
79+
//! however with [`atomic_polyfill`], which is enabled with the `cas` feature and is enabled by default
80+
//! for `thumbv6m-none-eabi` and `riscv32` targets. MSP430 is currently not supported by [`atomic_polyfill`].
7881
//!
7982
//! # References
8083
//!
@@ -84,18 +87,18 @@
8487
8588
use core::{cell::UnsafeCell, mem::MaybeUninit};
8689

87-
#[cfg(all(feature = "mpmc_large", not(armv6m)))]
90+
#[cfg(all(feature = "mpmc_large", not(cas_atomic_polyfill)))]
8891
type AtomicTargetSize = core::sync::atomic::AtomicUsize;
89-
#[cfg(all(feature = "mpmc_large", armv6m))]
92+
#[cfg(all(feature = "mpmc_large", cas_atomic_polyfill))]
9093
type AtomicTargetSize = atomic_polyfill::AtomicUsize;
91-
#[cfg(all(not(feature = "mpmc_large"), not(armv6m)))]
94+
#[cfg(all(not(feature = "mpmc_large"), not(cas_atomic_polyfill)))]
9295
type AtomicTargetSize = core::sync::atomic::AtomicU8;
93-
#[cfg(all(not(feature = "mpmc_large"), armv6m))]
96+
#[cfg(all(not(feature = "mpmc_large"), cas_atomic_polyfill))]
9497
type AtomicTargetSize = atomic_polyfill::AtomicU8;
9598

96-
#[cfg(not(armv6m))]
99+
#[cfg(not(cas_atomic_polyfill))]
97100
type Ordering = core::sync::atomic::Ordering;
98-
#[cfg(armv6m)]
101+
#[cfg(cas_atomic_polyfill)]
99102
type Ordering = atomic_polyfill::Ordering;
100103

101104
#[cfg(feature = "mpmc_large")]

src/pool/llsc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
pub use core::ptr::NonNull as Ptr;
44
use core::{cell::UnsafeCell, ptr};
55

6-
#[cfg(armv6m)]
6+
#[cfg(cas_atomic_polyfill)]
77
use atomic_polyfill::{AtomicPtr, Ordering};
88

9-
#[cfg(not(armv6m))]
9+
#[cfg(not(cas_atomic_polyfill))]
1010
use core::sync::atomic::{AtomicPtr, Ordering};
1111

1212
/// Unfortunate implementation detail required to use the

src/pool/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! A heap-less, interrupt-safe, lock-free memory pool (\*)
22
//!
3-
//! NOTE: This module is not available on targets that do *not* support CAS operations, e.g. ARMv6-M
3+
//! NOTE: This module is not available on targets that do *not* support CAS operations and are not
4+
//! emulated by the [`atomic_polyfill`] crate (e.g., MSP430).
45
//!
56
//! (\*) Currently, the implementation is only lock-free *and* `Sync` on ARMv6, ARMv7-{A,R,M} & ARMv8-M
67
//! devices
@@ -59,8 +60,10 @@
5960
//! on the target architecture (see section on ['Soundness'](#soundness) for more information). For
6061
//! this reason, `Pool` only implements `Sync` when compiling for some ARM cores.
6162
//!
62-
//! Also note that ARMv6-M architecture lacks the primitives for CAS loops so this module does *not*
63-
//! exist for `thumbv6m-none-eabi`.
63+
//! This module requires CAS atomic instructions which are not available on all architectures
64+
//! (e.g. ARMv6-M (`thumbv6m-none-eabi`) and MSP430 (`msp430-none-elf`)). These atomics can be emulated
65+
//! however with [`atomic_polyfill`], which is enabled with the `cas` feature and is enabled by default
66+
//! for `thumbv6m-none-eabi` and `riscv32` targets. MSP430 is currently not supported by [`atomic_polyfill`].
6467
//!
6568
//! # Soundness
6669
//!

src/pool/singleton/arc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ use core::{
8181
sync::atomic,
8282
};
8383

84-
#[cfg(armv6m)]
84+
#[cfg(cas_atomic_polyfill)]
8585
use atomic_polyfill::{AtomicUsize, Ordering};
8686

87-
#[cfg(not(armv6m))]
87+
#[cfg(not(cas_atomic_polyfill))]
8888
use core::sync::atomic::{AtomicUsize, Ordering};
8989

9090
use crate::pool::{self, stack::Ptr, Node};

src/spsc.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
//!
33
//! Implementation based on <https://www.codeproject.com/Articles/43510/Lock-Free-Single-Producer-Single-Consumer-Circular>
44
//!
5-
//! NOTE: This module is not available on targets that do *not* support atomic loads, e.g. RISC-V
6-
//! cores w/o the A (Atomic) extension
5+
//! NOTE: This module is not available on targets that do *not* support atomic loads and are not
6+
//! supported by [`atomic_polyfill`]. (e.g., MSP430).
77
//!
88
//! # Examples
99
//!
@@ -84,13 +84,12 @@
8484
//! - The numbers reported correspond to the successful path (i.e. `Some` is returned by `dequeue`
8585
//! and `Ok` is returned by `enqueue`).
8686
87-
use core::{
88-
cell::UnsafeCell,
89-
fmt, hash,
90-
mem::MaybeUninit,
91-
ptr,
92-
sync::atomic::{AtomicUsize, Ordering},
93-
};
87+
use core::{cell::UnsafeCell, fmt, hash, mem::MaybeUninit, ptr};
88+
89+
#[cfg(full_atomic_polyfill)]
90+
use atomic_polyfill::{AtomicUsize, Ordering};
91+
#[cfg(not(full_atomic_polyfill))]
92+
use core::sync::atomic::{AtomicUsize, Ordering};
9493

9594
/// A statically allocated single producer single consumer queue with a capacity of `N - 1` elements
9695
///

0 commit comments

Comments
 (0)