#include "wasm.h" #include "parser.h" #include "debug.h" #include "error.h" #include #include #include #include #include #include #include #include #include #include #define ERR_STACK_SIZE 16 #define NUM_LOCALS 0xFFFF #define WASM_PAGE_SIZE 65536 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) static const int MAX_LABELS = 1024; static int interp_code(struct wasm_interp *interp); static INLINE int pop_label_checkpoint(struct wasm_interp *interp); struct expr_parser { struct wasm_interp *interp; // optional... struct cursor *code; struct errors *errs; struct cursor *stack; // optional }; static INLINE int cursor_popval(struct cursor *cur, struct val *val) { return cursor_pop(cur, (unsigned char*)val, sizeof(*val)); } static const char *valtype_name(enum valtype valtype) { switch (valtype) { case val_i32: return "i32"; case val_i64: return "i64"; case val_f32: return "f32"; case val_f64: return "f64"; case val_ref_null: return "null"; case val_ref_func: return "func"; case val_ref_extern: return "extern"; } return "?"; } static const char *reftype_name(enum reftype reftype) { return valtype_name((enum valtype)reftype); } static const char *valtype_literal(enum valtype valtype) { switch (valtype) { case val_i32: return ""; case val_i64: return "L"; case val_f32: return ""; case val_f64: return "f"; case val_ref_null: return "null"; case val_ref_func: return "func"; case val_ref_extern: return "extern"; } return "?"; } static INLINE int is_valid_fn_index(struct module *module, u32 ind) { return ind < module->num_funcs; } static INLINE struct func *get_fn(struct module *module, u32 ind) { if (unlikely(!is_valid_fn_index(module, ind))) return NULL; return &module->funcs[ind]; } u8 *interp_mem_ptr(struct wasm_interp *interp, u32 ptr, int size) { u8 *pos = interp->memory.start + ptr; if (ptr == 0) { interp_error(interp, "null mem_ptr"); return NULL; } if (pos + size >= interp->memory.p) { interp_error(interp, "guest invalid mem read: %d > %d", pos, interp->memory.p - interp->memory.start); return NULL; } return pos; } /* static INLINE int read_mem(struct wasm_interp *interp, u32 ptr, int size, void *dest) { u8 *mem; if (!(mem = interp_mem_ptr(interp, ptr, size))) return interp_error(interp, "invalid mem pointer"); memcpy(dest, mem, size); return 1; } static INLINE int read_mem_u32(struct wasm_interp *interp, u32 ptr, u32 *i) { return read_mem(interp, ptr, sizeof(*i), i); } static INLINE int mem_ptr_f32(struct wasm_interp *interp, u32 ptr, float **i) { if (!(*i = (float*)interp_mem_ptr(interp, ptr, sizeof(float)))) return interp_error(interp, "int memptr"); return 1; } static INLINE int mem_ptr_u32(struct wasm_interp *interp, u32 ptr, u32 **i) { if (!(*i = (u32*)interp_mem_ptr(interp, ptr, sizeof(int)))) return interp_error(interp, "uint memptr"); return 1; } static INLINE int mem_ptr_u32_arr(struct wasm_interp *interp, u32 ptr, int n, u32 **i) { if (!(*i = (u32*)interp_mem_ptr(interp, ptr, n * sizeof(int)))) return interp_error(interp, "uint memptr"); return 1; } static INLINE int read_mem_i32(struct wasm_interp *interp, u32 ptr, int *i) { return read_mem(interp, ptr, sizeof(*i), i); } */ static INLINE struct val *get_local(struct wasm_interp *interp, u32 ind) { struct callframe *frame; if (unlikely(!(frame = top_callframe(&interp->callframes)))) { interp_error(interp, "no callframe?"); return NULL; } if (unlikely(ind >= frame->func->num_locals)) { interp_error(interp, "local index %d too high for %s:%d (max %d)", ind, frame->func->name, frame->func->idx, frame->func->num_locals-1); return NULL; } return &frame->locals[ind]; } int get_var_params(struct wasm_interp *interp, struct val **vals, u32 *num_vals) { struct callframe *frame; if (unlikely(!(frame = top_callframe(&interp->callframes)))) return interp_error(interp, "no callframe?"); *num_vals = frame->func->functype->params.num_valtypes; *vals = frame->locals; return 1; } int get_params(struct wasm_interp *interp, struct val** vals, u32 num_vals) { u32 nvals; if (!get_var_params(interp, vals, &nvals)) return 0; if (nvals != num_vals) return interp_error(interp, "requested %d params, but there are %d", num_vals, nvals); return 1; } static INLINE int stack_popval(struct wasm_interp *interp, struct val *val) { return cursor_popval(&interp->stack, val); } static INLINE struct val *cursor_topval(struct cursor *stack) { return (struct val *)cursor_top(stack, sizeof(struct val)); } static INLINE struct val *stack_topval(struct wasm_interp *interp) { return cursor_topval(&interp->stack); } static INLINE struct val *stack_top_type(struct wasm_interp *interp, enum valtype type) { struct val *val; if (unlikely(!(val = stack_topval(interp)))) { interp_error(interp, "pop"); return NULL; } if (val->type != type) { interp_error(interp, "type mismatch: got %s, expected %s", valtype_name(val->type), valtype_name(type) ); return NULL; } return val; } static INLINE struct val *stack_top_i32(struct wasm_interp *interp) { return stack_top_type(interp, val_i32); } static INLINE struct val *stack_top_f32(struct wasm_interp *interp) { return stack_top_type(interp, val_f32); } static INLINE struct val *stack_top_f64(struct wasm_interp *interp) { return stack_top_type(interp, val_f64); } static INLINE struct val *stack_top_i64(struct wasm_interp *interp) { return stack_top_type(interp, val_i64); } static INLINE int cursor_pop_i32(struct cursor *stack, int *i) { struct val val; if (unlikely(!cursor_popval(stack, &val))) return 0; if (unlikely(val.type != val_i32)) return 0; *i = val.num.i32; return 1; } static INLINE int cursor_pop_i64(struct cursor *stack, int64_t *i) { struct val val; if (unlikely(!cursor_popval(stack, &val))) return 0; if (unlikely(val.type != val_i64)) return 0; *i = val.num.i64; return 1; } static int is_reftype(enum valtype type) { switch (type) { case val_i32: case val_i64: case val_f32: case val_f64: return 0; case val_ref_null: case val_ref_func: case val_ref_extern: return 1; } return 0; } /* static INLINE int cursor_pop_ref(struct cursor *stack, struct val *val) { if (!cursor_popval(stack, val)) { return 0; } if (!is_reftype(val->type)) { return 0; } return 1; } */ static INLINE int stack_pop_ref(struct wasm_interp *interp, struct val *val) { if (!cursor_popval(&interp->stack, val)) { return interp_error(interp, "no value on stack"); } if (!is_reftype(val->type)) { return interp_error(interp, "not a reftype, got %s", valtype_name(val->type)); } return 1; } static INLINE int stack_pop_i32(struct wasm_interp *interp, int *i) { return cursor_pop_i32(&interp->stack, i); } static INLINE int stack_pop_i64(struct wasm_interp *interp, int64_t *i) { return cursor_pop_i64(&interp->stack, i); } static INLINE int cursor_pop_valtype(struct cursor *stack, enum valtype type, struct val *val) { if (unlikely(!cursor_popval(stack, val))) { return 0; } if (unlikely(val->type != type)) { return 0; } return 1; } static INLINE int stack_pop_valtype(struct wasm_interp *interp, enum valtype type, struct val *val) { return cursor_pop_valtype(&interp->stack, type, val); } static void print_val(struct val *val) { switch (val->type) { case val_i32: printf("%d", val->num.i32); break; case val_i64: printf("%" PRId64, val->num.i64); break; case val_f32: printf("%f", val->num.f32); break; case val_f64: printf("%f", val->num.f64); break; case val_ref_null: break; case val_ref_func: case val_ref_extern: printf("%d", val->ref.addr); break; } printf("%s", valtype_literal(val->type)); } #ifdef DEBUG static void print_refval(struct refval *ref, enum reftype reftype) { struct val val; val.type = (enum valtype)reftype; val.ref = *ref; print_val(&val); } #endif static void print_stack(struct cursor *stack) { struct val val; int i; u8 *p = stack->p; if (stack->p == stack->start) { return; } for (i = 0; stack->p > stack->start; i++) { cursor_popval(stack, &val); printf("[%d] ", i); print_val(&val); printf("\n"); } stack->p = p; } void print_callstack(struct wasm_interp *interp) { int i = 0; struct callframe *frame; printf("callstack:\n"); while ((frame = top_callframes(&interp->callframes, i++))) { if (!frame->func) { printf("??\n"); continue; } else { printf("%d %s:%d\n", i, frame->func->name, frame->func->idx); } } } static INLINE int cursor_push_i64(struct cursor *stack, s64 i) { struct val val; val.type = val_i64; val.num.i64 = i; return cursor_pushval(stack, &val); } static INLINE int cursor_push_f32(struct cursor *stack, float f) { struct val val; val.type = val_f32; val.num.f32 = f; return cursor_pushval(stack, &val); } static INLINE int cursor_push_f64(struct cursor *stack, double f) { struct val val; val.type = val_f64; val.num.f64 = f; return cursor_pushval(stack, &val); } static INLINE int cursor_push_u64(struct cursor *stack, u64 i) { struct val val; val.type = val_i64; val.num.u64 = i; return cursor_pushval(stack, &val); } static INLINE int cursor_push_funcref(struct cursor *stack, int addr) { struct val val; val.type = val_ref_func; val.ref.addr = addr; return cursor_pushval(stack, &val); } static INLINE int stack_push_i64(struct wasm_interp *interp, s64 i) { return cursor_push_u64(&interp->stack, (u64)i); } /* static INLINE int stack_push_u64(struct wasm_interp *interp, u64 i) { return cursor_push_u64(&interp->stack, i); } */ static INLINE void make_i32_val(struct val *val, int v) { val->type = val_i32; val->num.i32 = v; } static INLINE void make_f64_val(struct val *val, double v) { val->type = val_f64; val->num.f64 = v; } static INLINE void make_f32_val(struct val *val, float v) { val->type = val_f32; val->num.f32 = v; } static INLINE int stack_pushval(struct wasm_interp *interp, struct val *val) { return cursor_pushval(&interp->stack, val); } /* static int interp_exit(struct wasm_interp *interp) { struct val *vals = NULL; if (!get_params(interp, &vals, 1)) return interp_error(interp, "exit param missing?"); interp->quitting = 1; stack_push_i32(interp, vals[0].num.i32); return 0; } static int wasi_proc_exit(struct wasm_interp *interp) { return interp_exit(interp); } static int wasi_abort(struct wasm_interp *interp) { struct val *params = NULL; if (!get_params(interp, ¶ms, 4)) return interp_error(interp, "exit param missing?"); printf("abort\n"); interp->quitting = 1; stack_push_i32(interp, 88); return 0; } */ static INLINE const char *get_function_name(struct module *module, int fn) { struct func *func = NULL; if (unlikely(!(func = get_fn(module, fn)))) { return "unknown"; } return func->name; } /* static int wasi_args_sizes_get(struct wasm_interp *interp); static int wasi_args_get(struct wasm_interp *interp); static int wasi_fd_write(struct wasm_interp *interp); static int wasi_fd_close(struct wasm_interp *interp); static int wasi_environ_sizes_get(struct wasm_interp *interp); static int wasi_environ_get(struct wasm_interp *interp); */ static int parse_instr(struct expr_parser *parser, u8 tag, struct instr *op); static INLINE int is_valtype(unsigned char byte) { switch ((enum valtype)byte) { case val_i32: // i32 case val_i64: // i64 case val_f32: // f32 case val_f64: // f64 case val_ref_func: // funcref case val_ref_null: // null case val_ref_extern: // externref return 1; } return 0; } /* static int sizeof_valtype(enum valtype valtype) { switch (valtype) { case i32: return 4; case f32: return 4; case i64: return 8; case f64: return 8; } return 0; } */ static char *instr_name(enum instr_tag tag) { static char unk[6] = {0}; switch (tag) { case i_unreachable: return "unreachable"; case i_nop: return "nop"; case i_block: return "block"; case i_loop: return "loop"; case i_if: return "if"; case i_else: return "else"; case i_end: return "end"; case i_br: return "br"; case i_br_if: return "br_if"; case i_br_table: return "br_table"; case i_return: return "return"; case i_call: return "call"; case i_call_indirect: return "call_indirect"; case i_drop: return "drop"; case i_select: return "select"; case i_local_get: return "local_get"; case i_local_set: return "local_set"; case i_local_tee: return "local_tee"; case i_global_get: return "global_get"; case i_global_set: return "global_set"; case i_i32_load: return "i32_load"; case i_i64_load: return "i64_load"; case i_f32_load: return "f32_load"; case i_f64_load: return "f64_load"; case i_i32_load8_s: return "i32_load8_s"; case i_i32_load8_u: return "i32_load8_u"; case i_i32_load16_s: return "i32_load16_s"; case i_i32_load16_u: return "i32_load16_u"; case i_i64_load8_s: return "i64_load8_s"; case i_i64_load8_u: return "i64_load8_u"; case i_i64_load16_s: return "i64_load16_s"; case i_i64_load16_u: return "i64_load16_u"; case i_i64_load32_s: return "i64_load32_s"; case i_i64_load32_u: return "i64_load32_u"; case i_i32_store: return "i32_store"; case i_i64_store: return "i64_store"; case i_f32_store: return "f32_store"; case i_f64_store: return "f64_store"; case i_i32_store8: return "i32_store8"; case i_i32_store16: return "i32_store16"; case i_i64_store8: return "i64_store8"; case i_i64_store16: return "i64_store16"; case i_i64_store32: return "i64_store32"; case i_memory_size: return "memory_size"; case i_memory_grow: return "memory_grow"; case i_i32_const: return "i32_const"; case i_i64_const: return "i64_const"; case i_f32_const: return "f32_const"; case i_f64_const: return "f64_const"; case i_i32_eqz: return "i32_eqz"; case i_i32_eq: return "i32_eq"; case i_i32_ne: return "i32_ne"; case i_i32_lt_s: return "i32_lt_s"; case i_i32_lt_u: return "i32_lt_u"; case i_i32_gt_s: return "i32_gt_s"; case i_i32_gt_u: return "i32_gt_u"; case i_i32_le_s: return "i32_le_s"; case i_i32_le_u: return "i32_le_u"; case i_i32_ge_s: return "i32_ge_s"; case i_i32_ge_u: return "i32_ge_u"; case i_i64_eqz: return "i64_eqz"; case i_i64_eq: return "i64_eq"; case i_i64_ne: return "i64_ne"; case i_i64_lt_s: return "i64_lt_s"; case i_i64_lt_u: return "i64_lt_u"; case i_i64_gt_s: return "i64_gt_s"; case i_i64_gt_u: return "i64_gt_u"; case i_i64_le_s: return "i64_le_s"; case i_i64_le_u: return "i64_le_u"; case i_i64_ge_s: return "i64_ge_s"; case i_i64_ge_u: return "i64_ge_u"; case i_f32_eq: return "f32_eq"; case i_f32_ne: return "f32_ne"; case i_f32_lt: return "f32_lt"; case i_f32_gt: return "f32_gt"; case i_f32_le: return "f32_le"; case i_f32_ge: return "f32_ge"; case i_f64_eq: return "f64_eq"; case i_f64_ne: return "f64_ne"; case i_f64_lt: return "f64_lt"; case i_f64_gt: return "f64_gt"; case i_f64_le: return "f64_le"; case i_f64_ge: return "f64_ge"; case i_i32_clz: return "i32_clz"; case i_i32_ctz: return "i32_ctz"; case i_i32_popcnt: return "i32_popcnt"; case i_i32_add: return "i32_add"; case i_i32_sub: return "i32_sub"; case i_i32_mul: return "i32_mul"; case i_i32_div_s: return "i32_div_s"; case i_i32_div_u: return "i32_div_u"; case i_i32_rem_s: return "i32_rem_s"; case i_i32_rem_u: return "i32_rem_u"; case i_i32_and: return "i32_and"; case i_i32_or: return "i32_or"; case i_i32_xor: return "i32_xor"; case i_i32_shl: return "i32_shl"; case i_i32_shr_s: return "i32_shr_s"; case i_i32_shr_u: return "i32_shr_u"; case i_i32_rotl: return "i32_rotl"; case i_i32_rotr: return "i32_rotr"; case i_i64_clz: return "i64_clz"; case i_i64_ctz: return "i64_ctz"; case i_i64_popcnt: return "i64_popcnt"; case i_i64_add: return "i64_add"; case i_i64_sub: return "i64_sub"; case i_i64_mul: return "i64_mul"; case i_i64_div_s: return "i64_div_s"; case i_i64_div_u: return "i64_div_u"; case i_i64_rem_s: return "i64_rem_s"; case i_i64_rem_u: return "i64_rem_u"; case i_i64_and: return "i64_and"; case i_i64_or: return "i64_or"; case i_i64_xor: return "i64_xor"; case i_i64_shl: return "i64_shl"; case i_i64_shr_s: return "i64_shr_s"; case i_i64_shr_u: return "i64_shr_u"; case i_i64_rotl: return "i64_rotl"; case i_i64_rotr: return "i64_rotr"; case i_f32_abs: return "f32_abs"; case i_f32_neg: return "f32_neg"; case i_f32_ceil: return "f32_ceil"; case i_f32_floor: return "f32_floor"; case i_f32_trunc: return "f32_trunc"; case i_f32_nearest: return "f32_nearest"; case i_f32_sqrt: return "f32_sqrt"; case i_f32_add: return "f32_add"; case i_f32_sub: return "f32_sub"; case i_f32_mul: return "f32_mul"; case i_f32_div: return "f32_div"; case i_f32_min: return "f32_min"; case i_f32_max: return "f32_max"; case i_f32_copysign: return "f32_copysign"; case i_f64_abs: return "f64_abs"; case i_f64_neg: return "f64_neg"; case i_f64_ceil: return "f64_ceil"; case i_f64_floor: return "f64_floor"; case i_f64_trunc: return "f64_trunc"; case i_f64_nearest: return "f64_nearest"; case i_f64_sqrt: return "f64_sqrt"; case i_f64_add: return "f64_add"; case i_f64_sub: return "f64_sub"; case i_f64_mul: return "f64_mul"; case i_f64_div: return "f64_div"; case i_f64_min: return "f64_min"; case i_f64_max: return "f64_max"; case i_f64_copysign: return "f64_copysign"; case i_i32_wrap_i64: return "i32_wrap_i64"; case i_i32_trunc_f32_s: return "i32_trunc_f32_s"; case i_i32_trunc_f32_u: return "i32_trunc_f32_u"; case i_i32_trunc_f64_s: return "i32_trunc_f64_s"; case i_i32_trunc_f64_u: return "i32_trunc_f64_u"; case i_i64_extend_i32_s: return "i64_extend_i32_s"; case i_i64_extend_i32_u: return "i64_extend_i32_u"; case i_i64_trunc_f32_s: return "i64_trunc_f32_s"; case i_i64_trunc_f32_u: return "i64_trunc_f32_u"; case i_i64_trunc_f64_s: return "i64_trunc_f64_s"; case i_i64_trunc_f64_u: return "i64_trunc_f64_u"; case i_f32_convert_i32_s: return "f32_convert_i32_s"; case i_f32_convert_i32_u: return "f32_convert_i32_u"; case i_f32_convert_i64_s: return "f32_convert_i64_s"; case i_f32_convert_i64_u: return "f32_convert_i64_u"; case i_f32_demote_f64: return "f32_demote_f64"; case i_f64_convert_i32_s: return "f64_convert_i32_s"; case i_f64_convert_i32_u: return "f64_convert_i32_u"; case i_f64_convert_i64_s: return "f64_convert_i64_s"; case i_f64_convert_i64_u: return "f64_convert_i64_u"; case i_f64_promote_f32: return "f64_promote_f32"; case i_i32_reinterpret_f32: return "i32_reinterpret_f32"; case i_i64_reinterpret_f64: return "i64_reinterpret_f64"; case i_f32_reinterpret_i32: return "f32_reinterpret_i32"; case i_f64_reinterpret_i64: return "f64_reinterpret_i64"; case i_i32_extend8_s: return "i32_extend8_s"; case i_i32_extend16_s: return "i32_extend16_s"; case i_i64_extend8_s: return "i64_extend8_s"; case i_i64_extend16_s: return "i64_extend16_s"; case i_i64_extend32_s: return "i64_extend32_s"; case i_ref_null: return "ref_null"; case i_ref_func: return "ref_func"; case i_ref_is_null: return "ref_is_null"; case i_bulk_op: return "bulk_op"; case i_table_get: return "table_get"; case i_table_set: return "table_set"; case i_selects: return "selects"; } snprintf(unk, sizeof(unk), "0x%02x", tag); return unk; } static INLINE int was_name_section_parsed(struct module *module, enum name_subsection_tag subsection) { if (!was_section_parsed(module, section_name)) { return 0; } return module->name_section.parsed & (1 << subsection); } //static int callframe_cnt = 0; static INLINE int cursor_push_callframe(struct cursor *cur, struct callframe *frame) { //debug("pushing callframe %d fn:%d\n", ++callframe_cnt, frame->fn); return cursor_push(cur, (u8*)frame, sizeof(*frame)); } static INLINE int count_resolvers(struct wasm_interp *interp) { return (int)cursor_count(&interp->resolver_stack, sizeof(struct resolver)); } static INLINE int push_callframe(struct wasm_interp *interp, struct callframe *frame) { u32 offset; offset = count_resolvers(interp); /* push label resolver offsets, used to keep track of per-func resolvers */ /* TODO: maybe move this data to struct func? */ if (unlikely(!cursor_push_int(&interp->resolver_offsets, offset))) return interp_error(interp, "push resolver offset"); return cursor_push_callframe(&interp->callframes, frame); } static INLINE int cursor_drop_callframe(struct cursor *cur) { //debug("dropping callframe %d fn:%d\n", callframe_cnt--, top_callframe(cur)->fn); return cursor_drop(cur, sizeof(struct callframe)); } static INLINE int cursor_dropval(struct cursor *stack) { return cursor_drop(stack, sizeof(struct val)); } static INLINE int cursor_popint(struct cursor *cur, int *i) { return cursor_pop(cur, (u8 *)i, sizeof(int)); } void print_error_backtrace(struct errors *errors) { struct cursor errs; struct error err; copy_cursor(&errors->cur, &errs); errs.p = errs.start; while (errs.p < errors->cur.p) { if (!cursor_pull_error(&errs, &err)) { printf("backtrace: couldn't pull error\n"); return; } printf("%08x:%s\n", err.pos, err.msg); } } static void _functype_str(struct functype *ft, struct cursor *buf) { u32 i; cursor_push_str(buf, "("); for (i = 0; i < ft->params.num_valtypes; i++) { cursor_push_str(buf, valtype_name(ft->params.valtypes[i])); if (i != ft->params.num_valtypes-1) { cursor_push_str(buf, ", "); } } cursor_push_str(buf, ") -> ("); for (i = 0; i < ft->result.num_valtypes; i++) { cursor_push_str(buf, valtype_name(ft->result.valtypes[i])); if (i != ft->result.num_valtypes-1) { cursor_push_str(buf, ", "); } } cursor_push_c_str(buf, ")"); } static const char *functype_str(struct functype *ft, char *buf, int buflen) { struct cursor cur; if (buflen == 0) return ""; buf[buflen-1] = 0; make_cursor((u8*)buf, (u8*)buf + buflen-1, &cur); _functype_str(ft, &cur); return (const char*)buf; } static void print_functype(struct functype *ft) { static char buf[0xFF]; printf("%s\n", functype_str(ft, buf, sizeof(buf))); } static void print_type_section(struct typesec *typesec) { u32 i; printf("%d functypes:\n", typesec->num_functypes); for (i = 0; i < typesec->num_functypes; i++) { printf(" "); print_functype(&typesec->functypes[i]); } } static void print_func_section(struct funcsec *funcsec) { printf("%d functions\n", funcsec->num_indices); /* printf(" "); for (i = 0; i < funcsec->num_indices; i++) { printf("%d ", funcsec->type_indices[i]); } printf("\n"); */ } __attribute__((unused)) static const char *exportdesc_name(enum exportdesc desc) { switch (desc) { case export_func: return "function"; case export_table: return "table"; case export_mem: return "memory"; case export_global: return "global"; } return "unknown"; } static void print_import(struct import *import) { (void)import; printf("%s %s\n", import->module_name, import->name); } static void print_import_section(struct importsec *importsec) { u32 i; printf("%d imports:\n", importsec->num_imports); for (i = 0; i < importsec->num_imports; i++) { printf(" "); print_import(&importsec->imports[i]); } } static void print_limits(struct limits *limits) { switch (limits->type) { case limit_min: printf("%d", limits->min); break; case limit_min_max: printf("%d-%d", limits->min, limits->max); break; } } static void print_memory_section(struct memsec *memory) { u32 i; struct limits *mem; printf("%d memory:\n", memory->num_mems); for (i = 0; i < memory->num_mems; i++) { mem = &memory->mems[i]; printf(" "); print_limits(mem); printf("\n"); } } static void print_table_section(struct tablesec *section) { u32 i; struct table *table; printf("%d tables:\n", section->num_tables); for (i = 0; i < section->num_tables; i++) { table = §ion->tables[i]; printf(" "); printf("%s: ", reftype_name(table->reftype)); print_limits(&table->limits); printf("\n"); } } static int count_imports(struct module *module, enum import_type *typ) { u32 i, count = 0; struct import *import; struct importsec *imports; if (!was_section_parsed(module, section_import)) return 0; imports = &module->import_section; if (typ == NULL) return imports->num_imports; for (i = 0; i < imports->num_imports; i++) { import = &imports->imports[i]; if (import->desc.type == *typ) { count++; } } return count; } static INLINE int count_imported_functions(struct module *module) { enum import_type typ = import_func; return count_imports(module, &typ); } static void print_element_section(struct elemsec *section) { printf("%d elements\n", section->num_elements); } static void print_start_section(struct module *module) { u32 fn = module->start_section.start_fn; printf("start function: %d <%s>\n", fn, get_function_name(module, fn)); } static void print_export_section(struct exportsec *exportsec) { u32 i; printf("%d exports:\n", exportsec->num_exports); for (i = 0; i < exportsec->num_exports; i++) { printf(" "); printf("%s %s %d\n", exportdesc_name(exportsec->exports[i].desc), exportsec->exports[i].name, exportsec->exports[i].index); } } /* static void print_local(struct local *local) { debug("%d %s\n", local->n, valtype_name(local->valtype)); } static void print_func(struct wasm_func *func) { int i; debug("func locals (%d): \n", func->num_locals); for (i = 0; i < func->num_locals; i++) { print_local(&func->locals[i]); } debug("%d bytes of code\n", func->code_len); } */ static void print_global_section(struct globalsec *section) { printf("%d globals\n", section->num_globals); } static void print_code_section(struct codesec *codesec) { printf("%d code segments\n", codesec->num_funcs); /* for (i = 0; i < codesec->num_funcs; i++) { print_func(&codesec->funcs[i]); } */ } static void print_data_section(struct datasec *section) { printf("%d data segments\n", section->num_datas); } static void print_custom_section(struct customsec *section) { printf("custom (%s) %d bytes\n", section->name, section->data_len); } static void print_section(struct module *module, enum section_tag section) { u32 i; switch (section) { case section_custom: for (i = 0; i < module->custom_sections; i++) { print_custom_section(&module->custom_section[i]); } break; case section_type: print_type_section(&module->type_section); break; case section_import: print_import_section(&module->import_section); break; case section_function: print_func_section(&module->func_section); break; case section_table: print_table_section(&module->table_section); break; case section_memory: print_memory_section(&module->memory_section); break; case section_global: print_global_section(&module->global_section); break; case section_export: print_export_section(&module->export_section); break; case section_start: print_start_section(module); break; case section_element: print_element_section(&module->element_section); break; case section_code: print_code_section(&module->code_section); break; case section_data: print_data_section(&module->data_section); break; case section_data_count: printf("data count %d\n", module->data_section.num_datas); break; case section_name: printf("todo: print name section\n"); break; case num_sections: assert(0); break; } } static void print_module(struct module *module) { u32 i; enum section_tag section; for (i = 0; i < num_sections; i++) { section = (enum section_tag)i; if (was_section_parsed(module, section)) { print_section(module, section); } } } static int leb128_write(struct cursor *write, unsigned int value) { unsigned char byte; while (1) { byte = value & 0x7F; value >>= 7; if (value == 0) { if (!cursor_push_byte(write, byte)) return 0; return 1; } else { if (!cursor_push_byte(write, byte | 0x80)) return 0; } } } #define BYTE_AT(type, i, shift) (((type)(p[i]) & 0x7f) << (shift)) #define LEB128_1(type) (BYTE_AT(type, 0, 0)) #define LEB128_2(type) (BYTE_AT(type, 1, 7) | LEB128_1(type)) #define LEB128_3(type) (BYTE_AT(type, 2, 14) | LEB128_2(type)) #define LEB128_4(type) (BYTE_AT(type, 3, 21) | LEB128_3(type)) #define LEB128_5(type) (BYTE_AT(type, 4, 28) | LEB128_4(type)) static inline int shiftmask32(u32 val) { return val & 31; } static inline int shiftmask64(u64 val) { return val & 63; } static INLINE int parse_i64(struct cursor *read, uint64_t *val) { u8 shift; u8 byte; *val = 0; shift = 0; do { if (!pull_byte(read, &byte)) return 0; *val |= (byte & 0x7FULL) << shift; shift += 7; } while ((byte & 0x80) != 0); /* sign bit of byte is second high-order bit (0x40) */ if ((shift < 64) && (byte & 0x40)) *val |= (0xFFFFFFFFFFFFFFFF << shift); return 1; } static INLINE int uleb128_read(struct cursor *read, unsigned int *val) { unsigned int shift = 0; u8 byte; *val = 0; for (;;) { if (!pull_byte(read, &byte)) return 0; *val |= (0x7F & byte) << shift; if ((0x80 & byte) == 0) break; shift += 7; } return 1; } static INLINE int sleb128_read(struct cursor *read, signed int *val) { int shift; u8 byte; *val = 0; shift = 0; do { if (!pull_byte(read, &byte)) return 0; *val |= ((byte & 0x7F) << shift); shift += 7; } while ((byte & 0x80) != 0); /* sign bit of byte is second high-order bit (0x40) */ if ((shift < 32) && (byte & 0x40)) *val |= (0xFFFFFFFF << shift); return 1; } /* static INLINE int uleb128_read(struct cursor *read, unsigned int *val) { unsigned char p[6] = {0}; *val = 0; if (pull_byte(read, &p[0]) && (p[0] & 0x80) == 0) { *val = LEB128_1(unsigned int); if (p[0] == 0x7F) assert((int)*val == -1); return 1; } else if (pull_byte(read, &p[1]) && (p[1] & 0x80) == 0) { *val = LEB128_2(unsigned int); return 2; } else if (pull_byte(read, &p[2]) && (p[2] & 0x80) == 0) { *val = LEB128_3(unsigned int); return 3; } else if (pull_byte(read, &p[3]) && (p[3] & 0x80) == 0) { *val = LEB128_4(unsigned int); return 4; } else if (pull_byte(read, &p[4]) && (p[4] & 0x80) == 0) { if (!(p[4] & 0xF0)) { *val = LEB128_5(unsigned int); return 5; } //printf("%02X & 0xF0\n", p[4] & 0xF0); } return 0; } */ static INLINE int parse_int(struct cursor *read, int *val) { return sleb128_read(read, val); } static INLINE int parse_u32(struct cursor *read, u32 *val) { return uleb128_read(read, val); } static INLINE int read_f32(struct cursor *read, float *val) { return cursor_pull(read, (u8*)val, 4); } static INLINE int read_f64(struct cursor *read, double *val) { return cursor_pull(read, (u8*)val, 8); } static int parse_section_tag(struct cursor *cur, enum section_tag *section) { unsigned char byte; unsigned char *start; assert(section); start = cur->p; if (!pull_byte(cur, &byte)) { return 0; } if (byte >= num_sections) { cur->p = start; return 0; } *section = (enum section_tag)byte; return 1; } static int parse_valtype(struct wasm_parser *p, enum valtype *valtype) { unsigned char *start; start = p->cur.p; if (unlikely(!pull_byte(&p->cur, (unsigned char*)valtype))) { return parse_err(p, "valtype tag oob"); } if (unlikely(!is_valtype((unsigned char)*valtype))) { //cursor_print_around(&p->cur, 10); p->cur.p = start; return parse_err(p, "0x%02x is not a valid valtype tag", *valtype); } return 1; } static int parse_result_type(struct wasm_parser *p, struct resulttype *rt) { u32 i, elems; enum valtype valtype; unsigned char *start; rt->num_valtypes = 0; rt->valtypes = 0; start = p->mem.p; if (unlikely(!parse_u32(&p->cur, &elems))) { parse_err(p, "vec len"); return 0; } for (i = 0; i < elems; i++) { if (unlikely(!parse_valtype(p, &valtype))) { parse_err(p, "valtype #%d", i); p->mem.p = start; return 0; } if (unlikely(!cursor_push_byte(&p->mem, (unsigned char)valtype))) { parse_err(p, "valtype push data OOM #%d", i); p->mem.p = start; return 0; } } rt->num_valtypes = elems; rt->valtypes = start; return 1; } static int parse_func_type(struct wasm_parser *p, struct functype *func) { if (unlikely(!consume_byte(&p->cur, FUNC_TYPE_TAG))) { parse_err(p, "type tag"); return 0; } if (unlikely(!parse_result_type(p, &func->params))) { parse_err(p, "params"); return 0; } if (unlikely(!parse_result_type(p, &func->result))) { parse_err(p, "result"); return 0; } return 1; } static int parse_name(struct wasm_parser *p, const char **name) { u32 bytes; if (unlikely(!parse_u32(&p->cur, &bytes))) { parse_err(p, "name len"); return 0; } if (unlikely(!pull_data_into_cursor(&p->cur, &p->mem, (unsigned char**)name, bytes))) { parse_err(p, "name string"); return 0; } if (unlikely(!cursor_push_byte(&p->mem, 0))) { parse_err(p, "name null byte"); return 0; } return 1; } static INLINE int is_valid_name_subsection(u8 tag) { return tag < num_name_subsections; } static int parse_export_desc(struct wasm_parser *p, enum exportdesc *desc) { unsigned char byte; if (!pull_byte(&p->cur, &byte)) { parse_err(p, "export desc byte eof"); return 0; } switch((enum exportdesc)byte) { case export_func: case export_table: case export_mem: case export_global: *desc = (enum exportdesc)byte; return 1; } parse_err(p, "invalid tag: %x", byte); return 0; } static int parse_export(struct wasm_parser *p, struct wexport *export) { if (!parse_name(p, &export->name)) { parse_err(p, "export name"); return 0; } if (!parse_export_desc(p, &export->desc)) { parse_err(p, "export desc"); return 0; } if (!parse_u32(&p->cur, &export->index)) { parse_err(p, "export index"); return 0; } return 1; } static int parse_local_def(struct wasm_parser *p, struct local_def *def) { if (unlikely(!parse_u32(&p->cur, &def->num_types))) { debug("fail parse local def\n"); return parse_err(p, "n"); } if (unlikely(!parse_valtype(p, &def->type))) { debug("fail parse valtype\n"); return parse_err(p, "valtype"); } return 1; } static int parse_vector(struct wasm_parser *p, int item_size, u32 *elems, void **items) { if (!parse_u32(&p->cur, elems)) { return parse_err(p, "len"); } *items = cursor_alloc(&p->mem, *elems * item_size); if (*items == NULL) { parse_err(p, "vector alloc oom. item_size:%d elems:%d", item_size, *elems); return 0; } return 1; } static int parse_nameassoc(struct wasm_parser *p, struct nameassoc *assoc) { if (!parse_u32(&p->cur, &assoc->index)) return parse_err(p, "index"); if (!parse_name(p, &assoc->name)) return parse_err(p, "name"); //debug("parsed nameassoc %d %s\n", assoc->index, assoc->name); return 1; } static int parse_namemap(struct wasm_parser *p, struct namemap *map) { u32 i; if (!parse_vector(p, sizeof(struct nameassoc), &map->num_names, (void**)&map->names)) { return parse_err(p, "parse funcmap vec"); } for (i = 0; i < map->num_names; i++) { if (!parse_nameassoc(p, &map->names[i])) { return parse_err(p, "name assoc %d/%d", i+1, map->num_names); } } return 1; } static int parse_name_subsection(struct wasm_parser *p, struct namesec *sec, u32 *size) { u8 tag; u8 *start = p->cur.p; if (!pull_byte(&p->cur, &tag)) return parse_err(p, "name subsection tag oob?"); if (!is_valid_name_subsection(tag)) return parse_err(p, "invalid subsection tag 0x%02x", tag); if (!parse_u32(&p->cur, size)) return parse_err(p, "subsection size"); // include tag and size in size *size += p->cur.p - start; switch((enum name_subsection_tag)tag) { case name_subsection_module: if (!parse_name(p, &sec->module_name)) return parse_err(p, "parse module name"); sec->parsed |= 1 << name_subsection_module; return 1; case name_subsection_funcs: if (!parse_namemap(p, &sec->func_names)) return parse_err(p, "func namemap"); sec->parsed |= 1 << name_subsection_funcs; return 1; case name_subsection_locals: debug("TODO: parse local name subsection\n"); return 1; case num_name_subsections: return parse_err(p, "impossibru"); } return parse_err(p, "unknown name subsection: 0x%02x", tag); } static int parse_name_section(struct wasm_parser *p, struct namesec *sec, struct customsec *customsec) { int i; u32 size, subsection_size; subsection_size = 0; size = 0; i = 0; for (; i < 3; i++) { if (size == customsec->data_len) { break; } else if (size > customsec->data_len) { return parse_err(p, "parse_name_section did not parse" "the correct number of bytes. It parsed %d bytes" " but %d was expected.", size, customsec->data_len); } if (!parse_name_subsection(p, sec, &subsection_size)) return parse_err(p, "name subsection %d", i); size += subsection_size; } p->module.parsed |= (1 << section_name); return 1; } static int parse_func(struct wasm_parser *p, struct wasm_func *func) { struct local_def *defs; u32 i, size; u8 *start; if (!parse_u32(&p->cur, &size)) { return parse_err(p, "code size"); } start = p->cur.p; defs = (struct local_def*)p->mem.p; if (!parse_u32(&p->cur, &func->num_local_defs)) return parse_err(p, "read locals vec"); if (!cursor_alloc(&p->mem, sizeof(*defs) * func->num_local_defs)) return parse_err(p, "oom alloc param locals"); if (p->cur.p > p->cur.end) return parse_err(p, "corrupt functype?"); for (i = 0; i < func->num_local_defs; i++) { if (!parse_local_def(p, &defs[i])) { return parse_err(p, "local #%d", i); } } func->local_defs = defs; func->code.code_len = (int)(size - (p->cur.p - start)); if (!pull_data_into_cursor(&p->cur, &p->mem, &func->code.code, func->code.code_len)) { return parse_err(p, "code oom"); } if (!(func->code.code[func->code.code_len-1] == i_end)) { return parse_err(p, "no end tag (corruption?)"); } return 1; } static INLINE int count_internal_functions(struct module *module) { return !was_section_parsed(module, section_code) ? 0 : module->code_section.num_funcs; } static int parse_code_section(struct wasm_parser *p, struct codesec *code_section) { struct wasm_func *funcs; u32 i; if (!parse_vector(p, sizeof(*funcs), &code_section->num_funcs, (void**)&funcs)) { return parse_err(p, "funcs"); } for (i = 0; i < code_section->num_funcs; i++) { if (!parse_func(p, &funcs[i])) { return parse_err(p, "func #%d", i); } } code_section->funcs = funcs; return 1; } static int is_valid_reftype(unsigned char reftype) { switch ((enum reftype)reftype) { case funcref: return 1; case externref: return 1; } return 0; } static int parse_reftype(struct wasm_parser *p, enum reftype *reftype) { u8 tag; if (!pull_byte(&p->cur, &tag)) { parse_err(p, "reftype"); return 0; } if (!is_valid_reftype(tag)) { //cursor_print_around(&p->cur, 10); parse_err(p, "invalid reftype: 0x%02x", tag); return 0; } *reftype = (enum reftype)tag; return 1; } static int parse_export_section(struct wasm_parser *p, struct exportsec *export_section) { struct wexport *exports; u32 elems, i; if (!parse_vector(p, sizeof(*exports), &elems, (void**)&exports)) { parse_err(p, "vector"); return 0; } for (i = 0; i < elems; i++) { if (!parse_export(p, &exports[i])) { parse_err(p, "export #%d", i); return 0; } } export_section->num_exports = elems; export_section->exports = exports; return 1; } static int parse_limits(struct wasm_parser *p, struct limits *limits) { unsigned char tag; if (!pull_byte(&p->cur, &tag)) { return parse_err(p, "oob"); } if (tag != limit_min && tag != limit_min_max) { return parse_err(p, "invalid tag %02x", tag); } if (!parse_u32(&p->cur, &limits->min)) { return parse_err(p, "min"); } if (tag == limit_min) return 1; if (!parse_u32(&p->cur, &limits->max)) { return parse_err(p, "max"); } return 1; } static int parse_table(struct wasm_parser *p, struct table *table) { if (!parse_reftype(p, &table->reftype)) { return parse_err(p, "reftype"); } if (!parse_limits(p, &table->limits)) { return parse_err(p, "limits"); } return 1; } static int parse_mut(struct wasm_parser *p, enum mut *mut) { if (consume_byte(&p->cur, mut_const)) { *mut = mut_const; return 1; } if (consume_byte(&p->cur, mut_var)) { *mut = mut_var; return 1; } return parse_err(p, "unknown mut %02x", *p->cur.p); } static int parse_globaltype(struct wasm_parser *p, struct globaltype *g) { if (!parse_valtype(p, &g->valtype)) { return parse_err(p, "valtype"); } return parse_mut(p, &g->mut); } static INLINE void make_expr_parser(struct errors *errs, struct cursor *code, struct expr_parser *p) { p->interp = NULL; p->code = code; p->errs = errs; p->stack = NULL; } /* static void print_code(u8 *code, int code_len) { struct cursor c; struct expr_parser parser; struct errors errs; struct instr op; u8 tag; errs.enabled = 0; make_expr_parser(&errs, &c, &parser); make_cursor(code, code + code_len, &c); for (;;) { if (!pull_byte(&c, &tag)) { break; } printf("%s ", instr_name(tag)); if (!parse_instr(&parser, tag, &op)) { break; } } printf("\n"); } */ static INLINE int is_const_instr(u8 tag) { switch ((enum const_instr)tag) { case ci_global_get: case ci_ref_null: case ci_ref_func: case ci_const_i32: case ci_const_i64: case ci_const_f32: case ci_end: case ci_const_f64: return 1; } return 0; } static INLINE int cursor_push_nullval(struct cursor *stack) { struct val val; val.type = val_ref_null; return cursor_pushval(stack, &val); } static INLINE const char *bulk_op_name(struct bulk_op *op) { switch (op->tag) { case i_memory_fill: return "memory.fill"; case i_memory_copy: return "memory.copy"; case i_table_init: return "table.init"; case i_elem_drop: return "elem.drop"; case i_table_copy: return "table.copy"; case i_table_grow: return "table.grow"; case i_table_size: return "table.size"; case i_table_fill: return "table.fill"; } return "?"; } static const char *show_instr(struct instr *instr) { struct cursor buf; static char buffer[64]; static char tmp[128]; int len, i; buffer[sizeof(buffer)-1] = 0; make_cursor((u8*)buffer, (u8*)buffer + sizeof(buffer) - 1, &buf); cursor_push_str(&buf, instr_name(instr->tag)); len = (int)(buf.p - buf.start); for (i = 0; i < 14-len; i++) cursor_push_byte(&buf, ' '); switch (instr->tag) { // two-byte instrs case i_memory_size: case i_memory_grow: sprintf(tmp, "0x%02x", instr->memidx); cursor_push_str(&buf, tmp); break; case i_block: case i_loop: case i_if: break; case i_else: case i_end: break; case i_call: case i_local_get: case i_local_set: case i_local_tee: case i_global_get: case i_global_set: case i_br: case i_br_if: case i_i32_const: case i_ref_func: case i_table_set: case i_table_get: sprintf(tmp, "%d", instr->i32); cursor_push_str(&buf, tmp); break; case i_i64_const: sprintf(tmp, "%" PRId64, instr->i64); cursor_push_str(&buf, tmp); break; case i_ref_null: sprintf(tmp, "%s", reftype_name(instr->reftype)); cursor_push_str(&buf, tmp); break; case i_i32_load: case i_i64_load: case i_f32_load: case i_f64_load: case i_i32_load8_s: case i_i32_load8_u: case i_i32_load16_s: case i_i32_load16_u: case i_i64_load8_s: case i_i64_load8_u: case i_i64_load16_s: case i_i64_load16_u: case i_i64_load32_s: case i_i64_load32_u: case i_i32_store: case i_i64_store: case i_f32_store: case i_f64_store: case i_i32_store8: case i_i32_store16: case i_i64_store8: case i_i64_store16: case i_i64_store32: sprintf(tmp, "%d %d", instr->memarg.offset, instr->memarg.align); cursor_push_str(&buf, tmp); break; case i_selects: break; case i_br_table: break; case i_call_indirect: sprintf(tmp, "%d %d", instr->call_indirect.typeidx, instr->call_indirect.tableidx); cursor_push_str(&buf, tmp); break; case i_f32_const: sprintf(tmp, "%f", instr->f32); cursor_push_str(&buf, tmp); break; case i_f64_const: sprintf(tmp, "%f", instr->f64); cursor_push_str(&buf, tmp); break; // single-tag ops case i_unreachable: case i_nop: case i_return: case i_drop: case i_select: case i_i32_eqz: case i_i32_eq: case i_i32_ne: case i_i32_lt_s: case i_i32_lt_u: case i_i32_gt_s: case i_i32_gt_u: case i_i32_le_s: case i_i32_le_u: case i_i32_ge_s: case i_i32_ge_u: case i_i64_eqz: case i_i64_eq: case i_i64_ne: case i_i64_lt_s: case i_i64_lt_u: case i_i64_gt_s: case i_i64_gt_u: case i_i64_le_s: case i_i64_le_u: case i_i64_ge_s: case i_i64_ge_u: case i_f32_eq: case i_f32_ne: case i_f32_lt: case i_f32_gt: case i_f32_le: case i_f32_ge: case i_f64_eq: case i_f64_ne: case i_f64_lt: case i_f64_gt: case i_f64_le: case i_f64_ge: case i_i32_clz: case i_i32_ctz: case i_i32_popcnt: case i_i32_add: case i_i32_sub: case i_i32_mul: case i_i32_div_s: case i_i32_div_u: case i_i32_rem_s: case i_i32_rem_u: case i_i32_and: case i_i32_or: case i_i32_xor: case i_i32_shl: case i_i32_shr_s: case i_i32_shr_u: case i_i32_rotl: case i_i32_rotr: case i_i64_clz: case i_i64_ctz: case i_i64_popcnt: case i_i64_add: case i_i64_sub: case i_i64_mul: case i_i64_div_s: case i_i64_div_u: case i_i64_rem_s: case i_i64_rem_u: case i_i64_and: case i_i64_or: case i_i64_xor: case i_i64_shl: case i_i64_shr_s: case i_i64_shr_u: case i_i64_rotl: case i_i64_rotr: case i_f32_abs: case i_f32_neg: case i_f32_ceil: case i_f32_floor: case i_f32_trunc: case i_f32_nearest: case i_f32_sqrt: case i_f32_add: case i_f32_sub: case i_f32_mul: case i_f32_div: case i_f32_min: case i_f32_max: case i_f32_copysign: case i_f64_abs: case i_f64_neg: case i_f64_ceil: case i_f64_floor: case i_f64_trunc: case i_f64_nearest: case i_f64_sqrt: case i_f64_add: case i_f64_sub: case i_f64_mul: case i_f64_div: case i_f64_min: case i_f64_max: case i_f64_copysign: case i_i32_wrap_i64: case i_i32_trunc_f32_s: case i_i32_trunc_f32_u: case i_i32_trunc_f64_s: case i_i32_trunc_f64_u: case i_i64_extend_i32_s: case i_i64_extend_i32_u: case i_i64_trunc_f32_s: case i_i64_trunc_f32_u: case i_i64_trunc_f64_s: case i_i64_trunc_f64_u: case i_f32_convert_i32_s: case i_f32_convert_i32_u: case i_f32_convert_i64_s: case i_f32_convert_i64_u: case i_f32_demote_f64: case i_f64_convert_i32_s: case i_f64_convert_i32_u: case i_f64_convert_i64_s: case i_f64_convert_i64_u: case i_f64_promote_f32: case i_i32_reinterpret_f32: case i_i64_reinterpret_f64: case i_f32_reinterpret_i32: case i_f64_reinterpret_i64: case i_i32_extend8_s: case i_i32_extend16_s: case i_i64_extend8_s: case i_i64_extend16_s: case i_i64_extend32_s: case i_ref_is_null: break; case i_bulk_op: cursor_push_str(&buf, bulk_op_name(&instr->bulk_op)); break; } cursor_push_byte(&buf, 0); return buffer; } static int eval_const_instr(struct instr *instr, struct errors *errs, struct cursor *stack) { //debug("eval_const_instr %s\n", show_instr(instr)); switch ((enum const_instr)instr->tag) { case ci_global_get: return note_error(errs, stack, "todo: global_get inside global"); case ci_ref_null: if (unlikely(!cursor_push_nullval(stack))) { return note_error(errs, stack, "couldn't push null"); } return 1; case ci_ref_func: if (unlikely(!cursor_push_funcref(stack, instr->i32))) { return note_error(errs, stack, "couldn't push funcref"); } return 1; case ci_const_i32: if (unlikely(!cursor_push_i32(stack, instr->i32))) { return note_error(errs, stack, "global push i32 const"); } return 1; case ci_const_i64: if (unlikely(!cursor_push_i64(stack, instr->i64))) { return note_error(errs, stack, "global push i64 const"); } return 1; case ci_const_f32: if (unlikely(!cursor_push_f32(stack, instr->f32))) { return note_error(errs, stack, "global push f32 const"); } return 1; case ci_end: return note_error(errs, stack, "unexpected end tag"); case ci_const_f64: if (unlikely(!cursor_push_f64(stack, instr->f64))) { return note_error(errs, stack, "global push f64 const"); } return 1; } return note_error(errs, stack, "non-const expr instr %s", instr_name(instr->tag)); } static int parse_const_expr(struct expr_parser *p, struct expr *expr) { u8 tag; struct instr instr; expr->code = p->code->p; while (1) { if (unlikely(!pull_byte(p->code, &tag))) { return note_error(p->errs, p->code, "oob"); } if (unlikely(!is_const_instr(tag))) { //cursor_print_around(p->code, 20); return note_error(p->errs, p->code, "invalid const expr instruction: '%s'", instr_name(tag)); } if (tag == i_end) { expr->code_len = (int)(p->code->p - expr->code); return 1; } if (unlikely(!parse_instr(p, tag, &instr))) { return note_error(p->errs, p->code, "couldn't parse const expr instr '%s'", instr_name(tag)); } if (p->stack && unlikely(!eval_const_instr(&instr, p->errs, p->stack))) { return note_error(p->errs, p->code, "eval const instr"); } } return 0; } static INLINE void make_const_expr_evaluator(struct errors *errs, struct cursor *code, struct cursor *stack, struct expr_parser *parser) { parser->interp = NULL; parser->stack = stack; parser->code = code; parser->errs = errs; } static INLINE void make_const_expr_parser(struct wasm_parser *p, struct expr_parser *parser) { parser->interp = NULL; parser->stack = NULL; parser->code = &p->cur; parser->errs = &p->errs; } static INLINE int eval_const_expr(struct expr *expr, struct errors *errs, struct cursor *stack) { struct cursor code; struct expr expr_out; struct expr_parser parser; make_cursor(expr->code, expr->code + expr->code_len, &code); make_const_expr_evaluator(errs, &code, stack, &parser); return parse_const_expr(&parser, &expr_out); } static INLINE int eval_const_val(struct expr *expr, struct errors *errs, struct cursor *stack, struct val *val) { if (!eval_const_expr(expr, errs, stack)) { return note_error(errs, stack, "eval const expr"); } if (!cursor_popval(stack, val)) { return note_error(errs, stack, "no val to pop?"); } if (cursor_dropval(stack)) { return note_error(errs, stack, "stack not empty"); } return 1; } static int parse_global(struct wasm_parser *p, struct global *global) { struct expr_parser parser; struct cursor stack; stack.start = p->mem.p; stack.p = p->mem.p; stack.end = p->mem.end; make_const_expr_evaluator(&p->errs, &p->cur, &stack, &parser); if (!parse_globaltype(p, &global->type)) { return parse_err(p, "type"); } if (!parse_const_expr(&parser, &global->init)) { return parse_err(p, "init code"); } if (!cursor_popval(&stack, &global->val)) { return parse_err(p, "couldn't eval global expr"); } return 1; } static int parse_global_section(struct wasm_parser *p, struct globalsec *global_section) { struct global *globals; u32 elems, i; if (!parse_vector(p, sizeof(*globals), &elems, (void**)&globals)) { return parse_err(p, "globals vector"); } for (i = 0; i < elems; i++) { if (!parse_global(p, &globals[i])) { return parse_err(p, "global #%d/%d", i+1, elems); } } global_section->num_globals = elems; global_section->globals = globals; return 1; } static INLINE void make_interp_expr_parser(struct wasm_interp *interp, struct expr_parser *p) { assert(interp); p->interp = interp; p->code = interp_codeptr(interp); p->errs = &interp->errors; assert(p->code); } static int push_label_checkpoint(struct wasm_interp *interp, struct label **label, u8 start_tag, u8 end_tag); static int parse_instrs_until_at(struct expr_parser *p, u8 stop_instr, struct expr *expr, u8 *stopped_at) { u8 tag; struct instr op; #ifdef DEBUG static int dbg = 0; int dbg_inst = dbg++; #endif expr->code = p->code->p; expr->code_len = 0; debug("%04lX parse_instrs_until %d for %s starting\n", p->code->p - p->code->start, dbg_inst, instr_name(stop_instr)); for (;;) { if (!pull_byte(p->code, &tag)) return note_error(p->errs, p->code, "oob"); if ((tag != i_if && tag == stop_instr) || (stop_instr == i_if && (tag == i_else || tag == i_end))) { //debug("parse_instrs_until ending\n"); expr->code_len = (int)(p->code->p - expr->code); *stopped_at = tag; debug("%04lX parse_instrs_until @%s %d for %s done\n", p->code->p - p->code->start, instr_name(tag), dbg_inst, instr_name(stop_instr)); #ifdef DEBUG dbg--; #endif return 1; } debug("%04lX parsing instr %s (0x%02x)\n", p->code->p - 1 - p->code->start, instr_name(tag), tag); if (!parse_instr(p, tag, &op)) { return note_error(p->errs, p->code, "parse %s instr (0x%x)", instr_name(tag), tag); } } } static INLINE int parse_instrs_until(struct expr_parser *p, u8 stop_instr, struct expr *expr) { u8 at; return parse_instrs_until_at(p, stop_instr, expr, &at); } static int parse_elem_func_inits(struct wasm_parser *p, struct elem *elem) { u32 index, i; struct expr *expr; if (!parse_u32(&p->cur, &elem->num_inits)) return parse_err(p, "func indices vec read fail"); if (!(elem->inits = cursor_alloc(&p->mem, elem->num_inits * sizeof(struct expr)))) { return parse_err(p, "couldn't alloc vec(funcidx) for elem"); } for (i = 0; i < elem->num_inits; i++) { expr = &elem->inits[i]; expr->code = p->mem.p; if (!parse_u32(&p->cur, &index)) return parse_err(p, "func index %d read fail", i); if (!cursor_push_byte(&p->mem, i_ref_func)) return parse_err(p, "push ref_func instr oob for %d", i); if (!leb128_write(&p->mem, index)) return parse_err(p, "push ref_func u32 index oob for %d", i); if (!cursor_push_byte(&p->mem, i_end)) return parse_err(p, "push i_end for init %d", i); expr->code_len = (int)(p->mem.p - expr->code); } return 1; } static int parse_element(struct wasm_parser *p, struct elem *elem) { u8 tag = 0; struct expr_parser expr_parser; (void)elem; make_expr_parser(&p->errs, &p->cur, &expr_parser); if (!pull_byte(&p->cur, &tag)) return parse_err(p, "tag"); if (tag > 7) return parse_err(p, "expected tag 0x00 to 0x07, got 0x%02x", tag); switch (tag) { case 0x00: if (!parse_instrs_until(&expr_parser, i_end, &elem->offset)) return parse_err(p, "elem 0x00 offset expr"); // func inits if (!parse_elem_func_inits(p, elem)) return parse_err(p, "generate func index exprs"); elem->mode = elem_mode_active; elem->tableidx = 0; elem->reftype = funcref; break; default: return parse_err(p, "implement parse element 0x%02x", tag); } return 1; } static int parse_custom_section(struct wasm_parser *p, u32 size, struct customsec *section) { u8 *start; start = p->cur.p; if (p->module.custom_sections + 1 > MAX_CUSTOM_SECTIONS) return parse_err(p, "more than 32 custom sections!"); if (!parse_name(p, §ion->name)) return parse_err(p, "name"); section->data = p->cur.p; section->data_len = (int)(size - (p->cur.p - start)); debug("custom sec minus %ld\n", p->cur.p - start); if (!strcmp(section->name, "name")) { if (!parse_name_section(p, &p->module.name_section, section)) { return parse_err(p, "failed to parse name custom section"); } } else { p->cur.p += section->data_len; } p->module.custom_sections++; return 1; } static int parse_element_section(struct wasm_parser *p, struct elemsec *elemsec) { struct elem *elements; u32 count, i; if (!parse_vector(p, sizeof(struct elem), &count, (void**)&elements)) return parse_err(p, "elements vec"); for (i = 0; i < count; i++) { if (!parse_element(p, &elements[i])) return parse_err(p, "element %d of %d", i+1, count); } elemsec->num_elements = count; elemsec->elements = elements; return 1; } static int parse_memory_section(struct wasm_parser *p, struct memsec *memory_section) { struct limits *mems; u32 elems, i; if (!parse_vector(p, sizeof(*mems), &elems, (void**)&mems)) { return parse_err(p, "mems vector"); } for (i = 0; i < elems; i++) { if (!parse_limits(p, &mems[i])) { return parse_err(p, "memory #%d/%d", i+1, elems); } } memory_section->num_mems = elems; memory_section->mems = mems; return 1; } static int parse_start_section(struct wasm_parser *p, struct startsec *start_section) { if (!parse_u32(&p->cur, &start_section->start_fn)) { return parse_err(p, "start_fn index"); } return 1; } static INLINE int parse_byte_vector(struct wasm_parser *p, u8 **data, u32 *data_len) { if (!parse_u32(&p->cur, data_len)) { return parse_err(p, "len"); } if (p->cur.p + *data_len > p->cur.end) { return parse_err(p, "byte vector overflow"); } *data = p->cur.p; p->cur.p += *data_len; return 1; } static int parse_wdata(struct wasm_parser *p, struct wdata *data) { struct expr_parser parser; u8 tag; if (!pull_byte(&p->cur, &tag)) { return parse_err(p, "tag"); } if (tag > 2) { //cursor_print_around(&p->cur, 10); return parse_err(p, "invalid datasegment tag: 0x%x", tag); } make_const_expr_parser(p, &parser); switch (tag) { case 0: data->mode = datamode_active; data->active.mem_index = 0; if (!parse_const_expr(&parser, &data->active.offset_expr)) { return parse_err(p, "const expr"); } if (!parse_byte_vector(p, &data->bytes, &data->bytes_len)) { return parse_err(p, "bytes vector"); } break; case 1: data->mode = datamode_passive; if (!parse_byte_vector(p, &data->bytes, &data->bytes_len)) { return parse_err(p, "passive bytes vector"); } break; case 2: data->mode = datamode_active; if (!parse_u32(&p->cur, &data->active.mem_index)) { return parse_err(p, "read active data mem_index"); } if (!parse_const_expr(&parser, &data->active.offset_expr)) { return parse_err(p, "read active data (w/ mem_index) offset_expr"); } if (!parse_byte_vector(p, &data->bytes, &data->bytes_len)) { return parse_err(p, "active (w/ mem_index) bytes vector"); } break; } return 1; } static int parse_data_count_section(struct wasm_parser *p, struct datasec *section) { if (!parse_u32(&p->cur, §ion->num_datas)) return parse_err(p, "data count"); return 1; } static int parse_data_section(struct wasm_parser *p, struct datasec *section) { struct wdata *data; u32 elems, i; if (!parse_vector(p, sizeof(*data), &elems, (void**)&data)) return parse_err(p, "datas vector"); if (was_section_parsed(&p->module, section_data_count) && elems != section->num_datas) { return parse_err(p, "we got a data count section with %d " "elements but the data section says it has %d " "elements. what's up with that?", section->num_datas, elems); } for (i = 0; i < elems; i++) { if (!parse_wdata(p, &data[i])) { return parse_err(p, "data segment #%d/%d", i+1, elems); } } section->num_datas = elems; section->datas = data; return 1; } static int parse_table_section(struct wasm_parser *p, struct tablesec *table_section) { struct table *tables; u32 elems, i; if (!parse_vector(p, sizeof(*tables), &elems, (void**)&tables)) { return parse_err(p, "tables vector"); } for (i = 0; i < elems; i++) { if (!parse_table(p, &tables[i])) { parse_err(p, "table #%d/%d", i+1, elems); return 0; } } table_section->num_tables = elems; table_section->tables = tables; return 1; } static int parse_function_section(struct wasm_parser *p, struct funcsec *funcsec) { u32 i, elems, *indices; if (!parse_vector(p, sizeof(*indices), &elems, (void**)&indices)) { return parse_err(p, "indices"); } for (i = 0; i < elems; i++) { if (!parse_u32(&p->cur, &indices[i])) { parse_err(p, "typeidx #%d", i); return 0; } } funcsec->type_indices = indices; funcsec->num_indices = elems; return 1; } static int parse_import_table(struct wasm_parser *p, struct limits *limits) { if (!consume_byte(&p->cur, 0x70)) { parse_err(p, "elemtype != 0x70"); return 0; } if (!parse_limits(p, limits)) { parse_err(p, "limits"); return 0; } return 1; } static int parse_importdesc(struct wasm_parser *p, struct importdesc *desc) { u8 tag; if (!pull_byte(&p->cur, &tag)) { parse_err(p, "oom"); return 0; } desc->type = (enum import_type)tag; switch (desc->type) { case import_func: if (!parse_u32(&p->cur, &desc->typeidx)) { parse_err(p, "typeidx"); return 0; } return 1; case import_table: return parse_import_table(p, &desc->tabletype); case import_mem: if (!parse_limits(p, &desc->memtype)) { parse_err(p, "memtype limits"); return 0; } return 1; case import_global: if (!parse_globaltype(p, &desc->globaltype)) { parse_err(p, "globaltype"); return 0; } return 1; } parse_err(p, "unknown importdesc tag %02x", tag); return 0; } static int find_builtin(struct builtin *builtins, int num_builtins, const char *name) { struct builtin *b; int i; for (i = 0; i < num_builtins; i++) { b = &builtins[i]; if (!strcmp(b->name, name)) return i; } return -1; } static int parse_import(struct wasm_parser *p, struct import *import) { import->resolved_builtin = -1; if (!parse_name(p, &import->module_name)) return parse_err(p, "module name"); if (!parse_name(p, &import->name)) return parse_err(p, "name"); if (!parse_importdesc(p, &import->desc)) return parse_err(p, "desc"); if (import->desc.type == import_func) { import->resolved_builtin = find_builtin(p->builtins, p->num_builtins, import->name); } return 1; } static int parse_import_section(struct wasm_parser *p, struct importsec *importsec) { u32 elems, i; struct import *imports; if (!parse_vector(p, sizeof(*imports), &elems, (void**)&imports)) { return parse_err(p, "imports"); } for (i = 0; i < elems; i++) { if (!parse_import(p, &imports[i])) { return parse_err(p, "import #%d", i); } } importsec->imports = imports; importsec->num_imports = elems; return 1; } /* type section is just a vector of function types */ static int parse_type_section(struct wasm_parser *p, struct typesec *typesec) { u32 elems, i; struct functype *functypes; typesec->num_functypes = 0; typesec->functypes = NULL; if (!parse_vector(p, sizeof(*functypes), &elems, (void**)&functypes)) { parse_err(p, "functypes"); return 0; } for (i = 0; i < elems; i++) { if (!parse_func_type(p, &functypes[i])) { parse_err(p, "functype #%d", i); return 0; } } typesec->functypes = functypes; typesec->num_functypes = elems; return 1; } static int parse_section_by_tag(struct wasm_parser *p, enum section_tag tag, u32 size) { (void)size; switch (tag) { case section_custom: if (!parse_custom_section(p, size, &p->module.custom_section[p->module.custom_sections])) return parse_err(p, "custom section"); return 1; case section_type: if (!parse_type_section(p, &p->module.type_section)) { return parse_err(p, "type section"); } return 1; case section_import: if (!parse_import_section(p, &p->module.import_section)) { return parse_err(p, "import section"); } return 1; case section_function: if (!parse_function_section(p, &p->module.func_section)) { return parse_err(p, "function section"); } return 1; case section_table: if (!parse_table_section(p, &p->module.table_section)) { return parse_err(p, "table section"); } return 1; case section_memory: if (!parse_memory_section(p, &p->module.memory_section)) { return parse_err(p, "memory section"); } return 1; case section_global: if (!parse_global_section(p, &p->module.global_section)) { return parse_err(p, "global section"); } return 1; case section_export: if (!parse_export_section(p, &p->module.export_section)) { return parse_err(p, "export section"); } return 1; case section_start: if (!parse_start_section(p, &p->module.start_section)) { return parse_err(p, "start section"); } return 1; case section_element: if (!parse_element_section(p, &p->module.element_section)) { return parse_err(p, "element section"); } return 1; case section_code: if (!parse_code_section(p, &p->module.code_section)) { return parse_err(p, "code section"); } return 1; case section_data: if (!parse_data_section(p, &p->module.data_section)) return parse_err(p, "data section"); return 1; case section_data_count: if (!parse_data_count_section(p, &p->module.data_section)) return parse_err(p, "data count section"); return 1; default: return parse_err(p, "invalid section tag %d", tag); } return 1; } static const char *section_str(enum section_tag tag) { switch (tag) { case section_custom: return "custom"; case section_type: return "type"; case section_import: return "import"; case section_function: return "function"; case section_table: return "table"; case section_memory: return "memory"; case section_global: return "global"; case section_export: return "export"; case section_start: return "start"; case section_element: return "element"; case section_code: return "code"; case section_data: return "data"; default: return "invalid"; } } static int parse_section(struct wasm_parser *p) { enum section_tag tag; struct section; u32 bytes; if (!parse_section_tag(&p->cur, &tag)) { parse_err(p, "section tag"); return 2; } if (!parse_u32(&p->cur, &bytes)) { return parse_err(p, "section len"); } if (!parse_section_by_tag(p, tag, bytes)) { return parse_err(p, "%s (%d bytes)", section_str(tag), bytes); } p->module.parsed |= 1 << tag; return 1; } static struct builtin *builtin_func(struct builtin *builtins, u32 num_builtins, u32 ind) { if (unlikely(ind >= num_builtins)) { printf("UNUSUAL: invalid builtin index %d (max %d)\n", ind, num_builtins-1); return NULL; } return &builtins[ind]; } static const char *find_exported_function_name(struct module *module, u32 fn) { u32 i; struct wexport *export; if (!was_section_parsed(module, section_export)) return NULL; for (i = 0; i < module->export_section.num_exports; i++) { export = &module->export_section.exports[i]; if (export->desc == export_func && export->index == fn) { return export->name; } } return NULL; } static const char *find_debug_function_name(struct module *module, u32 fn) { u32 i; struct nameassoc *assoc; if (!was_name_section_parsed(module, name_subsection_funcs)) return NULL; for (i = 0; i < module->name_section.func_names.num_names; i++) { assoc = &module->name_section.func_names.names[i]; if (fn == assoc->index) { //debug("found fn debug name %d -> %s\n", fn, assoc->name); return assoc->name; } } debug("fn %d debug name not found\n", fn); return NULL; } static const char *find_function_name(struct module *module, u32 fn) { const char *name; if ((name = find_exported_function_name(module, fn))) { return name; } if ((name = find_debug_function_name(module, fn))) { return name; } return "unknown"; } static int count_fn_locals(struct func *func) { u32 i, num_locals = 0; num_locals += func->functype->params.num_valtypes; if (func->type == func_type_wasm) { // counts locals of the same type for (i = 0; i < func->wasm_func->num_local_defs; i++) { num_locals += func->wasm_func->local_defs[i].num_types; } } return num_locals; } static void make_builtin_func(struct func *func, const char *name, struct functype *type, struct builtin *builtin, u32 idx) { func->name = name; func->builtin = builtin; func->functype = type; func->type = func_type_builtin; func->num_locals = count_fn_locals(func); func->idx = idx; } static int make_func_lookup_table(struct wasm_parser *parser) { u32 i, num_imports, num_func_imports, num_internal_funcs, typeidx, fn; struct import *import; struct importsec *imports; struct func *func; struct builtin *builtin; fn = 0; imports = &parser->module.import_section; num_func_imports = count_imported_functions(&parser->module); num_internal_funcs = count_internal_functions(&parser->module); parser->module.num_funcs = num_func_imports + num_internal_funcs; if (!(parser->module.funcs = cursor_alloc(&parser->mem, sizeof(struct func) * parser->module.num_funcs))) { return parse_err(parser, "oom"); } /* imports */ num_imports = count_imports(&parser->module, NULL); debug("num_imports %d\n", num_imports); for (i = 0; i < num_imports; i++) { import = &imports->imports[i]; if (import->desc.type != import_func) continue; func = &parser->module.funcs[fn++]; if (import->resolved_builtin == -1) { debug("warning: %s not resolved\n", func->name); builtin = NULL; } else { builtin = builtin_func(parser->builtins, parser->num_builtins, import->resolved_builtin); } make_builtin_func( func, import->name, &parser->module.type_section.functypes[import->desc.typeidx], builtin, fn ); } /* module fns */ for (i = 0; i < num_internal_funcs; i++, fn++) { func = &parser->module.funcs[fn]; typeidx = parser->module.func_section.type_indices[i]; func->type = func_type_wasm; func->wasm_func = &parser->module.code_section.funcs[i]; func->functype = &parser->module.type_section.functypes[typeidx]; func->name = find_function_name(&parser->module, fn); func->num_locals = count_fn_locals(func); func->idx = fn; } assert(fn == parser->module.num_funcs); return 1; } int parse_wasm(struct wasm_parser *p) { p->module.parsed = 0; p->module.custom_sections = 0; if (!consume_bytes(&p->cur, WASM_MAGIC, sizeof(WASM_MAGIC))) { parse_err(p, "magic"); goto fail; } if (!consume_u32(&p->cur, WASM_VERSION)) { parse_err(p, "version"); goto fail; } while (1) { if (cursor_eof(&p->cur)) break; if (!parse_section(p)) { parse_err(p, "section"); goto fail; } } if (!make_func_lookup_table(p)) { return parse_err(p, "failed making func lookup table"); } //print_module(&p->module); debug("module parse success!\n\n"); return 1; fail: debug("\npartially parsed module:\n"); print_module(&p->module); debug("parse failure backtrace:\n"); print_error_backtrace(&p->errs); return 0; } static INLINE int interp_prep_binop(struct wasm_interp *interp, struct val *lhs, struct val *rhs, struct val *c, enum valtype typ) { c->type = typ; if (unlikely(!cursor_popval(&interp->stack, rhs))) return interp_error(interp, "couldn't pop first val"); if (unlikely(!cursor_popval(&interp->stack, lhs))) return interp_error(interp, "couldn't pop second val"); if (unlikely(lhs->type != typ || rhs->type != typ)) { return interp_error(interp, "type mismatch, %s or %s != %s", valtype_name(lhs->type), valtype_name(rhs->type), valtype_name(typ)); } return 1; } static INLINE int set_local(struct wasm_interp *interp, u32 ind, struct val *val) { struct callframe *frame; struct val *local; if (unlikely(!(frame = top_callframe(&interp->callframes)))) return interp_error(interp, "no callframe?"); if (unlikely(!(local = get_local(interp, ind)))) return interp_error(interp, "no local?"); memcpy(local, val, sizeof(*val)); return 1; } static INLINE int interp_local_tee(struct wasm_interp *interp, u32 index) { struct val *val; if (unlikely(!(val = stack_topval(interp)))) return interp_error(interp, "pop"); if (unlikely(!set_local(interp, index, val))) return interp_error(interp, "set local"); return 1; } static int interp_local_set(struct wasm_interp *interp, u32 index) { struct val val; if (unlikely(!interp_local_tee(interp, index))) return interp_error(interp, "tee set"); if (unlikely(!stack_popval(interp, &val))) return interp_error(interp, "pop"); return 1; } static INLINE int interp_local_get(struct wasm_interp *interp, u32 index) { struct val *val; if (unlikely(!(val = get_local(interp, index)))) { return interp_error(interp, "get local"); } return stack_pushval(interp, val); } static INLINE void make_i64_val(struct val *val, s64 v) { val->type = val_i64; val->num.i64 = v; } static INLINE int interp_i64_xor(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); c.num.i64 = lhs.num.i64 ^ rhs.num.i64; return stack_pushval(interp, &c); } /* static INLINE int interp_f32_min(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_f32))) return interp_error(interp, "binop prep"); c.num.f32 = lhs.num.f32 < rhs.num.f32 ? lhs.num.f32 : rhs.num.f32; return stack_pushval(interp, &c); } */ static INLINE int interp_f32_max(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_f32))) return interp_error(interp, "binop prep"); c.num.f32 = lhs.num.f32 > rhs.num.f32 ? lhs.num.f32 : rhs.num.f32; return stack_pushval(interp, &c); } static INLINE int interp_i64_div_u(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); if (rhs.num.u64 == 0) return interp_error(interp, "congrats, you divided by zero"); c.num.u64 = lhs.num.u64 / rhs.num.u64; return stack_pushval(interp, &c); } static int interp_i64_eqz(struct wasm_interp *interp) { struct val a, res; if (unlikely(!stack_pop_valtype(interp, val_i64, &a))) return interp_error(interp, "pop val"); res.type = val_i32; res.num.u32 = a.num.i64 == 0; return cursor_pushval(&interp->stack, &res); } static INLINE int interp_f32_sqrt(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f32(interp)))) return interp_error(interp, "pop"); val->num.f32 = sqrt(val->num.f32); return 1; } static INLINE int interp_f64_sqrt(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); val->num.f64 = sqrt(val->num.f64); return 1; } static INLINE int interp_f64_floor(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); val->num.f64 = floor(val->num.f64); return 1; } static INLINE int interp_f64_ceil(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); val->num.f64 = ceil(val->num.f64); return 1; } static INLINE int interp_f32_abs(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f32(interp)))) return interp_error(interp, "pop"); if (val->num.f32 >= 0) return 1; val->num.f32 = -val->num.f32; return 1; } static INLINE int interp_f64_neg(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); val->num.f64 = -val->num.f64; return 1; } static INLINE int interp_f64_abs(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); if (val->num.f64 >= 0) return 1; val->num.f64 = -val->num.f64; return 1; } static INLINE int interp_f64_div(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_f64))) return interp_error(interp, "binop prep"); if (rhs.num.f64 == 0) return interp_error(interp, "congrats, you divided by zero"); c.num.f64 = lhs.num.f64 / rhs.num.f64; return stack_pushval(interp, &c); } static INLINE int interp_f32_div(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_f32))) return interp_error(interp, "binop prep"); if (rhs.num.f32 == 0) return interp_error(interp, "congrats, you divided by zero"); c.num.f32 = lhs.num.f32 / rhs.num.f32; return stack_pushval(interp, &c); } static INLINE int interp_i32_div_s(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); if (rhs.num.i32 == 0) return interp_error(interp, "congrats, you divided by zero"); c.num.i32 = lhs.num.i32 / rhs.num.i32; return stack_pushval(interp, &c); } static INLINE int interp_i32_div_u(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); if (rhs.num.u32 == 0) return interp_error(interp, "congrats, you divided by zero"); c.num.u32 = lhs.num.u32 / rhs.num.u32; return stack_pushval(interp, &c); } const unsigned int ROTMASK = (CHAR_BIT*sizeof(uint32_t) - 1); // assumes width is a power of 2. static inline uint32_t rotl32 (uint32_t n, unsigned int c) { return (n << shiftmask32(c)) | (n >> shiftmask32(0 - c)); } static inline uint32_t rotr32 (uint32_t n, unsigned int c) { return (n >> shiftmask32(c)) | (n << shiftmask32(0 - c)); } static INLINE int interp_i32_rotr(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.u32 = rotr32(lhs.num.u32, rhs.num.u32); return stack_pushval(interp, &c); } static INLINE int interp_i32_rotl(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.u32 = rotl32(lhs.num.u32, rhs.num.u32); return stack_pushval(interp, &c); } static INLINE int interp_i64_const(struct wasm_interp *interp, s64 c) { struct val val; make_i64_val(&val, c); return cursor_pushval(&interp->stack, &val); } static INLINE int interp_i32_const(struct wasm_interp *interp, u32 c) { struct val val; make_i32_val(&val, c); return cursor_pushval(&interp->stack, &val); } static INLINE int interp_f64_const(struct wasm_interp *interp, double c) { struct val val; make_f64_val(&val, c); return stack_pushval(interp, &val); } static INLINE int interp_i32_and(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); return stack_push_i32(interp, lhs.num.u32 & rhs.num.u32); } static INLINE int interp_i64_and(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); return stack_push_i64(interp, lhs.num.u64 & rhs.num.u64); } #define BINOP(type, name, op) \ static INLINE int interp_##type##_##name(struct wasm_interp *interp) \ { \ struct val lhs, rhs, c; \ if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_##type))) \ return interp_error(interp, "binop prep"); \ c.num.type = lhs.num.type op rhs.num.type; \ return stack_pushval(interp, &c); \ } #define BINOP2(type, optype, name, op) \ static INLINE int interp_##type##_##name(struct wasm_interp *interp) \ { \ struct val lhs, rhs, c; \ if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_##type))) \ return interp_error(interp, "binop prep"); \ return stack_push_i32(interp, lhs.num.optype op rhs.num.optype); \ } BINOP(f64, mul, *) BINOP(f32, mul, *) BINOP(i32, mul, *) BINOP(i64, mul, *) BINOP(f64, sub, -) BINOP(f32, sub, -) BINOP(i32, sub, -) BINOP(i64, sub, -) BINOP(f64, add, +) BINOP(f32, add, +) BINOP(i32, add, +) BINOP(i64, add, +) BINOP(i32, or, |) BINOP(i64, or, |) BINOP2(i32, i32, lt_s, <) BINOP2(i64, i64, lt_s, <) BINOP2(i32, u32, lt_u, <) BINOP2(i64, u64, lt_u, <) BINOP2(f32, f32, lt, <) BINOP2(f64, f64, lt, <) BINOP2(i32, i32, gt_s, >) BINOP2(i64, i64, gt_s, >) BINOP2(i32, u32, gt_u, >) BINOP2(i64, u64, gt_u, >) BINOP2(f32, f32, gt, >) BINOP2(f64, f64, gt, >) BINOP2(i32, i32, le_s, <=) BINOP2(i64, i64, le_s, <=) BINOP2(i32, u32, le_u, <=) BINOP2(i64, u64, le_u, <=) BINOP2(f32, f32, le, <=) BINOP2(f64, f64, le, <=) BINOP2(i32, i32, ge_s, >=) BINOP2(i64, i64, ge_s, >=) BINOP2(i32, u32, ge_u, >=) BINOP2(i64, u64, ge_u, >=) BINOP2(f32, f32, ge, >=) BINOP2(f64, f64, ge, >=) BINOP2(f32, f32, eq, ==) BINOP2(f64, f64, eq, ==) BINOP2(f32, f32, ne, !=) BINOP2(f64, f64, ne, !=) static int interp_i32_rem_s(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.i32 = lhs.num.i32 % rhs.num.i32; return stack_pushval(interp, &c); } static int interp_i32_rem_u(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.u32 = lhs.num.u32 % rhs.num.u32; return stack_pushval(interp, &c); } static INLINE int interp_f32_const(struct wasm_interp *interp, float c) { struct val val; make_f32_val(&val, c); return cursor_pushval(&interp->stack, &val); } static INLINE int interp_f32_neg(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f32(interp)))) return interp_error(interp, "pop"); val->num.f32 = -val->num.f32; return 1; } static INLINE int interp_f32_reinterpret_i32(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_i32(interp)))) return interp_error(interp, "pop"); val->type = val_f32; return 1; } static INLINE int interp_f64_convert_i32_u(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_i32(interp)))) return interp_error(interp, "pop"); make_f64_val(val, (double)val->num.i32); return 1; } static INLINE int interp_i32_trunc_f64_u(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); make_i32_val(val, (u32)val->num.f64); return 1; } static INLINE int interp_f32_convert_i32_u(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_i32(interp)))) return interp_error(interp, "pop"); make_f32_val(val, (float)val->num.u32); return 1; } static INLINE int interp_i32_trunc_f32_s(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f32(interp)))) return interp_error(interp, "pop"); make_i32_val(val, (int)val->num.f32); return 1; } static INLINE int interp_f64_reinterpret_i64(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_i64(interp)))) return interp_error(interp, "pop"); val->type = val_f64; return 1; } static INLINE int interp_i64_reinterpret_f64(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); val->type = val_i64; return 1; } static INLINE int interp_f64_convert_i64_u(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_i64(interp)))) return interp_error(interp, "pop"); make_f64_val(val, (double)val->num.u64); return 1; } static INLINE int interp_f64_convert_i32_s(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_i32(interp)))) return interp_error(interp, "pop"); make_f64_val(val, (double)val->num.i32); return 1; } static INLINE int interp_f32_demote_f64(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); make_f32_val(val, (float)val->num.f64); return 1; } static INLINE int interp_i32_trunc_f64_s(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f64(interp)))) return interp_error(interp, "pop"); make_i32_val(val, (int)val->num.f64); return 1; } static INLINE int interp_f64_promote_f32(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f32(interp)))) return interp_error(interp, "pop"); make_f64_val(val, (double)val->num.f32); return 1; } static INLINE int interp_i32_reinterpret_f32(struct wasm_interp *interp) { struct val *val; if (unlikely(!(val = stack_top_f32(interp)))) return interp_error(interp, "pop"); val->type = val_i32; return 1; } static INLINE int interp_f32_convert_i32_s(struct wasm_interp *interp) { float f; struct val *val; if (unlikely(!(val = stack_top_i32(interp)))) return interp_error(interp, "pop"); f = (float)val->num.i32; make_f32_val(val, f); return 1; } static INLINE int count_local_resolvers(struct wasm_interp *interp, int *count) { int offset; u8 *p; *count = 0; if (unlikely(!cursor_top_int(&interp->resolver_offsets, &offset))) { return interp_error(interp, "no top resolver offset?"); } p = interp->resolver_stack.start + offset * sizeof(struct resolver); if (unlikely(p < interp->resolver_stack.start || p >= interp->resolver_stack.end)) { return interp_error(interp, "resolver offset oob?"); } *count = (int)((interp->resolver_stack.p - p) / sizeof(struct resolver)); //debug("offset %d count %d stack.p - p %ld\n", offset, *count, interp->resolver_stack.p - p); return 1; } static INLINE u32 count_stack_vals(struct cursor *stack) { return (u32)cursor_count(stack, sizeof(struct val)); } static INLINE int drop_callframe_return(struct wasm_interp *interp, int returning) { int offset, drop; u32 cnt; struct callframe *frame; struct func *func; #ifdef DEBUG int count, from_fn, to_fn; const char *from, *to; if (unlikely(!count_local_resolvers(interp, &count))) { return interp_error(interp, "count local resolvers"); } if (unlikely(count != 0)) { return interp_error(interp, "unclean callframe drop, still have" " %d unpopped labels", count); } frame = top_callframe(&interp->callframes); if (!frame) { from = "(aux)"; from_fn = -1; } else { from = get_function_name(interp->module, frame->func->idx); from_fn = frame->func->idx; } if (!(frame = top_callframes(&interp->callframes, 1))) { to = "(aux)"; to_fn = -1; } else { to = get_function_name(interp->module, frame->func->idx); to_fn = frame->func->idx; } #endif frame = top_callframe(&interp->callframes); func = frame->func; if (unlikely(!cursor_popint(&interp->resolver_offsets, &offset))) return interp_error(interp, "pop resolver_offsets"); cnt = count_stack_vals(&interp->stack); if (returning) { drop = cnt - frame->prev_stack_items - func->functype->result.num_valtypes; if (drop > 0 && !cursor_dropn(&interp->stack, sizeof(struct val), drop)) { return interp_error(interp, "error dropping extra stack values in return. " "drop:%d vals:%d prev:%d ret:%d", drop, cnt, frame->prev_stack_items, func->functype->result.num_valtypes); } } else if (unlikely(cnt - frame->prev_stack_items != func->functype->result.num_valtypes)) { return interp_error(interp, "%s:%d extra values on stack: have %d-prev:%d=%d, expected %d", func->name, frame->func->idx, cnt, frame->prev_stack_items, cnt - frame->prev_stack_items, func->functype->result.num_valtypes); } // free frame locals interp->locals.p = (u8*)frame->locals; debug("returning from %s:%d to %s:%d\n", from, from_fn, to, to_fn); return cursor_drop_callframe(&interp->callframes); } static INLINE int drop_callframe(struct wasm_interp *interp) { return drop_callframe_return(interp, 1); } static void make_default_val(struct val *val) { switch (val->type) { case val_i32: val->num.i32 = 0; break; case val_i64: val->num.i64 = 0; break; case val_f32: val->num.f32 = 0.0; break; case val_f64: val->num.f64 = 0.0; break; case val_ref_null: case val_ref_func: case val_ref_extern: val->ref.addr = 0; break; } } static struct val *alloc_frame_locals(struct wasm_interp *interp, struct func *func) { struct val *locals; u32 size; size = func->num_locals * sizeof(struct val); if (!(locals = cursor_malloc(&interp->locals, size))) { debug("alloc_locals err size %d\n", size); interp_error(interp, "could not alloc locals for %s", func->name); return NULL; } return locals; } static int prepare_call(struct wasm_interp *interp, struct func *func, struct val **locals, int *prev_items) { static char buf[128]; struct val *local; struct val val; u32 i, j, ind; *prev_items = count_stack_vals(&interp->stack); if (!(*locals = alloc_frame_locals(interp, func))) return interp_error(interp, "locals stack oom"); debug("new stack size %ld/%ld (%f%%)\n", ((u8*)*locals) - interp->locals.start, interp->locals.end - interp->locals.start, 100.0*((double)(((u8*)*locals) - interp->locals.start)/ (double)(interp->locals.end - interp->locals.start))); /* push params as locals */ for (i = 0; i < func->functype->params.num_valtypes; i++) { *prev_items = *prev_items - 1; ind = func->functype->params.num_valtypes-1-i; local = &(*locals)[ind]; local->type = (enum valtype)func->functype->params.valtypes[ind]; //ind = i; if (unlikely(!cursor_popval(&interp->stack, &val))) { return interp_error(interp, "not enough arguments for call to %s: [%s], needed %d args, got %d", func->name, functype_str(func->functype, buf, sizeof(buf)), func->functype->params.num_valtypes, ind); } if (unlikely(val.type != local->type)) { return interp_error(interp, "call parameter %d type mismatch. got %s, expected %s", ind+1, valtype_name(val.type), valtype_name(local->type)); } #ifdef DEBUG debug("setting param %d (%s) to ", ind, valtype_name(local->type)); print_val(&val); printf("\n"); #endif memcpy(local, &val, sizeof(struct val)); } if (func->type == func_type_builtin) return 1; ind = i; for (i = 0; i < func->wasm_func->num_local_defs; i++) { for (j = 0; j < func->wasm_func->local_defs[i].num_types; j++, ind++) { assert(ind < func->num_locals); local = (*locals) + ind; debug("initializing local %d to type %s\n", ind-func->functype->params.num_valtypes, valtype_name(func->wasm_func->local_defs[i].type)); local->type = func->wasm_func->local_defs[i].type; make_default_val(local); } } return 1; } static INLINE int call_wasm_func(struct wasm_interp *interp, struct func *func) { struct callframe callframe; struct val *locals; int prev_items; if (!prepare_call(interp, func, &locals, &prev_items)) return interp_error(interp, "prepare args"); /* update current function and push it to the callframe as well */ make_cursor(func->wasm_func->code.code, func->wasm_func->code.code + func->wasm_func->code.code_len, &callframe.code); callframe.func = func; callframe.locals = locals; callframe.prev_stack_items = prev_items; assert(func->wasm_func->code.code_len > 0); if (unlikely(!push_callframe(interp, &callframe))) return interp_error(interp, "push callframe"); /* if (unlikely(!interp_code(interp))) { return interp_error(interp, "call %s:%d", get_function_name(interp->module, fn), fn); } if (unlikely(!drop_callframe(interp))) return interp_error(interp, "drop callframe"); */ return 1; } static INLINE int call_builtin_func(struct wasm_interp *interp, struct func *func) { struct callframe callframe = {}; struct val *locals; int prev_items, res; if (!prepare_call(interp, func, &locals, &prev_items)) return interp_error(interp, "prepare args"); /* update current function and push it to the callframe as well */ callframe.func = func; callframe.locals = locals; callframe.prev_stack_items = prev_items; if (unlikely(!push_callframe(interp, &callframe))) return interp_error(interp, "oob cursor_pushcode"); res = func->builtin->fn(interp); if (!res) return interp_error(interp, "builtin trap"); if (unlikely(!drop_callframe(interp))) return interp_error(interp, "pop callframe"); return res; } static INLINE int call_func(struct wasm_interp *interp, struct func *func) { switch (func->type) { case func_type_wasm: return call_wasm_func(interp, func); case func_type_builtin: if (func->builtin == NULL) { return interp_error(interp, "attempted to call unresolved fn: %s", func->name); } return call_builtin_func(interp, func); } return interp_error(interp, "corrupt func type: %02x", func->type); } static int call_function(struct wasm_interp *interp, int func_index) { struct func *func; debug("calling %s:%d\n", get_function_name(interp->module, func_index), func_index); if (unlikely(!(func = get_fn(interp->module, func_index)))) { return interp_error(interp, "function %s (%d) not found (%d funcs)", get_function_name(interp->module, func_index), func_index, interp->module->code_section.num_funcs); } return call_func(interp, func); } static int interp_call(struct wasm_interp *interp, int func_index) { int res; #ifdef DEBUG struct callframe prev_frame; assert(top_callframe(&interp->callframes)); memcpy(&prev_frame, top_callframe(&interp->callframes), sizeof(struct callframe)); #endif res = call_function(interp, func_index); if (unlikely(!res)) return 0; /* debug("returning from %s:%d to %s:%d\n", get_function_name(interp->module, func_index), func_index, get_function_name(interp->module, prev_frame.fn), prev_frame.fn); */ return res; } static int interp_call_indirect(struct wasm_interp *interp, struct call_indirect *call) { static char buf[128]; static char buf2[128]; struct functype *type; struct func *func, pfunc; struct table_inst *table; struct builtin *builtin; struct refval *ref; u32 ftidx; int i; if (unlikely(!was_section_parsed(interp->module, section_table))) { return interp_error(interp, "no table section"); } if (unlikely(call->tableidx >= interp->module_inst.num_tables)) { return interp_error(interp, "invalid table index %d (max %d)", call->tableidx, interp->module_inst.num_tables-1); } if (unlikely(call->typeidx >= interp->module->type_section.num_functypes)) { return interp_error(interp, "invalid function type index: %d (max %d)", call->typeidx, interp->module->type_section.num_functypes); } table = &interp->module_inst.tables[call->tableidx]; type = &interp->module->type_section.functypes[call->typeidx]; if (unlikely(table->reftype != funcref)) { return interp_error(interp, "table[%d] is not a function reference table", call->tableidx ); } if (unlikely(!stack_pop_i32(interp, &i))) { return interp_error(interp, "pop i32"); } if (unlikely(i < 0 || i >= (int)table->num_refs)) { return interp_error(interp, "invalid index %d in table %d (max %d)", i, call->tableidx, table->num_refs-1); } ref = &table->refs[i]; if (ref->addr == 0) { return interp_error(interp, "null ref in index %d of table %d", i, call->tableidx); } // HACKY special case for indirect host builtins i = -((int)ref->addr); if (-i < 0 && i < interp->num_builtins ) { builtin = &interp->builtins[i]; make_builtin_func(&pfunc, builtin->name, type, builtin, -i); debug("calling indirect builtin %s\n", pfunc.name); return call_builtin_func(interp, &pfunc); } func = &interp->module->funcs[ref->addr]; if (func->functype != type) { ftidx = (int)((func->functype - interp->module->type_section.functypes ) / sizeof(struct functype)); return interp_error(interp, "functype mismatch, expected %d `%s`, got %d `%s`", ftidx, functype_str(func->functype, buf, sizeof(buf)), call->typeidx, functype_str(type, buf2, sizeof(buf2)), ref, interp->module->num_funcs-1); } debug("calling %s:%d indirectly\n", get_function_name(interp->module, ref->addr), ref->addr); return interp_call(interp, ref->addr); } static int parse_blocktype(struct cursor *cur, struct errors *errs, struct blocktype *blocktype) { unsigned char byte; if (unlikely(!pull_byte(cur, &byte))) { return note_error(errs, cur, "parse_blocktype: oob\n"); } if (byte == 0x40) { blocktype->tag = blocktype_empty; } else if (is_valtype(byte)) { blocktype->tag = blocktype_valtype; blocktype->valtype = (enum valtype)byte; } else { blocktype->tag = blocktype_index; cur->p--; if (!parse_int(cur, &blocktype->type_index)) return note_error(errs, cur, "parse_blocktype: read type_index\n"); } return 1; } static INLINE struct label *index_label(struct cursor *a, u32 fn, u32 ind) { return index_cursor(a, ((MAX_LABELS * fn) + ind), sizeof(struct label)); } static INLINE u32 label_instr_pos(struct label *label) { return label->instr_pos & 0x7FFFFFFF; } static INLINE int is_label_resolved(struct label *label) { return label->instr_pos & 0x80000000; } static struct label *index_frame_label(struct wasm_interp *interp, u32 ind) { struct callframe *frame; frame = top_callframe(&interp->callframes); if (unlikely(!frame)) { interp_error(interp, "no callframe?"); return NULL; } return index_label(&interp->labels, frame->func->idx, ind); } static INLINE int resolve_label(struct label *label, struct cursor *code) { if (is_label_resolved(label)) { return 1; } label->jump = (u32)(code->p - code->start); label->instr_pos |= 0x80000000; /* debug("resolving label %04x to %04x\n", label_instr_pos(label), label->jump); */ return 1; } static INLINE struct resolver *top_resolver_stack(struct cursor *stack, int index) { struct resolver *p = (struct resolver*)stack->p; p = &p[-(index+1)]; if (p < (struct resolver*)stack->start) return NULL; return p; } static INLINE struct resolver *top_resolver(struct wasm_interp *interp, u32 index) { return top_resolver_stack(&interp->resolver_stack, index); } /* static void print_resolver_stack(struct wasm_interp *interp) { int count, i, start_pos, end_pos; struct label *label; printf("resolver stack: "); count = (int)cursor_count(&interp->resolver_stack, sizeof(struct resolver)); for (i = 0; i < count; i++) { struct resolver *r = top_resolver(interp, i); if (i != 0) printf(", "); label = index_frame_label(interp, r->label); start_pos = label_instr_pos(label); end_pos = label->jump; printf("%s@%d:%s@%d", instr_name(r->start_tag), start_pos, instr_name(r->end_tag), end_pos); } printf("\n"); } */ static INLINE int pop_resolver(struct wasm_interp *interp, struct resolver *resolver) { #if 0 int num_resolvers; struct label *label; debug("pop label "); print_resolver_stack(interp); #endif if (!cursor_pop(&interp->resolver_stack, (u8*)resolver, sizeof(*resolver))) { return interp_error(interp, "pop resolver"); } #if 0 if (unlikely(!count_local_resolvers(interp, &num_resolvers))) { return interp_error(interp, "local resolvers fn start"); }; label = index_label(&interp->labels, top_callframe(&interp->callframes)->func->idx, resolver->label); debug("%04lX popped resolver label:%d %04x-%04x i_%s i_%s %d local_resolvers:%d\n", interp_codeptr(interp)->p - interp_codeptr(interp)->start, resolver->label, label_instr_pos(label), label->jump, instr_name(resolver->start_tag), instr_name(resolver->end_tag), count_resolvers(interp), num_resolvers ); #endif return 1; } static int pop_label(struct wasm_interp *interp, struct resolver *resolver, struct callframe **frame, struct label **label) { if (unlikely(!pop_resolver(interp, resolver))) return interp_error(interp, "couldn't pop jump resolver stack"); if (unlikely(!(*frame = top_callframe(&interp->callframes)))) return interp_error(interp, "no callframe?"); if (unlikely(!(*label = index_label(&interp->labels, (*frame)->func->idx, resolver->label)))) return interp_error(interp, "index label"); if (unlikely(!resolve_label(*label, &(*frame)->code))) return interp_error(interp, "resolve label"); return 1; } static INLINE int pop_label_checkpoint(struct wasm_interp *interp) { struct resolver resolver; struct callframe *frame; struct label *label; return pop_label(interp, &resolver, &frame, &label); } static INLINE u16 *func_num_labels(struct wasm_interp *interp, u32 fn) { u16 *num = index_cursor(&interp->num_labels, fn, sizeof(u16)); assert(num); assert(*num <= MAX_LABELS); return num; } static int find_label(struct wasm_interp *interp, u32 fn, u32 instr_pos) { u16 *num_labels; int i; struct label *label; num_labels = func_num_labels(interp, fn); if (!(label = index_label(&interp->labels, fn, *num_labels-1))) return interp_error(interp, "index label"); for (i = *num_labels-1; i >= 0; label--) { if (label_instr_pos(label) == instr_pos) return i; i--; } return -1; } static INLINE void set_label_pos(struct label *label, u32 pos) { assert(!(pos & 0x80000000)); label->instr_pos = pos; } // upsert an unresolved label static int upsert_label(struct wasm_interp *interp, u32 fn, u32 instr_pos, int *ind) { struct label *label; u16 *num_labels; num_labels = func_num_labels(interp, fn); if (*num_labels > 0 && ((*ind = find_label(interp, fn, instr_pos)) != -1)) { // we already have the label return 1; } if (*num_labels + 1 >= MAX_LABELS) { interp_error(interp, "too many labels in %s (> %d)", get_function_name(interp->module, fn), MAX_LABELS); return 0; } /* debug("upsert_label: %d labels for %s:%d\n", *num_labels, get_function_name(interp->module, fn), fn); */ *ind = *num_labels; if (unlikely(!(label = index_label(&interp->labels, fn, *ind)))) return interp_error(interp, "index label"); set_label_pos(label, instr_pos); *num_labels = *num_labels + 1; return 2; } static INLINE int cursor_push_resolver(struct cursor *stack, struct resolver *resolver) { return cursor_push(stack, (u8*)resolver, sizeof(*resolver)); } struct tag_info { u8 flags; u8 end_tag; }; // when we encounter a control instruction, try to resolve the label, otherwise // push the label index to the resolver stack for resolution later static int push_label_checkpoint(struct wasm_interp *interp, struct label **label, u8 start_tag, u8 end_tag) { u32 instr_pos, fns; int ind; struct resolver resolver; struct callframe *frame; #if 0 int num_resolvers; debug("push label "); print_resolver_stack(interp); #endif resolver.start_tag = start_tag; resolver.end_tag = end_tag; resolver.label = 0; *label = NULL; fns = interp->module->num_funcs; frame = top_callframe(&interp->callframes); if (unlikely(!frame)) { return interp_error(interp, "no callframes available?"); } else if (unlikely(frame->func->idx >= fns)) { return interp_error(interp, "invalid fn index?"); } instr_pos = (int)(frame->code.p - frame->code.start); if (unlikely(!upsert_label(interp, frame->func->idx, instr_pos, &ind))) { return interp_error(interp, "upsert label"); } if (unlikely(!(*label = index_label(&interp->labels, frame->func->idx, ind)))) { return interp_error(interp, "couldn't index label"); } resolver.label = ind; if (unlikely(!cursor_push_resolver(&interp->resolver_stack, &resolver))) { return interp_error(interp, "push label index to resolver stack oob"); } #if 0 if (unlikely(!count_local_resolvers(interp, &num_resolvers))) { return interp_error(interp, "local resolvers fn start"); }; debug("%04x pushed resolver label:%d 0x%04X-0x%04X i_%s i_%s %ld local_resolvers:%d \n", instr_pos, resolver.label, label_instr_pos(*label), (*label)->jump, instr_name(resolver.start_tag), instr_name(resolver.end_tag), cursor_count(&interp->resolver_stack, sizeof(resolver)), num_resolvers); #endif return 1; } static int interp_jump(struct wasm_interp *interp, int jmp) { struct callframe *frame; frame = top_callframe(&interp->callframes); if (unlikely(!frame)) { return interp_error(interp, "no callframe?"); } debug("jumping to %04x\n", jmp); frame->code.p = frame->code.start + jmp; if (unlikely(frame->code.p >= frame->code.end)) { return interp_error(interp, "code pointer at or past end, evil jump?"); } return 1; } static int pop_label_and_skip(struct wasm_interp *interp, struct label *label, int times) { int i; struct resolver resolver; assert(is_label_resolved(label)); for (i = 0; i < times; i++) { if (!pop_resolver(interp, &resolver)) { return interp_error(interp, "top resolver"); } } return interp_jump(interp, label->jump); } static int unresolved_break(struct wasm_interp *interp, int index); static int break_if(struct wasm_interp *interp, struct label *label) { struct cursor *code; struct label *else_label; struct expr_parser parser; struct expr expr; if (!interp_jump(interp, label->jump)) return interp_error(interp, "if break failed"); if (!(code = interp_codeptr(interp))) return interp_error(interp, "if break codeptr"); if (code->p - 1 < code->start) return interp_error(interp, "oob"); if (*(code->p - 1) != i_else) return 1; if (!push_label_checkpoint(interp, &else_label, i_else, i_end)) return interp_error(interp, "push else label"); if (is_label_resolved(else_label)) return pop_label_and_skip(interp, else_label, 1); make_interp_expr_parser(interp, &parser); if (!parse_instrs_until(&parser, i_end, &expr)) return interp_error(interp, "skip else instrs"); if (!pop_label_checkpoint(interp)) return interp_error(interp, "op else skip"); return 1; } static int break_label(struct wasm_interp *interp, struct resolver *resolver, struct label *label) { // we have a loop, push the popped resolver if (resolver->start_tag == i_loop) { //debug("repushing resolver for loop\n"); if (unlikely(!cursor_push_resolver(&interp->resolver_stack, resolver))) { return interp_error(interp, "re-push loop resolver"); } // loop jump return interp_jump(interp, label_instr_pos(label)); } else if (resolver->start_tag == i_if) { return break_if(interp, label); } return interp_jump(interp, label->jump); } static int pop_label_and_break(struct wasm_interp *interp, int times) { int i; struct resolver resolver; struct label *label; struct callframe *frame; if (unlikely(times == 0)) return interp_error(interp, "can't pop label 0 times"); label = NULL; for (i = 0; i < times; i++) { if (!pop_label(interp, &resolver, &frame, &label)) { return interp_error(interp, "pop resolver"); } } return break_label(interp, &resolver, label); } static int parse_block_instrs_at(struct expr_parser *p, struct expr *exprs, u8 start_tag, u8 end_tag, u8 *stopped_at) { struct label *label = NULL; // if we don't have an interpreter instance, we don't care about // label resolution (NOT TRUE ANYMORE!) if (p->interp && !push_label_checkpoint(p->interp, &label, start_tag, end_tag)) { return note_error(p->errs, p->code, "push checkpoint"); } if (label && is_label_resolved(label)) { debug("label is resolved, skipping block parse\n"); // TODO verify this is correct exprs->code = p->code->start + label_instr_pos(label); exprs->code_len = (int)((p->code->start + label->jump) - exprs->code); return pop_label_and_skip(p->interp, label, 1); } if (!parse_instrs_until_at(p, end_tag, exprs, stopped_at)) return note_error(p->errs, p->code, "parse instrs"); if (!pop_label_checkpoint(p->interp)) return note_error(p->errs, p->code, "pop label"); return 1; } static INLINE int parse_block_instrs(struct expr_parser *p, struct expr *exprs, u8 start_tag, u8 end_tag) { u8 stopped_at; return parse_block_instrs_at(p, exprs, start_tag, end_tag, &stopped_at); } static int parse_block_at(struct expr_parser *p, struct block *block, u8 start_tag, u8 end_tag, u8 *stopped_at) { if (!parse_blocktype(p->code, p->errs, &block->type)) return note_error(p->errs, p->code, "blocktype"); if (!parse_block_instrs_at(p, &block->instrs, start_tag, end_tag, stopped_at)) return note_error(p->errs, p->code, "block instrs"); debug("%04lX parse block ended\n", p->interp ? p->code->p - p->code->start : 0L); return 1; } static INLINE int parse_block(struct expr_parser *p, struct block *block, u8 start_tag, u8 end_tag) { u8 stopped_at; return parse_block_at(p, block, start_tag, end_tag, &stopped_at); } static INLINE int parse_else(struct expr_parser *p, struct expr *instrs) { if (p->interp && !pop_label_checkpoint(p->interp)) return note_error(p->errs, p->code, "pop if checkpoint"); debug("parsing else...\n"); return parse_block_instrs(p, instrs, i_else, i_end); } static INLINE int parse_memarg(struct cursor *code, struct memarg *memarg) { return parse_u32(code, &memarg->align) && parse_u32(code, &memarg->offset); } static int parse_call_indirect(struct cursor *code, struct call_indirect *call_indirect) { return parse_u32(code, &call_indirect->typeidx) && parse_u32(code, &call_indirect->tableidx); } static int parse_bulk_op(struct cursor *code, struct errors *errs, struct bulk_op *bulk_op) { u8 tag; if (unlikely(!pull_byte(code, &tag))) return note_error(errs, code, "oob"); if (unlikely(tag < 10 || tag > 17)) return note_error(errs, code, "invalid bulk op %d", tag); bulk_op->tag = tag; switch ((enum bulk_tag)tag) { case i_memory_copy: if (unlikely(!consume_byte(code, 0))) return note_error(errs, code, "mem idx dst 0"); if (unlikely(!consume_byte(code, 0))) return note_error(errs, code, "mem idx src 0"); return 1; case i_memory_fill: if (unlikely(!consume_byte(code, 0))) return note_error(errs, code, "mem idx 0"); return 1; case i_table_init: if (unlikely(!parse_u32(code, &bulk_op->table_init.elemidx))) return note_error(errs, code, "elemidx"); if (unlikely(!parse_u32(code, &bulk_op->table_init.tableidx))) return note_error(errs, code, "tableidx"); return 1; case i_elem_drop: if (unlikely(!parse_u32(code, &bulk_op->idx))) return note_error(errs, code, "elemidx"); return 1; case i_table_copy: if (unlikely(!parse_u32(code, &bulk_op->table_copy.from))) return note_error(errs, code, "elemidx"); if (unlikely(!parse_u32(code, &bulk_op->table_copy.to))) return note_error(errs, code, "tableidx"); return 1; case i_table_grow: case i_table_size: case i_table_fill: if (unlikely(!parse_u32(code, &bulk_op->idx))) return note_error(errs, code, "tableidx"); return 1; } return note_error(errs, code, "unhandled table op 0x%02x", tag); } static int parse_br_table(struct cursor *code, struct errors *errs, struct br_table *br_table) { u32 i; if (unlikely(!parse_u32(code, &br_table->num_label_indices))) { return note_error(errs, code, "fail read br_table num_indices"); } if (br_table->num_label_indices > ARRAY_SIZE(br_table->label_indices)) { return note_error(errs, code, "whoa slow down on that one chief. " "This br_table has %d indices but we only have room " "in our tiny struct for %d indices", br_table->num_label_indices, ARRAY_SIZE(br_table->label_indices)); } for (i = 0; i < br_table->num_label_indices; i++) { if (unlikely(!parse_u32(code, &br_table->label_indices[i]))) { return note_error(errs, code, "failed to read br_table label %d/%d", i+1, br_table->num_label_indices); } } if (unlikely(!parse_u32(code, &br_table->default_label))) { return note_error(errs, code, "failed to parse default label"); } return 1; } static int parse_select(struct cursor *code, struct errors *errs, u8 tag, struct select_instr *select) { if (tag == i_select) { select->num_valtypes = 0; select->valtypes = NULL; return 1; } if (unlikely(!parse_u32(code, &select->num_valtypes))) { return note_error(errs, code, "couldn't parse select valtype vec count"); } select->valtypes = code->p; code->p += select->num_valtypes; return 1; } static int parse_if(struct expr_parser *p, struct block *block) { struct label *label; struct expr expr; u8 stopped_at; if (!parse_block_at(p, block, i_if, i_if, &stopped_at)) return note_error(p->errs, p->code, "parse if block"); if (p->interp == NULL || stopped_at != i_else) return 1; // else if (!push_label_checkpoint(p->interp, &label, i_else, i_end)) return note_error(p->errs, p->code, "push else checkpoint"); if (is_label_resolved(label)) return pop_label_and_skip(p->interp, label, 1); if (!parse_instrs_until(p, i_end, &expr)) return note_error(p->errs, p->code, "parse else instrs"); if (!pop_label_checkpoint(p->interp)) return note_error(p->errs, p->code, "pop else checkpoint"); return 1; } static int parse_instr(struct expr_parser *p, u8 tag, struct instr *op) { op->pos = (int)(p->code->p - 1 - p->code->start); op->tag = tag; switch ((enum instr_tag)tag) { // two-byte instrs case i_select: case i_selects: return parse_select(p->code, p->errs, tag, &op->select); case i_memory_size: case i_memory_grow: return consume_byte(p->code, 0); case i_block: return parse_block(p, &op->block, i_block, i_end); case i_loop: return parse_block(p, &op->block, i_loop, i_end); case i_if: return parse_if(p, &op->block); case i_else: return parse_else(p, &op->else_block); case i_call: case i_local_get: case i_local_set: case i_local_tee: case i_global_get: case i_global_set: case i_br: case i_br_if: case i_ref_func: case i_table_set: case i_table_get: if (unlikely(!parse_u32(p->code, &op->u32))) { return note_error(p->errs, p->code, "couldn't read int"); } return 1; case i_i32_const: if (unlikely(!parse_int(p->code, &op->i32))) { return note_error(p->errs, p->code, "couldn't read int"); } return 1; case i_i64_const: if (unlikely(!parse_i64(p->code, &op->u64))) { return note_error(p->errs, p->code, "couldn't read i64"); } return 1; case i_ref_is_null: case i_i32_load: case i_i64_load: case i_f32_load: case i_f64_load: case i_i32_load8_s: case i_i32_load8_u: case i_i32_load16_s: case i_i32_load16_u: case i_i64_load8_s: case i_i64_load8_u: case i_i64_load16_s: case i_i64_load16_u: case i_i64_load32_s: case i_i64_load32_u: case i_i32_store: case i_i64_store: case i_f32_store: case i_f64_store: case i_i32_store8: case i_i32_store16: case i_i64_store8: case i_i64_store16: case i_i64_store32: return parse_memarg(p->code, &op->memarg); case i_br_table: return parse_br_table(p->code, p->errs, &op->br_table); case i_bulk_op: return parse_bulk_op(p->code, p->errs, &op->bulk_op); case i_call_indirect: return parse_call_indirect(p->code, &op->call_indirect); case i_f32_const: return read_f32(p->code, &op->f32); case i_f64_const: return read_f64(p->code, &op->f64); // single-tag ops case i_end: case i_ref_null: case i_unreachable: case i_nop: case i_return: case i_drop: case i_i32_eqz: case i_i32_eq: case i_i32_ne: case i_i32_lt_s: case i_i32_lt_u: case i_i32_gt_s: case i_i32_gt_u: case i_i32_le_s: case i_i32_le_u: case i_i32_ge_s: case i_i32_ge_u: case i_i64_eqz: case i_i64_eq: case i_i64_ne: case i_i64_lt_s: case i_i64_lt_u: case i_i64_gt_s: case i_i64_gt_u: case i_i64_le_s: case i_i64_le_u: case i_i64_ge_s: case i_i64_ge_u: case i_f32_eq: case i_f32_ne: case i_f32_lt: case i_f32_gt: case i_f32_le: case i_f32_ge: case i_f64_eq: case i_f64_ne: case i_f64_lt: case i_f64_gt: case i_f64_le: case i_f64_ge: case i_i32_clz: case i_i32_ctz: case i_i32_popcnt: case i_i32_add: case i_i32_sub: case i_i32_mul: case i_i32_div_s: case i_i32_div_u: case i_i32_rem_s: case i_i32_rem_u: case i_i32_and: case i_i32_or: case i_i32_xor: case i_i32_shl: case i_i32_shr_s: case i_i32_shr_u: case i_i32_rotl: case i_i32_rotr: case i_i64_clz: case i_i64_ctz: case i_i64_popcnt: case i_i64_add: case i_i64_sub: case i_i64_mul: case i_i64_div_s: case i_i64_div_u: case i_i64_rem_s: case i_i64_rem_u: case i_i64_and: case i_i64_or: case i_i64_xor: case i_i64_shl: case i_i64_shr_s: case i_i64_shr_u: case i_i64_rotl: case i_i64_rotr: case i_f32_abs: case i_f32_neg: case i_f32_ceil: case i_f32_floor: case i_f32_trunc: case i_f32_nearest: case i_f32_sqrt: case i_f32_add: case i_f32_sub: case i_f32_mul: case i_f32_div: case i_f32_min: case i_f32_max: case i_f32_copysign: case i_f64_abs: case i_f64_neg: case i_f64_ceil: case i_f64_floor: case i_f64_trunc: case i_f64_nearest: case i_f64_sqrt: case i_f64_add: case i_f64_sub: case i_f64_mul: case i_f64_div: case i_f64_min: case i_f64_max: case i_f64_copysign: case i_i32_wrap_i64: case i_i32_trunc_f32_s: case i_i32_trunc_f32_u: case i_i32_trunc_f64_s: case i_i32_trunc_f64_u: case i_i64_extend_i32_s: case i_i64_extend_i32_u: case i_i64_trunc_f32_s: case i_i64_trunc_f32_u: case i_i64_trunc_f64_s: case i_i64_trunc_f64_u: case i_f32_convert_i32_s: case i_f32_convert_i32_u: case i_f32_convert_i64_s: case i_f32_convert_i64_u: case i_f32_demote_f64: case i_f64_convert_i32_s: case i_f64_convert_i32_u: case i_f64_convert_i64_s: case i_f64_convert_i64_u: case i_f64_promote_f32: case i_i32_reinterpret_f32: case i_i64_reinterpret_f64: case i_f32_reinterpret_i32: case i_f64_reinterpret_i64: case i_i32_extend8_s: case i_i32_extend16_s: case i_i64_extend8_s: case i_i64_extend16_s: case i_i64_extend32_s: return 1; } return note_error(p->errs, p->code, "unhandled tag: 0x%x", tag); } // end or else static int if_jump(struct wasm_interp *interp, struct label *label) { struct expr expr; struct expr_parser parser; struct label *else_label; struct cursor *codeptr; u8 stopped_at; if (!label) { return interp_error(interp, "no label?"); } if (is_label_resolved(label)) { //debug("if_jump resolved label "); //print_resolver_stack(interp); if (!pop_label_and_skip(interp, label, 1)) return interp_error(interp, "pop if after resolved jump"); if (!(codeptr = interp_codeptr(interp)) && codeptr->p - 1 >= codeptr->start) return interp_error(interp, "codeptr looking for else"); stopped_at = *(codeptr->p-1); if (stopped_at == i_else && !push_label_checkpoint(interp, &else_label, i_else, i_end)) return interp_error(interp, "push else label"); return 1; } make_interp_expr_parser(interp, &parser); // consume instructions, use resolver stack to resolve jumps if (!parse_instrs_until_at(&parser, i_if, &expr, &stopped_at)) return interp_error(interp, "parse instrs start (if)"); if (!pop_label_checkpoint(interp)) return interp_error(interp, "pop label"); if (stopped_at == i_else && !push_label_checkpoint(interp, &else_label, i_else, i_end)) { return interp_error(interp, "push else label"); } debug("%04lX if_jump ended\n", parser.code->p - parser.code->start); return 1; } static int interp_block(struct wasm_interp *interp) { struct cursor *code; struct label *label; struct blocktype blocktype; if (unlikely(!(code = interp_codeptr(interp)))) return interp_error(interp, "empty callstack?"); if (unlikely(!parse_blocktype(code, &interp->errors, &blocktype))) return interp_error(interp, "couldn't parse blocktype"); if (unlikely(!push_label_checkpoint(interp, &label, i_block, i_end))) return interp_error(interp, "block label checkpoint"); return 1; } static INLINE struct label *top_label(struct wasm_interp *interp, u32 index) { struct resolver *resolver; if (unlikely(!(resolver = top_resolver(interp, index)))) { interp_error(interp, "invalid resolver index %d", index); return NULL; } return index_frame_label(interp, resolver->label); } static INLINE int interp_else(struct wasm_interp *interp) { (void)interp; /* struct label *label; struct expr expr; struct expr_parser parser; if (!(label = top_label(interp, 0))) return interp_error(interp, "no label?"); if (!push_label_checkpoint(interp, &label, i_else, i_end)) { return interp_error(interp, "label checkpoint"); } if (!is_label_resolved(label)) { return interp_error(interp, "expected label to be parsed"); } */ return 1; } static int interp_if(struct wasm_interp *interp) { struct val cond; struct blocktype blocktype; struct cursor *code; struct label *label; if (unlikely(!(code = interp_codeptr(interp)))) { return interp_error(interp, "empty callstack?"); } if (unlikely(!parse_blocktype(code, &interp->errors, &blocktype))) { return interp_error(interp, "couldn't parse blocktype"); } if (unlikely(!cursor_popval(&interp->stack, &cond))) { return interp_error(interp, "if pop val"); } if (!push_label_checkpoint(interp, &label, i_if, i_if)) { return interp_error(interp, "label checkpoint"); } if (cond.num.i32 != 0) { return 1; } if (unlikely(!if_jump(interp, label))) { return interp_error(interp, "jump"); } return 1; } static INLINE int clz32(u32 x) { return x ? __builtin_clz(x) : sizeof(x) * 8; } static INLINE int clz64(u64 x) { return x ? __builtin_clzll(x) : sizeof(x) * 8; } static INLINE int ctz(u32 x) { return x ? __builtin_ctz(x) : (int)sizeof(x) * 8; } static INLINE int popcnt(u32 x) { return x ? __builtin_popcount(x) : 0; } static INLINE int interp_i32_popcnt(struct wasm_interp *interp) { struct val a; if (unlikely(!stack_pop_valtype(interp, val_i32, &a))) return interp_error(interp, "pop val"); return stack_push_i32(interp, popcnt(a.num.u32)); } static INLINE int interp_i32_ctz(struct wasm_interp *interp) { struct val a; if (unlikely(!stack_pop_valtype(interp, val_i32, &a))) return interp_error(interp, "pop val"); return stack_push_i32(interp, ctz(a.num.u32)); } static INLINE int interp_i64_clz(struct wasm_interp *interp) { struct val a; if (unlikely(!stack_pop_valtype(interp, val_i64, &a))) return interp_error(interp, "pop val"); return stack_push_i64(interp, clz64(a.num.u64)); } static INLINE int interp_i32_clz(struct wasm_interp *interp) { struct val a; if (unlikely(!stack_pop_valtype(interp, val_i32, &a))) return interp_error(interp, "pop val"); return stack_push_i32(interp, clz32(a.num.u32)); } static INLINE int interp_i32_eqz(struct wasm_interp *interp) { struct val a; if (unlikely(!stack_pop_valtype(interp, val_i32, &a))) return interp_error(interp, "pop val"); return stack_push_i32(interp, a.num.i32 == 0); } static int unresolved_break(struct wasm_interp *interp, int index) { struct expr_parser parser; struct callframe *frame; struct expr expr; struct resolver *resolver = NULL; struct label *label = NULL; #if DEBUG int times; #endif make_interp_expr_parser(interp, &parser); if (unlikely(!(frame = top_callframe(&interp->callframes)))) { return interp_error(interp, "no top callframe?"); } #if DEBUG times = index+1; #endif debug("breaking %d times from unresolved label\n", times); while (index-- >= 0) { if (unlikely(!(resolver = top_resolver(interp, 0)))) { return interp_error(interp, "invalid resolver index %d", index); } if (unlikely(!(label = index_frame_label(interp, resolver->label)))) { return interp_error(interp, "no label"); } // TODO: breaking from functions (return) if (is_label_resolved(label)) { if (index == -1) return pop_label_and_break(interp, 1); else if (!pop_label_and_skip(interp, label, 1)) return interp_error(interp, "pop and jump"); else continue; } if (unlikely(!parse_instrs_until(&parser, resolver->end_tag, &expr))) return interp_error(interp, "parsing instrs"); if (index == -1) return pop_label_and_break(interp, 1); if (!pop_label_checkpoint(interp)) return interp_error(interp, "pop label"); } /* debug("finished breaking %d times from unresolved label (it was a %s)\n", times, instr_name(resolver->start_tag)); assert(resolver); assert(label); if (resolver->start_tag == i_loop) { debug("jumping to start of loop\n"); if (unlikely(!cursor_push_resolver(&interp->resolver_stack, resolver))) { return interp_error(interp, "re-push loop resolver"); } return interp_jump(interp, label_instr_pos(label)); } */ return interp_error(interp, "shouldn't get here"); } static int interp_return(struct wasm_interp *interp) { int count; if (unlikely(!count_local_resolvers(interp, &count))) { return interp_error(interp, "failed to count fn labels?"); } if (unlikely(!cursor_dropn(&interp->resolver_stack, sizeof(struct resolver), count))) { return interp_error(interp, "failed to drop %d local labels", count); } return drop_callframe_return(interp, 1); } static int interp_br_jump(struct wasm_interp *interp, u32 index) { struct label *label; if (unlikely(!(label = top_label(interp, index)))) { //print_resolver_stack(interp); return interp_return(interp); } if (is_label_resolved(label)) { return pop_label_and_break(interp, index+1); } return unresolved_break(interp, index); } static INLINE int interp_br(struct wasm_interp *interp, u32 ind) { return interp_br_jump(interp, ind); } static INLINE int interp_br_table(struct wasm_interp *interp, struct br_table *br_table) { int i; if (!stack_pop_i32(interp, &i)) { return interp_error(interp, "pop br_table index"); } if ((u32)i < br_table->num_label_indices) { return interp_br_jump(interp, br_table->label_indices[i]); } return interp_br_jump(interp, br_table->default_label); } static INLINE int interp_br_if(struct wasm_interp *interp, u32 ind) { int cond = 0; // TODO: can this be something other than an i32? if (unlikely(!stack_pop_i32(interp, &cond))) { return interp_error(interp, "pop br_if i32"); } if (cond != 0) return interp_br_jump(interp, ind); return 1; } static struct val *get_global_inst(struct wasm_interp *interp, u32 ind) { struct global_inst *global_inst; if (unlikely(!was_section_parsed(interp->module, section_global))) { interp_error(interp, "can't get global %d, no global section parsed!", ind); return NULL; } if (unlikely(ind >= interp->module_inst.num_globals)) { interp_error(interp, "invalid global index %d (max %d)", ind, interp->module_inst.num_globals); return NULL; } global_inst = &interp->module_inst.globals[ind]; /* copy initialized global from module to global instance */ //memcpy(&global_inst->val, &global->val, sizeof(global_inst->val)); return &global_inst->val; } static int interp_global_get(struct wasm_interp *interp, u32 ind) { struct globalsec *section = &interp->module->global_section; struct val *global; // TODO imported global indices? if (unlikely(ind >= section->num_globals)) { return interp_error(interp, "invalid global index %d / %d", ind, section->num_globals-1); } if (!(global = get_global_inst(interp, ind))) { return interp_error(interp, "get global"); } return stack_pushval(interp, global); } static INLINE int has_memory_section(struct module *module) { return was_section_parsed(module, section_memory) && module->memory_section.num_mems > 0; } static INLINE int bitwidth(enum valtype vt) { switch (vt) { case val_i32: case val_f32: return 32; case val_i64: case val_f64: return 64; /* invalid? */ case val_ref_null: case val_ref_func: case val_ref_extern: return 0; } return 0; } struct memtarget { int size; u8 *pos; }; static int interp_mem_offset(struct wasm_interp *interp, int *N, int i, enum valtype c, struct memarg *memarg, struct memtarget *t) { int offset, bw; if (unlikely(!has_memory_section(interp->module))) { return interp_error(interp, "no memory section"); } offset = i + memarg->offset; bw = bitwidth(c); if (*N == 0) { *N = bw; } t->size = *N/8; t->pos = interp->memory.start + offset; if (t->pos < interp->memory.start) { return interp_error(interp, "invalid memory offset %d\n", offset); } if (t->pos + t->size > interp->memory.p) { return interp_error(interp, "mem store oob pos:%d size:%d mem:%d", offset, t->size, interp->memory.p - interp->memory.start); } return 1; } static int wrap_val(struct val *val, unsigned int size) { switch (val->type) { case val_i32: if (size == 32) return 1; //debug("before %d size %d (mask %lx)\n", val->num.i32, size, (1UL << size)-1); val->num.i32 &= (1UL << size)-1; //debug("after %d size %d (mask %lx)\n", val->num.i32, size, (1UL << size)-1); break; case val_i64: if (size == 64) return 1; val->num.i64 &= (1ULL << size)-1; break; case val_f32: case val_f64: return 1; default: return 0; } return 1; } static int store_val(struct wasm_interp *interp, int i, struct memarg *memarg, enum valtype type, struct val *val, int N) { struct memtarget target; //struct cursor mem; if (unlikely(!interp_mem_offset(interp, &N, i, type, memarg, &target))) return 0; if (N != 0) { if (!wrap_val(val, N)) { return interp_error(interp, "implement wrap val (truncate?) for %s", valtype_name(val->type)); } } //make_cursor(target.pos, interp->memory.p, &mem); debug("storing "); #ifdef DEBUG print_val(val); #endif debug(" at %ld (%d bytes), N:%d\n", target.pos - interp->memory.start, target.size, N); //cursor_print_around(&mem, 20); memcpy(target.pos, &val->num.i32, target.size); return 1; } /* static INLINE int store_simple(struct wasm_interp *interp, int offset, struct val *val) { struct memarg memarg = {}; return store_val(interp, offset, &memarg, val->type, val, 0); } static INLINE int store_i32(struct wasm_interp *interp, int offset, int i) { struct val val; make_i32_val(&val, i); return store_simple(interp, offset, &val); } */ static int interp_load(struct wasm_interp *interp, struct memarg *memarg, enum valtype type, int N, int sign) { struct memtarget target; // struct cursor mem; struct val out = {0}; int i; (void)sign; out.type = type; if (unlikely(!stack_pop_i32(interp, &i))) { return interp_error(interp, "pop stack"); } if (unlikely(!interp_mem_offset(interp, &N, i, type, memarg, &target))) { return interp_error(interp, "memory target"); } memcpy(&out.num.i32, target.pos, target.size); wrap_val(&out, target.size * 8); //make_cursor(target.pos, interp->memory.p, &mem); debug("loading %d from %ld (copying %d bytes)\n", out.num.i32, target.pos - interp->memory.start, target.size); //cursor_print_around(&mem, 20); if (unlikely(!stack_pushval(interp, &out))) { return interp_error(interp, "push to stack after load %s", valtype_name(type)); } return 1; } /* static INLINE int load_i32(struct wasm_interp *interp, int addr, int *i) { struct memarg memarg = { .offset = 0, .align = 0 }; if (unlikely(!stack_push_i32(interp, addr))) return interp_error(interp, "push addr %d", addr); if (unlikely(!interp_load(interp, &memarg, val_i32, 0, -1))) return interp_error(interp, "load"); return stack_pop_i32(interp, i); } static int wasi_fd_close(struct wasm_interp *interp) { struct val *params = NULL; if (!get_params(interp, ¶ms, 1)) return interp_error(interp, "param"); close(params[0].num.i32); return stack_push_i32(interp, 0); } static int wasi_fd_write(struct wasm_interp *interp) { struct val *fd, *iovs_ptr, *iovs_len, *written; int i, ind, iovec_data, str_len, wrote, all; if (unlikely(!(fd = get_local(interp, 0)))) return interp_error(interp, "fd"); if (unlikely(!(iovs_ptr = get_local(interp, 1)))) return interp_error(interp, "iovs_ptr"); if (unlikely(!(iovs_len = get_local(interp, 2)))) return interp_error(interp, "iovs_len"); if (unlikely(!(written = get_local(interp, 3)))) return interp_error(interp, "written"); if (unlikely(fd->num.i32 >= 10)) return interp_error(interp, "weird fd %d", fd->num.i32); all = 0; str_len = 0; i = 0; iovec_data = 0; for (; i < iovs_len->num.i32; i++) { ind = 8*i; if (unlikely(!load_i32(interp, iovs_ptr->num.i32 + ind, &iovec_data))) { return interp_error(interp, "load iovec data"); } if (unlikely(!load_i32(interp,iovs_ptr->num.i32 + (ind+4), &str_len))) { return interp_error(interp, "load iovec data"); } if (unlikely(interp->memory.start + iovec_data + str_len >= interp->memory.p)) { return interp_error(interp, "fd_write oob"); } debug("fd_write #iovec %d/%d len %d '%.*s'\n", i+1, iovs_len->num.i32, str_len, str_len, interp->memory.start + iovec_data); wrote = (int)write(fd->num.i32, interp->memory.start + iovec_data, str_len ); all += wrote; if (wrote != str_len) { return interp_error(interp, "written %d != %d", written->num.i32, str_len); } } if (!store_i32(interp, written->num.i32, all)) { return interp_error(interp, "store written"); } return stack_push_i32(interp, 0); } static int wasi_get_strs(struct wasm_interp *interp, int count, const char **strs) { struct val *argv, *argv_buf; struct cursor writer; int i, len; if (!(argv = get_local(interp, 0))) return interp_error(interp, "strs"); if (!(argv_buf = get_local(interp, 1))) return interp_error(interp, "strs_buf"); make_cursor(interp->memory.start + argv_buf->num.i32, interp->memory.p, &writer); for (i = 0; i < count; i++) { if (!store_i32(interp, argv->num.i32 + i*4, (int)(writer.p - interp->memory.start))) { return interp_error(interp, "store argv %d ptr\n", i); } len = (int)strlen(strs[i]) + 1; // debug("get_str %d '%.*s'\n", i, len, strs[i]); if (!cursor_push(&writer, (u8*)strs[i], len)) { return interp_error(interp,"write arg %d", i+1); } } return stack_push_i32(interp, 0); } static int wasi_strs_sizes_get(struct wasm_interp *interp, int count, const char **strs) { struct val *argc_addr, *argv_buf_size_addr; int i, size = 0; if (!(argc_addr = get_local(interp, 0))) return interp_error(interp, "strs count"); if (!(argv_buf_size_addr = get_local(interp, 1))) return interp_error(interp, "strs buf_size"); if (!store_i32(interp, argc_addr->num.i32, count)) return interp_error(interp, "store argc"); for (i = 0; i < count; i++) size += strlen(strs[i])+1; if (!store_i32(interp, argv_buf_size_addr->num.i32, size)) { return interp_error(interp, "store strs size"); } return stack_push_i32(interp, 0); } static int wasi_args_get(struct wasm_interp *interp) { return wasi_get_strs(interp, interp->wasi.argc, interp->wasi.argv); } static int wasi_environ_get(struct wasm_interp *interp) { return wasi_get_strs(interp, interp->wasi.environc, interp->wasi.environ); } static int wasi_args_sizes_get(struct wasm_interp *interp) { return wasi_strs_sizes_get(interp, interp->wasi.argc, interp->wasi.argv); } static int wasi_environ_sizes_get(struct wasm_interp *interp) { return wasi_strs_sizes_get(interp, interp->wasi.environc, interp->wasi.environ); } */ static int interp_store(struct wasm_interp *interp, struct memarg *memarg, enum valtype type, int N) { struct val c; int i; if (unlikely(!stack_pop_valtype(interp, type, &c))) { return interp_error(interp, "pop stack"); } if (unlikely(!stack_pop_i32(interp, &i))) { return interp_error(interp, "pop stack"); } return store_val(interp, i, memarg, type, &c, N); } static INLINE int interp_global_set(struct wasm_interp *interp, int global_ind) { struct val *global, setval; if (unlikely(!(global = get_global_inst(interp, global_ind)))) { return interp_error(interp, "couldn't get global %d", global_ind); } if (unlikely(!stack_popval(interp, &setval))) { return interp_error(interp, "couldn't pop stack value"); } memcpy(global, &setval, sizeof(setval)); return 1; } static INLINE int active_pages(struct wasm_interp *interp) { return (int)cursor_count(&interp->memory, WASM_PAGE_SIZE); } static int interp_memory_grow(struct wasm_interp *interp, u8 memidx) { int pages = 0, prev_size, grow; (void)memidx; if (unlikely(!has_memory_section(interp->module))) { return interp_error(interp, "no memory section"); } if (!stack_pop_i32(interp, &pages)) { return interp_error(interp, "pop pages"); } grow = pages * WASM_PAGE_SIZE; prev_size = active_pages(interp); if (interp->memory.p + grow <= interp->memory.end) { interp->memory.p += grow; pages = prev_size; } else { pages = -1; } return stack_push_i32(interp, pages); } static INLINE int interp_memory_size(struct wasm_interp *interp, u8 memidx) { (void)memidx; if (unlikely(!has_memory_section(interp->module))) { return interp_error(interp, "no memory section"); } if (!stack_push_i32(interp, active_pages(interp))) { return interp_error(interp, "push memory size"); } return 1; } static INLINE int interp_i32_eq(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) { return interp_error(interp, "binop prep"); } return stack_push_i32(interp, lhs.num.i32 == rhs.num.i32); } static INLINE int interp_i32_wrap_i64(struct wasm_interp *interp) { int64_t n; if (unlikely(!stack_pop_i64(interp, &n))) return interp_error(interp, "pop"); return stack_push_i32(interp, (int)n); } static INLINE int interp_i32_xor(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); return stack_push_i32(interp, lhs.num.i32 ^ rhs.num.i32); } static INLINE int interp_i32_ne(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); return stack_push_i32(interp, lhs.num.i32 != rhs.num.i32); } static int interp_i64_shl(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); c.num.i64 = lhs.num.i64 << shiftmask64(rhs.num.i64); return stack_pushval(interp, &c); } static int interp_i64_ne(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); make_i32_val(&c, lhs.num.i64 != rhs.num.i64); return stack_pushval(interp, &c); } static int interp_i64_eq(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); make_i32_val(&c, lhs.num.i64 == rhs.num.i64); return stack_pushval(interp, &c); } static int interp_i64_rem_s(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); c.num.i64 = lhs.num.i64 % rhs.num.i64; return stack_pushval(interp, &c); } static int interp_i64_rem_u(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); c.num.u64 = lhs.num.u64 % rhs.num.u64; return stack_pushval(interp, &c); } static int interp_i32_shr_u(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.u32 = lhs.num.u32 >> shiftmask32(rhs.num.u32); return stack_pushval(interp, &c); } static int interp_i32_shr_s(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.i32 = lhs.num.i32 >> shiftmask32(rhs.num.i32); return stack_pushval(interp, &c); } static int interp_i64_shr_u(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); c.num.u64 = lhs.num.u64 >> shiftmask64(rhs.num.u64); return stack_pushval(interp, &c); } static int interp_i64_shr_s(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i64))) return interp_error(interp, "binop prep"); c.num.i64 = lhs.num.i64 >> shiftmask64(rhs.num.i64); return stack_pushval(interp, &c); } static int interp_i32_shl(struct wasm_interp *interp) { struct val lhs, rhs, c; if (unlikely(!interp_prep_binop(interp, &lhs, &rhs, &c, val_i32))) return interp_error(interp, "binop prep"); c.num.i32 = lhs.num.i32 << shiftmask32(rhs.num.i32); return stack_pushval(interp, &c); } #ifdef DEBUG static void print_linestack(struct cursor *stack) { struct val *val; int first = 1; val = (struct val*)stack->p; while (--val >= (struct val*)stack->start) { if (first) { first = 0; } else { printf(", "); } print_val(val); } printf("\n"); } #endif static int interp_extend(struct wasm_interp *interp, enum valtype to, enum valtype from, int sign) { struct val *val; int64_t i64; int i32; (void)sign; if (unlikely(!(val = stack_topval(interp)))) { return interp_error(interp, "no value on stack"); } if (val->type != from) { return interp_error(interp, "value on stack is of type %s, expected %s", valtype_name(val->type), valtype_name(from)); } switch (from) { case val_i32: i64 = val->num.i32; val->num.i64 = i64; break; case val_i64: i32 = (int)val->num.i64; val->num.i32 = i32; break; default: return interp_error(interp, "unhandled extend from %s to %s", valtype_name(from), valtype_name(to)); } val->type = to; return 1; } static INLINE int interp_drop(struct wasm_interp *interp) { return cursor_drop(&interp->stack, sizeof(struct val)); } static int interp_loop(struct wasm_interp *interp) { struct blocktype blocktype; struct cursor *code; struct label *label; if (unlikely(!(code = interp_codeptr(interp)))) { return interp_error(interp, "empty callstack?"); } if (unlikely(!parse_blocktype(code, &interp->errors, &blocktype))) { return interp_error(interp, "couldn't parse blocktype"); } if (unlikely(!push_label_checkpoint(interp, &label, i_loop, i_end))) { return interp_error(interp, "block label checkpoint"); } return 1; } static INLINE int table_set(struct wasm_interp *interp, struct table_inst *table, u32 ind, struct val *val) { if (unlikely(ind >= table->num_refs)) { return interp_error(interp, "invalid index %d (max %d)", ind, interp->module_inst.num_tables); } if (unlikely(table->reftype != (enum reftype)val->type)) { return interp_error(interp, "can't store %s ref in %s table", valtype_name(val->type), valtype_name((enum valtype)table->reftype)); } debug("setting table[%ld] ref %d to ", (table - interp->module_inst.tables) / sizeof (struct table_inst), ind); #ifdef DEBUG print_refval(&val->ref, table->reftype); printf("\n"); #endif memcpy(&table->refs[ind], &val->ref, sizeof(struct refval)); return 1; } static int interp_memory_copy(struct wasm_interp *interp) { int dest, src, size; u8 *data_src, *data_dest; if (unlikely(!stack_pop_i32(interp, &size))) return interp_error(interp, "size"); if (unlikely(!stack_pop_i32(interp, &src))) return interp_error(interp, "byte"); if (unlikely(!stack_pop_i32(interp, &dest))) return interp_error(interp, "destination"); if (!(data_dest = interp_mem_ptr(interp, dest, size))) return interp_error(interp, "memory copy dest out of bounds"); if (!(data_src = interp_mem_ptr(interp, src, size))) return interp_error(interp, "memory copy src out of bounds"); debug("memory.copy src:%d dst:%d size:%d\n", src, dest, size); memcpy(data_dest, data_src, size); return 1; } static int interp_memory_fill(struct wasm_interp *interp) { int dest, byte, size; u8 *data; if (unlikely(!stack_pop_i32(interp, &size))) return interp_error(interp, "size"); if (unlikely(!stack_pop_i32(interp, &byte))) return interp_error(interp, "byte"); if (unlikely(!stack_pop_i32(interp, &dest))) return interp_error(interp, "destination"); if (!(data = interp_mem_ptr(interp, dest, size))) return interp_error(interp, "memory fill out of bounds"); debug("memory.fill dst:%d byte:%d size:%d\n", dest, byte, size); memset(data, byte, size); return 1; } static INLINE int interp_bulk_op(struct wasm_interp *interp, struct bulk_op *op) { switch (op->tag) { case i_memory_fill: return interp_memory_fill(interp); case i_memory_copy: return interp_memory_copy(interp); case i_table_init: case i_elem_drop: case i_table_copy: case i_table_grow: case i_table_size: case i_table_fill: return interp_error(interp, "unhandled bulk op: %s", bulk_op_name(op)); } return interp_error(interp, "unhandled unknown bulk op: %d", op->tag); } static int interp_table_set(struct wasm_interp *interp, u32 tableidx) { struct table_inst *table; struct val val; int ind; if (unlikely(tableidx >= interp->module_inst.num_tables)) { return interp_error(interp, "tableidx oob %d (max %d)", tableidx, interp->module_inst.num_tables + 1); } table = &interp->module_inst.tables[tableidx]; if (unlikely(!stack_pop_ref(interp, &val))) { return interp_error(interp, "pop ref"); } if (unlikely(!stack_pop_i32(interp, &ind))) { return interp_error(interp, "pop elem index"); } return table_set(interp, table, ind, &val); } static int interp_memory_init(struct wasm_interp *interp, u32 dataidx) { struct wdata *data; int count, src, dst; u32 num_data; num_data = interp->module->data_section.num_datas; if (unlikely(dataidx >= num_data)) { return interp_error(interp, "invalid data index %d / %d", dataidx, num_data-1); } data = &interp->module->data_section.datas[dataidx]; if(unlikely(!stack_pop_i32(interp, &count))) return interp_error(interp, "pop count"); if(unlikely(!stack_pop_i32(interp, &src))) return interp_error(interp, "pop src"); if(unlikely(!stack_pop_i32(interp, &dst))) return interp_error(interp, "pop dst"); if (src + count > (int)data->bytes_len) { return interp_error(interp, "count %d > data len %d", count, data->bytes_len); } if (interp->memory.start + dst + count >= interp->memory.p) { return interp_error(interp, "memory write oob %d > %d", count, interp->memory.p - interp->memory.start); } debug("memory_init src:%d dst:%d count:%d\n", src, dst, count); memcpy(interp->memory.start + dst, data->bytes + src, count); return 1; /* for (; count; count--; dst++, src++) { if (unlikely(src + count > data)) { return interp_error(interp, "src %d (max %d)", src + count, num_data + 1); } if (unlikely(dst + count > active_pages(interp))) { return interp_error(interp, "dst oob", dst + count, table->num_refs + 1); } } */ return 1; } static int interp_table_init(struct wasm_interp *interp, struct table_init *t) { struct table_inst *table; struct elem_inst *elem_inst; int num_inits, dst, src; if (unlikely(t->tableidx >= interp->module_inst.num_tables)) { return interp_error(interp, "tableidx oob %d (max %d)", t->tableidx, interp->module_inst.num_tables + 1); } table = &interp->module_inst.tables[t->tableidx]; // TODO: elem addr ? if (unlikely(t->elemidx >= interp->module->element_section.num_elements)) { return interp_error(interp, "elemidx oob %d (max %d)", t->elemidx, interp->module->element_section.num_elements + 1); } if (unlikely(!stack_pop_i32(interp, &num_inits))) { return interp_error(interp, "pop num_inits"); } if (unlikely(!stack_pop_i32(interp, &src))) { return interp_error(interp, "pop src"); } if (unlikely(!stack_pop_i32(interp, &dst))) { return interp_error(interp, "pop dst"); } for (; num_inits; num_inits--, dst++, src++) { if (unlikely((u32)src + num_inits > interp->module_inst.num_elements)) { return interp_error(interp, "index oob elem.elem s+n %d (max %d)", src + num_inits, interp->module_inst.num_elements + 1); } if (unlikely((u32)dst + num_inits > table->num_refs)) { return interp_error(interp, "index oob tab.elem d+n %d (max %d)", dst + num_inits, table->num_refs + 1); } elem_inst = &interp->module_inst.elements[src]; if (!table_set(interp, table, dst, &elem_inst->val)) { return interp_error(interp, "table set failed for table %d ind %d"); } } return 1; } static int interp_select(struct wasm_interp *interp, struct select_instr *select) { struct val top, bottom; int c; (void)select; if (unlikely(!stack_pop_i32(interp, &c))) return interp_error(interp, "pop select"); if (unlikely(!stack_popval(interp, &top))) return interp_error(interp, "pop val top"); if (unlikely(!stack_popval(interp, &bottom))) return interp_error(interp, "pop val bottom"); if (unlikely(top.type != bottom.type)) return interp_error(interp, "type mismatch, %s != %s", valtype_name(top.type), valtype_name(bottom.type)); if (c != 0) return stack_pushval(interp, &bottom); else return stack_pushval(interp, &top); } enum interp_end { interp_end_err, interp_end_next, interp_end_done, }; // tricky... static int interp_end(struct wasm_interp *interp) { struct callframe *frame; int loc_resolvers; if (unlikely(!(frame = top_callframe(&interp->callframes)))) { debug("no callframes, done.\n"); // no more resolvers, we done. return interp_end_done; } if (unlikely(!count_local_resolvers(interp, &loc_resolvers))) { return interp_error(interp, "count local resolvers"); } if (loc_resolvers == 0) { if (!drop_callframe(interp)) return interp_error(interp, "drop callframe at end of fn"); return interp_end_next; } return pop_label_checkpoint(interp); } static int interp_instr(struct wasm_interp *interp, struct instr *instr) { interp->ops++; debug("%04X %-30s | ", instr->pos, show_instr(instr)); #if DEBUG print_linestack(&interp->stack); #endif switch (instr->tag) { case i_unreachable: return interp_error(interp, "unreachable"); case i_nop: return 1; case i_select: case i_selects: return interp_select(interp, &instr->select); case i_local_get: return interp_local_get(interp, instr->i32); case i_local_set: return interp_local_set(interp, instr->i32); case i_local_tee: return interp_local_tee(interp, instr->i32); case i_global_get: return interp_global_get(interp, instr->i32); case i_global_set: return interp_global_set(interp, instr->i32); case i_f32_const: return interp_f32_const(interp, instr->f32); case i_f32_abs: return interp_f32_abs(interp); case i_f32_div: return interp_f32_div(interp); case i_f32_mul: return interp_f32_mul(interp); case i_f32_neg: return interp_f32_neg(interp); case i_f32_add: return interp_f32_add(interp); case i_f32_sub: return interp_f32_sub(interp); case i_f32_lt: return interp_f32_lt(interp); case i_f32_le: return interp_f32_le(interp); case i_f32_gt: return interp_f32_gt(interp); case i_f32_ge: return interp_f32_ge(interp); case i_f32_eq: return interp_f32_eq(interp); case i_f32_ne: return interp_f32_ne(interp); case i_f32_max: return interp_f32_max(interp); case i_f32_min: return interp_f32_max(interp); case i_f32_sqrt: return interp_f32_sqrt(interp); case i_f32_convert_i32_s: return interp_f32_convert_i32_s(interp); case i_i32_reinterpret_f32: return interp_i32_reinterpret_f32(interp); case i_f64_promote_f32: return interp_f64_promote_f32(interp); case i_i32_trunc_f64_s: return interp_i32_trunc_f64_s(interp); case i_f32_demote_f64: return interp_f32_demote_f64(interp); case i_f64_convert_i32_s: return interp_f64_convert_i32_s(interp); case i_f64_convert_i64_u: return interp_f64_convert_i64_u(interp); case i_i64_reinterpret_f64: return interp_i64_reinterpret_f64(interp); case i_f64_reinterpret_i64: return interp_f64_reinterpret_i64(interp); case i_i32_trunc_f32_s: return interp_i32_trunc_f32_s(interp); case i_f32_convert_i32_u: return interp_f32_convert_i32_u(interp); case i_i32_trunc_f64_u: return interp_i32_trunc_f64_u(interp); case i_f64_convert_i32_u: return interp_f64_convert_i32_u(interp); case i_f32_reinterpret_i32: return interp_f32_reinterpret_i32(interp); case i_f64_abs: return interp_f64_abs(interp); case i_f64_eq: return interp_f64_eq(interp); case i_f64_ne: return interp_f64_ne(interp); case i_f64_add: return interp_f64_add(interp); case i_f64_neg: return interp_f64_neg(interp); case i_f64_ceil: return interp_f64_ceil(interp); case i_f64_floor: return interp_f64_floor(interp); case i_f64_sqrt: return interp_f64_sqrt(interp); case i_f64_const: return interp_f64_const(interp, instr->f64); case i_f64_div: return interp_f64_div(interp); case i_f64_ge: return interp_f64_ge(interp); case i_f64_gt: return interp_f64_gt(interp); case i_f64_le: return interp_f64_le(interp); case i_f64_lt: return interp_f64_lt(interp); case i_f64_mul: return interp_f64_mul(interp); case i_f64_sub: return interp_f64_sub(interp); case i_i32_clz: return interp_i32_clz(interp); case i_i32_ctz: return interp_i32_ctz(interp); case i_i32_popcnt: return interp_i32_popcnt(interp); case i_i32_eqz: return interp_i32_eqz(interp); case i_i32_add: return interp_i32_add(interp); case i_i32_sub: return interp_i32_sub(interp); case i_i32_const: return interp_i32_const(interp, instr->i32); case i_i32_div_u: return interp_i32_div_u(interp); case i_i32_div_s: return interp_i32_div_s(interp); case i_i32_ge_u: return interp_i32_ge_u(interp); case i_i32_rotl: return interp_i32_rotl(interp); case i_i32_rotr: return interp_i32_rotr(interp); case i_i32_ge_s: return interp_i32_ge_s(interp); case i_i32_gt_u: return interp_i32_gt_u(interp); case i_i32_gt_s: return interp_i32_gt_s(interp); case i_i32_le_s: return interp_i32_le_s(interp); case i_i32_le_u: return interp_i32_le_u(interp); case i_i32_lt_s: return interp_i32_lt_s(interp); case i_i32_lt_u: return interp_i32_lt_u(interp); case i_i32_shl: return interp_i32_shl(interp); case i_i32_shr_u: return interp_i32_shr_u(interp); case i_i32_shr_s: return interp_i32_shr_s(interp); case i_i32_or: return interp_i32_or(interp); case i_i32_and: return interp_i32_and(interp); case i_i32_mul: return interp_i32_mul(interp); case i_i32_xor: return interp_i32_xor(interp); case i_i32_ne: return interp_i32_ne(interp); case i_i32_rem_u: return interp_i32_rem_u(interp); case i_i32_rem_s: return interp_i32_rem_s(interp); case i_i32_eq: return interp_i32_eq(interp); case i_i32_wrap_i64:return interp_i32_wrap_i64(interp); case i_i64_clz: return interp_i64_clz(interp); case i_i64_add: return interp_i64_add(interp); case i_i64_and: return interp_i64_and(interp); case i_i64_eqz: return interp_i64_eqz(interp); case i_i64_gt_s: return interp_i64_gt_s(interp); case i_i64_lt_u: return interp_i64_lt_u(interp); case i_i64_lt_s: return interp_i64_lt_s(interp); case i_i64_le_u: return interp_i64_le_u(interp); case i_i64_le_s: return interp_i64_le_s(interp); case i_i64_gt_u: return interp_i64_gt_u(interp); case i_i64_ge_u: return interp_i64_ge_u(interp); case i_i64_ge_s: return interp_i64_ge_s(interp); case i_i64_div_u: return interp_i64_div_u(interp); case i_i64_xor: return interp_i64_xor(interp); case i_i64_mul: return interp_i64_mul(interp); case i_i64_shl: return interp_i64_shl(interp); case i_i64_ne: return interp_i64_ne(interp); case i_i64_eq: return interp_i64_eq(interp); case i_i64_rem_u: return interp_i64_rem_u(interp); case i_i64_rem_s: return interp_i64_rem_s(interp); case i_i64_shr_u: return interp_i64_shr_u(interp); case i_i64_shr_s: return interp_i64_shr_s(interp); case i_i64_or: return interp_i64_or(interp); case i_i64_sub: return interp_i64_sub(interp); case i_i64_const: return interp_i64_const(interp, instr->i64); case i_i64_extend_i32_u: return interp_extend(interp, val_i64, val_i32, 0); case i_i64_extend_i32_s: return interp_extend(interp, val_i64, val_i32, 1); case i_i32_store: return interp_store(interp, &instr->memarg, val_i32, 0); case i_i32_store8: return interp_store(interp, &instr->memarg, val_i32, 8); case i_i32_store16: return interp_store(interp, &instr->memarg, val_i32, 16); case i_f32_store: return interp_store(interp, &instr->memarg, val_f32, 0); case i_f64_store: return interp_store(interp, &instr->memarg, val_f64, 0); case i_i64_store: return interp_store(interp, &instr->memarg, val_i64, 0); case i_i64_store8: return interp_store(interp, &instr->memarg, val_i64, 8); case i_i64_store16: return interp_store(interp, &instr->memarg, val_i64, 16); case i_i64_store32: return interp_store(interp, &instr->memarg, val_i64, 32); case i_i32_load: return interp_load(interp, &instr->memarg, val_i32, 0, -1); case i_i32_load8_s: return interp_load(interp, &instr->memarg, val_i32, 8, 1); case i_i32_load8_u: return interp_load(interp, &instr->memarg, val_i32, 8, 0); case i_i32_load16_s: return interp_load(interp, &instr->memarg, val_i32, 16, 1); case i_i32_load16_u: return interp_load(interp, &instr->memarg, val_i32, 16, 0); case i_f32_load: return interp_load(interp, &instr->memarg, val_f32, 0, -1); case i_f64_load: return interp_load(interp, &instr->memarg, val_f64, 0, -1); case i_i64_load: return interp_load(interp, &instr->memarg, val_i64, 0, -1); case i_i64_load8_s: return interp_load(interp, &instr->memarg, val_i64, 8, 1); case i_i64_load8_u: return interp_load(interp, &instr->memarg, val_i64, 8, 0); case i_i64_load16_s: return interp_load(interp, &instr->memarg, val_i64, 16, 1); case i_i64_load16_u: return interp_load(interp, &instr->memarg, val_i64, 16, 0); case i_i64_load32_s: return interp_load(interp, &instr->memarg, val_i64, 32, 1); case i_i64_load32_u: return interp_load(interp, &instr->memarg, val_i64, 32, 0); case i_drop: return interp_drop(interp); case i_loop: return interp_loop(interp); case i_if: return interp_if(interp); case i_else: return interp_else(interp); case i_end: return interp_end(interp); case i_call: return interp_call(interp, instr->i32); case i_call_indirect: return interp_call_indirect(interp, &instr->call_indirect); case i_block: return interp_block(interp); case i_br: return interp_br(interp, instr->i32); case i_br_table: return interp_br_table(interp, &instr->br_table); case i_br_if: return interp_br_if(interp, instr->i32); case i_memory_size: return interp_memory_size(interp, instr->memidx); case i_memory_grow: return interp_memory_grow(interp, instr->memidx); case i_bulk_op: return interp_bulk_op(interp, &instr->bulk_op); case i_table_set: return interp_table_set(interp, instr->i32); case i_return: return interp_return(interp); default: interp_error(interp, "unhandled instruction %s 0x%x", instr_name(instr->tag), instr->tag); return 0; } return 0; } static int is_control_instr(u8 tag) { switch (tag) { case i_if: case i_block: case i_loop: return 1; } return 0; } static INLINE int interp_parse_instr(struct wasm_interp *interp, struct cursor *code, struct expr_parser *parser, struct instr *instr) { u8 tag; if (unlikely(!pull_byte(code, &tag))) { return interp_error(interp, "no more instrs to pull"); } instr->tag = tag; instr->pos = (int)(code->p - 1 - code->start); if (is_control_instr(tag)) { return 1; } parser->code = code; if (!parse_instr(parser, instr->tag, instr)) { return interp_error(interp, "parse non-control instr %s", instr_name(tag)); } return 1; } static int interp_elem_drop(struct wasm_interp *interp, int elemidx) { (void)interp; (void)elemidx; // we don't really need to do anything here... return 1; } static int interp_code(struct wasm_interp *interp) { struct instr instr; struct expr_parser parser; struct callframe *frame; int ret; parser.interp = interp; parser.errs = &interp->errors; for (;;) { if (unlikely(!(frame = top_callframe(&interp->callframes)))) { return 1; } if (unlikely(!interp_parse_instr(interp, &frame->code, &parser, &instr))) { return interp_error(interp, "parse instr"); } //cursor_print_around(&frame->code, 10); if (unlikely(!(ret = interp_instr(interp, &instr)))) { return interp_error(interp, "interp instr %s", show_instr(&instr)); } if (instr.tag == i_end) { //cursor_print_around(&frame->code, 10); switch (ret) { case interp_end_err: return 0; case interp_end_done: return 1; case interp_end_next: break; } } if (ret == BUILTIN_SUSPEND) return BUILTIN_SUSPEND; } return 1; } static int find_function(struct module *module, const char *name) { struct wexport *export; u32 i; for (i = 0; i < module->export_section.num_exports; i++) { export = &module->export_section.exports[i]; if (!strcmp(name, export->name)) { return export->index; } } return -1; } static int find_start_function(struct module *module) { int res; if (was_section_parsed(module, section_start)) { debug("getting start function from start section\n"); return module->start_section.start_fn; } if ((res = find_function(module, "_start")) != -1) { return res; } return find_function(module, "start"); } void wasm_parser_init(struct wasm_parser *p, u8 *wasm, size_t wasm_len, size_t arena_size, struct builtin *builtins, int num_builtins) { u8 *mem; mem = calloc(1, arena_size); assert(mem); make_cursor(wasm, wasm + wasm_len, &p->cur); make_cursor(mem, mem + arena_size, &p->mem); p->errs.enabled = 1; p->num_builtins = 0; p->builtins = builtins; p->num_builtins = num_builtins; cursor_slice(&p->mem, &p->errs.cur, 0xFFFF); } static int calculate_tables_size(struct module *module) { u32 i, num_tables, size; struct table *tables; if (!was_section_parsed(module, section_table)) return 0; tables = module->table_section.tables; num_tables = module->table_section.num_tables; size = num_tables * sizeof(struct table_inst); for (i = 0; i < num_tables; i++) { size += sizeof(struct refval) * tables[i].limits.min; } return size; } static int alloc_tables(struct wasm_interp *interp) { struct table *t; struct table_inst *inst; u32 i, size; if (!was_section_parsed(interp->module, section_table)) return 1; interp->module_inst.num_tables = interp->module->table_section.num_tables; if (!(interp->module_inst.tables = cursor_alloc(&interp->mem, interp->module_inst.num_tables * sizeof(struct table_inst)))) { return interp_error(interp, "couldn't alloc table instances"); } for (i = 0; i < interp->module_inst.num_tables; i++) { t = &interp->module->table_section.tables[i]; inst = &interp->module_inst.tables[i]; inst->reftype = t->reftype; inst->num_refs = t->limits.min; size = sizeof(struct refval) * t->limits.min; if (!(inst->refs = cursor_alloc(&interp->mem, size))) { return interp_error(interp, "couldn't alloc table inst %d/%d", i+1, interp->module->table_section.num_tables); } } return 1; } static int init_element(struct wasm_interp *interp, struct expr *init, struct elem_inst *elem_inst) { if (!eval_const_val(init, &interp->errors, &interp->stack, &elem_inst->val)) { return interp_error(interp, "failed to eval element init expr"); } return 1; } static int init_table(struct wasm_interp *interp, struct elem *elem, int elemidx, int num_elems) { struct table_init t; if (elem->tableidx != 0) { return interp_error(interp, "tableidx should be 0 for elem %d", elemidx); } if (!eval_const_expr(&elem->offset, &interp->errors, &interp->stack)) { return interp_error(interp, "failed to eval elem offset expr"); } if (!stack_push_i32(interp, 0)) { return interp_error(interp, "push 0 when init element"); } if (!stack_push_i32(interp, num_elems)) { return interp_error(interp, "push num_elems in init element"); } t.tableidx = elem->tableidx; t.elemidx = elemidx; if (!interp_table_init(interp, &t)) { return interp_error(interp, "table init"); } if (!interp_elem_drop(interp, elemidx)) { return interp_error(interp, "drop elem"); } return 1; } static int init_global(struct wasm_interp *interp, struct global *global, struct global_inst *global_inst) { if (!eval_const_val(&global->init, &interp->errors, &interp->stack, &global_inst->val)) { return interp_error(interp, "eval const expr"); } debug("init global to %s %d\n", valtype_name(global_inst->val.type), global_inst->val.num.i32); if (cursor_top(&interp->stack, sizeof(struct val))) { return interp_error(interp, "stack not empty"); } return 1; } static int init_globals(struct wasm_interp *interp) { struct global *globals, *global; struct global_inst *global_insts, *global_inst; u32 i; if (!was_section_parsed(interp->module, section_global)) { // nothing to init return 1; } globals = interp->module->global_section.globals; global_insts = interp->module_inst.globals; for (i = 0; i < interp->module->global_section.num_globals; i++) { global = &globals[i]; global_inst = &global_insts[i]; if (!init_global(interp, global, global_inst)) { return interp_error(interp, "global init"); } } return 1; } static int count_element_insts(struct module *module) { struct elem *elem; u32 i, size = 0; if (!was_section_parsed(module, section_element)) return 0; for (i = 0; i < module->element_section.num_elements; i++) { elem = &module->element_section.elements[i]; size += elem->num_inits; } return size; } static int init_memory(struct wasm_interp *interp, struct wdata *data, int dataidx) { if (!eval_const_expr(&data->active.offset_expr, &interp->errors, &interp->stack)) { return interp_error(interp, "failed to eval data offset expr"); } if (!stack_push_i32(interp, 0)) { return interp_error(interp, "push 0 when init element"); } if (!stack_push_i32(interp, data->bytes_len)) { return interp_error(interp, "push num_elems in init element"); } if (!interp_memory_init(interp, dataidx)) { return interp_error(interp, "table init"); } /* if (!interp_data_drop(interp, elemidx)) { return interp_error(interp, "drop elem"); } */ return 1; } static int init_memories(struct wasm_interp *interp) { struct wdata *data; u32 i; debug("init memories\n"); if (!was_section_parsed(interp->module, section_data)) return 1; if (!was_section_parsed(interp->module, section_memory)) return 1; for (i = 0; i < interp->module->data_section.num_datas; i++) { data = &interp->module->data_section.datas[i]; if (data->mode != datamode_active) continue; if (!init_memory(interp, data, i)) { return interp_error(interp, "init memory %d failed", i); } } return 1; } static int init_tables(struct wasm_interp *interp) { struct elem *elem; u32 i; if (!was_section_parsed(interp->module, section_table)) return 1; for (i = 0; i < interp->module->element_section.num_elements; i++) { elem = &interp->module->element_section.elements[i]; if (elem->mode != elem_mode_active) continue; if (!init_table(interp, elem, i, elem->num_inits)) { return interp_error(interp, "init table failed"); } } return 1; } static int init_elements(struct wasm_interp *interp) { struct elem *elems, *elem; struct elem_inst *inst; struct expr *init; u32 count = 0; u32 i, j; debug("init elements\n"); if (!was_section_parsed(interp->module, section_element)) return 1; elems = interp->module->element_section.elements; for (i = 0; i < interp->module->element_section.num_elements; i++) { elem = &elems[i]; if (elem->mode != elem_mode_active) continue; for (j = 0; j < elem->num_inits; j++, count++) { init = &elem->inits[j]; assert(count < interp->module_inst.num_elements); inst = &interp->module_inst.elements[count]; inst->elem = i; inst->init = j; if (!init_element(interp, init, inst)) { return interp_error(interp, "init element %d", j); } } } return 1; } // https://webassembly.github.io/spec/core/exec/modules.html#instantiation static int instantiate_module(struct wasm_interp *interp) { int func; //TODO:Assert module is valid with external types classifying its imports // TODO: If the number # of imports is not equal to the number of provided external values then fail /* if (!push_aux_callframe(interp)) { return interp_error(interp, "failed to pushed aux callframe?? " "ok if this happens seriously wtf why am I even" " writing this error message..)"; } */ func = interp->module_inst.start_fn != -1 ? interp->module_inst.start_fn : find_start_function(interp->module); /* memset(interp->module_inst.globals, 0, interp->module_inst.num_globals * sizeof(*interp->module_inst.globals)); memset(interp->module_inst.globals_init, 0, interp->module_inst.num_globals); */ if (func == -1) { return interp_error(interp, "no start function found"); } else { interp->module_inst.start_fn = func; debug("found start function %s (%d)\n", get_function_name(interp->module, func), func); } if (!init_memories(interp)) { return interp_error(interp, "memory init"); } if (!init_elements(interp)) { return interp_error(interp, "elements init"); } if (!init_tables(interp)) { return interp_error(interp, "table init"); } if (!init_globals(interp)) { return interp_error(interp, "globals init"); } return 1; } static int reset_memory(struct wasm_interp *interp) { int pages, num_mems; num_mems = was_section_parsed(interp->module, section_memory)? interp->module->memory_section.num_mems : 0; reset_cursor(&interp->memory); if (num_mems == 1) { pages = interp->module->memory_section.mems[0].min; if (pages == 0) return 1; if (!cursor_malloc(&interp->memory, pages * WASM_PAGE_SIZE)) { return interp_error(interp, "could not alloc %d memory pages", pages); } assert(interp->memory.p > interp->memory.start); // I technically need this... //memset(interp->memory.start, 0, pages * WASM_PAGE_SIZE); } return 1; } void setup_wasi(struct wasm_interp *interp, int argc, const char **argv, char **env) { char **s = env; interp->wasi.argc = argc; interp->wasi.argv = argv; interp->wasi.environ = (const char**)env; interp->wasi.environc = 0; if (env) for (; *s; s++, interp->wasi.environc++); } int wasm_interp_init(struct wasm_interp *interp, struct module *module) { unsigned char *mem, *heap, *start; unsigned int ok, fns, errors_size, stack_size, locals_size, callframes_size, resolver_size, labels_size, num_labels_size, labels_capacity, memsize, memory_pages_size, resolver_offsets_size, num_mems, globals_size, num_globals, tables_size, elems_size, num_elements; memset(interp, 0, sizeof(*interp)); setup_wasi(interp, 0, NULL, NULL); interp->quitting = 0; interp->module = module; interp->module_inst.start_fn = -1; interp->prev_resolvers = 0; //stack = calloc(1, STACK_SPACE); fns = module->num_funcs; labels_capacity = fns * MAX_LABELS; debug("%d fns, labels capacity %d\n", fns, labels_capacity); num_mems = was_section_parsed(module, section_memory)? module->memory_section.num_mems : 0; num_globals = was_section_parsed(module, section_global)? module->global_section.num_globals : 0; // TODO: make memory limits configurable errors_size = 0xFFF; stack_size = sizeof(struct val) * 0xFF; labels_size = labels_capacity * sizeof(struct label); num_labels_size = fns * sizeof(u16); resolver_offsets_size = sizeof(int) * 2048; callframes_size = sizeof(struct callframe) * 2048; resolver_size = sizeof(struct resolver) * MAX_LABELS * 32; globals_size = sizeof(struct global_inst) * num_globals; num_elements = count_element_insts(module); elems_size = num_elements * sizeof(struct elem_inst); locals_size = 1024 * 1024 * 5; // 5MB stack? tables_size = calculate_tables_size(module); if (num_mems > 1) { printf("more than one memory instance is not supported\n"); return 0; } // keep total memory size small for now, iOS doesn't like like mallocs memory_pages_size = 8 * WASM_PAGE_SIZE; memsize = stack_size + errors_size + resolver_offsets_size + resolver_size + callframes_size + globals_size + num_globals + labels_size + num_labels_size + locals_size + tables_size + elems_size ; mem = calloc(1, memsize); heap = malloc(memory_pages_size); make_cursor(mem, mem + memsize, &interp->mem); make_cursor(heap, heap + memory_pages_size, &interp->memory); // enable error reporting by default interp->errors.enabled = 1; start = interp->mem.p; ok = cursor_slice(&interp->mem, &interp->stack, stack_size); assert(interp->mem.p - start == stack_size); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->errors.cur, errors_size); assert(interp->mem.p - start == errors_size); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->resolver_offsets, resolver_offsets_size); assert(interp->mem.p - start == resolver_offsets_size); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->resolver_stack, resolver_size); assert(interp->mem.p - start == resolver_size); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->callframes, callframes_size); assert(interp->mem.p - start == callframes_size); interp->module_inst.num_globals = num_globals; start = interp->mem.p; ok = ok && (interp->module_inst.globals = cursor_alloc(&interp->mem, globals_size)); assert(interp->mem.p - start == globals_size); start = interp->mem.p; ok = ok && (interp->module_inst.globals_init = cursor_alloc(&interp->mem, num_globals)); assert(interp->mem.p - start == num_globals); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->labels, labels_size); assert(interp->mem.p - start == labels_size); start = interp->mem.p; ok = ok && alloc_tables(interp); assert(interp->mem.p - start == tables_size); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->num_labels, num_labels_size); assert(interp->mem.p - start == num_labels_size); start = interp->mem.p; ok = ok && cursor_slice(&interp->mem, &interp->locals, locals_size); assert(interp->mem.p - start == locals_size); interp->module_inst.num_elements = num_elements; start = interp->mem.p; ok = ok && (interp->module_inst.elements = cursor_alloc(&interp->mem, elems_size)); assert(interp->mem.p - start == elems_size); /* init memory pages */ assert((interp->mem.end - interp->mem.start) == memsize); if (!ok) { return interp_error(interp, "not enough memory"); } return 1; } void wasm_parser_free(struct wasm_parser *parser) { if (parser->mem.start) { free(parser->mem.start); parser->mem.start = 0; } } void wasm_interp_free(struct wasm_interp *interp) { if (interp->mem.start) { free(interp->mem.start); interp->mem.start = 0; } if (interp->memory.start) { free(interp->memory.start); interp->memory.start = 0; } } int interp_wasm_module_resume(struct wasm_interp *interp, int *retval) { int res = interp_code(interp); if (res == 1) { stack_pop_i32(interp, retval); debug("interp success!!\n"); } else if (interp->quitting) { stack_pop_i32(interp, retval); debug("process exited\n"); } else if (res == BUILTIN_SUSPEND) { return BUILTIN_SUSPEND; } else { *retval = 8; return interp_error(interp, "interp_code"); } return 1; } int interp_wasm_module(struct wasm_interp *interp, int *retval) { interp->ops = 0; *retval = 0; if (interp->module->code_section.num_funcs == 0) { interp_error(interp, "empty module"); return 0; } // reset cursors reset_cursor(&interp->stack); reset_cursor(&interp->resolver_stack); reset_cursor(&interp->resolver_offsets); reset_cursor(&interp->errors.cur); reset_cursor(&interp->callframes); // don't reset labels for perf! if (!reset_memory(interp)) return interp_error(interp, "reset memory"); if (!instantiate_module(interp)) return interp_error(interp, "instantiate module"); //interp->mem.p = interp->mem.start; if (!call_function(interp, interp->module_inst.start_fn)) { return interp_error(interp, "call start function"); } return interp_wasm_module_resume(interp, retval); } int run_wasm(unsigned char *wasm, unsigned long len, int argc, const char **argv, char **env, int *retval) { struct wasm_parser p; struct wasm_interp interp; wasm_parser_init(&p, wasm, len, len * 16, 0, 0); if (!parse_wasm(&p)) { wasm_parser_free(&p); return 0; } if (!wasm_interp_init(&interp, &p.module)) { print_error_backtrace(&interp.errors); return 0; } setup_wasi(&interp, argc, argv, env); if (!interp_wasm_module(&interp, retval)) { print_callstack(&interp); print_error_backtrace(&interp.errors); } print_stack(&interp.stack); wasm_interp_free(&interp); wasm_parser_free(&p); return 1; }