kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 00158c36b4bbbeaa9197b7142bdbc4c33936c9b0
parent 0b6a8f1ea1f601f5ab95420d780170f57d6f7ee7
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 17 May 2026 18:02:03 -0700

docs: plan wasm frontend and backend

Diffstat:
Adoc/WASM.md | 794+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 794 insertions(+), 0 deletions(-)

diff --git a/doc/WASM.md b/doc/WASM.md @@ -0,0 +1,794 @@ +# WebAssembly Input and Target Plan + +This plan covers two related but different features: + +- **Wasm as input**: accept WebAssembly modules as a frontend language and + lower them into cfree's normal codegen path for native or Wasm output. +- **Wasm as target**: compile C, toy, and later other frontends to + WebAssembly modules or linkable WebAssembly object files. + +The implementation should share one Wasm binary/module layer. Do not build a +reader for the frontend and a second writer for the target. + +Priority: implement **Wasm frontend to native object/JIT first**, then the +Wasm backend. Both directions remain part of the plan, but JITing a `.wasm` +module means validating Wasm input and lowering it through `CfreeCg` into the +existing native targets and native `LinkImage`/JIT path. The Wasm backend and +`WasmLinkImage` path are only needed when the selected target object format is +`CFREE_OBJ_WASM`, i.e. for producing final `.wasm` modules or relocatable Wasm +objects. + +## Current Anchors + +The public API already names the target and format: + +- `CFREE_ARCH_WASM`, `CFREE_OBJ_WASM`, and `CFREE_OS_WASI` exist in + `include/cfree.h`. +- `driver/target.c` parses `wasm32` and `wasm64` triples and selects + `CFREE_OBJ_WASM` for freestanding/WASI. +- `CFREE_CG_CC_WASM` exists in `include/cfree/cg.h`, and + `src/api/cg.c` reports it supported only for `CFREE_ARCH_WASM`. +- `emit_wasm` and `read_wasm` are declared but stubbed in + `src/api/stubs.c`. +- `src/obj/obj.h` already has `OBJ_EXT_WASM`, `SSEM_WASM_CUSTOM`, and a small + set of Wasm relocation kinds. +- `src/arch/arch.h` already expects a future Wasm backend to consume + structured CG scopes directly. + +The missing pieces are an `arch_impl_wasm`, a real Wasm object/module +reader/writer, a Wasm static linker, and a frontend that can validate/lower a +Wasm module. + +## Codebase Fit Findings + +The plan fits the current architecture, but a few seams must be made explicit +before implementation: + +- `CFREE_ARCH_WASM` is a public enum value, but there is no registered + `ArchImpl` in `src/arch/registry.c`. `compiler_init` constructs + `TargetABI` immediately through `abi_new`, so a usable Wasm compiler target + needs `arch_impl_wasm.abi_vtable` before C/toy-to-Wasm compile tests can + safely create a `CfreeCompiler`. +- `ObjBuilder` has `OBJ_EXT_WASM` as a tag, but no payload storage API. The + object metadata described below requires a small internal extension hook on + `ObjBuilder`; otherwise `WasmObjMeta associated with the builder` is only a + design phrase, not an implementable data path. +- The public compile path is already relocatable-object-first: + `cfree_compile_obj*` builds an `ObjBuilder`, finalizes it, then dispatches + `emit_wasm` from `emit_object_bytes` when `target.obj == CFREE_OBJ_WASM`. + The Wasm target should use that path rather than adding a one-TU final + module shortcut. +- The public link path currently always builds a `Linker`, calls + `link_resolve`, then emits a native `LinkImage`. A Wasm final module should + not use `LinkImage` segment layout. The least invasive fit is a + `target.obj == CFREE_OBJ_WASM` branch in `cfree_link_exe` after + `build_linker` and before `link_resolve`, calling a Wasm-specific resolver + and emitter. This branch is not used for Wasm input JIT: Wasm input lowered + to a native target should continue through the existing native `LinkImage` + path. +- Driver source classification only recognizes `.c`, `.toy`, and `.s` today, + and `cfree_language_for_path` only infers those languages. Adding + `CFREE_LANG_WASM` also requires driver classification for `.wasm`/`.wat`, + frontend registration in `driver_compiler_new`/`driver_pipeline_new`, and + updates to dbg language switches that enumerate `CFREE_LANG_*`. +- `CfreeTarget` has no feature field. Wasm feature policy should therefore + live in internal Wasm decode/validation/link options for v1, not in the + public target struct. A later public feature API can be added once the + target has real users. + +## References + +Primary references checked on 2026-05-18: + +- WebAssembly Core Specification 3.0: + <https://www.w3.org/TR/wasm-core/>. Binary modules are sectioned, most + sections are optional, custom sections are semantically ignored, and code + uses a structured stack-machine instruction model. +- WebAssembly tool-conventions `Linking.md`: + <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md>. + Relocatable Wasm object files are valid Wasm binaries distinguished by a + required `linking` custom section, plus `reloc.*` custom sections and + target-feature metadata. +- WebAssembly tool-conventions `BasicCABI.md`: + <https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md>. + The current basic C ABI uses wasm32 ILP32, maps C scalar types to Wasm value + types, uses a linear memory stack through a mutable stack-pointer global, + and passes most aggregates indirectly. + +Treat the core spec as normative. Treat tool-conventions as an interop target, +not as the core Wasm standard. + +## Design Principles + +- Keep Wasm state in context structs. No process-global decode tables, + current-module pointers, or singleton runtime state. +- Keep Wasm core parsing independent from cfree object and frontend code. + A module parser should not know whether the caller is `read_wasm`, the Wasm + frontend, or a validation test. +- Make unsupported features explicit. The frontend, object reader, and target + backend should diagnose unsupported Wasm features rather than silently + dropping sections, imports, memories, tables, or target features. +- Start with wasm32. `wasm64` triples can continue to parse, but memory64 and + 64-bit tool-convention relocations should stay behind explicit capability + checks until the object/linker story is stable. +- Preserve boundaries: `lang/wasm` uses public frontend/CG APIs where possible; + `src/wasm` owns binary/core Wasm mechanics; `src/obj` owns object-format + adaptation; `src/arch/wasm` owns target lowering. + +## Proposed Layout + +``` +src/wasm/ + wasm.h shared core structs, feature flags, error codes + decode.c binary reader: LEB128, sections, indices, expressions + wat.c text reader for the accepted feature subset + validate.c type stack, control stack, module validation + encode.c module/object section writer helpers + names.c name/import/export helpers + object.c linking custom section and reloc custom section helpers + +src/obj/ + wasm_read.c read_wasm: Wasm object file -> ObjBuilder plus metadata + wasm_emit.c emit_wasm: ObjBuilder plus metadata -> Wasm object/module + +src/arch/wasm/ + arch.c ArchImpl registration, target ABI selection + target.c CGTarget implementation emitting Wasm function bodies + abi.c BasicCABI classification + +lang/wasm/ + wasm_frontend.c CfreeCompileFn for CFREE_LANG_WASM + lower.c validated Wasm function -> CfreeCg + runtime_abi.h generated-module instance/runtime contract + +test/wasm/ + format/ parser/encoder/validation fixtures + front/ Wasm input -> native object/JIT behavior fixtures + obj/ object roundtrip and relocation fixtures + target/ C/toy -> Wasm module fixtures +``` + +Add `CFREE_LANG_WASM` and teach suffix inference about `.wasm` and `.wat`. +Binary Wasm remains the canonical object/input format, but v1 should include a +native `.wat` parser for the accepted feature subset. The text parser is a +developer and testability feature: it should lower into the same `WasmModule` +model as the binary reader and share validation, feature gating, and +diagnostics. Do not implement text-only semantics or a separate frontend path. + +## API and Data Structure Sketch + +### Public Surface + +Keep the first public API change small: + +```c +typedef enum CfreeLanguage { + CFREE_LANG_C = 0, + CFREE_LANG_ASM = 1, + CFREE_LANG_TOY = 2, + CFREE_LANG_WASM = 3, + CFREE_LANG_COUNT = 4, +} CfreeLanguage; +``` + +Update `cfree_language_for_path` and driver classification so `.wasm` and +`.wat` are source inputs only when compiling through the Wasm frontend. Object +inputs should remain classified by `.o`/`.obj`/archive suffixes and by +`cfree_detect_fmt` in linker/object-reader paths. + +Do not add public Wasm feature knobs in phase 0/1. Internally, construct the +accepted feature set from the target and the caller: + +```c +typedef struct WasmFeatureSet { + uint64_t core; + uint64_t tool_conventions; +} WasmFeatureSet; +``` + +If external embedders need to instantiate Wasm-input modules later, add that +public runtime API with the frontend milestone. Until then, keep +`CfreeWasmInstance`, `CfreeWasmMemory`, and `CfreeWasmTable` as runtime ABI +types under `lang/wasm` or `rt/`, not as prematurely stable libcfree API. + +### Internal Wasm Core + +The `src/wasm` layer should be usable by `read_wasm`, `emit_wasm`, validation +tests, and `lang/wasm` without depending on any one caller's pipeline shape: + +```c +typedef struct WasmBytes { + const uint8_t *data; + size_t len; +} WasmBytes; + +typedef enum WasmValType { + WASM_VAL_I32, + WASM_VAL_I64, + WASM_VAL_F32, + WASM_VAL_F64, + WASM_VAL_FUNCREF, + WASM_VAL_EXTERNREF, +} WasmValType; + +typedef struct WasmFuncType { + WasmValType *params; + uint32_t nparams; + WasmValType *results; + uint32_t nresults; +} WasmFuncType; + +typedef struct WasmFunc { + uint32_t typeidx; + WasmBytes raw_body; + uint32_t code_section_offset; + /* Filled only when validation/lowering asks for decoded instructions. */ + struct WasmInsn *insns; + uint32_t ninsns; +} WasmFunc; + +typedef struct WasmModule { + WasmFuncType *types; + uint32_t ntypes; + struct WasmImport *imports; + uint32_t nimports; + WasmFunc *funcs; + uint32_t nfuncs; + struct WasmTable *tables; + uint32_t ntables; + struct WasmMemory *memories; + uint32_t nmemories; + struct WasmGlobal *globals; + uint32_t nglobals; + struct WasmExport *exports; + uint32_t nexports; + struct WasmElemSegment *elems; + uint32_t nelems; + struct WasmDataSegment *data; + uint32_t ndata; + struct WasmCustomSection *customs; + uint32_t ncustoms; +} WasmModule; +``` + +Decode, validation, and encode contexts own all scratch state. The module +storage should come from a caller-owned arena so no decode result depends on +hidden globals: + +```c +typedef struct WasmDecodeOptions { + WasmFeatureSet features; + uint8_t preserve_custom_sections; + uint8_t allow_object_metadata; +} WasmDecodeOptions; + +typedef struct WasmDiag { + void (*error)(void *user, uint32_t offset, const char *fmt, va_list ap); + void *user; +} WasmDiag; + +typedef struct WasmDecodeCtx { + Heap *heap; + Arena *arena; /* module lifetime */ + Arena *scratch; /* transient decode stacks */ + WasmDiag diag; /* adapter-owned; may bridge to Compiler diagnostics */ + WasmDecodeOptions opts; +} WasmDecodeCtx; + +typedef struct WasmEncodeCtx { + Heap *heap; + Arena *scratch; + WasmDiag diag; + WasmFeatureSet features; +} WasmEncodeCtx; + +int wasm_decode_module(WasmDecodeCtx *, const uint8_t *data, size_t len, + WasmModule *out); +int wasm_validate_module(WasmDecodeCtx *, const WasmModule *); +int wasm_encode_module(WasmEncodeCtx *, const WasmModule *, Writer *); +``` + +The core parser should return structured failures to direct tests. The +libcfree adapters may translate those failures into `compiler_panic` or +diagnostics at the API boundary. + +### ObjBuilder Extension Payload + +Add a tiny internal extension API to `src/obj/obj.h` before implementing +`WasmObjMeta`: + +```c +typedef void (*ObjExtFreeFn)(Compiler *, void *); + +void obj_ext_set(ObjBuilder *, ObjExtKind, void *payload, ObjExtFreeFn); +void *obj_ext_get(const ObjBuilder *, ObjExtKind); +void obj_ext_clear(ObjBuilder *, ObjExtKind); +``` + +`ObjBuilder` should own extension payload lifetime and release it from +`obj_free`. One payload per `ObjExtKind` is enough for the current formats. +This keeps the generic object tables format-neutral while giving Wasm +read/write/link code a real place to hang module-level metadata. + +Wasm object metadata should be compact and typed: + +```c +typedef struct WasmObjReloc { + uint32_t reloc_section_index; /* reloc.CODE, reloc.DATA, etc. */ + uint32_t target_section_index; + uint32_t offset; + RelocKind kind; + ObjSymId sym; + int64_t addend; +} WasmObjReloc; + +typedef struct WasmObjSymbol { + ObjSymId obj_sym; + uint32_t flags; + uint32_t kind; /* tool-conventions symbol kind */ + uint32_t index; /* function/global/table/data/event index */ +} WasmObjSymbol; + +typedef struct WasmObjMeta { + WasmModule *module; + WasmObjSymbol *symbols; + uint32_t nsymbols; + WasmObjReloc *relocs; + uint32_t nrelocs; + struct WasmObjDataSegmentInfo *data_segments; + uint32_t ndata_segments; + struct WasmTargetFeature *target_features; + uint32_t ntarget_features; + struct WasmInitFunc *init_funcs; + uint32_t ninit_funcs; +} WasmObjMeta; +``` + +`read_wasm` should fill normal `ObjBuilder` symbols so archives and generic +symbol inspection still work. The Wasm linker and `emit_wasm` should use +`WasmObjMeta` for index spaces, custom sections, function bodies, data segment +metadata, and padded-LEB relocation details that do not fit a native section +layout model. + +### Wasm Target API + +Register `arch_impl_wasm` in `src/arch/registry.c` with: + +- `abi_vtable`: wasm32 BasicCABI first; reject wasm64 until memory64 and + object relocation support are real. +- `cgtarget_new`: returns the Wasm `CGTarget`. +- `asm_new`, `disasm_new`, `elf`, `macho`, and native register hooks: NULL for + v1. +- `link`: NULL; Wasm static linking is format-level module merging, not a + native `LinkArchDesc`. + +The Wasm `CGTarget` still implements the full vtable in `src/arch/arch.h`, +but its implementation maps operands to Wasm value-stack entries, locals, and +linear-memory addresses instead of hard registers. Unsupported hooks must +diagnose deliberately: inline asm, native TLS models, atomics, varargs, SIMD, +and irreducible control flow should fail before emitting malformed Wasm. + +### Wasm Link API + +Add a Wasm-specific link product beside `LinkImage`: + +```c +typedef struct WasmLinkImage WasmLinkImage; + +WasmLinkImage *wasm_link_resolve(Linker *); +void wasm_link_emit(WasmLinkImage *, Writer *); +void wasm_link_image_free(WasmLinkImage *); +``` + +`cfree_link_exe` should dispatch to this path when +`compiler->target.obj == CFREE_OBJ_WASM` after `build_linker` and before +`link_resolve`. That lets existing public inputs, archive loading, entry +selection, and diagnostics stay shared while avoiding native segment layout. + +## Shared Wasm Module Model + +Introduce a compact in-memory model that can represent both executable modules +and relocatable object files: + +- Types: function signatures over Wasm value types. +- Imports and exports: module/name pairs, external kind, type index or limits. +- Functions: type index, locals, raw body bytes initially, decoded instruction + stream after validation when needed. +- Tables, memories, globals, tags, elements, and data segments. +- Custom sections: preserve unknown custom sections by name and bytes. +- Object metadata: symbol table, init functions, data segment names, + `linking` custom section payload, `reloc.*` entries, and target features. + +The reader should decode raw sections first, then validate into typed arrays. +This keeps malformed-input diagnostics precise and lets object roundtrip tests +preserve unknown custom sections. + +cfree owns validation for every Wasm feature it accepts. External validators +may be used in optional comparison tests, but they are not the semantic gate for +input safety or output correctness. The internal validator should cover section +ordering, index spaces, type stack, control stack, block result types, +memory/table/global references, limits, data/element segment consistency, and +object metadata invariants needed by the linker. + +Feature policy should be centralized in a `WasmFeatureSet` from the beginning. +Decode may preserve unknown custom sections, but validation, lowering, and +linking must reject instructions or section semantics that are not enabled by +the active feature set. Initialize the v1 feature set conservatively and add +features by extending the table rather than scattering ad hoc checks. + +Represent object-level Wasm metadata with a hybrid model: + +- Generic sections, symbols, groups, and relocations stay in `ObjBuilder`. +- Wasm-only object metadata lives in a typed extension payload, for example + `WasmObjMeta`, associated with the builder under `OBJ_EXT_WASM`. +- Raw module parse/validation structures stay typed by `src/wasm`. When the + writer or linker still needs the module graph, the extension payload may own + a `WasmModule*`; the generic object core should not inline or interpret it. +- Generic object consumers may ignore the extension payload. `read_wasm`, + `emit_wasm`, and the Wasm linker are responsible for understanding it. + +This avoids turning the format-neutral object core into a Wasm-specific module +model while still preserving the metadata needed for archives, object +roundtrip, and linking. + +Do not use VLAs for section-local temporary arrays. All variable-size decode +storage should hang off `WasmDecodeCtx` or an arena owned by the caller. + +## End-to-End Data Flow + +### Wasm Input to Native Object/JIT + +This is the first implementation priority. + +1. `.wasm`/`.wat` source input is tagged `CFREE_LANG_WASM`. +2. The compiler target is a normal native target such as aa64, x64, or rv64, + not `CFREE_ARCH_WASM`. +3. `lang/wasm` decodes, validates, and rejects relocatable-object metadata. +4. Lowering creates explicit instance-state types and emits through `CfreeCg`. +5. Native backends compile the generated functions like any other frontend, + with imported functions and memories represented through the instance + context rather than hidden globals. +6. `cfree_compile_obj` produces a native `ObjBuilder`, and `cfree_link_jit` + uses the existing native `LinkImage` path. + +### C/Toy to Wasm Object + +This is the Wasm backend path and comes after the frontend-to-native path. + +1. Driver or host constructs `CfreeCompiler` with + `{CFREE_ARCH_WASM, CFREE_OS_FREESTANDING, CFREE_OBJ_WASM, ptr_size=4}`. +2. `compiler_init` selects the Wasm ABI vtable through `arch_impl_wasm`. +3. C or toy frontend emits through the existing public `CfreeCg` API. +4. `cgtarget_new` returns the Wasm target. It records function types, locals, + code bodies, data segments, exports, and relocations into `WasmObjMeta` + while maintaining enough generic `ObjBuilder` symbols/sections for + inspection and archives. +5. `obj_finalize` closes the generic builder. +6. `emit_wasm` reads `WasmObjMeta` and writes a tool-conventions relocatable + Wasm object. + +### Wasm Object to Final Module + +1. Linker byte inputs use `cfree_detect_fmt`; Wasm bytes dispatch to + `read_wasm`. +2. `read_wasm` decodes and validates the module/object metadata, fills generic + `ObjBuilder` symbols for archive demand loading, and attaches `WasmObjMeta`. +3. `cfree_link_exe` sees `CFREE_OBJ_WASM` and calls `wasm_link_resolve` + instead of native `link_resolve`. +4. The Wasm linker merges module index spaces, resolves symbols, applies + padded-LEB relocations, synthesizes memory/table/stack/ctor/export policy, + and returns `WasmLinkImage`. +5. `wasm_link_emit` writes the final `.wasm` module. + +## Wasm as Target + +### Target Contract + +Add `arch_impl_wasm` and a Wasm `CGTarget`. + +The Wasm target is not register-allocated in the native sense. It should map +CG values to Wasm stack values and Wasm locals, and it should use structured +scope callbacks for `block`, `loop`, `if`, `else`, and branch depths. The +backend still needs a complete `CGTarget` surface because the public CG API +and existing frontends call through that contract. + +Use a hybrid control-flow strategy: + +- Prefer structured CG scopes when frontends emit them. +- Include a limited CFG-to-structured-Wasm pass for reducible label/jump + patterns so C code does not have to be perfectly structured before the Wasm + target can work. +- Diagnose irreducible control flow, computed goto, and unsupported switch + lowering explicitly in v1 rather than emitting incorrect Wasm. + +This keeps toy and Wasm-input lowering on the clean structured path while +leaving room for ordinary C control flow that reaches the backend as labels. + +Initial target tuple: + +- `wasm32-unknown-unknown` or `wasm32-none` for freestanding modules. +- `wasm32-wasi` is recognized by triple parsing but unsupported in v1. It + should diagnose clearly until WASI imports, startup, argv/env, and libc + policy are specified. +- no threads, SIMD, exceptions, GC/reference types beyond `funcref`, or + multi-memory in v1. + +### ABI + +Implement BasicCABI for wasm32 first: + +- ILP32: `int`, `long`, and pointers are 32-bit. +- Scalar params/results map to `i32`, `i64`, `f32`, and `f64`. +- Aggregates lower according to BasicCABI: empty ignored, singleton scalar + direct, other structs/unions indirect. Arrays are indirect. +- Use a mutable `__stack_pointer` global and a downward-growing linear stack + for address-taken locals, spills that need memory, and aggregate temporaries. +- Defer varargs, `long double`, `i128`, atomics, broad compiler-rt helper + coverage, and full libc/runtime conventions. Diagnose those cases explicitly + in the first target milestone. + +This makes v1 larger than scalar-only smoke tests but smaller than a full C +runtime target. The initial ABI boundary is scalar plus indirect aggregates. +Varargs should later be caller-packed into a linear-memory buffer; the callee +walks that buffer through the existing `va_*` CG hooks. + +The ABI classifier should consume neutral CG types, not C `Type*`. + +### Module/Object Emission + +There are two output modes: + +1. **Relocatable Wasm object**: emit a tool-conventions object with a + `linking` custom section, `reloc.*` custom sections, symbol metadata, data + segment metadata, and target-feature metadata. This is required for `cfree + cc -c`, archives, and cfree's linker. +2. **Final module**: the linker merges relocatable Wasm objects into a valid + `.wasm` module with type/import/function/table/memory/global/export/code/ + data sections. + +Do not add a public single-TU final-module compile shortcut before the object +path exists. cfree's compile contract is already relocatable-object-first; Wasm +should fit that contract instead of growing a parallel target path. Low-level +encoder tests may still build tiny final modules directly as fixtures. + +### Linker + +Add a Wasm static link path separate from ELF/Mach-O image layout: + +- Merge type, import, function, table, global, element, data, and custom + sections. +- Renumber functions, globals, tables, memories, data segments, and types. +- Resolve undefined function/data/global/table symbols. +- Apply Wasm relocations without disassembling the code section. Relocatable + objects must use padded LEB encodings where relocations rewrite immediates. +- Merge compatible target-feature sections and diagnose incompatible feature + sets. +- Synthesize `__wasm_call_ctors`, stack/memory symbols, and exports according + to the selected output mode. + +Do not route Wasm final output through `LinkImage` segment layout. A Wasm +module is not a virtual-addressed native image. + +## Wasm as Frontend + +### Frontend Input Semantics + +Add `CFREE_LANG_WASM` for `.wasm` modules. This is a binary frontend, not an +object reader: + +- `read_wasm` is for link-time object inputs. +- `lang/wasm` is for compiling a Wasm module's semantics to the selected + target. + +The frontend should accept executable/core modules first, not relocatable Wasm +objects. If an input contains a `linking` custom section, diagnose that it is a +linkable object and should be supplied as an object input once `read_wasm` +exists. + +### Native Lowering Model + +Compiling Wasm input to a native target is a module-to-native translation, not +a C ABI translation of individual functions. A Wasm module instance has state: +memories, tables, mutable globals, imports, and traps. Represent that state +with generated or runtime-provided context structs. + +Always use the whole-module instance model at the semantic boundary. Even a +module whose exports look like standalone scalar functions is lowered with an +explicit instance context; if the context is unused, later optimization may +remove it. Do not add a standalone-export fast path with a second ABI. + +Suggested v1 instance contract: + +```c +typedef struct CfreeWasmInstance CfreeWasmInstance; +typedef struct CfreeWasmMemory CfreeWasmMemory; +typedef struct CfreeWasmTable CfreeWasmTable; +``` + +Generated internal functions receive a hidden `CfreeWasmInstance*` parameter. +Imported functions are indirect calls through import slots in the instance. +Memory accesses load the active memory base/size from the instance and perform +bounds checks before native load/store. Traps call runtime helpers that do not +return. + +Exported Wasm functions should be exposed in two layers: + +- internal ABI: `export_name(CfreeWasmInstance*, wasm params...)`. This is the + semantic ABI for every lowered Wasm-defined function and keeps module state + explicit. +- embedder C wrapper ABI: host-callable thunks that preserve the explicit + instance parameter while using C-friendly scalar types and symbol names for + exports that can be represented directly. + +Start with the internal ABI, but treat embedders as a first-class use case: +design and implement the C wrapper layer as an explicit follow-up task, not as +an optional later convenience. Do not generate wrappers that hide or globalize +the instance; the instance parameter remains part of the C-facing contract. + +### Instruction Coverage + +Frontend v1 should target the MVP numeric/control/memory subset: + +- numeric constants, integer and FP arithmetic/comparison/conversion, + `select`, `drop`, `unreachable`. +- `local.get/set/tee`, `global.get/set`. +- `block`, `loop`, `if`, `else`, `br`, `br_if`, `br_table`, `return`. +- direct `call`; `call_indirect` after table representation exists. +- memory load/store, `memory.size`, `memory.grow`, and active data segments. + +Defer or reject: + +- reference types beyond basic `funcref` for tables. +- exceptions/tags, GC, typed function references, tail-call input lowering, + threads/atomics, SIMD, multiple memories, memory64, and component model. + +Validation should run before lowering. A malformed module should never reach +CG emission. + +## Phasing + +### Phase 0: Format Spine + +Add `src/wasm` with binary decode helpers, section scanning, LEB128 encode/ +decode, minimal module structs, the `ObjBuilder` extension-payload hook, and +tests for tiny hand-authored modules. + +Acceptance: + +- `cfree_detect_fmt` remains able to identify Wasm. +- parser rejects malformed section lengths and bad ordering. +- encoder can roundtrip a minimal module with type/function/export/code. +- `obj_ext_set/get/clear` can attach and release a dummy `OBJ_EXT_WASM` + payload without affecting existing ELF/Mach-O object tests. + +Targeted test: `make test-wasm-format`. + +### Phase 1: Wasm Frontend to Native Object/JIT + +Add `CFREE_LANG_WASM`, `.wasm`/`.wat` driver classification as source input, +module validation, and native lowering through `CfreeCg`. + +Acceptance: + +- simple Wasm modules compile to native objects for aa64/x64/rv64 targets. +- simple Wasm modules JIT and execute through the existing native + `cfree_link_jit`/`LinkImage` path. +- bounds checks and traps are deterministic. +- imported scalar functions can be resolved through an explicit instance import + table. +- relocatable Wasm objects with a `linking` custom section are rejected as + frontend input with a clear diagnostic. + +Targeted test: `make test-wasm-front`. + +### Phase 2: Minimal Wasm Relocatable Object Target + +Add `src/arch/wasm` and enough `emit_wasm` support for `cfree cc -target +wasm32-none -c` to produce a valid tool-conventions relocatable object. Use +toy first where practical because it already exercises the public CG API +without C parser complexity, but keep the public compile output as a +relocatable object from the start. + +This phase must include `arch_impl_wasm` registration and a wasm32 ABI vtable; +otherwise creating a Wasm-target `CfreeCompiler` reaches `abi_init` before the +backend exists. + +Acceptance: + +- selected toy/C scalar functions lower into valid Wasm code sections inside a + relocatable Wasm object. +- `read_wasm` can ingest the produced object back into an `ObjBuilder` without + losing the function body, symbol, type, or relocation metadata needed for a + later link. +- unsupported target features report precise diagnostics. + +Targeted test: compile selected toy cases to Wasm objects and validate their +object metadata structurally. + +### Phase 3: Wasm Object Read/Write + +Broaden `emit_wasm` and `read_wasm` for relocatable objects using +tool-conventions metadata. + +Acceptance: + +- Wasm objects roundtrip through `cfree objdump`/reader/writer without losing + sections, symbols, relocations, and unknown custom sections. +- Existing internal relocation kinds `R_WASM_FUNCIDX`, `R_WASM_TABLEIDX`, + `R_WASM_MEMOFS`, and `R_WASM_TYPEIDX` are encoded/decoded against the + corresponding tool-conventions wire relocation numbers. Add new + `RelocKind` values only when the wire format requires a distinction the + current internal names cannot represent. +- unsupported relocation kinds fail with a diagnostic that names the kind. + +Targeted test: `make test-wasm-obj`. + +### Phase 4: Wasm Static Linker + +Implement a Wasm-specific link path reached from `cfree_link_exe` before the +native `link_resolve`/`LinkImage` path. Reuse the existing `Linker` input and +archive machinery where practical, but produce a `WasmLinkImage`, not a +native `LinkImage`. + +Acceptance: + +- multiple Wasm object files link into one valid module. +- archives participate in demand loading. +- undefined imports, exported symbols, constructors, memory layout, and table + layout are deterministic. + +Targeted test: `make test-wasm-link`. + +### Phase 5: Wasm to Wasm + +Use the frontend and target together for Wasm-to-Wasm normalization and later +optimization. This should not be a byte-preserving copy; object roundtrip tests +cover preservation. The frontend path validates and re-emits semantically. + +Acceptance: + +- Wasm input lowered to Wasm output validates. +- simple numeric/control modules preserve behavior. +- unsupported features still fail before emission. + +## Testing Strategy + +Prefer small, named fixtures over broad corpus runs: + +- hard-coded binary fixtures for malformed section and LEB cases. +- tiny `.wasm` modules checked into `test/wasm/format`. +- generated fixtures only when the generator is checked in and deterministic. +- external validators/runtimes are optional skips, not hard requirements for + all developer machines. + +Suggested new targets: + +- `make test-wasm-format` +- `make test-wasm-obj` +- `make test-wasm-target` +- `make test-wasm-link` +- `make test-wasm-front` + +External tools such as `wasm-tools validate` or WABT `wasm-validate` may be +used as optional comparison oracles, but the normal tests should exercise +cfree's own validator directly. The suite must remain useful without external +Wasm tooling installed. + +## Open Questions + +- Should the first Wasm final module target `unknown-unknown` only, or should + we define enough WASI startup/import policy for `wasm32-wasi` early? + Decision: freestanding only first. Keep `wasm32-wasi` as a recognized but + explicitly unsupported platform until the freestanding object/link path is + working. +- Should C-to-Wasm initially require structured CG scopes from frontends, or + should the Wasm backend include a general unstructured-CFG-to-structured-Wasm + pass from day one? Decision: hybrid. Prefer structured scopes, add a limited + reducible-CFG structurer, and diagnose irreducible/computed-goto cases in v1. +- How much of BasicCABI should be implemented before any public Wasm target + flag is advertised: scalar-only, scalar plus indirect aggregates, or full + varargs and long-double helper coverage? Decision: scalar plus indirect + aggregates first; defer varargs and runtime-heavy cases with diagnostics. +- What embedding API should own `CfreeWasmInstance` for Wasm-input modules? + Keeping it explicit avoids global state, but the public shape should be + designed before exported C wrappers are promised.