sefer-alloc 0.2.1

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
//! Thread-local heap binding -- the TLS routing truth for Phase 9.
//!
//! Each thread lazily initialises its own [`Heap`] on first use via
//! `std::thread_local!`. The heap is released when the thread exits (the TLS
//! destructor drops the `RefCell<Option<Heap>>`). This is allocation-free
//! init: `std::thread_local!` with a `const` initialiser does NOT call the
//! global allocator (it uses a linker-provided TLS slot on all major
//! platforms).
//!
//! **M5 reentrancy-freedom:** the TLS access is a plain thread-local load (no
//! lock, no alloc). The `Heap::new()` bootstrap calls the OS aperture
//! (`mmap`/`VirtualAlloc`) but never the global allocator. So the TLS init
//! path is reentrancy-free.

use std::cell::RefCell;

use super::heap::Heap;

thread_local! {
    /// The per-thread heap. `None` until first use; `Some` for the thread's
    /// lifetime. The `RefCell` is needed because `thread_local!` hands out
    /// shared references (`&`), but `Heap::alloc`/`dealloc` need `&mut`.
    /// The `RefCell` borrow is uncontended (single-thread, single-owner)
    /// and never panics (we never hold a borrow across a yield point).
    static HEAP: RefCell<Option<Heap>> = const { RefCell::new(None) };
}

/// Execute `f` with a mutable reference to the current thread's [`Heap`].
///
/// Lazily bootstraps the heap on first call. Returns `None` only if the
/// primordial segment reservation fails (OOM at startup -- unrecoverable for
/// an allocator, but we propagate gracefully).
///
/// # Panics
///
/// Panics if the TLS destructor has already run (thread is shutting down and
/// the TLS slot is poisoned). This is the standard `thread_local!` behaviour
/// and is acceptable: a thread that outlives its TLS is already in an
/// exceptional state.
pub fn with_heap<F, R>(f: F) -> Option<R>
where
    F: FnOnce(&mut Heap) -> R,
{
    HEAP.with(|cell| {
        let mut borrow = cell.borrow_mut();
        if borrow.is_none() {
            *borrow = Some(Heap::new()?);
        }
        let heap = borrow.as_mut().unwrap();
        Some(f(heap))
    })
}

/// Execute `f` with a mutable reference to the current thread's [`Heap`], or
/// return `None` if the heap cannot be accessed right now WITHOUT panicking.
///
/// This was the **no-panic** variant for the Phase 11 `GlobalAlloc` face.
/// Phase 12.3 rewired `SeferAlloc` to route through the registry-backed
/// raw-pointer TLS ([`crate::global::tls_heap`]) instead of this `RefCell`
/// binding, so `with_heap_try` is no longer on the malloc path. It is
/// retained as a documented part of the explicit-`Heap` API surface (and a
/// reference implementation of the no-panic TLS pattern); the `alloc` /
/// `alloc-xthread` paths and their tests still use [`with_heap`].
///
/// Returns `None` if the heap cannot be accessed (TLS destroyed, reentrant
/// borrow, or primordial OOM). The caller MUST handle `None` by returning null
/// -- never by panicking.
#[cfg(feature = "alloc-global")]
#[allow(dead_code)]
pub(crate) fn with_heap_try<F, R>(f: F) -> Option<R>
where
    F: FnOnce(&mut Heap) -> R,
{
    // `try_with` returns `Result<R, AccessError>`; `AccessError` means the
    // thread-local's destructor has already run (thread shutdown). We treat
    // this as "no heap available" and return None -- the caller (the
    // GlobalAlloc impl) returns null. This is the no-panic contract: `.ok()?`
    // converts Err(AccessError) to None without panicking.
    HEAP.try_with(|cell| {
        // `try_borrow_mut` returns `Err` only on a reentrant borrow. Under M5
        // (reentrancy-freedom) this is impossible on the alloc path, but we
        // guard defensively (no panic).
        let mut borrow = cell.try_borrow_mut().ok()?;
        if borrow.is_none() {
            *borrow = Some(Heap::new()?);
        }
        let heap = borrow.as_mut()?;
        Some(f(heap))
    })
    .ok()?
}