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:
| A | doc/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.