Skip to content

Commit

Permalink
no_std support, more minimal and less intrusive version (#93)
Browse files Browse the repository at this point in the history
* Support building for no_std using nightly Rust compiler

- Adds a new feature `experimental-thread-local` that enables building for no_std targets.
  If this feature is not enabled, the crate is identical as before,
  still allowing for compilation using Rust stable >= 1.38.0.

- The `experimental-thread-local` feature makes use of experimental features `thread_local`
  and `lazy_cell`, thus requiring a nightly Rust compiler.

- Support for `std::sync::RwLock` is dropped in no_std builds.
  • Loading branch information
Alexis211 authored Mar 3, 2024
1 parent 94f3789 commit 229c7ee
Show file tree
Hide file tree
Showing 17 changed files with 152 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Run cargo-tarpaulin
uses: actions-rs/[email protected]
with:
args: '--all-features --run-types Doctests,Tests'
args: '--features weak,internal-test-strategies,experimental-strategies --run-types Doctests,Tests'
timeout: 120

- name: Upload to codecov.io
Expand Down
38 changes: 30 additions & 8 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
RUST_VERSION: ${{ matrix.rust }}
OS: ${{ matrix.os }}
RUSTFLAGS: -D warnings
run: cargo test --all-features
run: cargo test --features weak,internal-test-strategies,experimental-strategies

big-tests:
name: Run the big ignored tests
Expand All @@ -68,7 +68,7 @@ jobs:
- name: Build & test
env:
RUSTFLAGS: -D warnings
run: cargo test --all-features --release -- --ignored
run: cargo test --features weak,internal-test-strategies,experimental-strategies --release -- --ignored

bits32:
name: 32bit tests
Expand All @@ -91,7 +91,7 @@ jobs:
- name: Build & test
env:
RUSTFLAGS: -D warnings
run: cargo test --all-features --target x86_64-unknown-linux-musl
run: cargo test --features weak,internal-test-strategies,experimental-strategies --target x86_64-unknown-linux-musl

rustfmt:
name: Check formatting
Expand Down Expand Up @@ -127,7 +127,7 @@ jobs:
uses: Swatinem/rust-cache@v2

- name: Check links
run: cargo rustdoc --all-features -- -D warnings
run: cargo rustdoc --features weak,internal-test-strategies,experimental-strategies -- -D warnings

clippy:
name: Clippy lints
Expand All @@ -148,7 +148,7 @@ jobs:
uses: Swatinem/rust-cache@v2

- name: Run clippy linter
run: cargo clippy --all --all-features --tests -- -D clippy::all -D warnings
run: cargo clippy --all --features weak,internal-test-strategies,experimental-strategies --tests -- -D clippy::all -D warnings

bench:
name: Verify benchmarks compile
Expand All @@ -168,7 +168,7 @@ jobs:
uses: Swatinem/rust-cache@v2

- name: Run clippy linter
run: cargo test --all --release --benches --all-features
run: cargo test --all --release --benches --features weak,internal-test-strategies,experimental-strategies

semi-ancient:
name: Check it compiles on old Rust (1.45.0)
Expand All @@ -188,7 +188,7 @@ jobs:
uses: Swatinem/rust-cache@v2

- name: Run check
run: rm Cargo.lock && cargo check --all-features
run: rm Cargo.lock && cargo check --features weak,internal-test-strategies,experimental-strategies

ancient:
name: Check it compiles on old Rust (1.31.0)
Expand All @@ -210,6 +210,28 @@ jobs:
- name: Run check
run: rm Cargo.lock && cargo check

experimental_thread_local:
name: Test with experimental-thread-local
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
default: true

- name: Restore cache
uses: Swatinem/rust-cache@v1

- name: Build & test
env:
RUSTFLAGS: -D warnings
run: cargo test --no-default-features --features weak,experimental-thread-local

miri:
name: Miri checks
runs-on: ubuntu-latest
Expand All @@ -232,7 +254,7 @@ jobs:
env:
PROPTEST_CASES: "10"
MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-permissive-provenance"
run: cargo miri test --all-features
run: cargo miri test --features weak,internal-test-strategies,experimental-strategies

thread_sanitizer-MacOS:
name: Thread Sanitizer checks MacOS
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ weak = []
internal-test-strategies = []
# Possibly some strategies we are experimenting with. Currently empty. No stability guarantees are included about them.
experimental-strategies = []
# Use the nightly "thread_local" feature, to allow no_std builds.
experimental-thread-local = []

[dependencies]
serde = { version = "1", features = ["rc"], optional = true }
Expand Down
4 changes: 2 additions & 2 deletions ci-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ fi
# Allow some warnings on the very old compiler.
export RUSTFLAGS="-D warnings"

cargo test --release --all-features
cargo test --release --all-features -- --ignored
cargo test --release --features weak,internal-test-strategies,experimental-strategies
cargo test --release --features weak,internal-test-strategies,experimental-strategies -- --ignored
9 changes: 5 additions & 4 deletions src/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@
//! // Passing a constant that can't change. Useful mostly for testing purposes.
//! work_with_usize(Constant(42)).join().unwrap();
//! ```
use core::marker::PhantomData;
use core::ops::Deref;

use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::sync::Arc;

use super::ref_cnt::RefCnt;
use super::strategy::Strategy;
Expand Down
6 changes: 3 additions & 3 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//!
//! [Arc]: std::sync::Arc

use std::ops::Deref;
use std::sync::atomic::Ordering;
use core::ops::Deref;
use core::sync::atomic::Ordering;

use super::ref_cnt::RefCnt;
use super::strategy::Strategy;
Expand Down Expand Up @@ -272,7 +272,7 @@ where

#[cfg(test)]
mod tests {
use std::sync::Arc;
use alloc::sync::Arc;

use super::*;
use crate::{ArcSwap, ArcSwapOption};
Expand Down
6 changes: 3 additions & 3 deletions src/debt/fast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
//! before the change and before any cleanup of the old pointer happened (in which case we know the
//! writer will see our debt).

use std::cell::Cell;
use std::slice::Iter;
use std::sync::atomic::Ordering::*;
use core::cell::Cell;
use core::slice::Iter;
use core::sync::atomic::Ordering::*;

use super::Debt;

Expand Down
8 changes: 4 additions & 4 deletions src/debt/helping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@
//! writer and that change is the destruction ‒ by that time, the destroying thread has exclusive
//! ownership and therefore there can be no new readers.

use std::cell::Cell;
use std::ptr;
use std::sync::atomic::Ordering::*;
use std::sync::atomic::{AtomicPtr, AtomicUsize};
use core::cell::Cell;
use core::ptr;
use core::sync::atomic::Ordering::*;
use core::sync::atomic::{AtomicPtr, AtomicUsize};

use super::Debt;
use crate::RefCnt;
Expand Down
37 changes: 31 additions & 6 deletions src/debt/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@
//! at least as up to date value of the writers as when the cooldown started. That we if we see 0,
//! we know it must have happened since then.

use std::cell::Cell;
use std::ptr;
use std::slice::Iter;
use std::sync::atomic::Ordering::*;
use std::sync::atomic::{AtomicPtr, AtomicUsize};
use core::cell::Cell;
use core::ptr;
use core::slice::Iter;
use core::sync::atomic::Ordering::*;
use core::sync::atomic::{AtomicPtr, AtomicUsize};

#[cfg(feature = "experimental-thread-local")]
use core::cell::OnceCell;

use alloc::boxed::Box;

use super::fast::{Local as FastLocal, Slots as FastSlots};
use super::helping::{Local as HelpingLocal, Slots as HelpingSlots};
Expand Down Expand Up @@ -214,6 +219,7 @@ pub(crate) struct LocalNode {
}

impl LocalNode {
#[cfg(not(feature = "experimental-thread-local"))]
pub(crate) fn with<R, F: FnOnce(&LocalNode) -> R>(f: F) -> R {
let f = Cell::new(Some(f));
THREAD_HEAD
Expand Down Expand Up @@ -242,6 +248,19 @@ impl LocalNode {
})
}

#[cfg(feature = "experimental-thread-local")]
pub(crate) fn with<R, F: FnOnce(&LocalNode) -> R>(f: F) -> R {
let thread_head = THREAD_HEAD.get_or_init(|| LocalNode {
node: Cell::new(None),
fast: FastLocal::default(),
helping: HelpingLocal::default(),
});
if thread_head.node.get().is_none() {
thread_head.node.set(Some(Node::get()));
}
f(&thread_head)
}

/// Creates a new debt.
///
/// This stores the debt of the given pointer (untyped, casted into an usize) and returns a
Expand Down Expand Up @@ -313,6 +332,7 @@ impl Drop for LocalNode {
}
}

#[cfg(not(feature = "experimental-thread-local"))]
thread_local! {
/// A debt node assigned to this thread.
static THREAD_HEAD: LocalNode = LocalNode {
Expand All @@ -322,14 +342,19 @@ thread_local! {
};
}

#[cfg(feature = "experimental-thread-local")]
#[thread_local]
/// A debt node assigned to this thread.
static THREAD_HEAD: OnceCell<LocalNode> = OnceCell::new();

#[cfg(test)]
mod tests {
use super::*;

impl Node {
fn is_empty(&self) -> bool {
self.fast_slots()
.chain(std::iter::once(self.helping_slot()))
.chain(core::iter::once(self.helping_slot()))
.all(|d| d.0.load(Relaxed) == Debt::NONE)
}

Expand Down
8 changes: 4 additions & 4 deletions src/debt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
//! Each node has some fast (but fallible) nodes and a fallback node, with different algorithms to
//! claim them (see the relevant submodules).

use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::*;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering::*;

pub(crate) use self::list::{LocalNode, Node};
use super::RefCnt;
Expand Down Expand Up @@ -96,7 +96,7 @@ impl Debt {

let all_slots = node
.fast_slots()
.chain(std::iter::once(node.helping_slot()));
.chain(core::iter::once(node.helping_slot()));
for slot in all_slots {
// Note: Release is enough even here. That makes sure the increment is
// visible to whoever might acquire on this slot and can't leak below this.
Expand All @@ -116,7 +116,7 @@ impl Debt {

#[cfg(test)]
mod tests {
use std::sync::Arc;
use alloc::sync::Arc;

/// Checks the assumption that arcs to ZSTs have different pointer values.
#[test]
Expand Down
13 changes: 13 additions & 0 deletions src/docs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
//! **are not** part of the API stability guarantees and they may be changed, renamed or removed at
//! any time.
//!
//! The `experimental-thread-local` feature can be used to build arc-swap for `no_std` targets, by
//! replacing occurences of [`std::thread_local!`] with the `#[thread_local]` directive. This
//! requires a nightly Rust compiler as it makes use of the experimental
//! [`thread_local`](https://doc.rust-lang.org/unstable-book/language-features/thread-local.html)
//! feature. Using this features, thread-local variables are compiled using LLVM built-ins, which
//! have [several underlying modes of
//! operation](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/tls-model.html). To add
//! support for thread-local variables on a platform that does not have OS or linker support, the
//! easiest way is to use `-Ztls-model=emulated` and to implement `__emutls_get_address` by hand,
//! as in [this
//! example](https://opensource.apple.com/source/clang/clang-800.0.38/src/projects/compiler-rt/lib/builtins/emutls.c.auto.html)
//! from Clang.
//!
//! # Minimal compiler version
//!
//! The `1` versions will compile on all compilers supporting the 2018 edition. Note that this
Expand Down
Loading

1 comment on commit 229c7ee

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Track benchmarks

Benchmark suite Current: 229c7ee Previous: 94f3789 Ratio
uncontended/load 6 ns/iter (± 0) 6 ns/iter (± 0) 1
uncontended/load_full 9 ns/iter (± 0) 9 ns/iter (± 0) 1
uncontended/load_many 14 ns/iter (± 0) 14 ns/iter (± 0) 1
uncontended/store 53 ns/iter (± 1) 53 ns/iter (± 0) 1
uncontended/cache 0 ns/iter (± 0) 0 ns/iter (± 0) 1
concurrent_loads/load 6 ns/iter (± 0) 12 ns/iter (± 0) 0.50
concurrent_loads/load_full 12 ns/iter (± 0) 12 ns/iter (± 0) 1
concurrent_loads/load_many 14 ns/iter (± 0) 23 ns/iter (± 0) 0.61
concurrent_loads/store 595 ns/iter (± 60) 529 ns/iter (± 1) 1.12
concurrent_loads/cache 1 ns/iter (± 0) 1 ns/iter (± 0) 1
concurrent_store/load 51 ns/iter (± 1) 49 ns/iter (± 2) 1.04
concurrent_store/load_full 66 ns/iter (± 3) 72 ns/iter (± 3) 0.92
concurrent_store/load_many 86 ns/iter (± 0) 88 ns/iter (± 0) 0.98
concurrent_store/store 615 ns/iter (± 4) 607 ns/iter (± 4) 1.01
concurrent_store/cache 1 ns/iter (± 0) 1 ns/iter (± 0) 1
utilities/access-map 6 ns/iter (± 0) 6 ns/iter (± 0) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.