kit

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

commit 164bf2ad58cd1d8878ae1c792f9a9b139de524de
parent a7d17edda7d748ce58062ed593e03bd03dea52ce
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 12:02:07 -0700

wasm: opcode coverage and switch/atomic limits

Several independent backend gaps closed together so the wasm/ core,
backend, and re-lower stay in lockstep:

- f32.neg/f64.neg are now real opcodes (enum, encode/decode, validator
  via wasm_fp_unop_kind, wat, classifier). The backend lowers UO_FNEG to
  the native opcode instead of the unsupported 0-x fallback, and the
  cfree-run re-lower (lang/wasm/cg.c) decodes them back to
  cfree_cg_fp_unop(FP_NEG). Unblocks 4 test/parse cases.

- WasmInsn.targets becomes a heap-grown vector (wasm_insn_set_targets);
  br_table no longer caps at 64 entries. The validator's control-frame
  stack grows on demand via wasm_ctrl_grow. The backend picks dense
  br_table vs compare-chain via switch_use_br_table, so a large dense
  switch lowers to a real br_table and the ncases >= 64 fatal is gone.
  Unblocks 6_8_26_switch_many_cases.

- AO_NAND has no native wasm-threads RMW opcode; expand to an
  atomic.rmw.cmpxchg retry loop. Unblocks builtin_25_atomic_fetch_nand.

- Factor the per-opcode decoder body switch into decode_body_insn and
  add a wasm_insn_mnemonic table covering every WasmInsnKind, so the
  WAT disassembler (next commit) can share both the opcode parsing
  and the spellings.

Diffstat:
Mlang/wasm/cg.c | 3+++
Msrc/arch/wasm/emit.c | 99++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/wasm/decode.c | 715+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/wasm/encode.c | 4++++
Msrc/wasm/insn.c | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/wasm/module.c | 22+++++++++++++++++++++-
Msrc/wasm/validate.c | 41+++++++++++++++++++++++++++++++++++------
Msrc/wasm/wasm.h | 24+++++++++++++++++++++++-
Msrc/wasm/wat.c | 60+++++++++++++++++++++++++++++++++++++++++-------------------
9 files changed, 828 insertions(+), 374 deletions(-)

diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -3622,6 +3622,9 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, if (wasm_int_cmp_op(in.kind, &cmp)) { cfree_cg_int_cmp(cg, cmp); cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I32]); + } else if (in.kind == WASM_INSN_F32_NEG || + in.kind == WASM_INSN_F64_NEG) { + cfree_cg_fp_unop(cg, CFREE_CG_FP_NEG, CFREE_CG_FP_NONE); } else if (wasm_fp_binop(in.kind, &fp_bin)) { cfree_cg_fp_binop(cg, fp_bin, CFREE_CG_FP_NONE); } else if (wasm_fp_cmp_op(in.kind, &fp_cmp)) { diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -351,7 +351,6 @@ void wasm_switch(CGTarget* tg, const CGSwitchDesc* d) { if (d->default_label == LABEL_NONE) wfail(t, "wasm: switch without default label"); if (d->ncases && !d->cases) wfail(t, "wasm: switch case count without cases"); - if (d->ncases >= 64u) wfail(t, "wasm: too many switch cases for br_table"); if (d->selector.kind != OPK_REG && d->selector.kind != OPK_IMM && d->selector.kind != OPK_LOCAL) wfail(t, "wasm: switch selector has unsupported operand kind"); @@ -1525,8 +1524,8 @@ void wasm_atomic_rmw(CGTarget* tg, AtomicOp op, Operand dst, Operand addr, atomic_require_addr_reg(t, addr, "atomic_rmw"); if (val.kind != OPK_REG && val.kind != OPK_IMM) wfail(t, "wasm: atomic_rmw value must be REG or IMM"); - if (op == AO_NAND) - wfail(t, "wasm target: atomic NAND has no native wasm-threads opcode"); + /* AO_NAND has no native wasm-threads opcode; the linearizer expands it into + * an atomic cmpxchg retry loop (see WIR_ATOMIC_RMW). */ ensure_shared_memory(t); WIR* w = wir_push(t); w->op = WIR_ATOMIC_RMW; @@ -2751,21 +2750,32 @@ static int wasm_switch_extents(WTarget* t, const WIR* w, i64* out_vmin, static void emit_br_table(WTarget* t, const u32* targets, u32 ntargets) { WasmInsn* in; - if (ntargets == 0 || ntargets > 64u) - wfail(t, "wasm: br_table target count out of range"); + if (ntargets == 0) wfail(t, "wasm: br_table needs at least the default target"); wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_BR_TABLE, 0); in = &t->cur_func->insns[t->cur_func->ninsns - 1u]; - in->ntargets = ntargets; - memcpy(in->targets, targets, sizeof(targets[0]) * ntargets); + wasm_insn_set_targets(t->c, t->module, in, targets, ntargets); +} + +/* A switch lowers to a dense br_table when its case values span a range that + * isn't pathologically sparse relative to the number of cases; otherwise an + * `eq`/`br_if` comparison chain. Small ranges always take the table (cheap + * either way); larger ranges only when at least ~half the table slots carry a + * real case, so a sparse switch (e.g. `case 0`, `case 1000000`) doesn't + * materialize a giant mostly-default table. There is no range-splitting yet, + * so a switch that fails this test is a linear scan. */ +static int switch_use_br_table(const WIR* w, u64 span) { + if (span <= 64u) return 1; + return span <= (u64)w->switch_ncases * 2u; } static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) { i64 vmin; u64 span; - u32 targets[64]; - Label labels[64]; + u32* targets; + Label* labels; u32 ntargets; WasmValType vt; + Heap* h = t->c->ctx->heap; if (w->switch_ncases == 0) { emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0])); @@ -2773,10 +2783,10 @@ static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) { } if (!wasm_switch_extents(t, w, &vmin, &span)) wfail(t, "wasm: unsupported switch selector type"); - if (span + 1u > 64u) { - vt = type_valtype(t, w->type); - if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64) - wfail(t, "wasm: switch selector must be integer"); + vt = type_valtype(t, w->type); + if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64) + wfail(t, "wasm: switch selector must be integer"); + if (!switch_use_br_table(w, span)) { for (u32 i = 0; i < w->switch_ncases; ++i) { u32 width = cfree_cg_type_int_width((CfreeCompiler*)t->c, w->type); i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); @@ -2790,6 +2800,12 @@ static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) { return; } + /* Dense table: one slot per value in [vmin, vmin+span), default-filled, with + * the default appended as the trailing out-of-range target. */ + ntargets = (u32)span + 1u; + labels = (Label*)h->alloc(h, sizeof(Label) * span, _Alignof(Label)); + targets = (u32*)h->alloc(h, sizeof(u32) * ntargets, _Alignof(u32)); + if (!labels || !targets) wfail(t, "wasm: out of memory for switch table"); for (u64 i = 0; i < span; ++i) labels[i] = w->labels[0]; for (u32 i = 0; i < w->switch_ncases; ++i) { u32 width = cfree_cg_type_int_width((CfreeCompiler*)t->c, w->type); @@ -2800,19 +2816,17 @@ static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) { } emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); - vt = type_valtype(t, w->type); - if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64) - wfail(t, "wasm: switch selector must be integer"); if (vmin != 0) { emit_push_imm(t, vt, vmin); emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB, 0); } if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); - ntargets = (u32)span + 1u; for (u32 i = 0; i < (u32)span; ++i) targets[i] = br_to_label(L, labels[i]); targets[ntargets - 1u] = br_to_label(L, w->labels[0]); emit_br_table(t, targets, ntargets); + h->free(h, targets, sizeof(u32) * ntargets); + h->free(h, labels, sizeof(Label) * span); } /* ----------------------------------------------------------------- @@ -3262,8 +3276,11 @@ static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { break; } case UO_FNEG: { - wfail(t, - "wasm: fneg via 0-x not supported; emit f32/f64.neg later"); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_insn( + t, vt == WASM_VAL_F64 ? WASM_INSN_F64_NEG : WASM_INSN_F32_NEG, + 0); + break; } case UO_BNOT: { /* a XOR -1 */ @@ -3623,6 +3640,50 @@ static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { break; } case WIR_ATOMIC_RMW: { + if ((AtomicOp)w->cgop == AO_NAND) { + /* wasm-threads has no atomic.rmw.nand. Expand to a cmpxchg retry + * loop computing desired = ~(old & val): + * loop + * old = atomic.load(addr) ; tee into old_local + * desired = (old & val) ^ -1 + * got = atomic.rmw.cmpxchg(addr, old, desired) + * br_if loop (got != old) ; lost the race, retry + * end + * dst = old_local ; fetch returns prior value + */ + WasmValType vt = type_valtype(t, w->type); + WasmInsnKind load_k = atomic_load_kind_for(t, w->type, w->mem); + WasmInsnKind cas_k = atomic_cmpxchg_kind_for(t, w->type, w->mem); + u32 load_w = wasm_mem_width((uint8_t)load_k); + u32 cas_w = wasm_mem_width((uint8_t)cas_k); + int is64 = (vt == WASM_VAL_I64); + u32 old_local = add_wasm_local(t, vt); + emit_insn(t, WASM_INSN_LOOP, 0); + /* addr (cmpxchg arg0) */ + emit_push_operand_reg(t, w->a); + /* expected = atomic.load(addr), tee into old_local */ + emit_push_operand_reg(t, w->a); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, load_k, + memarg_align_log2(w->mem.align, load_w), 0, 0); + emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)old_local); + /* desired = (old & val) ^ -1 */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + emit_insn(t, is64 ? WASM_INSN_I64_AND : WASM_INSN_I32_AND, 0); + emit_push_imm(t, vt, -1); + emit_insn(t, is64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR, 0); + /* cmpxchg -> value previously in memory */ + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, cas_k, + memarg_align_log2(w->mem.align, cas_w), 0, 0); + /* retry if memory had changed (got != expected) */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local); + emit_insn(t, is64 ? WASM_INSN_I64_NE : WASM_INSN_I32_NE, 0); + emit_insn(t, WASM_INSN_BR_IF, 0); /* 0 = innermost loop */ + emit_insn(t, WASM_INSN_END, 0); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } WasmInsnKind k = atomic_rmw_kind_for(t, (AtomicOp)w->cgop, w->type, w->mem); u32 width = wasm_mem_width((uint8_t)k); diff --git a/src/wasm/decode.c b/src/wasm/decode.c @@ -124,328 +124,15 @@ static WasmValType bin_val_type(BinReader* r, int refs_ok) { return WASM_VAL_I32; } -void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, - WasmModule* out) { - BinReader r; - uint32_t nfunc_types = 0; - uint8_t last_id = 0; - memset(&r, 0, sizeof r); - r.c = c; - r.data = input->data; - r.len = input->len; - r.module = out; - if (r.len < 8 || memcmp(r.data, "\0asm\1\0\0\0", 8) != 0) - wasm_error(c, wasm_loc(0, 0), "wasm: bad magic or version"); - r.pos = 8; - while (r.pos < r.len) { - uint8_t id = bin_u8(&r); - uint32_t size = bin_uleb(&r); - size_t end; - bin_need(&r, size); - end = r.pos + size; - if (id != 0 && id <= last_id) - wasm_error(c, wasm_loc(0, 0), "wasm: sections out of order"); - if (id != 0) last_id = id; - if (id == 0) { - uint32_t name_len = bin_uleb(&r); - bin_need(&r, name_len); - 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 (cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), - "target_features") || - cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), "target-feature")) - 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; - } - if (id == 1) { - uint32_t i, count = bin_uleb(&r); - 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); - for (j = 0; j < nparam; ++j) - wasm_type_push_param(c, out, t, bin_val_type(&r, 1)); - nresult = bin_uleb(&r); - if (nresult > 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); - t->nresults = nresult; - for (j = 0; j < nresult; ++j) t->results[j] = bin_val_type(&r, 1); - } - } else if (id == 2) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - char* mod = bin_name(&r, NULL); - char* name = bin_name(&r, 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; - wasm_func_set_params(c, out, f, out->types[typeidx].params, - out->types[typeidx].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; - WasmMemory* mem = wasm_add_memory(c, out); - mem->is_import = 1; - mem->import_module = mod; - mem->import_name = name; - flags = bin_uleb(&r); - if (flags & ~7u) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); - mem->has_max = (flags & 1u) != 0; - mem->shared = (flags & 2u) != 0; - mem->is64 = (flags & 4u) != 0; - mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - if (mem->has_max) - mem->max_pages = mem->is64 ? bin_uleb64(&r) : 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) - wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); - for (i = 0; i < count; ++i) { - uint32_t typeidx = bin_uleb(&r); - WasmFunc* f; - if (typeidx >= out->ntypes) - wasm_error(c, wasm_loc(0, 0), "wasm: bad function type index"); - f = wasm_add_func(c, out); - f->typeidx = typeidx; - f->has_typeidx = 1; - wasm_func_set_params(c, out, f, out->types[typeidx].params, - out->types[typeidx].nparams); - f->nresults = out->types[typeidx].nresults; - memcpy(f->results, out->types[typeidx].results, - sizeof(f->results[0]) * f->nresults); - nfunc_types++; - } - } else if (id == 5) { - uint32_t count = bin_uleb(&r); - for (uint32_t mi = 0; mi < count; ++mi) { - uint32_t flags = bin_uleb(&r); - WasmMemory* mem = wasm_add_memory(c, out); - if (flags & ~7u) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); - mem->has_max = (flags & 1u) != 0; - mem->shared = (flags & 2u) != 0; - mem->is64 = (flags & 4u) != 0; - mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - if (mem->has_max) - mem->max_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - } - } else if (id == 4) { - 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) { - 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) { - char* name; - uint8_t kind; - uint32_t idx; - WasmExport* ex; - name = bin_name(&r, 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) { - out->funcs[idx].export_name = - wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); - } else if (kind == 1 && idx < out->ntables) { - out->tables[idx].export_name = - wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); - } else if (kind == 2 && idx < out->nmemories) { - out->memories[idx].export_name = - wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); - } else if (kind == 3 && idx < out->nglobals) { - out->globals[idx].export_name = - wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); - } - } - } else if (id == 8) { - out->has_start = 1; - out->start_func = bin_uleb(&r); - } else if (id == 9) { - 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; - e->elem_type = WASM_VAL_FUNCREF; - if (flags == 0u) { - e->mode = WASM_SEG_ACTIVE; - 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"); - } else if (flags == 1u) { - e->mode = WASM_SEG_PASSIVE; - if (bin_u8(&r) != 0x00) - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported passive element kind"); - } else if (flags == 2u) { - e->mode = WASM_SEG_ACTIVE; - e->tableidx = bin_uleb(&r); - 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"); - if (bin_u8(&r) != 0x00) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported element kind"); - } else if (flags == 3u) { - e->mode = WASM_SEG_DECLARATIVE; - if (bin_u8(&r) != 0x00) - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported declarative element kind"); - } else { - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported element segment flags 0x%x", flags); - } - n = bin_uleb(&r); - for (uint32_t k = 0; k < n; ++k) - wasm_elem_push_func(c, out, e, 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) { - uint32_t body_size = bin_uleb(&r); - size_t body_end; - uint32_t local_groups, j; - uint32_t control_depth = 0; - 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); - for (j = 0; j < local_groups; ++j) { - uint32_t k, nlocals = bin_uleb(&r); - WasmValType vt = bin_val_type(&r, 1); - for (k = 0; k < nlocals; ++k) wasm_func_push_local(c, out, f, vt); - } - while (r.pos < body_end) { - 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; - } +/* Decode one instruction opcode body (everything except the body-terminating + * 0x0b end) into f's instruction list, advancing *rp. Shared verbatim with the + * function-body decode loop so the opcode mapping has a single source of truth; + * reused by wasm_decode_one_insn for the disassembler. */ +static void decode_body_insn(BinReader* rp, WasmModule* out, WasmFunc* f, + uint8_t op, uint32_t* pcontrol_depth) { + BinReader r = *rp; + CfreeCompiler* c = r.c; + uint32_t control_depth = *pcontrol_depth; switch (op) { case 0x00: wasm_func_add_insn(c, out, f, WASM_INSN_UNREACHABLE, 0); @@ -486,14 +173,16 @@ void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, case 0x0e: { WasmInsn* in; uint32_t n = bin_uleb(&r); - if (n >= 64u) + /* Each target is at least one byte, so a count exceeding the + * remaining input is malformed — reject before allocating. */ + if ((uint64_t)n >= r.len - r.pos) wasm_error(c, wasm_loc(0, 0), - "wasm: too many br_table targets"); + "wasm: br_table target count exceeds input"); wasm_func_add_insn(c, out, f, WASM_INSN_BR_TABLE, 0); in = &f->insns[f->ninsns - 1u]; + wasm_insn_set_targets(c, out, in, NULL, n + 1u); for (uint32_t k = 0; k < n; ++k) in->targets[k] = bin_uleb(&r); in->targets[n] = bin_uleb(&r); - in->ntargets = n + 1u; break; } case 0x0f: @@ -975,6 +664,12 @@ void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, case 0x66: wasm_func_add_insn(c, out, f, WASM_INSN_F64_GE, 0); break; + case 0x8c: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_NEG, 0); + break; + case 0x9a: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_NEG, 0); + break; case 0xa7: wasm_func_add_insn(c, out, f, WASM_INSN_I32_WRAP_I64, 0); break; @@ -1330,10 +1025,338 @@ void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, } break; } - default: - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", - op); + default: + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", + op); + } + + *pcontrol_depth = control_depth; + *rp = r; +} + +void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, + WasmModule* out) { + BinReader r; + uint32_t nfunc_types = 0; + uint8_t last_id = 0; + memset(&r, 0, sizeof r); + r.c = c; + r.data = input->data; + r.len = input->len; + r.module = out; + if (r.len < 8 || memcmp(r.data, "\0asm\1\0\0\0", 8) != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad magic or version"); + r.pos = 8; + while (r.pos < r.len) { + uint8_t id = bin_u8(&r); + uint32_t size = bin_uleb(&r); + size_t end; + bin_need(&r, size); + end = r.pos + size; + if (id != 0 && id <= last_id) + wasm_error(c, wasm_loc(0, 0), "wasm: sections out of order"); + if (id != 0) last_id = id; + if (id == 0) { + uint32_t name_len = bin_uleb(&r); + bin_need(&r, name_len); + 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 (cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), + "target_features") || + cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), "target-feature")) + 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; + } + if (id == 1) { + uint32_t i, count = bin_uleb(&r); + 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); + for (j = 0; j < nparam; ++j) + wasm_type_push_param(c, out, t, bin_val_type(&r, 1)); + nresult = bin_uleb(&r); + if (nresult > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); + t->nresults = nresult; + for (j = 0; j < nresult; ++j) t->results[j] = bin_val_type(&r, 1); + } + } else if (id == 2) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + char* mod = bin_name(&r, NULL); + char* name = bin_name(&r, 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; + wasm_func_set_params(c, out, f, out->types[typeidx].params, + out->types[typeidx].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; + WasmMemory* mem = wasm_add_memory(c, out); + mem->is_import = 1; + mem->import_module = mod; + mem->import_name = name; + flags = bin_uleb(&r); + if (flags & ~7u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); + mem->has_max = (flags & 1u) != 0; + mem->shared = (flags & 2u) != 0; + mem->is64 = (flags & 4u) != 0; + mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + if (mem->has_max) + mem->max_pages = mem->is64 ? bin_uleb64(&r) : 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) + wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); + for (i = 0; i < count; ++i) { + uint32_t typeidx = bin_uleb(&r); + WasmFunc* f; + if (typeidx >= out->ntypes) + wasm_error(c, wasm_loc(0, 0), "wasm: bad function type index"); + f = wasm_add_func(c, out); + f->typeidx = typeidx; + f->has_typeidx = 1; + wasm_func_set_params(c, out, f, out->types[typeidx].params, + out->types[typeidx].nparams); + f->nresults = out->types[typeidx].nresults; + memcpy(f->results, out->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + nfunc_types++; + } + } else if (id == 5) { + uint32_t count = bin_uleb(&r); + for (uint32_t mi = 0; mi < count; ++mi) { + uint32_t flags = bin_uleb(&r); + WasmMemory* mem = wasm_add_memory(c, out); + if (flags & ~7u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); + mem->has_max = (flags & 1u) != 0; + mem->shared = (flags & 2u) != 0; + mem->is64 = (flags & 4u) != 0; + mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + if (mem->has_max) + mem->max_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + } + } else if (id == 4) { + 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) { + 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) { + char* name; + uint8_t kind; + uint32_t idx; + WasmExport* ex; + name = bin_name(&r, 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) { + out->funcs[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 1 && idx < out->ntables) { + out->tables[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 2 && idx < out->nmemories) { + out->memories[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 3 && idx < out->nglobals) { + out->globals[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } + } + } else if (id == 8) { + out->has_start = 1; + out->start_func = bin_uleb(&r); + } else if (id == 9) { + 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; + e->elem_type = WASM_VAL_FUNCREF; + if (flags == 0u) { + e->mode = WASM_SEG_ACTIVE; + 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"); + } else if (flags == 1u) { + e->mode = WASM_SEG_PASSIVE; + if (bin_u8(&r) != 0x00) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported passive element kind"); + } else if (flags == 2u) { + e->mode = WASM_SEG_ACTIVE; + e->tableidx = bin_uleb(&r); + 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"); + if (bin_u8(&r) != 0x00) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported element kind"); + } else if (flags == 3u) { + e->mode = WASM_SEG_DECLARATIVE; + if (bin_u8(&r) != 0x00) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported declarative element kind"); + } else { + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported element segment flags 0x%x", flags); + } + n = bin_uleb(&r); + for (uint32_t k = 0; k < n; ++k) + wasm_elem_push_func(c, out, e, 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) { + uint32_t body_size = bin_uleb(&r); + size_t body_end; + uint32_t local_groups, j; + uint32_t control_depth = 0; + 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); + for (j = 0; j < local_groups; ++j) { + uint32_t k, nlocals = bin_uleb(&r); + WasmValType vt = bin_val_type(&r, 1); + for (k = 0; k < nlocals; ++k) wasm_func_push_local(c, out, f, vt); + } + while (r.pos < body_end) { + 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; } + decode_body_insn(&r, out, f, op, &control_depth); } if (!saw_body_end) wasm_error(c, wasm_loc(0, 0), "wasm: function body missing end"); @@ -1401,3 +1424,39 @@ int wasm_is_binary(const CfreeSlice* input) { return input->len >= 4u && input->data[0] == 0x00 && input->data[1] == 0x61 && input->data[2] == 0x73 && input->data[3] == 0x6d; } + +/* Decode exactly one instruction at data[pos..] into *out, returning the number + * of bytes consumed (0 if none). The control-flow opcodes block/loop/if/end are + * returned as plain instructions; callers that care about nesting track depth + * themselves. `scratch` is a caller-owned reusable module (init via + * wasm_module_init); its first function is reused as a decode buffer so this is + * allocation-free after the first call. Malformed input is fatal, matching + * wasm_decode_binary. */ +size_t wasm_decode_one_insn(CfreeCompiler* c, WasmModule* scratch, + const uint8_t* data, size_t len, size_t pos, + WasmInsn* out) { + BinReader r; + WasmFunc* f; + uint8_t op; + uint32_t control_depth = 0; + if (pos >= len) return 0; + if (scratch->nfuncs == 0) (void)wasm_add_func(c, scratch); + f = &scratch->funcs[0]; + f->ninsns = 0; + memset(&r, 0, sizeof r); + r.c = c; + r.data = data; + r.len = len; + r.pos = pos; + r.module = scratch; + op = bin_u8(&r); + if (op == 0x0bu) { + memset(out, 0, sizeof *out); + out->kind = WASM_INSN_END; + return r.pos - pos; + } + decode_body_insn(&r, scratch, f, op, &control_depth); + if (f->ninsns == 0) return 0; + *out = f->insns[f->ninsns - 1u]; + return r.pos - pos; +} diff --git a/src/wasm/encode.c b/src/wasm/encode.c @@ -483,6 +483,10 @@ static uint8_t wasm_opcode(uint8_t kind) { return 0x65; case WASM_INSN_F64_GE: return 0x66; + case WASM_INSN_F32_NEG: + return 0x8c; + case WASM_INSN_F64_NEG: + return 0x9a; case WASM_INSN_I32_WRAP_I64: return 0xa7; case WASM_INSN_I32_TRUNC_F32_S: diff --git a/src/wasm/insn.c b/src/wasm/insn.c @@ -310,6 +310,18 @@ int wasm_int_unop_kind(uint8_t kind, WasmValType* vt) { return 0; } +int wasm_fp_unop_kind(uint8_t kind, WasmValType* vt) { + if (kind == WASM_INSN_F32_NEG) { + *vt = WASM_VAL_F32; + return 1; + } + if (kind == WASM_INSN_F64_NEG) { + *vt = WASM_VAL_F64; + return 1; + } + return 0; +} + int wasm_fp_binop_kind(uint8_t kind, WasmValType* vt) { if (kind == WASM_INSN_F32_ADD || kind == WASM_INSN_F32_SUB || kind == WASM_INSN_F32_MUL || kind == WASM_INSN_F32_DIV) { @@ -419,3 +431,225 @@ int wasm_conversion_kind(uint8_t kind, WasmValType* src, WasmValType* dst) { return 0; } } + +/* WAT mnemonic for an instruction kind. Spellings match the WAT parser in + * wat.c so the disassembler and assembler agree. Returns ".unknown" for any + * out-of-range kind rather than NULL so callers can print unconditionally. */ +const char* wasm_insn_mnemonic(WasmInsnKind kind) { + static const char* const names[] = { + [WASM_INSN_UNREACHABLE] = "unreachable", + [WASM_INSN_NOP] = "nop", + [WASM_INSN_BLOCK] = "block", + [WASM_INSN_LOOP] = "loop", + [WASM_INSN_IF] = "if", + [WASM_INSN_ELSE] = "else", + [WASM_INSN_END] = "end", + [WASM_INSN_BR] = "br", + [WASM_INSN_BR_IF] = "br_if", + [WASM_INSN_BR_TABLE] = "br_table", + [WASM_INSN_SELECT] = "select", + [WASM_INSN_F32_CONST] = "f32.const", + [WASM_INSN_F64_CONST] = "f64.const", + [WASM_INSN_I32_CONST] = "i32.const", + [WASM_INSN_I64_CONST] = "i64.const", + [WASM_INSN_LOCAL_GET] = "local.get", + [WASM_INSN_LOCAL_SET] = "local.set", + [WASM_INSN_LOCAL_TEE] = "local.tee", + [WASM_INSN_CALL] = "call", + [WASM_INSN_CALL_INDIRECT] = "call_indirect", + [WASM_INSN_RETURN_CALL] = "return_call", + [WASM_INSN_RETURN_CALL_INDIRECT] = "return_call_indirect", + [WASM_INSN_REF_NULL] = "ref.null", + [WASM_INSN_REF_FUNC] = "ref.func", + [WASM_INSN_REF_IS_NULL] = "ref.is_null", + [WASM_INSN_CALL_REF] = "call_ref", + [WASM_INSN_RETURN_CALL_REF] = "return_call_ref", + [WASM_INSN_GLOBAL_GET] = "global.get", + [WASM_INSN_GLOBAL_SET] = "global.set", + [WASM_INSN_RETURN] = "return", + [WASM_INSN_DROP] = "drop", + [WASM_INSN_I32_LOAD] = "i32.load", + [WASM_INSN_I64_LOAD] = "i64.load", + [WASM_INSN_F32_LOAD] = "f32.load", + [WASM_INSN_F64_LOAD] = "f64.load", + [WASM_INSN_I32_LOAD8_S] = "i32.load8_s", + [WASM_INSN_I32_LOAD8_U] = "i32.load8_u", + [WASM_INSN_I32_LOAD16_S] = "i32.load16_s", + [WASM_INSN_I32_LOAD16_U] = "i32.load16_u", + [WASM_INSN_I64_LOAD8_S] = "i64.load8_s", + [WASM_INSN_I64_LOAD8_U] = "i64.load8_u", + [WASM_INSN_I64_LOAD16_S] = "i64.load16_s", + [WASM_INSN_I64_LOAD16_U] = "i64.load16_u", + [WASM_INSN_I64_LOAD32_S] = "i64.load32_s", + [WASM_INSN_I64_LOAD32_U] = "i64.load32_u", + [WASM_INSN_I32_STORE] = "i32.store", + [WASM_INSN_I64_STORE] = "i64.store", + [WASM_INSN_F32_STORE] = "f32.store", + [WASM_INSN_F64_STORE] = "f64.store", + [WASM_INSN_I32_STORE8] = "i32.store8", + [WASM_INSN_I32_STORE16] = "i32.store16", + [WASM_INSN_I64_STORE8] = "i64.store8", + [WASM_INSN_I64_STORE16] = "i64.store16", + [WASM_INSN_I64_STORE32] = "i64.store32", + [WASM_INSN_MEMORY_SIZE] = "memory.size", + [WASM_INSN_MEMORY_GROW] = "memory.grow", + [WASM_INSN_ATOMIC_FENCE] = "atomic.fence", + [WASM_INSN_I32_ATOMIC_LOAD] = "i32.atomic.load", + [WASM_INSN_I64_ATOMIC_LOAD] = "i64.atomic.load", + [WASM_INSN_I32_ATOMIC_LOAD8_U] = "i32.atomic.load8_u", + [WASM_INSN_I32_ATOMIC_LOAD16_U] = "i32.atomic.load16_u", + [WASM_INSN_I64_ATOMIC_LOAD8_U] = "i64.atomic.load8_u", + [WASM_INSN_I64_ATOMIC_LOAD16_U] = "i64.atomic.load16_u", + [WASM_INSN_I64_ATOMIC_LOAD32_U] = "i64.atomic.load32_u", + [WASM_INSN_I32_ATOMIC_STORE] = "i32.atomic.store", + [WASM_INSN_I64_ATOMIC_STORE] = "i64.atomic.store", + [WASM_INSN_I32_ATOMIC_STORE8] = "i32.atomic.store8", + [WASM_INSN_I32_ATOMIC_STORE16] = "i32.atomic.store16", + [WASM_INSN_I64_ATOMIC_STORE8] = "i64.atomic.store8", + [WASM_INSN_I64_ATOMIC_STORE16] = "i64.atomic.store16", + [WASM_INSN_I64_ATOMIC_STORE32] = "i64.atomic.store32", + [WASM_INSN_I32_ATOMIC_RMW_ADD] = "i32.atomic.rmw.add", + [WASM_INSN_I64_ATOMIC_RMW_ADD] = "i64.atomic.rmw.add", + [WASM_INSN_I32_ATOMIC_RMW_SUB] = "i32.atomic.rmw.sub", + [WASM_INSN_I64_ATOMIC_RMW_SUB] = "i64.atomic.rmw.sub", + [WASM_INSN_I32_ATOMIC_RMW_AND] = "i32.atomic.rmw.and", + [WASM_INSN_I64_ATOMIC_RMW_AND] = "i64.atomic.rmw.and", + [WASM_INSN_I32_ATOMIC_RMW_OR] = "i32.atomic.rmw.or", + [WASM_INSN_I64_ATOMIC_RMW_OR] = "i64.atomic.rmw.or", + [WASM_INSN_I32_ATOMIC_RMW_XOR] = "i32.atomic.rmw.xor", + [WASM_INSN_I64_ATOMIC_RMW_XOR] = "i64.atomic.rmw.xor", + [WASM_INSN_I32_ATOMIC_RMW_XCHG] = "i32.atomic.rmw.xchg", + [WASM_INSN_I64_ATOMIC_RMW_XCHG] = "i64.atomic.rmw.xchg", + [WASM_INSN_I32_ATOMIC_RMW_CMPXCHG] = "i32.atomic.rmw.cmpxchg", + [WASM_INSN_I64_ATOMIC_RMW_CMPXCHG] = "i64.atomic.rmw.cmpxchg", + [WASM_INSN_I32_ATOMIC_WAIT] = "memory.atomic.wait32", + [WASM_INSN_I64_ATOMIC_WAIT] = "memory.atomic.wait64", + [WASM_INSN_MEMORY_ATOMIC_NOTIFY] = "memory.atomic.notify", + [WASM_INSN_I32_ADD] = "i32.add", + [WASM_INSN_I32_SUB] = "i32.sub", + [WASM_INSN_I32_MUL] = "i32.mul", + [WASM_INSN_I32_DIV_S] = "i32.div_s", + [WASM_INSN_I32_DIV_U] = "i32.div_u", + [WASM_INSN_I32_REM_S] = "i32.rem_s", + [WASM_INSN_I32_REM_U] = "i32.rem_u", + [WASM_INSN_I32_AND] = "i32.and", + [WASM_INSN_I32_OR] = "i32.or", + [WASM_INSN_I32_XOR] = "i32.xor", + [WASM_INSN_I32_SHL] = "i32.shl", + [WASM_INSN_I32_SHR_S] = "i32.shr_s", + [WASM_INSN_I32_SHR_U] = "i32.shr_u", + [WASM_INSN_I32_ROTL] = "i32.rotl", + [WASM_INSN_I32_ROTR] = "i32.rotr", + [WASM_INSN_I32_CLZ] = "i32.clz", + [WASM_INSN_I32_CTZ] = "i32.ctz", + [WASM_INSN_I32_POPCNT] = "i32.popcnt", + [WASM_INSN_I32_EQZ] = "i32.eqz", + [WASM_INSN_I32_EQ] = "i32.eq", + [WASM_INSN_I32_NE] = "i32.ne", + [WASM_INSN_I32_LT_S] = "i32.lt_s", + [WASM_INSN_I32_LT_U] = "i32.lt_u", + [WASM_INSN_I32_GT_S] = "i32.gt_s", + [WASM_INSN_I32_GT_U] = "i32.gt_u", + [WASM_INSN_I32_LE_S] = "i32.le_s", + [WASM_INSN_I32_LE_U] = "i32.le_u", + [WASM_INSN_I32_GE_S] = "i32.ge_s", + [WASM_INSN_I32_GE_U] = "i32.ge_u", + [WASM_INSN_I64_ADD] = "i64.add", + [WASM_INSN_I64_SUB] = "i64.sub", + [WASM_INSN_I64_MUL] = "i64.mul", + [WASM_INSN_I64_DIV_S] = "i64.div_s", + [WASM_INSN_I64_DIV_U] = "i64.div_u", + [WASM_INSN_I64_REM_S] = "i64.rem_s", + [WASM_INSN_I64_REM_U] = "i64.rem_u", + [WASM_INSN_I64_AND] = "i64.and", + [WASM_INSN_I64_OR] = "i64.or", + [WASM_INSN_I64_XOR] = "i64.xor", + [WASM_INSN_I64_SHL] = "i64.shl", + [WASM_INSN_I64_SHR_S] = "i64.shr_s", + [WASM_INSN_I64_SHR_U] = "i64.shr_u", + [WASM_INSN_I64_ROTL] = "i64.rotl", + [WASM_INSN_I64_ROTR] = "i64.rotr", + [WASM_INSN_I64_CLZ] = "i64.clz", + [WASM_INSN_I64_CTZ] = "i64.ctz", + [WASM_INSN_I64_POPCNT] = "i64.popcnt", + [WASM_INSN_I64_EQZ] = "i64.eqz", + [WASM_INSN_I64_EQ] = "i64.eq", + [WASM_INSN_I64_NE] = "i64.ne", + [WASM_INSN_I64_LT_S] = "i64.lt_s", + [WASM_INSN_I64_LT_U] = "i64.lt_u", + [WASM_INSN_I64_GT_S] = "i64.gt_s", + [WASM_INSN_I64_GT_U] = "i64.gt_u", + [WASM_INSN_I64_LE_S] = "i64.le_s", + [WASM_INSN_I64_LE_U] = "i64.le_u", + [WASM_INSN_I64_GE_S] = "i64.ge_s", + [WASM_INSN_I64_GE_U] = "i64.ge_u", + [WASM_INSN_F32_ADD] = "f32.add", + [WASM_INSN_F32_SUB] = "f32.sub", + [WASM_INSN_F32_MUL] = "f32.mul", + [WASM_INSN_F32_DIV] = "f32.div", + [WASM_INSN_F32_EQ] = "f32.eq", + [WASM_INSN_F32_NE] = "f32.ne", + [WASM_INSN_F32_LT] = "f32.lt", + [WASM_INSN_F32_GT] = "f32.gt", + [WASM_INSN_F32_LE] = "f32.le", + [WASM_INSN_F32_GE] = "f32.ge", + [WASM_INSN_F64_ADD] = "f64.add", + [WASM_INSN_F64_SUB] = "f64.sub", + [WASM_INSN_F64_MUL] = "f64.mul", + [WASM_INSN_F64_DIV] = "f64.div", + [WASM_INSN_F64_EQ] = "f64.eq", + [WASM_INSN_F64_NE] = "f64.ne", + [WASM_INSN_F64_LT] = "f64.lt", + [WASM_INSN_F64_GT] = "f64.gt", + [WASM_INSN_F64_LE] = "f64.le", + [WASM_INSN_F64_GE] = "f64.ge", + [WASM_INSN_F32_NEG] = "f32.neg", + [WASM_INSN_F64_NEG] = "f64.neg", + [WASM_INSN_I32_WRAP_I64] = "i32.wrap_i64", + [WASM_INSN_I32_TRUNC_F32_S] = "i32.trunc_f32_s", + [WASM_INSN_I32_TRUNC_F32_U] = "i32.trunc_f32_u", + [WASM_INSN_I32_TRUNC_F64_S] = "i32.trunc_f64_s", + [WASM_INSN_I32_TRUNC_F64_U] = "i32.trunc_f64_u", + [WASM_INSN_I64_EXTEND_I32_S] = "i64.extend_i32_s", + [WASM_INSN_I64_EXTEND_I32_U] = "i64.extend_i32_u", + [WASM_INSN_I64_TRUNC_F32_S] = "i64.trunc_f32_s", + [WASM_INSN_I64_TRUNC_F32_U] = "i64.trunc_f32_u", + [WASM_INSN_I64_TRUNC_F64_S] = "i64.trunc_f64_s", + [WASM_INSN_I64_TRUNC_F64_U] = "i64.trunc_f64_u", + [WASM_INSN_F32_CONVERT_I32_S] = "f32.convert_i32_s", + [WASM_INSN_F32_CONVERT_I32_U] = "f32.convert_i32_u", + [WASM_INSN_F32_CONVERT_I64_S] = "f32.convert_i64_s", + [WASM_INSN_F32_CONVERT_I64_U] = "f32.convert_i64_u", + [WASM_INSN_F32_DEMOTE_F64] = "f32.demote_f64", + [WASM_INSN_F64_CONVERT_I32_S] = "f64.convert_i32_s", + [WASM_INSN_F64_CONVERT_I32_U] = "f64.convert_i32_u", + [WASM_INSN_F64_CONVERT_I64_S] = "f64.convert_i64_s", + [WASM_INSN_F64_CONVERT_I64_U] = "f64.convert_i64_u", + [WASM_INSN_F64_PROMOTE_F32] = "f64.promote_f32", + [WASM_INSN_I32_REINTERPRET_F32] = "i32.reinterpret_f32", + [WASM_INSN_I64_REINTERPRET_F64] = "i64.reinterpret_f64", + [WASM_INSN_F32_REINTERPRET_I32] = "f32.reinterpret_i32", + [WASM_INSN_F64_REINTERPRET_I64] = "f64.reinterpret_i64", + [WASM_INSN_I32_TRUNC_SAT_F32_S] = "i32.trunc_sat_f32_s", + [WASM_INSN_I32_TRUNC_SAT_F32_U] = "i32.trunc_sat_f32_u", + [WASM_INSN_I32_TRUNC_SAT_F64_S] = "i32.trunc_sat_f64_s", + [WASM_INSN_I32_TRUNC_SAT_F64_U] = "i32.trunc_sat_f64_u", + [WASM_INSN_I64_TRUNC_SAT_F32_S] = "i64.trunc_sat_f32_s", + [WASM_INSN_I64_TRUNC_SAT_F32_U] = "i64.trunc_sat_f32_u", + [WASM_INSN_I64_TRUNC_SAT_F64_S] = "i64.trunc_sat_f64_s", + [WASM_INSN_I64_TRUNC_SAT_F64_U] = "i64.trunc_sat_f64_u", + [WASM_INSN_MEMORY_INIT] = "memory.init", + [WASM_INSN_DATA_DROP] = "data.drop", + [WASM_INSN_MEMORY_COPY] = "memory.copy", + [WASM_INSN_MEMORY_FILL] = "memory.fill", + [WASM_INSN_TABLE_INIT] = "table.init", + [WASM_INSN_ELEM_DROP] = "elem.drop", + [WASM_INSN_TABLE_COPY] = "table.copy", + [WASM_INSN_TABLE_GROW] = "table.grow", + [WASM_INSN_TABLE_SIZE] = "table.size", + [WASM_INSN_TABLE_FILL] = "table.fill", + }; + if ((unsigned)kind >= sizeof(names) / sizeof(names[0]) || !names[kind]) + return ".unknown"; + return names[kind]; +} diff --git a/src/wasm/module.c b/src/wasm/module.c @@ -71,8 +71,13 @@ void wasm_module_free(WasmModule* m) { m->heap->free(m->heap, f->params, sizeof(*f->params) * f->cap_params); if (f->locals) m->heap->free(m->heap, f->locals, sizeof(*f->locals) * f->cap_locals); - if (f->insns) + if (f->insns) { + for (uint32_t j = 0; j < f->ninsns; ++j) + if (f->insns[j].targets) + m->heap->free(m->heap, f->insns[j].targets, + sizeof(uint32_t) * f->insns[j].ntargets); 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); @@ -425,6 +430,7 @@ void wasm_func_add_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, f->insns[f->ninsns].offset64 = imm < 0 ? 0 : (uint64_t)imm; f->insns[f->ninsns].aux_idx = 0; f->insns[f->ninsns].ntargets = 0; + f->insns[f->ninsns].targets = NULL; f->ninsns++; } @@ -443,3 +449,17 @@ void wasm_func_add_fp_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, wasm_func_add_insn(c, m, f, kind, 0); f->insns[f->ninsns - 1u].fp = value; } + +void wasm_insn_set_targets(CfreeCompiler* c, WasmModule* m, WasmInsn* in, + const uint32_t* targets, uint32_t ntargets) { + if (in->targets) + m->heap->free(m->heap, in->targets, sizeof(uint32_t) * in->ntargets); + in->targets = NULL; + in->ntargets = ntargets; + if (!ntargets) return; + in->targets = (uint32_t*)m->heap->alloc(m->heap, sizeof(uint32_t) * ntargets, + _Alignof(uint32_t)); + if (!in->targets) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory for br_table targets"); + if (targets) memcpy(in->targets, targets, sizeof(uint32_t) * ntargets); +} diff --git a/src/wasm/validate.c b/src/wasm/validate.c @@ -78,6 +78,20 @@ static void wasm_mark_unreachable(WasmValStack* s, WasmControlFrame* frames, top->unreachable = 1; } +/* Double the control-frame stack. Caller passes the current capacity by + * reference; it is updated to the new capacity. */ +static WasmControlFrame* wasm_ctrl_grow(CfreeCompiler* c, WasmModule* m, + WasmControlFrame* frames, uint32_t* cap, + CfreeSrcLoc loc) { + uint32_t nc = *cap * 2u; + WasmControlFrame* p = (WasmControlFrame*)wasm_realloc( + m->heap, frames, sizeof(WasmControlFrame) * *cap, + sizeof(WasmControlFrame) * nc); + if (!p) wasm_error(c, loc, "wasm: out of memory"); + *cap = nc; + return p; +} + void wasm_validate(WasmModule* m, CfreeCompiler* c) { uint32_t i, j; for (i = 0; i < m->ntypes; ++i) { @@ -142,16 +156,25 @@ void wasm_validate(WasmModule* m, CfreeCompiler* c) { void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f) { WasmValStack stack; - WasmControlFrame control[65]; + /* The control stack grows with structured-block nesting. Deeply nested + * shapes — e.g. a switch with hundreds of cases lowered to a tower of + * blocks — can run far past any fixed cap, so grow it on demand. On the + * error path wasm_error aborts the whole compile, so the leak is moot + * (matching the wasm frontend's growable control stack in lang/wasm/cg.c). */ + uint32_t control_cap = 64u; + WasmControlFrame* control = (WasmControlFrame*)m->heap->alloc( + m->heap, sizeof(WasmControlFrame) * control_cap, _Alignof(WasmControlFrame)); uint32_t ncontrol = 1; uint32_t j; + if (!control) wasm_error(c, f->loc, "wasm: out of memory"); memset(&stack, 0, sizeof stack); - memset(control, 0, sizeof control); + memset(control, 0, sizeof(WasmControlFrame) * control_cap); 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"); + m->heap->free(m->heap, control, sizeof(WasmControlFrame) * control_cap); return; } if (f->nresults > 1u) @@ -343,8 +366,8 @@ void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f) { break; case WASM_INSN_BLOCK: case WASM_INSN_LOOP: - if (ncontrol >= 65u) - wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + if (ncontrol == control_cap) + control = wasm_ctrl_grow(c, m, control, &control_cap, in->loc); control[ncontrol].kind = in->kind; control[ncontrol].height = stack.depth; control[ncontrol].seen_else = 0; @@ -353,8 +376,8 @@ void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f) { break; case WASM_INSN_IF: 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"); + if (ncontrol == control_cap) + control = wasm_ctrl_grow(c, m, control, &control_cap, in->loc); control[ncontrol].kind = in->kind; control[ncontrol].height = stack.depth; control[ncontrol].seen_else = 0; @@ -769,6 +792,11 @@ void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f) { wasm_stack_push(c, &stack, vt); break; } + if (wasm_fp_unop_kind(in->kind, &vt)) { + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp unary operand"); + wasm_stack_push(c, &stack, vt); + break; + } if (wasm_fp_binop_kind(in->kind, &vt)) { wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); @@ -821,4 +849,5 @@ void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f) { } } } + m->heap->free(m->heap, control, sizeof(WasmControlFrame) * control_cap); } diff --git a/src/wasm/wasm.h b/src/wasm/wasm.h @@ -209,6 +209,8 @@ typedef enum WasmInsnKind { WASM_INSN_F64_GT, WASM_INSN_F64_LE, WASM_INSN_F64_GE, + WASM_INSN_F32_NEG, + WASM_INSN_F64_NEG, WASM_INSN_I32_WRAP_I64, WASM_INSN_I32_TRUNC_F32_S, WASM_INSN_I32_TRUNC_F32_U, @@ -281,8 +283,13 @@ typedef struct WasmInsn { /* Secondary index slot for opcodes with two index immediates: memory.copy * (src memidx), table.init (tableidx), table.copy (src tableidx). */ uint32_t aux_idx; + /* br_table branch depths: targets[0..ntargets-1], the last being the + * default. Heap-owned (m->heap), sized exactly ntargets, freed with the + * function. A pointer rather than an inline array so a switch's jump table + * can hold an arbitrary number of cases without bloating every other + * instruction. Set via wasm_insn_set_targets. */ uint32_t ntargets; - uint32_t targets[64]; + uint32_t* targets; } WasmInsn; typedef struct WasmFunc { @@ -493,6 +500,11 @@ void wasm_func_add_mem_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, uint32_t memidx); void wasm_func_add_fp_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, WasmInsnKind kind, double value); +/* Replace `in`'s br_table target vector with a heap-owned copy of + * `targets[0..ntargets)` (allocated from m->heap, freed with the function). + * Any previous vector is released first. */ +void wasm_insn_set_targets(CfreeCompiler* c, WasmModule* m, WasmInsn* in, + const uint32_t* targets, uint32_t ntargets); int wasm_is_num_type(WasmValType vt); int wasm_is_ref_type(WasmValType vt); @@ -510,6 +522,8 @@ int wasm_insn_is_atomic_cmpxchg(WasmInsnKind kind); int wasm_insn_is_atomic_wait_notify(WasmInsnKind kind); int wasm_insn_is_atomic_mem(WasmInsnKind kind); int wasm_insn_is_mem(WasmInsnKind kind); +/* WAT mnemonic for an instruction kind (e.g. "i32.add"); never NULL. */ +const char* wasm_insn_mnemonic(WasmInsnKind kind); WasmValType wasm_func_local_type(const WasmFunc* f, uint32_t index); uint32_t wasm_mem_width(uint8_t kind); int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp* out); @@ -518,6 +532,7 @@ WasmValType wasm_store_value_type(uint8_t kind); WasmValType wasm_atomic_value_type(uint8_t kind); CfreeCgAtomicOp wasm_atomic_rmw_op(uint8_t kind); int wasm_int_unop_kind(uint8_t kind, WasmValType* vt); +int wasm_fp_unop_kind(uint8_t kind, WasmValType* vt); int wasm_fp_binop_kind(uint8_t kind, WasmValType* vt); int wasm_fp_cmp_kind(uint8_t kind, WasmValType* vt); int wasm_conversion_kind(uint8_t kind, WasmValType* src, WasmValType* dst); @@ -532,6 +547,13 @@ void wasm_parse_wat_body(CfreeCompiler* c, WasmModule* m, WasmFunc* f, const char* src, size_t len, CfreeSrcLoc loc); void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, WasmModule* out); +/* Decode one instruction from data[pos..] into *out; returns bytes consumed (0 + * if none). `scratch` is a caller-owned reusable module (wasm_module_init) used + * as an allocation-free decode buffer. Shares the opcode mapping with + * wasm_decode_binary. Used by the disassembler. */ +size_t wasm_decode_one_insn(CfreeCompiler* c, WasmModule* scratch, + const uint8_t* data, size_t len, size_t pos, + WasmInsn* out); int wasm_is_binary(const CfreeSlice* input); void wasm_validate(WasmModule* m, CfreeCompiler* c); /* Validate a single function under the typed operand/control stack rules. diff --git a/src/wasm/wat.c b/src/wasm/wat.c @@ -1005,6 +1005,14 @@ static int wat_instr_kind(WasmTok t, WasmInsnKind* out, int* has_imm) { *out = WASM_INSN_I64_GE_U; return 1; } + if (tok_is(t, "f32.neg")) { + *out = WASM_INSN_F32_NEG; + return 1; + } + if (tok_is(t, "f64.neg")) { + *out = WASM_INSN_F64_NEG; + return 1; + } if (tok_is(t, "f32.add")) { *out = WASM_INSN_F32_ADD; return 1; @@ -1717,24 +1725,30 @@ static void wat_parse_instr_list(WatParser* p, WasmFunc* f) { return; } if (kind == WASM_INSN_BR_TABLE) { - WasmInsn* in; - uint32_t n = 0; + CfreeHeap* heap = p->module->heap; + uint32_t n = 0, cap = 8u; + uint32_t* tmp = + (uint32_t*)heap->alloc(heap, sizeof(uint32_t) * cap, _Alignof(uint32_t)); + if (!tmp) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: oom"); while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { int64_t target; - if (n >= 64u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many br_table targets"); if (!wat_parse_i64(p, &target) || target < 0 || target > UINT32_MAX) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: bad br_table target"); - wasm_func_add_insn(p->c, p->module, f, - n ? WASM_INSN_NOP : WASM_INSN_BR_TABLE, 0); - in = &f->insns[f->ninsns - 1u - n]; - in->targets[n++] = (uint32_t)target; + if (n == cap) { + uint32_t nc = cap * 2u; + tmp = (uint32_t*)heap->realloc(heap, tmp, sizeof(uint32_t) * cap, + sizeof(uint32_t) * nc, _Alignof(uint32_t)); + if (!tmp) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: oom"); + cap = nc; + } + tmp[n++] = (uint32_t)target; wat_next(p); } - f->ninsns -= n - 1u; - f->insns[f->ninsns - 1u].ntargets = n; + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_BR_TABLE, 0); + wasm_insn_set_targets(p->c, p->module, &f->insns[f->ninsns - 1u], tmp, n); + heap->free(heap, tmp, sizeof(uint32_t) * cap); wat_expect(p, WT_RPAREN, "')'"); return; } @@ -1857,25 +1871,33 @@ static void wat_parse_instr(WatParser* p, WasmFunc* f) { return; } if (kind == WASM_INSN_BR_TABLE) { - WasmInsn* in; - uint32_t n = 0; + CfreeHeap* heap = p->module->heap; + uint32_t n = 0, cap = 8u; + uint32_t* tmp = + (uint32_t*)heap->alloc(heap, sizeof(uint32_t) * cap, _Alignof(uint32_t)); + if (!tmp) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: oom"); wasm_func_add_insn(p->c, p->module, f, WASM_INSN_BR_TABLE, 0); - in = &f->insns[f->ninsns - 1u]; while (p->tok.kind == WT_ATOM) { WasmInsnKind next_kind; int next_has_imm; int64_t target; if (wat_instr_kind(p->tok, &next_kind, &next_has_imm)) break; - if (n >= 64u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many br_table targets"); if (!wat_parse_i64(p, &target) || target < 0 || target > UINT32_MAX) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: bad br_table target"); - in->targets[n++] = (uint32_t)target; + if (n == cap) { + uint32_t nc = cap * 2u; + tmp = (uint32_t*)heap->realloc(heap, tmp, sizeof(uint32_t) * cap, + sizeof(uint32_t) * nc, _Alignof(uint32_t)); + if (!tmp) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: oom"); + cap = nc; + } + tmp[n++] = (uint32_t)target; wat_next(p); } - in->ntargets = n; + wasm_insn_set_targets(p->c, p->module, &f->insns[f->ninsns - 1u], tmp, n); + heap->free(heap, tmp, sizeof(uint32_t) * cap); return; } if (kind == WASM_INSN_CALL_INDIRECT ||