diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..420199e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ + +all: nostrscript/primal.wasm + +nostrscript/%.wasm: nostrscript/%.ts nostrscript/nostr.ts Makefile + asc $< --runtime stub --outFile $@ --optimize + +clean: + rm nostrscript/*.wasm diff --git a/damus-c/block.h b/damus-c/block.h index 566bce35..a4453af2 100644 --- a/damus-c/block.h +++ b/damus-c/block.h @@ -35,7 +35,7 @@ typedef struct mention_bech32_block { struct nostr_bech32 bech32; } mention_bech32_block_t; -typedef struct block { +typedef struct note_block { enum block_type type; union { struct str_block str; @@ -45,12 +45,12 @@ typedef struct block { } block; } block_t; -typedef struct blocks { +typedef struct note_blocks { int num_blocks; - struct block *blocks; + struct note_block *blocks; } blocks_t; -void blocks_init(struct blocks *blocks); -void blocks_free(struct blocks *blocks); +void blocks_init(struct note_blocks *blocks); +void blocks_free(struct note_blocks *blocks); #endif /* block_h */ diff --git a/damus-c/cursor.h b/damus-c/cursor.h index 0deed48c..78c7bc82 100644 --- a/damus-c/cursor.h +++ b/damus-c/cursor.h @@ -1,24 +1,432 @@ -// -// cursor.h -// damus -// -// Created by William Casarin on 2023-04-09. -// -#ifndef cursor_h -#define cursor_h +#ifndef PROTOVERSE_CURSOR_H +#define PROTOVERSE_CURSOR_H +#include "typedefs.h" +#include "varint.h" + +#include #include +#include #include -typedef unsigned char u8; +#define unlikely(x) __builtin_expect((x),0) +#define likely(x) __builtin_expect((x),1) struct cursor { - const u8 *p; - const u8 *start; - const u8 *end; + unsigned char *start; + unsigned char *p; + unsigned char *end; }; +struct array { + struct cursor cur; + unsigned int elem_size; +}; + +static inline void reset_cursor(struct cursor *cursor) +{ + cursor->p = cursor->start; +} + +static inline void wipe_cursor(struct cursor *cursor) +{ + reset_cursor(cursor); + memset(cursor->start, 0, cursor->end - cursor->start); +} + +static inline void make_cursor(u8 *start, u8 *end, struct cursor *cursor) +{ + cursor->start = start; + cursor->p = start; + cursor->end = end; +} + +static inline void make_array(struct array *a, u8* start, u8 *end, unsigned int elem_size) +{ + make_cursor(start, end, &a->cur); + a->elem_size = elem_size; +} + +static inline int cursor_eof(struct cursor *c) +{ + return c->p == c->end; +} + +static inline void *cursor_malloc(struct cursor *mem, unsigned long size) +{ + void *ret; + + if (mem->p + size > mem->end) { + return NULL; + } + + ret = mem->p; + mem->p += size; + + return ret; +} + +static inline void *cursor_alloc(struct cursor *mem, unsigned long size) +{ + void *ret; + if (!(ret = cursor_malloc(mem, size))) { + return 0; + } + + memset(ret, 0, size); + return ret; +} + +static inline int cursor_slice(struct cursor *mem, struct cursor *slice, size_t size) +{ + u8 *p; + if (!(p = cursor_alloc(mem, size))) { + return 0; + } + make_cursor(p, mem->p, slice); + return 1; +} + + +static inline void copy_cursor(struct cursor *src, struct cursor *dest) +{ + dest->start = src->start; + dest->p = src->p; + dest->end = src->end; +} + +static inline int pull_byte(struct cursor *cursor, u8 *c) +{ + if (unlikely(cursor->p >= cursor->end)) + return 0; + + *c = *cursor->p; + cursor->p++; + + return 1; +} + +static inline int cursor_pull_c_str(struct cursor *cursor, const char **str) +{ + *str = (const char*)cursor->p; + + for (; cursor->p < cursor->end; cursor->p++) { + if (*cursor->p == 0) { + cursor->p++; + return 1; + } + } + + return 0; +} + + +static inline int cursor_push_byte(struct cursor *cursor, u8 c) +{ + if (unlikely(cursor->p + 1 > cursor->end)) { + return 0; + } + + *cursor->p = c; + cursor->p++; + + return 1; +} + +static inline int cursor_pull(struct cursor *cursor, u8 *data, int len) +{ + if (unlikely(cursor->p + len > cursor->end)) { + return 0; + } + + memcpy(data, cursor->p, len); + cursor->p += len; + + return 1; +} + +static inline int pull_data_into_cursor(struct cursor *cursor, + struct cursor *dest, + unsigned char **data, + int len) +{ + int ok; + + if (unlikely(dest->p + len > dest->end)) { + printf("not enough room in dest buffer\n"); + return 0; + } + + ok = cursor_pull(cursor, dest->p, len); + if (!ok) return 0; + + *data = dest->p; + dest->p += len; + + return 1; +} + +static inline int cursor_dropn(struct cursor *cur, int size, int n) +{ + if (n == 0) + return 1; + + if (unlikely(cur->p - size*n < cur->start)) { + return 0; + } + + cur->p -= size*n; + return 1; +} + +static inline int cursor_drop(struct cursor *cur, int size) +{ + return cursor_dropn(cur, size, 1); +} + +static inline unsigned char *cursor_topn(struct cursor *cur, int len, int n) +{ + n += 1; + if (unlikely(cur->p - len*n < cur->start)) { + return NULL; + } + return cur->p - len*n; +} + +static inline unsigned char *cursor_top(struct cursor *cur, int len) +{ + if (unlikely(cur->p - len < cur->start)) { + return NULL; + } + return cur->p - len; +} + +static inline int cursor_top_int(struct cursor *cur, int *i) +{ + u8 *p; + if (unlikely(!(p = cursor_top(cur, sizeof(*i))))) { + return 0; + } + *i = *((int*)p); + return 1; +} + +static inline int cursor_pop(struct cursor *cur, u8 *data, int len) +{ + if (unlikely(cur->p - len < cur->start)) { + return 0; + } + + cur->p -= len; + memcpy(data, cur->p, len); + + return 1; +} + +static inline int cursor_push(struct cursor *cursor, u8 *data, int len) +{ + if (unlikely(cursor->p + len >= cursor->end)) { + return 0; + } + + if (cursor->p != data) + memcpy(cursor->p, data, len); + + cursor->p += len; + + return 1; +} + +static inline int cursor_push_int(struct cursor *cursor, int i) +{ + return cursor_push(cursor, (u8*)&i, sizeof(i)); +} + +static inline size_t cursor_count(struct cursor *cursor, size_t elem_size) +{ + return (cursor->p - cursor->start)/elem_size; +} + +/* TODO: push_varint */ +static inline int push_varint(struct cursor *cursor, int n) +{ + int ok, len; + unsigned char b; + len = 0; + + while (1) { + b = (n & 0xFF) | 0x80; + n >>= 7; + if (n == 0) { + b &= 0x7F; + ok = cursor_push_byte(cursor, b); + len++; + if (!ok) return 0; + break; + } + + ok = cursor_push_byte(cursor, b); + len++; + if (!ok) return 0; + } + + return len; +} + +/* TODO: pull_varint */ +static inline int pull_varint(struct cursor *cursor, int *n) +{ + int ok, i; + unsigned char b; + *n = 0; + + for (i = 0;; i++) { + ok = pull_byte(cursor, &b); + if (!ok) return 0; + + *n |= ((int)b & 0x7F) << (i * 7); + + /* is_last */ + if ((b & 0x80) == 0) { + return i+1; + } + + if (i == 4) return 0; + } + + return 0; +} + +static inline int cursor_pull_int(struct cursor *cursor, int *i) +{ + return cursor_pull(cursor, (u8*)i, sizeof(*i)); +} + +static inline int cursor_push_u16(struct cursor *cursor, u16 i) +{ + return cursor_push(cursor, (u8*)&i, sizeof(i)); +} + +static inline void *index_cursor(struct cursor *cursor, unsigned int index, int elem_size) +{ + u8 *p; + p = &cursor->start[elem_size * index]; + + if (unlikely(p >= cursor->end)) + return NULL; + + return (void*)p; +} + + +static inline int push_sized_str(struct cursor *cursor, const char *str, int len) +{ + return cursor_push(cursor, (u8*)str, len); +} + +static inline int cursor_push_str(struct cursor *cursor, const char *str) +{ + return cursor_push(cursor, (u8*)str, (int)strlen(str)); +} + +static inline int cursor_push_c_str(struct cursor *cursor, const char *str) +{ + return cursor_push_str(cursor, str) && cursor_push_byte(cursor, 0); +} + +/* TODO: push varint size */ +static inline int push_prefixed_str(struct cursor *cursor, const char *str) +{ + int ok, len; + len = (int)strlen(str); + ok = push_varint(cursor, len); + if (!ok) return 0; + return push_sized_str(cursor, str, len); +} + +static inline int pull_prefixed_str(struct cursor *cursor, struct cursor *dest_buf, const char **str) +{ + int len, ok; + + ok = pull_varint(cursor, &len); + if (!ok) return 0; + + if (unlikely(dest_buf->p + len > dest_buf->end)) { + return 0; + } + + ok = pull_data_into_cursor(cursor, dest_buf, (unsigned char**)str, len); + if (!ok) return 0; + + ok = cursor_push_byte(dest_buf, 0); + + return 1; +} + +static inline int cursor_remaining_capacity(struct cursor *cursor) +{ + return (int)(cursor->end - cursor->p); +} + + +#define max(a,b) ((a) > (b) ? (a) : (b)) +static inline void cursor_print_around(struct cursor *cur, int range) +{ + unsigned char *c; + + printf("[%ld/%ld]\n", cur->p - cur->start, cur->end - cur->start); + + c = max(cur->p - range, cur->start); + for (; c < cur->end && c < (cur->p + range); c++) { + printf("%02x", *c); + } + printf("\n"); + + c = max(cur->p - range, cur->start); + for (; c < cur->end && c < (cur->p + range); c++) { + if (c == cur->p) { + printf("^"); + continue; + } + printf(" "); + } + printf("\n"); +} +#undef max + +static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) { + if (cur->p + count > cur->end) + return 0; + + *bytes = cur->p; + cur->p += count; + return 1; +} + +static inline int parse_str(struct cursor *cur, const char *str) { + int i; + char c, cs; + unsigned long len; + + len = strlen(str); + + if (cur->p + len >= cur->end) + return 0; + + for (i = 0; i < len; i++) { + c = tolower(cur->p[i]); + cs = tolower(str[i]); + + if (c != cs) + return 0; + } + + cur->p += len; + + return 1; +} + static inline int is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } @@ -35,13 +443,6 @@ static inline int is_alphanumeric(char c) { return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } -static inline void make_cursor(struct cursor *c, const u8 *content, size_t len) -{ - c->start = content; - c->end = content + len; - c->p = content; -} - static inline int consume_until_boundary(struct cursor *cur) { char c; @@ -110,47 +511,4 @@ static inline int peek_char(struct cursor *cur, int ind) { return *(cur->p + ind); } - -static inline int pull_byte(struct cursor *cur, u8 *byte) { - if (cur->p >= cur->end) - return 0; - - *byte = *cur->p; - cur->p++; - return 1; -} - -static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) { - if (cur->p + count > cur->end) - return 0; - - *bytes = cur->p; - cur->p += count; - return 1; -} - -static inline int parse_str(struct cursor *cur, const char *str) { - int i; - char c, cs; - unsigned long len; - - len = strlen(str); - - if (cur->p + len >= cur->end) - return 0; - - for (i = 0; i < len; i++) { - c = tolower(cur->p[i]); - cs = tolower(str[i]); - - if (c != cs) - return 0; - } - - cur->p += len; - - return 1; -} - - -#endif /* cursor_h */ +#endif diff --git a/damus-c/damus-Bridging-Header.h b/damus-c/damus-Bridging-Header.h index 9e47d3be..fcbe63a0 100644 --- a/damus-c/damus-Bridging-Header.h +++ b/damus-c/damus-Bridging-Header.h @@ -5,3 +5,7 @@ #include "damus.h" #include "bolt11.h" #include "amount.h" +#include "nostr_bech32.h" +#include "wasm.h" +#include "nostrscript.h" + diff --git a/damus-c/damus.c b/damus-c/damus.c index 8c6c60bb..8073e4ff 100644 --- a/damus-c/damus.c +++ b/damus-c/damus.c @@ -28,9 +28,9 @@ static int parse_digit(struct cursor *cur, int *digit) { } -static int parse_mention_index(struct cursor *cur, struct block *block) { +static int parse_mention_index(struct cursor *cur, struct note_block *block) { int d1, d2, d3, ind; - const u8 *start = cur->p; + u8 *start = cur->p; if (!parse_str(cur, "#[")) return 0; @@ -59,9 +59,9 @@ static int parse_mention_index(struct cursor *cur, struct block *block) { return 1; } -static int parse_hashtag(struct cursor *cur, struct block *block) { +static int parse_hashtag(struct cursor *cur, struct note_block *block) { int c; - const u8 *start = cur->p; + u8 *start = cur->p; if (!parse_char(cur, '#')) return 0; @@ -81,7 +81,7 @@ static int parse_hashtag(struct cursor *cur, struct block *block) { return 1; } -static int add_block(struct blocks *blocks, struct block block) +static int add_block(struct note_blocks *blocks, struct note_block block) { if (blocks->num_blocks + 1 >= MAX_BLOCKS) return 0; @@ -90,9 +90,9 @@ static int add_block(struct blocks *blocks, struct block block) return 1; } -static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end) +static int add_text_block(struct note_blocks *blocks, const u8 *start, const u8 *end) { - struct block b; + struct note_block b; if (start == end) return 1; @@ -104,8 +104,8 @@ static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end) return add_block(blocks, b); } -static int parse_url(struct cursor *cur, struct block *block) { - const u8 *start = cur->p; +static int parse_url(struct cursor *cur, struct note_block *block) { + u8 *start = cur->p; if (!parse_str(cur, "http")) return 0; @@ -137,8 +137,8 @@ static int parse_url(struct cursor *cur, struct block *block) { return 1; } -static int parse_invoice(struct cursor *cur, struct block *block) { - const u8 *start, *end; +static int parse_invoice(struct cursor *cur, struct note_block *block) { + u8 *start, *end; char *fail; struct bolt11 *bolt11; // optional @@ -177,8 +177,8 @@ static int parse_invoice(struct cursor *cur, struct block *block) { } -static int parse_mention_bech32(struct cursor *cur, struct block *block) { - const u8 *start = cur->p; +static int parse_mention_bech32(struct cursor *cur, struct note_block *block) { + u8 *start = cur->p; if (!parse_str(cur, "nostr:")) return 0; @@ -197,7 +197,7 @@ static int parse_mention_bech32(struct cursor *cur, struct block *block) { return 1; } -static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct block block, const u8 **start, const u8 *pre_mention) +static int add_text_then_block(struct cursor *cur, struct note_blocks *blocks, struct note_block block, u8 **start, const u8 *pre_mention) { if (!add_text_block(blocks, *start, pre_mention)) return 0; @@ -210,14 +210,14 @@ static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct return 1; } -int damus_parse_content(struct blocks *blocks, const char *content) { +int damus_parse_content(struct note_blocks *blocks, const char *content) { int cp, c; struct cursor cur; - struct block block; - const u8 *start, *pre_mention; + struct note_block block; + u8 *start, *pre_mention; blocks->num_blocks = 0; - make_cursor(&cur, (const u8*)content, strlen(content)); + make_cursor((u8*)content, (u8*)content + strlen(content), &cur); start = cur.p; while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) { @@ -256,12 +256,12 @@ int damus_parse_content(struct blocks *blocks, const char *content) { return 1; } -void blocks_init(struct blocks *blocks) { - blocks->blocks = malloc(sizeof(struct block) * MAX_BLOCKS); +void blocks_init(struct note_blocks *blocks) { + blocks->blocks = malloc(sizeof(struct note_block) * MAX_BLOCKS); blocks->num_blocks = 0; } -void blocks_free(struct blocks *blocks) { +void blocks_free(struct note_blocks *blocks) { if (!blocks->blocks) { return; } diff --git a/damus-c/damus.h b/damus-c/damus.h index 2c69506c..4453fa54 100644 --- a/damus-c/damus.h +++ b/damus-c/damus.h @@ -9,10 +9,10 @@ #define damus_h #include -#include "nostr_bech32.h" #include "block.h" + typedef unsigned char u8; -int damus_parse_content(struct blocks *blocks, const char *content); +int damus_parse_content(struct note_blocks *blocks, const char *content); #endif /* damus_h */ diff --git a/damus-c/debug.h b/damus-c/debug.h new file mode 100644 index 00000000..11a6344e --- /dev/null +++ b/damus-c/debug.h @@ -0,0 +1,15 @@ + +#ifndef PROTOVERSE_DEBUG_H +#define PROTOVERSE_DEBUG_H + +#include + +#define unusual(...) fprintf(stderr, "UNUSUAL: " __VA_ARGS__) + +#ifdef DEBUG +#define debug(...) printf(__VA_ARGS__) +#else +#define debug(...) +#endif + +#endif /* PROTOVERSE_DEBUG_H */ diff --git a/damus-c/error.c b/damus-c/error.c new file mode 100644 index 00000000..5f26a2b8 --- /dev/null +++ b/damus-c/error.c @@ -0,0 +1,34 @@ + +#include "error.h" + +#include +#include + +int note_error_(struct errors *errs_, struct cursor *p, const char *fmt, ...) +{ + static char buf[512]; + struct error err; + struct cursor *errs; + va_list ap; + + errs = &errs_->cur; + + if (errs_->enabled == 0) + return 0; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + err.msg = buf; + err.pos = p ? (int)(p->p - p->start) : 0; + + if (!cursor_push_error(errs, &err)) { + fprintf(stderr, "arena OOM when recording error, "); + fprintf(stderr, "errs->p at %ld, remaining %ld, strlen %ld\n", + errs->p - errs->start, errs->end - errs->p, strlen(buf)); + } + + return 0; +} + diff --git a/damus-c/error.h b/damus-c/error.h new file mode 100644 index 00000000..48a62996 --- /dev/null +++ b/damus-c/error.h @@ -0,0 +1,33 @@ + +#ifndef PROTOVERSE_ERROR_H +#define PROTOVERSE_ERROR_H + +#include "cursor.h" + +struct error { + int pos; + const char *msg; +}; + +struct errors { + struct cursor cur; + int enabled; +}; + +#define note_error(errs, p, fmt, ...) note_error_(errs, p, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__) + +static inline int cursor_push_error(struct cursor *cur, struct error *err) +{ + return cursor_push_int(cur, err->pos) && + cursor_push_c_str(cur, err->msg); +} + +static inline int cursor_pull_error(struct cursor *cur, struct error *err) +{ + return cursor_pull_int(cur, &err->pos) && + cursor_pull_c_str(cur, &err->msg); +} + +int note_error_(struct errors *errs, struct cursor *p, const char *fmt, ...); + +#endif /* PROTOVERSE_ERROR_H */ diff --git a/damus-c/likely.h b/damus-c/likely.h index f54bd86d..598cfe40 100644 --- a/damus-c/likely.h +++ b/damus-c/likely.h @@ -52,9 +52,13 @@ */ #define unlikely(cond) __builtin_expect(!!(cond), 0) #else +#ifndef likely #define likely(cond) (!!(cond)) +#endif +#ifndef unlikely #define unlikely(cond) (!!(cond)) #endif +#endif #else /* CCAN_LIKELY_DEBUG versions */ #include diff --git a/damus-c/nostr_bech32.c b/damus-c/nostr_bech32.c index 52cd72cb..27874315 100644 --- a/damus-c/nostr_bech32.c +++ b/damus-c/nostr_bech32.c @@ -218,7 +218,7 @@ static int parse_nostr_bech32_nrelay(struct cursor *cur, struct bech32_nrelay *n } int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) { - const u8 *start, *end; + u8 *start, *end; start = cur->p; @@ -257,7 +257,7 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) { } struct cursor bcur; - make_cursor(&bcur, obj->buffer, obj->buflen); + make_cursor(obj->buffer, obj->buffer + obj->buflen, &bcur); switch (obj->type) { case NOSTR_BECH32_NOTE: diff --git a/damus-c/nostrscript.c b/damus-c/nostrscript.c new file mode 100644 index 00000000..18eabf09 --- /dev/null +++ b/damus-c/nostrscript.c @@ -0,0 +1,173 @@ +// +// nostrscript.c +// damus +// +// Created by William Casarin on 2023-06-02. +// + +#include "nostrscript.h" +#include "wasm.h" +#include "array_size.h" + +// function to check if the character is in surrogate pair range +static INLINE int is_surrogate(uint16_t uc) { + return (uc - 0xd800u) < 2048u; +} + +// function to convert utf16 to utf8 +static int utf16_to_utf8(u16 utf16, u8 *utf8) { + if (utf16 < 0x80) { // 1-byte sequence + utf8[0] = (uint8_t) utf16; + return 1; + } + else if (utf16 < 0x800) { // 2-byte sequence + utf8[0] = (uint8_t) (0xc0 | (utf16 >> 6)); + utf8[1] = (uint8_t) (0x80 | (utf16 & 0x3f)); + return 2; + } + else if (!is_surrogate(utf16)) { // 3-byte sequence + utf8[0] = (uint8_t) (0xe0 | (utf16 >> 12)); + utf8[1] = (uint8_t) (0x80 | ((utf16 >> 6) & 0x3f)); + utf8[2] = (uint8_t) (0x80 | (utf16 & 0x3f)); + return 3; + } + else { // surrogate pair, return error + return -1; + } +} + +static int nostr_cmd(struct wasm_interp *interp) { + struct val *params = NULL; + const char *val = NULL; + int len, cmd, ival; + + if (!get_params(interp, ¶ms, 3) || params == NULL) + return interp_error(interp, "get params"); + + // command + cmd = params[0].num.i32; + + // value + + ival = params[1].num.i32; + if (!mem_ptr_str(interp, ival, &val)) + val = 0; + + // length + len = params[2].num.i32; + + return nscript_nostr_cmd(interp, cmd, val ? (void*)val : (void*)ival, len); +} + +static int print_utf16_str(u16 *chars) { + u16 *p = chars; + int c; + + while (*p) { + if (utf16_to_utf8(*p, (u8*)&c) == -1) + return 0; + + printf("%c", c); + + p++; + } + + return 1; +} + +static int nostr_log(struct wasm_interp *interp) { + struct val *vals; + const char *str; + struct callframe *callframe; + + if (!get_params(interp, &vals, 1)) + return interp_error(interp, "nostr_log get params"); + + if (!mem_ptr_str(interp, vals[0].num.i32, &str)) + return interp_error(interp, "nostr_log log param"); + + if (!(callframe = top_callframes(&interp->callframes, 2))) + return interp_error(interp, "nostr_log callframe"); + + printf("nostr_log:%s: ", callframe->func->name); + + print_utf16_str((u16*)str); + printf("\n"); + + return 1; +} + +static int nostr_pool_send_to(struct wasm_interp *interp) { + struct val *params = NULL; + const u16 *req, *to; + int req_len, to_len; + + if (!get_params(interp, ¶ms, 4) || params == NULL) + return 0; + + if (!mem_ptr_str(interp, params[0].num.i32, (const char**)&req)) + return 0; + + req_len = params[1].num.i32; + + if (!mem_ptr_str(interp, params[2].num.i32, (const char**)&to)) + return 0; + + to_len = params[3].num.i32; + + return nscript_pool_send_to(interp, req, req_len, to, to_len); +} + +static int nscript_abort(struct wasm_interp *interp) { + struct val *params = NULL; + const char *msg = "", *filename; + int line, col; + + if (!get_params(interp, ¶ms, 4) || params == NULL) + return interp_error(interp, "get params"); + + if (params[0].ref.addr != 0 && !mem_ptr_str(interp, params[0].ref.addr, &msg)) + return interp_error(interp, "abort msg"); + + if (!mem_ptr_str(interp, params[1].ref.addr, &filename)) + return interp_error(interp, "abort filename"); + + line = params[2].num.i32; + col = params[3].num.i32; + + printf("nscript_abort:"); + print_utf16_str((u16*)filename); + printf(":%d:%d: ", line, col); + print_utf16_str((u16*)msg); + printf("\n"); + + return 0; +} + +static struct builtin nscript_builtins[] = { + { .name = "null", .fn = 0 }, + { .name = "nostr_log", .fn = nostr_log }, + { .name = "nostr_cmd", .fn = nostr_cmd }, + { .name = "nostr_pool_send_to", .fn = nostr_pool_send_to }, + { .name = "abort", .fn = nscript_abort }, +}; + +int nscript_load(struct wasm_parser *p, struct wasm_interp *interp, unsigned char *wasm, unsigned long len) { + wasm_parser_init(p, wasm, len, len * 16, nscript_builtins, ARRAY_SIZE(nscript_builtins)); + + if (!parse_wasm(p)) { + wasm_parser_free(p); + return NSCRIPT_PARSE_ERR; + } + + if (!wasm_interp_init(interp, &p->module)) { + print_error_backtrace(&interp->errors); + wasm_parser_free(p); + return NSCRIPT_INIT_ERR; + } + + //setup_wasi(&interp, argc, argv, env); + //wasm_parser_free(&p); + + return NSCRIPT_LOADED; +} diff --git a/damus-c/nostrscript.h b/damus-c/nostrscript.h new file mode 100644 index 00000000..1f8f797b --- /dev/null +++ b/damus-c/nostrscript.h @@ -0,0 +1,23 @@ +// +// nostrscript.h +// damus +// +// Created by William Casarin on 2023-06-02. +// + +#ifndef nostrscript_h +#define nostrscript_h + +#define NSCRIPT_LOADED 1 +#define NSCRIPT_PARSE_ERR 2 +#define NSCRIPT_INIT_ERR 3 + +#include +#include "wasm.h" + +int nscript_load(struct wasm_parser *p, struct wasm_interp *interp, unsigned char *wasm, unsigned long len); +int nscript_nostr_cmd(struct wasm_interp *interp, int, void*, int); +int nscript_pool_send_to(struct wasm_interp *interp, const u16*, int, const u16 *, int); + + +#endif /* nostrscript_h */ diff --git a/damus-c/parser.h b/damus-c/parser.h new file mode 100644 index 00000000..cb42402c --- /dev/null +++ b/damus-c/parser.h @@ -0,0 +1,42 @@ + +#ifndef CURSOR_PARSER +#define CURSOR_PARSER + +#include "cursor.h" + +static int consume_bytes(struct cursor *cursor, const unsigned char *match, int len) +{ + int i; + + if (cursor->p + len > cursor->end) { + fprintf(stderr, "consume_bytes overflow\n"); + return 0; + } + + for (i = 0; i < len; i++) { + if (cursor->p[i] != match[i]) + return 0; + } + + cursor->p += len; + + return 1; +} + +static inline int consume_byte(struct cursor *cursor, unsigned char match) +{ + if (unlikely(cursor->p >= cursor->end)) + return 0; + if (*cursor->p != match) + return 0; + cursor->p++; + return 1; +} + +static inline int consume_u32(struct cursor *cursor, unsigned int match) +{ + return consume_bytes(cursor, (unsigned char*)&match, sizeof(match)); +} + +#endif /* CURSOR_PARSER */ + diff --git a/damus-c/typedefs.h b/damus-c/typedefs.h new file mode 100644 index 00000000..d68388dd --- /dev/null +++ b/damus-c/typedefs.h @@ -0,0 +1,14 @@ + +#ifndef PROTOVERSE_TYPEDEFS_H +#define PROTOVERSE_TYPEDEFS_H + +#include + +typedef unsigned char u8; +typedef unsigned int u32; +typedef unsigned short u16; +typedef uint64_t u64; +typedef int64_t s64; + + +#endif /* PROTOVERSE_TYPEDEFS_H */ diff --git a/damus-c/varint.h b/damus-c/varint.h new file mode 100644 index 00000000..afb338cd --- /dev/null +++ b/damus-c/varint.h @@ -0,0 +1,14 @@ + +#ifndef PROTOVERSE_VARINT_H +#define PROTOVERSE_VARINT_H + +#define VARINT_MAX_LEN 9 + +#include +#include + +size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v); +size_t varint_size(uint64_t v); +size_t varint_get(const unsigned char *p, size_t max, int64_t *val); + +#endif /* PROTOVERSE_VARINT_H */ diff --git a/damus-c/wasm.c b/damus-c/wasm.c new file mode 100644 index 00000000..14da547d --- /dev/null +++ b/damus-c/wasm.c @@ -0,0 +1,7293 @@ + +#include "wasm.h" +#include "parser.h" +#include "debug.h" +#include "error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define ERR_STACK_SIZE 16 +#define NUM_LOCALS 0xFFFF +#define WASM_PAGE_SIZE 65536 +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +static const int MAX_LABELS = 1024; + +static int interp_code(struct wasm_interp *interp); +static INLINE int pop_label_checkpoint(struct wasm_interp *interp); + +struct expr_parser { + struct wasm_interp *interp; // optional... + struct cursor *code; + struct errors *errs; + struct cursor *stack; // optional +}; + +static INLINE int cursor_popval(struct cursor *cur, struct val *val) +{ + return cursor_pop(cur, (unsigned char*)val, sizeof(*val)); +} + +static const char *valtype_name(enum valtype valtype) +{ + switch (valtype) { + case val_i32: return "i32"; + case val_i64: return "i64"; + case val_f32: return "f32"; + case val_f64: return "f64"; + case val_ref_null: return "null"; + case val_ref_func: return "func"; + case val_ref_extern: return "extern"; + } + + return "?"; +} + +static const char *reftype_name(enum reftype reftype) { + return valtype_name((enum valtype)reftype); +} + +static const char *valtype_literal(enum valtype valtype) +{ + switch (valtype) { + case val_i32: return ""; + case val_i64: return "L"; + case val_f32: return ""; + case val_f64: return "f"; + case val_ref_null: return "null"; + case val_ref_func: return "func"; + case val_ref_extern: return "extern"; + } + + return "?"; +} + +static INLINE int is_valid_fn_index(struct module *module, u32 ind) +{ + return ind < module->num_funcs; +} + +static INLINE struct func *get_fn(struct module *module, u32 ind) +{ + if (unlikely(!is_valid_fn_index(module, ind))) + return NULL; + return &module->funcs[ind]; +} + +u8 *interp_mem_ptr(struct wasm_interp *interp, u32 ptr, int size) +{ + u8 *pos = interp->memory.start + ptr; + + if (ptr == 0) { + interp_error(interp, "null mem_ptr"); + return NULL; + } + + if (pos + size >= interp->memory.p) { + interp_error(interp, "guest invalid mem read: %d > %d", + pos, interp->memory.p - interp->memory.start); + return NULL; + } + + return pos; +} + +static INLINE int read_mem(struct wasm_interp *interp, u32 ptr, int size, + void *dest) +{ + u8 *mem; + if (!(mem = interp_mem_ptr(interp, ptr, size))) + return interp_error(interp, "invalid mem pointer"); + memcpy(dest, mem, size); + return 1; +} + +static INLINE int read_mem_u32(struct wasm_interp *interp, u32 ptr, u32 *i) +{ + return read_mem(interp, ptr, sizeof(*i), i); +} + +static INLINE int mem_ptr_f32(struct wasm_interp *interp, u32 ptr, float **i) +{ + if (!(*i = (float*)interp_mem_ptr(interp, ptr, sizeof(float)))) + return interp_error(interp, "int memptr"); + return 1; +} + +static INLINE int mem_ptr_u32(struct wasm_interp *interp, u32 ptr, u32 **i) +{ + if (!(*i = (u32*)interp_mem_ptr(interp, ptr, sizeof(int)))) + return interp_error(interp, "uint memptr"); + return 1; +} + +static INLINE int mem_ptr_u32_arr(struct wasm_interp *interp, u32 ptr, int n, u32 **i) +{ + if (!(*i = (u32*)interp_mem_ptr(interp, ptr, n * sizeof(int)))) + return interp_error(interp, "uint memptr"); + return 1; +} + + +static INLINE int read_mem_i32(struct wasm_interp *interp, u32 ptr, int *i) +{ + return read_mem(interp, ptr, sizeof(*i), i); +} + +static INLINE struct val *get_local(struct wasm_interp *interp, u32 ind) +{ + struct callframe *frame; + + if (unlikely(!(frame = top_callframe(&interp->callframes)))) { + interp_error(interp, "no callframe?"); + return NULL; + } + + if (unlikely(ind >= frame->func->num_locals)) { + interp_error(interp, "local index %d too high for %s:%d (max %d)", + ind, frame->func->name, frame->func->idx, + frame->func->num_locals-1); + return NULL; + } + + return &frame->locals[ind]; +} + +int get_var_params(struct wasm_interp *interp, struct val **vals, u32 *num_vals) +{ + struct callframe *frame; + + if (unlikely(!(frame = top_callframe(&interp->callframes)))) + return interp_error(interp, "no callframe?"); + + *num_vals = frame->func->functype->params.num_valtypes; + *vals = frame->locals; + + return 1; +} + +int get_params(struct wasm_interp *interp, struct val** vals, u32 num_vals) +{ + u32 nvals; + if (!get_var_params(interp, vals, &nvals)) + return 0; + + if (nvals != num_vals) + return interp_error(interp, "requested %d params, but there are %d", num_vals, nvals); + + return 1; +} + +static INLINE int stack_popval(struct wasm_interp *interp, struct val *val) +{ + return cursor_popval(&interp->stack, val); +} + +static INLINE struct val *cursor_topval(struct cursor *stack) +{ + return (struct val *)cursor_top(stack, sizeof(struct val)); +} + +static INLINE struct val *stack_topval(struct wasm_interp *interp) +{ + return cursor_topval(&interp->stack); +} + +static INLINE struct val *stack_top_type(struct wasm_interp *interp, + enum valtype type) +{ + struct val *val; + if (unlikely(!(val = stack_topval(interp)))) { + interp_error(interp, "pop"); + return NULL; + } + if (val->type != type) { + interp_error(interp, + "type mismatch: got %s, expected %s", + valtype_name(val->type), + valtype_name(type) + ); + return NULL; + } + return val; +} + +static INLINE struct val *stack_top_i32(struct wasm_interp *interp) +{ + return stack_top_type(interp, val_i32); +} + +static INLINE struct val *stack_top_f32(struct wasm_interp *interp) +{ + return stack_top_type(interp, val_f32); +} + +static INLINE struct val *stack_top_f64(struct wasm_interp *interp) +{ + return stack_top_type(interp, val_f64); +} + +static INLINE struct val *stack_top_i64(struct wasm_interp *interp) +{ + return stack_top_type(interp, val_i64); +} + +static INLINE int cursor_pop_i32(struct cursor *stack, int *i) +{ + struct val val; + if (unlikely(!cursor_popval(stack, &val))) + return 0; + if (unlikely(val.type != val_i32)) + return 0; + *i = val.num.i32; + return 1; +} + +static INLINE int cursor_pop_i64(struct cursor *stack, int64_t *i) +{ + struct val val; + if (unlikely(!cursor_popval(stack, &val))) + return 0; + if (unlikely(val.type != val_i64)) + return 0; + *i = val.num.i64; + return 1; +} + + +static int is_reftype(enum valtype type) +{ + switch (type) { + case val_i32: + case val_i64: + case val_f32: + case val_f64: + return 0; + case val_ref_null: + case val_ref_func: + case val_ref_extern: + return 1; + } + return 0; +} + +/* +static INLINE int cursor_pop_ref(struct cursor *stack, struct val *val) +{ + if (!cursor_popval(stack, val)) { + return 0; + } + if (!is_reftype(val->type)) { + return 0; + } + return 1; +} +*/ + +static INLINE int stack_pop_ref(struct wasm_interp *interp, struct val *val) +{ + if (!cursor_popval(&interp->stack, val)) { + return interp_error(interp, "no value on stack"); + } + if (!is_reftype(val->type)) { + return interp_error(interp, "not a reftype, got %s", + valtype_name(val->type)); + } + return 1; +} + +static INLINE int stack_pop_i32(struct wasm_interp *interp, int *i) +{ + return cursor_pop_i32(&interp->stack, i); +} + +static INLINE int stack_pop_i64(struct wasm_interp *interp, int64_t *i) +{ + return cursor_pop_i64(&interp->stack, i); +} + +static INLINE int cursor_pop_valtype(struct cursor *stack, enum valtype type, + struct val *val) +{ + if (unlikely(!cursor_popval(stack, val))) { + return 0; + } + + if (unlikely(val->type != type)) { + return 0; + } + + return 1; +} + +static INLINE int stack_pop_valtype(struct wasm_interp *interp, + enum valtype type, struct val *val) +{ + return cursor_pop_valtype(&interp->stack, type, val); +} + +static void print_val(struct val *val) +{ + switch (val->type) { + case val_i32: printf("%d", val->num.i32); break; + case val_i64: printf("%" PRId64, val->num.i64); break; + case val_f32: printf("%f", val->num.f32); break; + case val_f64: printf("%f", val->num.f64); break; + + case val_ref_null: + break; + case val_ref_func: + case val_ref_extern: + printf("%d", val->ref.addr); + break; + } + printf("%s", valtype_literal(val->type)); +} + +#ifdef DEBUG +static void print_refval(struct refval *ref, enum reftype reftype) +{ + struct val val; + val.type = (enum valtype)reftype; + val.ref = *ref; + print_val(&val); +} +#endif + +static void print_stack(struct cursor *stack) +{ + struct val val; + int i; + u8 *p = stack->p; + + if (stack->p == stack->start) { + return; + } + + for (i = 0; stack->p > stack->start; i++) { + cursor_popval(stack, &val); + printf("[%d] ", i); + print_val(&val); + printf("\n"); + } + + stack->p = p; +} + +void print_callstack(struct wasm_interp *interp) +{ + int i = 0; + struct callframe *frame; + + printf("callstack:\n"); + while ((frame = top_callframes(&interp->callframes, i++))) { + if (!frame->func) { + printf("??\n"); + continue; + } + else { + printf("%d %s:%d\n", i, frame->func->name, frame->func->idx); + } + } +} + +static INLINE int cursor_push_i64(struct cursor *stack, s64 i) +{ + struct val val; + val.type = val_i64; + val.num.i64 = i; + + return cursor_pushval(stack, &val); +} + +static INLINE int cursor_push_f32(struct cursor *stack, float f) +{ + struct val val; + val.type = val_f32; + val.num.f32 = f; + + return cursor_pushval(stack, &val); +} + +static INLINE int cursor_push_f64(struct cursor *stack, double f) +{ + struct val val; + val.type = val_f64; + val.num.f64 = f; + + return cursor_pushval(stack, &val); +} + +static INLINE int cursor_push_u64(struct cursor *stack, u64 i) +{ + struct val val; + val.type = val_i64; + val.num.u64 = i; + + return cursor_pushval(stack, &val); +} + +static INLINE int cursor_push_funcref(struct cursor *stack, int addr) +{ + struct val val; + val.type = val_ref_func; + val.ref.addr = addr; + return cursor_pushval(stack, &val); +} + +static INLINE int stack_push_i64(struct wasm_interp *interp, s64 i) +{ + return cursor_push_u64(&interp->stack, (u64)i); +} + +static INLINE int stack_push_u64(struct wasm_interp *interp, u64 i) +{ + return cursor_push_u64(&interp->stack, i); +} + + +static INLINE void make_i32_val(struct val *val, int v) +{ + val->type = val_i32; + val->num.i32 = v; +} + +static INLINE void make_f64_val(struct val *val, double v) +{ + val->type = val_f64; + val->num.f64 = v; +} + +static INLINE void make_f32_val(struct val *val, float v) +{ + val->type = val_f32; + val->num.f32 = v; +} + +static INLINE int stack_pushval(struct wasm_interp *interp, struct val *val) +{ + return cursor_pushval(&interp->stack, val); +} + +static int interp_exit(struct wasm_interp *interp) +{ + struct val *vals = NULL; + if (!get_params(interp, &vals, 1)) + return interp_error(interp, "exit param missing?"); + interp->quitting = 1; + stack_push_i32(interp, vals[0].num.i32); + return 0; +} + +static int wasi_proc_exit(struct wasm_interp *interp) +{ + return interp_exit(interp); +} + +static int wasi_abort(struct wasm_interp *interp) +{ + struct val *params = NULL; + + if (!get_params(interp, ¶ms, 4)) + return interp_error(interp, "exit param missing?"); + + printf("abort\n"); + + interp->quitting = 1; + stack_push_i32(interp, 88); + + return 0; +} + +static INLINE const char *get_function_name(struct module *module, int fn) +{ + struct func *func = NULL; + if (unlikely(!(func = get_fn(module, fn)))) { + return "unknown"; + } + return func->name; +} + +static int wasi_args_sizes_get(struct wasm_interp *interp); +static int wasi_args_get(struct wasm_interp *interp); +static int wasi_fd_write(struct wasm_interp *interp); +static int wasi_fd_close(struct wasm_interp *interp); +static int wasi_environ_sizes_get(struct wasm_interp *interp); +static int wasi_environ_get(struct wasm_interp *interp); + +static int parse_instr(struct expr_parser *parser, u8 tag, struct instr *op); + +static INLINE int is_valtype(unsigned char byte) +{ + switch ((enum valtype)byte) { + case val_i32: // i32 + case val_i64: // i64 + case val_f32: // f32 + case val_f64: // f64 + case val_ref_func: // funcref + case val_ref_null: // null + case val_ref_extern: // externref + return 1; + } + + return 0; +} + + +/* +static int sizeof_valtype(enum valtype valtype) +{ + switch (valtype) { + case i32: return 4; + case f32: return 4; + case i64: return 8; + case f64: return 8; + } + + return 0; +} +*/ + +static char *instr_name(enum instr_tag tag) +{ + static char unk[6] = {0}; + + switch (tag) { + case i_unreachable: return "unreachable"; + case i_nop: return "nop"; + case i_block: return "block"; + case i_loop: return "loop"; + case i_if: return "if"; + case i_else: return "else"; + case i_end: return "end"; + case i_br: return "br"; + case i_br_if: return "br_if"; + case i_br_table: return "br_table"; + case i_return: return "return"; + case i_call: return "call"; + case i_call_indirect: return "call_indirect"; + case i_drop: return "drop"; + case i_select: return "select"; + case i_local_get: return "local_get"; + case i_local_set: return "local_set"; + case i_local_tee: return "local_tee"; + case i_global_get: return "global_get"; + case i_global_set: return "global_set"; + case i_i32_load: return "i32_load"; + case i_i64_load: return "i64_load"; + case i_f32_load: return "f32_load"; + case i_f64_load: return "f64_load"; + case i_i32_load8_s: return "i32_load8_s"; + case i_i32_load8_u: return "i32_load8_u"; + case i_i32_load16_s: return "i32_load16_s"; + case i_i32_load16_u: return "i32_load16_u"; + case i_i64_load8_s: return "i64_load8_s"; + case i_i64_load8_u: return "i64_load8_u"; + case i_i64_load16_s: return "i64_load16_s"; + case i_i64_load16_u: return "i64_load16_u"; + case i_i64_load32_s: return "i64_load32_s"; + case i_i64_load32_u: return "i64_load32_u"; + case i_i32_store: return "i32_store"; + case i_i64_store: return "i64_store"; + case i_f32_store: return "f32_store"; + case i_f64_store: return "f64_store"; + case i_i32_store8: return "i32_store8"; + case i_i32_store16: return "i32_store16"; + case i_i64_store8: return "i64_store8"; + case i_i64_store16: return "i64_store16"; + case i_i64_store32: return "i64_store32"; + case i_memory_size: return "memory_size"; + case i_memory_grow: return "memory_grow"; + case i_i32_const: return "i32_const"; + case i_i64_const: return "i64_const"; + case i_f32_const: return "f32_const"; + case i_f64_const: return "f64_const"; + case i_i32_eqz: return "i32_eqz"; + case i_i32_eq: return "i32_eq"; + case i_i32_ne: return "i32_ne"; + case i_i32_lt_s: return "i32_lt_s"; + case i_i32_lt_u: return "i32_lt_u"; + case i_i32_gt_s: return "i32_gt_s"; + case i_i32_gt_u: return "i32_gt_u"; + case i_i32_le_s: return "i32_le_s"; + case i_i32_le_u: return "i32_le_u"; + case i_i32_ge_s: return "i32_ge_s"; + case i_i32_ge_u: return "i32_ge_u"; + case i_i64_eqz: return "i64_eqz"; + case i_i64_eq: return "i64_eq"; + case i_i64_ne: return "i64_ne"; + case i_i64_lt_s: return "i64_lt_s"; + case i_i64_lt_u: return "i64_lt_u"; + case i_i64_gt_s: return "i64_gt_s"; + case i_i64_gt_u: return "i64_gt_u"; + case i_i64_le_s: return "i64_le_s"; + case i_i64_le_u: return "i64_le_u"; + case i_i64_ge_s: return "i64_ge_s"; + case i_i64_ge_u: return "i64_ge_u"; + case i_f32_eq: return "f32_eq"; + case i_f32_ne: return "f32_ne"; + case i_f32_lt: return "f32_lt"; + case i_f32_gt: return "f32_gt"; + case i_f32_le: return "f32_le"; + case i_f32_ge: return "f32_ge"; + case i_f64_eq: return "f64_eq"; + case i_f64_ne: return "f64_ne"; + case i_f64_lt: return "f64_lt"; + case i_f64_gt: return "f64_gt"; + case i_f64_le: return "f64_le"; + case i_f64_ge: return "f64_ge"; + case i_i32_clz: return "i32_clz"; + case i_i32_ctz: return "i32_ctz"; + case i_i32_popcnt: return "i32_popcnt"; + case i_i32_add: return "i32_add"; + case i_i32_sub: return "i32_sub"; + case i_i32_mul: return "i32_mul"; + case i_i32_div_s: return "i32_div_s"; + case i_i32_div_u: return "i32_div_u"; + case i_i32_rem_s: return "i32_rem_s"; + case i_i32_rem_u: return "i32_rem_u"; + case i_i32_and: return "i32_and"; + case i_i32_or: return "i32_or"; + case i_i32_xor: return "i32_xor"; + case i_i32_shl: return "i32_shl"; + case i_i32_shr_s: return "i32_shr_s"; + case i_i32_shr_u: return "i32_shr_u"; + case i_i32_rotl: return "i32_rotl"; + case i_i32_rotr: return "i32_rotr"; + case i_i64_clz: return "i64_clz"; + case i_i64_ctz: return "i64_ctz"; + case i_i64_popcnt: return "i64_popcnt"; + case i_i64_add: return "i64_add"; + case i_i64_sub: return "i64_sub"; + case i_i64_mul: return "i64_mul"; + case i_i64_div_s: return "i64_div_s"; + case i_i64_div_u: return "i64_div_u"; + case i_i64_rem_s: return "i64_rem_s"; + case i_i64_rem_u: return "i64_rem_u"; + case i_i64_and: return "i64_and"; + case i_i64_or: return "i64_or"; + case i_i64_xor: return "i64_xor"; + case i_i64_shl: return "i64_shl"; + case i_i64_shr_s: return "i64_shr_s"; + case i_i64_shr_u: return "i64_shr_u"; + case i_i64_rotl: return "i64_rotl"; + case i_i64_rotr: return "i64_rotr"; + case i_f32_abs: return "f32_abs"; + case i_f32_neg: return "f32_neg"; + case i_f32_ceil: return "f32_ceil"; + case i_f32_floor: return "f32_floor"; + case i_f32_trunc: return "f32_trunc"; + case i_f32_nearest: return "f32_nearest"; + case i_f32_sqrt: return "f32_sqrt"; + case i_f32_add: return "f32_add"; + case i_f32_sub: return "f32_sub"; + case i_f32_mul: return "f32_mul"; + case i_f32_div: return "f32_div"; + case i_f32_min: return "f32_min"; + case i_f32_max: return "f32_max"; + case i_f32_copysign: return "f32_copysign"; + case i_f64_abs: return "f64_abs"; + case i_f64_neg: return "f64_neg"; + case i_f64_ceil: return "f64_ceil"; + case i_f64_floor: return "f64_floor"; + case i_f64_trunc: return "f64_trunc"; + case i_f64_nearest: return "f64_nearest"; + case i_f64_sqrt: return "f64_sqrt"; + case i_f64_add: return "f64_add"; + case i_f64_sub: return "f64_sub"; + case i_f64_mul: return "f64_mul"; + case i_f64_div: return "f64_div"; + case i_f64_min: return "f64_min"; + case i_f64_max: return "f64_max"; + case i_f64_copysign: return "f64_copysign"; + case i_i32_wrap_i64: return "i32_wrap_i64"; + case i_i32_trunc_f32_s: return "i32_trunc_f32_s"; + case i_i32_trunc_f32_u: return "i32_trunc_f32_u"; + case i_i32_trunc_f64_s: return "i32_trunc_f64_s"; + case i_i32_trunc_f64_u: return "i32_trunc_f64_u"; + case i_i64_extend_i32_s: return "i64_extend_i32_s"; + case i_i64_extend_i32_u: return "i64_extend_i32_u"; + case i_i64_trunc_f32_s: return "i64_trunc_f32_s"; + case i_i64_trunc_f32_u: return "i64_trunc_f32_u"; + case i_i64_trunc_f64_s: return "i64_trunc_f64_s"; + case i_i64_trunc_f64_u: return "i64_trunc_f64_u"; + case i_f32_convert_i32_s: return "f32_convert_i32_s"; + case i_f32_convert_i32_u: return "f32_convert_i32_u"; + case i_f32_convert_i64_s: return "f32_convert_i64_s"; + case i_f32_convert_i64_u: return "f32_convert_i64_u"; + case i_f32_demote_f64: return "f32_demote_f64"; + case i_f64_convert_i32_s: return "f64_convert_i32_s"; + case i_f64_convert_i32_u: return "f64_convert_i32_u"; + case i_f64_convert_i64_s: return "f64_convert_i64_s"; + case i_f64_convert_i64_u: return "f64_convert_i64_u"; + case i_f64_promote_f32: return "f64_promote_f32"; + case i_i32_reinterpret_f32: return "i32_reinterpret_f32"; + case i_i64_reinterpret_f64: return "i64_reinterpret_f64"; + case i_f32_reinterpret_i32: return "f32_reinterpret_i32"; + case i_f64_reinterpret_i64: return "f64_reinterpret_i64"; + case i_i32_extend8_s: return "i32_extend8_s"; + case i_i32_extend16_s: return "i32_extend16_s"; + case i_i64_extend8_s: return "i64_extend8_s"; + case i_i64_extend16_s: return "i64_extend16_s"; + case i_i64_extend32_s: return "i64_extend32_s"; + case i_ref_null: return "ref_null"; + case i_ref_func: return "ref_func"; + case i_ref_is_null: return "ref_is_null"; + case i_bulk_op: return "bulk_op"; + case i_table_get: return "table_get"; + case i_table_set: return "table_set"; + case i_selects: return "selects"; + } + + snprintf(unk, sizeof(unk), "0x%02x", tag); + return unk; +} + +static INLINE int was_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; +} diff --git a/damus-c/wasm.h b/damus-c/wasm.h new file mode 100644 index 00000000..e0f71e17 --- /dev/null +++ b/damus-c/wasm.h @@ -0,0 +1,840 @@ + +#ifndef PROTOVERSE_WASM_H +#define PROTOVERSE_WASM_H + +static const unsigned char WASM_MAGIC[] = {0,'a','s','m'}; + +#define WASM_VERSION 0x01 +#define MAX_U32_LEB128_BYTES 5 +#define MAX_U64_LEB128_BYTES 10 +#define MAX_CUSTOM_SECTIONS 32 +#define MAX_BUILTINS 64 +#define BUILTIN_SUSPEND 42 + +#define FUNC_TYPE_TAG 0x60 + + +#include "cursor.h" +#include "error.h" + +#ifdef NOINLINE + #define INLINE __attribute__((noinline)) +#else + #define INLINE inline +#endif + + +#define interp_error(p, fmt, ...) note_error(&((p)->errors), interp_codeptr(p), fmt, ##__VA_ARGS__) +#define parse_err(p, fmt, ...) note_error(&((p)->errs), &(p)->cur, fmt, ##__VA_ARGS__) + +enum valtype { + val_i32 = 0x7F, + val_i64 = 0x7E, + val_f32 = 0x7D, + val_f64 = 0x7C, + val_ref_null = 0xD0, + val_ref_func = 0x70, + val_ref_extern = 0x6F, +}; + +enum const_instr { + ci_const_i32 = 0x41, + ci_const_i64 = 0x42, + ci_const_f32 = 0x43, + ci_const_f64 = 0x44, + ci_ref_null = 0xD0, + ci_ref_func = 0xD2, + ci_global_get = 0x23, + ci_end = 0x0B, +}; + +enum limit_type { + limit_min = 0x00, + limit_min_max = 0x01, +}; + +struct limits { + u32 min; + u32 max; + enum limit_type type; +}; + +enum section_tag { + section_custom, + section_type, + section_import, + section_function, + section_table, + section_memory, + section_global, + section_export, + section_start, + section_element, + section_code, + section_data, + section_data_count, + section_name, + num_sections, +}; + +enum name_subsection_tag { + name_subsection_module, + name_subsection_funcs, + name_subsection_locals, + num_name_subsections, +}; + +enum reftype { + funcref = 0x70, + externref = 0x6F, +}; + +struct resulttype { + unsigned char *valtypes; /* enum valtype */ + u32 num_valtypes; +}; + +struct functype { + struct resulttype params; + struct resulttype result; +}; + +struct table { + enum reftype reftype; + struct limits limits; +}; + +struct tablesec { + struct table *tables; + u32 num_tables; +}; + +enum elem_mode { + elem_mode_passive, + elem_mode_active, + elem_mode_declarative, +}; + +struct expr { + u8 *code; + u32 code_len; +}; + +struct refval { + u32 addr; +}; + +struct table_inst { + struct refval *refs; + enum reftype reftype; + u32 num_refs; +}; + +struct numval { + union { + int i32; + u32 u32; + int64_t i64; + uint64_t u64; + float f32; + double f64; + }; +}; + +struct val { + enum valtype type; + union { + struct numval num; + struct refval ref; + }; +}; + +struct elem_inst { + struct val val; + u16 elem; + u16 init; +}; + +struct elem { + struct expr offset; + u32 tableidx; + struct expr *inits; + u32 num_inits; + enum elem_mode mode; + enum reftype reftype; + struct val val; +}; + +struct customsec { + const char *name; + unsigned char *data; + u32 data_len; +}; + +struct elemsec { + struct elem *elements; + u32 num_elements; +}; + +struct memsec { + struct limits *mems; /* memtype */ + u32 num_mems; +}; + +struct funcsec { + u32 *type_indices; + u32 num_indices; +}; + +enum mut { + mut_const, + mut_var, +}; + +struct globaltype { + enum valtype valtype; + enum mut mut; +}; + +struct globalsec { + struct global *globals; + u32 num_globals; +}; + +struct typesec { + struct functype *functypes; + u32 num_functypes; +}; + +enum import_type { + import_func, + import_table, + import_mem, + import_global, +}; + +struct importdesc { + enum import_type type; + union { + u32 typeidx; + struct limits tabletype; + struct limits memtype; + struct globaltype globaltype; + }; +}; + +struct import { + const char *module_name; + const char *name; + struct importdesc desc; + int resolved_builtin; +}; + +struct importsec { + struct import *imports; + u32 num_imports; +}; + +struct global { + struct globaltype type; + struct expr init; + struct val val; +}; + +struct local_def { + u32 num_types; + enum valtype type; +}; + +/* "code" */ +struct wasm_func { + struct expr code; + struct local_def *local_defs; + u32 num_local_defs; +}; + +enum func_type { + func_type_wasm, + func_type_builtin, +}; + +struct func { + union { + struct wasm_func *wasm_func; + struct builtin *builtin; + }; + u32 num_locals; + struct functype *functype; + enum func_type type; + const char *name; + u32 idx; +}; + +struct codesec { + struct wasm_func *funcs; + u32 num_funcs; +}; + +enum exportdesc { + export_func, + export_table, + export_mem, + export_global, +}; + +struct wexport { + const char *name; + u32 index; + enum exportdesc desc; +}; + +struct exportsec { + struct wexport *exports; + u32 num_exports; +}; + +struct nameassoc { + u32 index; + const char *name; +}; + +struct namemap { + struct nameassoc *names; + u32 num_names; +}; + +struct namesec { + const char *module_name; + struct namemap func_names; + int parsed; +}; + +struct wsection { + enum section_tag tag; +}; + +enum bulk_tag { + i_memory_copy = 10, + i_memory_fill = 11, + i_table_init = 12, + i_elem_drop = 13, + i_table_copy = 14, + i_table_grow = 15, + i_table_size = 16, + i_table_fill = 17, +}; + +enum instr_tag { + /* control instructions */ + i_unreachable = 0x00, + i_nop = 0x01, + i_block = 0x02, + i_loop = 0x03, + i_if = 0x04, + i_else = 0x05, + i_end = 0x0B, + i_br = 0x0C, + i_br_if = 0x0D, + i_br_table = 0x0E, + i_return = 0x0F, + i_call = 0x10, + i_call_indirect = 0x11, + + /* parametric instructions */ + i_drop = 0x1A, + i_select = 0x1B, + i_selects = 0x1C, + + /* variable instructions */ + i_local_get = 0x20, + i_local_set = 0x21, + i_local_tee = 0x22, + i_global_get = 0x23, + i_global_set = 0x24, + i_table_get = 0x25, + i_table_set = 0x26, + + /* memory instructions */ + i_i32_load = 0x28, + i_i64_load = 0x29, + i_f32_load = 0x2A, + i_f64_load = 0x2B, + i_i32_load8_s = 0x2C, + i_i32_load8_u = 0x2D, + i_i32_load16_s = 0x2E, + i_i32_load16_u = 0x2F, + i_i64_load8_s = 0x30, + i_i64_load8_u = 0x31, + i_i64_load16_s = 0x32, + i_i64_load16_u = 0x33, + i_i64_load32_s = 0x34, + i_i64_load32_u = 0x35, + i_i32_store = 0x36, + i_i64_store = 0x37, + i_f32_store = 0x38, + i_f64_store = 0x39, + i_i32_store8 = 0x3A, + i_i32_store16 = 0x3B, + i_i64_store8 = 0x3C, + i_i64_store16 = 0x3D, + i_i64_store32 = 0x3E, + i_memory_size = 0x3F, + i_memory_grow = 0x40, + + /* numeric instructions */ + i_i32_const = 0x41, + i_i64_const = 0x42, + i_f32_const = 0x43, + i_f64_const = 0x44, + + i_i32_eqz = 0x45, + i_i32_eq = 0x46, + i_i32_ne = 0x47, + i_i32_lt_s = 0x48, + i_i32_lt_u = 0x49, + i_i32_gt_s = 0x4A, + i_i32_gt_u = 0x4B, + i_i32_le_s = 0x4C, + i_i32_le_u = 0x4D, + i_i32_ge_s = 0x4E, + i_i32_ge_u = 0x4F, + + i_i64_eqz = 0x50, + i_i64_eq = 0x51, + i_i64_ne = 0x52, + i_i64_lt_s = 0x53, + i_i64_lt_u = 0x54, + i_i64_gt_s = 0x55, + i_i64_gt_u = 0x56, + i_i64_le_s = 0x57, + i_i64_le_u = 0x58, + i_i64_ge_s = 0x59, + i_i64_ge_u = 0x5A, + + i_f32_eq = 0x5B, + i_f32_ne = 0x5C, + i_f32_lt = 0x5D, + i_f32_gt = 0x5E, + i_f32_le = 0x5F, + i_f32_ge = 0x60, + + i_f64_eq = 0x61, + i_f64_ne = 0x62, + i_f64_lt = 0x63, + i_f64_gt = 0x64, + i_f64_le = 0x65, + i_f64_ge = 0x66, + + i_i32_clz = 0x67, + i_i32_ctz = 0x68, + i_i32_popcnt = 0x69, + + i_i32_add = 0x6A, + i_i32_sub = 0x6B, + i_i32_mul = 0x6C, + i_i32_div_s = 0x6D, + i_i32_div_u = 0x6E, + i_i32_rem_s = 0x6F, + i_i32_rem_u = 0x70, + i_i32_and = 0x71, + i_i32_or = 0x72, + i_i32_xor = 0x73, + i_i32_shl = 0x74, + i_i32_shr_s = 0x75, + i_i32_shr_u = 0x76, + i_i32_rotl = 0x77, + i_i32_rotr = 0x78, + + i_i64_clz = 0x79, + i_i64_ctz = 0x7A, + i_i64_popcnt = 0x7B, + i_i64_add = 0x7C, + i_i64_sub = 0x7D, + i_i64_mul = 0x7E, + i_i64_div_s = 0x7F, + i_i64_div_u = 0x80, + i_i64_rem_s = 0x81, + i_i64_rem_u = 0x82, + i_i64_and = 0x83, + i_i64_or = 0x84, + i_i64_xor = 0x85, + i_i64_shl = 0x86, + i_i64_shr_s = 0x87, + i_i64_shr_u = 0x88, + i_i64_rotl = 0x89, + i_i64_rotr = 0x8A, + + i_f32_abs = 0x8b, + i_f32_neg = 0x8c, + i_f32_ceil = 0x8d, + i_f32_floor = 0x8e, + i_f32_trunc = 0x8f, + i_f32_nearest = 0x90, + i_f32_sqrt = 0x91, + i_f32_add = 0x92, + i_f32_sub = 0x93, + i_f32_mul = 0x94, + i_f32_div = 0x95, + i_f32_min = 0x96, + i_f32_max = 0x97, + i_f32_copysign = 0x98, + + i_f64_abs = 0x99, + i_f64_neg = 0x9a, + i_f64_ceil = 0x9b, + i_f64_floor = 0x9c, + i_f64_trunc = 0x9d, + i_f64_nearest = 0x9e, + i_f64_sqrt = 0x9f, + i_f64_add = 0xa0, + i_f64_sub = 0xa1, + i_f64_mul = 0xa2, + i_f64_div = 0xa3, + i_f64_min = 0xa4, + i_f64_max = 0xa5, + i_f64_copysign = 0xa6, + + i_i32_wrap_i64 = 0xa7, + i_i32_trunc_f32_s = 0xa8, + i_i32_trunc_f32_u = 0xa9, + i_i32_trunc_f64_s = 0xaa, + i_i32_trunc_f64_u = 0xab, + i_i64_extend_i32_s = 0xac, + i_i64_extend_i32_u = 0xad, + i_i64_trunc_f32_s = 0xae, + i_i64_trunc_f32_u = 0xaf, + i_i64_trunc_f64_s = 0xb0, + i_i64_trunc_f64_u = 0xb1, + i_f32_convert_i32_s = 0xb2, + i_f32_convert_i32_u = 0xb3, + i_f32_convert_i64_s = 0xb4, + i_f32_convert_i64_u = 0xb5, + i_f32_demote_f64 = 0xb6, + i_f64_convert_i32_s = 0xb7, + i_f64_convert_i32_u = 0xb8, + i_f64_convert_i64_s = 0xb9, + i_f64_convert_i64_u = 0xba, + i_f64_promote_f32 = 0xbb, + + i_i32_reinterpret_f32 = 0xbc, + i_i64_reinterpret_f64 = 0xbd, + i_f32_reinterpret_i32 = 0xbe, + i_f64_reinterpret_i64 = 0xbf, + + i_i32_extend8_s = 0xc0, + i_i32_extend16_s = 0xc1, + i_i64_extend8_s = 0xc2, + i_i64_extend16_s = 0xc3, + i_i64_extend32_s = 0xc4, + + i_ref_null = 0xD0, + i_ref_is_null = 0xD1, + i_ref_func = 0xD2, + + i_bulk_op = 0xFC, + /* TODO: more instrs */ + +}; + +enum blocktype_tag { + blocktype_empty, + blocktype_valtype, + blocktype_index, +}; + +struct blocktype { + enum blocktype_tag tag; + union { + enum valtype valtype; + int type_index; + }; +}; + +struct instrs { + unsigned char *data; + u32 len; +}; + +struct block { + struct blocktype type; + struct expr instrs; +}; + +struct memarg { + u32 offset; + u32 align; +}; + +struct br_table { + u32 num_label_indices; + u32 label_indices[512]; + u32 default_label; +}; + +struct call_indirect { + u32 tableidx; + u32 typeidx; +}; + +struct table_init { + u32 tableidx; + u32 elemidx; +}; + +struct table_copy { + u32 from; + u32 to; +}; + +struct bulk_op { + enum bulk_tag tag; + union { + struct table_init table_init; + struct table_copy table_copy; + u32 idx; + }; +}; + +struct select_instr { + u8 *valtypes; + u32 num_valtypes; +}; + +struct instr { + enum instr_tag tag; + int pos; + union { + struct br_table br_table; + struct bulk_op bulk_op; + struct call_indirect call_indirect; + struct memarg memarg; + struct select_instr select; + struct block block; + struct expr else_block; + double f64; + float f32; + int i32; + u32 u32; + int64_t i64; + u64 u64; + unsigned char memidx; + enum reftype reftype; + }; +}; + +enum datamode { + datamode_active, + datamode_passive, +}; + +struct wdata_active { + u32 mem_index; + struct expr offset_expr; +}; + +struct wdata { + struct wdata_active active; + u8 *bytes; + u32 bytes_len; + enum datamode mode; +}; + +struct datasec { + struct wdata *datas; + u32 num_datas; +}; + +struct startsec { + u32 start_fn; +}; + +struct module { + unsigned int parsed; + unsigned int custom_sections; + + struct func *funcs; + + u32 num_funcs; + + struct customsec custom_section[MAX_CUSTOM_SECTIONS]; + struct typesec type_section; + struct funcsec func_section; + struct importsec import_section; + struct exportsec export_section; + struct codesec code_section; + struct tablesec table_section; + struct memsec memory_section; + struct globalsec global_section; + struct startsec start_section; + struct elemsec element_section; + struct datasec data_section; + struct namesec name_section; +}; + +// make sure the struct is packed so that +struct label { + u32 instr_pos; // resolved status is stored in HOB of pos + u32 jump; +}; + +struct callframe { + struct cursor code; + struct val *locals; + struct func *func; + u16 prev_stack_items; +}; + +struct resolver { + u16 label; + u8 end_tag; + u8 start_tag; +}; + +struct global_inst { + struct val val; +}; + +struct module_inst { + struct table_inst *tables; + struct global_inst *globals; + struct elem_inst *elements; + + u32 num_tables; + u32 num_globals; + u32 num_elements; + + int start_fn; + unsigned char *globals_init; +}; + +struct wasi { + int argc; + const char **argv; + + int environc; + const char **environ; +}; + +struct wasm_interp; + +struct builtin { + const char *name; + int (*fn)(struct wasm_interp *); + int (*prepare_args)(struct wasm_interp *); +}; + +struct wasm_interp { + struct module *module; + struct module_inst module_inst; + struct wasi wasi; + void *context; + + struct builtin builtins[MAX_BUILTINS]; + int num_builtins; + + int prev_resolvers, quitting; + + struct errors errors; /* struct error */ + size_t ops; + + struct cursor callframes; /* struct callframe */ + struct cursor stack; /* struct val */ + struct cursor mem; /* u8/mixed */ + + struct cursor memory; /* memory pages (65536 blocks) */ + + struct cursor locals; /* struct val */ + struct cursor labels; /* struct labels */ + struct cursor num_labels; + + // resolve stack for the current function. every time a control + // instruction is encountered, the label index is pushed. When an + // instruction is popped, we can resolve the label + struct cursor resolver_stack; /* struct resolver */ + struct cursor resolver_offsets; /* int */ +}; + +struct wasm_parser { + struct module module; + struct builtin *builtins; + u32 num_builtins; + struct cursor cur; + struct cursor mem; + struct errors errs; +}; + + +int run_wasm(unsigned char *wasm, unsigned long len, int argc, const char **argv, char **env, int *retval); +int parse_wasm(struct wasm_parser *p); +int wasm_interp_init(struct wasm_interp *interp, struct module *module); +void wasm_parser_free(struct wasm_parser *parser); +void wasm_parser_init(struct wasm_parser *p, u8 *wasm, size_t wasm_len, size_t arena_size, struct builtin *, int num_builtins); +void wasm_interp_free(struct wasm_interp *interp); +int interp_wasm_module(struct wasm_interp *interp, int *retval); +int interp_wasm_module_resume(struct wasm_interp *interp, int *retval); +void print_error_backtrace(struct errors *errors); +void setup_wasi(struct wasm_interp *interp, int argc, const char **argv, char **env); +void print_callstack(struct wasm_interp *interp); + +// builtin helpers +int get_params(struct wasm_interp *interp, struct val** vals, u32 num_vals); +int get_var_params(struct wasm_interp *interp, struct val** vals, u32 *num_vals); +u8 *interp_mem_ptr(struct wasm_interp *interp, u32 ptr, int size); + +static INLINE struct callframe *top_callframe(struct cursor *cur) +{ + return (struct callframe*)cursor_top(cur, sizeof(struct callframe)); +} + + +static INLINE struct cursor *interp_codeptr(struct wasm_interp *interp) +{ + struct callframe *frame; + if (unlikely(!(frame = top_callframe(&interp->callframes)))) + return 0; + return &frame->code; +} + + +static INLINE int mem_ptr_str(struct wasm_interp *interp, u32 ptr, + const char **str) +{ + // still technically unsafe if the string runs over the end of memory... + if (!(*str = (const char*)interp_mem_ptr(interp, ptr, 1))) { + return interp_error(interp, "int memptr"); + } + return 1; +} + +static INLINE int mem_ptr_i32(struct wasm_interp *interp, u32 ptr, int **i) +{ + if (!(*i = (int*)interp_mem_ptr(interp, ptr, sizeof(int)))) + return interp_error(interp, "int memptr"); + return 1; +} + +static INLINE int cursor_pushval(struct cursor *cur, struct val *val) +{ + return cursor_push(cur, (u8*)val, sizeof(*val)); +} + +static INLINE int cursor_push_i32(struct cursor *stack, int i) +{ + struct val val; + val.type = val_i32; + val.num.i32 = i; + + return cursor_pushval(stack, &val); +} + +static INLINE int stack_push_i32(struct wasm_interp *interp, int i) +{ + return cursor_push_i32(&interp->stack, i); +} + +static INLINE struct callframe *top_callframes(struct cursor *cur, int top) +{ + return (struct callframe*)cursor_topn(cur, sizeof(struct callframe), top); +} + +#endif /* PROTOVERSE_WASM_H */ diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index fbda526c..c6ae81f8 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 4C06670E28FDEAA000038D2A /* utf8.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670D28FDEAA000038D2A /* utf8.c */; }; 4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8E280F640A000448DE /* ThreadModel.swift */; }; 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; }; + 4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; }; 4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */; }; 4C198DF029F88C6B004C165C /* Readme.md in Resources */ = {isa = PBXBuildFile; fileRef = 4C198DEC29F88C6B004C165C /* Readme.md */; }; 4C198DF129F88C6B004C165C /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4C198DED29F88C6B004C165C /* License.txt */; }; @@ -123,6 +124,8 @@ 4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */; }; 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; }; 4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; }; + 4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */; }; + 4C4F14AC2A2A763B0045A0B9 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C4F14AB2A2A763B0045A0B9 /* primal.wasm */; }; 4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0629A540BA003E4487 /* NotificationsModel.swift */; }; 4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0929A55429003E4487 /* EventGroup.swift */; }; 4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */; }; @@ -174,8 +177,11 @@ 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; }; 4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; }; 4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; }; + 4C9146FC2A2A77B300DDEA40 /* NostrScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */; }; + 4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; }; + 4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; }; + 4C9147002A2A891E00DDEA40 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.c */; }; 4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; }; - 4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */; }; 4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; }; 4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; }; 4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; }; @@ -461,6 +467,7 @@ 4C06670D28FDEAA000038D2A /* utf8.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = utf8.c; sourceTree = ""; }; 4C0A3F8E280F640A000448DE /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = ""; }; 4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = ""; }; + 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = ""; }; 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = ""; }; 4C198DEC29F88C6B004C165C /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; 4C198DED29F88C6B004C165C /* License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License.txt; sourceTree = ""; }; @@ -570,6 +577,10 @@ 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoiceView.swift; sourceTree = ""; }; 4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = ""; }; 4C4A3A5A288A1B2200453788 /* damus.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = damus.entitlements; sourceTree = ""; }; + 4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrScriptTests.swift; sourceTree = ""; }; + 4C4F14A82A2A71AB0045A0B9 /* nostrscript.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nostrscript.h; sourceTree = ""; }; + 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nostrscript.c; sourceTree = ""; }; + 4C4F14AB2A2A763B0045A0B9 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; }; 4C54AA0629A540BA003E4487 /* NotificationsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsModel.swift; sourceTree = ""; }; 4C54AA0929A55429003E4487 /* EventGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroup.swift; sourceTree = ""; }; 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapGroup.swift; sourceTree = ""; }; @@ -625,8 +636,9 @@ 4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = ""; }; 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = ""; }; + 4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrScript.swift; sourceTree = ""; }; + 4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = ""; }; 4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = ""; }; - 4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = ""; }; 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = ""; }; 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayName.swift; sourceTree = ""; }; 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfileName.swift; sourceTree = ""; }; @@ -635,6 +647,13 @@ 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = ""; }; 4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapTypePicker.swift; sourceTree = ""; }; 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = ""; }; + 4CA9276D2A2A5D110098A105 /* wasm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wasm.h; sourceTree = ""; }; + 4CA9276E2A2A5D110098A105 /* wasm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wasm.c; sourceTree = ""; }; + 4CA9276F2A2A5D470098A105 /* parser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = ""; }; + 4CA927702A2A5D470098A105 /* debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = ""; }; + 4CA927712A2A5D480098A105 /* error.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = ""; }; + 4CA927742A2A5E2F0098A105 /* varint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = varint.h; sourceTree = ""; }; + 4CA927752A2A5E2F0098A105 /* typedefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typedefs.h; sourceTree = ""; }; 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = ""; }; 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = ""; }; 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = ""; }; @@ -827,6 +846,16 @@ 4C06670728FDE62900038D2A /* damus-c */ = { isa = PBXGroup; children = ( + 4C9146FF2A2A891E00DDEA40 /* error.c */, + 4CA927752A2A5E2F0098A105 /* typedefs.h */, + 4CA927742A2A5E2F0098A105 /* varint.h */, + 4CA927702A2A5D470098A105 /* debug.h */, + 4CA927712A2A5D480098A105 /* error.h */, + 4CA9276F2A2A5D470098A105 /* parser.h */, + 4CA9276E2A2A5D110098A105 /* wasm.c */, + 4CA9276D2A2A5D110098A105 /* wasm.h */, + 4C4F14A82A2A71AB0045A0B9 /* nostrscript.h */, + 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */, 4C06670928FDE64700038D2A /* damus.h */, 4C06670A28FDE64700038D2A /* damus.c */, 4C06670828FDE64700038D2A /* damus-Bridging-Header.h */, @@ -886,7 +915,7 @@ 4C0A3F8D280F63FF000448DE /* Models */ = { isa = PBXGroup; children = ( - 4C9AA1462A444422003F49FD /* Zaps */, + 4C190F1E2A535FC200027FD5 /* Zaps */, 4C54AA0829A55416003E4487 /* Notifications */, 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */, 4C0A3F8E280F640A000448DE /* ThreadModel.swift */, @@ -935,6 +964,14 @@ path = Models; sourceTree = ""; }; + 4C190F1E2A535FC200027FD5 /* Zaps */ = { + isa = PBXGroup; + children = ( + 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */, + ); + path = Zaps; + sourceTree = ""; + }; 4C198DEA29F88C6B004C165C /* BlurHash */ = { isa = PBXGroup; children = ( @@ -987,6 +1024,14 @@ path = Notifications; sourceTree = ""; }; + 4C4F14AA2A2A76270045A0B9 /* Fixtures */ = { + isa = PBXGroup; + children = ( + 4C4F14AB2A2A763B0045A0B9 /* primal.wasm */, + ); + path = Fixtures; + sourceTree = ""; + }; 4C54AA0829A55416003E4487 /* Notifications */ = { isa = PBXGroup; children = ( @@ -1181,12 +1226,12 @@ path = Buttons; sourceTree = ""; }; - 4C9AA1462A444422003F49FD /* Zaps */ = { + 4CA927732A2A5DCC0098A105 /* nostrscript */ = { isa = PBXGroup; children = ( - 4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */, + 4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */, ); - path = Zaps; + path = nostrscript; sourceTree = ""; }; 4CAAD8AE29888A9B00060CEA /* Relays */ = { @@ -1313,6 +1358,7 @@ 4CE6DEDA27F7A08100C66700 = { isa = PBXGroup; children = ( + 4CA927732A2A5DCC0098A105 /* nostrscript */, 4C06670728FDE62900038D2A /* damus-c */, 4CE6DEE527F7A08100C66700 /* damus */, 4CE6DEF627F7A08200C66700 /* damusTests */, @@ -1367,6 +1413,7 @@ 4CE6DEF627F7A08200C66700 /* damusTests */ = { isa = PBXGroup; children = ( + 4C4F14AA2A2A76270045A0B9 /* Fixtures */, 4C7D097D2A0C58B900943473 /* WalletConnectTests.swift */, F944F56C29EA9CB20067B3BF /* Models */, 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */, @@ -1391,6 +1438,7 @@ 3AFBF3FC29FDA7CC00E79C7C /* CustomZapViewTests.swift */, 3A5E47C62A4A76C800C0D090 /* TrieTests.swift */, 3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */, + 4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */, ); path = damusTests; sourceTree = ""; @@ -1669,6 +1717,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4C4F14AC2A2A763B0045A0B9 /* primal.wasm in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1705,6 +1754,7 @@ 4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */, 4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */, 4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */, + 4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */, 4C75EFB728049D990006080F /* RelayPool.swift in Sources */, F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */, 4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */, @@ -1799,7 +1849,6 @@ 4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */, 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, - 4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */, 4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */, @@ -1809,6 +1858,7 @@ 4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */, 4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */, 64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */, + 4C9146FC2A2A77B300DDEA40 /* NostrScript.swift in Sources */, 4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */, 4C3EA64928FF597700C48A62 /* bech32.c in Sources */, 4CE879522996B68900F758CC /* RelayType.swift in Sources */, @@ -1844,6 +1894,7 @@ 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */, 4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */, 4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */, + 4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */, 4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */, @@ -1920,6 +1971,7 @@ 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */, 4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */, 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */, + 4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */, 4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */, 4C1A9A1A29DCA17E00516EAC /* ReplyCounter.swift in Sources */, 50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */, @@ -1933,6 +1985,7 @@ 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */, F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */, 4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */, + 4C9147002A2A891E00DDEA40 /* error.c in Sources */, 4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */, 501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */, 4CF0ABD42980996B00D66079 /* Report.swift in Sources */, @@ -1980,6 +2033,7 @@ 3AFBF3FD29FDA7CC00E79C7C /* CustomZapViewTests.swift in Sources */, 3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */, DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */, + 4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */, 3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */, 4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */, 4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */, diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift index fdff553c..d790dd5b 100644 --- a/damus/Models/Mentions.swift +++ b/damus/Models/Mentions.swift @@ -153,7 +153,7 @@ func render_blocks(blocks: [Block]) -> String { func parse_mentions(content: String, tags: [[String]]) -> [Block] { var out: [Block] = [] - var bs = blocks() + var bs = note_blocks() bs.num_blocks = 0; blocks_init(&bs) diff --git a/damus/Nostr/NostrRequest.swift b/damus/Nostr/NostrRequest.swift index a268ab90..2b272997 100644 --- a/damus/Nostr/NostrRequest.swift +++ b/damus/Nostr/NostrRequest.swift @@ -12,6 +12,28 @@ struct NostrSubscribe { let sub_id: String } + +enum NostrRequestType { + case typical(NostrRequest) + case custom(String) + + var is_write: Bool { + guard case .typical(let req) = self else { + return true + } + + return req.is_write + } + + var is_read: Bool { + guard case .typical(let req) = self else { + return true + } + + return req.is_read + } +} + enum NostrRequest { case subscribe(NostrSubscribe) case unsubscribe(String) @@ -31,4 +53,5 @@ enum NostrRequest { var is_read: Bool { return !is_write } + } diff --git a/damus/Nostr/RelayConnection.swift b/damus/Nostr/RelayConnection.swift index f09931ee..ab292fff 100644 --- a/damus/Nostr/RelayConnection.swift +++ b/damus/Nostr/RelayConnection.swift @@ -99,15 +99,25 @@ final class RelayConnection: ObservableObject { isConnected = false isConnecting = false } - - func send(_ req: NostrRequest) { - guard let req = make_nostr_req(req) else { - print("failed to encode nostr req: \(req)") - return - } + + func send_raw(_ req: String) { socket.send(.string(req)) } + func send(_ req: NostrRequestType) { + switch req { + case .typical(let req): + guard let req = make_nostr_req(req) else { + print("failed to encode nostr req: \(req)") + return + } + send_raw(req) + + case .custom(let req): + send_raw(req) + } + } + private func receive(event: WebSocketEvent) { switch event { case .connected: diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift index 316176f8..1126d7ab 100644 --- a/damus/Nostr/RelayPool.swift +++ b/damus/Nostr/RelayPool.swift @@ -14,8 +14,9 @@ struct RelayHandler { } struct QueuedRequest { - let req: NostrRequest + let req: NostrRequestType let relay: String + let skip_ephemeral: Bool } struct SeenEvent: Hashable { @@ -178,18 +179,18 @@ class RelayPool { return c } - func queue_req(r: NostrRequest, relay: String) { + func queue_req(r: NostrRequestType, relay: String, skip_ephemeral: Bool) { let count = count_queued(relay: relay) guard count <= 10 else { print("can't queue, too many queued events for \(relay)") return } - print("queueing request: \(r) for \(relay)") - request_queue.append(QueuedRequest(req: r, relay: relay)) + print("queueing request for \(relay)") + request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral)) } - func send(_ req: NostrRequest, to: [String]? = nil, skip_ephemeral: Bool = true) { + func send_raw(_ req: NostrRequestType, to: [String]? = nil, skip_ephemeral: Bool = true) { let relays = to.map{ get_relays($0) } ?? self.relays for relay in relays { @@ -206,7 +207,7 @@ class RelayPool { } guard relay.connection.isConnected else { - queue_req(r: req, relay: relay.id) + queue_req(r: req, relay: relay.id, skip_ephemeral: skip_ephemeral) continue } @@ -214,6 +215,10 @@ class RelayPool { } } + func send(_ req: NostrRequest, to: [String]? = nil, skip_ephemeral: Bool = true) { + send_raw(.typical(req), to: to, skip_ephemeral: skip_ephemeral) + } + func get_relays(_ ids: [String]) -> [Relay] { // don't include ephemeral relays in the default list to query relays.filter { ids.contains($0.id) } @@ -231,7 +236,7 @@ class RelayPool { } print("running queueing request: \(req.req) for \(relay_id)") - self.send(req.req, to: [relay_id]) + self.send_raw(req.req, to: [relay_id], skip_ephemeral: false) } } diff --git a/damus/Util/Zap.swift b/damus/Util/Zap.swift index 2ddb9f49..664cbe77 100644 --- a/damus/Util/Zap.swift +++ b/damus/Util/Zap.swift @@ -360,7 +360,7 @@ func determine_zap_target(_ ev: NostrEvent) -> ZapTarget? { } func decode_bolt11(_ s: String) -> Invoice? { - var bs = blocks() + var bs = note_blocks() bs.num_blocks = 0 blocks_init(&bs) diff --git a/damusTests/Models/DamusParseContentTests.swift b/damusTests/Models/DamusParseContentTests.swift index 5166808d..099da5d3 100644 --- a/damusTests/Models/DamusParseContentTests.swift +++ b/damusTests/Models/DamusParseContentTests.swift @@ -21,7 +21,7 @@ class DamusParseContentTests: XCTestCase { } func test_damus_parse_content_can_parse_mention_without_white_space_at_front() throws { - var bs = blocks() + var bs = note_blocks() bs.num_blocks = 0; blocks_init(&bs) diff --git a/damusTests/NostrScriptTests.swift b/damusTests/NostrScriptTests.swift new file mode 100644 index 00000000..af007ba3 --- /dev/null +++ b/damusTests/NostrScriptTests.swift @@ -0,0 +1,83 @@ +// +// NostrScriptTests.swift +// damusTests +// +// Created by William Casarin on 2023-06-02. +// + +import XCTest +@testable import damus + +final class NostrScriptTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func loadTestWasm() throws -> Data { + let bundle = Bundle(for: type(of: self)) + guard let fileURL = bundle.url(forResource: "primal", withExtension: "wasm") else { + throw CocoaError(.fileReadNoSuchFile) + } + + return try Data(contentsOf: fileURL) + } + + func test_nostrscript() throws { + var data = try loadTestWasm().bytes + let pool = RelayPool() + let script = NostrScript(pool: pool) + + let load_err = script.load(wasm: &data) + XCTAssertNil(load_err) + + let res = script.run() + switch res { + case .finished: XCTAssert(false) + case .runtime_err: XCTAssert(false) + case .suspend: + XCTAssertEqual(script.waiting_on, .event("sidebar_trending")) + break + } + + let resume_expected = XCTestExpectation(description: "we got ") + pool.register_handler(sub_id: "sidebar_trending") { (relay_id, conn) in + if script.runstate?.exited == true { + pool.disconnect() + resume_expected.fulfill() + return + } + + guard case .nostr_event(let resp) = conn else { + return + } + + let with: NScriptResumeWith = .event(resp) + guard let res = script.resume(with: with) else { + return + } + + switch res { + case .finished: break + case .runtime_err: XCTAssert(false) + case .suspend: break + } + } + + pool.connect(to: ["wss://cache3.primal.net/cache15"]) + + self.wait(for: [resume_expected], timeout: 10.0) + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/nostrscript/NostrScript.swift b/nostrscript/NostrScript.swift new file mode 100644 index 00000000..63c1cc9e --- /dev/null +++ b/nostrscript/NostrScript.swift @@ -0,0 +1,361 @@ +// +// NostrScript.swift +// damus +// +// Created by William Casarin on 2023-06-02. +// + +import Foundation + +enum NostrScriptLoadErr { + case parse + case module_init +} + +enum NostrScriptRunResult { + case runtime_err([String]) + case suspend + case finished(Int) + + var exited: Bool { + switch self { + case .runtime_err: + return true + case .suspend: + return false + case .finished: + return true + } + } + + var is_suspended: Bool { + if case .suspend = self { + return true + } + return false + } +} + +enum NostrScriptLoadResult { + case err(NostrScriptLoadErr) + case loaded(wasm_interp) +} + +class NostrScript { + private var interp: wasm_interp + private var parser: wasm_parser + var waiting_on: NScriptWaiting? + + private(set) var runstate: NostrScriptRunResult? + private(set) var pool: RelayPool + private(set) var event: NostrResponse? + + init(pool: RelayPool) { + self.interp = wasm_interp() + self.parser = wasm_parser() + self.pool = pool + self.event = nil + self.runstate = nil + } + + deinit { + wasm_parser_free(&self.parser) + wasm_interp_free(&self.interp) + } + + func is_suspended(on: NScriptWaiting) -> Bool { + return self.waiting_on == on + } + + func can_resume(with: NScriptResumeWith) -> Bool { + guard let waiting_on else { + return false + } + switch waiting_on { + case .event(let subid): + switch with { + case .event(let resp): + return resp.subid == subid + } + } + } + + func test(_ str: String) { + print("hello from \(str)") + } + + func load(wasm: inout [UInt8]) -> NostrScriptLoadErr? { + switch nscript_load(&parser, &interp, &wasm, UInt(wasm.count)) { + case NSCRIPT_LOADED: + print("load num_exports \(interp.module.pointee.export_section.num_exports)") + interp.context = Unmanaged.passUnretained(self).toOpaque() + return nil + case NSCRIPT_INIT_ERR: + return .module_init + case NSCRIPT_PARSE_ERR: + return .parse + default: + return .parse + } + } + + func resume(with: NScriptResumeWith) -> NostrScriptRunResult? { + guard let runstate, runstate.is_suspended, can_resume(with: with) else { + return nil + } + + switch with { + case .event(let resp): + load_data(resp: resp) + } + + let st = nscript_run(interp: &interp, resuming: true) + self.runstate = st + self.event = nil + return st + } + + private func load_data(resp: NostrResponse) { + self.event = resp + } + + func run() -> NostrScriptRunResult { + if let runstate { + return runstate + } + + let st = nscript_run(interp: &interp, resuming: false) + self.runstate = st + return st + } +} + +fileprivate func interp_nostrscript(interp: UnsafeMutablePointer?) -> NostrScript? { + guard let interp = interp?.pointee else { + return nil + } + + return Unmanaged.fromOpaque(interp.context).takeUnretainedValue() +} + +fileprivate func asm_str_byteptr(cstr: UnsafePointer, len: Int32) -> String? { + let u16 = cstr.withMemoryRebound(to: UInt16.self, capacity: Int(len)) { p in p } + return asm_str(cstr: u16, len: len) +} + +fileprivate func asm_str(cstr: UnsafePointer, len: Int32) -> String? { + return String(utf16CodeUnits: cstr, count: Int(len)) +} + +enum NScriptCommand: Int { + case pool_send = 1 + case add_relay = 2 + case event_await = 3 + case event_get_type = 4 + case event_get_note = 5 + case note_get_kind = 6 + case note_get_content = 7 + case note_get_content_length = 8 +} + +enum NScriptEventType: Int { + case ok = 1 + case note = 2 + case notice = 3 + case eose = 4 + + init(resp: NostrResponse) { + switch resp { + case .event: + self = .note + case .notice: + self = .notice + case .eose: + self = .eose + case .ok: + self = .ok + } + } +} + +enum NScriptWaiting: Equatable { + case event(String) + + var subid: String { + switch self { + case .event(let subid): + return subid + } + } +} + +enum NScriptResumeWith { + case event(NostrResponse) +} + +enum NScriptCmdResult { + case suspend(NScriptWaiting) + case ok + case fatal +} + +@_cdecl("nscript_nostr_cmd") +public func nscript_nostr_cmd(interp: UnsafeMutablePointer?, cmd: Int32, value: UnsafePointer, len: Int32) -> Int32 { + guard let script = interp_nostrscript(interp: interp), + let cmd = NScriptCommand(rawValue: Int(cmd)) else { + return 0 + } + + print("nostr_cmd \(cmd)") + + switch cmd { + case .pool_send: + guard let req = asm_str_byteptr(cstr: value, len: len) else { return 0 } + let res = nscript_pool_send(script: script, req: req) + stack_push_i32(interp, 0); + return res; + + case .add_relay: + guard let relay = asm_str_byteptr(cstr: value, len: len) else { return 0 } + let ok = nscript_add_relay(script: script, relay: relay) + stack_push_i32(interp, ok ? 1 : 0) + return 1; + + case .event_await: + guard let subid = asm_str_byteptr(cstr: value, len: len) else { return 0 } + nscript_event_await(script: script, subid: subid) + let ev_handle: Int32 = 1 + stack_push_i32(interp, ev_handle); + return BUILTIN_SUSPEND + + case .event_get_type: + guard let event = script.event else { + stack_push_i32(interp, 0); + return 1 + } + + let type = NScriptEventType(resp: event) + stack_push_i32(interp, Int32(type.rawValue)); + return 1 + + case .event_get_note: + guard let event = script.event, case .event = event + else { stack_push_i32(interp, 0); return 1 } + + let note_handle: Int32 = 1 + stack_push_i32(interp, note_handle) + return 1 + + case .note_get_kind: + guard let event = script.event, case .event(_, let note) = event + else { + stack_push_i32(interp, 0); + return 1 + + } + + stack_push_i32(interp, Int32(note.kind)) + return 1 + + case .note_get_content: + guard let event = script.event, case .event(_, let note) = event + else { stack_push_i32(interp, 0); return 1 } + + stack_push_i32(interp, Int32(note.kind)) + return 1 + + case .note_get_content_length: + guard let event = script.event, case .event(_, let note) = event + else { stack_push_i32(interp, 0); return 1 } + + stack_push_i32(interp, Int32(note.content.utf8.count)) + return 1 + } + +} + +func nscript_add_relay(script: NostrScript, relay: String) -> Bool { + guard let url = RelayURL(relay) else { return false } + let desc = RelayDescriptor(url: url, info: .rw, variant: .ephemeral) + return (try? script.pool.add_relay(desc)) != nil +} + + +@_cdecl("nscript_pool_send_to") +public func nscript_pool_send_to(interp: UnsafeMutablePointer?, preq: UnsafePointer, req_len: Int32, to: UnsafePointer, to_len: Int32) -> Int32 { + + guard let script = interp_nostrscript(interp: interp), + let req_str = asm_str(cstr: preq, len: req_len), + let to = asm_str(cstr: to, len: to_len) + else { + return 0 + } + + DispatchQueue.main.async { + script.pool.send_raw(.custom(req_str), to: [to], skip_ephemeral: false) + } + + return 1; +} + +func nscript_pool_send(script: NostrScript, req req_str: String) -> Int32 { + script.test("pool_send: '\(req_str)'") + + DispatchQueue.main.sync { + script.pool.send_raw(.custom(req_str), skip_ephemeral: false) + } + + return 1; +} + +func nscript_event_await(script: NostrScript, subid: String) { + script.waiting_on = .event(subid) +} + +func nscript_get_error_backtrace(errors: inout errors) -> [String] { + var xs = [String]() + var errs = cursor() + var err = error() + + copy_cursor(&errors.cur, &errs) + errs.p = errs.start; + + while (errs.p < errors.cur.p) { + if (cursor_pull_error(&errs, &err) == 0) { + return xs + } + + xs.append(String(cString: err.msg)) + } + + return xs +} + +func nscript_run(interp: inout wasm_interp, resuming: Bool) -> NostrScriptRunResult { + var res: Int32 = 0 + var retval: Int32 = 0 + + if (resuming) { + print("resuming nostrscript"); + res = interp_wasm_module_resume(&interp, &retval); + } else { + res = interp_wasm_module(&interp, &retval); + } + + if res == 0 { + print_callstack(&interp); + print_error_backtrace(&interp.errors); + let backtrace = nscript_get_error_backtrace(errors: &interp.errors) + return .runtime_err(backtrace) + } + + if res == BUILTIN_SUSPEND { + return .suspend + } + + //print_stack(&interp.stack); + wasm_interp_free(&interp); + + return .finished(Int(retval)) +} + diff --git a/nostrscript/nostr.ts b/nostrscript/nostr.ts new file mode 100644 index 00000000..e1a62745 --- /dev/null +++ b/nostrscript/nostr.ts @@ -0,0 +1,77 @@ + +// these are handles not actual pointers +export type Note = i32; +export type Event = i32; + +export enum EventType { + OK = 1, + NOTE = 2, + NOTICE = 3, + EOSE = 4 +} + +enum Command { + POOL_SEND = 1, + ADD_RELAY = 2, + EVENT_AWAIT = 3, + EVENT_GET_TYPE = 4, + EVENT_GET_NOTE = 5, + NOTE_GET_KIND = 6, + NOTE_GET_CONTENT = 7, + NOTE_GET_CONTENT_LENGTH = 8, +} + +declare function nostr_log(log: string): void; +declare function nostr_cmd(cmd: i32, val: i32, len: i32): i32; +declare function nostr_pool_send_to(req: string, req_len: i32, to: string, to_len: i32): void; + +export function pool_send(req: string): void { + nostr_cmd(Command.POOL_SEND, changetype(req), req.length) +} + +export function pool_send_to(req: string, to: string): void { + return nostr_pool_send_to(req, req.length, to, to.length) +} + +export function pool_add_relay(relay: string): boolean { + let ok = nostr_cmd(Command.ADD_RELAY, changetype(relay), relay.length) + return ok as boolean +} + +export function event_await(subid: string): Event { + return nostr_cmd(Command.EVENT_AWAIT, changetype(subid), subid.length) as i32 +} + +export function event_get_type(ev: Event): EventType { + if (!ev) return 0; + return nostr_cmd(Command.EVENT_GET_TYPE, ev, 0) as EventType +} + +export function event_get_note(ev: Event): Note { + if (!ev) return 0; + return nostr_cmd(Command.EVENT_GET_NOTE, ev, 0) +} + +export function note_get_kind(note: Note): u32 { + if (!note) return 0; + return nostr_cmd(Command.NOTE_GET_KIND, note, 0); +} + +function note_get_content_length(): i32 { + return nostr_cmd(Command.NOTE_GET_CONTENT_LENGTH, note, 0) +} + +export function log(log: string): void { + nostr_log(log) +} + +export function note_get_content(): string { + let res = nostr_cmd(Command.NOTE_GET_CONTENT, note, 0); + if (!res) return ""; + + let len = note_get_content_length() + let codes = TypedArray.wrap() + + return String.fromCharCodes(codes) +} + diff --git a/nostrscript/primal.ts b/nostrscript/primal.ts new file mode 100644 index 00000000..ce411a04 --- /dev/null +++ b/nostrscript/primal.ts @@ -0,0 +1,38 @@ + +import * as nostr from './nostr' + +export function go(): i32 { + let subid = "sidebar_trending" + let relay = 'wss://cache3.primal.net/cache15' + var done: i32 = 0 + var events: i32 = 0 + + nostr.pool_add_relay(relay) + nostr.pool_send_to(`["REQ","${subid}",{"cache":["explore_global_trending_24h"]}]`, relay) + + while (!done) { + var ev = nostr.event_await(subid) + let type = nostr.event_get_type(ev) + switch (type) { + case nostr.EventType.OK: + nostr.log("ok") + break + case nostr.EventType.NOTE: + events++ + let note = nostr.event_get_note(ev) + let kind = nostr.note_get_kind(note) + nostr.log(`type:${type} #${events} note, kind:${kind}`) + break + case nostr.EventType.EOSE: + nostr.log("eose, got " + events.toString() + " events") + done = true + break + default: + nostr.log("got event type " + type.toString()) + } + } + + return events +} + +go() diff --git a/nostrscript/primal.wasm b/nostrscript/primal.wasm new file mode 100644 index 00000000..ba1ce198 Binary files /dev/null and b/nostrscript/primal.wasm differ diff --git a/shell.nix b/shell.nix index 997ed458..bb3ffc4c 100644 --- a/shell.nix +++ b/shell.nix @@ -1,5 +1,5 @@ { pkgs ? import {} }: with pkgs; mkShell { - buildInputs = with python3Packages; [ Mako requests ]; + buildInputs = with python3Packages; [ Mako requests wabt ]; }