From f58e509664e43605228f7abf9a2dc64a09e46b51 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 30 Oct 2023 23:46:49 +0100 Subject: [PATCH] Simplify atomic availability detection. - `cfg(target_has_atomic)` is stable now, use that. - Hardcode in `build.rs` the list of targets with load/store but no CAS, since `cfg(target_has_atomic_load_store)` is not stable yet. - Do not try to autodetect whether `portable-atomic` is needed or not, just let the user control it directly. If the user doesn't explicitly enable `portable-atomic` and native atomics are unavailable, the features requiring it will be missing. --- .github/workflows/build.yml | 22 +++------- CHANGELOG.md | 40 +++++++++-------- Cargo.toml | 39 ++++++++--------- build.rs | 87 ++++++------------------------------- no-atomics.sh | 14 ------ no-cas.sh | 14 ------ src/lib.rs | 24 +++++++--- src/mpmc.rs | 4 +- src/spsc.rs | 4 +- 9 files changed, 83 insertions(+), 165 deletions(-) delete mode 100644 no-atomics.sh delete mode 100644 no-cas.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 143cf350ff..4749e189a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,13 +80,6 @@ jobs: - thumbv7m-none-eabi - thumbv8m.base-none-eabi - thumbv8m.main-none-eabi - toolchain: - - stable - - nightly - features: - - "" - - "cas,portable-atomic/critical-section" - - "serde" steps: - name: Checkout uses: actions/checkout@v4 @@ -113,14 +106,17 @@ jobs: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }} ${{ runner.OS }}-build- - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + - name: Install Rust with target (${{ matrix.target }}) uses: dtolnay/rust-toolchain@master with: - toolchain: ${{ matrix.toolchain }} + toolchain: stable targets: ${{ matrix.target }} - name: cargo check - run: cargo check --target=${{ matrix.target }} --no-default-features --features=${{ matrix.features }} + run: | + cargo check --target=${{ matrix.target }} + cargo check --target=${{ matrix.target }} --features="portable-atomic-critical-section" + cargo check --target=${{ matrix.target }} --features="ufmt serde defmt-03 mpmc_large" doc: name: doc @@ -130,10 +126,6 @@ jobs: target: - x86_64-unknown-linux-gnu - thumbv7m-none-eabi - features: - - "" - - "cas" - - "serde" steps: - name: Checkout uses: actions/checkout@v4 @@ -166,7 +158,7 @@ jobs: targets: ${{ matrix.target }} - name: cargo doc - run: cargo doc --target=${{ matrix.target }} --no-default-features --features=${{ matrix.features }} + run: cargo doc --target=${{ matrix.target }} --features="ufmt serde defmt-03 mpmc_large portable-atomic-critical-section" # Run cpass tests testcpass: diff --git a/CHANGELOG.md b/CHANGELOG.md index 32753b771c..ab35e47f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [breaking-change] this crate now uses `portable-atomic` v1.0 instead of `atomic-polyfill` for emulating CAS instructions on targets where they're not natively available. - [breaking-change] `From<&str>` for `String` was replaced with `TryFrom<&str>` because the `From` trait must not fail. +- [breaking-change] Renamed Cargo features + - `defmt-impl` is now `defmt-03` + - `ufmt-impl` is now `ufmt` + - `cas` is removed, atomic polyfilling is now opt-in via the `portable-atomic` feature. ### Fixed @@ -91,31 +95,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -* Added support for AVR architecture. -* Add `entry` API to `IndexMap` -* Implement `IntoIterator` trait for `Indexmap` -* Implement `FromIterator` for `String` -* Add `first` and `last` methods to `IndexMap` and `IndexSet` -* Add `pop_{front_back}_unchecked` methods to `Deque` +- Added support for AVR architecture. +- Add `entry` API to `IndexMap` +- Implement `IntoIterator` trait for `Indexmap` +- Implement `FromIterator` for `String` +- Add `first` and `last` methods to `IndexMap` and `IndexSet` +- Add `pop_{front_back}_unchecked` methods to `Deque` ### Changed -* Optimize the codegen of `Vec::clone` -* `riscv32i` and `riscv32imc` targets unconditionally (e.g. `build --no-default-features`) depends on `atomic-polyfill` +- Optimize the codegen of `Vec::clone` +- `riscv32i` and `riscv32imc` targets unconditionally (e.g. `build --no-default-features`) depends on `atomic-polyfill` ### Fixed -* Inserting an item that replaces an already present item will no longer -fail with an error +- Inserting an item that replaces an already present item will no longer + fail with an error ## [v0.7.11] - 2022-05-09 ### Fixed -* Fixed `pool` example in docstring. -* Fixed undefined behavior in `Vec::truncate()`, `Vec::swap_remove_unchecked()`, +- Fixed `pool` example in docstring. +- Fixed undefined behavior in `Vec::truncate()`, `Vec::swap_remove_unchecked()`, and `Hole::move_to()` (internal to the binary heap implementation). -* Fixed `BinaryHeap` elements are being dropped twice +- Fixed `BinaryHeap` elements are being dropped twice ## [v0.7.10] - 2022-01-21 @@ -295,8 +299,8 @@ fail with an error ### Added - opt-out `cas` feature to disable parts of the API that use CAS instructions. -Useful if using a custom (i.e. not built-in) rustc target that does not have CAS -instructions. + Useful if using a custom (i.e. not built-in) rustc target that does not have CAS + instructions. - singleton `Pool` support on ARMv7-A devices @@ -315,7 +319,7 @@ instructions. - `Pool` now implements the `Sync` trait when targeting ARMv7-R. - Most data structures can now be constructed in "const context" (e.g. `static - [mut]` variables) using a newtype in `heapless::i`. +[mut]` variables) using a newtype in `heapless::i`. - `Pool` has gained a `grow_exact` method to more efficiently use statically allocated memory. @@ -360,7 +364,7 @@ instructions. ### Added - Added a memory pool that's lock-free and interrupt-safe on the ARMv7-M -architecture. + architecture. - `IndexMap` have gained `Eq` and `PartialEq` implementations. @@ -548,7 +552,7 @@ architecture. - [breaking-change] The error type of all operations that may fail has changed from `()` to `BufferFullError`. -- Both `RingBuffer` and `Vec` now support arrays of *any* size for their backup storage. +- Both `RingBuffer` and `Vec` now support arrays of _any_ size for their backup storage. ## [v0.1.0] - 2017-04-27 diff --git a/Cargo.toml b/Cargo.toml index 687f161dd4..7a4a222758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,35 +15,32 @@ repository = "https://github.com/rust-embedded/heapless" version = "0.8.0" [features] -default = ["cas"] -cas = ["portable-atomic"] -ufmt-impl = ["ufmt-write"] -# only for tests -__trybuild = [] -# Enable larger MPMC sizes. -mpmc_large = [] -# This flag has no version guarantee, the `defmt` dependency can be updated in a patch release -defmt-impl = ["defmt"] +# Enable polyfilling of atomics via `portable-atomic`. +# `portable-atomic` polyfills some functionality by default, but to get full atomics you must +# enable one of its features to tell it how to do it. See `portable-atomic` documentation for details. +portable-atomic = ["dep:portable-atomic"] -[target.thumbv6m-none-eabi.dependencies] -portable-atomic = { version = "1.0", optional = true } +# Enable polyfilling of atomics via portable-atomic, using critical section for locking +portable-atomic-critical-section = ["dep:portable-atomic", "portable-atomic?/critical-section"] -[target.riscv32i-unknown-none-elf.dependencies] -portable-atomic = { version = "1.0" } +# Enable polyfilling of atomics via portable-atomic, using disabling interrupts for locking. +# WARNING: this is only sound for single-core bare-metal privileged-mode targets! +portable-atomic-unsafe-assume-single-core = ["dep:portable-atomic", "portable-atomic?/unsafe-assume-single-core"] -[target.riscv32imc-unknown-none-elf.dependencies] -portable-atomic = { version = "1.0" } +# implement serde traits. +serde = ["dep:serde"] -[target.msp430-none-elf.dependencies] -portable-atomic = { version = "1.0" } +# implement ufmt traits. +ufmt = ["dep:ufmt-write"] -[target.xtensa-esp32s2-none-elf.dependencies] -portable-atomic = { version = "1.0", optional = true } +# Implement defmt::Format from defmt v0.3 +defmt-03 = ["dep:defmt"] -[target.'cfg(target_arch = "avr")'.dependencies] -portable-atomic = { version = "1.0", optional = true } +# Enable larger MPMC sizes. +mpmc_large = [] [dependencies] +portable-atomic = { version = "1.0", optional = true } hash32 = "0.3.0" serde = { version = "1", optional = true, default-features = false } stable_deref_trait = { version = "1", default-features = false } diff --git a/build.rs b/build.rs index 1c9892e081..1373281224 100644 --- a/build.rs +++ b/build.rs @@ -11,82 +11,21 @@ use std::{ fn main() -> Result<(), Box> { let target = env::var("TARGET")?; - if target.starts_with("thumbv6m-") { - println!("cargo:rustc-cfg=armv6m"); - } else if target.starts_with("thumbv7m-") { - println!("cargo:rustc-cfg=armv7m"); - } else if target.starts_with("thumbv7em-") { - println!("cargo:rustc-cfg=armv7m"); - } else if target.starts_with("armv7r-") | target.starts_with("armebv7r-") { - println!("cargo:rustc-cfg=armv7r"); - } else if target.starts_with("thumbv8m.base") { - println!("cargo:rustc-cfg=armv8m_base"); - } else if target.starts_with("thumbv8m.main") { - println!("cargo:rustc-cfg=armv8m_main"); - } else if target.starts_with("armv7-") | target.starts_with("armv7a-") { - println!("cargo:rustc-cfg=armv7a"); - } - - let is_avr = env::var("CARGO_CFG_TARGET_ARCH").as_deref() == Ok("avr"); - - // Set some cfg's depending on the target. - // - has_atomics: atomic load/store is available (either natively or through portable-atomic) - // - has_cas: atomic CAS is available (either natively or through portable-atomic) - // - use_portable_atomic: Use portable-atomic for all atomics (load/store and CAS). - // - use_portable_atomic_cas: Use portable-atomic for CAS atomic operations. Load/store can keep using core::sync:atomic. - - // built-in targets with no atomic / CAS support as of nightly-2022-01-13 - // AND not supported by the portable-atomic crate - // see the `no-atomics.sh` / `no-cas.sh` script sitting next to this file - if is_avr { - // lacks cas - } else { - match &target[..] { - "avr-unknown-gnu-atmega328" - | "bpfeb-unknown-none" - | "bpfel-unknown-none" - // | "msp430-none-elf" // supported by portable-atomic - // | "riscv32i-unknown-none-elf" // supported by portable-atomic - // | "riscv32imc-unknown-none-elf" // supported by portable-atomic - // | "thumbv4t-none-eabi" // supported by portable-atomic - // | "thumbv6m-none-eabi" // supported by portable-atomic - => {} - - _ => { - println!("cargo:rustc-cfg=has_cas"); - } - } + // Manually list targets that have atomic load/store, but no CAS. + // Remove when `cfg(target_has_atomic_load_store)` is stable. + // last updated nightly-2023-10-28 + match &target[..] { + "armv4t-none-eabi" + | "armv5te-none-eabi" + | "avr-unknown-gnu-atmega328" + | "bpfeb-unknown-none" + | "bpfel-unknown-none" + | "thumbv4t-none-eabi" + | "thumbv5te-none-eabi" + | "thumbv6m-none-eabi" => println!("cargo:rustc-cfg=has_atomic_load_store"), + _ => {} }; - if is_avr { - // lacks atomics - } else { - println!("cargo:rustc-cfg=has_atomics"); - } - - // Let the code know if it should use portable-atomic or not, for either - // only CAS, or for all atomics. - if is_avr { - println!("cargo:rustc-cfg=use_portable_atomic"); - println!("cargo:rustc-cfg=use_portable_atomic_cas"); - } else { - match &target[..] { - "riscv32i-unknown-none-elf" - | "riscv32imc-unknown-none-elf" - | "xtensa-esp32s2-none-elf" - | "thumbv4t-none-eabi" - | "msp430-none-elf" => { - println!("cargo:rustc-cfg=use_portable_atomic"); - println!("cargo:rustc-cfg=use_portable_atomic_cas"); - } - - "thumbv6m-none-eabi" => { - println!("cargo:rustc-cfg=use_portable_atomic_cas"); - } - _ => {} - } - } - // AArch64 instruction set contains `clrex` but not `ldrex` or `strex`; the // probe will succeed when we already know to deny this target from LLSC. if !target.starts_with("aarch64") { diff --git a/no-atomics.sh b/no-atomics.sh deleted file mode 100644 index 697933eee8..0000000000 --- a/no-atomics.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -main() { - IFS=' -' - for t in $(rustc --print target-list); do - rustc +nightly --print cfg --target $t | grep 'target_has_atomic_load_store=' >/dev/null || echo $t - done - -} - -main diff --git a/no-cas.sh b/no-cas.sh deleted file mode 100644 index 9ec576fcf3..0000000000 --- a/no-cas.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -main() { - IFS=' -' - for t in $(rustc --print target-list); do - rustc +nightly --print cfg --target $t | grep 'target_has_atomic=' >/dev/null || echo $t - done - -} - -main diff --git a/src/lib.rs b/src/lib.rs index 6228233397..130120908d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,7 @@ //! //! The `heapless` crate provides the following optional Cargo features: //! -//! - `ufmt-impl`: Implement [`ufmt_write::uWrite`] for `String` and `Vec` +//! - `ufmt`: Implement [`ufmt_write::uWrite`] for `String` and `Vec` //! //! [`ufmt_write::uWrite`]: https://docs.rs/ufmt-write/ //! @@ -108,17 +108,31 @@ mod de; mod ser; pub mod binary_heap; -#[cfg(feature = "defmt-impl")] +#[cfg(feature = "defmt-03")] mod defmt; -#[cfg(all(has_cas, feature = "cas"))] +#[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") +))] pub mod mpmc; #[cfg(any(arm_llsc, target_arch = "x86"))] pub mod pool; pub mod sorted_linked_list; -#[cfg(has_atomics)] +#[cfg(any( + // assume we have all atomics available if we're using portable-atomic + feature = "portable-atomic", + // target has native atomic CAS. Note this is too restrictive, spsc requires load/store only, not CAS. + // This should be `cfg(target_has_atomic_load_store)`, but that's not stable yet. + target_has_atomic = "ptr", + // or the current target is in a list in build.rs of targets known to have load/store but no CAS. + has_atomic_load_store +))] pub mod spsc; -#[cfg(feature = "ufmt-impl")] +#[cfg(feature = "ufmt")] mod ufmt; mod sealed; diff --git a/src/mpmc.rs b/src/mpmc.rs index 53c515ea09..db38934b66 100644 --- a/src/mpmc.rs +++ b/src/mpmc.rs @@ -88,9 +88,9 @@ use core::{cell::UnsafeCell, mem::MaybeUninit}; -#[cfg(not(use_portable_atomic_cas))] +#[cfg(not(feature = "portable-atomic"))] use core::sync::atomic; -#[cfg(use_portable_atomic_cas)] +#[cfg(feature = "portable-atomic")] use portable_atomic as atomic; use atomic::Ordering; diff --git a/src/spsc.rs b/src/spsc.rs index 8cf4493df1..c8085720e7 100644 --- a/src/spsc.rs +++ b/src/spsc.rs @@ -96,9 +96,9 @@ use core::{cell::UnsafeCell, fmt, hash, mem::MaybeUninit, ptr}; -#[cfg(not(use_portable_atomic))] +#[cfg(not(feature = "portable-atomic"))] use core::sync::atomic; -#[cfg(use_portable_atomic)] +#[cfg(feature = "portable-atomic")] use portable_atomic as atomic; use atomic::{AtomicUsize, Ordering};