Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ jobs:
run: >
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --features="alloc,
defmt,
mpmc_large,
portable-atomic-critical-section,
serde,
ufmt,
Expand Down Expand Up @@ -97,7 +96,6 @@ jobs:
cargo test --features="
alloc,
defmt,
mpmc_large,
portable-atomic-critical-section,
serde,
ufmt,
Expand Down Expand Up @@ -200,7 +198,6 @@ jobs:
fi

cargo check --target="${target}" --features="defmt"
cargo check --target="${target}" --features="mpmc_large"
cargo check --target="${target}" --features="portable-atomic-critical-section"
cargo check --target="${target}" --features="serde"
cargo check --target="${target}" --features="ufmt"
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed unsoundness in `IndexMap:insert`.
- Limited max size of `IndexMap` to u16::MAX + 1.
The implementation for sizes higher than u16::MAX were unsound anyway.
- Removed `mpmc_large` feature. Targets that need `mpmc` must either support `target_has_atomic =
"ptr"` or use `portable-atomic`.
- Added `LenT` type parameter to `Queue` and `QueueView` with default value `u8`
- Added `wrapping_add` and `signed_wrapping_cmp` to `LenType` as provided methods
- Added `LenType::Atomic` and `LenType::Signed` associated types

## [v0.9.3] 2025-04-15

Expand Down
4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ zeroize = ["dep:zeroize"]
# Implement `embedded_io::Write`
"embedded-io-v0.7" = ["dep:embedded-io"]

# Enable larger MPMC sizes.
mpmc_large = []

# Implement some alloc Vec interoperability
alloc = []

Expand Down Expand Up @@ -86,7 +83,6 @@ features = [
"ufmt",
"serde",
"defmt",
"mpmc_large",
"portable-atomic-critical-section",
"alloc",
]
Expand Down
10 changes: 10 additions & 0 deletions cfail/ui/mpmc_capacity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use core::mem::ManuallyDrop;
use heapless::mpmc::Queue;

const _: () = {
#[allow(deprecated)]
// 256 > u8::MAX
let _ = ManuallyDrop::new(Queue::<u8, 256, u8>::new());
};

fn main() {}
15 changes: 15 additions & 0 deletions cfail/ui/mpmc_capacity.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0080]: evaluation panicked: assertion failed: N < LenT::MAX_USIZE
--> $HEAPLESS/src/mpmc.rs
|
| assert!(N < LenT::MAX_USIZE);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `heapless::mpmc::QueueInner::<u8, u8, heapless::storage::OwnedStorage<256>>::new::{constant#0}` failed here

note: erroneous constant encountered
--> $HEAPLESS/src/mpmc.rs
|
| / const {
| | assert!(N > 1);
| | assert!(N.is_power_of_two());
| | assert!(N < LenT::MAX_USIZE);
| | }
| |_________^
114 changes: 105 additions & 9 deletions src/len_type.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use core::{
fmt::{Debug, Display},
mem,
ops::{Add, AddAssign, Sub, SubAssign},
ops::{Add, AddAssign, BitAnd, Sub, SubAssign},
};

#[cfg(not(feature = "portable-atomic"))]
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU8, AtomicUsize, Ordering};
#[cfg(feature = "portable-atomic")]
use portable_atomic::{AtomicU16, AtomicU32, AtomicU8, AtomicUsize, Ordering};

#[allow(non_camel_case_types)]
pub enum TypeEnum {
u8,
Expand All @@ -24,9 +29,11 @@ pub trait Sealed:
+ PartialEq
+ Add<Output = Self>
+ AddAssign
+ BitAnd<Self, Output = Self>
+ Sub<Output = Self>
+ SubAssign
+ PartialOrd
+ Ord
+ TryFrom<usize, Error: Debug>
+ TryInto<usize, Error: Debug>
{
Expand All @@ -39,6 +46,11 @@ pub trait Sealed:
/// This type as an enum.
const TYPE: TypeEnum;

/// The corresponding atomic integer type.
type Atomic: Atomic<Self, Self::Atomic>;
/// The corresponding signed integer type.
type Signed;

/// The one value of the integer type.
///
/// It's a function instead of constant because we want to have implementation which panics for
Expand Down Expand Up @@ -66,24 +78,108 @@ pub trait Sealed:
Some(self.into_usize())
}
}

/// Wrapping (modular) addition. Computes `self + rhs`, wrapping around at `Self::MAX_USIZE`.
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
Self::from_usize(self.into_usize().wrapping_add(rhs.into_usize()) & Self::MAX_USIZE)
}

/// Compare `seq` and `expected_pos` as if they were signed integers, returning the result of
/// `(seq as Signed).wrapping_sub(expected_pos as Signed).cmp(&0)`.
#[inline]
fn signed_wrapping_cmp(seq: Self, expected_pos: Self) -> core::cmp::Ordering {
match seq.into_usize().wrapping_sub(expected_pos.into_usize()) {
0 => core::cmp::Ordering::Equal,
d if d > (Self::MAX_USIZE / 2) => core::cmp::Ordering::Less,
_ => core::cmp::Ordering::Greater,
}
}
}

// TODO consider replacing with stdlib version once generic_atomic lands in stable
// (https://github.com/rust-lang/rust/issues/130539)
pub trait Atomic<T, A> {
/// Loads a value from the atomic integer.
///
/// Behavior must be identical to the corresponding `load` implementation for the underlying
/// atomic type.
fn load(&self, order: Ordering) -> T;
/// Stores a value into the atomic integer.
///
/// Behavior must be identical to the corresponding `store` implementation for the underlying
/// atomic type.
fn store(&self, val: T, order: Ordering);
/// Stores a value into the atomic integer if the current value is the same as the current
/// value.
///
/// Behavior must be identical to the corresponding `compare_exchange_weak` implementation for
/// the underlying atomic type.
#[cfg(any(target_has_atomic = "ptr", feature = "portable-atomic"))]
fn compare_exchange_weak(
&self,
current: T,
new: T,
success: Ordering,
failure: Ordering,
) -> Result<T, T>;
}

macro_rules! impl_atomic {
($($(#[$meta:meta])* ($T:ty, $A:tt)),*) => {$(
impl Atomic<$T, $A> for $A {
fn load(&self, order: Ordering) -> $T {
self.load(order)
}
fn store(&self, val: $T, order: Ordering) {
self.store(val, order)
}
fn compare_exchange_weak(&self, current: $T, new: $T, success: Ordering, failure: Ordering) -> Result<$T, $T> {
self.compare_exchange_weak(current, new, success, failure)
}
}
)*}
}

/// Converts a `usize` into the atomic type associated with the given `LenType`.
pub const fn new_atomic_lentype<L: LenType>(val: usize) -> L::Atomic {
unsafe {
match L::TYPE {
TypeEnum::u8 => mem::transmute_copy(&AtomicU8::new(val as u8)),
TypeEnum::u16 => mem::transmute_copy(&AtomicU16::new(val as u16)),
TypeEnum::u32 => mem::transmute_copy(&AtomicU32::new(val as u32)),
TypeEnum::usize => mem::transmute_copy(&AtomicUsize::new(val)),
}
}
}

impl_atomic!(
(u8, AtomicU8),
(u16, AtomicU16),
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
(u32, AtomicU32),
(usize, AtomicUsize)
);

macro_rules! impl_lentype {
($($(#[$meta:meta])* $LenT:ident),*) => {$(
($($(#[$meta:meta])* ($ULenT:tt, $SLenT:ty, $AULenT:ty)),*) => {$(
$(#[$meta])*
impl Sealed for $LenT {
impl Sealed for $ULenT {
const ZERO: Self = 0;
const MAX: Self = Self::MAX;
const MAX_USIZE: usize = Self::MAX as _;
const TYPE: TypeEnum = TypeEnum::$LenT;
const TYPE: TypeEnum = TypeEnum::$ULenT;

type Atomic = $AULenT;
type Signed = $SLenT;

fn one() -> Self {
1
}
}

$(#[$meta])*
impl LenType for $LenT {}
impl LenType for $ULenT {}
)*}
}

Expand All @@ -102,11 +198,11 @@ pub trait LenType: Sealed + Zeroize {}
pub trait LenType: Sealed {}

impl_lentype!(
u8,
u16,
(u8, i8, AtomicU8),
(u16, i16, AtomicU16),
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
u32,
usize
(u32, i32, AtomicU32),
(usize, isize, AtomicUsize)
);

pub const fn check_capacity_fits<LenT: LenType, const N: usize>() {
Expand Down
8 changes: 1 addition & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,7 @@ pub mod binary_heap;
mod bytes;
#[cfg(feature = "defmt")]
mod defmt;
#[cfg(any(
// assume we have all atomics available if we're using portable-atomic
feature = "portable-atomic",
// target has native atomic CAS (mpmc_large requires usize, otherwise just u8)
all(feature = "mpmc_large", target_has_atomic = "ptr"),
all(not(feature = "mpmc_large"), target_has_atomic = "8")
))]
#[cfg(any(feature = "portable-atomic", target_has_atomic = "ptr",))]
pub mod mpmc;
#[cfg(any(
arm_llsc,
Expand Down
Loading
Loading