Memory and allocation

Memory and allocation

Allocator interfaces, scopes and raw allocation.

Contents

NameSummary
AllocatorEverything a type must have to be used as an allocator: alloc and free, both static.
ResizerAn allocator that can grow or shrink a block without moving it.
ReallocatorAn allocator that can grow or shrink a block, moving it if it has to.
ZeroAllocatorAn allocator that gets zeroed memory for free (fresh pages, calloc).
ScopedAllocatorAn allocator usable with AllocatorScope.
FullAllocatorAn allocator with every capability: Allocator plus resize, realloc and zeroed allocation.
ScopedFullAllocatorA FullAllocator that is also a ScopedAllocator: what AllocatorScope takes.
is_pow2Returns true when v is a power of two (zero is not).
align_upRounds v up to the next multiple of align.
danglingReturns a non-null, aligned pointer that owns nothing: what a zero-size request gets.
realloc_by_copyA realloc for an allocator with no faster path: allocates, copies, frees the old block.
set_oom_handlerReplaces what create does when the allocator returns &null.
oomRuns the OOM handler for a failed allocation of size bytes at align; never returns.
ScopeFrameOne active scope: the allocator's static functions as pointers, the range snapshot taken when it opened, and the bounding box of it and every scope below it.
ScopeChunkSCOPE_CHUNK frames, plus their ranges packed for the scan: lo[i + 1] and span[i + 1] = hi - lo for frames[i], so a hit is one subtract and one unsigned compare.
alloc_rawAllocates size bytes from the allocator in effect: the innermost open AllocatorScope, else Global.
free_rawGives a block back to whichever allocator it came from: the open scope whose range holds it, else Global.
UninitializedStorage for one T that holds no T.
uninitialized_fromRuns *p's destructor and returns its storage as an Uninitialized<T>.
UninitializedSliceStorage for cap Ts, of which [0, len) are live.
sizeReturns the number of live elements.
capacityReturns the number of elements the storage has room for.
as_bytesReturns the storage as raw bytes.
pushConstructs a T from args in the next free slot.
finishEvery slot is live: ownership of the cap Ts moves to the caller, who destroys them and frees capacity() * sizeof T bytes.
clearDestroys the live prefix, last first.

Allocator

interface Allocator

Everything a type must have to be used as an allocator: alloc and free, both static.

Failure is &null; an allocator never aborts or panics (the OOM policy belongs to the front end). See base/alloc.k for the full contract.

Allocator.alloc

static fn alloc(size: usize, align: usize) -> unsafe *u8

Returns size bytes aligned to align, or &null on failure.

Parameters

  • size: Nonzero; the front end never forwards a zero-size request.
  • align: A power of two.

Allocator.free

static fn free(ptr: unsafe *u8, size: usize, align: usize)

Gives a block back.

Safety

ptr must be a non-null block this allocator returned, and size and align must be the values it was allocated (or last successfully resized) with.

Resizer

interface Resizer derives Allocator

An allocator that can grow or shrink a block without moving it.

Resizer.resize

static fn resize(ptr: unsafe *u8, old_size: usize, new_size: usize, align: usize) -> bool

Resizes the block in place. Returns false, with the block untouched, when it cannot.

Reallocator

interface Reallocator derives Allocator

An allocator that can grow or shrink a block, moving it if it has to.

Reallocator.realloc

static fn realloc(ptr: unsafe *u8, old_size: usize, new_size: usize, align: usize) -> unsafe *u8

Resizes the block, moving it if needed, and returns the new pointer. Returns &null, with the original block untouched, on failure.

ZeroAllocator

interface ZeroAllocator derives Allocator

An allocator that gets zeroed memory for free (fresh pages, calloc).

ZeroAllocator.alloc_zeroed

static fn alloc_zeroed(size: usize, align: usize) -> unsafe *u8

Like alloc, but every returned byte is zero. Returns &null on failure.

ScopedAllocator

interface ScopedAllocator derives Allocator

An allocator usable with AllocatorScope.

enter() returns a token that leave() takes back, and range() is the address range every block it hands out lies in while the scope is open. No two open scopes may have overlapping ranges.

ScopedAllocator.enter

static fn enter() -> usize

Called when a scope opens; returns a token that is passed back to leave.

ScopedAllocator.leave

static fn leave(mark: usize)

Called when the scope closes, with the token enter returned.

ScopedAllocator.range

static fn range() -> (unsafe *u8, unsafe *u8)

Returns the half-open address range [lo, hi) that all of this allocator's blocks lie in.

FullAllocator

interface FullAllocator derives Resizer + Reallocator + ZeroAllocator

An allocator with every capability: Allocator plus resize, realloc and zeroed allocation.

What the front end and @set_global_allocator take until eval if can test a capability per instantiation; then the front end will take Allocator and fall back where one is missing.

ScopedFullAllocator

interface ScopedFullAllocator derives ScopedAllocator + FullAllocator

A FullAllocator that is also a ScopedAllocator: what AllocatorScope takes.

is_pow2

inline fn is_pow2(v: usize) -> bool

Returns true when v is a power of two (zero is not).

align_up

inline fn align_up(v: usize, align: usize) -> usize

Rounds v up to the next multiple of align.

Parameters

  • align: A power of two.

dangling

inline fn dangling(align: usize) -> unsafe *u8

Returns a non-null, aligned pointer that owns nothing: what a zero-size request gets.

Safety

Never dereference or free the result.

realloc_by_copy

fn <A impl Allocator> realloc_by_copy(
    ptr: unsafe *u8,
    old_size: usize,
    new_size: usize,
    align: usize
) -> unsafe *u8

A realloc for an allocator with no faster path: allocates, copies, frees the old block.

Returns &null, with the original block untouched, when the new allocation fails.

Safety

ptr must be a block A allocated with old_size and align.

set_oom_handler

fn set_oom_handler(h: fn(usize, usize) -> !)

Replaces what create does when the allocator returns &null.

The handler never returns. The default writes kairo: out of memory to stderr and exits with code 134. The handler is process-wide, not per thread, and the call is not synchronized.

oom

fn oom(size: usize, align: usize) -> !

Runs the OOM handler for a failed allocation of size bytes at align; never returns.

ScopeFrame

struct ScopeFrame

One active scope: the allocator's static functions as pointers, the range snapshot taken when it opened, and the bounding box of it and every scope below it. 64 bytes on a 64-bit target: one cache line.

ScopeChunk

struct ScopeChunk

SCOPE_CHUNK frames, plus their ranges packed for the scan: lo[i + 1] and span[i + 1] = hi - lo for frames[i], so a hit is one subtract and one unsigned compare. Slot 0 is a sentinel (lo 0, span MAX) that every address hits, which ends the scan of a chunk without a bounds check.

alloc_raw

inline fn alloc_raw(size: usize, align: usize) -> unsafe *u8

Allocates size bytes from the allocator in effect: the innermost open AllocatorScope, else Global. Returns &null on failure.

A zero size returns dangling(align). Pair the block with free_raw.

Parameters

  • align: A power of two (asserted).

Safety

The result is uninitialized memory; free it with free_raw using the same size and align.

free_raw

inline fn free_raw(ptr: unsafe *u8, size: usize, align: usize)

Gives a block back to whichever allocator it came from: the open scope whose range holds it, else Global.

With no scope open this is one thread-local load, one test and a tail call to Global::free. A zero size is a no-op.

Safety

ptr must come from alloc_raw / resize_raw (or dangling for size 0), with the size and align it was last allocated with.

Uninitialized

class <T> Uninitialized

Storage for one T that holds no T. Move-only.

Turn it into an object with construct, or give it back with release_in. This is the typed layer between an allocator's bytes and a live object.

Uninitialized.Uninitialized

inline fn Uninitialized(self, ptr: unsafe *u8)

Wraps raw bytes. The caller vouches for size and alignment.

Uninitialized.construct

inline fn <...A> construct(self, ...args: A) -> *T

Builds a T in the storage. The only safe way to a *T.

Uninitialized.assume_init

inline fn assume_init(
    self
) -> *T requires __builtin_is_trivially_copyable<T>() && __builtin_is_trivially_destructible<T>()

For a T whose bytes have all been written through as_bytes. Only for types that come into being when their bytes are written (trivially copyable and destructible).

Uninitialized.as_bytes

inline fn as_bytes(const self) -> unsafe *u8

Returns the storage as raw bytes.

Uninitialized.retype

fn <U> retype(self) -> Uninitialized<U> requires sizeof U <= sizeof T && alignof U <= alignof T

Reinterprets the storage for a U of the same or smaller size and alignment. Nothing lives in it either way.

uninitialized_from

inline fn <T> uninitialized_from(p: *T) -> Uninitialized<T>

Runs *p's destructor and returns its storage as an Uninitialized<T>.

Safety

p must point at a live T that is not used afterwards.

UninitializedSlice

class <T> UninitializedSlice

Storage for cap Ts, of which [0, len) are live.

Dropping it destroys only the live prefix; the storage itself goes back through destroy_slice or destroy_slice_in. Move-only: a moved-from slice is left empty so its drop destroys nothing.

UninitializedSlice.UninitializedSlice

inline fn UninitializedSlice(self, ptr: unsafe *u8, cap: usize)

Wraps cap Ts' worth of raw bytes, with none of them live.

Safety

ptr must be valid for cap * sizeof T bytes at alignof T.

size

inline fn size(const self) -> usize

Returns the number of live elements.

capacity

inline fn capacity(const self) -> usize

Returns the number of elements the storage has room for.

as_bytes

inline fn as_bytes(const self) -> unsafe *u8

Returns the storage as raw bytes.

push

inline fn <...A> push(self, ...args: A)

Constructs a T from args in the next free slot.

Panics

Traps (__builtin_trap) when the slice is full.

finish

inline fn finish(self) -> *T

Every slot is live: ownership of the cap Ts moves to the caller, who destroys them and frees capacity() * sizeof T bytes.

clear

fn clear(self)

Destroys the live prefix, last first. Idempotent.

Generated from Kairo source, Apache-2.0 WITH AdditionRef-Kairo-Runtime-exception.