kit

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

commit 796bb740fdbdc05fc863d42e13c42f706a9ba46b
parent 528833572f1915d74cc42c5bc2ffdf5e1a54f97b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 17 May 2026 21:42:42 -0700

Complete Wasm frontend reader validation

Diffstat:
Mdoc/WASM.md | 33+++++++++++++++++++++------------
Mlang/wasm/wasm.c | 1760+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mtest/wasm/cases/control_loop.wat | 2+-
Atest/wasm/err/bad_index.wat | 3+++
Atest/wasm/err/br_table_depth.wat | 4++++
Atest/wasm/err/global_init_type.wat | 4++++
Atest/wasm/err/result_shape.wat | 2++
Atest/wasm/err/stack_underflow.wat | 3+++
Atest/wasm/err/start_sig.wat | 6++++++
Atest/wasm/err/table_ref_type.wat | 4++++
Atest/wasm/err/unsupported_opcode.wat | 4++++
Atest/wasm/meta/import_table_global_start.wat | 10++++++++++
Atest/wasm/meta/type_custom.wat | 6++++++
Mtest/wasm/run.sh | 47+++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 1669 insertions(+), 219 deletions(-)

diff --git a/doc/WASM.md b/doc/WASM.md @@ -757,12 +757,19 @@ This checklist tracks the path from the initial Wasm/WAT frontend subset to a complete implementation. Keep each item tied to a small named fixture or targeted test target. +Current checked reader/encoder and validation items describe the +`lang/wasm` frontend implementation: it can parse, validate, preserve, and +re-encode the listed module metadata. Metadata that is not lowered yet +(`import`, `table`, `global`, `elem`, and `start`) is accepted by the +reader/encoder and rejected before native CG emission with explicit +diagnostics. + ### Frontend Source and Driver - [x] Add `CFREE_LANG_WASM` and suffix inference for `.wat`/`.wasm`. - [x] Register the Wasm frontend in driver-created compilers and pipelines. - [x] Add `make test-wasm-front` and a small WAT-to-Wasm test helper. -- [ ] Add explicit negative frontend tests for malformed WAT, malformed Wasm, +- [x] Add explicit negative frontend tests for malformed WAT, malformed Wasm, bad indices, stack underflow, unsupported sections, and unsupported opcodes. - [ ] Decide stdin language selection for WAT input instead of treating `-` as C-only. @@ -777,10 +784,10 @@ targeted test target. - [x] Parse standard WAT string escapes and byte escapes. - [x] Parse integer literals with signs, underscores, hex notation, and boundary diagnostics. -- [ ] Parse float literals for `f32.const` and `f64.const`. -- [ ] Parse module-level type definitions and `(func (type N) ...)`. +- [x] Parse float literals for `f32.const` and `f64.const`. +- [x] Parse module-level type definitions and `(func (type N) ...)`. - [x] Parse memories and active data segments for the frontend subset. -- [ ] Parse imports, tables, globals, elements, start, and custom/name +- [x] Parse imports, tables, globals, elements, start, and custom/name sections. - [ ] Preserve source locations through validation and lowering diagnostics. @@ -790,14 +797,16 @@ targeted test target. export, code, locals, constants, calls, local ops, and integer ops. - [x] Reject `linking` custom sections as frontend input. - [ ] Move shared binary mechanics into `src/wasm` with decode/encode contexts. -- [ ] Validate section length, ordering, count, and index-space edge cases with +- [x] Validate section length, ordering, count, and index-space edge cases with direct format tests. - [x] Decode and encode memories and active data segments for the frontend subset. -- [ ] Decode and encode imports, tables, globals, elements, start, - custom/name sections, and target-feature metadata. -- [ ] Preserve unknown custom sections when the caller requests preservation. -- [ ] Add deterministic fixtures for malformed LEB128 and truncated bodies. +- [x] Decode and encode imports, tables, globals, elements, start, and + custom/name sections; target-feature custom sections are preserved as raw + metadata. +- [x] Preserve unknown custom sections in the frontend WAT-to-Wasm/binary + module path. +- [x] Add deterministic fixtures for malformed LEB128 and truncated bodies. ### Validation @@ -805,12 +814,12 @@ targeted test target. integer-only locals/params for the current subset. - [x] Replace depth-only validation with typed operand and control stacks for the accepted frontend subset. -- [ ] Validate exact function result stack shape, unreachable polymorphism, and +- [x] Validate exact function result stack shape, unreachable polymorphism, and fallthrough after `return`/`unreachable`. - [x] Validate blocks, loops, if/else, and branch depths for no-result control blocks. -- [ ] Validate branch result arity and `br_table`. -- [ ] Validate memory/table/global/data/element indices, limits, active/passive +- [x] Validate branch result arity and `br_table`. +- [x] Validate memory/table/global/data/element indices, limits, active/passive segment rules, and start function signature. - [ ] Centralize feature gating in a `WasmFeatureSet`. - [ ] Add clear diagnostics for every deferred proposal: SIMD, threads, diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c @@ -13,6 +13,8 @@ typedef enum WasmValType { WASM_VAL_I64 = 0x7e, WASM_VAL_F32 = 0x7d, WASM_VAL_F64 = 0x7c, + WASM_VAL_FUNCREF = 0x70, + WASM_VAL_EXTERNREF = 0x6f, } WasmValType; typedef enum WasmInsnKind { @@ -175,6 +177,11 @@ typedef struct WasmInsn { typedef struct WasmFunc { char *name; + uint32_t typeidx; + int has_typeidx; + int is_import; + char *import_module; + char *import_name; WasmValType params[16]; uint32_t nparams; WasmValType locals[32]; @@ -188,21 +195,97 @@ typedef struct WasmFunc { uint32_t cap_insns; } WasmFunc; +typedef struct WasmFuncType { + char *name; + WasmValType params[16]; + uint32_t nparams; + WasmValType results[1]; + uint32_t nresults; +} WasmFuncType; + typedef struct WasmMemory { + char *name; uint32_t min_pages; uint32_t max_pages; int has_max; + int is_import; + char *import_module; + char *import_name; + char *export_name; uint8_t *data; uint32_t data_len; } WasmMemory; +typedef struct WasmTable { + char *name; + WasmValType elem_type; + uint32_t min; + uint32_t max; + int has_max; + int is_import; + char *import_module; + char *import_name; + char *export_name; +} WasmTable; + +typedef struct WasmGlobal { + char *name; + WasmValType type; + uint8_t mutable_; + WasmInsn init; + int is_import; + char *import_module; + char *import_name; + char *export_name; +} WasmGlobal; + +typedef struct WasmElemSegment { + uint32_t tableidx; + int64_t offset; + uint32_t funcs[64]; + uint32_t nfuncs; +} WasmElemSegment; + +typedef struct WasmExport { + char *name; + uint8_t kind; + uint32_t index; +} WasmExport; + +typedef struct WasmCustom { + char *name; + uint8_t *data; + uint32_t len; +} WasmCustom; + typedef struct WasmModule { CfreeHeap *heap; + WasmFuncType *types; + uint32_t ntypes; + uint32_t cap_types; WasmFunc *funcs; uint32_t nfuncs; uint32_t cap_funcs; WasmMemory memory; int has_memory; + WasmTable *tables; + uint32_t ntables; + uint32_t cap_tables; + WasmGlobal *globals; + uint32_t nglobals; + uint32_t cap_globals; + WasmElemSegment *elems; + uint32_t nelems; + uint32_t cap_elems; + WasmExport *exports; + uint32_t nexports; + uint32_t cap_exports; + WasmCustom *customs; + uint32_t ncustoms; + uint32_t cap_customs; + uint32_t start_func; + int has_start; + int has_target_features; } WasmModule; typedef struct WasmTok { @@ -271,6 +354,13 @@ static char *wasm_strdup(CfreeHeap *h, const char *s, size_t len) { return out; } +static void wasm_free_str(CfreeHeap *h, char **s) { + if (*s) { + h->free(h, *s, strlen(*s) + 1u); + *s = NULL; + } +} + static void wasm_module_init(WasmModule *m, CfreeHeap *heap) { memset(m, 0, sizeof *m); m->heap = heap; @@ -280,23 +370,58 @@ static void wasm_module_free(WasmModule *m) { uint32_t i; if (!m || !m->heap) return; + for (i = 0; i < m->ntypes; ++i) + wasm_free_str(m->heap, &m->types[i].name); + if (m->types) + m->heap->free(m->heap, m->types, sizeof(*m->types) * m->cap_types); for (i = 0; i < m->nfuncs; ++i) { WasmFunc *f = &m->funcs[i]; - if (f->name) - m->heap->free(m->heap, f->name, strlen(f->name) + 1u); - if (f->export_name) - m->heap->free(m->heap, f->export_name, strlen(f->export_name) + 1u); + wasm_free_str(m->heap, &f->name); + wasm_free_str(m->heap, &f->import_module); + wasm_free_str(m->heap, &f->import_name); + wasm_free_str(m->heap, &f->export_name); for (uint32_t j = 0; j < f->nparams + f->nlocals; ++j) - if (f->local_names[j]) - m->heap->free(m->heap, f->local_names[j], - strlen(f->local_names[j]) + 1u); + wasm_free_str(m->heap, &f->local_names[j]); if (f->insns) m->heap->free(m->heap, f->insns, sizeof(*f->insns) * f->cap_insns); } if (m->funcs) m->heap->free(m->heap, m->funcs, sizeof(*m->funcs) * m->cap_funcs); + wasm_free_str(m->heap, &m->memory.name); + wasm_free_str(m->heap, &m->memory.import_module); + wasm_free_str(m->heap, &m->memory.import_name); + wasm_free_str(m->heap, &m->memory.export_name); if (m->memory.data) m->heap->free(m->heap, m->memory.data, m->memory.data_len); + for (i = 0; i < m->ntables; ++i) { + wasm_free_str(m->heap, &m->tables[i].name); + wasm_free_str(m->heap, &m->tables[i].import_module); + wasm_free_str(m->heap, &m->tables[i].import_name); + wasm_free_str(m->heap, &m->tables[i].export_name); + } + if (m->tables) + m->heap->free(m->heap, m->tables, sizeof(*m->tables) * m->cap_tables); + for (i = 0; i < m->nglobals; ++i) { + wasm_free_str(m->heap, &m->globals[i].name); + wasm_free_str(m->heap, &m->globals[i].import_module); + wasm_free_str(m->heap, &m->globals[i].import_name); + wasm_free_str(m->heap, &m->globals[i].export_name); + } + if (m->globals) + m->heap->free(m->heap, m->globals, sizeof(*m->globals) * m->cap_globals); + if (m->elems) + m->heap->free(m->heap, m->elems, sizeof(*m->elems) * m->cap_elems); + for (i = 0; i < m->nexports; ++i) + wasm_free_str(m->heap, &m->exports[i].name); + if (m->exports) + m->heap->free(m->heap, m->exports, sizeof(*m->exports) * m->cap_exports); + for (i = 0; i < m->ncustoms; ++i) { + wasm_free_str(m->heap, &m->customs[i].name); + if (m->customs[i].data) + m->heap->free(m->heap, m->customs[i].data, m->customs[i].len); + } + if (m->customs) + m->heap->free(m->heap, m->customs, sizeof(*m->customs) * m->cap_customs); memset(m, 0, sizeof *m); } @@ -340,6 +465,139 @@ static WasmFunc *wasm_add_func(CfreeCompiler *c, WasmModule *m) { return f; } +static WasmFuncType *wasm_add_type(CfreeCompiler *c, WasmModule *m) { + WasmFuncType *t; + if (m->ntypes == m->cap_types) { + uint32_t new_cap = m->cap_types ? m->cap_types * 2u : 8u; + void *p = wasm_realloc(m->heap, m->types, sizeof(*m->types) * m->cap_types, + sizeof(*m->types) * new_cap); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->types = (WasmFuncType *)p; + memset(m->types + m->cap_types, 0, + sizeof(*m->types) * (new_cap - m->cap_types)); + m->cap_types = new_cap; + } + t = &m->types[m->ntypes++]; + memset(t, 0, sizeof *t); + return t; +} + +static uint32_t wasm_intern_func_type(CfreeCompiler *c, WasmModule *m, + const WasmFunc *f) { + uint32_t i; + for (i = 0; i < m->ntypes; ++i) { + WasmFuncType *t = &m->types[i]; + if (t->nparams == f->nparams && t->nresults == f->nresults && + memcmp(t->params, f->params, sizeof(t->params[0]) * t->nparams) == 0 && + memcmp(t->results, f->results, + sizeof(t->results[0]) * t->nresults) == 0) + return i; + } + { + WasmFuncType *t = wasm_add_type(c, m); + t->nparams = f->nparams; + memcpy(t->params, f->params, sizeof(t->params[0]) * f->nparams); + t->nresults = f->nresults; + memcpy(t->results, f->results, sizeof(t->results[0]) * f->nresults); + return m->ntypes - 1u; + } +} + +static WasmTable *wasm_add_table(CfreeCompiler *c, WasmModule *m) { + WasmTable *t; + if (m->ntables == m->cap_tables) { + uint32_t new_cap = m->cap_tables ? m->cap_tables * 2u : 2u; + void *p = wasm_realloc(m->heap, m->tables, + sizeof(*m->tables) * m->cap_tables, + sizeof(*m->tables) * new_cap); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->tables = (WasmTable *)p; + memset(m->tables + m->cap_tables, 0, + sizeof(*m->tables) * (new_cap - m->cap_tables)); + m->cap_tables = new_cap; + } + t = &m->tables[m->ntables++]; + memset(t, 0, sizeof *t); + return t; +} + +static WasmGlobal *wasm_add_global(CfreeCompiler *c, WasmModule *m) { + WasmGlobal *g; + if (m->nglobals == m->cap_globals) { + uint32_t new_cap = m->cap_globals ? m->cap_globals * 2u : 4u; + void *p = wasm_realloc(m->heap, m->globals, + sizeof(*m->globals) * m->cap_globals, + sizeof(*m->globals) * new_cap); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->globals = (WasmGlobal *)p; + memset(m->globals + m->cap_globals, 0, + sizeof(*m->globals) * (new_cap - m->cap_globals)); + m->cap_globals = new_cap; + } + g = &m->globals[m->nglobals++]; + memset(g, 0, sizeof *g); + return g; +} + +static WasmElemSegment *wasm_add_elem(CfreeCompiler *c, WasmModule *m) { + WasmElemSegment *e; + if (m->nelems == m->cap_elems) { + uint32_t new_cap = m->cap_elems ? m->cap_elems * 2u : 4u; + void *p = wasm_realloc(m->heap, m->elems, sizeof(*m->elems) * m->cap_elems, + sizeof(*m->elems) * new_cap); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->elems = (WasmElemSegment *)p; + memset(m->elems + m->cap_elems, 0, + sizeof(*m->elems) * (new_cap - m->cap_elems)); + m->cap_elems = new_cap; + } + e = &m->elems[m->nelems++]; + memset(e, 0, sizeof *e); + return e; +} + +static WasmExport *wasm_add_export(CfreeCompiler *c, WasmModule *m) { + WasmExport *e; + if (m->nexports == m->cap_exports) { + uint32_t new_cap = m->cap_exports ? m->cap_exports * 2u : 8u; + void *p = wasm_realloc(m->heap, m->exports, + sizeof(*m->exports) * m->cap_exports, + sizeof(*m->exports) * new_cap); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->exports = (WasmExport *)p; + memset(m->exports + m->cap_exports, 0, + sizeof(*m->exports) * (new_cap - m->cap_exports)); + m->cap_exports = new_cap; + } + e = &m->exports[m->nexports++]; + memset(e, 0, sizeof *e); + return e; +} + +static WasmCustom *wasm_add_custom(CfreeCompiler *c, WasmModule *m) { + WasmCustom *cs; + if (m->ncustoms == m->cap_customs) { + uint32_t new_cap = m->cap_customs ? m->cap_customs * 2u : 4u; + void *p = wasm_realloc(m->heap, m->customs, + sizeof(*m->customs) * m->cap_customs, + sizeof(*m->customs) * new_cap); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->customs = (WasmCustom *)p; + memset(m->customs + m->cap_customs, 0, + sizeof(*m->customs) * (new_cap - m->cap_customs)); + m->cap_customs = new_cap; + } + cs = &m->customs[m->ncustoms++]; + memset(cs, 0, sizeof *cs); + return cs; +} + static void wasm_func_add_insn(CfreeCompiler *c, WasmModule *m, WasmFunc *f, WasmInsnKind kind, int64_t imm) { if (f->ninsns == f->cap_insns) { @@ -630,9 +888,34 @@ static int wat_val_type(WasmTok t, WasmValType *out) { *out = WASM_VAL_F64; return 1; } + if (tok_is(t, "funcref")) { + *out = WASM_VAL_FUNCREF; + return 1; + } + if (tok_is(t, "externref")) { + *out = WASM_VAL_EXTERNREF; + return 1; + } return 0; } +static int wasm_is_num_type(WasmValType vt) { + return vt == WASM_VAL_I32 || vt == WASM_VAL_I64 || vt == WASM_VAL_F32 || + vt == WASM_VAL_F64; +} + +static uint8_t wasm_export_kind_from_tok(WasmTok t) { + if (tok_is(t, "func")) + return 0; + if (tok_is(t, "table")) + return 1; + if (tok_is(t, "memory")) + return 2; + if (tok_is(t, "global")) + return 3; + return 0xffu; +} + static void wat_skip_list(WatParser *p) { uint32_t depth = 1; while (depth && p->tok.kind != WT_EOF) { @@ -1121,6 +1404,23 @@ static void wat_parse_func_index(WatParser *p, int64_t *out) { "wasm wat: expected instruction immediate"); } +static void wat_parse_type_index(WatParser *p, int64_t *out) { + uint32_t i; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < p->module->ntypes; ++i) { + if (wasm_name_eq(p->module->types[i].name, p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown type name"); + } + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected type index"); +} + static void wat_parse_local_index(WatParser *p, WasmFunc *f, int64_t *out) { uint32_t i, nlocals = f->nparams + f->nlocals; if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { @@ -1466,6 +1766,8 @@ static void wat_parse_instr(WatParser *p, WasmFunc *f) { static void wat_parse_func(WatParser *p) { WasmFunc *f = wasm_add_func(p->c, p->module); + uint32_t checked_params = 0; + uint32_t checked_results = 0; wat_expect(p, WT_LPAREN, "'('"); if (!tok_is(p->tok, "func")) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), @@ -1487,6 +1789,33 @@ static void wat_parse_func(WatParser *p) { wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: expected export string"); f->export_name = wat_dup_string(p, p->tok, NULL); + { + WasmExport *ex = wasm_add_export(p->c, p->module); + ex->name = wasm_strdup(p->module->heap, f->export_name, + strlen(f->export_name)); + if (!ex->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + ex->kind = 0; + ex->index = p->module->nfuncs - 1u; + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "type")) { + int64_t typeidx; + wat_next(p); + wat_parse_type_index(p, &typeidx); + if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: type index out of range"); + f->typeidx = (uint32_t)typeidx; + f->has_typeidx = 1; + f->nparams = p->module->types[typeidx].nparams; + memcpy(f->params, p->module->types[typeidx].params, + sizeof(f->params[0]) * f->nparams); + f->nresults = p->module->types[typeidx].nresults; + memcpy(f->results, p->module->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); wat_next(p); wat_expect(p, WT_RPAREN, "')'"); } else if (tok_is(p->tok, "param")) { @@ -1505,6 +1834,26 @@ static void wat_parse_func(WatParser *p) { if (!wat_val_type(p->tok, &vt)) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: expected parameter type"); + if (!wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported parameter type"); + if (f->has_typeidx) { + if (checked_params >= f->nparams || f->params[checked_params] != vt) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: parameter does not match type"); + if (have_name) { + f->local_names[checked_params] = + wasm_strdup(p->module->heap, pending_name.p, + pending_name.len); + if (!f->local_names[checked_params]) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + have_name = 0; + } + checked_params++; + wat_next(p); + continue; + } if (f->nparams >= 16u) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: too many parameters"); @@ -1526,8 +1875,18 @@ static void wat_parse_func(WatParser *p) { if (!wat_val_type(p->tok, &vt)) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: expected result type"); - f->results[0] = vt; - f->nresults = 1; + if (!wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported result type"); + if (f->has_typeidx) { + if (checked_results >= f->nresults || f->results[checked_results] != vt) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: result does not match type"); + checked_results++; + } else { + f->results[0] = vt; + f->nresults = 1; + } wat_next(p); wat_expect(p, WT_RPAREN, "')'"); } else if (tok_is(p->tok, "local")) { @@ -1577,30 +1936,128 @@ static void wat_parse_func(WatParser *p) { wat_parse_instr(p, f); } } + if (!f->has_typeidx) + f->typeidx = wasm_intern_func_type(p->c, p->module, f); + else if (checked_params && checked_params != f->nparams) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: parameter list does not match type"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_type_field(WatParser *p) { + WasmFuncType *t = wasm_add_type(p->c, p->module); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + t->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!t->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "func")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected func type"); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + wat_expect(p, WT_LPAREN, "'('"); + if (tok_is(p->tok, "param")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + wat_next(p); + continue; + } + if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected parameter type"); + if (t->nparams >= 16u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: too many parameters"); + t->params[t->nparams++] = vt; + wat_next(p); + } + } else if (tok_is(p->tok, "result")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected result type"); + if (t->nresults >= 1u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: multi-result unsupported"); + t->results[t->nresults++] = vt; + wat_next(p); + } + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected type field"); + } + wat_expect(p, WT_RPAREN, "')'"); + } + wat_expect(p, WT_RPAREN, "')'"); wat_expect(p, WT_RPAREN, "')'"); } static void wat_parse_export_field(WatParser *p) { char *name; int64_t idx = 0; + uint8_t kind; if (p->tok.kind != WT_STRING) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: expected export string"); name = wat_dup_string(p, p->tok, NULL); wat_next(p); wat_expect(p, WT_LPAREN, "'('"); - if (!tok_is(p->tok, "func")) + kind = wasm_export_kind_from_tok(p->tok); + if (kind == 0xffu) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: only function exports are supported"); + "wasm wat: expected export kind"); wat_next(p); - wat_parse_func_index(p, &idx); - if (idx < 0 || (uint64_t)idx >= p->module->nfuncs) + if (kind == 0) + wat_parse_func_index(p, &idx); + else if (!wat_parse_i64(p, &idx)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export index"); + if (idx < 0 || idx > UINT32_MAX) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: function export index out of range"); - if (p->module->funcs[idx].export_name) - p->module->heap->free(p->module->heap, p->module->funcs[idx].export_name, - strlen(p->module->funcs[idx].export_name) + 1u); - p->module->funcs[idx].export_name = name; + "wasm wat: export index out of range"); + { + WasmExport *ex = wasm_add_export(p->c, p->module); + ex->name = name; + ex->kind = kind; + ex->index = (uint32_t)idx; + } + if (kind == 0 && (uint64_t)idx < p->module->nfuncs) { + wasm_free_str(p->module->heap, &p->module->funcs[idx].export_name); + p->module->funcs[idx].export_name = wasm_strdup(p->module->heap, name, + strlen(name)); + if (!p->module->funcs[idx].export_name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + } else if (kind == 2 && idx == 0 && p->module->has_memory) { + wasm_free_str(p->module->heap, &p->module->memory.export_name); + p->module->memory.export_name = wasm_strdup(p->module->heap, name, + strlen(name)); + if (!p->module->memory.export_name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + } else if (kind == 1 && (uint64_t)idx < p->module->ntables) { + wasm_free_str(p->module->heap, &p->module->tables[idx].export_name); + p->module->tables[idx].export_name = wasm_strdup(p->module->heap, name, + strlen(name)); + if (!p->module->tables[idx].export_name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + } else if (kind == 3 && (uint64_t)idx < p->module->nglobals) { + wasm_free_str(p->module->heap, &p->module->globals[idx].export_name); + p->module->globals[idx].export_name = wasm_strdup(p->module->heap, name, + strlen(name)); + if (!p->module->globals[idx].export_name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + } wat_next(p); wat_expect(p, WT_RPAREN, "')'"); wat_expect(p, WT_RPAREN, "')'"); @@ -1633,6 +2090,371 @@ static void wat_parse_memory_field(WatParser *p) { wat_expect(p, WT_RPAREN, "')'"); } +static void wat_parse_limits(WatParser *p, uint32_t *min, uint32_t *max, + int *has_max, const char *what) { + int64_t lo, hi; + if (!wat_parse_i64(p, &lo) || lo < 0 || lo > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected %s minimum", what); + *min = (uint32_t)lo; + wat_next(p); + if (p->tok.kind != WT_RPAREN) { + if (!wat_parse_i64(p, &hi) || hi < lo || hi > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad %s maximum", what); + *has_max = 1; + *max = (uint32_t)hi; + wat_next(p); + } +} + +static void wat_parse_table_limits_and_type(WatParser *p, WasmTable *t) { + int64_t lo, hi; + if (!wat_parse_i64(p, &lo) || lo < 0 || lo > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected table minimum"); + t->min = (uint32_t)lo; + wat_next(p); + if (p->tok.kind == WT_ATOM) { + WasmValType maybe_type; + if (!wat_val_type(p->tok, &maybe_type)) { + if (!wat_parse_i64(p, &hi) || hi < lo || hi > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad table maximum"); + t->has_max = 1; + t->max = (uint32_t)hi; + wat_next(p); + } + } + if (!wat_val_type(p->tok, &t->elem_type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected table element type"); + wat_next(p); +} + +static void wat_parse_import_field(WatParser *p) { + char *mod, *name; + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected import module string"); + mod = wat_dup_string(p, p->tok, NULL); + wat_next(p); + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected import name string"); + name = wat_dup_string(p, p->tok, NULL); + wat_next(p); + wat_expect(p, WT_LPAREN, "'('"); + if (tok_is(p->tok, "func")) { + WasmFunc *f = wasm_add_func(p->c, p->module); + f->is_import = 1; + f->import_module = mod; + f->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + f->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!f->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + wat_expect(p, WT_LPAREN, "'('"); + if (tok_is(p->tok, "type")) { + int64_t typeidx; + wat_next(p); + wat_parse_type_index(p, &typeidx); + if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: type index out of range"); + f->typeidx = (uint32_t)typeidx; + f->has_typeidx = 1; + f->nparams = p->module->types[typeidx].nparams; + memcpy(f->params, p->module->types[typeidx].params, + sizeof(f->params[0]) * f->nparams); + f->nresults = p->module->types[typeidx].nresults; + memcpy(f->results, p->module->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + wat_next(p); + } else if (tok_is(p->tok, "param")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + wat_next(p); + continue; + } + if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected parameter type"); + if (f->nparams >= 16u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: too many parameters"); + f->params[f->nparams++] = vt; + wat_next(p); + } + } else if (tok_is(p->tok, "result")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected result type"); + if (f->nresults >= 1u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: multi-result unsupported"); + f->results[f->nresults++] = vt; + wat_next(p); + } + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected import func type field"); + } + wat_expect(p, WT_RPAREN, "')'"); + } + if (!f->has_typeidx) + f->typeidx = wasm_intern_func_type(p->c, p->module, f); + } else if (tok_is(p->tok, "memory")) { + if (p->module->has_memory) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: multiple memories are unsupported"); + p->module->has_memory = 1; + p->module->memory.is_import = 1; + p->module->memory.import_module = mod; + p->module->memory.import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + p->module->memory.name = + wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!p->module->memory.name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + wat_parse_limits(p, &p->module->memory.min_pages, + &p->module->memory.max_pages, + &p->module->memory.has_max, "memory"); + } else if (tok_is(p->tok, "table")) { + WasmTable *t = wasm_add_table(p->c, p->module); + t->is_import = 1; + t->import_module = mod; + t->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + t->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!t->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + wat_parse_table_limits_and_type(p, t); + } else if (tok_is(p->tok, "global")) { + WasmGlobal *g = wasm_add_global(p->c, p->module); + g->is_import = 1; + g->import_module = mod; + g->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + g->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!g->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "mut")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected mut"); + g->mutable_ = 1; + wat_next(p); + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else { + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + } + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported import kind"); + } + wat_expect(p, WT_RPAREN, "')'"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_table_field(WatParser *p) { + WasmTable *t = wasm_add_table(p->c, p->module); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + t->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!t->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + wat_parse_table_limits_and_type(p, t); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_const_expr(WatParser *p, WasmInsn *out) { + WasmInsnKind kind; + int has_imm; + memset(out, 0, sizeof *out); + wat_expect(p, WT_LPAREN, "'('"); + if (!wat_instr_kind(p->tok, &kind, &has_imm) || + (kind != WASM_INSN_I32_CONST && kind != WASM_INSN_I64_CONST && + kind != WASM_INSN_F32_CONST && kind != WASM_INSN_F64_CONST)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected constant expression"); + out->kind = (uint8_t)kind; + wat_next(p); + if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { + if (!wat_parse_f64(p, &out->fp)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected float immediate"); + } else if (!wat_parse_i64(p, &out->imm)) { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected integer immediate"); + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_global_field(WatParser *p) { + WasmGlobal *g = wasm_add_global(p->c, p->module); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + g->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + if (!g->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + wat_next(p); + } + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (tok_is(p->tok, "export")) { + wat_next(p); + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export string"); + g->export_name = wat_dup_string(p, p->tok, NULL); + { + WasmExport *ex = wasm_add_export(p->c, p->module); + ex->name = wasm_strdup(p->module->heap, g->export_name, + strlen(g->export_name)); + if (!ex->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + ex->kind = 3; + ex->index = p->module->nglobals - 1u; + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "mut")) { + g->mutable_ = 1; + wat_next(p); + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + } + } else { + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + } + if (!g->type) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: missing global type"); + wat_parse_const_expr(p, &g->init); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_elem_field(WatParser *p) { + WasmElemSegment *e = wasm_add_elem(p->c, p->module); + e->tableidx = 0; + if (p->tok.kind == WT_ATOM) { + int64_t tableidx; + if (wat_parse_i64(p, &tableidx)) { + if (tableidx < 0 || tableidx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: table index out of range"); + e->tableidx = (uint32_t)tableidx; + wat_next(p); + } + } + { + WasmInsn off; + wat_parse_const_expr(p, &off); + if (off.kind != WASM_INSN_I32_CONST || off.imm < 0) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: element offset must be i32.const"); + e->offset = off.imm; + } + if (tok_is(p->tok, "func")) + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + int64_t idx; + if (e->nfuncs >= 64u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: too many element functions"); + wat_parse_func_index(p, &idx); + if (idx < 0 || idx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: element function index out of range"); + e->funcs[e->nfuncs++] = (uint32_t)idx; + wat_next(p); + } + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_start_field(WatParser *p) { + int64_t idx; + wat_parse_func_index(p, &idx); + if (idx < 0 || idx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: start function index out of range"); + p->module->has_start = 1; + p->module->start_func = (uint32_t)idx; + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_custom_field(WatParser *p) { + WasmCustom *cs; + size_t n = 0; + char *bytes; + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected custom section name"); + cs = wasm_add_custom(p->c, p->module); + cs->name = wat_dup_string(p, p->tok, NULL); + if (strcmp(cs->name, "target_features") == 0 || + strcmp(cs->name, "target-feature") == 0) + p->module->has_target_features = 1; + wat_next(p); + if (p->tok.kind == WT_STRING) { + bytes = wat_dup_string(p, p->tok, &n); + cs->data = (uint8_t *)p->module->heap->alloc(p->module->heap, n ? n : 1u, 1); + if (!cs->data) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + memcpy(cs->data, bytes, n); + cs->len = (uint32_t)n; + p->module->heap->free(p->module->heap, bytes, p->tok.len + 1u); + wat_next(p); + } + wat_expect(p, WT_RPAREN, "')'"); +} + static void wat_parse_data_field(WatParser *p) { int64_t offset = 0; char *bytes; @@ -1686,7 +2508,10 @@ static void wat_parse_module(CfreeCompiler *c, const CfreeBytesInput *input, wasm_error(c, wasm_loc(p.tok.line, p.tok.col), "wasm wat: expected module field"); wat_next(&p); - if (tok_is(p.tok, "func")) { + if (tok_is(p.tok, "type")) { + wat_next(&p); + wat_parse_type_field(&p); + } else if (tok_is(p.tok, "func")) { p.pos = (size_t)(p.tok.p - p.src); p.line = p.tok.line; p.col = p.tok.col; @@ -1705,11 +2530,24 @@ static void wat_parse_module(CfreeCompiler *c, const CfreeBytesInput *input, } else if (tok_is(p.tok, "data")) { wat_next(&p); wat_parse_data_field(&p); - } else if (tok_is(p.tok, "import") || tok_is(p.tok, "table") || - tok_is(p.tok, "global") || tok_is(p.tok, "elem") || - tok_is(p.tok, "start")) { - wasm_error(c, wasm_loc(p.tok.line, p.tok.col), - "wasm wat: unsupported module field"); + } else if (tok_is(p.tok, "import")) { + wat_next(&p); + wat_parse_import_field(&p); + } else if (tok_is(p.tok, "table")) { + wat_next(&p); + wat_parse_table_field(&p); + } else if (tok_is(p.tok, "global")) { + wat_next(&p); + wat_parse_global_field(&p); + } else if (tok_is(p.tok, "elem")) { + wat_next(&p); + wat_parse_elem_field(&p); + } else if (tok_is(p.tok, "start")) { + wat_next(&p); + wat_parse_start_field(&p); + } else if (tok_is(p.tok, "custom") || tok_is(p.tok, "@custom")) { + wat_next(&p); + wat_parse_custom_field(&p); } else { wat_skip_list(&p); } @@ -1728,9 +2566,10 @@ static uint8_t bin_u8(BinReader *r) { static uint32_t bin_uleb(BinReader *r) { uint32_t result = 0, shift = 0; + uint32_t nbytes = 0; for (;;) { uint8_t b = bin_u8(r); - if (shift >= 35u) + if (nbytes++ >= 5u || (shift == 28u && (b & 0xf0u))) wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid uleb128"); result |= (uint32_t)(b & 0x7fu) << shift; if (!(b & 0x80u)) @@ -1742,8 +2581,12 @@ static uint32_t bin_uleb(BinReader *r) { static int64_t bin_sleb(BinReader *r, uint32_t bits) { int64_t result = 0; uint32_t shift = 0; + uint32_t max_bytes = (bits + 6u) / 7u; + uint32_t nbytes = 0; uint8_t b; do { + if (nbytes++ >= max_bytes) + wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid sleb128"); b = bin_u8(r); result |= (int64_t)(b & 0x7fu) << shift; shift += 7u; @@ -1776,18 +2619,42 @@ static void bin_need(BinReader *r, size_t n) { wasm_error(r->c, wasm_loc(0, 0), "wasm: section length out of bounds"); } -typedef struct BinType { - WasmValType params[16]; - uint32_t nparams; - WasmValType results[1]; - uint32_t nresults; -} BinType; +static char *bin_name(BinReader *r, CfreeHeap *h, uint32_t *len_out) { + uint32_t n = bin_uleb(r); + char *s; + bin_need(r, n); + s = wasm_strdup(h, (const char *)(r->data + r->pos), n); + if (!s) + wasm_error(r->c, wasm_loc(0, 0), "wasm: out of memory"); + r->pos += n; + if (len_out) + *len_out = n; + return s; +} + +static WasmValType bin_val_type(BinReader *r, int refs_ok) { + uint8_t b = bin_u8(r); + switch (b) { + case WASM_VAL_I32: + case WASM_VAL_I64: + case WASM_VAL_F32: + case WASM_VAL_F64: + return (WasmValType)b; + case WASM_VAL_FUNCREF: + case WASM_VAL_EXTERNREF: + if (refs_ok) + return (WasmValType)b; + break; + default: + break; + } + wasm_error(r->c, wasm_loc(0, 0), "wasm: unsupported value type 0x%02x", b); + return WASM_VAL_I32; +} static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, WasmModule *out) { BinReader r; - BinType types[64]; - uint32_t ntypes = 0; uint32_t nfunc_types = 0; uint8_t last_id = 0; memset(&r, 0, sizeof r); @@ -1814,6 +2681,24 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, if (name_len == 7u && memcmp(r.data + r.pos, "linking", 7) == 0) wasm_error(c, wasm_loc(0, 0), "wasm: relocatable object metadata is not frontend input"); + { + WasmCustom *cs = wasm_add_custom(c, out); + cs->name = wasm_strdup(out->heap, (const char *)(r.data + r.pos), + name_len); + if (!cs->name) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + r.pos += name_len; + if (strcmp(cs->name, "target_features") == 0 || + strcmp(cs->name, "target-feature") == 0) + out->has_target_features = 1; + cs->len = (uint32_t)(end - r.pos); + if (cs->len) { + cs->data = (uint8_t *)out->heap->alloc(out->heap, cs->len, 1); + if (!cs->data) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + memcpy(cs->data, r.data + r.pos, cs->len); + } + } r.pos = end; continue; } @@ -1822,25 +2707,89 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, if (count > 64u) wasm_error(c, wasm_loc(0, 0), "wasm: too many types"); for (i = 0; i < count; ++i) { + WasmFuncType *t = wasm_add_type(c, out); uint32_t j, nparam, nresult; if (bin_u8(&r) != 0x60u) wasm_error(c, wasm_loc(0, 0), "wasm: expected function type"); nparam = bin_uleb(&r); if (nparam > 16u) wasm_error(c, wasm_loc(0, 0), "wasm: too many parameters"); - types[ntypes].nparams = nparam; + t->nparams = nparam; for (j = 0; j < nparam; ++j) - types[ntypes].params[j] = (WasmValType)bin_u8(&r); + t->params[j] = bin_val_type(&r, 0); nresult = bin_uleb(&r); if (nresult > 1u) wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); - types[ntypes].nresults = nresult; + t->nresults = nresult; for (j = 0; j < nresult; ++j) - types[ntypes].results[j] = (WasmValType)bin_u8(&r); - ntypes++; + t->results[j] = bin_val_type(&r, 0); } } else if (id == 2) { - wasm_error(c, wasm_loc(0, 0), "wasm: imports are unsupported"); + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + char *mod = bin_name(&r, out->heap, NULL); + char *name = bin_name(&r, out->heap, NULL); + uint8_t kind = bin_u8(&r); + if (kind == 0) { + uint32_t typeidx = bin_uleb(&r); + WasmFunc *f; + if (typeidx >= out->ntypes) + wasm_error(c, wasm_loc(0, 0), "wasm: bad import type index"); + f = wasm_add_func(c, out); + f->is_import = 1; + f->import_module = mod; + f->import_name = name; + f->typeidx = typeidx; + f->has_typeidx = 1; + f->nparams = out->types[typeidx].nparams; + memcpy(f->params, out->types[typeidx].params, + sizeof(f->params[0]) * f->nparams); + f->nresults = out->types[typeidx].nresults; + memcpy(f->results, out->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + } else if (kind == 1) { + WasmTable *t = wasm_add_table(c, out); + t->is_import = 1; + t->import_module = mod; + t->import_name = name; + t->elem_type = bin_val_type(&r, 1); + { + uint32_t flags = bin_uleb(&r); + if (flags & ~1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported table limits"); + t->has_max = (flags & 1u) != 0; + t->min = bin_uleb(&r); + if (t->has_max) + t->max = bin_uleb(&r); + } + } else if (kind == 2) { + uint32_t flags; + if (out->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: multiple memories unsupported"); + out->has_memory = 1; + out->memory.is_import = 1; + out->memory.import_module = mod; + out->memory.import_name = name; + flags = bin_uleb(&r); + if (flags & ~1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); + out->memory.has_max = (flags & 1u) != 0; + out->memory.min_pages = bin_uleb(&r); + if (out->memory.has_max) + out->memory.max_pages = bin_uleb(&r); + } else if (kind == 3) { + WasmGlobal *g = wasm_add_global(c, out); + g->is_import = 1; + g->import_module = mod; + g->import_name = name; + g->type = bin_val_type(&r, 0); + g->mutable_ = bin_u8(&r); + if (g->mutable_ > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: bad global mutability"); + } else { + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported import kind"); + } + } } else if (id == 3) { uint32_t i, count = bin_uleb(&r); if (count > 64u) @@ -1848,14 +2797,16 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, for (i = 0; i < count; ++i) { uint32_t typeidx = bin_uleb(&r); WasmFunc *f; - if (typeidx >= ntypes) + if (typeidx >= out->ntypes) wasm_error(c, wasm_loc(0, 0), "wasm: bad function type index"); f = wasm_add_func(c, out); - f->nparams = types[typeidx].nparams; - memcpy(f->params, types[typeidx].params, + f->typeidx = typeidx; + f->has_typeidx = 1; + f->nparams = out->types[typeidx].nparams; + memcpy(f->params, out->types[typeidx].params, sizeof(f->params[0]) * f->nparams); - f->nresults = types[typeidx].nresults; - memcpy(f->results, types[typeidx].results, + f->nresults = out->types[typeidx].nresults; + memcpy(f->results, out->types[typeidx].results, sizeof(f->results[0]) * f->nresults); nfunc_types++; } @@ -1865,6 +2816,8 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, if (count > 1u) wasm_error(c, wasm_loc(0, 0), "wasm: multiple memories unsupported"); if (count == 1u) { + if (out->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: multiple memories unsupported"); flags = bin_u8(&r); out->has_memory = 1; out->memory.min_pages = bin_uleb(&r); @@ -1877,37 +2830,115 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, wasm_memory_ensure(c, out, out->memory.min_pages * 65536u); } } else if (id == 4) { - wasm_error(c, wasm_loc(0, 0), "wasm: tables are unsupported"); + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmTable *t = wasm_add_table(c, out); + uint32_t flags; + t->elem_type = bin_val_type(&r, 1); + flags = bin_uleb(&r); + if (flags & ~1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported table limits"); + t->has_max = (flags & 1u) != 0; + t->min = bin_uleb(&r); + if (t->has_max) + t->max = bin_uleb(&r); + } } else if (id == 6) { - wasm_error(c, wasm_loc(0, 0), "wasm: globals are unsupported"); + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmGlobal *g = wasm_add_global(c, out); + uint8_t op; + g->type = bin_val_type(&r, 0); + g->mutable_ = bin_u8(&r); + if (g->mutable_ > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: bad global mutability"); + op = bin_u8(&r); + switch (op) { + case 0x41: + g->init.kind = WASM_INSN_I32_CONST; + g->init.imm = bin_sleb(&r, 32); + break; + case 0x42: + g->init.kind = WASM_INSN_I64_CONST; + g->init.imm = bin_sleb(&r, 64); + break; + case 0x43: + g->init.kind = WASM_INSN_F32_CONST; + g->init.fp = bin_f32(&r); + break; + case 0x44: + g->init.kind = WASM_INSN_F64_CONST; + g->init.fp = bin_f64(&r); + break; + default: + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported global initializer"); + } + if (bin_u8(&r) != 0x0bu) + wasm_error(c, wasm_loc(0, 0), "wasm: malformed global initializer"); + } } else if (id == 7) { uint32_t i, count = bin_uleb(&r); for (i = 0; i < count; ++i) { - uint32_t n = bin_uleb(&r); - const uint8_t *name; + char *name; uint8_t kind; uint32_t idx; - bin_need(&r, n); - name = r.data + r.pos; - r.pos += n; + WasmExport *ex; + name = bin_name(&r, out->heap, NULL); kind = bin_u8(&r); idx = bin_uleb(&r); + if (kind > 3u) + wasm_error(c, wasm_loc(0, 0), "wasm: bad export kind"); + ex = wasm_add_export(c, out); + ex->name = name; + ex->kind = kind; + ex->index = idx; if (kind == 0 && idx < out->nfuncs) { WasmFunc *f = &out->funcs[idx]; - if (f->export_name) - out->heap->free(out->heap, f->export_name, - strlen(f->export_name) + 1u); - f->export_name = wasm_strdup(out->heap, (const char *)name, n); + wasm_free_str(out->heap, &f->export_name); + f->export_name = wasm_strdup(out->heap, name, strlen(name)); if (!f->export_name) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + } else if (kind == 1 && idx < out->ntables) { + wasm_free_str(out->heap, &out->tables[idx].export_name); + out->tables[idx].export_name = wasm_strdup(out->heap, name, + strlen(name)); + } else if (kind == 2 && idx == 0 && out->has_memory) { + wasm_free_str(out->heap, &out->memory.export_name); + out->memory.export_name = wasm_strdup(out->heap, name, strlen(name)); + } else if (kind == 3 && idx < out->nglobals) { + wasm_free_str(out->heap, &out->globals[idx].export_name); + out->globals[idx].export_name = wasm_strdup(out->heap, name, + strlen(name)); } } } else if (id == 8) { - wasm_error(c, wasm_loc(0, 0), "wasm: start functions are unsupported"); + out->has_start = 1; + out->start_func = bin_uleb(&r); } else if (id == 9) { - wasm_error(c, wasm_loc(0, 0), "wasm: element segments are unsupported"); + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmElemSegment *e = wasm_add_elem(c, out); + uint32_t flags = bin_uleb(&r); + uint32_t n; + if (flags != 0) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported element segment kind"); + e->tableidx = 0; + if (bin_u8(&r) != 0x41) + wasm_error(c, wasm_loc(0, 0), + "wasm: expected i32.const element offset"); + e->offset = bin_sleb(&r, 32); + if (bin_u8(&r) != 0x0b || e->offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad element offset"); + n = bin_uleb(&r); + if (n > 64u) + wasm_error(c, wasm_loc(0, 0), "wasm: too many element functions"); + for (uint32_t k = 0; k < n; ++k) + e->funcs[e->nfuncs++] = bin_uleb(&r); + } } else if (id == 10) { uint32_t i, count = bin_uleb(&r); + uint32_t func_index = 0; if (count != nfunc_types) wasm_error(c, wasm_loc(0, 0), "wasm: function/code count mismatch"); for (i = 0; i < count; ++i) { @@ -1915,7 +2946,13 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, size_t body_end; uint32_t local_groups, j; uint32_t control_depth = 0; - WasmFunc *f = &out->funcs[i]; + int saw_body_end = 0; + WasmFunc *f; + while (func_index < out->nfuncs && out->funcs[func_index].is_import) + func_index++; + if (func_index >= out->nfuncs) + wasm_error(c, wasm_loc(0, 0), "wasm: code body without function"); + f = &out->funcs[func_index++]; bin_need(&r, body_size); body_end = r.pos + body_size; local_groups = bin_uleb(&r); @@ -1931,7 +2968,10 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, uint8_t op = bin_u8(&r); if (op == 0x0bu) { if (!control_depth) + { + saw_body_end = 1; break; + } control_depth--; wasm_func_add_insn(c, out, f, WASM_INSN_END, 0); continue; @@ -1944,17 +2984,23 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, wasm_func_add_insn(c, out, f, WASM_INSN_NOP, 0); break; case 0x02: - (void)bin_u8(&r); + if (bin_u8(&r) != 0x40u) + wasm_error(c, wasm_loc(0, 0), + "wasm: block results are unsupported"); control_depth++; wasm_func_add_insn(c, out, f, WASM_INSN_BLOCK, 0); break; case 0x03: - (void)bin_u8(&r); + if (bin_u8(&r) != 0x40u) + wasm_error(c, wasm_loc(0, 0), + "wasm: loop results are unsupported"); control_depth++; wasm_func_add_insn(c, out, f, WASM_INSN_LOOP, 0); break; case 0x04: - (void)bin_u8(&r); + if (bin_u8(&r) != 0x40u) + wasm_error(c, wasm_loc(0, 0), + "wasm: if results are unsupported"); control_depth++; wasm_func_add_insn(c, out, f, WASM_INSN_IF, 0); break; @@ -2417,6 +3463,8 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, op); } } + if (!saw_body_end) + wasm_error(c, wasm_loc(0, 0), "wasm: function body missing end"); r.pos = body_end; } } else if (id == 11) { @@ -2464,8 +3512,12 @@ static CfreeCgTypeId wasm_cg_type(CfreeCompiler *c, CfreeCgBuiltinTypes b, return b.id[CFREE_CG_BUILTIN_F32]; case WASM_VAL_F64: return b.id[CFREE_CG_BUILTIN_F64]; + case WASM_VAL_FUNCREF: + case WASM_VAL_EXTERNREF: + break; } wasm_error(c, wasm_loc(0, 0), "wasm: unsupported value type"); + return b.id[CFREE_CG_BUILTIN_I32]; } static WasmValType wasm_func_local_type(const WasmFunc *f, uint32_t index) { @@ -2878,164 +3930,268 @@ static int wasm_conversion_kind(uint8_t kind, WasmValType *src, } } +typedef struct WasmValStack { + WasmValType vals[256]; + uint32_t depth; +} WasmValStack; + +typedef struct WasmControlFrame { + uint8_t kind; + uint32_t height; + int seen_else; + int unreachable; +} WasmControlFrame; + +static WasmValType wasm_global_init_type(const WasmInsn *in) { + switch (in->kind) { + case WASM_INSN_I32_CONST: + return WASM_VAL_I32; + case WASM_INSN_I64_CONST: + return WASM_VAL_I64; + case WASM_INSN_F32_CONST: + return WASM_VAL_F32; + case WASM_INSN_F64_CONST: + return WASM_VAL_F64; + default: + return 0; + } +} + +static void wasm_stack_push(CfreeCompiler *c, WasmValStack *s, + WasmValType vt) { + if (s->depth >= 256u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); + s->vals[s->depth++] = vt; +} + +static int wasm_stack_pop(CfreeCompiler *c, WasmValStack *s, + WasmControlFrame *frames, uint32_t nframes, + WasmValType expected, const char *what) { + WasmControlFrame *top = &frames[nframes - 1u]; + if (s->depth <= top->height) { + if (top->unreachable) + return 1; + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + } + if (expected && s->vals[s->depth - 1u] != expected) + wasm_error(c, wasm_loc(0, 0), "wasm: %s type mismatch", what); + s->depth--; + return 1; +} + +static void wasm_mark_unreachable(WasmValStack *s, WasmControlFrame *frames, + uint32_t nframes) { + WasmControlFrame *top = &frames[nframes - 1u]; + s->depth = top->height; + top->unreachable = 1; +} + static void wasm_validate(WasmModule *m, CfreeCompiler *c) { uint32_t i, j; + for (i = 0; i < m->ntypes; ++i) { + for (j = 0; j < m->types[i].nparams; ++j) + if (!wasm_is_num_type(m->types[i].params[j])) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported parameter type"); + for (j = 0; j < m->types[i].nresults; ++j) + if (!wasm_is_num_type(m->types[i].results[j])) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported result type"); + } + if (m->has_memory && m->memory.has_max && + m->memory.max_pages < m->memory.min_pages) + wasm_error(c, wasm_loc(0, 0), "wasm: memory maximum below minimum"); + for (i = 0; i < m->ntables; ++i) { + if (m->tables[i].elem_type != WASM_VAL_FUNCREF) + wasm_error(c, wasm_loc(0, 0), + "wasm: reference type is unsupported for tables"); + if (m->tables[i].has_max && m->tables[i].max < m->tables[i].min) + wasm_error(c, wasm_loc(0, 0), "wasm: table maximum below minimum"); + } + for (i = 0; i < m->nglobals; ++i) { + WasmGlobal *g = &m->globals[i]; + if (!wasm_is_num_type(g->type)) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported global type"); + if (!g->is_import && wasm_global_init_type(&g->init) != g->type) + wasm_error(c, wasm_loc(0, 0), "wasm: global initializer type mismatch"); + } + for (i = 0; i < m->nexports; ++i) { + WasmExport *ex = &m->exports[i]; + if ((ex->kind == 0 && ex->index >= m->nfuncs) || + (ex->kind == 1 && ex->index >= m->ntables) || + (ex->kind == 2 && (!m->has_memory || ex->index != 0)) || + (ex->kind == 3 && ex->index >= m->nglobals)) + wasm_error(c, wasm_loc(0, 0), "wasm: export index out of range"); + } + if (m->has_start) { + if (m->start_func >= m->nfuncs) + wasm_error(c, wasm_loc(0, 0), "wasm: start function index out of range"); + if (m->funcs[m->start_func].nparams || m->funcs[m->start_func].nresults) + wasm_error(c, wasm_loc(0, 0), + "wasm: start function must have no params or results"); + } + for (i = 0; i < m->nelems; ++i) { + if (m->elems[i].tableidx >= m->ntables) + wasm_error(c, wasm_loc(0, 0), "wasm: element table index out of range"); + for (j = 0; j < m->elems[i].nfuncs; ++j) + if (m->elems[i].funcs[j] >= m->nfuncs) + wasm_error(c, wasm_loc(0, 0), + "wasm: element function index out of range"); + } for (i = 0; i < m->nfuncs; ++i) { WasmFunc *f = &m->funcs[i]; - WasmValType stack[256]; - uint32_t depth = 0; - uint32_t control_depth = 0; + WasmValStack stack; + WasmControlFrame control[65]; + uint32_t ncontrol = 1; + memset(&stack, 0, sizeof stack); + memset(control, 0, sizeof control); + control[0].kind = 0xffu; + control[0].height = 0; + if (f->is_import) { + if (f->ninsns) + wasm_error(c, wasm_loc(0, 0), "wasm: imported function has body"); + continue; + } if (f->nresults > 1u) wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); for (j = 0; j < f->ninsns; ++j) { WasmInsn *in = &f->insns[j]; WasmValType vt, src, dst; switch (in->kind) { - case WASM_INSN_F32_CONST: - if (depth >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); - stack[depth++] = WASM_VAL_F32; - break; - case WASM_INSN_F64_CONST: - if (depth >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); - stack[depth++] = WASM_VAL_F64; - break; - case WASM_INSN_I32_CONST: - if (depth >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); - stack[depth++] = WASM_VAL_I32; - break; - case WASM_INSN_I64_CONST: - if (depth >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); - stack[depth++] = WASM_VAL_I64; - break; + case WASM_INSN_F32_CONST: wasm_stack_push(c, &stack, WASM_VAL_F32); break; + case WASM_INSN_F64_CONST: wasm_stack_push(c, &stack, WASM_VAL_F64); break; + case WASM_INSN_I32_CONST: wasm_stack_push(c, &stack, WASM_VAL_I32); break; + case WASM_INSN_I64_CONST: wasm_stack_push(c, &stack, WASM_VAL_I64); break; case WASM_INSN_LOCAL_GET: - if (in->kind == WASM_INSN_LOCAL_GET && + if (in->imm < 0 || (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); - if (depth >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); - stack[depth++] = wasm_func_local_type(f, (uint32_t)in->imm); + wasm_stack_push(c, &stack, wasm_func_local_type(f, (uint32_t)in->imm)); break; case WASM_INSN_LOCAL_SET: - if (in->imm < 0 || (uint64_t)in->imm >= - (uint64_t)f->nparams + f->nlocals) - wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); - if (depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 1u] != wasm_func_local_type(f, (uint32_t)in->imm)) - wasm_error(c, wasm_loc(0, 0), "wasm: local type mismatch"); - depth--; - break; case WASM_INSN_LOCAL_TEE: - if (in->imm < 0 || (uint64_t)in->imm >= - (uint64_t)f->nparams + f->nlocals) + if (in->imm < 0 || + (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); - if (depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 1u] != wasm_func_local_type(f, (uint32_t)in->imm)) - wasm_error(c, wasm_loc(0, 0), "wasm: local type mismatch"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_func_local_type(f, (uint32_t)in->imm), "local"); + if (in->kind == WASM_INSN_LOCAL_TEE) + wasm_stack_push(c, &stack, wasm_func_local_type(f, (uint32_t)in->imm)); break; case WASM_INSN_CALL: if (in->imm < 0 || (uint64_t)in->imm >= m->nfuncs) wasm_error(c, wasm_loc(0, 0), "wasm: call index out of range"); - if (depth < m->funcs[in->imm].nparams) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); for (uint32_t k = 0; k < m->funcs[in->imm].nparams; ++k) { uint32_t param = m->funcs[in->imm].nparams - 1u - k; - if (stack[depth - 1u - k] != m->funcs[in->imm].params[param]) - wasm_error(c, wasm_loc(0, 0), "wasm: call argument type mismatch"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->funcs[in->imm].params[param], "call argument"); } - depth -= m->funcs[in->imm].nparams; if (m->funcs[in->imm].nresults) - stack[depth++] = m->funcs[in->imm].results[0]; + wasm_stack_push(c, &stack, m->funcs[in->imm].results[0]); break; case WASM_INSN_RETURN: - if (depth < (f->nresults ? 1 : 0)) - wasm_error(c, wasm_loc(0, 0), "wasm: return stack underflow"); - if (f->nresults && stack[depth - 1u] != f->results[0]) - wasm_error(c, wasm_loc(0, 0), "wasm: return type mismatch"); + if (f->nresults) + wasm_stack_pop(c, &stack, control, ncontrol, f->results[0], + "return"); + wasm_mark_unreachable(&stack, control, ncontrol); break; case WASM_INSN_DROP: - if (depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - depth--; + wasm_stack_pop(c, &stack, control, ncontrol, 0, "drop"); break; case WASM_INSN_I32_EQZ: + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "eqz"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; case WASM_INSN_I64_EQZ: - if (depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if ((in->kind == WASM_INSN_I32_EQZ && stack[depth - 1u] != WASM_VAL_I32) || - (in->kind == WASM_INSN_I64_EQZ && stack[depth - 1u] != WASM_VAL_I64)) - wasm_error(c, wasm_loc(0, 0), "wasm: eqz type mismatch"); - stack[depth - 1u] = WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I64, "eqz"); + wasm_stack_push(c, &stack, WASM_VAL_I32); break; case WASM_INSN_BLOCK: case WASM_INSN_LOOP: - control_depth++; + if (ncontrol >= 65u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + control[ncontrol].kind = in->kind; + control[ncontrol].height = stack.depth; + control[ncontrol].seen_else = 0; + control[ncontrol].unreachable = 0; + ncontrol++; break; case WASM_INSN_IF: - if (depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 1u] != WASM_VAL_I32) - wasm_error(c, wasm_loc(0, 0), "wasm: if condition must be i32"); - depth--; - control_depth++; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "if"); + if (ncontrol >= 65u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + control[ncontrol].kind = in->kind; + control[ncontrol].height = stack.depth; + control[ncontrol].seen_else = 0; + control[ncontrol].unreachable = 0; + ncontrol++; break; case WASM_INSN_ELSE: - if (!control_depth) - wasm_error(c, wasm_loc(0, 0), "wasm: else without block"); + if (ncontrol <= 1u || control[ncontrol - 1u].kind != WASM_INSN_IF) + wasm_error(c, wasm_loc(0, 0), "wasm: else without if"); + if (!control[ncontrol - 1u].unreachable && + stack.depth != control[ncontrol - 1u].height) + wasm_error(c, wasm_loc(0, 0), "wasm: if branch result mismatch"); + stack.depth = control[ncontrol - 1u].height; + control[ncontrol - 1u].seen_else = 1; + control[ncontrol - 1u].unreachable = 0; break; case WASM_INSN_END: - if (!control_depth) + if (ncontrol <= 1u) wasm_error(c, wasm_loc(0, 0), "wasm: end without block"); - control_depth--; + if (!control[ncontrol - 1u].unreachable && + stack.depth != control[ncontrol - 1u].height) + wasm_error(c, wasm_loc(0, 0), "wasm: block result mismatch"); + stack.depth = control[ncontrol - 1u].height; + ncontrol--; break; case WASM_INSN_BR: - if (in->imm < 0 || (uint64_t)in->imm >= control_depth) + if (in->imm < 0 || (uint64_t)in->imm >= ncontrol - 1u) wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_mark_unreachable(&stack, control, ncontrol); break; case WASM_INSN_BR_IF: - if (in->imm < 0 || (uint64_t)in->imm >= control_depth) + if (in->imm < 0 || (uint64_t)in->imm >= ncontrol - 1u) wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); - if (depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 1u] != WASM_VAL_I32) - wasm_error(c, wasm_loc(0, 0), "wasm: br_if condition must be i32"); - depth--; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "br_if"); break; case WASM_INSN_BR_TABLE: if (in->ntargets == 0) wasm_error(c, wasm_loc(0, 0), "wasm: br_table without targets"); - if (depth < 1 || stack[depth - 1u] != WASM_VAL_I32) - wasm_error(c, wasm_loc(0, 0), "wasm: br_table selector must be i32"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "br_table selector"); for (uint32_t k = 0; k < in->ntargets; ++k) - if (in->targets[k] >= control_depth) + if (in->targets[k] >= ncontrol - 1u) wasm_error(c, wasm_loc(0, 0), "wasm: br_table depth out of range"); - depth--; + wasm_mark_unreachable(&stack, control, ncontrol); break; - case WASM_INSN_SELECT: - if (depth < 3) + case WASM_INSN_SELECT: { + WasmValType rhs, lhs; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "select"); + if (stack.depth <= control[ncontrol - 1u].height && + control[ncontrol - 1u].unreachable) { + in->type = WASM_VAL_I32; + break; + } + if (stack.depth < control[ncontrol - 1u].height + 2u) wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 1u] != WASM_VAL_I32 || - stack[depth - 2u] != stack[depth - 3u]) + rhs = stack.vals[--stack.depth]; + lhs = stack.vals[--stack.depth]; + if (lhs != rhs) wasm_error(c, wasm_loc(0, 0), "wasm: select type mismatch"); - in->type = (uint8_t)stack[depth - 3u]; - depth -= 2u; + in->type = (uint8_t)lhs; + wasm_stack_push(c, &stack, lhs); break; + } case WASM_INSN_MEMORY_SIZE: if (!m->has_memory) wasm_error(c, wasm_loc(0, 0), "wasm: memory.size without memory"); - stack[depth++] = WASM_VAL_I32; + wasm_stack_push(c, &stack, WASM_VAL_I32); break; case WASM_INSN_MEMORY_GROW: if (!m->has_memory) wasm_error(c, wasm_loc(0, 0), "wasm: memory.grow without memory"); - if (depth < 1 || stack[depth - 1u] != WASM_VAL_I32) - wasm_error(c, wasm_loc(0, 0), "wasm: memory.grow expects i32"); - stack[depth - 1u] = WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "memory.grow"); + wasm_stack_push(c, &stack, WASM_VAL_I32); break; case WASM_INSN_I32_LOAD: case WASM_INSN_I64_LOAD: @@ -3051,9 +4207,8 @@ static void wasm_validate(WasmModule *m, CfreeCompiler *c) { case WASM_INSN_I64_LOAD32_U: if (!m->has_memory) wasm_error(c, wasm_loc(0, 0), "wasm: load without memory"); - if (depth < 1 || stack[depth - 1u] != WASM_VAL_I32) - wasm_error(c, wasm_loc(0, 0), "wasm: load address must be i32"); - stack[depth - 1u] = wasm_load_result_type(in->kind); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "load"); + wasm_stack_push(c, &stack, wasm_load_result_type(in->kind)); break; case WASM_INSN_I32_STORE: case WASM_INSN_I64_STORE: @@ -3064,62 +4219,68 @@ static void wasm_validate(WasmModule *m, CfreeCompiler *c) { case WASM_INSN_I64_STORE32: if (!m->has_memory) wasm_error(c, wasm_loc(0, 0), "wasm: store without memory"); - if (depth < 2) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 2u] != WASM_VAL_I32 || - stack[depth - 1u] != wasm_store_value_type(in->kind)) - wasm_error(c, wasm_loc(0, 0), "wasm: store type mismatch"); - depth -= 2u; + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_store_value_type(in->kind), "store"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "store"); break; case WASM_INSN_UNREACHABLE: + wasm_mark_unreachable(&stack, control, ncontrol); + break; case WASM_INSN_NOP: break; default: if (wasm_int_unop_kind(in->kind, &vt)) { - if (depth < 1 || stack[depth - 1u] != vt) - wasm_error(c, wasm_loc(0, 0), "wasm: unary operand type mismatch"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "unary operand"); + wasm_stack_push(c, &stack, vt); break; } if (wasm_fp_binop_kind(in->kind, &vt)) { - if (depth < 2 || stack[depth - 1u] != vt || - stack[depth - 2u] != vt) - wasm_error(c, wasm_loc(0, 0), "wasm: fp operand type mismatch"); - depth--; + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); + wasm_stack_push(c, &stack, vt); break; } if (wasm_fp_cmp_kind(in->kind, &vt)) { - if (depth < 2 || stack[depth - 1u] != vt || - stack[depth - 2u] != vt) - wasm_error(c, wasm_loc(0, 0), "wasm: fp compare type mismatch"); - stack[depth - 2u] = WASM_VAL_I32; - depth--; + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp compare"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp compare"); + wasm_stack_push(c, &stack, WASM_VAL_I32); break; } if (wasm_conversion_kind(in->kind, &src, &dst)) { - if (depth < 1 || stack[depth - 1u] != src) - wasm_error(c, wasm_loc(0, 0), "wasm: conversion type mismatch"); - stack[depth - 1u] = dst; + wasm_stack_pop(c, &stack, control, ncontrol, src, "conversion"); + wasm_stack_push(c, &stack, dst); break; } { CfreeCgIntCmpOp cmp; - if (depth < 2) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - if (stack[depth - 1u] != stack[depth - 2u]) - wasm_error(c, wasm_loc(0, 0), "wasm: operand type mismatch"); - if (wasm_int_cmp_op(in->kind, &cmp)) - stack[depth - 2u] = WASM_VAL_I32; - depth--; - break; + WasmValType rhs, lhs; + rhs = stack.depth > control[ncontrol - 1u].height + ? stack.vals[stack.depth - 1u] + : WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, 0, "operand"); + lhs = stack.depth > control[ncontrol - 1u].height + ? stack.vals[stack.depth - 1u] + : rhs; + wasm_stack_pop(c, &stack, control, ncontrol, rhs, "operand"); + if (lhs != rhs) + wasm_error(c, wasm_loc(0, 0), "wasm: operand type mismatch"); + wasm_stack_push(c, &stack, + wasm_int_cmp_op(in->kind, &cmp) ? WASM_VAL_I32 + : lhs); + break; } } } - if (control_depth) + if (ncontrol != 1u) wasm_error(c, wasm_loc(0, 0), "wasm: unterminated control block"); - if (f->nresults && depth < 1) - wasm_error(c, wasm_loc(0, 0), "wasm: missing function result"); - if (f->nresults && stack[depth - 1u] != f->results[0]) - wasm_error(c, wasm_loc(0, 0), "wasm: function result type mismatch"); + if (!control[0].unreachable) { + if (f->nresults) { + if (stack.depth != 1u || stack.vals[0] != f->results[0]) + wasm_error(c, wasm_loc(0, 0), "wasm: function result type mismatch"); + } else if (stack.depth != 0) { + wasm_error(c, wasm_loc(0, 0), "wasm: function leaves extra values"); + } + } } } @@ -3136,6 +4297,20 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, wasm_error(c, wasm_loc(0, 0), "wasm: failed to initialize codegen"); if (m->nfuncs > 64u) wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); + if (m->ntables || m->nelems) + wasm_error(c, wasm_loc(0, 0), + "wasm: tables and call_indirect are not lowered yet"); + if (m->nglobals) + wasm_error(c, wasm_loc(0, 0), "wasm: globals are not lowered yet"); + if (m->has_start) + wasm_error(c, wasm_loc(0, 0), "wasm: start functions are not lowered yet"); + if (m->has_memory && m->memory.is_import) + wasm_error(c, wasm_loc(0, 0), + "wasm: imported memories are not lowered yet"); + for (i = 0; i < m->nfuncs; ++i) + if (m->funcs[i].is_import) + wasm_error(c, wasm_loc(0, 0), + "wasm: imported functions are not lowered yet"); memset(syms, 0, sizeof syms); memset(func_types, 0, sizeof func_types); if (m->has_memory) { @@ -3775,9 +4950,9 @@ static void encode_section(CfreeHeap *h, CfreeWriter *out, uint8_t id, static void enc_type(CfreeWriter *w, const WasmModule *m) { uint32_t i, j; - write_uleb(w, m->nfuncs); - for (i = 0; i < m->nfuncs; ++i) { - const WasmFunc *f = &m->funcs[i]; + write_uleb(w, m->ntypes); + for (i = 0; i < m->ntypes; ++i) { + const WasmFuncType *f = &m->types[i]; write_byte(w, 0x60); write_uleb(w, f->nparams); for (j = 0; j < f->nparams; ++j) @@ -3788,25 +4963,101 @@ static void enc_type(CfreeWriter *w, const WasmModule *m) { } } -static void enc_func(CfreeWriter *w, const WasmModule *m) { - uint32_t i; - write_uleb(w, m->nfuncs); - for (i = 0; i < m->nfuncs; ++i) - write_uleb(w, i); +static void write_limits(CfreeWriter *w, uint32_t min, uint32_t max, + int has_max) { + write_byte(w, has_max ? 1 : 0); + write_uleb(w, min); + if (has_max) + write_uleb(w, max); } -static void enc_export(CfreeWriter *w, const WasmModule *m) { +static void enc_import(CfreeWriter *w, const WasmModule *m) { uint32_t i, n = 0; for (i = 0; i < m->nfuncs; ++i) - if (m->funcs[i].export_name) + if (m->funcs[i].is_import) + n++; + for (i = 0; i < m->ntables; ++i) + if (m->tables[i].is_import) + n++; + if (m->has_memory && m->memory.is_import) + n++; + for (i = 0; i < m->nglobals; ++i) + if (m->globals[i].is_import) n++; write_uleb(w, n); for (i = 0; i < m->nfuncs; ++i) { - if (!m->funcs[i].export_name) + const WasmFunc *f = &m->funcs[i]; + if (!f->is_import) continue; - write_name(w, m->funcs[i].export_name); + write_name(w, f->import_module ? f->import_module : ""); + write_name(w, f->import_name ? f->import_name : ""); write_byte(w, 0); - write_uleb(w, i); + write_uleb(w, f->typeidx); + } + for (i = 0; i < m->ntables; ++i) { + const WasmTable *t = &m->tables[i]; + if (!t->is_import) + continue; + write_name(w, t->import_module ? t->import_module : ""); + write_name(w, t->import_name ? t->import_name : ""); + write_byte(w, 1); + write_byte(w, (uint8_t)t->elem_type); + write_limits(w, t->min, t->max, t->has_max); + } + if (m->has_memory && m->memory.is_import) { + write_name(w, m->memory.import_module ? m->memory.import_module : ""); + write_name(w, m->memory.import_name ? m->memory.import_name : ""); + write_byte(w, 2); + write_limits(w, m->memory.min_pages, m->memory.max_pages, + m->memory.has_max); + } + for (i = 0; i < m->nglobals; ++i) { + const WasmGlobal *g = &m->globals[i]; + if (!g->is_import) + continue; + write_name(w, g->import_module ? g->import_module : ""); + write_name(w, g->import_name ? g->import_name : ""); + write_byte(w, 3); + write_byte(w, (uint8_t)g->type); + write_byte(w, g->mutable_); + } +} + +static int wasm_module_has_imports(const WasmModule *m) { + uint32_t i; + for (i = 0; i < m->nfuncs; ++i) + if (m->funcs[i].is_import) + return 1; + for (i = 0; i < m->ntables; ++i) + if (m->tables[i].is_import) + return 1; + if (m->has_memory && m->memory.is_import) + return 1; + for (i = 0; i < m->nglobals; ++i) + if (m->globals[i].is_import) + return 1; + return 0; +} + +static void enc_func(CfreeWriter *w, const WasmModule *m) { + uint32_t i; + uint32_t n = 0; + for (i = 0; i < m->nfuncs; ++i) + if (!m->funcs[i].is_import) + n++; + write_uleb(w, n); + for (i = 0; i < m->nfuncs; ++i) + if (!m->funcs[i].is_import) + write_uleb(w, m->funcs[i].typeidx); +} + +static void enc_export(CfreeWriter *w, const WasmModule *m) { + uint32_t i; + write_uleb(w, m->nexports); + for (i = 0; i < m->nexports; ++i) { + write_name(w, m->exports[i].name ? m->exports[i].name : ""); + write_byte(w, m->exports[i].kind); + write_uleb(w, m->exports[i].index); } } @@ -3822,6 +5073,47 @@ static void enc_memory(CfreeWriter *w, const WasmModule *m) { write_uleb(w, m->memory.max_pages); } +static uint8_t wasm_opcode(uint8_t kind); + +static void enc_table(CfreeWriter *w, const WasmModule *m) { + uint32_t i, n = 0; + for (i = 0; i < m->ntables; ++i) + if (!m->tables[i].is_import) + n++; + write_uleb(w, n); + for (i = 0; i < m->ntables; ++i) { + const WasmTable *t = &m->tables[i]; + if (t->is_import) + continue; + write_byte(w, (uint8_t)t->elem_type); + write_limits(w, t->min, t->max, t->has_max); + } +} + +static void enc_global(CfreeWriter *w, const WasmModule *m) { + uint32_t i, n = 0; + for (i = 0; i < m->nglobals; ++i) + if (!m->globals[i].is_import) + n++; + write_uleb(w, n); + for (i = 0; i < m->nglobals; ++i) { + const WasmGlobal *g = &m->globals[i]; + if (g->is_import) + continue; + write_byte(w, (uint8_t)g->type); + write_byte(w, g->mutable_); + write_byte(w, wasm_opcode(g->init.kind)); + if (g->init.kind == WASM_INSN_I32_CONST || + g->init.kind == WASM_INSN_I64_CONST) + write_sleb(w, g->init.imm); + else if (g->init.kind == WASM_INSN_F32_CONST) + write_f32(w, g->init.fp); + else if (g->init.kind == WASM_INSN_F64_CONST) + write_f64(w, g->init.fp); + write_byte(w, 0x0b); + } +} + static uint8_t wasm_opcode(uint8_t kind) { switch (kind) { case WASM_INSN_UNREACHABLE: @@ -4120,11 +5412,19 @@ static uint8_t wasm_opcode(uint8_t kind) { static void enc_code(CfreeWriter *w, const WasmModule *m) { uint32_t i, j; - write_uleb(w, m->nfuncs); + uint32_t n = 0; + for (i = 0; i < m->nfuncs; ++i) + if (!m->funcs[i].is_import) + n++; + write_uleb(w, n); for (i = 0; i < m->nfuncs; ++i) { CfreeWriter *body = cfree_writer_mem(m->heap); size_t len; const uint8_t *bytes; + if (m->funcs[i].is_import) { + cfree_writer_close(body); + continue; + } if (m->funcs[i].nlocals) { uint32_t group_count = 0; WasmValType prev = 0; @@ -4202,19 +5502,67 @@ static void enc_data(CfreeWriter *w, const WasmModule *m) { w->write(w, m->memory.data, m->memory.data_len); } +static void enc_elem(CfreeWriter *w, const WasmModule *m) { + uint32_t i, j; + write_uleb(w, m->nelems); + for (i = 0; i < m->nelems; ++i) { + const WasmElemSegment *e = &m->elems[i]; + write_uleb(w, 0); + write_byte(w, 0x41); + write_sleb(w, e->offset); + write_byte(w, 0x0b); + write_uleb(w, e->nfuncs); + for (j = 0; j < e->nfuncs; ++j) + write_uleb(w, e->funcs[j]); + } +} + +static void enc_start(CfreeWriter *w, const WasmModule *m) { + write_uleb(w, m->start_func); +} + +static void encode_custom(CfreeHeap *h, CfreeWriter *out, + const WasmCustom *cs) { + CfreeWriter *tmp = cfree_writer_mem(h); + size_t len; + const uint8_t *bytes; + if (!tmp) + return; + write_name(tmp, cs->name ? cs->name : ""); + if (cs->len) + tmp->write(tmp, cs->data, cs->len); + bytes = cfree_writer_mem_bytes(tmp, &len); + write_byte(out, 0); + write_uleb(out, len); + out->write(out, bytes, len); + cfree_writer_close(tmp); +} + static void wasm_encode(CfreeCompiler *c, const WasmModule *m, CfreeWriter *out) { static const uint8_t magic[] = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}; CfreeHeap *h = cfree_compiler_heap(c); out->write(out, magic, sizeof magic); + for (uint32_t i = 0; i < m->ncustoms; ++i) + encode_custom(h, out, &m->customs[i]); encode_section(h, out, 1, enc_type, m); + if (wasm_module_has_imports(m)) + encode_section(h, out, 2, enc_import, m); encode_section(h, out, 3, enc_func, m); - if (m->has_memory) + if (m->ntables) + encode_section(h, out, 4, enc_table, m); + if (m->has_memory && !m->memory.is_import) encode_section(h, out, 5, enc_memory, m); + if (m->nglobals) + encode_section(h, out, 6, enc_global, m); encode_section(h, out, 7, enc_export, m); + if (m->has_start) + encode_section(h, out, 8, enc_start, m); + if (m->nelems) + encode_section(h, out, 9, enc_elem, m); encode_section(h, out, 10, enc_code, m); - if (m->has_memory) + if (m->has_memory && m->memory.data_len) encode_section(h, out, 11, enc_data, m); } diff --git a/test/wasm/cases/control_loop.wat b/test/wasm/cases/control_loop.wat @@ -4,7 +4,7 @@ (local.set $i (i32.const 0)) (block (loop - (local.tee $i + (local.set $i (i32.add (local.get $i) (i32.const 1))) diff --git a/test/wasm/err/bad_index.wat b/test/wasm/err/bad_index.wat @@ -0,0 +1,3 @@ +(module + (func (export "test_main") (result i32) + local.get 0)) diff --git a/test/wasm/err/br_table_depth.wat b/test/wasm/err/br_table_depth.wat @@ -0,0 +1,4 @@ +(module + (func (export "test_main") + i32.const 0 + br_table 0)) diff --git a/test/wasm/err/global_init_type.wat b/test/wasm/err/global_init_type.wat @@ -0,0 +1,4 @@ +(module + (global i32 (i64.const 1)) + (func (export "test_main") (result i32) + i32.const 0)) diff --git a/test/wasm/err/result_shape.wat b/test/wasm/err/result_shape.wat @@ -0,0 +1,2 @@ +(module + (func (export "test_main") (result i32))) diff --git a/test/wasm/err/stack_underflow.wat b/test/wasm/err/stack_underflow.wat @@ -0,0 +1,3 @@ +(module + (func (export "test_main") (result i32) + i32.add)) diff --git a/test/wasm/err/start_sig.wat b/test/wasm/err/start_sig.wat @@ -0,0 +1,6 @@ +(module + (func $start (result i32) + i32.const 0) + (start $start) + (func (export "test_main") (result i32) + i32.const 0)) diff --git a/test/wasm/err/table_ref_type.wat b/test/wasm/err/table_ref_type.wat @@ -0,0 +1,4 @@ +(module + (table 1 externref) + (func (export "test_main") (result i32) + i32.const 0)) diff --git a/test/wasm/err/unsupported_opcode.wat b/test/wasm/err/unsupported_opcode.wat @@ -0,0 +1,4 @@ +(module + (func (export "test_main") (result i32) + i32.const 0 + i32.atomic.load)) diff --git a/test/wasm/meta/import_table_global_start.wat b/test/wasm/meta/import_table_global_start.wat @@ -0,0 +1,10 @@ +(module + (type $binop (func (param i32 i32) (result i32))) + (import "host" "add" (func $add (type $binop))) + (table 1 funcref) + (global $g i32 (i32.const 7)) + (elem (i32.const 0) func $add) + (func $start) + (start $start) + (func (export "test_main") (result i32) + i32.const 0)) diff --git a/test/wasm/meta/type_custom.wat b/test/wasm/meta/type_custom.wat @@ -0,0 +1,6 @@ +(module + (custom "cfree.test" "metadata") + (type $ret_i32 (func (result i32))) + (func (type $ret_i32) + i32.const 0) + (export "test_main" (func 0))) diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -4,6 +4,8 @@ set -u ROOT=$(cd "$(dirname "$0")/../.." && pwd) BUILD_DIR="$ROOT/build/test/wasm" CASES_DIR="$ROOT/test/wasm/cases" +ERR_DIR="$ROOT/test/wasm/err" +META_DIR="$ROOT/test/wasm/meta" CFREE_BIN="${CFREE:-$ROOT/build/cfree}" WASM_TOOL="$ROOT/build/test/wasm-tool" JIT_RUNNER="$ROOT/build/test/jit-runner" @@ -109,6 +111,16 @@ run_expect_zero() { fi } +run_expect_fail() { + local label=$1 + shift + if "$@" >"$BUILD_DIR/${label//\//_}.out" 2>"$BUILD_DIR/${label//\//_}.err"; then + note_fail "$label expected failure" + else + note_pass "$label" + fi +} + printf 'test-wasm-front target=%s obj=%s\n' "$target_triple" "$TEST_OBJ" for wat in "$CASES_DIR"/*.wat; do @@ -169,5 +181,40 @@ for wat in "$CASES_DIR"/*.wat; do fi done +for wat in "$ERR_DIR"/*.wat; do + [ -e "$wat" ] || continue + name=$(basename "$wat" .wat) + run_expect_fail "err/$name/cc" "$CFREE_BIN" cc -target "$target_triple" -c \ + "$wat" -o "$BUILD_DIR/err-$name.o" +done + +if [ "$have_wasm_tool" -eq 1 ]; then + for wat in "$META_DIR"/*.wat; do + [ -e "$wat" ] || continue + name=$(basename "$wat" .wat) + wasm="$BUILD_DIR/meta-$name.wasm" + run_expect_zero "meta/$name/W" "$WASM_TOOL" --wat2wasm "$wat" "$wasm" + if [ "$name" = "type_custom" ]; then + run_expect_zero "meta/$name/native" "$CFREE_BIN" cc \ + -target "$target_triple" -c "$wasm" -o "$BUILD_DIR/meta-$name.o" + else + run_expect_fail "meta/$name/native" "$CFREE_BIN" cc \ + -target "$target_triple" -c "$wasm" -o "$BUILD_DIR/meta-$name.o" + fi + done +else + note_skip "meta" "no wasm-tool" +fi + +bad_wasm="$BUILD_DIR/malformed-section.wasm" +printf '\000asm\001\000\000\000\001\005\001\140' > "$bad_wasm" +run_expect_fail "err/malformed-section/wasm" "$CFREE_BIN" cc \ + -target "$target_triple" -c "$bad_wasm" -o "$BUILD_DIR/bad-section.o" + +bad_leb="$BUILD_DIR/malformed-leb.wasm" +printf '\000asm\001\000\000\000\001\200\200\200\200\020' > "$bad_leb" +run_expect_fail "err/malformed-leb/wasm" "$CFREE_BIN" cc \ + -target "$target_triple" -c "$bad_leb" -o "$BUILD_DIR/bad-leb.o" + printf 'test-wasm-front: pass=%d fail=%d skip=%d\n' "$pass" "$fail" "$skip" [ "$fail" -eq 0 ]