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