Lowent Manual←↑→

allocs — allocator traits, bumps and default allocators

Source
lib/alloc.low
Layer
L1 — storage
Capabilities
none for bumps · fixed_bytes spawns only where cap allocator is held · heap_bytes only where cap heap is held

The allocator interface (two traits) and four actors satisfying it. The most common use is a bump that cuts pieces from the front of a large borrowed byte block — suited to data “made in many pieces, discarded all at once” (a parser’s temporary nodes, strings living for one pass) (chapter 35).

var bump allocs.bump_bytes be spawn actor allocs.bump_bytes . .
var c u64 be send bump init mem .
let b option mut slice u8 . be send bump reserve 64 .

The root and what sits on it. Lowent has no implicit global heap. The only place bytes first enter a program is the root op alloc_bytes <capability> capacity n, and there are two roots — a fixed window (cap allocator, effect alloc; on bare metal the linker sets the window’s bounds) and a heap (cap heap, effect heap; only on hosted systems with an operating system) (chapter 20). allocs handles what sits on the root. It does not ask whether the bytes came from a root or were lent by a caller. So code without cap allocator still allocates fully on bytes someone else gave — capabilities divide abilities. It is written with actors, traits, options and subslices only, so it is a library, not a builtin.

export trait byte_allocator do
  reserve input s self . input n u64 . output option mut slice u8 . . effects state via self .
  grow    input s self . input old mut slice u8 . . input newn u64 . output option mut slice u8 . . effects state via self .
  used    input s self . output u64 . effects state .
end

freeing_allocator adds release input s self . input v mut slice u8 . . output bool . to the same three. via self means “this op’s effect is the effect the implementation declares” — a bump’s reserve is just state, while heap_bytes’s reserve is heap state. So heap appears in the signature of container ops monomorphised with that allocator.

actorBacking · policyreserve effect · where usable
bump_bytesbuffer attached with init · advances the cursor by the request · both traitsstate · anywhere
bump_alignedsame backing · rounds the start up to a multiple of 8 before cutting (padding is lost)state · anywhere
fixed_bytescapability slot root cap allocator · carves straight from the rootalloc state · anywhere (the linker window on bare metal)
heap_bytescapability slot root cap heapheap state · hosted only (E-HEAP-NOHOST on bare metal)

Table 50.1 — Actors of allocs

opShapeFailure
init (outside the trait)backing mut slice u8 → u64 (capacity), effects statenone. Calling again switches to the new buffer with the cursor at 0
reserven u64 → option mut slice u8none — the cursor does not move (no partial allocation)
growold mut slice u8, newn u64 → option mut slice u8none — not the last piece, or no room
releasev mut slice u8 → boolfalse — changes nothing unless it is the last piece
used→ u64 (cursor position, including padding), effects nonenone

Table 50.2 — Ops of the bumps

Code taking an allocator accepts any implementation with input comptime a type . + using al a . + requires allocs.byte_allocator a .. Callers pass it not as a positional argument but with let x … using <source> be …, and if the op has only one source it is the default without being written. Monomorphisation means no vtables and no indirect calls.

proc two_from .
  input comptime a type .
  using al a .
  output u64 .
  effects state via a .
  requires allocs.byte_allocator a .
do
  let p option mut slice u8 . . be send al reserve 3 .
  guard is_some p . else return 91 .
  let q option mut slice u8 . . be send al reserve 5 .
  guard is_some q . else return 92 .
  let g option mut slice u8 . . be send al grow (some_value q) 9 .
  return send al used .
end

proc borrowed2 input buf mut slice u8 . . output u64 . effects state . do
  var b allocs.bump_bytes be spawn actor allocs.bump_bytes . .
  let c u64 be send b init buf .
  let n u64 using b be two_from .
  return n .
end

With a bump, 3 + 5 grown to 9 gives 12; with bump_aligned the second piece starts at 8, giving 17; with heap_bytes, grow is none and heap appears in the instance’s signature.

Counter-example. Unwrapping without checking none

some_value (send a reserve 99) stops with E-VM-NONE when the buffer is small. It compiles, so it is easy to be careless.

Counter-example. Reaching the root without capability · sending from a fn

An op without a root capability cannot call alloc_bytes (E-ALLOC-NOCAP, E-HEAP-NOCAP for the heap) — allocs is not a substitute. Calling handlers from an effects none fn is E-EFFECT-CALC. Passing a source that does not satisfy the trait is E-BOUND-UNSAT.

Cautions. Nothing but the last piece can be freed — for take-and-release patterns, pool fits. Scope-based bulk release belongs to region blocks (chapter 18). Views from reserve alias the backing buffer and do not vanish when init is called again. Actors assume sequential delivery — passing a bump to a task is rejected (E-ALLOC-SHARED: an allocator handed to a task must have an atomic reserve). The floor starts at 0 and no addresses are exposed, so VM and native see the same bytes.