Skip to content

Memory Layout Spec

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.

Every value occupies a fixed 16-byte slot:

Offset Size Field
0 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:

TagValuePayload A (offset +4)Payload B (offset +8)
Null0unusedunused
Bool10 or 1 (u32)unused
I322value (i32)unused
F643unusedvalue (f64)
String4string header ptr (u32)unused
Array5array handle ptr (u32)unused
Object6object handle ptr (u32)unused
BigInt7unusedvalue (i64)
Bytes8byte buffer header ptr (u32)unused
Offset Size Field
0 4 byteLength (u32) — UTF-8 byte count
4 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 Field
0 4 byteLength (u32)
4 byteLength raw bytes

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.

Offset Size Field
0 4 data pointer (u32) — points to array data
Offset Size Field
0 4 capacity (u32)
4 4 length (u32)
8 16 * cap value slots (VALUE_SLOT each)

Element i is at data offset 8 + i * 16.

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).

Objects also use handle indirection, same as arrays.

Offset Size Field
0 4 data pointer (u32) — points to object data
Offset Size Field
0 4 capacity (u32)
4 4 count (u32) — number of properties
8 24 * cap entries (OBJECT_ENTRY each)
Offset Size Field
0 4 key pointer (u32) — points to UTF-8 key bytes in memory
4 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.

Same as arrays: when count == capacity, allocate capacity * 2, copy entries, append new entry, update handle.

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.

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.

TypeAlignment
Value slot4 bytes
String header1 byte
String bytes1 byte
Array/object data4 bytes
Handle4 bytes
F64 payloadaligned to offset +8 within the 16-byte slot