arena
arena hands out blocks of memory from a buffer you provide: a global
array, a stack buffer, a kernel page. It never touches the heap or the OS,
so it works in every profile, t0 included. Allocation is a pointer bump;
freeing is all at once (reset) or back to a saved position (release).
import arena from "arena";The module is @profile(t0) and pure: no imports, no I/O, no global state.
Example
@profile(t0);
import arena from "arena";
struct Point { x: i64; y: i64; }
var memory: [4096]u8;
pub fn main() -> i32 {
var a: arena.Arena = arena.over(memory[..]);
let p: ?*u8 = a.alloc(16, 8); // room for one Point
if (p == null) { return 1; }
let pt: *Point = p! as *Point;
pt.x = 3;
pt.y = 4;
let m: i64 = a.mark();
let scratch: *u8 = a.alloc(1000, 1)!; // temporary work space
scratch[0] = 1;
a.release(m); // scratch is gone, pt stays
return (pt.x + pt.y) as i32; // 7
}A t1 program uses it the same way. Pass *arena.Arena to the functions
that allocate, so they all bump the same cursor:
@profile(t1);
import io from "io";
import arena from "arena";
var scratch: [1024]u8;
// Takes `n` bytes from the arena, or reports that it is full.
fn take(a: *arena.Arena, n: i64) -> bool {
let p: ?*u8 = a.alloc(n, 8);
if (p == null) { return false; }
let bytes: *u8 = p!;
for (i in 0..n) { bytes[i] = 0; }
return true;
}
pub fn main() -> i32 {
var a: arena.Arena = arena.over(scratch[..]);
for (round in 0..3) {
let m: i64 = a.mark();
var blocks: i64 = 0;
while (take(&a, 100)) { blocks = blocks + 1; }
io.println("round {round}: {blocks} blocks"); // 9 blocks each round
a.release(m);
}
return 0;
}Arena
pub struct Arena {
base: *u8; // start of your memory
size: i64; // its length in bytes
used: i64; // bytes handed out so far, padding included
}You can read the fields: a.used is what is in use, a.size - a.used what
is left. Don’t write them; the functions keep 0 <= used <= size.
An Arena is a value. Copying it copies the cursor, not the memory, so two
copies would hand out the same bytes. Keep one per buffer and pass a
pointer to it.
arena.over(buf: []u8) -> Arena
An empty arena over buf. The arena borrows buf: the memory has to
outlive every pointer the arena hands out. Over an empty buf the arena
gives nothing: every alloc returns null.
var memory: [4096]u8;
var a: arena.Arena = arena.over(memory[..]);a.alloc(size: i64, align: i64) -> ?*u8
size bytes at an address that is a multiple of align, or null when
the rest of the buffer is too small. A null result leaves the arena
unchanged, so you can try a smaller request.
- The bytes are not zeroed.
- Padding to reach the alignment counts as used. The address is aligned, so a buffer that doesn’t start aligned still gives aligned blocks.
- A request for 0 bytes succeeds while the aligned position is inside the buffer or at its end. Don’t dereference the pointer it returns.
- Traps if
sizeis negative oralignis not a power of two.
let p: ?*u8 = a.alloc(64, 16);
if (p == null) { return 1; } // out of space
let block: *u8 = p!;a.reset()
Frees everything: the whole buffer is available again. Pointers you got before must no longer be used.
a.mark() -> i64
The current position, to pass to release later.
a.release(mark: i64)
Frees everything allocated since mark was taken; blocks from before stay.
Marks nest like a stack: releasing to an older mark frees what came after
the newer ones too.
let m: i64 = a.mark();
let tmp: *u8 = a.alloc(256, 1)!;
// ... use tmp ...
a.release(m); // tmp is freedTraps if mark is negative or beyond the current position: a mark
taken after one you already released, or before a reset.
Alignment helpers
arena.align_up(x: i64, align: i64) -> i64
x rounded up to the next multiple of align (x itself if it already
is one). Traps if align is not a power of two.
arena.align_up(13, 8) // 16
arena.align_up(16, 8) // 16arena.is_aligned(x: i64, align: i64) -> bool
Whether x is a multiple of align. Traps if align is not a power
of two.
arena.is_aligned(64, 16) // true
arena.is_aligned(6, 4) // falseTraps
Each trap is a broken contract, a mistake in the calling code, and stops
the program with trap: <message> at <file>:<line>:<col> (exit code 101):
| Message | Cause |
|---|---|
arena.align_up: align is not a power of two | align is 0, negative, or not 1, 2, 4, 8, … |
arena.is_aligned: align is not a power of two | as above |
arena.alloc: align is not a power of two | as above |
arena.alloc: negative size | size < 0 |
arena.release: mark is beyond the current position | mark < 0 or mark > used |
Running out of space is not a trap: alloc returns null.
For heap memory in t1 code, use mem.
Last modified: