commit b688f8167ef5e24dc94e24b781bf68833f749328
parent e482eb80dfa279758ac403286b60bdad98526911
Author: William Casarin <jb55@jb55.com>
Date: Sat, 21 Nov 2020 03:00:38 -0800
Initial wasm parser
Diffstat:
16 files changed, 790 insertions(+), 7 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,5 +1,8 @@
*.o
+.build-result
/protoverse
/libprotoverse.a
/TAGS
/test
+*.wasm
+/tags
diff --git a/Makefile b/Makefile
@@ -1,7 +1,17 @@
CFLAGS = -Wno-error=unused-function -O1 -g -std=c89 -Wall -Wextra -Werror -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Wdeclaration-after-statement
-OBJS = src/io.o src/parse.o src/cursor.o src/describe.o src/serve.o src/client.o src/net.o src/varint.o src/util.o
+OBJS = src/io.o \
+ src/parse.o \
+ src/cursor.o \
+ src/describe.o \
+ src/serve.o \
+ src/client.o \
+ src/net.o \
+ src/varint.o \
+ src/util.o \
+ src/parser.o \
+ src/wasm.o
all: protoverse libprotoverse.a
@@ -9,6 +19,9 @@ all: protoverse libprotoverse.a
@echo "cc $<"
@$(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $<
+%.wasm: %.wat
+ wat2wasm $^
+
protoverse: src/protoverse.c $(OBJS)
@echo "ld $@"
@$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
@@ -17,7 +30,7 @@ libprotoverse.a: $(OBJS)
ar rcs $@ $^
clean:
- rm -f protoverse test $(OBJS)
+ rm -f protoverse test $(OBJS) libprotoverse.a
test: src/test.c $(OBJS)
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
diff --git a/default.nix b/default.nix
@@ -2,5 +2,5 @@
with pkgs;
stdenv.mkDerivation {
name = "protoverse";
- nativeBuildInputs = [ gdb ];
+ nativeBuildInputs = [ gdb wabt ];
}
diff --git a/src/cursor.c b/src/cursor.c
@@ -6,6 +6,21 @@
#include <stdio.h>
#include <string.h>
+void *cursor_alloc(struct cursor *mem, unsigned long size)
+{
+ void *ret;
+
+ if (mem->p + size > mem->end) {
+ return NULL;
+ }
+
+ ret = mem->p;
+ memset(ret, 0, size);
+ mem->p += size;
+
+ return ret;
+}
+
void copy_cursor(struct cursor *src, struct cursor *dest)
{
dest->start = src->start;
@@ -40,7 +55,7 @@ int pull_byte(struct cursor *cursor, u8 *c)
int push_byte(struct cursor *cursor, u8 c)
{
- if (cursor->p + 1 >= cursor->end) {
+ if (cursor->p + 1 > cursor->end) {
return 0;
}
@@ -57,7 +72,7 @@ int pull_data_into_cursor(struct cursor *cursor,
{
int ok;
- if (dest->p + len >= dest->end) {
+ if (dest->p + len > dest->end) {
printf("not enough room in dest buffer\n");
return 0;
}
@@ -73,7 +88,7 @@ int pull_data_into_cursor(struct cursor *cursor,
int pull_data(struct cursor *cursor, u8 *data, int len)
{
- if (cursor->p + len >= cursor->end) {
+ if (cursor->p + len > cursor->end) {
return 0;
}
diff --git a/src/cursor.h b/src/cursor.h
@@ -9,6 +9,7 @@ struct cursor {
};
+void *cursor_alloc(struct cursor *mem, unsigned long size);
void copy_cursor(struct cursor *src, struct cursor *dest);
int cursor_index(struct cursor *cursor, int elem_size);
void make_cursor(unsigned char *start, unsigned char *end, struct cursor *cursor);
diff --git a/src/io.c b/src/io.c
@@ -2,6 +2,10 @@
#include "io.h"
#include <string.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written)
{
@@ -20,6 +24,20 @@ int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written)
return 1;
}
+int map_file(const char *filename, unsigned char **p, size_t *flen)
+{
+ struct stat st;
+ int des;
+ stat(filename, &st);
+ *flen = st.st_size;
+
+ des = open(filename, O_RDONLY);
+
+ *p = mmap(NULL, *flen, PROT_READ, MAP_PRIVATE, des, 0);
+ close(des);
+
+ return *p != MAP_FAILED;
+}
int read_file(const char *filename, unsigned char *buf, int buflen, int *written)
{
diff --git a/src/io.h b/src/io.h
@@ -7,6 +7,7 @@
int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written);
int read_file(const char *filename, unsigned char *buf, int buflen, int *written);
int read_file_or_stdin(const char *filename, unsigned char *buf, int buflen, int *written);
+int map_file(const char *filename, unsigned char **p, size_t *flen);
#endif /* PROTOVERSE_IO_H */
diff --git a/src/parser.c b/src/parser.c
@@ -0,0 +1,33 @@
+
+#include "parser.h"
+#include <stdio.h>
+
+
+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;
+}
+
+int consume_byte(struct cursor *cursor, unsigned char match)
+{
+ return consume_bytes(cursor, &match, 1);
+}
+
+int consume_u32(struct cursor *cursor, unsigned int match)
+{
+ return consume_bytes(cursor, (unsigned char*)&match, sizeof(match));
+}
diff --git a/src/parser.h b/src/parser.h
@@ -0,0 +1,12 @@
+
+#ifndef CURSOR_PARSER
+#define CURSOR_PARSER
+
+#include "cursor.h"
+
+int consume_bytes(struct cursor *cur, const unsigned char *match, int len);
+int consume_byte(struct cursor *cur, const unsigned char match);
+int consume_u32(struct cursor *cur, unsigned int match);
+
+#endif /* CURSOR_PARSER */
+
diff --git a/src/protoverse.c b/src/protoverse.c
@@ -4,10 +4,12 @@
#include "describe.h"
#include "serve.h"
#include "client.h"
+#include "wasm.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mman.h>
#define streq(a, b) strcmp(a,b) == 0
@@ -70,6 +72,7 @@ static int usage(void)
printf(" parse file.space\n");
printf(" serve file.space\n");
printf(" client\n");
+ printf(" run code.wasm\n");
return 1;
}
@@ -78,12 +81,14 @@ static int usage(void)
int main(int argc, const char *argv[])
{
- const char *space;
+ const char *space, *code_file;
const char *cmd;
+ unsigned char *wasm_data;
struct parser parser;
struct protoverse_server server;
u16 root;
int ok;
+ size_t len;
if (argc < 2)
return usage();
@@ -115,6 +120,18 @@ int main(int argc, const char *argv[])
protoverse_serve(&server);
} else if (streq(cmd, "client")) {
protoverse_connect("127.0.0.1", 1988);
+ } else if (streq(cmd, "run")) {
+ if (argc != 3)
+ return usage();
+ code_file = argv[2];
+ if (!map_file(code_file, &wasm_data, &len)) {
+ perror("mmap");
+ return 1;
+ }
+ if (!run_wasm(wasm_data, len)) {
+ return 2;
+ }
+ munmap(wasm_data, len);
}
return 0;
diff --git a/src/wasm.c b/src/wasm.c
@@ -0,0 +1,490 @@
+
+#include "wasm.h"
+#include "parser.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define note_error(p, fmt, ...) note_error_(p, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+
+#define ERR_STACK_SIZE 16
+
+struct parse_error {
+ int pos;
+ char *msg;
+ struct parse_error *next;
+};
+
+struct wasm_parser {
+ struct module module;
+ struct cursor cur;
+ struct cursor mem;
+ struct parse_error *errors;
+};
+
+#ifdef DEBUG
+static void log_dbg_(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+#define log_dbg(...) log_dbg_(__VA_ARGS__)
+#else
+#define log_dbg(...)
+#endif
+
+
+static void note_error_(struct wasm_parser *p, const char *fmt, ...)
+{
+ static char buf[512];
+ struct parse_error err;
+ struct parse_error *perr, *new_err;
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ perr = NULL;
+ err.msg = (char*)p->mem.p;
+ err.pos = p->cur.p - p->cur.start;
+ err.next = NULL;
+
+ if (!push_str(&p->mem, buf)) {
+ fprintf(stderr, "arena OOM when recording parse error, ");
+ fprintf(stderr, "mem->p at %ld, remaining %ld, strlen %ld\n",
+ p->mem.p - p->mem.start,
+ p->mem.end - p->mem.p,
+ strlen(buf));
+ return;
+ }
+
+ new_err = (struct parse_error *)p->mem.p;
+
+ if (!push_data(&p->mem, (unsigned char*)&err, sizeof(err))) {
+ fprintf(stderr, "arena OOM when pushing data, ");
+ fprintf(stderr, "mem->p at %ld, remaining %ld, data size %ld\n",
+ p->mem.p - p->mem.start,
+ p->mem.end - p->mem.p,
+ sizeof(err));
+ return;
+ }
+
+ for (perr = p->errors; perr != NULL;) {
+ if (perr == NULL || perr->next == NULL)
+ break;
+ perr = perr->next;
+ }
+
+ if (p->errors == NULL) {
+ p->errors = new_err;
+ } else {
+ perr->next = new_err;
+ }
+}
+
+static void print_parse_backtrace(struct wasm_parser *p)
+{
+ struct parse_error *err;
+ for (err = p->errors; err != NULL; err = err->next) {
+ fprintf(stderr, "%08x:%s\n", err->pos, err->msg);
+ }
+}
+
+static const char *valtype_name(enum valtype valtype)
+{
+ switch (valtype) {
+ case i32: return "i32";
+ case i64: return "i64";
+ case f32: return "f32";
+ case f64: return "f64";
+ }
+
+ return "unk";
+}
+
+static void print_functype(struct functype *ft)
+{
+ int i;
+
+ for (i = 0; i < ft->params.num_valtypes; i++) {
+ printf("%s ", valtype_name(ft->params.valtypes[i]));
+ }
+ printf("-> ");
+ for (i = 0; i < ft->result.num_valtypes; i++) {
+ printf("%s ", valtype_name(ft->result.valtypes[i]));
+ }
+ printf("\n");
+}
+
+static void print_module(struct module *module)
+{
+ int i;
+ printf("%d functypes:\n", module->type_section.num_functypes);
+ for (i = 0; i < module->type_section.num_functypes; i++) {
+ print_functype(&module->type_section.functypes[i]);
+ }
+}
+
+/* I DONT NEED THIS (yet?) */
+/*
+static int leb128_write(struct cursor *write, unsigned int val)
+{
+ unsigned char byte;
+ while (1) {
+ byte = value & 0x7F;
+ value >>= 7;
+ if (value == 0) {
+ if (!push_byte(write, byte))
+ return 0;
+ return 1;
+ } else {
+ if (!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 int leb128_read(struct cursor *read, unsigned int *val)
+{
+ unsigned char p[5];
+ unsigned char *start;
+
+ start = read->p;
+ *val = 0;
+
+ if (pull_byte(read, &p[0]) && (p[0] & 0x80) == 0) {
+ *val = LEB128_1(unsigned int);
+ 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;
+ }
+ }
+
+ /* reset if we're missing */
+ read->p = start;
+ return 0;
+}
+
+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, unsigned char *valtype)
+{
+ unsigned char *start;
+
+ start = p->cur.p;
+
+ if (!pull_byte(&p->cur, valtype)) {
+ note_error(p, "valtype tag oob");
+ return 0;
+ }
+
+ switch ((enum valtype)*valtype) {
+ case i32:
+ case i64:
+ case f32:
+ case f64:
+ return 1;
+ }
+
+ p->cur.p = start;
+ note_error(p, "%c is not a valid valtype tag", *valtype);
+ return 0;
+}
+
+static int parse_result_type(struct wasm_parser *p, struct resulttype *rt)
+{
+ int i, elems;
+ unsigned char valtype;
+ unsigned char *start;
+
+ rt->num_valtypes = 0;
+ rt->valtypes = 0;
+ start = p->mem.p;
+
+ if (!leb128_read(&p->cur, (unsigned int*)&elems)) {
+ note_error(p, "vec len");
+ return 0;
+ }
+
+ for (i = 0; i < elems; i++)
+ {
+ if (!parse_valtype(p, &valtype)) {
+ note_error(p, "valtype #%d", i);
+ p->mem.p = start;
+ return 0;
+ }
+
+ if (!push_byte(&p->mem, valtype)) {
+ note_error(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 (!consume_byte(&p->cur, FUNC_TYPE_TAG)) {
+ note_error(p, "type tag");
+ return 0;
+ }
+
+ if (!parse_result_type(p, &func->params)) {
+ note_error(p, "params");
+ return 0;
+ }
+
+ if (!parse_result_type(p, &func->result)) {
+ note_error(p, "result");
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/* type section is just a vector of function types */
+static int parse_type_section(struct wasm_parser *p, struct typesec *typesec)
+{
+ unsigned int elems, i;
+ struct functype *functypes;
+
+ typesec->num_functypes = 0;
+ typesec->functypes = NULL;
+
+ if (!leb128_read(&p->cur, &elems)) {
+ fprintf(stderr, "what\n");
+ note_error(p, "functypes vec len");
+ return 0;
+ }
+
+ functypes = cursor_alloc(&p->mem, elems * sizeof(struct functype));
+
+ if (!functypes) {
+ /* can't use note_error because we're oom */
+ fprintf(stderr, "could not allocate memory for type section\n");
+ return 0;
+ }
+
+ for (i = 0; i < elems; i++) {
+ if (!parse_func_type(p, &functypes[i])) {
+ note_error(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, unsigned int size)
+{
+ (void)size;
+ switch (tag) {
+ case section_custom:
+ note_error(p, "section_custom parse not implemented");
+ return 0;
+ case section_type:
+ if (!parse_type_section(p, &p->module.type_section)) {
+ fprintf(stderr,"what\n");
+ note_error(p, "type section");
+ return 0;
+ }
+ return 1;
+ case section_import:
+ note_error(p, "section_import parse not implemented");
+ return 0;
+ case section_function:
+ note_error(p, "section_function parse not implemented");
+ return 0;
+ case section_table:
+ note_error(p, "section_table parse not implemented");
+ return 0;
+ case section_memory:
+ note_error(p, "section_memory parse not implemented");
+ return 0;
+ case section_global:
+ note_error(p, "section_global parse not implemented");
+ return 0;
+ case section_export:
+ note_error(p, "section_export parse not implemented");
+ return 0;
+ case section_start:
+ note_error(p, "section_start parse not implemented");
+ return 0;
+ case section_element:
+ note_error(p, "section_element parse not implemented");
+ return 0;
+ case section_code:
+ note_error(p, "section_code parse not implemented");
+ return 0;
+ case section_data:
+ note_error(p, "section_data parse not implemented");
+ return 0;
+ default:
+ note_error(p, "invalid section tag");
+ return 0;
+ }
+
+ return 1;
+}
+
+static const char *section_name(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;
+ unsigned int bytes;
+
+ if (!parse_section_tag(&p->cur, &tag)) {
+ note_error(p, "section tag");
+ return 0;
+ }
+
+ if (!leb128_read(&p->cur, &bytes)) {
+ note_error(p, "section len");
+ return 0;
+ }
+
+ if (!parse_section_by_tag(p, tag, bytes)) {
+ note_error(p, "%s (%d bytes)", section_name(tag), bytes);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int parse_wasm(struct wasm_parser *p)
+{
+ if (!consume_bytes(&p->cur, WASM_MAGIC, sizeof(WASM_MAGIC))) {
+ note_error(p, "magic");
+ goto fail;
+ }
+
+ if (!consume_u32(&p->cur, WASM_VERSION)) {
+ note_error(p, "version");
+ goto fail;
+ }
+
+ while (1) {
+ if (!parse_section(p)) {
+ note_error(p, "section");
+ goto fail;
+ }
+ }
+
+ return 1;
+
+fail:
+ printf("parse failure backtrace:\n");
+ print_parse_backtrace(p);
+ printf("\npartially parsed module:\n");
+ print_module(&p->module);
+ return 0;
+}
+
+int run_wasm(unsigned char *wasm, unsigned long len)
+{
+ struct wasm_parser p;
+
+ void *mem;
+ int ok, arena_size;
+
+ arena_size = len * 16;
+ memset(&p, 0, sizeof(p));
+ mem = malloc(arena_size);
+ assert(mem);
+
+ make_cursor(wasm, wasm + len, &p.cur);
+ make_cursor(mem, mem + arena_size, &p.mem);
+
+ ok = parse_wasm(&p);
+ free(mem);
+ return ok;
+}
diff --git a/src/wasm.h b/src/wasm.h
@@ -0,0 +1,167 @@
+
+#ifndef WASM
+#define WASM
+
+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 FUNC_TYPE_TAG 0x60
+
+struct resulttype {
+ unsigned char *valtypes; /* enum valtype */
+ int num_valtypes;
+};
+
+struct functype {
+ struct resulttype params;
+ struct resulttype result;
+};
+
+struct typesec {
+ struct functype *functypes;
+ int num_functypes;
+};
+
+struct module {
+ struct typesec type_section;
+};
+
+enum valtype {
+ i32 = 0x7F,
+ i64 = 0x7E,
+ f32 = 0x7D,
+ f64 = 0x7C,
+};
+
+enum limits {
+ limit_min = 0x00,
+ limit_min_max = 0x01,
+};
+
+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,
+ num_sections,
+};
+
+struct section {
+ enum section_tag tag;
+};
+
+enum instr {
+ /* 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,
+
+ /* variable instructions */
+ i_local_get = 0x20,
+ i_local_set = 0x21,
+ i_local_tee = 0x22,
+ i_global_get = 0x23,
+ i_global_set = 0x24,
+
+ /* 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,
+ /* TODO: more instrs */
+};
+
+int run_wasm(unsigned char *wasm, unsigned long len);
+
+#endif /* WASM */
diff --git a/wasm/func.wat b/wasm/func.wat
@@ -0,0 +1,5 @@
+(module
+ (func $add (param $a i32) (param $b i32) (result i32)
+ local.get $a
+ local.get $b
+ i32.add))
diff --git a/wasm/hello-2.wat b/wasm/hello-2.wat
@@ -0,0 +1,6 @@
+(module
+ (func $add (param $lhs i32) (param $rhs i32) (result i32)
+ local.get $lhs
+ local.get $rhs
+ i32.add)
+ (export "add" (func $add)))
diff --git a/wasm/hello.wat b/wasm/hello.wat
@@ -0,0 +1 @@
+(module)
diff --git a/wasm/module.wat b/wasm/module.wat
@@ -0,0 +1 @@
+(module)