Skip to content

std/sections

std/wasm/sections — section composers for the WebAssembly Core 1.0 binary format. One step up from the per-element primitives in encode.fern / leb128.fern / inst.fern / etc.: each function here takes the section’s logical input (a list of typeidxs, a list of function bodies, etc.) and emits the complete id : u8 + size : uleb + body envelope. Spec: https://webassembly.github.io/spec/core/binary/modules.html Seventh slice of Phase 1 of docs/TOOLCHAIN-SELF-HOSTING.md. With these composers in place, an IR-walking driver only has to arrange its inputs as the right Lang arrays and call through — no more re-implementing the section-header / vec-length / memarg / memtype / export-desc bookkeeping at each call site. Sections covered: type, function, table, memory, global (via a single-const-global helper since that’s all today’s backend needs), export, start, element, code, data. Import lives in imports.fern — its descriptor is a four-variant union (func / table / mem / global) that warrants its own module. Table + element are here because they’re what call_indirect (closures + indirect dispatch in the production backend) needs.

pub function export_func(): u8

---- Export-kind constants ----------------------------------- (https://webassembly.github.io/spec/core/binary/modules.html#export-section)

pub function export_table(): u8
pub function export_memory(): u8
pub function export_global(): u8
pub function encode_custom_section(buf: u8[], name: string, payload: u8[]): u8[]

---- Custom section ------------------------------------------

Body: name : uleb-prefixed UTF-8 + payload : bytes. The outer section’s size covers both. wasm engines ignore custom sections semantically but tools (wasm-tools, the names section, debug-info, the component-type marker that component-embed writes) all hang their data here.

pub function encode_type_section(buf: u8[], params_per_type: u8[][], results_per_type: u8[][]): u8[]

---- Type section --------------------------------------------

Body: vec(functype). Each functype is 0x60 + vec(params) + vec(results), encoded by encode.put_func_type. The two parallel arrays let callers compose the param / result valtype byte sequences per entry without needing struct-of-arrays composite types.

pub function encode_function_section(buf: u8[], typeidxs: u32[]): u8[]

---- Function section ----------------------------------------

Body: vec(typeidx). Each function declared in the module gets one typeidx here, in declaration order — the i-th typeidx in this section binds the i-th body in the code section to one of the types from the type section.

pub function encode_table_section(buf: u8[], reftype: u8, min: u32, max: i32): u8[]

---- Table section -------------------------------------------

Body: vec(tabletype). Each tabletype is reftype + limits — the same limits shape the memory section uses. wasm 1.0 caps a module at one table, so this helper writes exactly one entry. reftype is funcref (0x70) for the call_indirect dispatch table; max = -1 means “no maximum”.

pub function encode_memory_section(buf: u8[], min_pages: u32, max_pages: i32): u8[]

---- Memory section ------------------------------------------

Body: vec(memtype). Wasm 1.0 allows at most one memory, so this helper writes exactly one entry. memtype is a limits record: a flag byte (0 = no max, 1 = with max), the initial page count, and optionally the max page count. max_pages = -1 means “no maximum”.

pub function encode_export_section(buf: u8[], names: string[], kinds: u8[], indices: u32[]): u8[]

---- Export section ------------------------------------------

Body: vec(export). Each export is name : utf8 + kind : u8 + idx : u32. The three parallel arrays cover the three fields — same shape as the type section’s params/results split.

pub function encode_start_section(buf: u8[], funcidx: u32): u8[]

---- Start section -------------------------------------------

Body: one funcidx (no length prefix — this section holds at most one value, like a Maybe<funcidx>).

pub function encode_element_section(buf: u8[], offsets: i32[], funcidxs_per_seg: u32[][]): u8[]

---- Element section -----------------------------------------

Body: vec(elem). Each segment here is the MVP “active, table 0, funcref” kind (flag 0): a 0x00 flag, an offset expression (i32.const <off> ; end), and a vec(funcidx) that initialises the table starting at that offset. This is what populates the call_indirect dispatch table — funcidxs_per_seg[i][j] becomes table slot offsets[i] + j.

offsets and funcidxs_per_seg are parallel arrays — one offset and one funcidx list per segment.

pub function encode_code_section(buf: u8[], bodies: u8[][]): u8[]

---- Code section --------------------------------------------

Body: vec(code). Each code entry is itself a size : uleb + locals_vec + expr — produced by inst.put_function_body, which already prefixes the size. So the caller passes pre-wrapped bodies and we just emit the count + concatenation.

pub function encode_data_section(buf: u8[], offsets: i32[], init_bytes: u8[][]): u8[]

---- Data section --------------------------------------------

Body: vec(data). Each data segment in wasm 1.0 is the “active” kind: memidx (always 0 in MVP) + offset (an i32.const expr) + init bytes (vec(byte)). The offset expr is just i32.const <offset>; end, four to six bytes total.

offsets and init_bytes are parallel arrays — one offset and one byte sequence per segment.