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 spawn actor allocs.bump_bytes . .
var c usize send bump. init mem. . .
let b option mut slice u8 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 core op.

export trait byte_allocator do
  reserve input s self . input n usize . output option mut slice u8 . effects state via self .
  grow    input s self . input old mut slice u8 . input newn usize . output option mut slice u8 . effects state via self .
  used    input s self . output usize . 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 usize → option mut slice u8none — the cursor does not move (no partial allocation)
growold mut slice u8, newn usize → 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→ usize (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 . + use al a . + requires allocs.byte_allocator a. . .. Callers pass it not as a positional argument but with let x use <source>. …, 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 .
  use al a .
  output usize .
  effects state via a .
  requires allocs.byte_allocator a. . .
do
  let p option mut slice u8 send al. reserve 3 . .
  guard is_some p. . else return 91 . .
  let q option mut slice u8 send al. reserve 5 . .
  guard is_some q. . else return 92 . .
  let g option mut slice u8 send al. grow some_value q. . 9 . .
  return send al. used . .
end .

proc borrowed2 input buf mut slice u8 . output usize . effects state . do
  var b allocs.bump_bytes spawn actor allocs.bump_bytes . .
  let c usize send b. init buf. . .
  let n use b. usize 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.