sefer-alloc 0.2.0

A safe-by-construction, 100% Rust memory toolkit (no C/C++ libraries — no libnuma/mimalloc/jemalloc/snmalloc/tcmalloc): a single-threaded handle store (Region<T>) and a drop-in #[global_allocator] (SeferAlloc) over one verified segment substrate, with #![forbid(unsafe_code)] at the top.
Documentation
[package]
name = "sefer-alloc"
version = "0.2.0"
edition = "2021"
# Matches the resocks5 ecosystem floor; the core is plain safe Rust and will
# build on much older toolchains, but we pin a known-good MSRV from day one.
rust-version = "1.88"
license = "MIT OR Apache-2.0"
description = "A safe-by-construction, 100% Rust memory toolkit (no C/C++ libraries — no libnuma/mimalloc/jemalloc/snmalloc/tcmalloc): a single-threaded handle store (Region<T>) and a drop-in #[global_allocator] (SeferAlloc) over one verified segment substrate, with #![forbid(unsafe_code)] at the top."
readme = "README.md"
repository = "https://github.com/PHPCraftdream/sefer-alloc"
homepage = "https://github.com/PHPCraftdream/sefer-alloc"
documentation = "https://docs.rs/sefer-alloc"
keywords = ["allocator", "arena", "generational", "handle", "lock-free"]
categories = ["data-structures", "memory-management", "concurrency", "no-std"]

# Workspace root. `sefer-alloc` is the root crate AND the workspace; the
# extracted, independently-publishable building blocks live under `crates/`:
#   - sefer-region    (crates/region)       — typed handle store (Handle<T>/Region<T>)
#                                             #![forbid(unsafe_code)]
#   - aligned-vmem    (crates/vmem)         — OS virtual-memory aperture
#                                             #![allow(unsafe_code)], single-purpose, audit-friendly
#   - numa-shim       (crates/numa)         — dependency-free NUMA detection + binding
#                                             #![allow(unsafe_code)], single-purpose, audit-friendly
#   - malloc-bench-rs (crates/malloc-bench) — portable GlobalAlloc benchmark harness
#                                             standalone, not in sefer-alloc runtime dep tree
# Each is a real crates.io crate someone can `cargo add` on its own; sefer-alloc
# re-exports sefer-region's surface for backward compatibility, and delegates its
# OS-unsafe seams to aligned-vmem and numa-shim (thin interop wrappers).
[workspace]
members = ["crates/vmem", "crates/numa", "crates/malloc-bench", "crates/region"]
resolver = "2"

[lints.rust]
# `loom` is a build-time cfg (set via `RUSTFLAGS="--cfg loom"`) that gates the
# loom model-check harness. Declare it so `unexpected cfg` warnings are silent.
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }

[features]
# The single-threaded core (`Region<T>` / `Handle<T>`) needs only `alloc` and
# builds under `no_std`. `std` (default-on) is required only for `SyncRegion`
# (it wraps `std::sync::RwLock`) and the concurrent/byte tiers. Disable default
# features to get the bare `no_std` + `alloc` core.
default = ["std"]
std = ["sefer-region/std"]
# Phase 3b: lock-free reads. The concurrent tier is opt-in; it requires `std`
# (its deps need it) and pulls in only `arc-swap` + `crossbeam-epoch` beyond the
# core.
# - 3b-I (`arc-swap`): RCU with page-granularity copy-on-write — zero `unsafe`
#   of our own.
# - 3b-II (`crossbeam-epoch`): per-slot atomics + epoch reclamation — the single
#   confined `unsafe` Hand organ (src/concurrent/hand.rs), the only module with
#   `#![allow(unsafe_code)]`. See docs/PLAN.md.
experimental = ["std", "dep:arc-swap", "dep:crossbeam-epoch"]
# Phase 7c: thread-per-core pinning. Additive over `experimental` — pulls in
# `core_affinity` (a safe wrapper over the OS affinity syscalls; zero `unsafe`
# of our own) and exposes an explicit shard-binding API plus a thread-per-core
# runner on `ShardedRegion`. The default and `experimental` builds do NOT pull
# `core_affinity`: this feature is purely opt-in. See docs/PLAN.md §7c.
pinning = ["experimental", "dep:core_affinity"]
# Phase 8: the segment substrate + self-hosted metadata — the foundation of a
# real allocator (the Membrane Inversion). OS-backed SEGMENT-aligned spans via a
# confined `unsafe` aperture (`mmap`/`VirtualAlloc`, NO `std::alloc` anywhere),
# a self-hosted `SegmentTable` carved from the first primordial segment,
# per-segment `PageMap` / `BinTable`, and single-threaded
# `alloc`/`dealloc`/`realloc`/`alloc_zeroed`. The alloc path is provably
# reentrancy-free (M5): no `Vec`/`Box`/`HashSet`/`std::alloc`/`format!` is
# reachable — all metadata self-hosts in segment memory. This is single-threaded
# correctness-first; per-thread heaps + concurrency are Phases 9–10. Introduces
# two confined-`unsafe` modules — `alloc_core::os` (the OS aperture) and
# `alloc_core::node` (the intrusive free-list node seam, the generalized `hand`
# discipline) — so the crate switches to `deny(unsafe_code)` with this feature
# on; `unsafe` outside those modules stays a hard error. Requires `std` (the OS
# syscalls are std-gated). See `docs/ALLOC_PLAN.md` §5 P8 and §6.
alloc-core = ["std", "dep:aligned-vmem"]
# Phase 9: per-thread heap + intrusive free lists -- the lock-free fast path.
# A thread allocates from its own heap with no lock and no atomic on the common
# case. Single-owner still (cross-thread free is Phase 10). Builds on the
# Phase 8 segment substrate (`alloc-core`). Requires `std` (TLS).
alloc = ["alloc-core"]
# Phase 10: opt-in cross-thread free. Enables the lock-free Treiber stack
# (`ThreadFreeStack`) for cross-thread `dealloc`, owner stamping on segment
# headers, and the `dealloc_any_thread` entry point. Sound across thread death
# via abandonment-leak (segments and the Treiber head are leaked on `Heap::drop`
# so late cross-thread frees never touch unmapped memory). Full abandoned-heap
# adoption (reclaiming leaked segments) is Phase 11. Without this feature, the
# `alloc` feature provides the Phase 9 single-thread-only allocator (sound:
# single owner, no cross-thread refs). Requires `alloc` (and thus `alloc-core`).
alloc-xthread = ["alloc"]
# Phase 11: the drop-in `GlobalAlloc` face -- `SeferAlloc`. Installs over the
# per-thread `Heap` substrate via the TLS `with_heap` binding (allocation-free
# lazy init). The `unsafe impl GlobalAlloc` is the single new `unsafe` surface
# of this phase -- it is the documented malloc-face seam (the trait is `unsafe`;
# the body delegates to the already-proven safe `Heap` entry points). Every
# entry point is no-panic (a panic in a global allocator aborts the process):
# checked branches return null on OOM, never panic/abort. Reentrancy-free (M5)
# is upheld structurally: no `Vec`/`Box`/`HashSet`/`std::alloc`/`format!` on the
# path -- the TLS init is the Phase 9 lazy allocation-free discipline. Requires
# `alloc` (and thus `alloc-core`). Add `alloc-xthread` alongside it for the
# multi-thread face (cross-thread `dealloc` via the Phase 10 Treiber stack).
alloc-global = ["alloc"]
# Phase 35: M6 decommit — return the payload pages of an empty small segment to
# the OS (Win `VirtualFree MEM_DECOMMIT` / unix `madvise MADV_DONTNEED`) when its
# live-block count drops to zero, recommitting on first reuse. Additive over
# `alloc-core`: it adds an owner-only `live_count` field to the segment header
# (the field is present in EVERY build's layout so the byte layout is stable;
# its mutation/read is gated on this feature) and wires the decommit/reset/
# recommit policy into the own-thread free + cross-thread reclaim + carve paths,
# all under `#[cfg(feature = "alloc-decommit")]`. With this feature OFF the
# default build is byte-for-byte unchanged. NO new dependency and NO new `unsafe`
# site — the OS seam (`os::decommit_pages`/`recommit_pages`) already exists; the
# new bookkeeping is plain safe arithmetic through the `node` seam. M6 is
# decommit-safe WITHOUT crossbeam-epoch / M11 — see the safety argument at the
# decommit point in `alloc_core` and `docs/PHASE35_DECOMMIT_DESIGN.md` §1.
# Requires `alloc-core`. Default OFF.
alloc-decommit = ["alloc-core"]
# Convenience alias: the recommended feature set for long-running multi-thread
# workloads (DBMS-style, async runtimes, anything that creates and frees many
# segments over time). Without `alloc-decommit` the per-process SegmentTable
# (1024 slots, append-only) is the hard ceiling on cumulative segment
# allocations; `alloc-decommit` lets empty segments be recycled (#60),
# lifting that ceiling. Add this feature for any long-running deployment.
fastbin = ["alloc-global"]
production = ["alloc-global", "alloc-xthread", "alloc-decommit", "fastbin"]
# NUMA-aware segment reservation: when a new segment is reserved, pages are
# steered to the NUMA node of the calling thread
# (Linux: mbind(2), Windows: VirtualAllocExNuma).
# macOS: no-op (no public NUMA API). Default OFF. Requires `alloc-core`.
numa-aware = ["alloc-core", "dep:numa-shim"]

[dependencies]
# Phase 1: typed handle store (Region<T> + Handle<T> + SyncRegion<T>), extracted
# from sefer-alloc into a standalone, independently-publishable crate alongside
# aligned-vmem / numa-shim / malloc-bench-rs. sefer-alloc re-exports its surface
# for backward compatibility — existing users of `use sefer_alloc::{Region,
# Handle, SyncRegion}` continue to work unchanged. New consumers who want ONLY
# the handle store (no allocator stack) should depend on `sefer-region` directly.
#
# `default-features = false`: sefer-region gates its std-dependent SyncRegion on
# its own `std` feature, which we re-enable via our own `std` feature so the
# no_std core compiles without it and the default build gets SyncRegion as before.
sefer-region = { path = "crates/region", version = "0.1", default-features = false }
# Phase 3b-I: atomic snapshot publication for lock-free reads. Optional — only
# the `experimental` concurrent tier pulls it in. Its internal `unsafe` is the
# dependency's, not ours; our crate stays `#![forbid(unsafe_code)]` with it on.
arc-swap = { version = "1", optional = true }
# Phase 3b-II: epoch-based memory reclamation for the per-slot-atomics lock-free
# read tier. Optional — only pulled in by `experimental`. Its `unsafe` is the
# dependency's; OUR `unsafe` is confined to src/concurrent/hand.rs (the single
# documented `#![allow(unsafe_code)]` module) and is wrapped in a safe-to-use
# `AtomicSlot<T>` API. See docs/PLAN.md.
crossbeam-epoch = { version = "0.9", optional = true }
# Phase 7c: OS thread-to-core pinning for the thread-per-core runner. A safe
# wrapper over `sched_setaffinity` (Linux) / `SetThreadAffinityMask` (Windows)
# / `thread_policy_set` (macOS). Optional — only the `pinning` feature pulls it
# in. Pinning is best-effort on some OSes; the runner still runs correctly if
# the host refuses the affinity. See docs/PLAN.md §7c.
core_affinity = { version = "0.8", optional = true }
# Phase 8 (alloc-core) OS aperture: SEGMENT-aligned anonymous virtual memory,
# page-level decommit/recommit. Extracted from sefer-alloc into a standalone
# crate so it can be used and published independently. Pulled in ONLY by the
# `alloc-core` feature (and every feature that depends on it).
aligned-vmem = { path = "crates/vmem", version = "0.1", optional = true }
# Phase 78 (numa-aware) NUMA seam: dependency-free NUMA detection + binding,
# extracted from sefer-alloc into a standalone crate so it can be published
# independently. Pulled in ONLY by the `numa-aware` feature.
numa-shim = { path = "crates/numa", version = "0.1", features = ["vmem-integration"], optional = true }

[dev-dependencies]
# Phase 2 dependency: the locality bench compares `Region` against raw
# `slotmap::SlotMap` / `DenseSlotMap`. slotmap is no longer a direct production
# dep (it lives in `sefer-region`), but the bench still needs it for the head-
# to-head numbers. Dev-only.
slotmap = "1"
# Phase 0 dependency: property-based differential testing against a reference
# model. This is the heart of the verification-first build — see docs/PLAN.md.
proptest = "1"
# Task #52: tokio burn-in example — async runtime driving SeferAlloc as
# #[global_allocator].  Minimal feature set: rt-multi-thread (multi-thread
# scheduler), macros (#[tokio::main]), sync (mpsc channel), time (sleep /
# timeout / interval).  No fs/net/signal/process — not needed for the harness.
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
# Phase 2: criterion benches that empirically choose the single-threaded
# backing container (SlotMap vs DenseSlotMap vs HashMap vs Vec<Box<T>>) across
# the iterate / lookup / churn axes. See docs/BENCHMARKS.md for the verdict.
criterion = "0.5"
# Phase 9: mimalloc for head-to-head benchmarking of the per-thread heap's
# alloc/dealloc hot path vs a state-of-the-art allocator. Dev-only — NOT a
# runtime dependency.
mimalloc = "0.1"

# Phase 3b-II: loom model-checks the publication PROTOCOL (generation/value
# ordering) of the epoch tier. loom and crossbeam-epoch do NOT compose (loom
# replaces the allocator and atomics), so the loom harness models a bare
# AtomicU32 generation + AtomicPtr value publication using loom's own atomics,
# NOT crossbeam. Reclamation correctness rests on the crate + miri, not loom.
# Built only under `cfg(loom)` (`RUSTFLAGS="--cfg loom"` / `--cfg loom`).
[target.'cfg(loom)'.dev-dependencies]
loom = "0.7"

[[bench]]
name = "locality"
harness = false

# Phase 7a: sharded write-scaling bench — `ShardedRegion` vs `SyncRegion` /
# `Arc<Mutex<Region>>` across thread counts. Quick criterion profile. Requires
# `experimental` (for `ShardedRegion`) and the default `std` (for `SyncRegion`).
[[bench]]
name = "sharded_write"
harness = false
required-features = ["experimental"]

# Phase 7c: pinned thread-per-core write bench — `ShardedRegion` writes with
# each worker pinned to a core AND explicitly bound to the matching shard, vs
# the unpinned 7a path. Quick criterion profile. Honest verdict: the win is
# workload-dependent (only manifests when the working set is cache-hot and
# shard-local); see the bench's module docs. Requires `pinning` (which brings
# `experimental` + `core_affinity`).
[[bench]]
name = "pinned_write"
harness = false
required-features = ["pinning"]

# Phase 9: per-thread heap alloc/dealloc hot path vs mimalloc and the system
# allocator. Quick criterion profile (sample_size 10, short warm-up). Honest
# verdict in docs/HEAP_BENCH.md.
[[bench]]
name = "heap_alloc"
harness = false
required-features = ["alloc"]

# Phase 11: `SeferAlloc` installed as `#[global_allocator]` vs `mimalloc` and
# the system allocator, exercising real Rust allocation patterns (Vec/Box churn).
# Quick criterion profile (sample_size 10, short warm-up). Honest verdict in
# docs/ALLOC_BENCH.md. Requires `alloc-global` (which brings `alloc`).
[[bench]]
name = "global_alloc"
harness = false
required-features = ["alloc-global"]

# Task #54: large-block and realloc-heavy profiling — SeferAlloc vs mimalloc
# vs System across three groups: single-shot alloc+free of multi-MiB blocks,
# geometric grow via GlobalAlloc::realloc, and realloc under adversarial
# neighbour pressure. Quick criterion profile. Requires `alloc-global`.
[[bench]]
name = "large_realloc"
harness = false
required-features = ["alloc-global"]

# Phase 11: minimal example installing `SeferAlloc` as the `#[global_allocator]`.
# Gated so the default build does not try to compile it without the feature.
[[example]]
name = "global_allocator"
required-features = ["alloc-global"]

# Phase 13.7: multi-thread macro-benchmark (larson + mstress sweep, T=1/2/4).
# The file is `#![cfg]`-gated on alloc-global + alloc-xthread; without this
# registration `cargo build` / `cargo test` (without `--tests`) tries to compile
# it under the default feature set and fails with E0601 (no `main`).
[[example]]
name = "malloc_macro"
required-features = ["alloc-global", "alloc-xthread"]

# Soak harness: high-load multi-thread stability test (32/64/128 threads, hours).
# NOT a throughput benchmark — a correctness + stability harness that detects
# panics, deadlocks, and accounting mismatches at scale.
# Run: `cargo run --release --example soak_xthread --features "alloc-global alloc-xthread"`
# Configure via SEFER_SOAK_THREADS=N and SEFER_SOAK_SECONDS=N (default: avail_par × 5s).
[[example]]
name = "soak_xthread"
required-features = ["alloc-global", "alloc-xthread"]

# Task #53: RSS-probe harness — measures resident-set-size and in-flight block
# count under asymmetric cross-thread free pressure (producers allocate only;
# 1 consumer frees only).  Designed to stress RemoteFreeRing and show the
# alloc-decommit recovery ratio.  NOT a throughput benchmark.
# Run: `cargo run --release --example rss_probe --features "alloc-global alloc-xthread"`
# With decommit: add `alloc-decommit` to the features list.
# Tune via env: SEFER_RSS_PRODUCERS, SEFER_RSS_SECONDS, SEFER_RSS_LARGE_FRACTION.
[[example]]
name = "rss_probe"
required-features = ["alloc-global", "alloc-xthread"]

# Task #62 §A-1: cross-thread free micro-bench — direct push→drain of the
# RemoteFreeRing via AllocCore test seams (dbg_push_to_ring +
# dbg_drain_all_rings), no tokio, no channels. Produces a low-noise flamegraph
# where criterion KDE overhead should be ≤ 50% (vs 84–85% in §1/§3 profiles).
# Documented in docs/PROFILE_FLAMEGRAPHS.md §6.
[[bench]]
name = "heap_xthread"
harness = false
required-features = ["alloc-core", "alloc-xthread"]

# Task #62 §A-2: synthetic async-like allocation pattern — mixed-size
# alloc/dealloc/realloc (row buffers, accumulators, medium blobs) via
# SeferAlloc::GlobalAlloc called directly. No tokio runtime noise, no
# channels, no spawns. Single-threaded DBMS-pipeline simulation.
# Documented in docs/PROFILE_FLAMEGRAPHS.md §6.
[[bench]]
name = "heap_async_pattern"
harness = false
required-features = ["alloc-global"]

# Task #52: tokio async burn-in — SeferAlloc as #[global_allocator] under a
# real tokio multi-thread runtime with a DBMS-pipeline-like async workload.
# All tokio-internal allocations also go through SeferAlloc.
# Run: `SEFER_BURNIN_SECONDS=10 SEFER_TOKIO_WORKERS=4 SEFER_BURNIN_TASKS=256 \
#        cargo run --release --example tokio_burn_in \
#        --features "alloc-global alloc-xthread"`
[[example]]
name = "tokio_burn_in"
required-features = ["alloc-global", "alloc-xthread"]

# ── §0 — Flamegraph profiling: debug-info is OPT-IN, NOT persistent ──────────
#
# Persistent `debug = true` in [profile.release] / [profile.bench] slows linking
# and inflates `.rlib` / rmeta sizes noticeably for every `cargo build --release`
# (even non-profiling runs). We therefore do NOT add a [profile.*] section.
#
# Instead: pass the env var once, at profiling time:
#
#   CARGO_PROFILE_BENCH_DEBUG=line-tables-only \
#     cargo build --bench heap_xthread \
#       --features "alloc-core alloc-xthread" --profile bench \
#       CARGO_TARGET_DIR=/tmp/sefer-fg-v3a
#
#   CARGO_PROFILE_RELEASE_DEBUG=line-tables-only \
#     cargo build --release --example malloc_macro \
#       --features "alloc-global alloc-xthread" \
#       CARGO_TARGET_DIR=/tmp/sefer-fg-v3a
#
# `line-tables-only` gives adequate function names + file/line for flamegraphs
# without the full DWARF variable info that `true`/`full` would add.
# Documented in docs/PROFILE_FLAMEGRAPHS.md §0.