zena:component-abi

zena
import {…} from 'zena:component-abi';

Functions

realloc

zena
function realloc(ptr: i32, oldSize: i32, align: i32, newSize: i32): i32

The canonical ABI's allocator, as the realloc option names it.

The host calls this to build values in the guest's memory before handing them over — a string argument to an export arrives already copied into a buffer this returned. Its contract is C's realloc plus an alignment: ptr == 0 allocates, newSize == 0 frees, and anything else moves.

align is not passed on to the allocator. FreeListAllocator puts an 8-byte header in front of every block and rounds every size up to 8, so the pointers it hands out are already 8-aligned, and the canonical ABI never asks for more than 8. Going through allocAligned instead would align a pointer inside a block and return that, which free cannot then look up.

Exhaustion throws, and this is the one function in the language the host calls with no Zena frame above it — lowering an argument happens before the export is entered. So the throw unwinds out of the module and becomes a trap. That is the right outcome: returning 0 would make the canonical ABI write the value at address 0 and carry on.

stringFromMemory

zena
function stringFromMemory(ptr: i32, len: i32): String

A string as the guest sees it: len bytes of UTF-8 at ptr, copied into a fresh Zena String.

Copies rather than viewing, and does not free: a Zena String is a view over a garbage-collected byte array, and there is no linear-memory buffer it could keep. Whether the buffer was the guest's to release is the caller's to know — see releaseStringArgument for the case where it is.

releaseStringArgument

zena
function releaseStringArgument(ptr: i32, len: i32): void

Free the buffer a string argument arrived in.

Lowering an argument into an export uses the lifted function's realloc, so the bytes come out of this allocator and are the guest's to release once they have been copied. The canonical ABI's post-return is specified for the return value and cannot reach them; a guest that never calls this leaks a block per string argument, and the free list never gets it back.

The guard is on the length rather than the pointer. A zero-length string is lowered through realloc(0, 0, 1, 0), which returns align — an aligned address that was never allocated. Freeing that would walk into a block header that is not there.

stringResultArea

zena
function stringResultArea(s: String): i32

A string result, as the host reads it: the bytes copied into linear memory, and the address of an 8-byte area holding (ptr, len).

A single string flattens to two core values, one more than a lifted function may return, so the canonical ABI returns it indirectly: the core function's i32 result is this area's address, and the host reads the pair out of it. postReturn frees both halves once it has.

The bytes are copied verbatim, which assertWtf8 guards — true for every string a component can build today, since WTF-16 strings come from JS interop and the js target is not this one, but an assertion rather than an assumption.

postReturn

zena
function postReturn(area: i32): void

The post-return option: free what stringResultArea allocated.

The host calls this after it has read the result out, which is the only moment at which the guest can know the buffer is dead. Without it the component still works and leaks a string per call.

stringToMemory

zena
function stringToMemory(s: String): (i32, i32)

Stage a String's bytes for a lowered call.

bytesToMemory

zena
function bytesToMemory(bytes: FixedArray<u8>): (i32, i32)

Stage a byte buffer for a lowered call: list<u8> and string have the same wire shape. The cast widens: Memory's indexer trades in i32 — the language has no implicit numeric conversions, widening included — so the u8 element is spelled up to it.

resultArea

zena
function resultArea(size: i32): i32

Allocate the return area a spilled result is written into.

liftStringAt

zena
function liftStringAt(ptr: i32): String

A string lifted in place: the (ptr, len) pair at ptr, its bytes copied out and the buffer they arrived in freed — the canonical ABI allocated it through realloc above, so it is ours. The eight bytes of the pair itself belong to whatever contains them (a return area, a record field, a list element) and stay put.

liftBytesAt

zena
function liftBytesAt(ptr: i32): FixedArray<u8>

A list<u8> lifted in place, with the same ownership story.

freeLifted

zena
function freeLifted(ptr: i32): void

Free a host-written allocation once its contents are lifted: a return area, or a list's element buffer.

loadU8

zena
function loadU8(ptr: i32): i32

loadS8

zena
function loadS8(ptr: i32): i32

loadU16

zena
function loadU16(ptr: i32): i32

loadS16

zena
function loadS16(ptr: i32): i32

loadI32

zena
function loadI32(ptr: i32): i32

loadI64

zena
function loadI64(ptr: i32): i64

loadF32

zena
function loadF32(ptr: i32): f32

loadF64

zena
function loadF64(ptr: i32): f64

storeU8

zena
function storeU8(ptr: i32, value: i32): void

storeU16

zena
function storeU16(ptr: i32, value: i32): void

storeI32

zena
function storeI32(ptr: i32, value: i32): void

storeI64

zena
function storeI64(ptr: i32, value: i64): void

storeF32

zena
function storeF32(ptr: i32, value: f32): void

storeF64

zena
function storeF64(ptr: i32, value: f64): void

stageBuffer

zena
function stageBuffer(size: i32): i32

Allocate size bytes of staging space, freed by releaseStaged. Zero bytes is zero: realloc(…, 0) returns an unowned aligned sentinel that must never reach free.

stageString

zena
function stageString(s: String): (i32, i32)

Stage a String's bytes, arena-tracked: (ptr, len), with (0, 0) for an empty string — nothing is allocated, nothing to free.

stageByteList

zena
function stageByteList(bytes: FixedArray<u8>): (i32, i32)

Stage a byte buffer, arena-tracked: list<u8> and string have the same wire shape.

releaseStaged

zena
function releaseStaged(): void

Free everything staged since the last release.