zena:wasi

zena
import {…} from 'zena:wasi';

Low-level WebAssembly Component Model and WASI runtime integration.

Provides Canonical ABI string marshaling, memory management hooks, asynchronous subtask integration for futures, and byte stream bridges.

Classes

ByteStreamPair

zena
final class ByteStreamPair
Re-exported from zena:wasi/stream.zena

A fresh stream's two ends.

Constructors
zena
new()
#
Properties
zena
readable: ReadableByteStream
#
zena
writable: WritableByteStream
#

Type aliases

Waitable

zena
type Waitable = i32
Re-exported from zena:wasi/async.zena

A waitable's handle: a subtask, or a stream or future end. One host table holds them all, so one type names them all.

WaitableSet

zena
type WaitableSet = i32
Re-exported from zena:wasi/async.zena

A waitable set's handle. Zero is never one — the host never hands out 0 — which is what lets a field mean "no set yet".

CallbackCode

zena
type CallbackCode = i32
Re-exported from zena:wasi/async.zena

What the entry and the callback return to the host: the low four bits are the code — EXIT or WAIT — and, for WAIT, the rest is the waitable set to wake the task for, so a WAIT is WAIT | (set << 4).

ReadableByteStream

zena
type ReadableByteStream = i32
Re-exported from zena:wasi/stream.zena

The two ends of a stream<u8>, as unforgeable handle types: a readable end is what a WIT import like write-via-stream is handed, a writable end is what the guest keeps and writes, and nothing but newByteStreamPair can produce either. On the wire each is a component-model handle index (a core i32).

WritableByteStream

zena
type WritableByteStream = i32
Re-exported from zena:wasi/stream.zena

Functions

realloc

zena
function realloc(ptr: i32, oldSize: i32, align: i32, newSize: i32): i32
Re-exported from zena:wasi/abi.zena

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
Re-exported from zena:wasi/abi.zena

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
Re-exported from zena:wasi/abi.zena

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.

takeStringArgument

zena
function takeStringArgument(ptr: i32, len: i32): String
Re-exported from zena:wasi/abi.zena

A string argument taken whole: copied out, its buffer released. For a string inside an aggregate parameter, where the generated lift is one expression.

takeBytesArgument

zena
function takeBytesArgument(ptr: i32, len: i32): FixedArray<u8>
Re-exported from zena:wasi/abi.zena

A list<u8> argument taken whole, with the same ownership story.

stringResultArea

zena
function stringResultArea(s: String): i32
Re-exported from zena:wasi/abi.zena

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.

notePostReturn

zena
function notePostReturn(mark: i32): void
Re-exported from zena:wasi/abi.zena

Note that the return area a wrapper is about to return, and everything staged since mark (a string's bytes, a list's elements — what the area points into), stays allocated until the host's post-return.

postReturn

zena
function postReturn(area: i32): void
Re-exported from zena:wasi/abi.zena

The post-return option: free the return area the host has just read — a staged range a wrapper noted, or else 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 result per call.

stringToMemory

zena
function stringToMemory(s: String): (i32, i32)
Re-exported from zena:wasi/abi.zena

Stage a String's bytes for a lowered call.

bytesToMemory

zena
function bytesToMemory(bytes: FixedArray<u8>): (i32, i32)
Re-exported from zena:wasi/abi.zena

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
Re-exported from zena:wasi/abi.zena

Allocate the return area a spilled result is written into.

liftStringAt

zena
function liftStringAt(ptr: i32): String
Re-exported from zena:wasi/abi.zena

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>
Re-exported from zena:wasi/abi.zena

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

freeLifted

zena
function freeLifted(ptr: i32): void
Re-exported from zena:wasi/abi.zena

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
Re-exported from zena:wasi/abi.zena

loadS8

zena
function loadS8(ptr: i32): i32
Re-exported from zena:wasi/abi.zena

loadU16

zena
function loadU16(ptr: i32): i32
Re-exported from zena:wasi/abi.zena

loadS16

zena
function loadS16(ptr: i32): i32
Re-exported from zena:wasi/abi.zena

loadI32

zena
function loadI32(ptr: i32): i32
Re-exported from zena:wasi/abi.zena

loadI64

zena
function loadI64(ptr: i32): i64
Re-exported from zena:wasi/abi.zena

loadF32

zena
function loadF32(ptr: i32): f32
Re-exported from zena:wasi/abi.zena

loadF64

zena
function loadF64(ptr: i32): f64
Re-exported from zena:wasi/abi.zena

storeU8

zena
function storeU8(ptr: i32, value: i32): void
Re-exported from zena:wasi/abi.zena

storeU16

zena
function storeU16(ptr: i32, value: i32): void
Re-exported from zena:wasi/abi.zena

storeI32

zena
function storeI32(ptr: i32, value: i32): void
Re-exported from zena:wasi/abi.zena

storeI64

zena
function storeI64(ptr: i32, value: i64): void
Re-exported from zena:wasi/abi.zena

storeF32

zena
function storeF32(ptr: i32, value: f32): void
Re-exported from zena:wasi/abi.zena

storeF64

zena
function storeF64(ptr: i32, value: f64): void
Re-exported from zena:wasi/abi.zena

stageBuffer

zena
function stageBuffer(size: i32): i32
Re-exported from zena:wasi/abi.zena

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)
Re-exported from zena:wasi/abi.zena

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)
Re-exported from zena:wasi/abi.zena

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

stagedMark

zena
function stagedMark(): i32
Re-exported from zena:wasi/abi.zena

The arena's current position, taken before a wrapper stages anything. Release is by range (releaseStagedFrom), not pop-all: an async-lowered call's arguments may be lifted by the callee after the call returns (the STARTING state), so a wrapper suspended on its subtask still owns live staging — and any wrapper that ran in the meantime must free only its own.

releaseStagedFrom

zena
function releaseStagedFrom(mark: i32): void
Re-exported from zena:wasi/abi.zena

Free everything staged since mark. Ranges nest like the calls that created them, so popping to a mark never frees an outer wrapper's still-live staging.

waitableSetNew

zena
declare function waitableSetNew(): WaitableSet
Re-exported from zena:wasi/async.zena

waitableJoin

zena
declare function waitableJoin(waitable: Waitable, set: WaitableSet): void
Re-exported from zena:wasi/async.zena

Put waitable into set; a set of 0 removes it from whichever set it is in. A join is persistent until the next join.

beginTask

zena
function beginTask(): void
Re-exported from zena:wasi/async.zena

Start a task. Called by the synthesized adapter at the top of every export the host lifted async — the entry, a service's handle — before the program's function runs, so what that function waits on and returns belongs to the new task and not to whichever task ran last. Without this, two requests in flight in one instance would share a context: both joined to one waitable set, the second value parked where the first was already returned, and the host answering the wrong request or none.

awaitPacked

zena
function awaitPacked(packed: i32): Future<void>
Re-exported from zena:wasi/async.zena

An async-lowered import's packed result, as a future. packed is the raw (subtask << 4) | state the lowering returned (see STATE_MASK), taken as an i32 because that is what a raw import hands back.

State RETURNED means the call finished inside the lowering — the timer had already expired, the write already flushed — and the future arrives completed, with no subtask to own. Anything else is a subtask: joined to the running task's set, registered, resolved by the callback.

awaitWaitable

zena
function awaitWaitable(end: i32): Future<i32>
Re-exported from zena:wasi/async.zena

A stream or future end's next event, as a future of the packed (progress << 4) | CopyResult payload. The caller has just issued an async copy on end and got BLOCKED back: the copy is in flight, and the payload arrives when the host has moved what it can. end is the raw handle as the copy builtin took it.

finishTask

zena
async function finishTask<T>(result: Future<T>, ret: (value: T) => void): Future<void>
Re-exported from zena:wasi/async.zena

Hand the running task's result to the host once result settles: ret is the typed task.return for the task's result type, and the driver calls it from the task's own entry — the one running now if the value is already known by the time the entry asks what to do next, else the callback re-entry after which it is. A failure is reported from that entry instead. Called by the entry wrapper the compiler writes for an async main that returns a value (lib/component-entry.zena in the compiler).

componentPoll

zena
function componentPoll(): CallbackCode
Re-exported from zena:wasi/async.zena

Called by the synthesized entry after main returns: run the microtask checkpoint, then either exit or ask to be woken.

componentResume

zena
function componentResume(event: i32, fired: i32, code: i32): CallbackCode
Re-exported from zena:wasi/async.zena

The callback the host calls when a waitable fires. It resolves the completer parked on that waitable, runs the microtask checkpoint — the runtime's, at the host boundary, where a JS engine runs its own — and answers for the task that was waiting on the waitable.

A SUBTASK event's code is its new state — a subtask is delivered more than once, and only RETURNED resolves anything (dropping an unresolved subtask traps, so the state is checked rather than assumed). A stream or future event's code is the packed copy payload, delivered exactly once per parked copy, and it resolves the end's completer with the payload for the parked reader or writer to decode.

newByteStreamPair

zena
function newByteStreamPair(): ByteStreamPair
Re-exported from zena:wasi/stream.zena

writeStringBlocking

zena
function writeStringBlocking(end: WritableByteStream, s: String, newline: boolean): void
Re-exported from zena:wasi/stream.zena

Write a String's bytes to a writable end, appending a newline when asked, blocking until every byte is consumed. The String staging is the one convenience the current consumer (zena:console) needs; a byte-buffer entry point over the same flushBlocking core arrives with its first consumer, the Stream<u8> boundary binding.

lowerByteStream

zena
function lowerByteStream(source: Stream<u8>): i32
Re-exported from zena:wasi/stream.zena

A guest Stream<u8> as a canonical stream<u8> value: a fresh pair, a background pump copying the guest stream into the writable end, and the readable end — which IS the transferable value — returned as the wire handle. The pump ends the canonical stream when the source ends, and stops the source when the host reader drops its end.

liftByteStream

zena
function liftByteStream(handle: i32): Stream<u8>
Re-exported from zena:wasi/stream.zena

A canonical stream<u8> handle as a guest Stream<u8>: a background pump reads the readable end on the event loop and delivers into a StreamWriter, whose stream is returned — with the writer's backpressure, so the pump never reads the host faster than the guest consumes. The pump closes the stream when the host drops its writable end, and drops the readable end when the guest reader stops.

Symbols

rawHandle

zena
symbol rawHandle
Re-exported from zena:wasi/abi.zena

The marshaling keys every synthesized resource class shares. A WIT resource wrapper exposes its wire handle and its moved flag through symbol-keyed members, and the symbols live HERE — one module, one identity — because a synthesized interface may hand its resources to another synthesized interface (use types.{request}), and a per-module symbol would make the consumer's [rawHandle] a different member than the class declares.

markMoved

zena
symbol markMoved
Re-exported from zena:wasi/abi.zena