NostrScript is a WebAssembly implementation that interacts with Damus. It enables dynamic scripting that can be used to power custom list views, enabling pluggable algorithms. The web has JavaScript, Damus has NostrScript. NostrScripts can be written in any language that compiles to WASM. This commit adds a WASM interpreter I've written as a mostly-single C file for portability and embeddability. In the future we could JIT-compile these for optimal performance if NostrScripts get large and complicated. For now an interpreter is simple enough for algorithm list view plugins. Changelog-Added: Add initial NostrScript implementation Signed-off-by: William Casarin <jb55@jb55.com>
7294 lines
174 KiB
C
7294 lines
174 KiB
C
|
|
#include "wasm.h"
|
|
#include "parser.h"
|
|
#include "debug.h"
|
|
#include "error.h"
|
|
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <inttypes.h>
|
|
|
|
|
|
#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_section_parsed(struct module *module,
|
|
enum section_tag section)
|
|
{
|
|
if (section == section_custom)
|
|
return module->custom_sections > 0;
|
|
|
|
return module->parsed & (1 << section);
|
|
}
|
|
|
|
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))) {
|
|
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_clz(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;
|
|
}
|
|
|
|
memory_pages_size = 64 * 256 * WASM_PAGE_SIZE; /* 1 gb virt */
|
|
|
|
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;
|
|
}
|