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:
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 ||