std1.0.0-beta-7 SafeC docs

Source alloc/allocator.h

View in the repository

#pragma once
// SafeC Standard Library — allocation traits shared by every allocator.
//
// std/alloc has four allocators over a buffer of their own (or one you
// hand them); each suits a different allocation pattern:
//
//   BumpAllocator  (bump.h)  arena: O(1) alloc, frees everything at once
//                            with reset(); dealloc of one block is a no-op.
//   PoolAllocator  (pool.h)  variable sizes, first fit, coalescing frees.
//   SlabAllocator  (slab.h)  one fixed object size, O(1) alloc and free.
//   TlsfAllocator  (tlsf.h)  variable sizes, O(1) alloc and free with
//                            bounded fragmentation (real-time use).
//
// The interface is split into one trait per capability, composed into the
// two roles generic code asks for, and the typed helpers are written once
// against the smallest trait they need:
//
//   Allocate        alloc(size, align)
//   Deallocate      dealloc(ptr)
//   Resettable      reset(): frees every block at once
//   Allocator       Allocate + Deallocate: BumpAllocator (no-op dealloc),
//                   PoolAllocator, SlabAllocator, TlsfAllocator,
//                   SecureAllocator (crypto/secure_alloc.h),
//                   CountingAllocator<A> (below)
//   ArenaAllocator  Allocate + Resettable: allocate freely, then reset() —
//                   BumpAllocator, PoolAllocator, SlabAllocator,
//                   TlsfAllocator
//
// Traits are structural: a type conforms by having the methods, so your own
// allocator works with alloc_t / alloc_array_t / dealloc_t (and with any
// generic<A: Allocator> code) as soon as it defines alloc and dealloc with
// these signatures. Each call is resolved at compile time (no dispatch at
// run time).
//
//   struct BumpAllocator arena = BumpAllocator::new(4096UL);
//   ?&heap Packet p = alloc_t(&arena);              // T = Packet, from the target
//   ?&heap float xs = alloc_array_t(&arena, 64UL);  // 64 floats
//   ...
//   arena.destroy();
//
// Alignment each allocator honours ('align' must be a power of two; 0 is
// taken as 1; anything else gives null):
//   bump  any power of two (measured on the address, padding the arena).
//   pool  up to 8; null beyond.
//   slab  up to the largest power of two dividing its object size, at most
//         8; null beyond, and null when size > its object size.
//   tlsf  up to 8; null beyond.
// The bounds for pool, slab and tlsf assume an 8-aligned backing buffer:
// always true for X::new; for X::over it's the buffer you pass.
#include <std/mem.h>
#include <std/traits.h>   // Resettable

namespace std {

trait Allocate {
    // 'size' bytes aligned to 'align' (a power of two), uninitialised; null
    // when the allocator can't (out of space, or an alignment or size it
    // doesn't provide).
    ?&heap unsigned char alloc(unsigned long size, unsigned long align);
}

trait Deallocate {
    // Gives back a block alloc returned; null is a no-op. A no-op on an
    // allocator that can't free one block (bump: reset() frees them all).
    void dealloc(?&heap unsigned char ptr);
}

trait Allocator : Allocate + Deallocate {}

trait ArenaAllocator : Allocate + Resettable {}

// ── Typed allocation (recommended) ───────────────────────────────────────────
// One T / n Ts, aligned for T; null when the allocator can't. The memory is
// uninitialised. T is inferred from the target ('?&heap Packet p =
// alloc_t(&pool);'). alloc_array_t aborts with a diagnostic if n * sizeof(T)
// overflows (checked_mul_size).
generic<T, A: Allocate>
?&heap T alloc_t(&A a) {
    unsafe { return (?&heap T)a.alloc(sizeof(T), alignof(T)); }
}

generic<T, A: Allocate>
?&heap T alloc_array_t(&A a, unsigned long n) {
    unsafe { return (?&heap T)a.alloc(checked_mul_size(n, sizeof(T)), alignof(T)); }
}

// Gives back a block from alloc_t / alloc_array_t on the same allocator.
generic<T, A: Deallocate>
void dealloc_t(&A a, &heap T obj) {
    unsafe { a.dealloc((?&heap unsigned char)obj); }
}

// ── CountingAllocator: usage statistics over any Allocator ──────────────────
// Forwards every call to the allocator it borrows and counts them: blocks
// handed out and given back, failed requests, bytes requested. It is an
// Allocator itself, so it slots in wherever the allocator it wraps would
// (leak checks in tests, sizing an arena or pool from a real workload).
//
//   struct PoolAllocator pool = PoolAllocator::new(65536UL);
//   struct CountingAllocator<PoolAllocator> c = CountingAllocator::over(&pool);
//   run_workload(&c);                       // any generic<A: Allocator> code
//   if (c.live() != 0UL) { ... }            // blocks never given back
generic<A: Allocator>
struct CountingAllocator {
    &A            inner;      // the allocator doing the work (not owned)
    unsigned long allocs;     // alloc calls that returned a block
    unsigned long frees;      // dealloc calls with a non-null block
    unsigned long failures;   // alloc calls that returned null
    unsigned long bytes;      // bytes requested by the successful allocs

    static struct CountingAllocator<A> over(&A inner);
    ?&heap unsigned char alloc(unsigned long size, unsigned long align);
    void          dealloc(?&heap unsigned char ptr);
    // Blocks handed out and not given back yet.
    unsigned long live() const;
};

generic<A: Allocator>
struct CountingAllocator<A> CountingAllocator::over(&A inner) {
    struct CountingAllocator<A> c;
    c.inner = inner;
    c.allocs = 0UL;
    c.frees = 0UL;
    c.failures = 0UL;
    c.bytes = 0UL;
    return c;
}

generic<A: Allocator>
?&heap unsigned char CountingAllocator::alloc(unsigned long size, unsigned long align) {
    ?&heap unsigned char p = self.inner.alloc(size, align);
    if (p.is_null()) {
        self.failures = self.failures + 1UL;
    } else {
        self.allocs = self.allocs + 1UL;
        self.bytes = self.bytes + size;
    }
    return p;
}

generic<A: Allocator>
void CountingAllocator::dealloc(?&heap unsigned char ptr) {
    if (!ptr.is_null()) { self.frees = self.frees + 1UL; }
    self.inner.dealloc(ptr);
}

generic<A: Allocator>
unsigned long CountingAllocator::live() const { return self.allocs - self.frees; }

} // namespace std