zena:component-abi
import {…} from 'zena:component-abi';
Functions
realloc
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
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
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
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
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
function stringToMemory(s: String): (i32, i32)
Stage a String's bytes for a lowered call.
bytesToMemory
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
function resultArea(size: i32): i32
Allocate the return area a spilled result is written into.
liftStringAt
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
function liftBytesAt(ptr: i32): FixedArray<u8>
A list<u8> lifted in place, with the same ownership story.
freeLifted
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
function loadU8(ptr: i32): i32
loadS8
function loadS8(ptr: i32): i32
loadU16
function loadU16(ptr: i32): i32
loadS16
function loadS16(ptr: i32): i32
loadI32
function loadI32(ptr: i32): i32
loadI64
function loadI64(ptr: i32): i64
loadF32
function loadF32(ptr: i32): f32
loadF64
function loadF64(ptr: i32): f64
storeU8
function storeU8(ptr: i32, value: i32): void
storeU16
function storeU16(ptr: i32, value: i32): void
storeI32
function storeI32(ptr: i32, value: i32): void
storeI64
function storeI64(ptr: i32, value: i64): void
storeF32
function storeF32(ptr: i32, value: f32): void
storeF64
function storeF64(ptr: i32, value: f64): void
stageBuffer
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
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
function stageByteList(bytes: FixedArray<u8>): (i32, i32)
Stage a byte buffer, arena-tracked: list<u8> and string have
the same wire shape.
releaseStaged
function releaseStaged(): void
Free everything staged since the last release.