WebAssembly (planned work)
kit treats WebAssembly as both an input language and an output target,
sharing one binary/module layer between the two directions. The frontend path
(Wasm in, native object/JIT out) and the minimal final-module backend (C/toy
in, single-TU .wasm out) are working baselines. The remaining work is
concentrated in three areas: completing the obj/wasm object backend so it can
read and write tool-conventions relocatable objects (today it only mirrors
raw section bytes); building the Wasm static linker; and closing the last
frontend/backend feature gaps (relocations for cross-TU references, atomics
sub-word coverage, the C-facing exported wrapper ABI, and the unsupported-proposal
diagnostics). This doc is the forward-looking plan. Design rationale, the shared
module model, and the API sketch live in ../WASM.md; related
design docs are ../OBJ.md and ../LINK.md, and the
sibling plan is LINKER.md.
Baseline already in place (do not re-plan): src/wasm core decode/validate/
encode/wat; lang/wasm frontend with native lowering through KitCg and an
explicit KitWasmInstance* ABI; src/arch/wasm with arch_impl_wasm, a
wasm32 BasicCABI vtable, a structured-CG + CFG-structurer backend, and a
single-TU emit_wasm; host-import binding via kit_wasm_set_host_imports.
read_wasm/emit_wasm are real (no longer stubbed) but partial.
Object backend (largest gap)
src/obj/wasm is far smaller than the ELF/Mach-O/COFF backends. read.c
mirrors each Wasm section into an ObjBuilder section carrying raw payload
bytes and synthesizes one function symbol per defined function (enough for
objdump -h/-s/-d/-t). emit.c flushes a WasmModule attached under
OBJ_EXT_WASM via wasm_encode, or an empty magic+version header. Neither
understands tool-conventions object metadata: there is no symbol-table decode,
no relocation decode/encode, no linking/reloc.* custom-section handling,
and no WasmObjMeta.
Work items:
- Add the typed
WasmObjMetaextension payload (module graph, symbol table, relocations, data-segment metadata, target features, init funcs) and hang it off the builder underOBJ_EXT_WASM. Today only a bareWasmModule*is stored there; the linker and relocatable emitter need the richer struct. - Extend
emit_wasmto produce tool-conventions relocatable objects: a requiredlinkingcustom section,reloc.CODE/reloc.DATAcustom sections, symbol-info subsections, data-segment-info subsections, and target-feature metadata. Relocatable objects must use padded-LEB immediate encodings so relocations can rewrite immediates without re-disassembling the code section. - Extend
read_wasmfrom raw-byte mirroring to a real relocatable-object reader: decode the symbol table into genericObjBuildersymbols (so archives and generic symbol inspection keep working), decodereloc.*into generic relocations plusWasmObjMeta, and preserve unknown custom sections by name and bytes for lossless roundtrip. - Map the existing internal
RelocKindvalues (R_WASM_FUNCIDX,R_WASM_TABLEIDX,R_WASM_MEMOFS,R_WASM_TYPEIDX) onto the tool-conventions wire relocation numbers, plus the data-symbol and table-index variants the cross-TU work needs (R_WASM_MEMORY_ADDR_{LEB,SLEB,I32,I64},R_WASM_TABLE_INDEX_{SLEB,I32}). Add newRelocKindvalues only where the wire format needs a distinction the current names cannot express; unsupported kinds fail with a diagnostic naming the kind. - Add
make test-wasm-obj: objects roundtrip through reader/writer/objdumpwithout losing sections, symbols, relocations, or unknown custom sections.
Static linker
There is no Wasm linker yet. kit_link_exe always builds a native Linker
and emits a native LinkImage; a Wasm final module is not a virtual-addressed
native image and must not go through that segment layout.
Work items:
- Add
WasmLinkImage,wasm_link_resolve(Linker*),wasm_link_emit, andwasm_link_image_free. Dispatch to this path fromkit_link_exewhentarget.obj == KIT_OBJ_WASM, afterbuild_linkerand beforelink_resolve, so existing input handling, archive loading, entry selection, and diagnostics stay shared. - Merge index spaces across objects: renumber functions, globals, tables, memories, data segments, and types; merge type/import/function/table/global/ element/data/custom sections.
- Resolve undefined function/data/global/table symbols and apply Wasm relocations against the merged module without disassembling the code section.
- Merge compatible target-feature sections; diagnose incompatible feature sets.
- Synthesize
__wasm_call_ctors, stack/memory symbols, and exports per the selected output mode; participate in archive demand loading. - Add
make test-wasm-link: multiple objects link into one valid module; archives demand-load; imports, exports, ctors, and memory/table layout are deterministic.
This is the path that unblocks multi-TU Wasm output. The single-TU final module the backend produces today needs no relocations; everything cross-TU depends on the object backend and linker above.
Frontend validator coverage
The shared validator (src/wasm/validate.c) and WAT/binary readers already
cover the accepted feature subset with typed operand/control stacks, section
ordering and index-space checks, branch arity, br_table, limits, segment
rules, and start-function signatures, plus the staged proposal gates (threads,
typed function refs, tail calls, multi-memory, memory64, bulk memory,
non-trapping float-to-int) behind WasmFeatureSet.
Remaining work item:
- Add explicit "outside the support plan" diagnostics for SIMD, exceptions/ tags, GC, and the component model, so a module using them fails with a feature-naming message rather than a generic stack/opcode error. These proposals stay rejected, not lowered.
Frontend lowering gaps
Native lowering covers the MVP numeric/control/memory subset plus the staged
proposals (threads, typed refs, tail calls, multi-memory, memory64, bulk
memory), all routed through the explicit KitWasmInstance* ABI with import
slots and runtime table storage.
Remaining work items:
- Define and implement the C-facing exported wrapper ABI: host-callable thunks
that keep the instance parameter explicit but use C-friendly scalar types and
symbol names. Today only the internal
export_name(KitWasmInstance*, ...)ABI exists. Embedders are a first-class use case; do not generate wrappers that hide or globalize the instance. - Once
read_wasmhandles relocatable objects, consider relaxing the frontend rule that rejects modules with alinkingcustom section (currently directed to be supplied as an object input instead).
Backend feature gaps
The wasm32-none backend emits valid single-TU final modules: scalar +
indirect-aggregate BasicCABI, structured control flow with a reducible-CFG
structurer and br_table switches, linear memory + __stack_pointer,
compact data layout with intra-module R_ABS32 relocations, varargs via a
caller-packed linear-memory buffer, the bit/overflow intrinsics, atomics via
wasm-threads opcodes, memory.copy/memory.fill lowering, inline asm with
WAT templates, conventional "memory" export, and (import "env" ...)
declarations with import_module/import_name attribute overrides.
Remaining work items, mostly blocked on the object/linker layer or on wider ABI support:
- Cross-TU references. Address-of an undefined symbol and address-of a
cross-TU function currently diagnose ("address of undefined symbol not yet
implemented"). The fix is the object linking section plus relocations:
data symbols via
R_WASM_MEMORY_ADDR_*, address-taken functions placed in the indirect-call table viaR_WASM_TABLE_INDEX_*, with the undefined symbol carryingWASM_SYM_UNDEFINED(andWASM_SYM_BINDING_WEAKfor weak undefs). Resolved at link time. Shared machinery for any cross-TU function pointer. R_ABS64and non-R_ABS32data relocation kinds in the linear-memory image (currently diagnosed explicitly).&&labeladdresses in static-data initializers (diagnosed early today).- Wider scalar ABI:
__int128(wasm32 ABI rejects 16-byte scalars) andlong double/binary128 (advertised but fatals on materialization). These are phased-rollout SKIPs in the C corpus W path. - 64-bit checked-overflow multiply (
__builtin_*mul_overflowon i64), lowerable as a software 64x64->128 product splitting each i64 into i32 halves and checking the high word; i32 already widens through i64. - Atomics: sub-word (8/16-bit) RMW and cmpxchg, and atomic NAND — blocked on the 8/16-bit atomic RMW opcodes not yet defined in kit's Wasm core. Full i32/i64 width is covered. Memory order is ignored (Wasm models seq_cst only).
- TLS and bitfields diagnose-and-fail; WASI startup, and irreducible control flow / computed goto still produce raw rather than wasm-specific diagnostics — tighten the messages later.
Wasm-to-Wasm
A Toy/C -> Wasm -> run roundtrip exists via make test-wasm-toy/test-wasm-c,
but it routes the backend's output back through the lang/wasm frontend's native
JIT (Wasm-to-native), not a direct Wasm-to-Wasm path.
Work items:
- Wire the lang/wasm frontend to the Wasm target backend instead of native CG for a direct Wasm-input -> Wasm-output normalization mode. Re-emit semantically (not byte-preserving; object roundtrip tests cover preservation); unsupported features still fail before emission.
- Add an explicit validate-only pass over Wasm-backend output (the frontend
validator currently only runs on Toy-produced
.wasmduringkit run).
wasm64 and WASI
Both stay recognized-but-unsupported until the freestanding wasm32 object/link path is solid:
wasm64/memory64 parses and the frontend lowers it, but the wasm64 target ABI panics incompute_func_info. Keep 64-bit tool-convention relocations and the wasm64 backend behind explicit capability checks until the object/linker story is stable.wasm32-wasiparses but is unsupported as a target: it must diagnose clearly until WASI imports, startup, argv/env, and libc policy are specified. Decision stands: freestandingwasm32-unknown-unknown/wasm32-nonefirst.
Cleanup
- Move the shared Wasm core from
lang/wasm/tosrc/wasm/per the layout in ../WASM.md. The core (decode/encode/validate/insn/module/wat) already lives undersrc/wasm/; the obj-layer glue is undersrc/obj/wasm/. Confirm no TU still reaches the core through-Ilang/wasmand retire that include path.
Test targets
Present and green: test-wasm-front, test-wasm-target, test-wasm-toy,
test-wasm-c (aggregated under test-wasm). Still to add as the object and
link layers land: make test-wasm-obj and make test-wasm-link. Prefer small
named fixtures over broad corpus runs; keep external validators (wasm-tools,
WABT) as optional comparison oracles, never a hard dependency — kit's own
validator is the semantic gate. See TESTING.md.