From 16153b1103a80b573b5df8fcd4011e8ffabb0ef3 Mon Sep 17 00:00:00 2001 From: meh Date: Sun, 6 Sep 2026 02:48:33 +0700 Subject: [PATCH 1/4] docs: carry the house working agreement --- AGENTS.md | 22 ++++++++++++++++++++++ CLAUDE.md | 7 +++++++ 2 files changed, 29 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md 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. From 06937a69de93119011194985c797e83a58dd105a Mon Sep 17 00:00:00 2001 From: meh Date: Mon, 7 Sep 2026 17:58:05 +0700 Subject: [PATCH 2/4] Build without std, behind a default-on std feature The crate is an adaptive radix tree over raw pointers and atomics. Nothing in it needed an operating system, and it linked one anyway, which is what kept every consumer that wanted to be no_std from being it. core:: for ptr, mem, marker, fmt, hint, cmp, slice, arch, alloc::Layout alloc:: for Arc, Vec, Box, format, the allocator calls Two things genuinely wanted std and now say so: - `debug_print` writes to stdout, so it is behind the feature. - `thread::yield_now` in the spin backoff falls back to `spin_loop` when there is no scheduler to yield to. That is the same instruction the fast path above it is already executing, minus the syscall. `stats` keyed its per-level counters by depth in a `HashMap`; it is a `BTreeMap` now, which is the same map without a hasher and is what `alloc` has. `serde` and `ps-reclaim` arrive with default features off and get `std` back through this crate's own. Doc examples still say `std::`, because a doctest is compiled as a consumer and a consumer has one. cargo test 103 + 7 + 39 passed, 0 failed clippy, std and no_std clean default -> ps-reclaim [libc,spin,std]; no_std -> [libc,spin] --- Cargo.toml | 9 ++++-- src/congee.rs | 6 +++- src/congee_compact_set.rs | 16 ++++++---- src/congee_inner.rs | 15 +++++---- src/congee_raw.rs | 6 ++-- src/congee_set.rs | 5 ++- src/error.rs | 4 +-- src/lib.rs | 14 ++++++-- src/lock.rs | 6 ++-- src/nodes/base_node.rs | 47 +++++++++++++-------------- src/nodes/node_16.rs | 7 ++-- src/nodes/node_256.rs | 5 +-- src/nodes/node_4.rs | 5 +-- src/nodes/node_48.rs | 5 +-- src/nodes/node_ptr.rs | 23 +++++++------- src/range_scan.rs | 4 +-- src/stats.rs | 11 +++++-- src/topology.rs | 13 ++++---- src/utils.rs | 67 +++++++++++++++++++++------------------ 19 files changed, 157 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31afd9f..cd800d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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, >=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() From 4e4dd3cdc284991725f155469993ee36383fdeb9 Mon Sep 17 00:00:00 2001 From: meh Date: Mon, 7 Sep 2026 17:58:23 +0700 Subject: [PATCH 3/4] chore: 0.4.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cd800d8..4e61cf4 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"] From b28ba7560cd768611c6265e61db9a5328c02bc11 Mon Sep 17 00:00:00 2001 From: meh Date: Mon, 7 Sep 2026 18:07:40 +0700 Subject: [PATCH 4/4] Say the ps-reclaim requirement as a caret `"^0.1, >=0.1.4"` and `"0.1.4"` describe the same set. The second is the form the rest of the house uses. cargo test 103 passed cargo test --no-default-features 103 passed --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4e61cf4..ade4cbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" members = ["fuzz"] [dependencies] -ps-reclaim = { version = "^0.1, >=0.1.4", default-features = false, features = ["libc", "spin"] } +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]