kit

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

commit 528833572f1915d74cc42c5bc2ffdf5e1a54f97b
parent 5630fc443be3e72d1562851d2a98e71b1e2e9447
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 17 May 2026 21:19:12 -0700

Complete wasm frontend native lowering

Diffstat:
Mdoc/WASM.md | 47++++++++++++++++++++++++++++-------------------
Mlang/wasm/wasm.c | 2574++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Atest/wasm/cases/br_table.expect | 1+
Atest/wasm/cases/br_table.wat | 12++++++++++++
Atest/wasm/cases/control_loop.expect | 1+
Atest/wasm/cases/control_loop.wat | 15+++++++++++++++
Atest/wasm/cases/float_ops.expect | 1+
Atest/wasm/cases/float_ops.wat | 6++++++
Atest/wasm/cases/if_return.expect | 1+
Atest/wasm/cases/if_return.wat | 6++++++
Atest/wasm/cases/int_conversions.expect | 1+
Atest/wasm/cases/int_conversions.wat | 10++++++++++
Atest/wasm/cases/int_extra_ops.expect | 1+
Atest/wasm/cases/int_extra_ops.wat | 16++++++++++++++++
Atest/wasm/cases/memory_data.expect | 1+
Atest/wasm/cases/memory_data.wat | 5+++++
Atest/wasm/cases/memory_store.expect | 1+
Atest/wasm/cases/memory_store.wat | 5+++++
Atest/wasm/cases/plain_control.expect | 1+
Atest/wasm/cases/plain_control.wat | 17+++++++++++++++++
Atest/wasm/cases/select.expect | 1+
Atest/wasm/cases/select.wat | 6++++++
Atest/wasm/cases/top_export_comments.expect | 1+
Atest/wasm/cases/top_export_comments.wat | 5+++++
24 files changed, 2634 insertions(+), 101 deletions(-)

diff --git a/doc/WASM.md b/doc/WASM.md @@ -773,14 +773,15 @@ targeted test target. - [x] Parse modules, functions, exports, params, results, locals, folded expressions, numeric indices, and `$name` function/local references. - [x] Parse line comments. -- [ ] Parse block comments. -- [ ] Parse standard WAT string escapes and byte escapes. -- [ ] Parse integer literals with signs, underscores, hex notation, and +- [x] Parse block comments. +- [x] Parse standard WAT string escapes and byte escapes. +- [x] Parse integer literals with signs, underscores, hex notation, and boundary diagnostics. - [ ] Parse float literals for `f32.const` and `f64.const`. - [ ] Parse module-level type definitions and `(func (type N) ...)`. -- [ ] Parse imports, memories, tables, globals, elements, data segments, start, - and custom/name sections. +- [x] Parse memories and active data segments for the frontend subset. +- [ ] Parse imports, tables, globals, elements, start, and custom/name + sections. - [ ] Preserve source locations through validation and lowering diagnostics. ### Binary Reader and Encoder @@ -791,8 +792,10 @@ targeted test target. - [ ] Move shared binary mechanics into `src/wasm` with decode/encode contexts. - [ ] Validate section length, ordering, count, and index-space edge cases with direct format tests. -- [ ] Decode and encode imports, memories, tables, globals, elements, data, - start, custom/name sections, and target-feature metadata. +- [x] Decode and encode memories and active data segments for the frontend + subset. +- [ ] Decode and encode imports, tables, globals, elements, start, + custom/name sections, and target-feature metadata. - [ ] Preserve unknown custom sections when the caller requests preservation. - [ ] Add deterministic fixtures for malformed LEB128 and truncated bodies. @@ -800,11 +803,13 @@ targeted test target. - [x] Validate basic stack depth, direct call indices, local indices, and integer-only locals/params for the current subset. -- [ ] Replace depth-only validation with typed operand and control stacks. +- [x] Replace depth-only validation with typed operand and control stacks for + the accepted frontend subset. - [ ] Validate exact function result stack shape, unreachable polymorphism, and fallthrough after `return`/`unreachable`. -- [ ] Validate blocks, loops, if/else, branch depths, branch result arity, and - `br_table`. +- [x] Validate blocks, loops, if/else, and branch depths for no-result + control blocks. +- [ ] Validate branch result arity and `br_table`. - [ ] Validate memory/table/global/data/element indices, limits, active/passive segment rules, and start function signature. - [ ] Centralize feature gating in a `WasmFeatureSet`. @@ -817,18 +822,22 @@ targeted test target. `CfreeCg`. - [x] Lower i32/i64 constants, local get/set/tee, direct calls, returns, drops, integer arithmetic, shifts, bitwise ops, and integer comparisons. -- [ ] Lower `unreachable` and deterministic traps. -- [ ] Lower `select`. +- [x] Lower `unreachable` and deterministic traps for memory bounds checks. +- [x] Lower deterministic traps for integer divide/remainder edge cases. +- [x] Lower `select`. - [ ] Lower `local.set`/`local.tee` for address-taken or spilled locals without depending on backend-local optimization behavior. -- [ ] Lower structured control flow: `block`, `loop`, `if`, `else`, `br`, - `br_if`, `br_table`, and `return`. -- [ ] Lower full i32/i64 integer ops, including count/rotate/popcount/clz/ctz - and all conversions. -- [ ] Lower f32/f64 arithmetic, comparisons, constants, conversions, and +- [x] Lower structured control flow: `block`, `loop`, `if`, `else`, `br`, + `br_if`, and `return`. +- [x] Lower `br_table`. +- [x] Lower full i32/i64 integer ops, including count/rotate/popcount/clz/ctz + and MVP conversions. +- [x] Lower f32/f64 arithmetic, comparisons, constants, conversions, and reinterpret ops. -- [ ] Implement memory state in an explicit instance context, with checked - loads/stores, `memory.size`, `memory.grow`, and active data initialization. +- [x] Lower single linear memory load/store operations, `memory.size`, + deterministic `memory.grow` failure, and active data initialization. +- [ ] Implement memory state in an explicit instance context with checked + loads/stores and growable storage. - [ ] Implement globals through the instance context. - [ ] Implement imports through explicit import slots in the instance context. - [ ] Implement tables and `call_indirect`. diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c @@ -5,6 +5,7 @@ #include <stdarg.h> #include <stddef.h> #include <stdint.h> +#include <stdlib.h> #include <string.h> typedef enum WasmValType { @@ -15,6 +16,19 @@ typedef enum WasmValType { } WasmValType; typedef enum WasmInsnKind { + WASM_INSN_UNREACHABLE, + WASM_INSN_NOP, + WASM_INSN_BLOCK, + WASM_INSN_LOOP, + WASM_INSN_IF, + WASM_INSN_ELSE, + WASM_INSN_END, + WASM_INSN_BR, + WASM_INSN_BR_IF, + WASM_INSN_BR_TABLE, + WASM_INSN_SELECT, + WASM_INSN_F32_CONST, + WASM_INSN_F64_CONST, WASM_INSN_I32_CONST, WASM_INSN_I64_CONST, WASM_INSN_LOCAL_GET, @@ -23,6 +37,27 @@ typedef enum WasmInsnKind { WASM_INSN_CALL, WASM_INSN_RETURN, WASM_INSN_DROP, + WASM_INSN_I32_LOAD, + WASM_INSN_I64_LOAD, + WASM_INSN_I32_LOAD8_S, + WASM_INSN_I32_LOAD8_U, + WASM_INSN_I32_LOAD16_S, + WASM_INSN_I32_LOAD16_U, + WASM_INSN_I64_LOAD8_S, + WASM_INSN_I64_LOAD8_U, + WASM_INSN_I64_LOAD16_S, + WASM_INSN_I64_LOAD16_U, + WASM_INSN_I64_LOAD32_S, + WASM_INSN_I64_LOAD32_U, + WASM_INSN_I32_STORE, + WASM_INSN_I64_STORE, + WASM_INSN_I32_STORE8, + WASM_INSN_I32_STORE16, + WASM_INSN_I64_STORE8, + WASM_INSN_I64_STORE16, + WASM_INSN_I64_STORE32, + WASM_INSN_MEMORY_SIZE, + WASM_INSN_MEMORY_GROW, WASM_INSN_I32_ADD, WASM_INSN_I32_SUB, WASM_INSN_I32_MUL, @@ -36,6 +71,11 @@ typedef enum WasmInsnKind { WASM_INSN_I32_SHL, WASM_INSN_I32_SHR_S, WASM_INSN_I32_SHR_U, + WASM_INSN_I32_ROTL, + WASM_INSN_I32_ROTR, + WASM_INSN_I32_CLZ, + WASM_INSN_I32_CTZ, + WASM_INSN_I32_POPCNT, WASM_INSN_I32_EQZ, WASM_INSN_I32_EQ, WASM_INSN_I32_NE, @@ -50,12 +90,21 @@ typedef enum WasmInsnKind { WASM_INSN_I64_ADD, WASM_INSN_I64_SUB, WASM_INSN_I64_MUL, + WASM_INSN_I64_DIV_S, + WASM_INSN_I64_DIV_U, + WASM_INSN_I64_REM_S, + WASM_INSN_I64_REM_U, WASM_INSN_I64_AND, WASM_INSN_I64_OR, WASM_INSN_I64_XOR, WASM_INSN_I64_SHL, WASM_INSN_I64_SHR_S, WASM_INSN_I64_SHR_U, + WASM_INSN_I64_ROTL, + WASM_INSN_I64_ROTR, + WASM_INSN_I64_CLZ, + WASM_INSN_I64_CTZ, + WASM_INSN_I64_POPCNT, WASM_INSN_I64_EQZ, WASM_INSN_I64_EQ, WASM_INSN_I64_NE, @@ -67,11 +116,61 @@ typedef enum WasmInsnKind { WASM_INSN_I64_LE_U, WASM_INSN_I64_GE_S, WASM_INSN_I64_GE_U, + WASM_INSN_F32_ADD, + WASM_INSN_F32_SUB, + WASM_INSN_F32_MUL, + WASM_INSN_F32_DIV, + WASM_INSN_F32_EQ, + WASM_INSN_F32_NE, + WASM_INSN_F32_LT, + WASM_INSN_F32_GT, + WASM_INSN_F32_LE, + WASM_INSN_F32_GE, + WASM_INSN_F64_ADD, + WASM_INSN_F64_SUB, + WASM_INSN_F64_MUL, + WASM_INSN_F64_DIV, + WASM_INSN_F64_EQ, + WASM_INSN_F64_NE, + WASM_INSN_F64_LT, + WASM_INSN_F64_GT, + WASM_INSN_F64_LE, + WASM_INSN_F64_GE, + WASM_INSN_I32_WRAP_I64, + WASM_INSN_I32_TRUNC_F32_S, + WASM_INSN_I32_TRUNC_F32_U, + WASM_INSN_I32_TRUNC_F64_S, + WASM_INSN_I32_TRUNC_F64_U, + WASM_INSN_I64_EXTEND_I32_S, + WASM_INSN_I64_EXTEND_I32_U, + WASM_INSN_I64_TRUNC_F32_S, + WASM_INSN_I64_TRUNC_F32_U, + WASM_INSN_I64_TRUNC_F64_S, + WASM_INSN_I64_TRUNC_F64_U, + WASM_INSN_F32_CONVERT_I32_S, + WASM_INSN_F32_CONVERT_I32_U, + WASM_INSN_F32_CONVERT_I64_S, + WASM_INSN_F32_CONVERT_I64_U, + WASM_INSN_F32_DEMOTE_F64, + WASM_INSN_F64_CONVERT_I32_S, + WASM_INSN_F64_CONVERT_I32_U, + WASM_INSN_F64_CONVERT_I64_S, + WASM_INSN_F64_CONVERT_I64_U, + WASM_INSN_F64_PROMOTE_F32, + WASM_INSN_I32_REINTERPRET_F32, + WASM_INSN_I64_REINTERPRET_F64, + WASM_INSN_F32_REINTERPRET_I32, + WASM_INSN_F64_REINTERPRET_I64, } WasmInsnKind; typedef struct WasmInsn { uint8_t kind; + uint8_t type; int64_t imm; + double fp; + uint32_t align; + uint32_t ntargets; + uint32_t targets[16]; } WasmInsn; typedef struct WasmFunc { @@ -89,11 +188,21 @@ typedef struct WasmFunc { uint32_t cap_insns; } WasmFunc; +typedef struct WasmMemory { + uint32_t min_pages; + uint32_t max_pages; + int has_max; + uint8_t *data; + uint32_t data_len; +} WasmMemory; + typedef struct WasmModule { CfreeHeap *heap; WasmFunc *funcs; uint32_t nfuncs; uint32_t cap_funcs; + WasmMemory memory; + int has_memory; } WasmModule; typedef struct WasmTok { @@ -186,9 +295,33 @@ static void wasm_module_free(WasmModule *m) { } if (m->funcs) m->heap->free(m->heap, m->funcs, sizeof(*m->funcs) * m->cap_funcs); + if (m->memory.data) + m->heap->free(m->heap, m->memory.data, m->memory.data_len); memset(m, 0, sizeof *m); } +static void wasm_memory_ensure(CfreeCompiler *c, WasmModule *m, + uint32_t min_len) { + uint32_t old_len, new_len; + void *p; + if (!m->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: data segment without memory"); + old_len = m->memory.data_len; + new_len = old_len; + if (new_len < min_len) + new_len = min_len; + if (m->memory.min_pages && new_len < m->memory.min_pages * 65536u) + new_len = m->memory.min_pages * 65536u; + if (new_len == old_len) + return; + p = wasm_realloc(m->heap, m->memory.data, old_len, new_len); + if (!p) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->memory.data = (uint8_t *)p; + memset(m->memory.data + old_len, 0, new_len - old_len); + m->memory.data_len = new_len; +} + static WasmFunc *wasm_add_func(CfreeCompiler *c, WasmModule *m) { WasmFunc *f; if (m->nfuncs == m->cap_funcs) { @@ -219,10 +352,28 @@ static void wasm_func_add_insn(CfreeCompiler *c, WasmModule *m, WasmFunc *f, f->cap_insns = new_cap; } f->insns[f->ninsns].kind = (uint8_t)kind; + f->insns[f->ninsns].type = 0; f->insns[f->ninsns].imm = imm; + f->insns[f->ninsns].fp = 0.0; + f->insns[f->ninsns].align = 0; + f->insns[f->ninsns].ntargets = 0; f->ninsns++; } +static void wasm_func_add_mem_insn(CfreeCompiler *c, WasmModule *m, + WasmFunc *f, WasmInsnKind kind, + uint32_t align, uint32_t offset) { + wasm_func_add_insn(c, m, f, kind, (int64_t)offset); + f->insns[f->ninsns - 1u].align = align; +} + +static void wasm_func_add_fp_insn(CfreeCompiler *c, WasmModule *m, + WasmFunc *f, WasmInsnKind kind, + double value) { + wasm_func_add_insn(c, m, f, kind, 0); + f->insns[f->ninsns - 1u].fp = value; +} + static int tok_is(WasmTok t, const char *s) { size_t n = strlen(s); return t.kind == WT_ATOM && t.len == n && memcmp(t.p, s, n) == 0; @@ -236,6 +387,67 @@ static int wasm_name_eq(const char *name, WasmTok t) { return t.len == n && memcmp(name, t.p, n) == 0; } +static int wat_hex(char ch) { + if (ch >= '0' && ch <= '9') + return ch - '0'; + if (ch >= 'a' && ch <= 'f') + return ch - 'a' + 10; + if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + return -1; +} + +static char *wat_dup_string(WatParser *p, WasmTok t, size_t *len_out) { + char *out; + size_t i, n = 0; + if (t.kind != WT_STRING) + wasm_error(p->c, wasm_loc(t.line, t.col), "wasm wat: expected string"); + out = (char *)p->module->heap->alloc(p->module->heap, t.len + 1u, 1); + if (!out) + wasm_error(p->c, wasm_loc(t.line, t.col), "wasm: out of memory"); + for (i = 0; i < t.len; ++i) { + char ch = t.p[i]; + if (ch != '\\') { + out[n++] = ch; + continue; + } + if (++i >= t.len) + wasm_error(p->c, wasm_loc(t.line, t.col), + "wasm wat: unterminated string escape"); + ch = t.p[i]; + switch (ch) { + case 'n': + out[n++] = '\n'; + break; + case 'r': + out[n++] = '\r'; + break; + case 't': + out[n++] = '\t'; + break; + case '"': + case '\'': + case '\\': + out[n++] = ch; + break; + default: { + int hi = wat_hex(ch); + int lo = (i + 1u < t.len) ? wat_hex(t.p[i + 1u]) : -1; + if (hi < 0 || lo < 0) + wasm_error(p->c, wasm_loc(t.line, t.col), + "wasm wat: unsupported string escape"); + out[n++] = (char)((hi << 4) | lo); + i++; + break; + } + } + } + out[n] = '\0'; + if (len_out) + *len_out = n; + return out; +} + static void wat_next(WatParser *p) { const char *s = p->src; while (p->pos < p->len) { @@ -258,6 +470,35 @@ static void wat_next(WatParser *p) { } continue; } + if (ch == '(' && p->pos + 1u < p->len && s[p->pos + 1u] == ';') { + uint32_t depth = 1; + p->pos += 2u; + p->col += 2u; + while (depth && p->pos < p->len) { + if (s[p->pos] == '\n') { + p->pos++; + p->line++; + p->col = 1; + } else if (s[p->pos] == '(' && p->pos + 1u < p->len && + s[p->pos + 1u] == ';') { + p->pos += 2u; + p->col += 2u; + depth++; + } else if (s[p->pos] == ';' && p->pos + 1u < p->len && + s[p->pos + 1u] == ')') { + p->pos += 2u; + p->col += 2u; + depth--; + } else { + p->pos++; + p->col++; + } + } + if (depth) + wasm_error(p->c, wasm_loc(p->line, p->col), + "wasm wat: unterminated block comment"); + continue; + } break; } p->tok.p = s + p->pos; @@ -287,9 +528,15 @@ static void wat_next(WatParser *p) { p->tok.kind = WT_STRING; p->tok.p = s + start; while (p->pos < p->len && s[p->pos] != '"') { - if ((unsigned char)s[p->pos] < 0x20) + if (s[p->pos] == '\\') { + p->pos++; + p->col++; + if (p->pos >= p->len) + break; + } else if ((unsigned char)s[p->pos] < 0x20) { wasm_error(p->c, wasm_loc(p->line, p->col), "wasm wat: unsupported string escape/control character"); + } p->pos++; p->col++; } @@ -325,27 +572,47 @@ static int wat_parse_i64(WatParser *p, int64_t *out) { size_t n = p->tok.len, i = 0; uint64_t v = 0; int neg = 0; + unsigned base = 10; if (p->tok.kind != WT_ATOM || n == 0) return 0; - if (s[0] == '-') { - neg = 1; + if (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-'; i = 1; } + if (i + 2u <= n && s[i] == '0' && (s[i + 1u] == 'x' || s[i + 1u] == 'X')) { + base = 16; + i += 2u; + } if (i == n) return 0; for (; i < n; ++i) { + int hd; unsigned d; - if (s[i] < '0' || s[i] > '9') + if (s[i] == '_') + continue; + hd = wat_hex(s[i]); + if (hd < 0 || (unsigned)hd >= base) return 0; - d = (unsigned)(s[i] - '0'); - if (v > (UINT64_MAX - d) / 10u) + d = (unsigned)hd; + if (v > (UINT64_MAX - d) / base) return 0; - v = v * 10u + d; + v = v * base + d; } *out = neg ? -(int64_t)v : (int64_t)v; return 1; } +static int wat_parse_f64(WatParser *p, double *out) { + char buf[128]; + char *end = NULL; + if (p->tok.kind != WT_ATOM || p->tok.len == 0 || p->tok.len >= sizeof buf) + return 0; + memcpy(buf, p->tok.p, p->tok.len); + buf[p->tok.len] = '\0'; + *out = strtod(buf, &end); + return end && *end == '\0'; +} + static int wat_val_type(WasmTok t, WasmValType *out) { if (tok_is(t, "i32")) { *out = WASM_VAL_I32; @@ -379,6 +646,62 @@ static void wat_skip_list(WatParser *p) { static int wat_instr_kind(WasmTok t, WasmInsnKind *out, int *has_imm) { *has_imm = 0; + if (tok_is(t, "unreachable")) { + *out = WASM_INSN_UNREACHABLE; + return 1; + } + if (tok_is(t, "nop")) { + *out = WASM_INSN_NOP; + return 1; + } + if (tok_is(t, "block")) { + *out = WASM_INSN_BLOCK; + return 1; + } + if (tok_is(t, "loop")) { + *out = WASM_INSN_LOOP; + return 1; + } + if (tok_is(t, "if")) { + *out = WASM_INSN_IF; + return 1; + } + if (tok_is(t, "else")) { + *out = WASM_INSN_ELSE; + return 1; + } + if (tok_is(t, "end")) { + *out = WASM_INSN_END; + return 1; + } + if (tok_is(t, "br")) { + *out = WASM_INSN_BR; + *has_imm = 1; + return 1; + } + if (tok_is(t, "br_if")) { + *out = WASM_INSN_BR_IF; + *has_imm = 1; + return 1; + } + if (tok_is(t, "br_table")) { + *out = WASM_INSN_BR_TABLE; + return 1; + } + if (tok_is(t, "select")) { + *out = WASM_INSN_SELECT; + return 1; + } + if (tok_is(t, "f32.const")) { + *out = WASM_INSN_F32_CONST; + *has_imm = 1; + return 1; + } + if (tok_is(t, "f64.const")) { + *out = WASM_INSN_F64_CONST; + *has_imm = 1; + return 1; + } if (tok_is(t, "i32.const")) { *out = WASM_INSN_I32_CONST; *has_imm = 1; @@ -417,6 +740,90 @@ static int wat_instr_kind(WasmTok t, WasmInsnKind *out, int *has_imm) { *out = WASM_INSN_DROP; return 1; } + if (tok_is(t, "i32.load")) { + *out = WASM_INSN_I32_LOAD; + return 1; + } + if (tok_is(t, "i64.load")) { + *out = WASM_INSN_I64_LOAD; + return 1; + } + if (tok_is(t, "i32.load8_s")) { + *out = WASM_INSN_I32_LOAD8_S; + return 1; + } + if (tok_is(t, "i32.load8_u")) { + *out = WASM_INSN_I32_LOAD8_U; + return 1; + } + if (tok_is(t, "i32.load16_s")) { + *out = WASM_INSN_I32_LOAD16_S; + return 1; + } + if (tok_is(t, "i32.load16_u")) { + *out = WASM_INSN_I32_LOAD16_U; + return 1; + } + if (tok_is(t, "i64.load8_s")) { + *out = WASM_INSN_I64_LOAD8_S; + return 1; + } + if (tok_is(t, "i64.load8_u")) { + *out = WASM_INSN_I64_LOAD8_U; + return 1; + } + if (tok_is(t, "i64.load16_s")) { + *out = WASM_INSN_I64_LOAD16_S; + return 1; + } + if (tok_is(t, "i64.load16_u")) { + *out = WASM_INSN_I64_LOAD16_U; + return 1; + } + if (tok_is(t, "i64.load32_s")) { + *out = WASM_INSN_I64_LOAD32_S; + return 1; + } + if (tok_is(t, "i64.load32_u")) { + *out = WASM_INSN_I64_LOAD32_U; + return 1; + } + if (tok_is(t, "i32.store")) { + *out = WASM_INSN_I32_STORE; + return 1; + } + if (tok_is(t, "i64.store")) { + *out = WASM_INSN_I64_STORE; + return 1; + } + if (tok_is(t, "i32.store8")) { + *out = WASM_INSN_I32_STORE8; + return 1; + } + if (tok_is(t, "i32.store16")) { + *out = WASM_INSN_I32_STORE16; + return 1; + } + if (tok_is(t, "i64.store8")) { + *out = WASM_INSN_I64_STORE8; + return 1; + } + if (tok_is(t, "i64.store16")) { + *out = WASM_INSN_I64_STORE16; + return 1; + } + if (tok_is(t, "i64.store32")) { + *out = WASM_INSN_I64_STORE32; + return 1; + } + if (tok_is(t, "memory.size")) { + *out = WASM_INSN_MEMORY_SIZE; + return 1; + } + if (tok_is(t, "memory.grow")) { + *out = WASM_INSN_MEMORY_GROW; + return 1; + } if (tok_is(t, "i32.add")) { *out = WASM_INSN_I32_ADD; return 1; @@ -469,6 +876,26 @@ static int wat_instr_kind(WasmTok t, WasmInsnKind *out, int *has_imm) { *out = WASM_INSN_I32_SHR_U; return 1; } + if (tok_is(t, "i32.rotl")) { + *out = WASM_INSN_I32_ROTL; + return 1; + } + if (tok_is(t, "i32.rotr")) { + *out = WASM_INSN_I32_ROTR; + return 1; + } + if (tok_is(t, "i32.clz")) { + *out = WASM_INSN_I32_CLZ; + return 1; + } + if (tok_is(t, "i32.ctz")) { + *out = WASM_INSN_I32_CTZ; + return 1; + } + if (tok_is(t, "i32.popcnt")) { + *out = WASM_INSN_I32_POPCNT; + return 1; + } if (tok_is(t, "i32.eqz")) { *out = WASM_INSN_I32_EQZ; return 1; @@ -525,6 +952,22 @@ static int wat_instr_kind(WasmTok t, WasmInsnKind *out, int *has_imm) { *out = WASM_INSN_I64_MUL; return 1; } + if (tok_is(t, "i64.div_s")) { + *out = WASM_INSN_I64_DIV_S; + return 1; + } + if (tok_is(t, "i64.div_u")) { + *out = WASM_INSN_I64_DIV_U; + return 1; + } + if (tok_is(t, "i64.rem_s")) { + *out = WASM_INSN_I64_REM_S; + return 1; + } + if (tok_is(t, "i64.rem_u")) { + *out = WASM_INSN_I64_REM_U; + return 1; + } if (tok_is(t, "i64.and")) { *out = WASM_INSN_I64_AND; return 1; @@ -549,6 +992,26 @@ static int wat_instr_kind(WasmTok t, WasmInsnKind *out, int *has_imm) { *out = WASM_INSN_I64_SHR_U; return 1; } + if (tok_is(t, "i64.rotl")) { + *out = WASM_INSN_I64_ROTL; + return 1; + } + if (tok_is(t, "i64.rotr")) { + *out = WASM_INSN_I64_ROTR; + return 1; + } + if (tok_is(t, "i64.clz")) { + *out = WASM_INSN_I64_CLZ; + return 1; + } + if (tok_is(t, "i64.ctz")) { + *out = WASM_INSN_I64_CTZ; + return 1; + } + if (tok_is(t, "i64.popcnt")) { + *out = WASM_INSN_I64_POPCNT; + return 1; + } if (tok_is(t, "i64.eqz")) { *out = WASM_INSN_I64_EQZ; return 1; @@ -593,6 +1056,51 @@ 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.add")) { *out = WASM_INSN_F32_ADD; return 1; } + if (tok_is(t, "f32.sub")) { *out = WASM_INSN_F32_SUB; return 1; } + if (tok_is(t, "f32.mul")) { *out = WASM_INSN_F32_MUL; return 1; } + if (tok_is(t, "f32.div")) { *out = WASM_INSN_F32_DIV; return 1; } + if (tok_is(t, "f32.eq")) { *out = WASM_INSN_F32_EQ; return 1; } + if (tok_is(t, "f32.ne")) { *out = WASM_INSN_F32_NE; return 1; } + if (tok_is(t, "f32.lt")) { *out = WASM_INSN_F32_LT; return 1; } + if (tok_is(t, "f32.gt")) { *out = WASM_INSN_F32_GT; return 1; } + if (tok_is(t, "f32.le")) { *out = WASM_INSN_F32_LE; return 1; } + if (tok_is(t, "f32.ge")) { *out = WASM_INSN_F32_GE; return 1; } + if (tok_is(t, "f64.add")) { *out = WASM_INSN_F64_ADD; return 1; } + if (tok_is(t, "f64.sub")) { *out = WASM_INSN_F64_SUB; return 1; } + if (tok_is(t, "f64.mul")) { *out = WASM_INSN_F64_MUL; return 1; } + if (tok_is(t, "f64.div")) { *out = WASM_INSN_F64_DIV; return 1; } + if (tok_is(t, "f64.eq")) { *out = WASM_INSN_F64_EQ; return 1; } + if (tok_is(t, "f64.ne")) { *out = WASM_INSN_F64_NE; return 1; } + if (tok_is(t, "f64.lt")) { *out = WASM_INSN_F64_LT; return 1; } + if (tok_is(t, "f64.gt")) { *out = WASM_INSN_F64_GT; return 1; } + if (tok_is(t, "f64.le")) { *out = WASM_INSN_F64_LE; return 1; } + if (tok_is(t, "f64.ge")) { *out = WASM_INSN_F64_GE; return 1; } + if (tok_is(t, "i32.wrap_i64")) { *out = WASM_INSN_I32_WRAP_I64; return 1; } + if (tok_is(t, "i32.trunc_f32_s")) { *out = WASM_INSN_I32_TRUNC_F32_S; return 1; } + if (tok_is(t, "i32.trunc_f32_u")) { *out = WASM_INSN_I32_TRUNC_F32_U; return 1; } + if (tok_is(t, "i32.trunc_f64_s")) { *out = WASM_INSN_I32_TRUNC_F64_S; return 1; } + if (tok_is(t, "i32.trunc_f64_u")) { *out = WASM_INSN_I32_TRUNC_F64_U; return 1; } + if (tok_is(t, "i64.extend_i32_s")) { *out = WASM_INSN_I64_EXTEND_I32_S; return 1; } + if (tok_is(t, "i64.extend_i32_u")) { *out = WASM_INSN_I64_EXTEND_I32_U; return 1; } + if (tok_is(t, "i64.trunc_f32_s")) { *out = WASM_INSN_I64_TRUNC_F32_S; return 1; } + if (tok_is(t, "i64.trunc_f32_u")) { *out = WASM_INSN_I64_TRUNC_F32_U; return 1; } + if (tok_is(t, "i64.trunc_f64_s")) { *out = WASM_INSN_I64_TRUNC_F64_S; return 1; } + if (tok_is(t, "i64.trunc_f64_u")) { *out = WASM_INSN_I64_TRUNC_F64_U; return 1; } + if (tok_is(t, "f32.convert_i32_s")) { *out = WASM_INSN_F32_CONVERT_I32_S; return 1; } + if (tok_is(t, "f32.convert_i32_u")) { *out = WASM_INSN_F32_CONVERT_I32_U; return 1; } + if (tok_is(t, "f32.convert_i64_s")) { *out = WASM_INSN_F32_CONVERT_I64_S; return 1; } + if (tok_is(t, "f32.convert_i64_u")) { *out = WASM_INSN_F32_CONVERT_I64_U; return 1; } + if (tok_is(t, "f32.demote_f64")) { *out = WASM_INSN_F32_DEMOTE_F64; return 1; } + if (tok_is(t, "f64.convert_i32_s")) { *out = WASM_INSN_F64_CONVERT_I32_S; return 1; } + if (tok_is(t, "f64.convert_i32_u")) { *out = WASM_INSN_F64_CONVERT_I32_U; return 1; } + if (tok_is(t, "f64.convert_i64_s")) { *out = WASM_INSN_F64_CONVERT_I64_S; return 1; } + if (tok_is(t, "f64.convert_i64_u")) { *out = WASM_INSN_F64_CONVERT_I64_U; return 1; } + if (tok_is(t, "f64.promote_f32")) { *out = WASM_INSN_F64_PROMOTE_F32; return 1; } + if (tok_is(t, "i32.reinterpret_f32")) { *out = WASM_INSN_I32_REINTERPRET_F32; return 1; } + if (tok_is(t, "i64.reinterpret_f64")) { *out = WASM_INSN_I64_REINTERPRET_F64; return 1; } + if (tok_is(t, "f32.reinterpret_i32")) { *out = WASM_INSN_F32_REINTERPRET_I32; return 1; } + if (tok_is(t, "f64.reinterpret_i64")) { *out = WASM_INSN_F64_REINTERPRET_I64; return 1; } return 0; } @@ -649,6 +1157,104 @@ static void wat_parse_instr_imm(WatParser *p, WasmFunc *f, WasmInsnKind kind, } } +static int wasm_insn_is_load(WasmInsnKind kind) { + return kind == WASM_INSN_I32_LOAD || kind == WASM_INSN_I64_LOAD || + kind == WASM_INSN_I32_LOAD8_S || kind == WASM_INSN_I32_LOAD8_U || + kind == WASM_INSN_I32_LOAD16_S || kind == WASM_INSN_I32_LOAD16_U || + kind == WASM_INSN_I64_LOAD8_S || kind == WASM_INSN_I64_LOAD8_U || + kind == WASM_INSN_I64_LOAD16_S || kind == WASM_INSN_I64_LOAD16_U || + kind == WASM_INSN_I64_LOAD32_S || kind == WASM_INSN_I64_LOAD32_U; +} + +static int wasm_insn_is_store(WasmInsnKind kind) { + return kind == WASM_INSN_I32_STORE || kind == WASM_INSN_I64_STORE || + kind == WASM_INSN_I32_STORE8 || kind == WASM_INSN_I32_STORE16 || + kind == WASM_INSN_I64_STORE8 || kind == WASM_INSN_I64_STORE16 || + kind == WASM_INSN_I64_STORE32; +} + +static int wasm_insn_is_mem(WasmInsnKind kind) { + return wasm_insn_is_load(kind) || wasm_insn_is_store(kind); +} + +static int wat_atom_prefix(WasmTok t, const char *prefix) { + size_t n = strlen(prefix); + return t.kind == WT_ATOM && t.len >= n && memcmp(t.p, prefix, n) == 0; +} + +static int wat_parse_u32_atom(WasmTok t, uint32_t *out) { + uint64_t v = 0; + size_t i = 0; + unsigned base = 10; + if (t.kind != WT_ATOM || t.len == 0) + return 0; + if (i + 2u <= t.len && t.p[i] == '0' && + (t.p[i + 1u] == 'x' || t.p[i + 1u] == 'X')) { + base = 16; + i += 2u; + } + if (i == t.len) + return 0; + for (; i < t.len; ++i) { + int hd; + if (t.p[i] == '_') + continue; + hd = wat_hex(t.p[i]); + if (hd < 0 || (unsigned)hd >= base) + return 0; + if (v > (UINT32_MAX - (uint32_t)hd) / base) + return 0; + v = v * base + (uint32_t)hd; + } + *out = (uint32_t)v; + return 1; +} + +static void wat_parse_mem_attrs(WatParser *p, uint32_t *align, + uint32_t *offset) { + while (p->tok.kind == WT_ATOM) { + WasmTok val; + if (wat_atom_prefix(p->tok, "align=")) { + val = p->tok; + val.p += 6; + val.len -= 6; + if (!wat_parse_u32_atom(val, align)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory alignment"); + wat_next(p); + } else if (wat_atom_prefix(p->tok, "offset=")) { + val = p->tok; + val.p += 7; + val.len -= 7; + if (!wat_parse_u32_atom(val, offset)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory offset"); + wat_next(p); + } else { + break; + } + } +} + +static void wat_reject_inline_result(WatParser *p, const char *what) { + if (p->tok.kind != WT_LPAREN) + return; + wat_next(p); + if (!tok_is(p->tok, "result")) { + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + return; + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: %s results are unsupported", what); +} + static void wat_parse_instr(WatParser *p, WasmFunc *f); static void wat_parse_instr_list(WatParser *p, WasmFunc *f) { @@ -658,13 +1264,136 @@ static void wat_parse_instr_list(WatParser *p, WasmFunc *f) { WasmTok head; wat_expect(p, WT_LPAREN, "'('"); head = p->tok; + if (tok_is(head, "block") || tok_is(head, "loop")) { + kind = tok_is(head, "block") ? WASM_INSN_BLOCK : WASM_INSN_LOOP; + wat_next(p); + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (tok_is(p->tok, "result")) { + wat_next(p); + if (!tok_is(p->tok, "i32") && !tok_is(p->tok, "i64")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported block result type"); + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: block results are unsupported"); + } + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + } + wasm_func_add_insn(p->c, p->module, f, kind, 0); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) + wat_parse_instr(p, f); + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_END, 0); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (tok_is(head, "if")) { + wat_next(p); + while (p->tok.kind == WT_LPAREN) { + WasmTok save_head; + size_t save_pos = p->pos; + uint32_t save_line = p->line, save_col = p->col; + wat_next(p); + save_head = p->tok; + p->pos = save_pos; + p->line = save_line; + p->col = save_col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = save_line; + p->tok.col = save_col - 1u; + if (tok_is(save_head, "then") || tok_is(save_head, "else")) + break; + if (tok_is(save_head, "result")) + wasm_error(p->c, wasm_loc(save_head.line, save_head.col), + "wasm wat: if results are unsupported"); + wat_parse_instr(p, f); + } + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_IF, 0); + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "then")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected then"); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) + wat_parse_instr(p, f); + wat_expect(p, WT_RPAREN, "')'"); + } + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "else")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected else"); + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_ELSE, 0); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) + wat_parse_instr(p, f); + wat_expect(p, WT_RPAREN, "')'"); + } + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_END, 0); + wat_expect(p, WT_RPAREN, "')'"); + return; + } if (!wat_instr_kind(head, &kind, &has_imm)) wasm_error(p->c, wasm_loc(head.line, head.col), "wasm wat: unsupported instruction"); wat_next(p); + if (wasm_insn_is_mem(kind)) { + uint32_t align = 0, offset = 0; + wat_parse_mem_attrs(p, &align, &offset); + while (p->tok.kind == WT_LPAREN) + wat_parse_instr(p, f); + wasm_func_add_mem_insn(p->c, p->module, f, kind, align, offset); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_BR_TABLE) { + WasmInsn *in; + uint32_t n = 0; + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + int64_t target; + if (n >= 16u) + 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; + wat_next(p); + } + f->ninsns -= n - 1u; + f->insns[f->ninsns - 1u].ntargets = n; + wat_expect(p, WT_RPAREN, "')'"); + return; + } if (has_imm) { - wat_parse_instr_imm(p, f, kind, &imm); - wat_next(p); + if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { + double fv; + if (!wat_parse_f64(p, &fv)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected float immediate"); + wat_next(p); + while (p->tok.kind == WT_LPAREN) + wat_parse_instr(p, f); + wasm_func_add_fp_insn(p->c, p->module, f, kind, fv); + wat_expect(p, WT_RPAREN, "')'"); + return; + } else { + wat_parse_instr_imm(p, f, kind, &imm); + wat_next(p); + } } while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); @@ -683,11 +1412,53 @@ static void wat_parse_instr(WatParser *p, WasmFunc *f) { wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: unsupported instruction"); wat_next(p); + if (kind == WASM_INSN_BLOCK || kind == WASM_INSN_LOOP) + wat_reject_inline_result(p, "block"); + else if (kind == WASM_INSN_IF) + wat_reject_inline_result(p, "if"); + if (wasm_insn_is_mem(kind)) { + uint32_t align = 0, offset = 0; + wat_parse_mem_attrs(p, &align, &offset); + wasm_func_add_mem_insn(p->c, p->module, f, kind, align, offset); + return; + } + if (kind == WASM_INSN_BR_TABLE) { + WasmInsn *in; + uint32_t n = 0; + 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 >= 16u) + 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; + wat_next(p); + } + in->ntargets = n; + return; + } if (has_imm) { - int64_t imm; - wat_parse_instr_imm(p, f, kind, &imm); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_next(p); + if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { + double fv; + if (!wat_parse_f64(p, &fv)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected float immediate"); + wasm_func_add_fp_insn(p->c, p->module, f, kind, fv); + wat_next(p); + } else { + int64_t imm; + wat_parse_instr_imm(p, f, kind, &imm); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_next(p); + } } else { wasm_func_add_insn(p->c, p->module, f, kind, 0); } @@ -715,10 +1486,7 @@ static void wat_parse_func(WatParser *p) { if (p->tok.kind != WT_STRING) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: expected export string"); - f->export_name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); - if (!f->export_name) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm: out of memory"); + f->export_name = wat_dup_string(p, p->tok, NULL); wat_next(p); wat_expect(p, WT_RPAREN, "')'"); } else if (tok_is(p->tok, "param")) { @@ -812,37 +1580,136 @@ static void wat_parse_func(WatParser *p) { wat_expect(p, WT_RPAREN, "')'"); } -static void wat_parse_module(CfreeCompiler *c, const CfreeBytesInput *input, - WasmModule *out) { - WatParser p; - memset(&p, 0, sizeof p); - p.c = c; - p.name = input->name; - p.src = (const char *)input->data; - p.len = input->len; - p.line = 1; - p.col = 1; - p.module = out; - wat_next(&p); - wat_expect(&p, WT_LPAREN, "'('"); - if (!tok_is(p.tok, "module")) - wasm_error(c, wasm_loc(p.tok.line, p.tok.col), "wasm wat: expected module"); - wat_next(&p); - while (p.tok.kind != WT_RPAREN && p.tok.kind != WT_EOF) { - if (p.tok.kind != WT_LPAREN) - wasm_error(c, wasm_loc(p.tok.line, p.tok.col), - "wasm wat: expected module field"); - wat_next(&p); - if (tok_is(p.tok, "func")) { - p.pos = (size_t)(p.tok.p - p.src); - p.line = p.tok.line; - p.col = p.tok.col; - p.tok.kind = WT_LPAREN; - p.tok.p = p.src + p.pos - 1u; - p.tok.len = 1; - p.tok.line = p.line; +static void wat_parse_export_field(WatParser *p) { + char *name; + int64_t idx = 0; + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export string"); + name = wat_dup_string(p, p->tok, NULL); + wat_next(p); + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "func")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: only function exports are supported"); + wat_next(p); + wat_parse_func_index(p, &idx); + if (idx < 0 || (uint64_t)idx >= p->module->nfuncs) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: function export index out of range"); + if (p->module->funcs[idx].export_name) + p->module->heap->free(p->module->heap, p->module->funcs[idx].export_name, + strlen(p->module->funcs[idx].export_name) + 1u); + p->module->funcs[idx].export_name = name; + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_memory_field(WatParser *p) { + int64_t min_pages, max_pages; + if (p->module->has_memory) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: multiple memories are unsupported"); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') + wat_next(p); + if (!wat_parse_i64(p, &min_pages) || min_pages < 0 || + min_pages > UINT16_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected memory minimum"); + p->module->has_memory = 1; + p->module->memory.min_pages = (uint32_t)min_pages; + wat_next(p); + if (p->tok.kind != WT_RPAREN) { + if (!wat_parse_i64(p, &max_pages) || max_pages < min_pages || + max_pages > UINT16_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory maximum"); + p->module->memory.has_max = 1; + p->module->memory.max_pages = (uint32_t)max_pages; + wat_next(p); + } + wasm_memory_ensure(p->c, p->module, p->module->memory.min_pages * 65536u); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_data_field(WatParser *p) { + int64_t offset = 0; + char *bytes; + size_t nbytes; + size_t alloc_len; + if (p->tok.kind == WT_ATOM) + wat_next(p); + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "i32.const")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected i32.const data offset"); + wat_next(p); + if (!wat_parse_i64(p, &offset) || offset < 0 || offset > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad data offset"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected data string"); + alloc_len = p->tok.len + 1u; + bytes = wat_dup_string(p, p->tok, &nbytes); + if ((uint64_t)offset + nbytes > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: data segment too large"); + wasm_memory_ensure(p->c, p->module, (uint32_t)offset + (uint32_t)nbytes); + memcpy(p->module->memory.data + (uint32_t)offset, bytes, nbytes); + p->module->heap->free(p->module->heap, bytes, alloc_len); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_module(CfreeCompiler *c, const CfreeBytesInput *input, + WasmModule *out) { + WatParser p; + memset(&p, 0, sizeof p); + p.c = c; + p.name = input->name; + p.src = (const char *)input->data; + p.len = input->len; + p.line = 1; + p.col = 1; + p.module = out; + wat_next(&p); + wat_expect(&p, WT_LPAREN, "'('"); + if (!tok_is(p.tok, "module")) + wasm_error(c, wasm_loc(p.tok.line, p.tok.col), "wasm wat: expected module"); + wat_next(&p); + while (p.tok.kind != WT_RPAREN && p.tok.kind != WT_EOF) { + if (p.tok.kind != WT_LPAREN) + wasm_error(c, wasm_loc(p.tok.line, p.tok.col), + "wasm wat: expected module field"); + wat_next(&p); + if (tok_is(p.tok, "func")) { + p.pos = (size_t)(p.tok.p - p.src); + p.line = p.tok.line; + p.col = p.tok.col; + p.tok.kind = WT_LPAREN; + p.tok.p = p.src + p.pos - 1u; + p.tok.len = 1; + p.tok.line = p.line; p.tok.col = p.col - 1u; wat_parse_func(&p); + } else if (tok_is(p.tok, "export")) { + wat_next(&p); + wat_parse_export_field(&p); + } else if (tok_is(p.tok, "memory")) { + wat_next(&p); + wat_parse_memory_field(&p); + } else if (tok_is(p.tok, "data")) { + wat_next(&p); + wat_parse_data_field(&p); + } else if (tok_is(p.tok, "import") || tok_is(p.tok, "table") || + tok_is(p.tok, "global") || tok_is(p.tok, "elem") || + tok_is(p.tok, "start")) { + wasm_error(c, wasm_loc(p.tok.line, p.tok.col), + "wasm wat: unsupported module field"); } else { wat_skip_list(&p); } @@ -886,6 +1753,24 @@ static int64_t bin_sleb(BinReader *r, uint32_t bits) { return result; } +static double bin_f32(BinReader *r) { + uint32_t bits = 0; + float f; + for (uint32_t i = 0; i < 4u; ++i) + bits |= (uint32_t)bin_u8(r) << (i * 8u); + memcpy(&f, &bits, sizeof f); + return (double)f; +} + +static double bin_f64(BinReader *r) { + uint64_t bits = 0; + double d; + for (uint32_t i = 0; i < 8u; ++i) + bits |= (uint64_t)bin_u8(r) << (i * 8u); + memcpy(&d, &bits, sizeof d); + return d; +} + static void bin_need(BinReader *r, size_t n) { if (n > r->len || r->pos > r->len - n) wasm_error(r->c, wasm_loc(0, 0), "wasm: section length out of bounds"); @@ -954,6 +1839,8 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, types[ntypes].results[j] = (WasmValType)bin_u8(&r); ntypes++; } + } else if (id == 2) { + wasm_error(c, wasm_loc(0, 0), "wasm: imports are unsupported"); } else if (id == 3) { uint32_t i, count = bin_uleb(&r); if (count > 64u) @@ -972,6 +1859,27 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, sizeof(f->results[0]) * f->nresults); nfunc_types++; } + } else if (id == 5) { + uint32_t count = bin_uleb(&r); + uint8_t flags; + if (count > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: multiple memories unsupported"); + if (count == 1u) { + flags = bin_u8(&r); + out->has_memory = 1; + out->memory.min_pages = bin_uleb(&r); + if (flags & 1u) { + out->memory.has_max = 1; + out->memory.max_pages = bin_uleb(&r); + } + if (flags & ~1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); + wasm_memory_ensure(c, out, out->memory.min_pages * 65536u); + } + } else if (id == 4) { + wasm_error(c, wasm_loc(0, 0), "wasm: tables are unsupported"); + } else if (id == 6) { + wasm_error(c, wasm_loc(0, 0), "wasm: globals are unsupported"); } else if (id == 7) { uint32_t i, count = bin_uleb(&r); for (i = 0; i < count; ++i) { @@ -994,6 +1902,10 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); } } + } else if (id == 8) { + wasm_error(c, wasm_loc(0, 0), "wasm: start functions are unsupported"); + } else if (id == 9) { + wasm_error(c, wasm_loc(0, 0), "wasm: element segments are unsupported"); } else if (id == 10) { uint32_t i, count = bin_uleb(&r); if (count != nfunc_types) @@ -1002,6 +1914,7 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, uint32_t body_size = bin_uleb(&r); size_t body_end; uint32_t local_groups, j; + uint32_t control_depth = 0; WasmFunc *f = &out->funcs[i]; bin_need(&r, body_size); body_end = r.pos + body_size; @@ -1016,15 +1929,152 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, } while (r.pos < body_end) { uint8_t op = bin_u8(&r); - if (op == 0x0bu) - break; + if (op == 0x0bu) { + if (!control_depth) + break; + control_depth--; + wasm_func_add_insn(c, out, f, WASM_INSN_END, 0); + continue; + } switch (op) { + case 0x00: + wasm_func_add_insn(c, out, f, WASM_INSN_UNREACHABLE, 0); + break; + case 0x01: + wasm_func_add_insn(c, out, f, WASM_INSN_NOP, 0); + break; + case 0x02: + (void)bin_u8(&r); + control_depth++; + wasm_func_add_insn(c, out, f, WASM_INSN_BLOCK, 0); + break; + case 0x03: + (void)bin_u8(&r); + control_depth++; + wasm_func_add_insn(c, out, f, WASM_INSN_LOOP, 0); + break; + case 0x04: + (void)bin_u8(&r); + control_depth++; + wasm_func_add_insn(c, out, f, WASM_INSN_IF, 0); + break; + case 0x05: + wasm_func_add_insn(c, out, f, WASM_INSN_ELSE, 0); + break; + case 0x0c: + wasm_func_add_insn(c, out, f, WASM_INSN_BR, bin_uleb(&r)); + break; + case 0x0d: + wasm_func_add_insn(c, out, f, WASM_INSN_BR_IF, bin_uleb(&r)); + break; + case 0x0e: { + WasmInsn *in; + uint32_t n = bin_uleb(&r); + if (n >= 16u) + wasm_error(c, wasm_loc(0, 0), "wasm: too many br_table targets"); + wasm_func_add_insn(c, out, f, WASM_INSN_BR_TABLE, 0); + in = &f->insns[f->ninsns - 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: wasm_func_add_insn(c, out, f, WASM_INSN_RETURN, 0); break; case 0x1a: wasm_func_add_insn(c, out, f, WASM_INSN_DROP, 0); break; + case 0x1b: + wasm_func_add_insn(c, out, f, WASM_INSN_SELECT, 0); + break; + case 0x28: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x29: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x2c: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_S, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x2d: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_U, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x2e: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_S, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x2f: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_U, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x30: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_S, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x31: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_U, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x32: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_S, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x33: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_U, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x34: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_S, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x35: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_U, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x36: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x37: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x3a: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE8, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x3b: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE16, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x3c: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE8, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x3d: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE16, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x3e: + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE32, + bin_uleb(&r), bin_uleb(&r)); + break; + case 0x3f: + if (bin_u8(&r) != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad memory.size index"); + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_SIZE, 0); + break; + case 0x40: + if (bin_u8(&r) != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad memory.grow index"); + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_GROW, 0); + break; case 0x10: wasm_func_add_insn(c, out, f, WASM_INSN_CALL, bin_uleb(&r)); break; @@ -1045,6 +2095,14 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, wasm_func_add_insn(c, out, f, WASM_INSN_I64_CONST, bin_sleb(&r, 64)); break; + case 0x43: + wasm_func_add_fp_insn(c, out, f, WASM_INSN_F32_CONST, + bin_f32(&r)); + break; + case 0x44: + wasm_func_add_fp_insn(c, out, f, WASM_INSN_F64_CONST, + bin_f64(&r)); + break; case 0x45: wasm_func_add_insn(c, out, f, WASM_INSN_I32_EQZ, 0); break; @@ -1150,6 +2208,30 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, case 0x76: wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHR_U, 0); break; + case 0x67: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_CLZ, 0); + break; + case 0x68: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_CTZ, 0); + break; + case 0x69: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_POPCNT, 0); + break; + case 0x77: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_ROTL, 0); + break; + case 0x78: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_ROTR, 0); + break; + case 0x79: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_CLZ, 0); + break; + case 0x7a: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_CTZ, 0); + break; + case 0x7b: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_POPCNT, 0); + break; case 0x7c: wasm_func_add_insn(c, out, f, WASM_INSN_I64_ADD, 0); break; @@ -1159,6 +2241,18 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, case 0x7e: wasm_func_add_insn(c, out, f, WASM_INSN_I64_MUL, 0); break; + case 0x7f: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_DIV_S, 0); + break; + case 0x80: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_DIV_U, 0); + break; + case 0x81: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_REM_S, 0); + break; + case 0x82: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_REM_U, 0); + break; case 0x83: wasm_func_add_insn(c, out, f, WASM_INSN_I64_AND, 0); break; @@ -1177,6 +2271,147 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, case 0x88: wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHR_U, 0); break; + case 0x89: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_ROTL, 0); + break; + case 0x8a: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_ROTR, 0); + break; + case 0x92: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_ADD, 0); + break; + case 0x93: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_SUB, 0); + break; + case 0x94: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_MUL, 0); + break; + case 0x95: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_DIV, 0); + break; + case 0xa0: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_ADD, 0); + break; + case 0xa1: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_SUB, 0); + break; + case 0xa2: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_MUL, 0); + break; + case 0xa3: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_DIV, 0); + break; + case 0x5b: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_EQ, 0); + break; + case 0x5c: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_NE, 0); + break; + case 0x5d: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_LT, 0); + break; + case 0x5e: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_GT, 0); + break; + case 0x5f: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_LE, 0); + break; + case 0x60: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_GE, 0); + break; + case 0x61: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_EQ, 0); + break; + case 0x62: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_NE, 0); + break; + case 0x63: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_LT, 0); + break; + case 0x64: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_GT, 0); + break; + case 0x65: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_LE, 0); + break; + case 0x66: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_GE, 0); + break; + case 0xa7: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_WRAP_I64, 0); + break; + case 0xa8: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F32_S, 0); + break; + case 0xa9: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F32_U, 0); + break; + case 0xaa: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F64_S, 0); + break; + case 0xab: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F64_U, 0); + break; + case 0xac: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_EXTEND_I32_S, 0); + break; + case 0xad: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_EXTEND_I32_U, 0); + break; + case 0xae: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F32_S, 0); + break; + case 0xaf: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F32_U, 0); + break; + case 0xb0: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F64_S, 0); + break; + case 0xb1: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F64_U, 0); + break; + case 0xb2: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I32_S, 0); + break; + case 0xb3: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I32_U, 0); + break; + case 0xb4: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I64_S, 0); + break; + case 0xb5: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I64_U, 0); + break; + case 0xb6: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_DEMOTE_F64, 0); + break; + case 0xb7: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I32_S, 0); + break; + case 0xb8: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I32_U, 0); + break; + case 0xb9: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I64_S, 0); + break; + case 0xba: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I64_U, 0); + break; + case 0xbb: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_PROMOTE_F32, 0); + break; + case 0xbc: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_REINTERPRET_F32, 0); + break; + case 0xbd: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_REINTERPRET_F64, 0); + break; + case 0xbe: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_REINTERPRET_I32, 0); + break; + case 0xbf: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_REINTERPRET_I64, 0); + break; default: wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", op); @@ -1184,6 +2419,27 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, } r.pos = body_end; } + } else if (id == 11) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + uint32_t flags = bin_uleb(&r); + int64_t offset; + uint32_t n; + if (flags != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: passive data unsupported"); + if (bin_u8(&r) != 0x41) + wasm_error(c, wasm_loc(0, 0), "wasm: expected i32.const data offset"); + offset = bin_sleb(&r, 32); + if (bin_u8(&r) != 0x0b || offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad data offset"); + n = bin_uleb(&r); + bin_need(&r, n); + wasm_memory_ensure(c, out, (uint32_t)offset + n); + memcpy(out->memory.data + (uint32_t)offset, r.data + r.pos, n); + r.pos += n; + } + } else if (id == 12) { + wasm_error(c, wasm_loc(0, 0), "wasm: data count sections are unsupported"); } else { r.pos = end; } @@ -1226,6 +2482,191 @@ static CfreeCgMemAccess wasm_cg_mem(CfreeCompiler *c, CfreeCgBuiltinTypes b, return mem; } +static CfreeCgTypeId wasm_load_storage_type(CfreeCgBuiltinTypes b, + uint8_t kind) { + switch (kind) { + case WASM_INSN_I32_LOAD8_S: + case WASM_INSN_I32_LOAD8_U: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + return b.id[CFREE_CG_BUILTIN_I8]; + case WASM_INSN_I32_LOAD16_S: + case WASM_INSN_I32_LOAD16_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + return b.id[CFREE_CG_BUILTIN_I16]; + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + case WASM_INSN_I32_LOAD: + return b.id[CFREE_CG_BUILTIN_I32]; + default: + return b.id[CFREE_CG_BUILTIN_I64]; + } +} + +static CfreeCgTypeId wasm_store_storage_type(CfreeCgBuiltinTypes b, + uint8_t kind) { + switch (kind) { + case WASM_INSN_I32_STORE8: + case WASM_INSN_I64_STORE8: + return b.id[CFREE_CG_BUILTIN_I8]; + case WASM_INSN_I32_STORE16: + case WASM_INSN_I64_STORE16: + return b.id[CFREE_CG_BUILTIN_I16]; + case WASM_INSN_I64_STORE32: + case WASM_INSN_I32_STORE: + return b.id[CFREE_CG_BUILTIN_I32]; + default: + return b.id[CFREE_CG_BUILTIN_I64]; + } +} + +static uint32_t wasm_mem_width(uint8_t kind) { + switch (kind) { + case WASM_INSN_I32_LOAD8_S: + case WASM_INSN_I32_LOAD8_U: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I32_STORE8: + case WASM_INSN_I64_STORE8: + return 1; + case WASM_INSN_I32_LOAD16_S: + case WASM_INSN_I32_LOAD16_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I32_STORE16: + case WASM_INSN_I64_STORE16: + return 2; + case WASM_INSN_I32_LOAD: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + case WASM_INSN_I32_STORE: + case WASM_INSN_I64_STORE32: + return 4; + default: + return 8; + } +} + +static void wasm_cg_trap(CfreeCg *cg) { + cfree_cg_intrinsic(cg, CFREE_CG_INTRIN_TRAP, 0, CFREE_CG_TYPE_NONE); + cfree_cg_unreachable(cg); +} + +static void wasm_cg_memory_check(CfreeCompiler *c, CfreeCg *cg, + CfreeCgBuiltinTypes b, const WasmModule *m, + const WasmInsn *in) { + uint32_t width = wasm_mem_width(in->kind); + uint64_t end = (uint64_t)(uint32_t)in->imm + width; + CfreeCgLabel ok = cfree_cg_label_new(cg); + if (end > m->memory.data_len) { + wasm_cg_trap(cg); + return; + } + (void)c; + cfree_cg_dup(cg); + cfree_cg_push_int(cg, (uint64_t)(m->memory.data_len - (uint32_t)end), + b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_int_cmp(cg, CFREE_CG_INT_LE_U); + cfree_cg_branch_true(cg, ok); + wasm_cg_trap(cg); + cfree_cg_label_place(cg, ok); +} + +static void wasm_cg_memory_lvalue(CfreeCg *cg, CfreeCgSym memory_sym, + uint32_t offset) { + cfree_cg_push_symbol_lvalue(cg, memory_sym, 0); + cfree_cg_swap(cg); + cfree_cg_index(cg, offset); +} + +static void wasm_cg_rotate(CfreeCompiler *c, CfreeCg *cg, CfreeCgBuiltinTypes b, + WasmValType vt, int right) { + CfreeCgTypeId ty = wasm_cg_type(c, b, vt); + CfreeCgMemAccess mem = wasm_cg_mem(c, b, vt); + CfreeCgLocalAttrs attrs; + CfreeCgLocal lhs, rhs; + uint32_t mask = vt == WASM_VAL_I32 ? 31u : 63u; + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + rhs = cfree_cg_local(cg, ty, attrs); + lhs = cfree_cg_local(cg, ty, attrs); + cfree_cg_push_local(cg, rhs); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + cfree_cg_push_local(cg, lhs); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + + cfree_cg_push_local(cg, lhs); + cfree_cg_load(cg, mem); + cfree_cg_push_local(cg, rhs); + cfree_cg_load(cg, mem); + cfree_cg_push_int(cg, mask, ty); + cfree_cg_int_binop(cg, CFREE_CG_INT_AND, 0); + cfree_cg_int_binop(cg, right ? CFREE_CG_INT_LSHR : CFREE_CG_INT_SHL, 0); + + cfree_cg_push_local(cg, lhs); + cfree_cg_load(cg, mem); + cfree_cg_push_int(cg, 0, ty); + cfree_cg_push_local(cg, rhs); + cfree_cg_load(cg, mem); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_push_int(cg, mask, ty); + cfree_cg_int_binop(cg, CFREE_CG_INT_AND, 0); + cfree_cg_int_binop(cg, right ? CFREE_CG_INT_SHL : CFREE_CG_INT_LSHR, 0); + cfree_cg_int_binop(cg, CFREE_CG_INT_OR, 0); +} + +static void wasm_cg_checked_divrem(CfreeCompiler *c, CfreeCg *cg, + CfreeCgBuiltinTypes b, WasmValType vt, + CfreeCgIntBinOp op) { + CfreeCgTypeId ty = wasm_cg_type(c, b, vt); + CfreeCgMemAccess mem = wasm_cg_mem(c, b, vt); + CfreeCgLocalAttrs attrs; + CfreeCgLocal lhs, rhs; + CfreeCgLabel ok = cfree_cg_label_new(cg); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + rhs = cfree_cg_local(cg, ty, attrs); + lhs = cfree_cg_local(cg, ty, attrs); + cfree_cg_push_local(cg, rhs); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + cfree_cg_push_local(cg, lhs); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + cfree_cg_push_local(cg, rhs); + cfree_cg_load(cg, mem); + cfree_cg_push_int(cg, 0, ty); + cfree_cg_int_cmp(cg, CFREE_CG_INT_NE); + cfree_cg_branch_true(cg, ok); + wasm_cg_trap(cg); + cfree_cg_label_place(cg, ok); + if (op == CFREE_CG_INT_SDIV) { + CfreeCgLabel no_overflow = cfree_cg_label_new(cg); + uint64_t min_val = vt == WASM_VAL_I32 ? UINT64_C(0x80000000) + : UINT64_C(0x8000000000000000); + cfree_cg_push_local(cg, lhs); + cfree_cg_load(cg, mem); + cfree_cg_push_int(cg, min_val, ty); + cfree_cg_int_cmp(cg, CFREE_CG_INT_NE); + cfree_cg_branch_true(cg, no_overflow); + cfree_cg_push_local(cg, rhs); + cfree_cg_load(cg, mem); + cfree_cg_push_int(cg, UINT64_MAX, ty); + cfree_cg_int_cmp(cg, CFREE_CG_INT_NE); + cfree_cg_branch_true(cg, no_overflow); + wasm_cg_trap(cg); + cfree_cg_label_place(cg, no_overflow); + } + cfree_cg_push_local(cg, lhs); + cfree_cg_load(cg, mem); + cfree_cg_push_local(cg, rhs); + cfree_cg_load(cg, mem); + cfree_cg_int_binop(cg, op, 0); +} + static int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp *out) { switch (kind) { case WASM_INSN_I32_EQ: @@ -1273,61 +2714,249 @@ static int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp *out) { } } -static void wasm_validate(const WasmModule *m, CfreeCompiler *c) { +static int wasm_fp_binop(uint8_t kind, CfreeCgFpBinOp *out) { + switch (kind) { + case WASM_INSN_F32_ADD: + case WASM_INSN_F64_ADD: + *out = CFREE_CG_FP_ADD; return 1; + case WASM_INSN_F32_SUB: + case WASM_INSN_F64_SUB: + *out = CFREE_CG_FP_SUB; return 1; + case WASM_INSN_F32_MUL: + case WASM_INSN_F64_MUL: + *out = CFREE_CG_FP_MUL; return 1; + case WASM_INSN_F32_DIV: + case WASM_INSN_F64_DIV: + *out = CFREE_CG_FP_DIV; return 1; + default: + return 0; + } +} + +static int wasm_fp_cmp_op(uint8_t kind, CfreeCgFpCmpOp *out) { + switch (kind) { + case WASM_INSN_F32_EQ: + case WASM_INSN_F64_EQ: + *out = CFREE_CG_FP_OEQ; return 1; + case WASM_INSN_F32_NE: + case WASM_INSN_F64_NE: + *out = CFREE_CG_FP_ONE; return 1; + case WASM_INSN_F32_LT: + case WASM_INSN_F64_LT: + *out = CFREE_CG_FP_OLT; return 1; + case WASM_INSN_F32_GT: + case WASM_INSN_F64_GT: + *out = CFREE_CG_FP_OGT; return 1; + case WASM_INSN_F32_LE: + case WASM_INSN_F64_LE: + *out = CFREE_CG_FP_OLE; return 1; + case WASM_INSN_F32_GE: + case WASM_INSN_F64_GE: + *out = CFREE_CG_FP_OGE; return 1; + default: + return 0; + } +} + +static WasmValType wasm_load_result_type(uint8_t kind) { + switch (kind) { + case WASM_INSN_I64_LOAD: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + return WASM_VAL_I64; + default: + return WASM_VAL_I32; + } +} + +static WasmValType wasm_store_value_type(uint8_t kind) { + switch (kind) { + case WASM_INSN_I64_STORE: + case WASM_INSN_I64_STORE8: + case WASM_INSN_I64_STORE16: + case WASM_INSN_I64_STORE32: + return WASM_VAL_I64; + default: + return WASM_VAL_I32; + } +} + +static int wasm_int_unop_kind(uint8_t kind, WasmValType *vt) { + if (kind == WASM_INSN_I32_CLZ || kind == WASM_INSN_I32_CTZ || + kind == WASM_INSN_I32_POPCNT) { + *vt = WASM_VAL_I32; + return 1; + } + if (kind == WASM_INSN_I64_CLZ || kind == WASM_INSN_I64_CTZ || + kind == WASM_INSN_I64_POPCNT) { + *vt = WASM_VAL_I64; + return 1; + } + return 0; +} + +static 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) { + *vt = WASM_VAL_F32; + return 1; + } + if (kind == WASM_INSN_F64_ADD || kind == WASM_INSN_F64_SUB || + kind == WASM_INSN_F64_MUL || kind == WASM_INSN_F64_DIV) { + *vt = WASM_VAL_F64; + return 1; + } + return 0; +} + +static int wasm_fp_cmp_kind(uint8_t kind, WasmValType *vt) { + if (kind == WASM_INSN_F32_EQ || kind == WASM_INSN_F32_NE || + kind == WASM_INSN_F32_LT || kind == WASM_INSN_F32_GT || + kind == WASM_INSN_F32_LE || kind == WASM_INSN_F32_GE) { + *vt = WASM_VAL_F32; + return 1; + } + if (kind == WASM_INSN_F64_EQ || kind == WASM_INSN_F64_NE || + kind == WASM_INSN_F64_LT || kind == WASM_INSN_F64_GT || + kind == WASM_INSN_F64_LE || kind == WASM_INSN_F64_GE) { + *vt = WASM_VAL_F64; + return 1; + } + return 0; +} + +static int wasm_conversion_kind(uint8_t kind, WasmValType *src, + WasmValType *dst) { + switch (kind) { + case WASM_INSN_I32_WRAP_I64: + *src = WASM_VAL_I64; *dst = WASM_VAL_I32; return 1; + case WASM_INSN_I64_EXTEND_I32_S: + case WASM_INSN_I64_EXTEND_I32_U: + *src = WASM_VAL_I32; *dst = WASM_VAL_I64; return 1; + case WASM_INSN_I32_TRUNC_F32_S: + case WASM_INSN_I32_TRUNC_F32_U: + *src = WASM_VAL_F32; *dst = WASM_VAL_I32; return 1; + case WASM_INSN_I32_TRUNC_F64_S: + case WASM_INSN_I32_TRUNC_F64_U: + *src = WASM_VAL_F64; *dst = WASM_VAL_I32; return 1; + case WASM_INSN_I64_TRUNC_F32_S: + case WASM_INSN_I64_TRUNC_F32_U: + *src = WASM_VAL_F32; *dst = WASM_VAL_I64; return 1; + case WASM_INSN_I64_TRUNC_F64_S: + case WASM_INSN_I64_TRUNC_F64_U: + *src = WASM_VAL_F64; *dst = WASM_VAL_I64; return 1; + case WASM_INSN_F32_CONVERT_I32_S: + case WASM_INSN_F32_CONVERT_I32_U: + *src = WASM_VAL_I32; *dst = WASM_VAL_F32; return 1; + case WASM_INSN_F32_CONVERT_I64_S: + case WASM_INSN_F32_CONVERT_I64_U: + *src = WASM_VAL_I64; *dst = WASM_VAL_F32; return 1; + case WASM_INSN_F64_CONVERT_I32_S: + case WASM_INSN_F64_CONVERT_I32_U: + *src = WASM_VAL_I32; *dst = WASM_VAL_F64; return 1; + case WASM_INSN_F64_CONVERT_I64_S: + case WASM_INSN_F64_CONVERT_I64_U: + *src = WASM_VAL_I64; *dst = WASM_VAL_F64; return 1; + case WASM_INSN_F32_DEMOTE_F64: + *src = WASM_VAL_F64; *dst = WASM_VAL_F32; return 1; + case WASM_INSN_F64_PROMOTE_F32: + *src = WASM_VAL_F32; *dst = WASM_VAL_F64; return 1; + case WASM_INSN_I32_REINTERPRET_F32: + *src = WASM_VAL_F32; *dst = WASM_VAL_I32; return 1; + case WASM_INSN_I64_REINTERPRET_F64: + *src = WASM_VAL_F64; *dst = WASM_VAL_I64; return 1; + case WASM_INSN_F32_REINTERPRET_I32: + *src = WASM_VAL_I32; *dst = WASM_VAL_F32; return 1; + case WASM_INSN_F64_REINTERPRET_I64: + *src = WASM_VAL_I64; *dst = WASM_VAL_F64; return 1; + default: + return 0; + } +} + +static void wasm_validate(WasmModule *m, CfreeCompiler *c) { uint32_t i, j; for (i = 0; i < m->nfuncs; ++i) { - const WasmFunc *f = &m->funcs[i]; - int32_t depth = 0; + WasmFunc *f = &m->funcs[i]; + WasmValType stack[256]; + uint32_t depth = 0; + uint32_t control_depth = 0; if (f->nresults > 1u) wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); - for (j = 0; j < f->nparams; ++j) { - if (f->params[j] != WASM_VAL_I32 && f->params[j] != WASM_VAL_I64) - wasm_error(c, wasm_loc(0, 0), - "wasm: only integer parameters are supported"); - } - for (j = 0; j < f->nlocals; ++j) { - if (f->locals[j] != WASM_VAL_I32 && f->locals[j] != WASM_VAL_I64) - wasm_error(c, wasm_loc(0, 0), - "wasm: only integer locals are supported"); - } for (j = 0; j < f->ninsns; ++j) { - WasmInsn in = f->insns[j]; - switch (in.kind) { + WasmInsn *in = &f->insns[j]; + WasmValType vt, src, dst; + switch (in->kind) { + case WASM_INSN_F32_CONST: + if (depth >= 256u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); + stack[depth++] = WASM_VAL_F32; + break; + case WASM_INSN_F64_CONST: + if (depth >= 256u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); + stack[depth++] = WASM_VAL_F64; + break; case WASM_INSN_I32_CONST: + if (depth >= 256u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); + stack[depth++] = WASM_VAL_I32; + break; case WASM_INSN_I64_CONST: + if (depth >= 256u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); + stack[depth++] = WASM_VAL_I64; + break; case WASM_INSN_LOCAL_GET: - if (in.kind == WASM_INSN_LOCAL_GET && - (uint64_t)in.imm >= (uint64_t)f->nparams + f->nlocals) + if (in->kind == WASM_INSN_LOCAL_GET && + (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); - depth++; + if (depth >= 256u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); + stack[depth++] = wasm_func_local_type(f, (uint32_t)in->imm); break; case WASM_INSN_LOCAL_SET: - if (in.imm < 0 || (uint64_t)in.imm >= + if (in->imm < 0 || (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); if (depth < 1) wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 1u] != wasm_func_local_type(f, (uint32_t)in->imm)) + wasm_error(c, wasm_loc(0, 0), "wasm: local type mismatch"); depth--; break; case WASM_INSN_LOCAL_TEE: - if (in.imm < 0 || (uint64_t)in.imm >= + if (in->imm < 0 || (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); if (depth < 1) wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 1u] != wasm_func_local_type(f, (uint32_t)in->imm)) + wasm_error(c, wasm_loc(0, 0), "wasm: local type mismatch"); break; case WASM_INSN_CALL: - if (in.imm < 0 || (uint64_t)in.imm >= m->nfuncs) + if (in->imm < 0 || (uint64_t)in->imm >= m->nfuncs) wasm_error(c, wasm_loc(0, 0), "wasm: call index out of range"); - if (depth < (int32_t)m->funcs[in.imm].nparams) + if (depth < m->funcs[in->imm].nparams) wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - depth -= (int32_t)m->funcs[in.imm].nparams; - if (m->funcs[in.imm].nresults) - depth++; + for (uint32_t k = 0; k < m->funcs[in->imm].nparams; ++k) { + uint32_t param = m->funcs[in->imm].nparams - 1u - k; + if (stack[depth - 1u - k] != m->funcs[in->imm].params[param]) + wasm_error(c, wasm_loc(0, 0), "wasm: call argument type mismatch"); + } + depth -= m->funcs[in->imm].nparams; + if (m->funcs[in->imm].nresults) + stack[depth++] = m->funcs[in->imm].results[0]; break; case WASM_INSN_RETURN: if (depth < (f->nresults ? 1 : 0)) wasm_error(c, wasm_loc(0, 0), "wasm: return stack underflow"); + if (f->nresults && stack[depth - 1u] != f->results[0]) + wasm_error(c, wasm_loc(0, 0), "wasm: return type mismatch"); break; case WASM_INSN_DROP: if (depth < 1) @@ -1338,16 +2967,159 @@ static void wasm_validate(const WasmModule *m, CfreeCompiler *c) { case WASM_INSN_I64_EQZ: if (depth < 1) wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if ((in->kind == WASM_INSN_I32_EQZ && stack[depth - 1u] != WASM_VAL_I32) || + (in->kind == WASM_INSN_I64_EQZ && stack[depth - 1u] != WASM_VAL_I64)) + wasm_error(c, wasm_loc(0, 0), "wasm: eqz type mismatch"); + stack[depth - 1u] = WASM_VAL_I32; + break; + case WASM_INSN_BLOCK: + case WASM_INSN_LOOP: + control_depth++; + break; + case WASM_INSN_IF: + if (depth < 1) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 1u] != WASM_VAL_I32) + wasm_error(c, wasm_loc(0, 0), "wasm: if condition must be i32"); + depth--; + control_depth++; + break; + case WASM_INSN_ELSE: + if (!control_depth) + wasm_error(c, wasm_loc(0, 0), "wasm: else without block"); + break; + case WASM_INSN_END: + if (!control_depth) + wasm_error(c, wasm_loc(0, 0), "wasm: end without block"); + control_depth--; + break; + case WASM_INSN_BR: + if (in->imm < 0 || (uint64_t)in->imm >= control_depth) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + break; + case WASM_INSN_BR_IF: + if (in->imm < 0 || (uint64_t)in->imm >= control_depth) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + if (depth < 1) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 1u] != WASM_VAL_I32) + wasm_error(c, wasm_loc(0, 0), "wasm: br_if condition must be i32"); + depth--; + break; + case WASM_INSN_BR_TABLE: + if (in->ntargets == 0) + wasm_error(c, wasm_loc(0, 0), "wasm: br_table without targets"); + if (depth < 1 || stack[depth - 1u] != WASM_VAL_I32) + wasm_error(c, wasm_loc(0, 0), "wasm: br_table selector must be i32"); + for (uint32_t k = 0; k < in->ntargets; ++k) + if (in->targets[k] >= control_depth) + wasm_error(c, wasm_loc(0, 0), "wasm: br_table depth out of range"); + depth--; + break; + case WASM_INSN_SELECT: + if (depth < 3) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 1u] != WASM_VAL_I32 || + stack[depth - 2u] != stack[depth - 3u]) + wasm_error(c, wasm_loc(0, 0), "wasm: select type mismatch"); + in->type = (uint8_t)stack[depth - 3u]; + depth -= 2u; + break; + case WASM_INSN_MEMORY_SIZE: + if (!m->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: memory.size without memory"); + stack[depth++] = WASM_VAL_I32; + break; + case WASM_INSN_MEMORY_GROW: + if (!m->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: memory.grow without memory"); + if (depth < 1 || stack[depth - 1u] != WASM_VAL_I32) + wasm_error(c, wasm_loc(0, 0), "wasm: memory.grow expects i32"); + stack[depth - 1u] = WASM_VAL_I32; + break; + case WASM_INSN_I32_LOAD: + case WASM_INSN_I64_LOAD: + case WASM_INSN_I32_LOAD8_S: + case WASM_INSN_I32_LOAD8_U: + case WASM_INSN_I32_LOAD16_S: + case WASM_INSN_I32_LOAD16_U: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + if (!m->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: load without memory"); + if (depth < 1 || stack[depth - 1u] != WASM_VAL_I32) + wasm_error(c, wasm_loc(0, 0), "wasm: load address must be i32"); + stack[depth - 1u] = wasm_load_result_type(in->kind); + break; + case WASM_INSN_I32_STORE: + case WASM_INSN_I64_STORE: + case WASM_INSN_I32_STORE8: + case WASM_INSN_I32_STORE16: + case WASM_INSN_I64_STORE8: + case WASM_INSN_I64_STORE16: + case WASM_INSN_I64_STORE32: + if (!m->has_memory) + wasm_error(c, wasm_loc(0, 0), "wasm: store without memory"); + if (depth < 2) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 2u] != WASM_VAL_I32 || + stack[depth - 1u] != wasm_store_value_type(in->kind)) + wasm_error(c, wasm_loc(0, 0), "wasm: store type mismatch"); + depth -= 2u; + break; + case WASM_INSN_UNREACHABLE: + case WASM_INSN_NOP: break; default: + if (wasm_int_unop_kind(in->kind, &vt)) { + if (depth < 1 || stack[depth - 1u] != vt) + wasm_error(c, wasm_loc(0, 0), "wasm: unary operand type mismatch"); + break; + } + if (wasm_fp_binop_kind(in->kind, &vt)) { + if (depth < 2 || stack[depth - 1u] != vt || + stack[depth - 2u] != vt) + wasm_error(c, wasm_loc(0, 0), "wasm: fp operand type mismatch"); + depth--; + break; + } + if (wasm_fp_cmp_kind(in->kind, &vt)) { + if (depth < 2 || stack[depth - 1u] != vt || + stack[depth - 2u] != vt) + wasm_error(c, wasm_loc(0, 0), "wasm: fp compare type mismatch"); + stack[depth - 2u] = WASM_VAL_I32; + depth--; + break; + } + if (wasm_conversion_kind(in->kind, &src, &dst)) { + if (depth < 1 || stack[depth - 1u] != src) + wasm_error(c, wasm_loc(0, 0), "wasm: conversion type mismatch"); + stack[depth - 1u] = dst; + break; + } + { + CfreeCgIntCmpOp cmp; if (depth < 2) wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + if (stack[depth - 1u] != stack[depth - 2u]) + wasm_error(c, wasm_loc(0, 0), "wasm: operand type mismatch"); + if (wasm_int_cmp_op(in->kind, &cmp)) + stack[depth - 2u] = WASM_VAL_I32; depth--; break; + } } } + if (control_depth) + wasm_error(c, wasm_loc(0, 0), "wasm: unterminated control block"); if (f->nresults && depth < 1) wasm_error(c, wasm_loc(0, 0), "wasm: missing function result"); + if (f->nresults && stack[depth - 1u] != f->results[0]) + wasm_error(c, wasm_loc(0, 0), "wasm: function result type mismatch"); } } @@ -1358,6 +3130,7 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, CfreeCgSym syms[64]; CfreeCgTypeId func_types[64]; CfreeCgLocal locals[48]; + CfreeCgSym memory_sym = CFREE_CG_SYM_NONE; uint32_t i, j; if (!cg) wasm_error(c, wasm_loc(0, 0), "wasm: failed to initialize codegen"); @@ -1365,6 +3138,29 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); memset(syms, 0, sizeof syms); memset(func_types, 0, sizeof func_types); + if (m->has_memory) { + CfreeCgDecl decl; + CfreeCgDataDefAttrs attrs; + CfreeCgTypeId mem_ty = + cfree_cg_type_array(c, b.id[CFREE_CG_BUILTIN_I8], + m->memory.data_len ? m->memory.data_len : 1u); + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = cfree_sym_intern(c, "__cfree_wasm_memory"); + decl.display_name = decl.linkage_name; + decl.type = mem_ty; + decl.sym.bind = CFREE_SB_LOCAL; + decl.as.object.align = 16; + memory_sym = cfree_cg_decl(cg, decl); + memset(&attrs, 0, sizeof attrs); + attrs.align = 16; + cfree_cg_data_begin(cg, memory_sym, attrs); + if (m->memory.data_len) + cfree_cg_data_bytes(cg, m->memory.data, m->memory.data_len); + else + cfree_cg_data_zero(cg, 1); + cfree_cg_data_end(cg); + } for (i = 0; i < m->nfuncs; ++i) { const WasmFunc *f = &m->funcs[i]; CfreeCgFuncParam cg_params[16]; @@ -1414,6 +3210,14 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, } for (i = 0; i < m->nfuncs; ++i) { const WasmFunc *f = &m->funcs[i]; + struct { + uint8_t kind; + int seen_else; + CfreeCgLabel start; + CfreeCgLabel end; + CfreeCgLabel else_label; + } control[64]; + uint32_t ncontrol = 0; cfree_cg_func_begin(cg, syms[i]); for (j = 0; j < f->nparams; ++j) { CfreeCgLocalAttrs attrs; @@ -1431,6 +3235,151 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, for (j = 0; j < f->ninsns; ++j) { WasmInsn in = f->insns[j]; switch (in.kind) { + case WASM_INSN_UNREACHABLE: + cfree_cg_unreachable(cg); + break; + case WASM_INSN_NOP: + break; + case WASM_INSN_BLOCK: + if (ncontrol >= 64u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + memset(&control[ncontrol], 0, sizeof control[ncontrol]); + control[ncontrol].kind = WASM_INSN_BLOCK; + control[ncontrol].end = cfree_cg_label_new(cg); + ncontrol++; + break; + case WASM_INSN_LOOP: + if (ncontrol >= 64u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + memset(&control[ncontrol], 0, sizeof control[ncontrol]); + control[ncontrol].kind = WASM_INSN_LOOP; + control[ncontrol].start = cfree_cg_label_new(cg); + control[ncontrol].end = cfree_cg_label_new(cg); + cfree_cg_label_place(cg, control[ncontrol].start); + ncontrol++; + break; + case WASM_INSN_IF: + if (ncontrol >= 64u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + memset(&control[ncontrol], 0, sizeof control[ncontrol]); + control[ncontrol].kind = WASM_INSN_IF; + control[ncontrol].else_label = cfree_cg_label_new(cg); + control[ncontrol].end = cfree_cg_label_new(cg); + cfree_cg_branch_false(cg, control[ncontrol].else_label); + ncontrol++; + break; + case WASM_INSN_ELSE: + if (!ncontrol || control[ncontrol - 1u].kind != WASM_INSN_IF) + wasm_error(c, wasm_loc(0, 0), "wasm: else without if"); + cfree_cg_jump(cg, control[ncontrol - 1u].end); + cfree_cg_label_place(cg, control[ncontrol - 1u].else_label); + control[ncontrol - 1u].seen_else = 1; + break; + case WASM_INSN_END: + if (!ncontrol) + wasm_error(c, wasm_loc(0, 0), "wasm: end without block"); + ncontrol--; + if (control[ncontrol].kind == WASM_INSN_IF && + !control[ncontrol].seen_else) + cfree_cg_label_place(cg, control[ncontrol].else_label); + cfree_cg_label_place(cg, control[ncontrol].end); + break; + case WASM_INSN_BR: { + uint32_t depth = (uint32_t)in.imm; + uint32_t idx; + if (depth >= ncontrol) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + idx = ncontrol - 1u - depth; + cfree_cg_jump(cg, control[idx].kind == WASM_INSN_LOOP + ? control[idx].start + : control[idx].end); + break; + } + case WASM_INSN_BR_IF: { + uint32_t depth = (uint32_t)in.imm; + uint32_t idx; + if (depth >= ncontrol) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + idx = ncontrol - 1u - depth; + cfree_cg_branch_true(cg, control[idx].kind == WASM_INSN_LOOP + ? control[idx].start + : control[idx].end); + break; + } + case WASM_INSN_BR_TABLE: { + CfreeCgSwitchCase cases[15]; + CfreeCgSwitch sw; + memset(cases, 0, sizeof cases); + if (in.ntargets == 0 || in.ntargets > 16u) + wasm_error(c, wasm_loc(0, 0), "wasm: bad br_table target count"); + for (uint32_t k = 0; k + 1u < in.ntargets; ++k) { + uint32_t idx; + if (in.targets[k] >= ncontrol) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + idx = ncontrol - 1u - in.targets[k]; + cases[k].value = k; + cases[k].label = control[idx].kind == WASM_INSN_LOOP + ? control[idx].start + : control[idx].end; + } + if (in.targets[in.ntargets - 1u] >= ncontrol) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + memset(&sw, 0, sizeof sw); + sw.selector_type = b.id[CFREE_CG_BUILTIN_I32]; + sw.cases = cases; + sw.ncases = in.ntargets - 1u; + sw.default_label = + control[ncontrol - 1u - in.targets[in.ntargets - 1u]].kind == + WASM_INSN_LOOP + ? control[ncontrol - 1u - in.targets[in.ntargets - 1u]].start + : control[ncontrol - 1u - in.targets[in.ntargets - 1u]].end; + sw.hint = CFREE_CG_SWITCH_BRANCH_CHAIN; + cfree_cg_switch(cg, sw); + break; + } + case WASM_INSN_SELECT: + { + CfreeCgTypeId ty = wasm_cg_type(c, b, (WasmValType)in.type); + CfreeCgMemAccess mem = wasm_cg_mem(c, b, (WasmValType)in.type); + CfreeCgLocalAttrs attrs; + CfreeCgLocal lhs, rhs, cond, result; + CfreeCgLabel else_label = cfree_cg_label_new(cg); + CfreeCgLabel end_label = cfree_cg_label_new(cg); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + lhs = cfree_cg_local(cg, ty, attrs); + rhs = cfree_cg_local(cg, ty, attrs); + result = cfree_cg_local(cg, ty, attrs); + cond = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + + cfree_cg_push_local(cg, cond); + cfree_cg_swap(cg); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_local(cg, rhs); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + cfree_cg_push_local(cg, lhs); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + + cfree_cg_push_local(cg, cond); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_branch_false(cg, else_label); + cfree_cg_push_local(cg, result); + cfree_cg_push_local(cg, lhs); + cfree_cg_load(cg, mem); + cfree_cg_store(cg, mem); + cfree_cg_jump(cg, end_label); + cfree_cg_label_place(cg, else_label); + cfree_cg_push_local(cg, result); + cfree_cg_push_local(cg, rhs); + cfree_cg_load(cg, mem); + cfree_cg_store(cg, mem); + cfree_cg_label_place(cg, end_label); + cfree_cg_push_local(cg, result); + cfree_cg_load(cg, mem); + } + break; case WASM_INSN_I32_CONST: cfree_cg_push_int(cg, (uint64_t)(uint32_t)in.imm, b.id[CFREE_CG_BUILTIN_I32]); @@ -1438,6 +3387,12 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, case WASM_INSN_I64_CONST: cfree_cg_push_int(cg, (uint64_t)in.imm, b.id[CFREE_CG_BUILTIN_I64]); break; + case WASM_INSN_F32_CONST: + cfree_cg_push_float(cg, in.fp, b.id[CFREE_CG_BUILTIN_F32]); + break; + case WASM_INSN_F64_CONST: + cfree_cg_push_float(cg, in.fp, b.id[CFREE_CG_BUILTIN_F64]); + break; case WASM_INSN_LOCAL_GET: { uint32_t index = (uint32_t)in.imm; cfree_cg_push_local(cg, locals[index]); @@ -1475,6 +3430,88 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, case WASM_INSN_DROP: cfree_cg_drop(cg); break; + case WASM_INSN_MEMORY_SIZE: + cfree_cg_push_int(cg, m->memory.min_pages, b.id[CFREE_CG_BUILTIN_I32]); + break; + case WASM_INSN_MEMORY_GROW: + cfree_cg_drop(cg); + cfree_cg_push_int(cg, UINT64_C(0xffffffff), + b.id[CFREE_CG_BUILTIN_I32]); + break; + case WASM_INSN_I32_LOAD: + case WASM_INSN_I64_LOAD: + case WASM_INSN_I32_LOAD8_S: + case WASM_INSN_I32_LOAD8_U: + case WASM_INSN_I32_LOAD16_S: + case WASM_INSN_I32_LOAD16_U: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: { + CfreeCgTypeId storage = wasm_load_storage_type(b, in.kind); + CfreeCgTypeId result = + wasm_cg_type(c, b, wasm_load_result_type(in.kind)); + CfreeCgMemAccess mem; + memset(&mem, 0, sizeof mem); + mem.type = storage; + mem.align = in.align; + wasm_cg_memory_check(c, cg, b, m, &in); + wasm_cg_memory_lvalue(cg, memory_sym, (uint32_t)in.imm); + cfree_cg_load(cg, mem); + if (storage != result) { + if (in.kind == WASM_INSN_I32_LOAD8_S || + in.kind == WASM_INSN_I32_LOAD16_S || + in.kind == WASM_INSN_I64_LOAD8_S || + in.kind == WASM_INSN_I64_LOAD16_S || + in.kind == WASM_INSN_I64_LOAD32_S) + cfree_cg_sext(cg, result); + else + cfree_cg_zext(cg, result); + } + break; + } + case WASM_INSN_I32_STORE: + case WASM_INSN_I64_STORE: + case WASM_INSN_I32_STORE8: + case WASM_INSN_I32_STORE16: + case WASM_INSN_I64_STORE8: + case WASM_INSN_I64_STORE16: + case WASM_INSN_I64_STORE32: { + CfreeCgTypeId storage = wasm_store_storage_type(b, in.kind); + CfreeCgTypeId value_type = + wasm_cg_type(c, b, wasm_store_value_type(in.kind)); + CfreeCgMemAccess mem; + CfreeCgLocalAttrs attrs; + CfreeCgLocal addr_tmp, value_tmp; + memset(&mem, 0, sizeof mem); + mem.type = storage; + mem.align = in.align; + if (storage != value_type) + cfree_cg_trunc(cg, storage); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + value_tmp = cfree_cg_local(cg, storage, attrs); + addr_tmp = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + cfree_cg_push_local(cg, value_tmp); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + wasm_cg_memory_check(c, cg, b, m, &in); + cfree_cg_push_local(cg, addr_tmp); + cfree_cg_swap(cg); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_local(cg, addr_tmp); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_local(cg, value_tmp); + cfree_cg_load(cg, mem); + cfree_cg_push_symbol_lvalue(cg, memory_sym, 0); + cfree_cg_rot3(cg); + cfree_cg_index(cg, (uint32_t)in.imm); + cfree_cg_swap(cg); + cfree_cg_store(cg, mem); + break; + } case WASM_INSN_I32_ADD: case WASM_INSN_I64_ADD: cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); @@ -1488,16 +3525,28 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, cfree_cg_int_binop(cg, CFREE_CG_INT_MUL, 0); break; case WASM_INSN_I32_DIV_S: - cfree_cg_int_binop(cg, CFREE_CG_INT_SDIV, 0); + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I32, CFREE_CG_INT_SDIV); break; case WASM_INSN_I32_DIV_U: - cfree_cg_int_binop(cg, CFREE_CG_INT_UDIV, 0); + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I32, CFREE_CG_INT_UDIV); break; case WASM_INSN_I32_REM_S: - cfree_cg_int_binop(cg, CFREE_CG_INT_SREM, 0); + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I32, CFREE_CG_INT_SREM); break; case WASM_INSN_I32_REM_U: - cfree_cg_int_binop(cg, CFREE_CG_INT_UREM, 0); + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I32, CFREE_CG_INT_UREM); + break; + case WASM_INSN_I64_DIV_S: + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I64, CFREE_CG_INT_SDIV); + break; + case WASM_INSN_I64_DIV_U: + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I64, CFREE_CG_INT_UDIV); + break; + case WASM_INSN_I64_REM_S: + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I64, CFREE_CG_INT_SREM); + break; + case WASM_INSN_I64_REM_U: + wasm_cg_checked_divrem(c, cg, b, WASM_VAL_I64, CFREE_CG_INT_UREM); break; case WASM_INSN_I32_AND: case WASM_INSN_I64_AND: @@ -1523,6 +3572,39 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, case WASM_INSN_I64_SHR_U: cfree_cg_int_binop(cg, CFREE_CG_INT_LSHR, 0); break; + case WASM_INSN_I32_ROTL: + wasm_cg_rotate(c, cg, b, WASM_VAL_I32, 0); + break; + case WASM_INSN_I32_ROTR: + wasm_cg_rotate(c, cg, b, WASM_VAL_I32, 1); + break; + case WASM_INSN_I64_ROTL: + wasm_cg_rotate(c, cg, b, WASM_VAL_I64, 0); + break; + case WASM_INSN_I64_ROTR: + wasm_cg_rotate(c, cg, b, WASM_VAL_I64, 1); + break; + case WASM_INSN_I32_CLZ: + case WASM_INSN_I64_CLZ: + cfree_cg_intrinsic(cg, CFREE_CG_INTRIN_CLZ, 1, + in.kind == WASM_INSN_I32_CLZ + ? b.id[CFREE_CG_BUILTIN_I32] + : b.id[CFREE_CG_BUILTIN_I64]); + break; + case WASM_INSN_I32_CTZ: + case WASM_INSN_I64_CTZ: + cfree_cg_intrinsic(cg, CFREE_CG_INTRIN_CTZ, 1, + in.kind == WASM_INSN_I32_CTZ + ? b.id[CFREE_CG_BUILTIN_I32] + : b.id[CFREE_CG_BUILTIN_I64]); + break; + case WASM_INSN_I32_POPCNT: + case WASM_INSN_I64_POPCNT: + cfree_cg_intrinsic(cg, CFREE_CG_INTRIN_POPCOUNT, 1, + in.kind == WASM_INSN_I32_POPCNT + ? b.id[CFREE_CG_BUILTIN_I32] + : b.id[CFREE_CG_BUILTIN_I64]); + break; case WASM_INSN_I32_EQZ: cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I32]); cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); @@ -1533,12 +3615,88 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I32]); break; + case WASM_INSN_I32_WRAP_I64: + cfree_cg_trunc(cg, b.id[CFREE_CG_BUILTIN_I32]); + break; + case WASM_INSN_I64_EXTEND_I32_S: + cfree_cg_sext(cg, b.id[CFREE_CG_BUILTIN_I64]); + break; + case WASM_INSN_I64_EXTEND_I32_U: + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + break; + case WASM_INSN_I32_TRUNC_F32_S: + case WASM_INSN_I32_TRUNC_F64_S: + cfree_cg_float_to_sint(cg, b.id[CFREE_CG_BUILTIN_I32], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_I32_TRUNC_F32_U: + case WASM_INSN_I32_TRUNC_F64_U: + cfree_cg_float_to_uint(cg, b.id[CFREE_CG_BUILTIN_I32], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_I64_TRUNC_F32_S: + case WASM_INSN_I64_TRUNC_F64_S: + cfree_cg_float_to_sint(cg, b.id[CFREE_CG_BUILTIN_I64], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_I64_TRUNC_F32_U: + case WASM_INSN_I64_TRUNC_F64_U: + cfree_cg_float_to_uint(cg, b.id[CFREE_CG_BUILTIN_I64], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_F32_CONVERT_I32_S: + case WASM_INSN_F32_CONVERT_I64_S: + cfree_cg_sint_to_float(cg, b.id[CFREE_CG_BUILTIN_F32], + CFREE_CG_ROUND_DEFAULT); + break; + case WASM_INSN_F32_CONVERT_I32_U: + case WASM_INSN_F32_CONVERT_I64_U: + cfree_cg_uint_to_float(cg, b.id[CFREE_CG_BUILTIN_F32], + CFREE_CG_ROUND_DEFAULT); + break; + case WASM_INSN_F64_CONVERT_I32_S: + case WASM_INSN_F64_CONVERT_I64_S: + cfree_cg_sint_to_float(cg, b.id[CFREE_CG_BUILTIN_F64], + CFREE_CG_ROUND_DEFAULT); + break; + case WASM_INSN_F64_CONVERT_I32_U: + case WASM_INSN_F64_CONVERT_I64_U: + cfree_cg_uint_to_float(cg, b.id[CFREE_CG_BUILTIN_F64], + CFREE_CG_ROUND_DEFAULT); + break; + case WASM_INSN_F32_DEMOTE_F64: + cfree_cg_fptrunc(cg, b.id[CFREE_CG_BUILTIN_F32]); + break; + case WASM_INSN_F64_PROMOTE_F32: + cfree_cg_fpext(cg, b.id[CFREE_CG_BUILTIN_F64]); + break; + case WASM_INSN_I32_REINTERPRET_F32: + cfree_cg_bitcast(cg, b.id[CFREE_CG_BUILTIN_I32]); + break; + case WASM_INSN_I64_REINTERPRET_F64: + cfree_cg_bitcast(cg, b.id[CFREE_CG_BUILTIN_I64]); + break; + case WASM_INSN_F32_REINTERPRET_I32: + cfree_cg_bitcast(cg, b.id[CFREE_CG_BUILTIN_F32]); + break; + case WASM_INSN_F64_REINTERPRET_I64: + cfree_cg_bitcast(cg, b.id[CFREE_CG_BUILTIN_F64]); + break; default: { CfreeCgIntCmpOp cmp; - if (!wasm_int_cmp_op(in.kind, &cmp)) + CfreeCgFpBinOp fp_bin; + CfreeCgFpCmpOp fp_cmp; + 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 (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)) { + cfree_cg_fp_cmp(cg, fp_cmp); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I32]); + } else { wasm_error(c, wasm_loc(0, 0), "wasm: unsupported instruction"); - cfree_cg_int_cmp(cg, cmp); - cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I32]); + } break; } } @@ -1578,6 +3736,21 @@ static void write_sleb(CfreeWriter *w, int64_t v) { } } +static void write_f32(CfreeWriter *w, double value) { + float f = (float)value; + uint32_t bits; + memcpy(&bits, &f, sizeof bits); + for (uint32_t i = 0; i < 4u; ++i) + write_byte(w, (uint8_t)(bits >> (i * 8u))); +} + +static void write_f64(CfreeWriter *w, double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof bits); + for (uint32_t i = 0; i < 8u; ++i) + write_byte(w, (uint8_t)(bits >> (i * 8u))); +} + static void write_name(CfreeWriter *w, const char *s) { size_t n = strlen(s); write_uleb(w, n); @@ -1637,12 +3810,48 @@ static void enc_export(CfreeWriter *w, const WasmModule *m) { } } +static void enc_memory(CfreeWriter *w, const WasmModule *m) { + if (!m->has_memory) { + write_uleb(w, 0); + return; + } + write_uleb(w, 1); + write_byte(w, m->memory.has_max ? 1 : 0); + write_uleb(w, m->memory.min_pages); + if (m->memory.has_max) + write_uleb(w, m->memory.max_pages); +} + static uint8_t wasm_opcode(uint8_t kind) { switch (kind) { + case WASM_INSN_UNREACHABLE: + return 0x00; + case WASM_INSN_NOP: + return 0x01; + case WASM_INSN_BLOCK: + return 0x02; + case WASM_INSN_LOOP: + return 0x03; + case WASM_INSN_IF: + return 0x04; + case WASM_INSN_ELSE: + return 0x05; + case WASM_INSN_END: + return 0x0b; + case WASM_INSN_BR: + return 0x0c; + case WASM_INSN_BR_IF: + return 0x0d; + case WASM_INSN_BR_TABLE: + return 0x0e; case WASM_INSN_I32_CONST: return 0x41; case WASM_INSN_I64_CONST: return 0x42; + case WASM_INSN_F32_CONST: + return 0x43; + case WASM_INSN_F64_CONST: + return 0x44; case WASM_INSN_LOCAL_GET: return 0x20; case WASM_INSN_LOCAL_SET: @@ -1655,6 +3864,50 @@ static uint8_t wasm_opcode(uint8_t kind) { return 0x0f; case WASM_INSN_DROP: return 0x1a; + case WASM_INSN_SELECT: + return 0x1b; + case WASM_INSN_I32_LOAD: + return 0x28; + case WASM_INSN_I64_LOAD: + return 0x29; + case WASM_INSN_I32_LOAD8_S: + return 0x2c; + case WASM_INSN_I32_LOAD8_U: + return 0x2d; + case WASM_INSN_I32_LOAD16_S: + return 0x2e; + case WASM_INSN_I32_LOAD16_U: + return 0x2f; + case WASM_INSN_I64_LOAD8_S: + return 0x30; + case WASM_INSN_I64_LOAD8_U: + return 0x31; + case WASM_INSN_I64_LOAD16_S: + return 0x32; + case WASM_INSN_I64_LOAD16_U: + return 0x33; + case WASM_INSN_I64_LOAD32_S: + return 0x34; + case WASM_INSN_I64_LOAD32_U: + return 0x35; + case WASM_INSN_I32_STORE: + return 0x36; + case WASM_INSN_I64_STORE: + return 0x37; + case WASM_INSN_I32_STORE8: + return 0x3a; + case WASM_INSN_I32_STORE16: + return 0x3b; + case WASM_INSN_I64_STORE8: + return 0x3c; + case WASM_INSN_I64_STORE16: + return 0x3d; + case WASM_INSN_I64_STORE32: + return 0x3e; + case WASM_INSN_MEMORY_SIZE: + return 0x3f; + case WASM_INSN_MEMORY_GROW: + return 0x40; case WASM_INSN_I32_EQZ: return 0x45; case WASM_INSN_I32_EQ: @@ -1725,12 +3978,36 @@ static uint8_t wasm_opcode(uint8_t kind) { return 0x75; case WASM_INSN_I32_SHR_U: return 0x76; + case WASM_INSN_I32_CLZ: + return 0x67; + case WASM_INSN_I32_CTZ: + return 0x68; + case WASM_INSN_I32_POPCNT: + return 0x69; + case WASM_INSN_I32_ROTL: + return 0x77; + case WASM_INSN_I32_ROTR: + return 0x78; + case WASM_INSN_I64_CLZ: + return 0x79; + case WASM_INSN_I64_CTZ: + return 0x7a; + case WASM_INSN_I64_POPCNT: + return 0x7b; case WASM_INSN_I64_ADD: return 0x7c; case WASM_INSN_I64_SUB: return 0x7d; case WASM_INSN_I64_MUL: return 0x7e; + case WASM_INSN_I64_DIV_S: + return 0x7f; + case WASM_INSN_I64_DIV_U: + return 0x80; + case WASM_INSN_I64_REM_S: + return 0x81; + case WASM_INSN_I64_REM_U: + return 0x82; case WASM_INSN_I64_AND: return 0x83; case WASM_INSN_I64_OR: @@ -1743,6 +4020,100 @@ static uint8_t wasm_opcode(uint8_t kind) { return 0x87; case WASM_INSN_I64_SHR_U: return 0x88; + case WASM_INSN_I64_ROTL: + return 0x89; + case WASM_INSN_I64_ROTR: + return 0x8a; + case WASM_INSN_F32_ADD: + return 0x92; + case WASM_INSN_F32_SUB: + return 0x93; + case WASM_INSN_F32_MUL: + return 0x94; + case WASM_INSN_F32_DIV: + return 0x95; + case WASM_INSN_F64_ADD: + return 0xa0; + case WASM_INSN_F64_SUB: + return 0xa1; + case WASM_INSN_F64_MUL: + return 0xa2; + case WASM_INSN_F64_DIV: + return 0xa3; + case WASM_INSN_F32_EQ: + return 0x5b; + case WASM_INSN_F32_NE: + return 0x5c; + case WASM_INSN_F32_LT: + return 0x5d; + case WASM_INSN_F32_GT: + return 0x5e; + case WASM_INSN_F32_LE: + return 0x5f; + case WASM_INSN_F32_GE: + return 0x60; + case WASM_INSN_F64_EQ: + return 0x61; + case WASM_INSN_F64_NE: + return 0x62; + case WASM_INSN_F64_LT: + return 0x63; + case WASM_INSN_F64_GT: + return 0x64; + case WASM_INSN_F64_LE: + return 0x65; + case WASM_INSN_F64_GE: + return 0x66; + case WASM_INSN_I32_WRAP_I64: + return 0xa7; + case WASM_INSN_I32_TRUNC_F32_S: + return 0xa8; + case WASM_INSN_I32_TRUNC_F32_U: + return 0xa9; + case WASM_INSN_I32_TRUNC_F64_S: + return 0xaa; + case WASM_INSN_I32_TRUNC_F64_U: + return 0xab; + case WASM_INSN_I64_EXTEND_I32_S: + return 0xac; + case WASM_INSN_I64_EXTEND_I32_U: + return 0xad; + case WASM_INSN_I64_TRUNC_F32_S: + return 0xae; + case WASM_INSN_I64_TRUNC_F32_U: + return 0xaf; + case WASM_INSN_I64_TRUNC_F64_S: + return 0xb0; + case WASM_INSN_I64_TRUNC_F64_U: + return 0xb1; + case WASM_INSN_F32_CONVERT_I32_S: + return 0xb2; + case WASM_INSN_F32_CONVERT_I32_U: + return 0xb3; + case WASM_INSN_F32_CONVERT_I64_S: + return 0xb4; + case WASM_INSN_F32_CONVERT_I64_U: + return 0xb5; + case WASM_INSN_F32_DEMOTE_F64: + return 0xb6; + case WASM_INSN_F64_CONVERT_I32_S: + return 0xb7; + case WASM_INSN_F64_CONVERT_I32_U: + return 0xb8; + case WASM_INSN_F64_CONVERT_I64_S: + return 0xb9; + case WASM_INSN_F64_CONVERT_I64_U: + return 0xba; + case WASM_INSN_F64_PROMOTE_F32: + return 0xbb; + case WASM_INSN_I32_REINTERPRET_F32: + return 0xbc; + case WASM_INSN_I64_REINTERPRET_F64: + return 0xbd; + case WASM_INSN_F32_REINTERPRET_I32: + return 0xbe; + case WASM_INSN_F64_REINTERPRET_I64: + return 0xbf; } return 0; } @@ -1780,13 +4151,34 @@ static void enc_code(CfreeWriter *w, const WasmModule *m) { WasmInsn in = m->funcs[i].insns[j]; uint8_t op = wasm_opcode(in.kind); write_byte(body, op); - if (in.kind == WASM_INSN_I32_CONST || in.kind == WASM_INSN_I64_CONST) + if (in.kind == WASM_INSN_BLOCK || in.kind == WASM_INSN_LOOP || + in.kind == WASM_INSN_IF) + write_byte(body, 0x40); + else if (in.kind == WASM_INSN_I32_CONST || in.kind == WASM_INSN_I64_CONST) write_sleb(body, in.imm); + else if (in.kind == WASM_INSN_F32_CONST) + write_f32(body, in.fp); + else if (in.kind == WASM_INSN_F64_CONST) + write_f64(body, in.fp); else if (in.kind == WASM_INSN_LOCAL_GET || in.kind == WASM_INSN_LOCAL_SET || in.kind == WASM_INSN_LOCAL_TEE || - in.kind == WASM_INSN_CALL) + in.kind == WASM_INSN_CALL || + in.kind == WASM_INSN_BR || + in.kind == WASM_INSN_BR_IF) + write_uleb(body, (uint64_t)in.imm); + else if (in.kind == WASM_INSN_BR_TABLE) { + write_uleb(body, in.ntargets ? in.ntargets - 1u : 0u); + for (uint32_t k = 0; k < in.ntargets; ++k) + write_uleb(body, in.targets[k]); + } + else if (wasm_insn_is_mem(in.kind)) { + write_uleb(body, in.align); write_uleb(body, (uint64_t)in.imm); + } else if (in.kind == WASM_INSN_MEMORY_SIZE || + in.kind == WASM_INSN_MEMORY_GROW) { + write_byte(body, 0); + } } write_byte(body, 0x0b); bytes = cfree_writer_mem_bytes(body, &len); @@ -1796,6 +4188,20 @@ static void enc_code(CfreeWriter *w, const WasmModule *m) { } } +static void enc_data(CfreeWriter *w, const WasmModule *m) { + if (!m->has_memory || !m->memory.data_len) { + write_uleb(w, 0); + return; + } + write_uleb(w, 1); + write_uleb(w, 0); + write_byte(w, 0x41); + write_sleb(w, 0); + write_byte(w, 0x0b); + write_uleb(w, m->memory.data_len); + w->write(w, m->memory.data, m->memory.data_len); +} + static void wasm_encode(CfreeCompiler *c, const WasmModule *m, CfreeWriter *out) { static const uint8_t magic[] = {0x00, 0x61, 0x73, 0x6d, @@ -1804,8 +4210,12 @@ static void wasm_encode(CfreeCompiler *c, const WasmModule *m, out->write(out, magic, sizeof magic); encode_section(h, out, 1, enc_type, m); encode_section(h, out, 3, enc_func, m); + if (m->has_memory) + encode_section(h, out, 5, enc_memory, m); encode_section(h, out, 7, enc_export, m); encode_section(h, out, 10, enc_code, m); + if (m->has_memory) + encode_section(h, out, 11, enc_data, m); } static void wasm_parse_any(CfreeCompiler *c, const CfreeBytesInput *input, diff --git a/test/wasm/cases/br_table.expect b/test/wasm/cases/br_table.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/br_table.wat b/test/wasm/cases/br_table.wat @@ -0,0 +1,12 @@ +(module + (func (export "test_main") (result i32) + (block + (block + (block + i32.const 1 + br_table 0 1 2) + i32.const 1 + return) + i32.const 42 + return) + i32.const 2)) diff --git a/test/wasm/cases/control_loop.expect b/test/wasm/cases/control_loop.expect @@ -0,0 +1 @@ +5 diff --git a/test/wasm/cases/control_loop.wat b/test/wasm/cases/control_loop.wat @@ -0,0 +1,15 @@ +(module + (func (export "test_main") (result i32) + (local $i i32) + (local.set $i (i32.const 0)) + (block + (loop + (local.tee $i + (i32.add + (local.get $i) + (i32.const 1))) + (i32.lt_s + (local.get $i) + (i32.const 5)) + (br_if 0))) + (local.get $i))) diff --git a/test/wasm/cases/float_ops.expect b/test/wasm/cases/float_ops.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/float_ops.wat b/test/wasm/cases/float_ops.wat @@ -0,0 +1,6 @@ +(module + (func (export "test_main") (result i32) + f64.const 40.75 + f64.const 1.25 + f64.add + i32.trunc_f64_s)) diff --git a/test/wasm/cases/if_return.expect b/test/wasm/cases/if_return.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/if_return.wat b/test/wasm/cases/if_return.wat @@ -0,0 +1,6 @@ +(module + (func (export "test_main") (result i32) + (if (i32.eqz (i32.const 0)) + (then (return (i32.const 42))) + (else (return (i32.const 1)))) + (i32.const 0))) diff --git a/test/wasm/cases/int_conversions.expect b/test/wasm/cases/int_conversions.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/int_conversions.wat b/test/wasm/cases/int_conversions.wat @@ -0,0 +1,10 @@ +(module + (func (export "test_main") (result i32) + i64.const 0x10000002a + i32.wrap_i64 + i32.const -1 + i64.extend_i32_s + i32.wrap_i64 + i32.add + i32.const 1 + i32.add)) diff --git a/test/wasm/cases/int_extra_ops.expect b/test/wasm/cases/int_extra_ops.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/int_extra_ops.wat b/test/wasm/cases/int_extra_ops.wat @@ -0,0 +1,16 @@ +(module + (func (export "test_main") (result i32) + i32.const 1 + i32.clz + i32.const 16 + i32.ctz + i32.add + i32.const 15 + i32.popcnt + i32.add + i32.const 8 + i32.const 1 + i32.rotr + i32.add + i32.const 1 + i32.sub)) diff --git a/test/wasm/cases/memory_data.expect b/test/wasm/cases/memory_data.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/memory_data.wat b/test/wasm/cases/memory_data.wat @@ -0,0 +1,5 @@ +(module + (memory 1) + (data (i32.const 4) "\2a\00\00\00") + (func (export "test_main") (result i32) + (i32.load offset=4 (i32.const 0)))) diff --git a/test/wasm/cases/memory_store.expect b/test/wasm/cases/memory_store.expect @@ -0,0 +1 @@ +37 diff --git a/test/wasm/cases/memory_store.wat b/test/wasm/cases/memory_store.wat @@ -0,0 +1,5 @@ +(module + (memory 1) + (func (export "test_main") (result i32) + (i32.store (i32.const 8) (i32.const 37)) + (i32.load (i32.const 8)))) diff --git a/test/wasm/cases/plain_control.expect b/test/wasm/cases/plain_control.expect @@ -0,0 +1 @@ +3 diff --git a/test/wasm/cases/plain_control.wat b/test/wasm/cases/plain_control.wat @@ -0,0 +1,17 @@ +(module + (func (export "test_main") (result i32) + (local $i i32) + i32.const 0 + local.set $i + block + loop + local.get $i + i32.const 1 + i32.add + local.tee $i + i32.const 3 + i32.lt_s + br_if 0 + end + end + local.get $i)) diff --git a/test/wasm/cases/select.expect b/test/wasm/cases/select.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/select.wat b/test/wasm/cases/select.wat @@ -0,0 +1,6 @@ +(module + (func (export "test_main") (result i32) + (select + (i32.const 42) + (i32.const 7) + (i32.eqz (i32.const 0))))) diff --git a/test/wasm/cases/top_export_comments.expect b/test/wasm/cases/top_export_comments.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/top_export_comments.wat b/test/wasm/cases/top_export_comments.wat @@ -0,0 +1,5 @@ +(module + (; nested (; block ;) comment ;) + (func $main (result i32) + i32.const +0x2a) + (export "test_main" (func $main)))