/* * nanowasm, a tiny WebAssembly/Wasm interpreter * Copyright (C) 2023-2024 Xavier Del Campo Romero * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const enum opcode initexpr_opcodes[] = { OP_NOP, OP_END, OP_I32_CONST, OP_I64_CONST, OP_F32_CONST, OP_F64_CONST }; const struct interp_set interp_initexpr_set = { .opcodes = initexpr_opcodes, .n = sizeof initexpr_opcodes / sizeof *initexpr_opcodes }; static int (*const ops[])(struct nw_interp *) = { [OP_UNREACHABLE] = op_unreachable, [OP_NOP] = op_nop, [OP_BLOCK] = op_block, [OP_LOOP] = op_loop, [OP_IF] = op_if, [OP_ELSE] = op_else, [OP_END] = op_end, [OP_BR] = op_br, [OP_BR_IF] = op_br_if, [OP_BR_TABLE] = op_br_table, [OP_RETURN] = op_return, [OP_CALL] = op_call, [OP_CALL_INDIRECT] = op_call_indirect, [OP_GET_LOCAL] = op_get_local, [OP_SET_LOCAL] = op_set_local, [OP_TEE_LOCAL] = op_tee_local, [OP_GET_GLOBAL] = op_get_global, [OP_SET_GLOBAL] = op_set_global, [OP_I32_LOAD] = op_i32_load, [OP_I32_STORE] = op_i32_store, [OP_I32_CONST] = op_i32_const, [OP_I64_CONST] = op_i64_const, [OP_F32_CONST] = op_f32_const, [OP_F64_CONST] = op_f64_const, [OP_I32_SUB] = op_i32_sub }; int interp_run(struct nw_interp *const i) { uint8_t op; if (!fread(&op, sizeof op, 1, i->f)) { LOG("%s: fread(3) failed, feof=%d, ferror=%d\n", __func__, feof(i->f), ferror(i->f)); i->exception = "I/O error"; return -1; } else if (op >= sizeof ops / sizeof *ops) { LOG("%s: invalid opcode %#" PRIx8 "\n", __func__, op); i->exception = "invalid opcode"; return -1; } else if (!ops[op]) { LOG("%s: unsupported opcode %#" PRIx8 "\n", __func__, op); i->exception = "invalid opcode"; return -1; } return ops[op](i); } static int run_opcode_limited(struct nw_interp *const in, const struct interp_set *const set) { uint8_t op; if (!fread(&op, sizeof op, 1, in->f)) { LOG("%s: fread(3) failed, feof=%d, ferror=%d\n", __func__, feof(in->f), ferror(in->f)); in->exception = "I/O error"; return -1; } for (size_t i = 0; i < set->n; i++) if (op == set->opcodes[i]) return ops[op](in); LOG("%s: unexpected opcode %#" PRIx8 "\n", __func__, op); in->exception = "invalid opcode"; return -1; } int interp_run_limited(struct nw_interp *const i, const struct interp_set *const set) { while (!i->exit) if (run_opcode_limited(i, set)) { LOG("%s: run_opcode_limited failed\n", __func__); return -1; } return 0; } int interp_check_opcode(const uint8_t op, FILE *const f) { static int (*const checks[])(FILE *) = { [OP_UNREACHABLE] = check_unreachable, [OP_NOP] = check_nop, [OP_BLOCK] = check_block, [OP_LOOP] = check_loop, [OP_IF] = check_if, [OP_ELSE] = check_else, [OP_END] = check_end, [OP_BR] = check_br, [OP_BR_IF] = check_br_if, [OP_BR_TABLE] = check_br_table, [OP_RETURN] = check_return, [OP_CALL] = check_call, [OP_CALL_INDIRECT] = check_call_indirect, [OP_GET_LOCAL] = check_get_local, [OP_SET_LOCAL] = check_set_local, [OP_TEE_LOCAL] = check_tee_local, [OP_GET_GLOBAL] = check_get_global, [OP_SET_GLOBAL] = check_set_global, [OP_I32_LOAD] = check_i32_load, [OP_I32_STORE] = check_i32_store, [OP_I32_CONST] = check_i32_const, [OP_I64_CONST] = check_i64_const, [OP_F32_CONST] = check_f32_const, [OP_F64_CONST] = check_f64_const, [OP_I32_SUB] = check_i32_sub }; if (op >= sizeof checks / sizeof *checks) { LOG("%s: invalid opcode %#" PRIx8 "\n", __func__, op); return 1; } else if (!checks[op]) { LOG("%s: unsupported opcode %#" PRIx8 "\n", __func__, op); return 1; } return checks[op](f); } int interp_start(const struct nw_interp_cfg *const cfg, FILE *const f, struct nw_interp *const i) { *i = (const struct nw_interp) { .f = f, .cfg = *cfg }; return 0; } void *interp_stackptr(const struct nw_interp *const i) { return (char *)i->cfg.stack.buf + i->stack_i; } struct retval_priv { enum value_type type; union { int32_t i32; int64_t i64; float f32; double f64; } u; }; static int get_i32(struct nw_interp *const i, struct retval_priv *const rp) { return interp_stack_pop(i, &rp->u.i32, sizeof rp->u.i32); } static int get_i64(struct nw_interp *const i, struct retval_priv *const rp) { return interp_stack_pop(i, &rp->u.i64, sizeof rp->u.i64); } static int get_f32(struct nw_interp *const i, struct retval_priv *const rp) { return interp_stack_pop(i, &rp->u.f32, sizeof rp->u.f32); } static int get_f64(struct nw_interp *const i, struct retval_priv *const rp) { return interp_stack_pop(i, &rp->u.f64, sizeof rp->u.f64); } static int get_retval(struct nw_interp *const i, struct retval_priv *const rp) { const struct retval *const r = &i->fp->retval; const enum value_type v = r->type; static int (*const f[])(struct nw_interp *, struct retval_priv *) = { [VALUE_TYPE_I32] = get_i32, [VALUE_TYPE_I64] = get_i64, [VALUE_TYPE_F32] = get_f32, [VALUE_TYPE_F64] = get_f64 }; if (f[r->type](i, rp)) { LOG("%s: callback for type %s failed\n", __func__, value_type_tostr(v)); return -1; } rp->type = r->type; return 0; } int interp_pop(struct nw_interp *const i) { if (!i->fp) { static const char exc[] = "no frame pointer"; LOG("%s: %s\n", __func__, exc); i->exception = exc; return -1; } const struct retval *const r = &i->fp->retval; struct retval_priv rp; if (r->returns && get_retval(i, &rp)) { LOG("%s: get_retval failed\n", __func__); return -1; } #if 0 #error TODO: do Wasm modules really have an exit status? #endif if (!(i->fp = i->fp->prev)) { /* Entry point is always assumed to return int, which is * defined as an i32 variable according to Wasm. */ if (rp.type != VALUE_TYPE_I32) { LOG("%s: expected i32 return type, got %s\n", __func__, value_type_tostr(rp.type)); i->exception = "unexpected return type"; return -1; } i->retval = rp.u.i32; i->exit = true; } return 0; } int interp_stack_push(struct nw_interp *const i, const void *const src, const size_t n) { const size_t max = i->cfg.stack.n; void *const dst = interp_stackptr(i); if (max < n || i->stack_i > max - n) { static const char exc[] = "stack overflow"; LOG("%s: %s\n", __func__, exc); i->exception = exc; return -1; } memcpy(dst, src, n); i->stack_i += n; return 0; } int interp_stack_pop(struct nw_interp *const i, void *const dst, const size_t n) { if (i->stack_i < n) { static const char exc[] = "stack underflow"; LOG("%s: %s\n", __func__, exc); i->exception = exc; return -1; } i->stack_i -= n; memcpy(dst, interp_stackptr(i), n); return 0; } int interp_heap_store(struct nw_interp *const i, const size_t addr, const void *const src, const size_t n) { if (i->cfg.heap.n < n || addr > i->cfg.heap.n - n) { static const char exc[] = "heap overflow"; LOG("%s: %s\n", __func__, exc); i->exception = exc; return -1; } void *const dst = (char *)i->cfg.heap.buf + addr; memcpy(dst, src, n); return 0; } int interp_heap_load(struct nw_interp *const i, const size_t addr, void *const dst, const size_t n) { const size_t max = i->cfg.heap.n; if (max < n || addr > max - n) { static const char exc[] = "heap out-of-bounds access"; LOG("%s: %s (%lu, max %zu)\n", __func__, exc, (unsigned long)addr, max); i->exception = exc; return -1; } const void *const src = (const char *)i->cfg.heap.buf + addr; memcpy(dst, src, n); return 0; } int interp_global_push(struct nw_interp *const i, const void *const src, const size_t n) { const size_t max = i->cfg.global.n; void *const dst = (char *)i->cfg.global.buf + i->global_i; if (max < n || i->global_i > max - n) { static const char exc[] = "global memory overflow"; LOG("%s: %s\n", __func__, exc); i->exception = exc; return -1; } memcpy(dst, src, n); i->global_i += n; return 0; } int interp_global_pop(struct nw_interp *const i, void *const dst, const size_t n) { const void *const src = (const char *)i->cfg.global.buf + i->global_i; if (i->global_i < n) { static const char exc[] = "global memory underflow"; LOG("%s: %s\n", __func__, exc); i->exception = exc; return -1; } i->global_i -= n; memcpy(dst, src, n); return 0; } void *interp_globalptr(const struct nw_interp *const i) { return (char *)i->cfg.global.buf + i->global_i; }