diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b3c9409 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,22 @@ +# Working agreement — congee-wt + +The operating contract for **any** coding agent working in this repository. Codex, Cursor +and Gemini CLI read `AGENTS.md` natively; Claude Code loads it through the `@AGENTS.md` +import in [`CLAUDE.md`](CLAUDE.md). Never fork these rules into a per-vendor file. + +**Rust** concurrent index, a WorkTable dependency. + +## Invariants (do not break these) + +- **No Python.** Not a script, not `python3 -c`, not a heredoc. Reaching for it is the + tell that a step is being solved by parsing when the tool that owns the answer could + just be asked. Do not swap it for another parser either, and do not assume `jq` is + present: it does not ship with macOS. A fixed-shape field is one `sed -nE` line; + anything needing real parsing belongs in this repo's own language, where it can be + tested. If a task seems to need Python, the approach is wrong. + +- **The Python still here is on its way out, not a precedent.** It predates the rule and + is deleted on sight when the owner finds it, so do not add to it, do not import from it, + and do not copy its approach into something new. If you are already changing one of + these, port it out rather than editing it in place: + - `.github/workflows/shumai_converter.py` (benchmark output conversion) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..331af7a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +@AGENTS.md + +# Claude Code notes — congee-wt + +The import above is binding: [`AGENTS.md`](AGENTS.md) is the working agreement for this +repository, and every Claude Code session loads it automatically. Do not copy rules here, +one source of truth, no drift. Only genuinely Claude-specific wiring belongs below. diff --git a/Cargo.toml b/Cargo.toml index 31afd9f..ade4cbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "congee-wt" -version = "0.4.4" +version = "0.4.5" edition = "2024" description = "A Rust implementation of ART-OLC concurrent adaptive radix tree." keywords = ["ART", "adaptive-radix-tree", "concurrent"] @@ -19,8 +19,8 @@ path = "src/lib.rs" members = ["fuzz"] [dependencies] -ps-reclaim = { version = "^0.1" } -serde = { version = "1.0.219", features = ["derive"], optional = true } +ps-reclaim = { version = "0.1.4", default-features = false, features = ["libc", "spin"] } +serde = { version = "1.0.219", default-features = false, features = ["derive", "alloc"], optional = true } [dev-dependencies] tracing-subscriber = "0.3" @@ -56,6 +56,11 @@ path = "bench/compact_set.rs" harness = false [features] +default = ["std"] +# Off, this crate does not link `std`. What it costs: `debug_print`, which needs +# somewhere to print to, and `thread::yield_now` in the spin backoff, which +# falls back to a spin because there is no scheduler to yield to. +std = ["ps-reclaim/std", "serde?/std"] flamegraph = ["shumai/flamegraph"] perf = ["shumai/perf"] stats = ["serde"] diff --git a/src/congee.rs b/src/congee.rs index 90a6a8b..fd810be 100644 --- a/src/congee.rs +++ b/src/congee.rs @@ -1,4 +1,8 @@ -use std::{marker::PhantomData, ptr::with_exposed_provenance, sync::Arc}; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::marker::PhantomData; +use core::ptr::with_exposed_provenance; use crate::{CongeeInner, DefaultAllocator, epoch, error::OOMError}; diff --git a/src/congee_compact_set.rs b/src/congee_compact_set.rs index 7be1c82..20c8558 100644 --- a/src/congee_compact_set.rs +++ b/src/congee_compact_set.rs @@ -63,7 +63,7 @@ //! //! The structure is created by converting from a `CongeeSet` using `to_compact_set()`. -use std::marker::PhantomData; +use core::marker::PhantomData; pub struct NodeType(pub u8); @@ -226,8 +226,8 @@ impl CompactSetStats { } } -impl std::fmt::Display for CompactSetStats { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for CompactSetStats { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { writeln!( f, "╭─────────────────────────────────────────────────────────────────╮" @@ -421,7 +421,7 @@ where data: &'a [u8], _phantom: PhantomData, #[cfg(feature = "access-stats")] - access_stats: std::sync::Arc>, + access_stats: alloc::sync::Arc>, } #[cfg(feature = "access-stats")] @@ -470,7 +470,7 @@ where data, _phantom: PhantomData, #[cfg(feature = "access-stats")] - access_stats: std::sync::Arc::new(std::sync::Mutex::new(AccessStats::default())), + access_stats: alloc::sync::Arc::new(std::sync::Mutex::new(AccessStats::default())), } } @@ -512,7 +512,7 @@ where node_type: u8, ) -> Option { unsafe { - use std::arch::x86_64::{ + use core::arch::x86_64::{ _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_set1_epi8, }; @@ -670,7 +670,7 @@ where // Read bitmap let bitmap = unsafe { - std::slice::from_raw_parts(self.data.as_ptr().add(bitmap_start), 32) + core::slice::from_raw_parts(self.data.as_ptr().add(bitmap_start), 32) }; let bitmap_array = unsafe { *(bitmap.as_ptr() as *const [u8; 32]) }; @@ -832,6 +832,8 @@ where } /// Print the compact set in a human readable format + /// Needs somewhere to print to, which a `no_std` build does not have. + #[cfg(feature = "std")] pub fn debug_print(&self) { println!("\n=== CongeeCompactSet Debug Structure ==="); println!("Total nodes: {}", self.node_count()); diff --git a/src/congee_inner.rs b/src/congee_inner.rs index 573ba02..dcfa158 100644 --- a/src/congee_inner.rs +++ b/src/congee_inner.rs @@ -1,4 +1,7 @@ -use std::{marker::PhantomData, ptr::NonNull, sync::Arc}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::marker::PhantomData; +use core::ptr::NonNull; use crate::{ Allocator, DefaultAllocator, cast_ptr, @@ -9,10 +12,10 @@ use crate::{ range_scan::RangeScan, utils::{Backoff, KeyTracker}, }; +#[cfg(not(all(feature = "shuttle", test)))] +use core::sync::atomic::AtomicPtr; #[cfg(all(feature = "shuttle", test))] use shuttle::sync::atomic::AtomicPtr; -#[cfg(not(all(feature = "shuttle", test)))] -use std::sync::atomic::AtomicPtr; /// Raw interface to the ART tree. /// The `Art` is a wrapper around the `RawArt` that provides a safe interface. @@ -112,7 +115,7 @@ impl CongeeInner { #[inline] pub(crate) fn load_root(&self) -> NonNull { - let root_ptr = self.root.load(std::sync::atomic::Ordering::Relaxed); + let root_ptr = self.root.load(core::sync::atomic::Ordering::Relaxed); // SAFETY: The root pointer is always non-null after initialization. unsafe { NonNull::new_unchecked(root_ptr) } } @@ -612,7 +615,7 @@ impl CongeeInner { let allocator = self.allocator.clone(); guard.defer(move || unsafe { let ptr = NonNull::from(write_n.as_mut()); - std::mem::forget(write_n); + core::mem::forget(write_n); BaseNode::drop_node(ptr, allocator); }); } else { @@ -661,7 +664,7 @@ impl CongeeInner { pub(crate) fn to_compact_set(&self) -> Vec { use crate::congee_compact_set::NodeType as CompactNodeType; - use std::collections::VecDeque; + use alloc::collections::VecDeque; let _pin = self.pin(); diff --git a/src/congee_raw.rs b/src/congee_raw.rs index 10fdfc4..658e309 100644 --- a/src/congee_raw.rs +++ b/src/congee_raw.rs @@ -1,4 +1,6 @@ -use std::{marker::PhantomData, sync::Arc}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::marker::PhantomData; use crate::{Allocator, CongeeInner, DefaultAllocator, epoch, error::OOMError, stats}; @@ -233,7 +235,7 @@ where let start: [u8; 8] = start.to_be_bytes(); let end: [u8; 8] = end.to_be_bytes(); let result_ref = unsafe { - std::slice::from_raw_parts_mut( + core::slice::from_raw_parts_mut( result.as_mut_ptr() as *mut ([u8; 8], usize), result.len(), ) diff --git a/src/congee_set.rs b/src/congee_set.rs index 04e4159..f3ba26a 100644 --- a/src/congee_set.rs +++ b/src/congee_set.rs @@ -1,4 +1,7 @@ -use std::{marker::PhantomData, sync::Arc}; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::marker::PhantomData; use crate::{Allocator, CongeeInner, DefaultAllocator, epoch, error::OOMError, stats}; diff --git a/src/error.rs b/src/error.rs index be20fef..8f4308e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ -use std::error::Error; -use std::fmt::{self, Debug, Display, Formatter}; +use core::error::Error; +use core::fmt::{self, Debug, Display, Formatter}; #[derive(Debug)] pub(crate) enum ArtError { diff --git a/src/lib.rs b/src/lib.rs index d586e3f..06c77e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,16 @@ +#![cfg_attr(not(any(test, feature = "std")), no_std)] #![doc = include_str!("../README.md")] #![allow(clippy::comparison_chain)] #![allow(clippy::len_without_is_empty)] #![cfg_attr(docsrs, feature(doc_cfg))] +// The crate does not link `std` unless asked. Tests always do: they spawn +// threads and time things, which is what a concurrency test is made of. +#[cfg(any(test, feature = "std"))] +extern crate std; + +extern crate alloc; + mod congee; pub mod congee_compact_set; mod congee_inner; @@ -22,7 +30,7 @@ mod tests; /// Types needed to safely access shared data concurrently. pub mod epoch { - use std::sync::atomic::{AtomicUsize, Ordering}; + use core::sync::atomic::{AtomicUsize, Ordering}; const RECLAIM_BATCH: usize = 256; @@ -87,7 +95,7 @@ pub mod epoch { } pub(crate) fn belongs_to(&self, reclaimer: &Reclaimer) -> bool { - std::ptr::eq(self.reclaimer, reclaimer) + core::ptr::eq(self.reclaimer, reclaimer) } } @@ -102,8 +110,8 @@ pub mod epoch { #[cfg(test)] mod tests { use super::{RECLAIM_BATCH, Reclaimer, pin_in}; + use core::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; - use std::sync::atomic::{AtomicUsize, Ordering}; #[test] fn automatic_collection_is_bounded_to_one_batch() { diff --git a/src/lock.rs b/src/lock.rs index e423e52..82f602c 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -1,10 +1,12 @@ -use std::{marker::PhantomData, ptr::NonNull, sync::atomic::Ordering}; +use core::marker::PhantomData; +use core::ptr::NonNull; +use core::sync::atomic::Ordering; #[cfg(all(feature = "shuttle", test))] use shuttle::sync::atomic::fence; #[cfg(not(all(feature = "shuttle", test)))] -use std::sync::atomic::fence; +use core::sync::atomic::fence; use crate::{ error::ArtError, diff --git a/src/nodes/base_node.rs b/src/nodes/base_node.rs index a402ee8..80ed58f 100644 --- a/src/nodes/base_node.rs +++ b/src/nodes/base_node.rs @@ -1,8 +1,9 @@ +use alloc::vec::Vec; +use core::ptr::NonNull; +#[cfg(not(all(feature = "shuttle", test)))] +use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering}; #[cfg(all(feature = "shuttle", test))] use shuttle::sync::atomic::{AtomicPtr, AtomicU32, Ordering}; -use std::ptr::NonNull; -#[cfg(not(all(feature = "shuttle", test)))] -use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering}; use crate::{ Allocator, @@ -49,26 +50,26 @@ impl NodeType { } } - pub(crate) fn node_layout(&self) -> std::alloc::Layout { + pub(crate) fn node_layout(&self) -> core::alloc::Layout { match *self { - NodeType::N4 => std::alloc::Layout::from_size_align( - std::mem::size_of::(), - std::mem::align_of::(), + NodeType::N4 => core::alloc::Layout::from_size_align( + core::mem::size_of::(), + core::mem::align_of::(), ) .unwrap(), - NodeType::N16 => std::alloc::Layout::from_size_align( - std::mem::size_of::(), - std::mem::align_of::(), + NodeType::N16 => core::alloc::Layout::from_size_align( + core::mem::size_of::(), + core::mem::align_of::(), ) .unwrap(), - NodeType::N48 => std::alloc::Layout::from_size_align( - std::mem::size_of::(), - std::mem::align_of::(), + NodeType::N48 => core::alloc::Layout::from_size_align( + core::mem::size_of::(), + core::mem::align_of::(), ) .unwrap(), - NodeType::N256 => std::alloc::Layout::from_size_align( - std::mem::size_of::(), - std::mem::align_of::(), + NodeType::N256 => core::alloc::Layout::from_size_align( + core::mem::size_of::(), + core::mem::align_of::(), ) .unwrap(), } @@ -148,10 +149,10 @@ impl NodeMeta { #[cfg(not(feature = "shuttle"))] mod layout_assertion { use super::*; - const _: () = assert!(std::mem::size_of::() == 12); - const _: () = assert!(std::mem::align_of::() == 2); - const _: () = assert!(std::mem::size_of::() == 16); - const _: () = assert!(std::mem::align_of::() == 4); + const _: () = assert!(core::mem::size_of::() == 12); + const _: () = assert!(core::mem::align_of::() == 2); + const _: () = assert!(core::mem::size_of::() == 16); + const _: () = assert!(core::mem::align_of::() == 4); } macro_rules! gen_method { @@ -251,7 +252,7 @@ impl BaseNode { let base_ptr = ptr.as_ptr() as *mut BaseNode; let node = BaseNode::new(N::get_type(), prefix); unsafe { - std::ptr::write(base_ptr, node); + core::ptr::write(base_ptr, node); if matches!(N::get_type(), NodeType::N48) { let mem = base_ptr as *mut Node48; @@ -288,7 +289,7 @@ impl BaseNode { /// Here we must get a clone of allocator because the drop_node might be called in epoch guard pub(crate) unsafe fn drop_node(node: NonNull, allocator: A) { let layout = unsafe { node.as_ref() }.get_type().node_layout(); - let ptr = std::ptr::NonNull::new(node.as_ptr() as *mut u8).unwrap(); + let ptr = core::ptr::NonNull::new(node.as_ptr() as *mut u8).unwrap(); unsafe { allocator.deallocate(ptr, layout); } @@ -422,7 +423,7 @@ impl BaseNode { write_n.mark_obsolete(); let delete_n = write_n.as_mut() as *mut CurT as usize; - std::mem::forget(write_n); + core::mem::forget(write_n); let allocator: A = allocator.clone(); guard.defer(move || unsafe { let delete_n = NonNull::new(delete_n as *mut BaseNode).unwrap(); diff --git a/src/nodes/node_16.rs b/src/nodes/node_16.rs index 32d2048..697c8c4 100644 --- a/src/nodes/node_16.rs +++ b/src/nodes/node_16.rs @@ -2,9 +2,10 @@ use super::{ NodePtr, base_node::{BaseNode, Node, NodeIter, NodeType}, }; +use alloc::vec::Vec; #[cfg(target_arch = "x86_64")] -use std::arch::x86_64::*; +use core::arch::x86_64::*; #[repr(C)] #[repr(align(8))] // Node 16 doesn't need to align to 64 bc it occupies 3 cache lines anyway @@ -15,10 +16,10 @@ pub(crate) struct Node16 { } #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::size_of::() == 160); +const _: () = assert!(core::mem::size_of::() == 160); #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::align_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 8); impl Node16 { fn get_insert_pos(&self, key: u8) -> usize { diff --git a/src/nodes/node_256.rs b/src/nodes/node_256.rs index 91cebeb..3043242 100644 --- a/src/nodes/node_256.rs +++ b/src/nodes/node_256.rs @@ -2,6 +2,7 @@ use super::{ base_node::{BaseNode, Node, NodeIter, NodeType}, node_ptr::NodePtr, }; +use alloc::vec::Vec; #[repr(C)] #[repr(align(8))] @@ -12,9 +13,9 @@ pub(crate) struct Node256 { } #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::size_of::() == 2096); +const _: () = assert!(core::mem::size_of::() == 2096); #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::align_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 8); impl Node256 { #[inline] diff --git a/src/nodes/node_4.rs b/src/nodes/node_4.rs index 227ea44..75a21bd 100644 --- a/src/nodes/node_4.rs +++ b/src/nodes/node_4.rs @@ -1,5 +1,6 @@ use super::NodePtr; use super::base_node::{BaseNode, Node, NodeIter, NodeType}; +use alloc::vec::Vec; #[repr(C)] #[repr(align(8))] @@ -10,10 +11,10 @@ pub(crate) struct Node4 { } #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::size_of::() == 56); +const _: () = assert!(core::mem::size_of::() == 56); #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::align_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 8); pub(crate) struct Node4Iter<'a> { start: u8, diff --git a/src/nodes/node_48.rs b/src/nodes/node_48.rs index ef33008..df86e40 100644 --- a/src/nodes/node_48.rs +++ b/src/nodes/node_48.rs @@ -1,4 +1,5 @@ use crate::cast_ptr; +use alloc::vec::Vec; use super::{ NodePtr, @@ -18,10 +19,10 @@ pub(crate) struct Node48 { } #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::size_of::() == 664); +const _: () = assert!(core::mem::size_of::() == 664); #[cfg(not(feature = "shuttle"))] -const _: () = assert!(std::mem::align_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 8); impl Node48 { pub(crate) fn init_empty(&mut self) { diff --git a/src/nodes/node_ptr.rs b/src/nodes/node_ptr.rs index 72eb535..ad2c8e2 100644 --- a/src/nodes/node_ptr.rs +++ b/src/nodes/node_ptr.rs @@ -1,4 +1,5 @@ -use std::{fmt::Debug, ptr::NonNull}; +use core::fmt::Debug; +use core::ptr::NonNull; use crate::{ Allocator, @@ -6,25 +7,25 @@ use crate::{ }; pub(crate) struct ChildIsPayload<'a> { - _marker: std::marker::PhantomData<&'a ()>, + _marker: core::marker::PhantomData<&'a ()>, } impl ChildIsPayload<'_> { pub(crate) fn new() -> Self { Self { - _marker: std::marker::PhantomData, + _marker: core::marker::PhantomData, } } } pub(crate) struct ChildIsSubNode<'a> { - _marker: std::marker::PhantomData<&'a ()>, + _marker: core::marker::PhantomData<&'a ()>, } impl ChildIsSubNode<'_> { pub(crate) fn new() -> Self { Self { - _marker: std::marker::PhantomData, + _marker: core::marker::PhantomData, } } } @@ -89,7 +90,7 @@ pub(crate) struct NodePtr { } impl Debug for NodePtr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { cast_ptr!(self => { Payload(val) => { write!(f, "Payload: {val:?}") @@ -159,13 +160,13 @@ impl<'a, N: Node, A: Allocator> AllocatedNode<'a, N, A> { pub(crate) fn into_note_ptr(self) -> NodePtr { let ptr = self.ptr; - std::mem::forget(self); - unsafe { NodePtr::from_node(std::mem::transmute::, NonNull>(ptr)) } + core::mem::forget(self); + unsafe { NodePtr::from_node(core::mem::transmute::, NonNull>(ptr)) } } pub(crate) fn into_non_null(self) -> NonNull { let ptr = self.ptr; - std::mem::forget(self); + core::mem::forget(self); ptr } } @@ -173,8 +174,8 @@ impl<'a, N: Node, A: Allocator> AllocatedNode<'a, N, A> { impl<'a, N: Node, A: Allocator> Drop for AllocatedNode<'a, N, A> { fn drop(&mut self) { unsafe { - std::ptr::drop_in_place(self.ptr.as_mut()); - let ptr = std::ptr::NonNull::new(self.ptr.as_ptr() as *mut u8).unwrap(); + core::ptr::drop_in_place(self.ptr.as_mut()); + let ptr = core::ptr::NonNull::new(self.ptr.as_ptr() as *mut u8).unwrap(); let layout = N::get_type().node_layout(); self.allocator.deallocate(ptr, layout); } diff --git a/src/range_scan.rs b/src/range_scan.rs index 835cf01..86de6a2 100644 --- a/src/range_scan.rs +++ b/src/range_scan.rs @@ -6,8 +6,8 @@ use crate::{ nodes::{BaseNode, NodePtr}, utils::KeyTracker, }; -use std::cmp; -use std::ptr::NonNull; +use core::cmp; +use core::ptr::NonNull; enum PrefixCheckEqualsResult { BothMatch, diff --git a/src/stats.rs b/src/stats.rs index 504da1a..4e7f232 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -1,4 +1,9 @@ -use std::{collections::HashMap, fmt::Display, ptr::NonNull}; +use alloc::collections::BTreeMap; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt::Display; +use core::ptr::NonNull; use crate::{ Allocator, @@ -9,7 +14,7 @@ use crate::{ #[cfg_attr(feature = "stats", derive(serde::Serialize))] #[derive(Default, Debug, Clone)] pub struct NodeStats { - levels: HashMap, + levels: BTreeMap, kv_pairs: usize, /// Global prefix length distribution [length_0, length_1, ..., length_8] prefix_distribution: [usize; 9], @@ -49,7 +54,7 @@ impl NodeStats { } impl Display for NodeStats { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn calc_load_factor(n: &NodeInfo, scale: usize) -> f64 { if n.node_count == 0 { return 0.0; diff --git a/src/topology.rs b/src/topology.rs index b2a5655..59f7e32 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -4,10 +4,11 @@ //! codec. Durable framing, checksums, generations, and write-ahead logging are //! intentionally owned by the caller. -use std::fmt; -use std::marker::PhantomData; -use std::ptr::NonNull; -use std::sync::Arc; +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::fmt; +use core::marker::PhantomData; +use core::ptr::NonNull; use crate::cast_ptr; use crate::congee_inner::CongeeInner; @@ -169,7 +170,7 @@ impl fmt::Display for Error { } } -impl std::error::Error for Error {} +impl core::error::Error for Error {} impl Topology { /// Validate node kinds, compressed paths, slots, and Node48 free lists. @@ -495,9 +496,9 @@ unsafe fn destroy_tree( #[cfg(test)] mod tests { + use core::sync::atomic::{AtomicUsize, Ordering}; use std::alloc::Layout; use std::sync::Arc; - use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; use crate::error::OOMError; diff --git a/src/utils.rs b/src/utils.rs index 7a55786..551a073 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,9 +2,9 @@ use crate::congee_raw::CongeeRaw; use crate::error::{ArtError, OOMError}; use crate::nodes::{BaseNode, NodePtr}; use crate::{CongeeSet, cast_ptr}; +use alloc::sync::Arc; use core::cell::Cell; use core::fmt; -use std::sync::Arc; const SPIN_LIMIT: u32 = 6; const YIELD_LIMIT: u32 = 10; @@ -29,7 +29,7 @@ impl Backoff { #[inline] pub(crate) fn spin(&self) { for _ in 0..1 << self.step.get().min(SPIN_LIMIT) { - std::hint::spin_loop(); + core::hint::spin_loop(); } if self.step.get() <= SPIN_LIMIT { @@ -44,14 +44,19 @@ impl Backoff { pub(crate) fn snooze(&self) { if self.step.get() <= SPIN_LIMIT { for _ in 0..1 << self.step.get() { - std::hint::spin_loop(); + core::hint::spin_loop(); } } else { #[cfg(all(feature = "shuttle", test))] shuttle::thread::yield_now(); - #[cfg(not(all(feature = "shuttle", test)))] + // No scheduler to yield to without `std`, so spin instead. It is + // what the fast path above is already doing, minus the syscall. + #[cfg(all(not(all(feature = "shuttle", test)), feature = "std"))] ::std::thread::yield_now(); + + #[cfg(all(not(all(feature = "shuttle", test)), not(feature = "std")))] + core::hint::spin_loop(); } if self.step.get() <= YIELD_LIMIT { @@ -175,40 +180,40 @@ unsafe impl Sync for DefaultAllocator {} /// We should use the `Allocator` trait in the std, but it is not stable yet. /// https://github.com/rust-lang/rust/issues/32838 pub trait Allocator { - fn allocate(&self, layout: std::alloc::Layout) -> Result, OOMError>; + fn allocate(&self, layout: core::alloc::Layout) -> Result, OOMError>; fn allocate_zeroed( &self, - layout: std::alloc::Layout, - ) -> Result, OOMError> { + layout: core::alloc::Layout, + ) -> Result, OOMError> { let ptr = self.allocate(layout)?; unsafe { - std::ptr::write_bytes(ptr.as_ptr() as *mut u8, 0, layout.size()); + core::ptr::write_bytes(ptr.as_ptr() as *mut u8, 0, layout.size()); } Ok(ptr) } /// # Safety /// The caller must ensure that the pointer is valid and that the layout is correct. /// The pointer must allocated by this allocator. - unsafe fn deallocate(&self, ptr: std::ptr::NonNull, layout: std::alloc::Layout); + unsafe fn deallocate(&self, ptr: core::ptr::NonNull, layout: core::alloc::Layout); } impl Allocator for DefaultAllocator { - fn allocate(&self, layout: std::alloc::Layout) -> Result, OOMError> { - let ptr = unsafe { std::alloc::alloc(layout) }; - let ptr_slice = std::ptr::slice_from_raw_parts_mut(ptr, layout.size()); - Ok(std::ptr::NonNull::new(ptr_slice).unwrap()) + fn allocate(&self, layout: core::alloc::Layout) -> Result, OOMError> { + let ptr = unsafe { alloc::alloc::alloc(layout) }; + let ptr_slice = core::ptr::slice_from_raw_parts_mut(ptr, layout.size()); + Ok(core::ptr::NonNull::new(ptr_slice).unwrap()) } - unsafe fn deallocate(&self, ptr: std::ptr::NonNull, layout: std::alloc::Layout) { + unsafe fn deallocate(&self, ptr: core::ptr::NonNull, layout: core::alloc::Layout) { unsafe { - std::alloc::dealloc(ptr.as_ptr(), layout); + alloc::alloc::dealloc(ptr.as_ptr(), layout); } } } struct AllocStats { - allocated: std::sync::atomic::AtomicUsize, - deallocated: std::sync::atomic::AtomicUsize, + allocated: core::sync::atomic::AtomicUsize, + deallocated: core::sync::atomic::AtomicUsize, } #[derive(Clone)] @@ -222,26 +227,26 @@ impl MemoryStatsAllocator { Self { inner, stats: Arc::new(AllocStats { - allocated: std::sync::atomic::AtomicUsize::new(0), - deallocated: std::sync::atomic::AtomicUsize::new(0), + allocated: core::sync::atomic::AtomicUsize::new(0), + deallocated: core::sync::atomic::AtomicUsize::new(0), }), } } } impl Allocator for MemoryStatsAllocator { - fn allocate(&self, layout: std::alloc::Layout) -> Result, OOMError> { + fn allocate(&self, layout: core::alloc::Layout) -> Result, OOMError> { let ptr = self.inner.allocate(layout)?; self.stats .allocated - .fetch_add(layout.size(), std::sync::atomic::Ordering::Relaxed); + .fetch_add(layout.size(), core::sync::atomic::Ordering::Relaxed); Ok(ptr) } - unsafe fn deallocate(&self, ptr: std::ptr::NonNull, layout: std::alloc::Layout) { + unsafe fn deallocate(&self, ptr: core::ptr::NonNull, layout: core::alloc::Layout) { self.stats .deallocated - .fetch_add(layout.size(), std::sync::atomic::Ordering::Relaxed); + .fetch_add(layout.size(), core::sync::atomic::Ordering::Relaxed); unsafe { self.inner.deallocate(ptr, layout) } } } @@ -257,14 +262,14 @@ where self.allocator() .stats .allocated - .load(std::sync::atomic::Ordering::Relaxed) + .load(core::sync::atomic::Ordering::Relaxed) } pub fn deallocated_bytes(&self) -> usize { self.allocator() .stats .deallocated - .load(std::sync::atomic::Ordering::Relaxed) + .load(core::sync::atomic::Ordering::Relaxed) } } @@ -277,14 +282,14 @@ where self.allocator() .stats .allocated - .load(std::sync::atomic::Ordering::Relaxed) + .load(core::sync::atomic::Ordering::Relaxed) } pub fn deallocated_bytes(&self) -> usize { self.allocator() .stats .deallocated - .load(std::sync::atomic::Ordering::Relaxed) + .load(core::sync::atomic::Ordering::Relaxed) } } @@ -294,8 +299,8 @@ pub(crate) mod leak_check { use crate::error::OOMError; use crate::{Allocator, DefaultAllocator}; + use core::ptr::NonNull; use std::collections::HashSet; - use std::ptr::NonNull; use std::sync::{Arc, Mutex}; struct LeakCheckAllocatorInner { @@ -350,8 +355,8 @@ pub(crate) mod leak_check { impl Allocator for LeakCheckAllocator { fn allocate( &self, - layout: std::alloc::Layout, - ) -> Result, OOMError> { + layout: core::alloc::Layout, + ) -> Result, OOMError> { let ptr = self.inner.inner.allocate(layout)?; self.inner .allocated @@ -361,7 +366,7 @@ pub(crate) mod leak_check { Ok(ptr) } - unsafe fn deallocate(&self, ptr: std::ptr::NonNull, layout: std::alloc::Layout) { + unsafe fn deallocate(&self, ptr: core::ptr::NonNull, layout: core::alloc::Layout) { self.inner .allocated .lock()