Memory Layout Spec
Overview
Section titled “Overview”zerobuf defines a binary layout in WebAssembly.Memory that any language can read/write. This page is the spec — if you’re implementing a binding for a new language, this is all you need.
All values are little-endian. All offsets are byte offsets from the start of WASM linear memory.
Tagged Value Slot (16 bytes)
Section titled “Tagged Value Slot (16 bytes)”Every value occupies a fixed 16-byte slot:
Offset Size Field0 1 tag (enum, see below)1-3 3 padding (unused)4 4 payload A (i32/bool/ptr)8 8 payload B (f64)The tag determines which payload fields are used:
| Tag | Value | Payload A (offset +4) | Payload B (offset +8) |
|---|---|---|---|
Null | 0 | unused | unused |
Bool | 1 | 0 or 1 (u32) | unused |
I32 | 2 | value (i32) | unused |
F64 | 3 | unused | value (f64) |
String | 4 | string header ptr (u32) | unused |
Array | 5 | array handle ptr (u32) | unused |
Object | 6 | object handle ptr (u32) | unused |
BigInt | 7 | unused | value (i64) |
Bytes | 8 | byte buffer header ptr (u32) | unused |
String
Section titled “String”Offset Size Field0 4 byteLength (u32) — UTF-8 byte count4 byteLength UTF-8 bytes (no null terminator)Strings are immutable once written. To change a string property, allocate a new string and update the pointer in the value slot.
Same layout as String. The tag distinguishes them (String = UTF-8 text, Bytes = raw binary).
Offset Size Field0 4 byteLength (u32)4 byteLength raw bytesBigInt
Section titled “BigInt”Stored as i64 in payload B (offset +8). Same slot layout as F64 but interpreted as a signed 64-bit integer.
Arrays use handle indirection — a 4-byte cell holds the current data pointer. When the array grows beyond capacity, a new data block is allocated and the handle is updated. Proxies capture the handle address, so they survive realloc.
Handle (4 bytes)
Section titled “Handle (4 bytes)”Offset Size Field0 4 data pointer (u32) — points to array dataData Block
Section titled “Data Block”Offset Size Field0 4 capacity (u32)4 4 length (u32)8 16 * cap value slots (VALUE_SLOT each)Element i is at data offset 8 + i * 16.
Growth
Section titled “Growth”When length == capacity, allocate a new data block with capacity * 2, copy existing elements, write the new element, and update the handle to point to the new data. Old data is abandoned (arena is append-only).
Object
Section titled “Object”Objects also use handle indirection, same as arrays.
Handle (4 bytes)
Section titled “Handle (4 bytes)”Offset Size Field0 4 data pointer (u32) — points to object dataData Block
Section titled “Data Block”Offset Size Field0 4 capacity (u32)4 4 count (u32) — number of properties8 24 * cap entries (OBJECT_ENTRY each)Entry (24 bytes)
Section titled “Entry (24 bytes)”Offset Size Field0 4 key pointer (u32) — points to UTF-8 key bytes in memory4 4 key byte length (u32)8 16 value (VALUE_SLOT — tagged value)The JS library uses Object.defineProperty with captured entry indices — each key’s offset is resolved once at creation time. Reads and writes are O(1). The binary layout stores entries sequentially, so a new language binding would do a linear scan on first access to build its own index.
Growth
Section titled “Growth”Same as arrays: when count == capacity, allocate capacity * 2, copy entries, append new entry, update handle.
Arena Allocator
Section titled “Arena Allocator”All allocations use a bump allocator over WebAssembly.Memory:
- Alignment:
offset = (offset + align - 1) & ~(align - 1)before each alloc - Growth: doubling strategy —
memory.grow(max(needed, current * 2)) - Max: 65535 pages (4GB minus 64KB — avoids Chrome unsigned overflow at exactly 4GB)
- No free: arena is append-only. Realloc allocates new and abandons old.
Shared Arena State (planned)
Section titled “Shared Arena State (planned)”For Zig/Rust WASM modules that need to allocate zerobuf objects, the arena offset will be stored at a fixed address in memory. Both JS and WASM read/write the same offset — no import call needed.
Alignment
Section titled “Alignment”| Type | Alignment |
|---|---|
| Value slot | 4 bytes |
| String header | 1 byte |
| String bytes | 1 byte |
| Array/object data | 4 bytes |
| Handle | 4 bytes |
| F64 payload | aligned to offset +8 within the 16-byte slot |