kit

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

commit f67721344dae984c70e84c70eac94e2f3f48fc3b
parent 796bb740fdbdc05fc863d42e13c42f706a9ba46b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 17 May 2026 22:08:27 -0700

wasm: lower globals tables memory growth and locals

Diffstat:
Mdoc/WASM.md | 26++++++++++++++------------
Mlang/wasm/wasm.c | 464++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Atest/wasm/cases/call_indirect.expect | 1+
Atest/wasm/cases/call_indirect.wat | 9+++++++++
Atest/wasm/cases/global_state.expect | 1+
Atest/wasm/cases/global_state.wat | 8++++++++
Atest/wasm/cases/local_pressure_tee.expect | 1+
Atest/wasm/cases/local_pressure_tee.wat | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/wasm/cases/local_zero_tee.expect | 1+
Atest/wasm/cases/local_zero_tee.wat | 9+++++++++
Atest/wasm/cases/memory_grow.expect | 1+
Atest/wasm/cases/memory_grow.wat | 9+++++++++
Atest/wasm/cases/start_global.expect | 1+
Atest/wasm/cases/start_global.wat | 8++++++++
Mtest/wasm/run.sh | 3++-
15 files changed, 582 insertions(+), 40 deletions(-)

diff --git a/doc/WASM.md b/doc/WASM.md @@ -759,10 +759,10 @@ targeted test target. Current checked reader/encoder and validation items describe the `lang/wasm` frontend implementation: it can parse, validate, preserve, and -re-encode the listed module metadata. Metadata that is not lowered yet -(`import`, `table`, `global`, `elem`, and `start`) is accepted by the -reader/encoder and rejected before native CG emission with explicit -diagnostics. +re-encode the listed module metadata. Native lowering now covers mutable +numeric globals, imported function declarations, active tables/elements for +`call_indirect`, start functions, and growable single-memory state for the +accepted frontend subset. ### Frontend Source and Driver @@ -834,7 +834,7 @@ diagnostics. - [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 +- [x] Lower `local.set`/`local.tee` for address-taken or spilled locals without depending on backend-local optimization behavior. - [x] Lower structured control flow: `block`, `loop`, `if`, `else`, `br`, `br_if`, and `return`. @@ -843,13 +843,15 @@ diagnostics. and MVP conversions. - [x] Lower f32/f64 arithmetic, comparisons, constants, conversions, and reinterpret ops. -- [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`. +- [x] Lower single linear memory load/store operations, `memory.size`, and + active data initialization. +- [x] Implement checked single-memory loads/stores and growable storage. +- [x] Implement numeric globals for native lowering. +- [x] Implement imported function declarations for native lowering. +- [x] Implement tables, active elements, and `call_indirect`. +- [ ] Move lowered memory/global/import/table state behind the explicit + `CfreeWasmInstance` ABI instead of generated module-local storage/direct + external declarations. - [ ] Define and implement the C-facing exported wrapper ABI that keeps the instance parameter explicit. diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c @@ -37,6 +37,9 @@ typedef enum WasmInsnKind { WASM_INSN_LOCAL_SET, WASM_INSN_LOCAL_TEE, WASM_INSN_CALL, + WASM_INSN_CALL_INDIRECT, + WASM_INSN_GLOBAL_GET, + WASM_INSN_GLOBAL_SET, WASM_INSN_RETURN, WASM_INSN_DROP, WASM_INSN_I32_LOAD, @@ -1015,6 +1018,20 @@ static int wat_instr_kind(WasmTok t, WasmInsnKind *out, int *has_imm) { *has_imm = 1; return 1; } + if (tok_is(t, "call_indirect")) { + *out = WASM_INSN_CALL_INDIRECT; + return 1; + } + if (tok_is(t, "global.get")) { + *out = WASM_INSN_GLOBAL_GET; + *has_imm = 1; + return 1; + } + if (tok_is(t, "global.set")) { + *out = WASM_INSN_GLOBAL_SET; + *has_imm = 1; + return 1; + } if (tok_is(t, "return")) { *out = WASM_INSN_RETURN; return 1; @@ -1438,12 +1455,33 @@ static void wat_parse_local_index(WatParser *p, WasmFunc *f, int64_t *out) { "wasm wat: expected instruction immediate"); } +static void wat_parse_global_index(WatParser *p, int64_t *out) { + uint32_t i; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < p->module->nglobals; ++i) { + if (wasm_name_eq(p->module->globals[i].name, p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown global name"); + } + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global index"); +} + static void wat_parse_instr_imm(WatParser *p, WasmFunc *f, WasmInsnKind kind, int64_t *out) { switch (kind) { case WASM_INSN_CALL: wat_parse_func_index(p, out); break; + case WASM_INSN_GLOBAL_GET: + case WASM_INSN_GLOBAL_SET: + wat_parse_global_index(p, out); + break; case WASM_INSN_LOCAL_GET: case WASM_INSN_LOCAL_SET: case WASM_INSN_LOCAL_TEE: @@ -1557,6 +1595,22 @@ static void wat_reject_inline_result(WatParser *p, const char *what) { static void wat_parse_instr(WatParser *p, WasmFunc *f); +static uint32_t wat_parse_call_indirect_type(WatParser *p) { + int64_t typeidx; + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "type")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected call_indirect type"); + wat_next(p); + wat_parse_type_index(p, &typeidx); + if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: type index out of range"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + return (uint32_t)typeidx; +} + static void wat_parse_instr_list(WatParser *p, WasmFunc *f) { WasmInsnKind kind; int has_imm; @@ -1678,6 +1732,14 @@ static void wat_parse_instr_list(WatParser *p, WasmFunc *f) { wat_expect(p, WT_RPAREN, "')'"); return; } + if (kind == WASM_INSN_CALL_INDIRECT) { + uint32_t typeidx = wat_parse_call_indirect_type(p); + while (p->tok.kind == WT_LPAREN) + wat_parse_instr(p, f); + wasm_func_add_insn(p->c, p->module, f, kind, typeidx); + wat_expect(p, WT_RPAREN, "')'"); + return; + } if (has_imm) { if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { double fv; @@ -1745,6 +1807,11 @@ static void wat_parse_instr(WatParser *p, WasmFunc *f) { in->ntargets = n; return; } + if (kind == WASM_INSN_CALL_INDIRECT) { + uint32_t typeidx = wat_parse_call_indirect_type(p); + wasm_func_add_insn(p->c, p->module, f, kind, typeidx); + return; + } if (has_imm) { if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { double fv; @@ -3124,6 +3191,14 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, case 0x10: wasm_func_add_insn(c, out, f, WASM_INSN_CALL, bin_uleb(&r)); break; + case 0x11: { + WasmInsn *in; + wasm_func_add_insn(c, out, f, WASM_INSN_CALL_INDIRECT, + bin_uleb(&r)); + in = &f->insns[f->ninsns - 1u]; + in->align = bin_uleb(&r); + break; + } case 0x20: wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_GET, bin_uleb(&r)); break; @@ -3133,6 +3208,12 @@ static void wasm_decode_binary(CfreeCompiler *c, const CfreeBytesInput *input, case 0x22: wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_TEE, bin_uleb(&r)); break; + case 0x23: + wasm_func_add_insn(c, out, f, WASM_INSN_GLOBAL_GET, bin_uleb(&r)); + break; + case 0x24: + wasm_func_add_insn(c, out, f, WASM_INSN_GLOBAL_SET, bin_uleb(&r)); + break; case 0x41: wasm_func_add_insn(c, out, f, WASM_INSN_I32_CONST, bin_sleb(&r, 32)); @@ -3534,6 +3615,15 @@ static CfreeCgMemAccess wasm_cg_mem(CfreeCompiler *c, CfreeCgBuiltinTypes b, return mem; } +static void wasm_cg_push_zero(CfreeCompiler *c, CfreeCg *cg, + CfreeCgBuiltinTypes b, WasmValType vt) { + CfreeCgTypeId ty = wasm_cg_type(c, b, vt); + if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) + cfree_cg_push_float(cg, 0.0, ty); + else + cfree_cg_push_int(cg, 0, ty); +} + static CfreeCgTypeId wasm_load_storage_type(CfreeCgBuiltinTypes b, uint8_t kind) { switch (kind) { @@ -3607,18 +3697,25 @@ static void wasm_cg_trap(CfreeCg *cg) { static void wasm_cg_memory_check(CfreeCompiler *c, CfreeCg *cg, CfreeCgBuiltinTypes b, const WasmModule *m, + CfreeCgSym memory_pages_sym, 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) { + uint32_t max_pages = m->memory.has_max ? m->memory.max_pages + : m->memory.min_pages; + if (end > (uint64_t)max_pages * 65536u) { 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_push_symbol_lvalue(cg, memory_pages_sym, 0); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_int(cg, 65536u, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_int_binop(cg, CFREE_CG_INT_MUL, 0); + cfree_cg_push_int(cg, end, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); cfree_cg_int_cmp(cg, CFREE_CG_INT_LE_U); cfree_cg_branch_true(cg, ok); wasm_cg_trap(cg); @@ -4087,6 +4184,39 @@ static void wasm_validate(WasmModule *m, CfreeCompiler *c) { if (m->funcs[in->imm].nresults) wasm_stack_push(c, &stack, m->funcs[in->imm].results[0]); break; + case WASM_INSN_CALL_INDIRECT: { + WasmFuncType *t; + if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) + wasm_error(c, wasm_loc(0, 0), + "wasm: call_indirect type index out of range"); + if (in->align >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: call_indirect table index out of range"); + t = &m->types[in->imm]; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "call_indirect index"); + for (uint32_t k = 0; k < t->nparams; ++k) { + uint32_t param = t->nparams - 1u - k; + wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], + "call_indirect argument"); + } + if (t->nresults) + wasm_stack_push(c, &stack, t->results[0]); + break; + } + case WASM_INSN_GLOBAL_GET: + if (in->imm < 0 || (uint64_t)in->imm >= m->nglobals) + wasm_error(c, wasm_loc(0, 0), "wasm: global index out of range"); + wasm_stack_push(c, &stack, m->globals[in->imm].type); + break; + case WASM_INSN_GLOBAL_SET: + if (in->imm < 0 || (uint64_t)in->imm >= m->nglobals) + wasm_error(c, wasm_loc(0, 0), "wasm: global index out of range"); + if (!m->globals[in->imm].mutable_) + wasm_error(c, wasm_loc(0, 0), "wasm: global is immutable"); + wasm_stack_pop(c, &stack, control, ncontrol, m->globals[in->imm].type, + "global"); + break; case WASM_INSN_RETURN: if (f->nresults) wasm_stack_pop(c, &stack, control, ncontrol, f->results[0], @@ -4290,35 +4420,32 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, CfreeCgBuiltinTypes b = cfree_cg_builtin_types(c); CfreeCgSym syms[64]; CfreeCgTypeId func_types[64]; + CfreeCgSym global_syms[64]; CfreeCgLocal locals[48]; CfreeCgSym memory_sym = CFREE_CG_SYM_NONE; + CfreeCgSym memory_pages_sym = CFREE_CG_SYM_NONE; + CfreeCgSym start_flag_sym = CFREE_CG_SYM_NONE; uint32_t i, j; if (!cg) wasm_error(c, wasm_loc(0, 0), "wasm: failed to initialize codegen"); if (m->nfuncs > 64u) wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); - if (m->ntables || m->nelems) - wasm_error(c, wasm_loc(0, 0), - "wasm: tables and call_indirect are not lowered yet"); - if (m->nglobals) - wasm_error(c, wasm_loc(0, 0), "wasm: globals are not lowered yet"); - if (m->has_start) - wasm_error(c, wasm_loc(0, 0), "wasm: start functions are not lowered yet"); + if (m->nglobals > 64u) + wasm_error(c, wasm_loc(0, 0), "wasm: too many globals"); if (m->has_memory && m->memory.is_import) wasm_error(c, wasm_loc(0, 0), "wasm: imported memories are not lowered yet"); - for (i = 0; i < m->nfuncs; ++i) - if (m->funcs[i].is_import) - wasm_error(c, wasm_loc(0, 0), - "wasm: imported functions are not lowered yet"); memset(syms, 0, sizeof syms); memset(func_types, 0, sizeof func_types); + memset(global_syms, 0, sizeof global_syms); if (m->has_memory) { CfreeCgDecl decl; CfreeCgDataDefAttrs attrs; + uint32_t max_pages = m->memory.has_max ? m->memory.max_pages + : m->memory.min_pages; + uint32_t memory_len = max_pages ? max_pages * 65536u : 1u; CfreeCgTypeId mem_ty = - cfree_cg_type_array(c, b.id[CFREE_CG_BUILTIN_I8], - m->memory.data_len ? m->memory.data_len : 1u); + cfree_cg_type_array(c, b.id[CFREE_CG_BUILTIN_I8], memory_len); memset(&decl, 0, sizeof decl); decl.kind = CFREE_CG_DECL_OBJECT; decl.linkage_name = cfree_sym_intern(c, "__cfree_wasm_memory"); @@ -4330,10 +4457,86 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, memset(&attrs, 0, sizeof attrs); attrs.align = 16; cfree_cg_data_begin(cg, memory_sym, attrs); - if (m->memory.data_len) + if (m->memory.data_len) { cfree_cg_data_bytes(cg, m->memory.data, m->memory.data_len); + if (memory_len > m->memory.data_len) + cfree_cg_data_zero(cg, memory_len - m->memory.data_len); + } else { + cfree_cg_data_zero(cg, memory_len); + } + cfree_cg_data_end(cg); + + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = cfree_sym_intern(c, "__cfree_wasm_memory_pages"); + decl.display_name = decl.linkage_name; + decl.type = b.id[CFREE_CG_BUILTIN_I32]; + decl.sym.bind = CFREE_SB_LOCAL; + decl.as.object.align = 4; + memory_pages_sym = cfree_cg_decl(cg, decl); + memset(&attrs, 0, sizeof attrs); + attrs.align = 4; + cfree_cg_data_begin(cg, memory_pages_sym, attrs); + cfree_cg_data_int(cg, m->memory.min_pages, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_data_end(cg); + } + for (i = 0; i < m->nglobals; ++i) { + const WasmGlobal *g = &m->globals[i]; + CfreeCgDecl decl; + CfreeCgDataDefAttrs attrs; + CfreeSym name; + char local_name[40]; + if (g->is_import && g->import_name) { + name = cfree_sym_intern(c, g->import_name); + } else { + size_t pos = 0; + const char prefix[] = "__cfree_wasm_global_"; + uint32_t n = i, div = 1000000000u; + memcpy(local_name, prefix, sizeof(prefix) - 1u); + pos = sizeof(prefix) - 1u; + while (div > 1u && n / div == 0) + div /= 10u; + while (div) { + local_name[pos++] = (char)('0' + (n / div) % 10u); + div /= 10u; + } + local_name[pos] = '\0'; + name = cfree_sym_intern(c, local_name); + } + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = g->is_import ? cfree_cg_c_linkage_name(c, name) : name; + decl.display_name = name; + decl.type = wasm_cg_type(c, b, g->type); + decl.sym.bind = g->is_import ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + decl.as.object.align = cfree_cg_type_align(c, decl.type); + global_syms[i] = cfree_cg_decl(cg, decl); + if (g->is_import) + continue; + memset(&attrs, 0, sizeof attrs); + attrs.align = decl.as.object.align; + cfree_cg_data_begin(cg, global_syms[i], attrs); + if (g->type == WASM_VAL_F32 || g->type == WASM_VAL_F64) + cfree_cg_data_float(cg, g->init.fp, decl.type); else - cfree_cg_data_zero(cg, 1); + cfree_cg_data_int(cg, (uint64_t)g->init.imm, decl.type); + cfree_cg_data_end(cg); + } + if (m->has_start) { + CfreeCgDecl decl; + CfreeCgDataDefAttrs attrs; + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = cfree_sym_intern(c, "__cfree_wasm_start_ran"); + decl.display_name = decl.linkage_name; + decl.type = b.id[CFREE_CG_BUILTIN_I32]; + decl.sym.bind = CFREE_SB_LOCAL; + decl.as.object.align = 4; + start_flag_sym = cfree_cg_decl(cg, decl); + memset(&attrs, 0, sizeof attrs); + attrs.align = 4; + cfree_cg_data_begin(cg, start_flag_sym, attrs); + cfree_cg_data_int(cg, 0, b.id[CFREE_CG_BUILTIN_I32]); cfree_cg_data_end(cg); } for (i = 0; i < m->nfuncs; ++i) { @@ -4356,7 +4559,9 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, func_types[i] = cfree_cg_type_func(c, sig); if (!func_types[i]) wasm_error(c, wasm_loc(0, 0), "wasm: failed to create function type"); - if (f->export_name) { + if (f->is_import && f->import_name) { + source_name = cfree_sym_intern(c, f->import_name); + } else if (f->export_name) { source_name = cfree_sym_intern(c, f->export_name); } else { size_t pos = 0; @@ -4378,7 +4583,8 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, decl.linkage_name = cfree_cg_c_linkage_name(c, source_name); decl.display_name = source_name; decl.type = func_types[i]; - decl.sym.bind = f->export_name ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + decl.sym.bind = (f->export_name || f->is_import) ? CFREE_SB_GLOBAL + : CFREE_SB_LOCAL; syms[i] = cfree_cg_decl(cg, decl); if (!syms[i]) wasm_error(c, wasm_loc(0, 0), "wasm: failed to declare function"); @@ -4393,6 +4599,8 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, CfreeCgLabel else_label; } control[64]; uint32_t ncontrol = 0; + if (f->is_import) + continue; cfree_cg_func_begin(cg, syms[i]); for (j = 0; j < f->nparams; ++j) { CfreeCgLocalAttrs attrs; @@ -4406,6 +4614,22 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; locals[f->nparams + j] = cfree_cg_local(cg, wasm_cg_type(c, b, f->locals[j]), attrs); + cfree_cg_push_local(cg, locals[f->nparams + j]); + wasm_cg_push_zero(c, cg, b, f->locals[j]); + cfree_cg_store(cg, wasm_cg_mem(c, b, f->locals[j])); + } + if (m->has_start && f->export_name && i != m->start_func) { + CfreeCgLabel started = cfree_cg_label_new(cg); + cfree_cg_push_symbol_lvalue(cg, start_flag_sym, 0); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_int_cmp(cg, CFREE_CG_INT_NE); + cfree_cg_branch_true(cg, started); + cfree_cg_push_symbol_lvalue(cg, start_flag_sym, 0); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_call_symbol(cg, syms[m->start_func], 0, (CfreeCgCallAttrs){0}); + cfree_cg_label_place(cg, started); } for (j = 0; j < f->ninsns; ++j) { WasmInsn in = f->insns[j]; @@ -4596,6 +4820,133 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, cfree_cg_call_symbol(cg, syms[in.imm], m->funcs[in.imm].nparams, (CfreeCgCallAttrs){0}); break; + case WASM_INSN_CALL_INDIRECT: { + const WasmFuncType *t = &m->types[in.imm]; + CfreeCgLocalAttrs attrs; + CfreeCgLocal selector, args[16], result = CFREE_CG_LOCAL_NONE; + CfreeCgSwitchCase cases[64]; + CfreeCgLabel labels[64], trap_label, done_label; + CfreeCgSwitch sw; + uint32_t ncases = 0; + CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + if (t->nparams > 16u) + wasm_error(c, wasm_loc(0, 0), "wasm: too many call_indirect args"); + selector = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + if (t->nresults) + result = cfree_cg_local(cg, wasm_cg_type(c, b, t->results[0]), + attrs); + cfree_cg_push_local(cg, selector); + cfree_cg_swap(cg); + cfree_cg_store(cg, i32_mem); + for (uint32_t p = 0; p < t->nparams; ++p) { + uint32_t param = t->nparams - 1u - p; + args[param] = cfree_cg_local(cg, wasm_cg_type(c, b, t->params[param]), + attrs); + cfree_cg_push_local(cg, args[param]); + cfree_cg_swap(cg); + cfree_cg_store(cg, wasm_cg_mem(c, b, t->params[param])); + } + memset(cases, 0, sizeof cases); + memset(labels, 0, sizeof labels); + trap_label = cfree_cg_label_new(cg); + done_label = cfree_cg_label_new(cg); + for (uint32_t e = 0; e < m->nelems; ++e) { + const WasmElemSegment *seg = &m->elems[e]; + if (seg->tableidx != in.align) + continue; + for (uint32_t k = 0; k < seg->nfuncs; ++k) { + uint32_t funcidx = seg->funcs[k]; + if (funcidx >= m->nfuncs) + continue; + if (m->funcs[funcidx].nparams != t->nparams || + m->funcs[funcidx].nresults != t->nresults || + memcmp(m->funcs[funcidx].params, t->params, + sizeof(t->params[0]) * t->nparams) != 0 || + memcmp(m->funcs[funcidx].results, t->results, + sizeof(t->results[0]) * t->nresults) != 0) + continue; + if (ncases >= 64u) + wasm_error(c, wasm_loc(0, 0), + "wasm: too many call_indirect table entries"); + labels[ncases] = cfree_cg_label_new(cg); + cases[ncases].value = (uint64_t)(seg->offset + k); + cases[ncases].label = labels[ncases]; + ncases++; + } + } + cfree_cg_push_local(cg, selector); + cfree_cg_load(cg, i32_mem); + memset(&sw, 0, sizeof sw); + sw.selector_type = b.id[CFREE_CG_BUILTIN_I32]; + sw.default_label = trap_label; + sw.cases = cases; + sw.ncases = ncases; + sw.hint = CFREE_CG_SWITCH_BRANCH_CHAIN; + cfree_cg_switch(cg, sw); + for (uint32_t e = 0; e < m->nelems; ++e) { + const WasmElemSegment *seg = &m->elems[e]; + if (seg->tableidx != in.align) + continue; + for (uint32_t k = 0; k < seg->nfuncs; ++k) { + uint32_t funcidx = seg->funcs[k]; + const WasmFunc *callee; + int matched; + CfreeCgLabel label = CFREE_CG_LABEL_NONE; + for (uint32_t ci = 0; ci < ncases; ++ci) + if (cases[ci].value == (uint64_t)(seg->offset + k)) + label = labels[ci]; + if (!label) + continue; + callee = &m->funcs[funcidx]; + matched = callee->nparams == t->nparams && + callee->nresults == t->nresults && + memcmp(callee->params, t->params, + sizeof(t->params[0]) * t->nparams) == 0 && + memcmp(callee->results, t->results, + sizeof(t->results[0]) * t->nresults) == 0; + cfree_cg_label_place(cg, label); + if (!matched) { + wasm_cg_trap(cg); + continue; + } + for (uint32_t p = 0; p < t->nparams; ++p) { + cfree_cg_push_local(cg, args[p]); + cfree_cg_load(cg, wasm_cg_mem(c, b, t->params[p])); + } + cfree_cg_call_symbol(cg, syms[funcidx], t->nparams, + (CfreeCgCallAttrs){0}); + if (t->nresults) { + cfree_cg_push_local(cg, result); + cfree_cg_swap(cg); + cfree_cg_store(cg, wasm_cg_mem(c, b, t->results[0])); + } + cfree_cg_jump(cg, done_label); + } + } + cfree_cg_label_place(cg, trap_label); + wasm_cg_trap(cg); + cfree_cg_label_place(cg, done_label); + if (t->nresults) { + cfree_cg_push_local(cg, result); + cfree_cg_load(cg, wasm_cg_mem(c, b, t->results[0])); + } + break; + } + case WASM_INSN_GLOBAL_GET: { + uint32_t index = (uint32_t)in.imm; + cfree_cg_push_symbol_lvalue(cg, global_syms[index], 0); + cfree_cg_load(cg, wasm_cg_mem(c, b, m->globals[index].type)); + break; + } + case WASM_INSN_GLOBAL_SET: { + uint32_t index = (uint32_t)in.imm; + cfree_cg_push_symbol_lvalue(cg, global_syms[index], 0); + cfree_cg_swap(cg); + cfree_cg_store(cg, wasm_cg_mem(c, b, m->globals[index].type)); + break; + } case WASM_INSN_RETURN: if (f->nresults) cfree_cg_ret(cg); @@ -4606,13 +4957,60 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, 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]); + cfree_cg_push_symbol_lvalue(cg, memory_pages_sym, 0); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_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]); + case WASM_INSN_MEMORY_GROW: { + CfreeCgLocalAttrs attrs; + CfreeCgLocal delta, old_pages, grow_result; + CfreeCgLabel fail = cfree_cg_label_new(cg); + CfreeCgLabel done = cfree_cg_label_new(cg); + CfreeCgTypeId i32 = b.id[CFREE_CG_BUILTIN_I32]; + uint32_t max_pages = m->memory.has_max ? m->memory.max_pages + : m->memory.min_pages; + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + delta = cfree_cg_local(cg, i32, attrs); + old_pages = cfree_cg_local(cg, i32, attrs); + grow_result = cfree_cg_local(cg, i32, attrs); + cfree_cg_push_local(cg, delta); + cfree_cg_swap(cg); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_local(cg, old_pages); + cfree_cg_push_symbol_lvalue(cg, memory_pages_sym, 0); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + + cfree_cg_push_local(cg, delta); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_int(cg, max_pages, i32); + cfree_cg_push_local(cg, old_pages); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_int_cmp(cg, CFREE_CG_INT_LE_U); + cfree_cg_branch_false(cg, fail); + + cfree_cg_push_symbol_lvalue(cg, memory_pages_sym, 0); + cfree_cg_push_local(cg, old_pages); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_local(cg, delta); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_push_local(cg, grow_result); + cfree_cg_push_local(cg, old_pages); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_jump(cg, done); + cfree_cg_label_place(cg, fail); + cfree_cg_push_local(cg, grow_result); + cfree_cg_push_int(cg, UINT64_C(0xffffffff), i32); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_label_place(cg, done); + cfree_cg_push_local(cg, grow_result); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); break; + } case WASM_INSN_I32_LOAD: case WASM_INSN_I64_LOAD: case WASM_INSN_I32_LOAD8_S: @@ -4632,7 +5030,7 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, memset(&mem, 0, sizeof mem); mem.type = storage; mem.align = in.align; - wasm_cg_memory_check(c, cg, b, m, &in); + wasm_cg_memory_check(c, cg, b, m, memory_pages_sym, &in); wasm_cg_memory_lvalue(cg, memory_sym, (uint32_t)in.imm); cfree_cg_load(cg, mem); if (storage != result) { @@ -4672,7 +5070,7 @@ static void wasm_emit_cg(CfreeCompiler *c, const CfreeCompileOptions *opts, 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); + wasm_cg_memory_check(c, cg, b, m, memory_pages_sym, &in); cfree_cg_push_local(cg, addr_tmp); cfree_cg_swap(cg); cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32)); @@ -5152,6 +5550,12 @@ static uint8_t wasm_opcode(uint8_t kind) { return 0x22; case WASM_INSN_CALL: return 0x10; + case WASM_INSN_CALL_INDIRECT: + return 0x11; + case WASM_INSN_GLOBAL_GET: + return 0x23; + case WASM_INSN_GLOBAL_SET: + return 0x24; case WASM_INSN_RETURN: return 0x0f; case WASM_INSN_DROP: @@ -5463,10 +5867,16 @@ static void enc_code(CfreeWriter *w, const WasmModule *m) { else if (in.kind == WASM_INSN_LOCAL_GET || in.kind == WASM_INSN_LOCAL_SET || in.kind == WASM_INSN_LOCAL_TEE || + in.kind == WASM_INSN_GLOBAL_GET || + in.kind == WASM_INSN_GLOBAL_SET || 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_CALL_INDIRECT) { + write_uleb(body, (uint64_t)in.imm); + write_uleb(body, in.align); + } 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) diff --git a/test/wasm/cases/call_indirect.expect b/test/wasm/cases/call_indirect.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/call_indirect.wat b/test/wasm/cases/call_indirect.wat @@ -0,0 +1,9 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 42) + (table 1 funcref) + (elem (i32.const 0) func $target) + (func (export "test_main") (result i32) + i32.const 0 + call_indirect (type $ret_i32))) diff --git a/test/wasm/cases/global_state.expect b/test/wasm/cases/global_state.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/global_state.wat b/test/wasm/cases/global_state.wat @@ -0,0 +1,8 @@ +(module + (global $g (mut i32) (i32.const 40)) + (func (export "test_main") (result i32) + global.get $g + i32.const 2 + i32.add + global.set $g + global.get $g)) diff --git a/test/wasm/cases/local_pressure_tee.expect b/test/wasm/cases/local_pressure_tee.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/local_pressure_tee.wat b/test/wasm/cases/local_pressure_tee.wat @@ -0,0 +1,80 @@ +(module + (func (export "test_main") (result i32) + (local i32 i32 i32 i32 i32 i32 i32 i32) + (local i32 i32 i32 i32 i32 i32 i32 i32) + (local i32 i32 i32 i32 i32 i32 i32 i32) + i32.const 1 + local.tee 0 + local.tee 1 + local.tee 2 + local.tee 3 + local.tee 4 + local.tee 5 + local.tee 6 + local.tee 7 + local.tee 8 + local.tee 9 + local.tee 10 + local.tee 11 + local.tee 12 + local.tee 13 + local.tee 14 + local.tee 15 + local.tee 16 + local.tee 17 + local.tee 18 + local.tee 19 + local.tee 20 + local.tee 21 + local.tee 22 + local.tee 23 + drop + local.get 0 + local.get 1 + i32.add + local.get 2 + i32.add + local.get 3 + i32.add + local.get 4 + i32.add + local.get 5 + i32.add + local.get 6 + i32.add + local.get 7 + i32.add + local.get 8 + i32.add + local.get 9 + i32.add + local.get 10 + i32.add + local.get 11 + i32.add + local.get 12 + i32.add + local.get 13 + i32.add + local.get 14 + i32.add + local.get 15 + i32.add + local.get 16 + i32.add + local.get 17 + i32.add + local.get 18 + i32.add + local.get 19 + i32.add + local.get 20 + i32.add + local.get 21 + i32.add + local.get 22 + i32.add + local.get 23 + i32.add + i32.const 18 + i32.add)) diff --git a/test/wasm/cases/local_zero_tee.expect b/test/wasm/cases/local_zero_tee.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/local_zero_tee.wat b/test/wasm/cases/local_zero_tee.wat @@ -0,0 +1,9 @@ +(module + (func (export "test_main") (result i32) + (local $a i32) + (local $b i64) + local.get $a + i64.const 42 + local.tee $b + i32.wrap_i64 + i32.add)) diff --git a/test/wasm/cases/memory_grow.expect b/test/wasm/cases/memory_grow.expect @@ -0,0 +1 @@ +4 diff --git a/test/wasm/cases/memory_grow.wat b/test/wasm/cases/memory_grow.wat @@ -0,0 +1,9 @@ +(module + (memory 1 2) + (func (export "test_main") (result i32) + memory.size + i32.const 1 + memory.grow + memory.size + i32.add + i32.add)) diff --git a/test/wasm/cases/start_global.expect b/test/wasm/cases/start_global.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/start_global.wat b/test/wasm/cases/start_global.wat @@ -0,0 +1,8 @@ +(module + (global $g (mut i32) (i32.const 0)) + (func $start + i32.const 42 + global.set $g) + (start $start) + (func (export "test_main") (result i32) + global.get $g)) diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -194,7 +194,8 @@ if [ "$have_wasm_tool" -eq 1 ]; then name=$(basename "$wat" .wat) wasm="$BUILD_DIR/meta-$name.wasm" run_expect_zero "meta/$name/W" "$WASM_TOOL" --wat2wasm "$wat" "$wasm" - if [ "$name" = "type_custom" ]; then + if [ "$name" = "type_custom" ] || + [ "$name" = "import_table_global_start" ]; then run_expect_zero "meta/$name/native" "$CFREE_BIN" cc \ -target "$target_triple" -c "$wasm" -o "$BUILD_DIR/meta-$name.o" else