The runtime ABI is the byte-level contract shared by generated WebAssembly,
atoll-runtime-abi, and the host runtime. A field rename in Rust is harmless;
changing a size, offset, tag, or interpretation requires coordinated ABI
evolution.
Generated constants and compile-time layout assertions are authoritative. Older design documents are not.
Address types
| Type | Representation | Meaning |
|---|---|---|
UnitId |
u32 |
program-wide compiled Unit |
GroupId |
u32 |
link-group artifact |
MethodId |
u16 |
method within a Unit |
FunctionId |
u32 |
dense continuation function in one build |
FrameOffset |
u32 |
arena-relative frame handle |
| TCB handle | u32 |
arena-relative task handle |
Offset 0 is null. Function IDs are build-local and are not stable migration
identities. The 32-bit offset space gives one arena a 4 GiB representational
ceiling.
Frame header
FrameHeader is exactly 32 bytes with eight-byte alignment:
| Offset | Field | Type | Meaning |
|---|---|---|---|
0 |
caller_cont |
8 bytes | caller continuation pair |
8 |
pc |
u32 |
packed block and operation position |
12 |
result_tag |
u32 |
tagged completion state |
16 |
result_value |
u64 |
result payload lane |
24 |
parent_cont |
u32 |
parent continuation handle |
28 |
call_link |
u32 |
physical call-link state |
The packed program counter is:
pc = (block_id << 16) | operation_indexcall_link values are:
| Value | Meaning |
|---|---|
0 |
resumed |
1 |
present in physical call stack |
2 |
completed |
Debug stack reconstruction uses the continuation links plus compiler metadata; it must tolerate optimized or missing source frames.
Task block
The task control block is exactly 80 bytes with eight-byte alignment:
| Offset | Field | Type |
|---|---|---|
0 |
parent |
u32 |
4 |
first_child |
u32 |
8 |
next_sibling |
u32 |
12 |
cancelled |
u32 |
16 |
completed |
u32 |
20 |
completed_tag |
u32 |
24 |
debug_stamp |
u32 |
28 |
last_cont_fn |
u32 |
32 |
last_cont_frame |
u32 |
36 |
pending_io |
u32 |
40 |
pending_io_aux |
u32 |
44 |
ready_loop_head |
u32 |
48 |
ready_loop_tail |
u32 |
52 |
ready_resume_head |
u32 |
56 |
ready_resume_tail |
u32 |
60 |
awaiter_head |
u32 |
64 |
detached |
u32 |
68 |
frame_resident |
u32 |
72 |
completed_value |
i64 |
The two pending-I/O words are retained ABI slots but are vestigial in the current slot-based host I/O path.
debug_stamp changes when a TCB offset is reused. Debuggers and external task
references use (TCB offset, debug_stamp), not the offset alone.
Scheduler nodes
| Structure | Size | Purpose |
|---|---|---|
| ready-resume node | 24 bytes | TCB, function, frame, and result |
| awaiter node | 24 bytes | task completion waiter |
| ready-loop node | 12 bytes | loop/yield continuation |
| select group | variable | embeds coordinated awaiters |
All links are arena-relative. Node layouts are internal ABI contracts and should be constructed through generated/runtime helpers.
Streams
A stream control block begins with:
| Offset | Field | Type |
|---|---|---|
0 |
tag | u32 |
4 |
consumer awaiter | u32 |
8 |
head | u32 |
12 |
tail | u32 |
16 |
count | u32 |
20 |
capacity | u32 |
24 |
closed | u32 |
28 |
producer awaiter | u32 |
32 |
data begins | flex |
The low handle bit distinguishes stream handles from naturally aligned task handles. Consumers must mask and validate the tag before resolving an offset.
Results
| Value | Constant |
|---|---|
0 |
OK |
1 |
ERR |
2 |
CANCELLED |
3 |
TIMEOUT |
4 |
ERR_FLAT |
The 64-bit result-value lane may contain a scalar, packed fields, or an arena-relative handle according to the operation contract.
Method entry
The v1 compiled method signature is:
(payload_abs: u32, payload_len: u32, root_tcb: u32) -> ()The payload is copied into the selected Unit instance’s arena before entry.
payload_abs is an absolute address valid for that entry path; root_tcb is
body-relative. The method publishes completion through the root task state and
runtime scheduler rather than a direct Wasm return value.
Arena globals
Before entering generated Unit code, the runtime selects the Unit’s link group and publishes the active arena base, capacity, allocator functions, and collector tables expected by that group. Growth republishes them after relocation.
Generated code must rematerialize absolute addresses after any operation that can relocate the arena. The arena generation is available to detect stale materialization.
Evolution
An ABI change needs all of the following:
- update the canonical ABI crate and generated constants;
- update compiler lowering and runtime reads/writes together;
- change compatibility metadata or layout fingerprints;
- add compile-time size and offset assertions;
- test old/new redeploy and migration rejection;
- update this page and remove conflicting legacy claims.
Adding a Rust field without assigning an ABI offset does not extend the wire
layout. Reordering a repr(C) structure without a coordinated ABI version is a
breaking change.
Invariants
- every offset table is little-endian and byte-exact;
- generated and host code use the same size and alignment constants;
- null offsets are never dereferenced;
- build-local function IDs do not cross incompatible redeploys;
- TCB debug identity includes its reuse stamp;
- reserved or vestigial fields retain their offsets until an ABI version explicitly removes them.