attachable-slab-allocator 0.1.0

A high-performance, $O(1)$, Master-Slave slab allocator designed for `no_std` environments, kernels, and embedded systems. This library provides fixed-size memory management with RAII safety while remaining completely agnostic of the underlying memory provider.
Documentation

crate Test Status Lines of Code

Repo Size License Docs

📖 Table of Contents


🛠 Core Philosophy

The Attachable Slab Allocator logic is built on a "Plug-and-Play" model. It knows how to divide memory into slots, but it does not know where that memory comes from. You "attach" the allocator to your system (be it a heap, a static pool, or hardware pages) by providing two allocation hooks.

✨ Key Features

  • $O(1)$ Complexity: Allocation and deallocation are constant-time operations.
  • RAII Memory Management: Uses SlabBox<T>, a smart pointer that returns memory to the slab automatically when dropped.
  • Master-Slave Hierarchy: Automatically grows by allocating "Slave" slabs when the "Master" is full.
  • Location Independence: Deallocate a SlabBox anywhere! The box finds its parent slab using a high-speed bitmask trick ($Alignment == Size$).
  • No-Std First: Designed for environments without a standard library.
  • Pluggable Locks: Use SpinLock for multi-threaded safety, or NoLock for maximum performance on single-core/interrupt-driven systems.

📦 Installation

Add this to your Cargo.toml:

[dependencies]
attachable-slab-allocator = "0.1.0"

🌉 The Bridge (Setup)

Before the library can be used, you must define how it acquires raw slabs from your system. If you skip this step, your project will fail to link.

Use the define_allocation_hooks! macro to connect your allocator:

use std::alloc::{alloc, dealloc, Layout};
use core::ptr::NonNull;
use attachable_slab_allocator::{define_allocation_hooks, Result};

// 1. Define your raw allocation functions
unsafe fn system_alloc(layout: Layout) -> Option<NonNull<u8>> {
    let ptr = unsafe { alloc(layout) };
    NonNull::new(ptr)
}

unsafe fn system_free(ptr: NonNull<u8>, layout: Layout) -> Result<()> {
    unsafe { dealloc(ptr.as_ptr(), layout) };
    Ok(())
}

// 2. Register them globally (Once in your main.rs or lib.rs)
define_allocation_hooks!(system_alloc, system_free);

🚀 Quick Start

use attachable_slab_allocator::{SlabCache, locks::SpinLock};

struct MyConfig {
    id: u32,
    data: [u8; 60],
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize a cache for MyConfig. 
    // SLAB_SIZE (4096) must be a power of two!
    let mut cache = SlabCache::<MyConfig, SpinLock, 4096>::new()?;

    // Allocate an object
    let mut item = cache.alloc()?; // Returns SlabBox<MyConfig>
    
    item.id = 1;
    item.data[0] = 0xFF;

    // Memory is automatically returned to the slab when 'item' is dropped.
    Ok(())
}

🏗 Deep Dive: Master-Slave Architecture

  1. Master Slab: The first slab created by SlabCache. It holds the global lock and reference count.
  2. Slave Slabs: When the Master is full, it requests more memory via your hooks to create a Slave.
  3. Independent Deallocation: This is the killer feature. Because every slab is aligned to its own size, a SlabBox can find its header by simply masking its own address. You can clone the SlabCache, drop the original, or move the SlabBox to a different thread; it will always find its way home.

⚠️ Safety & Constraints (Important!)

1. The Power-of-Two Rule

The SLAB_SIZE constant must be a power of two (e.g., 1024, 4096, 65536). This is required for the bitmasking logic that finds the slab header. The library checks this at compile-time.

2. Minimum Type Size

Your type T must be at least 4 bytes (u32). When a slot is free, the allocator stores a 32-bit index inside the slot to maintain the free-list. Types smaller than 4 bytes will trigger a compile-time error.

3. Maximum Type Size

A single slab must be large enough to fit the Slab header plus at least two slots of type T.

4. Lock Size

If you implement a custom LockTrait, your lock structure must be 16 bytes or smaller. This ensures the Slab header remains compact and efficient.

5. Memory Corruption

Since this is a low-level allocator:

  • Double Frees: Manually freeing the same pointer twice will corrupt the internal free-list. Always use SlabBox to avoid this.
  • Buffer Overflows: Writing outside the bounds of your type T will overwrite slab metadata or other slots, leading to internal panics or silent corruption.

❌ Error Handling

The library uses a custom Result type and SlabError enum:

  • OutOfMemory: The system hooks failed to provide memory, or the slab is at capacity.
  • InvalidPointer: The "Magic Sentinel" check failed. This usually indicates a use-after-free or that the pointer doesn't belong to a valid slab.
  • AlignmentMismatch: The pointer address is not correctly aligned for the slab size.
  • FatalError: Internal logic corruption occurred.

⚖️ License

Licensed under the MIT License. See LICENSE for details.