“Flat” describes the canonical in-arena representation of an Atoll value. A flat value uses fixed-width scalar fields and arena-relative handles rather than native pointers. The complete object graph can therefore move with its arena without rewriting internal references.
Flat values are runtime memory layouts. They are not the removed legacy
FlatMessage wire codec, and an arbitrary arena image is not automatically a
stable cross-version serialization format.
Encoding rules
- multi-byte numbers are little-endian;
- primitive values use their natural canonical width, listed below;
Charuses the four-byteu32Unicode scalar representation;- structs use source field order with C-like alignment and padding;
- enums store an
i32discriminant at offset0, followed by the selected payload layout; - references to variable-size blocks are
u32arena-relative handles; - handle
0represents null or an empty niche where the type permits it.
Padding belongs to the layout but does not carry language-level data.
The canonical collection-element widths on wasm32 are:
| Width | Kinds |
|---|---|
| 1 byte | i8, byte, bool |
| 2 bytes | i16, u16 |
| 4 bytes | i32, u32, f32, char, arena handle |
| 8 bytes | int, u64, f64 |
| 16 bytes | i128, u128 |
WebAssembly evaluation lanes can be wider than stored fields. For example, a
one-byte bool is carried in an i32 Wasm lane.
Resolving handles
Suppose a struct field at offset 20 contains the value 96. 96 is not a
WebAssembly address. It resolves relative to the current body base:
field address = arena body base + 20
block address = arena body base + 96After growth, the body base may differ, but the values 20 and 96 still
describe the same relationship.
A handle is meaningful only with the arena that owns it. Passing a bare offset between Unit instances, link groups, or restored arena generations without an explicit transfer protocol is invalid.
Strings
A stored string field is one four-byte handle. The referenced block begins with:
| Offset | Field |
|---|---|
0 |
byte length, u32 |
4 |
byte capacity, u32 |
8 |
UTF-8 bytes |
Handle 0 is the empty representation. Checked readers validate the block
bounds and UTF-8. Trusted runtime paths may use a Safe view after the guest
builder has established those invariants.
Lists
A stored list field is also a four-byte handle:
| Offset | Field |
|---|---|
0 |
element count, u32 |
4 |
element capacity, u32 |
8 |
canonical element storage |
Elements are laid out at the element type’s canonical size and alignment. Reference-containing elements store their own relative handles and participate in generated clone, drop, and mark walks.
The empty-list niche uses handle 0. Implementations must not derive a data
pointer from it before checking the length or handle.
ABI lanes
Do not confuse an in-arena field layout with a compiler ABI lane. Frames and calls sometimes reserve a wider logical lane so generated code can materialize cached metadata:
| Value | Stored field | Logical ABI lane |
|---|---|---|
| String | 4-byte block handle | 8 bytes |
| List | 4-byte block handle | 12 bytes |
Lane zero contains the backing block handle. Length and capacity can be loaded or materialized into the remaining lanes. This is an execution convenience, not evidence that every stored string or list is a fat pointer.
Maps
Maps use a Swiss-table-style block. The first 16 bytes contain:
| Offset | Field |
|---|---|
0 |
length |
4 |
capacity |
8 |
tombstone count |
12 |
reserved |
The header is followed by capacity + 16 control bytes and aligned key/value
storage. Control byte 0x80 marks an empty bucket and 0xFE marks a
tombstone. Other control values contain hash fragments used during probing.
Keys and values still use their canonical flat layouts. A map’s internal table is an implementation layout and must be traversed through generated/runtime operations rather than assumed by application code.
Sets
Sets use the same table organization as maps but store only keys. They share the empty and tombstone control values, probing discipline, and ownership requirements.
Function values
Compiled function identities are dense u32 FunctionId values for one build.
They are not stable symbols across program versions. A Unit instance containing
stored function values is therefore not automatically safe to adopt during a
redeploy; the deployment compatibility check must prove that the relevant
mapping remains valid or reject migration.
Checked views
Flat access has two trust levels:
- checked access validates offsets, lengths, alignment, discriminants, and UTF-8 before exposing typed data;
- trusted
Safeviews avoid repeating checks after a builder or runtime boundary has established the complete layout.
Trusted does not mean that bytes from an untrusted host or old program version may skip validation. It describes a proof already obtained in the current operation.
Copying
Copying the complete [FlatBuilderHeader | arena body] preserves all
intra-arena relationships. Copying one referenced block alone generally does
not: its nested handles still point into the source arena. Cross-arena value
transfer therefore requires a generated deep clone or a typed marshal/unmarshal
operation.
Host calls use typed layouts rather than exposing raw native pointers. At suspension boundaries, code retains handles and offsets or participates in the generation-pin protocol described in Host I/O.
Evolution
Flat layouts are shared by compiler-generated code and the runtime. Changes must be coordinated through ABI/version metadata and migration compatibility checks. In particular:
- never infer current sizes from old prose when generated constants disagree;
- never persist a build-local
FunctionIdas a cross-version identity; - never reinterpret an arena from a different layout fingerprint;
- validate external or restored bytes before creating a trusted view.