Commit 1263346774

Jacob Young <jacobly0@users.noreply.github.com>
2022-11-28 01:55:12
use zig-wasm2c for bootstrapping
1 parent a63305b
stage1/FuncGen.h
@@ -0,0 +1,164 @@
+#ifndef FUNC_GEN_H
+#define FUNC_GEN_H
+
+#include "panic.h"
+#include "wasm.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+struct Block {
+    uint32_t type;
+    uint32_t label;
+    uint32_t stack_i;
+};
+
+struct FuncGen {
+    int8_t *type;
+    uint32_t *stack;
+    struct Block *block;
+    uint32_t type_i;
+    uint32_t stack_i;
+    uint32_t block_i;
+    uint32_t type_len;
+    uint32_t stack_len;
+    uint32_t block_len;
+};
+
+static void FuncGen_init(struct FuncGen *self) {
+    memset(self, 0, sizeof(struct FuncGen));
+}
+
+static void FuncGen_reset(struct FuncGen *self) {
+    self->type_i = 0;
+    self->stack_i = 0;
+    self->block_i = 0;
+}
+
+static void FuncGen_free(struct FuncGen *self) {
+    free(self->block);
+    free(self->stack);
+    free(self->type);
+}
+
+static void FuncGen_outdent(struct FuncGen *self, FILE *out) {
+    for (uint32_t i = 0; i < self->block_i; i += 1) fputs("    ", out);
+}
+
+static void FuncGen_indent(struct FuncGen *self, FILE *out) {
+    FuncGen_outdent(self, out);
+    fputs("    ", out);
+}
+
+static void FuncGen_cont(struct FuncGen *self, FILE *out) {
+    FuncGen_indent(self, out);
+    fputs("    ", out);
+}
+
+static uint32_t FuncGen_localAlloc(struct FuncGen *self, int8_t type) {
+    if (self->type_i == self->type_len) {
+        self->type_len += 10;
+        self->type_len *= 2;
+        self->type = realloc(self->type, sizeof(int8_t) * self->type_len);
+        if (self->type == NULL) panic("out of memory");
+    }
+    uint32_t local_i = self->type_i;
+    self->type[local_i] = type;
+    self->type_i += 1;
+    return local_i;
+}
+
+static uint32_t FuncGen_localDeclare(struct FuncGen *self, FILE *out, enum WasmValType val_type) {
+    uint32_t local_i = FuncGen_localAlloc(self, (int8_t)val_type);
+    fprintf(out, "%s l%" PRIu32, WasmValType_toC(val_type), local_i);
+    return local_i;
+}
+
+static enum WasmValType FuncGen_localType(const struct FuncGen *self, uint32_t local_idx) {
+    return self->type[local_idx];
+}
+
+static void FuncGen_stackPush(struct FuncGen *self, FILE *out, enum WasmValType val_type) {
+    if (self->stack_i == self->stack_len) {
+        self->stack_len += 10;
+        self->stack_len *= 2;
+        self->stack = realloc(self->stack, sizeof(uint32_t) * self->stack_len);
+        if (self->stack == NULL) panic("out of memory");
+    }
+    FuncGen_indent(self, out);
+    fputs("const ", out);
+    self->stack[self->stack_i] = FuncGen_localDeclare(self, out, val_type);
+    self->stack_i += 1;
+    fputs(" = ", out);
+}
+
+static uint32_t FuncGen_stackAt(const struct FuncGen *self, uint32_t stack_idx) {
+    return self->stack[self->stack_i - 1 - stack_idx];
+}
+
+static uint32_t FuncGen_stackPop(struct FuncGen *self) {
+    self->stack_i -= 1;
+    return self->stack[self->stack_i];
+}
+
+static void FuncGen_label(struct FuncGen *self, FILE *out, uint32_t label) {
+    FuncGen_indent(self, out);
+    fprintf(out, "goto l%" PRIu32 ";\n", label);
+    FuncGen_outdent(self, out);
+    fprintf(out, "l%" PRIu32 ":;\n", label);
+}
+
+static void FuncGen_blockBegin(struct FuncGen *self, FILE *out, enum WasmOpcode kind, int64_t type) {
+    if (self->block_i == self->block_len) {
+        self->block_len += 10;
+        self->block_len *= 2;
+        self->block = realloc(self->block, sizeof(struct Block) * self->block_len);
+        if (self->block == NULL) panic("out of memory");
+    }
+    uint32_t label = FuncGen_localAlloc(self, type < 0 ? ~(int8_t)kind : (int8_t)kind);
+    FuncGen_indent(self, out);
+    if (kind == WasmOpcode_if) fprintf(out, "if (l%" PRIu32 ") ", FuncGen_stackPop(self));
+    fputs("{\n", out);
+    self->block[self->block_i].type = type < 0 ? ~type : type;
+    self->block[self->block_i].label = label;
+    self->block[self->block_i].stack_i = self->stack_i;
+    self->block_i += 1;
+    if (kind == WasmOpcode_loop) FuncGen_label(self, out, label);
+}
+
+static enum WasmOpcode FuncGen_blockKind(const struct FuncGen *self, uint32_t label_idx) {
+    int8_t kind = self->type[self->block[self->block_i - 1 - label_idx].label];
+    return (enum WasmOpcode)(kind < 0 ? ~kind : kind);
+}
+
+static int64_t FuncGen_blockType(const struct FuncGen *self, uint32_t label_idx) {
+    struct Block *block = &self->block[self->block_i - 1 - label_idx];
+    return self->type[block->label] < 0 ? ~(int64_t)block->type : (int64_t)block->type;
+}
+
+static uint32_t FuncGen_blockLabel(const struct FuncGen *self, uint32_t label_idx) {
+    return self->block[self->block_i - 1 - label_idx].label;
+}
+
+static void FuncGen_blockEnd(struct FuncGen *self, FILE *out) {
+    enum WasmOpcode kind = FuncGen_blockKind(self, 0);
+    uint32_t label = FuncGen_blockLabel(self, 0);
+    if (kind != WasmOpcode_loop) FuncGen_label(self, out, label);
+    self->block_i -= 1;
+    FuncGen_indent(self, out);
+    fputs("}\n", out);
+    if (self->stack_i != self->block[self->block_i].stack_i) {
+        FuncGen_indent(self, out);
+        fprintf(out, "// stack mismatch %u != %u\n", self->stack_i, self->block[self->block_i].stack_i);
+    }
+    self->stack_i = self->block[self->block_i].stack_i;
+}
+
+static bool FuncGen_done(const struct FuncGen *self) {
+    return self->block_i == 0;
+}
+
+#endif /* FUNC_GEN_H */
stage1/InputStream.h
@@ -0,0 +1,241 @@
+#ifndef INPUT_STREAM_H
+#define INPUT_STREAM_H
+
+#include "panic.h"
+#include "wasm.h"
+
+#include <zstd.h>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct InputStream {
+    FILE *stream;
+    ZSTD_DStream *ds;
+    ZSTD_outBuffer out;
+    ZSTD_inBuffer in;
+    size_t pos;
+};
+
+static void InputStream_open(struct InputStream *self, const char *path) {
+    self->stream = fopen(path, "rb");
+    if (self->stream == NULL) panic("unable to open input file");
+    self->ds = ZSTD_createDStream();
+    if (self->ds == NULL) panic("unable to create zstd context");
+    size_t in_size = ZSTD_initDStream(self->ds);
+    if (ZSTD_isError(in_size)) panic(ZSTD_getErrorName(in_size));
+    self->out.size = ZSTD_DStreamOutSize();
+    self->out.dst = malloc(self->out.size + ZSTD_DStreamInSize());
+    if (self->out.dst == NULL) panic("unable to allocate input buffers");
+    self->out.pos = 0;
+    self->in.src = (const char *)self->out.dst + self->out.size;
+    self->in.size = fread((void *)self->in.src, 1, in_size, self->stream);
+    self->in.pos = 0;
+    self->pos = 0;
+}
+
+static void InputStream_close(struct InputStream *self) {
+    free(self->out.dst);
+    ZSTD_freeDStream(self->ds);
+    fclose(self->stream);
+}
+
+static bool InputStream_atEnd(struct InputStream *self) {
+    while (self->pos >= self->out.pos) {
+        self->out.pos = 0;
+        self->pos = 0;
+        size_t in_size = ZSTD_decompressStream(self->ds, &self->out, &self->in);
+        if (ZSTD_isError(in_size)) panic(ZSTD_getErrorName(in_size));
+        if (self->in.pos >= self->in.size) {
+            size_t max_in_size = ZSTD_DStreamInSize();
+            if (in_size > max_in_size) in_size = max_in_size;
+            self->in.size = fread((void *)self->in.src, 1, in_size, self->stream);
+            self->in.pos = 0;
+            if (self->in.pos >= self->in.size) return true;
+        }
+    }
+    return false;
+}
+
+static uint8_t InputStream_readByte(struct InputStream *self) {
+    if (InputStream_atEnd(self)) panic("unexpected end of input stream");
+    uint8_t value = ((uint8_t *)self->out.dst)[self->pos];
+    self->pos += 1;
+    return value;
+}
+
+static uint32_t InputStream_readLittle_u32(struct InputStream *self) {
+    uint32_t value = 0;
+    value |= (uint32_t)InputStream_readByte(self) << 0;
+    value |= (uint32_t)InputStream_readByte(self) << 8;
+    value |= (uint32_t)InputStream_readByte(self) << 16;
+    value |= (uint32_t)InputStream_readByte(self) << 24;
+    return value;
+}
+
+static uint64_t InputStream_readLittle_u64(struct InputStream *self) {
+    uint64_t value = 0;
+    value |= (uint64_t)InputStream_readByte(self) << 0;
+    value |= (uint64_t)InputStream_readByte(self) << 8;
+    value |= (uint64_t)InputStream_readByte(self) << 16;
+    value |= (uint64_t)InputStream_readByte(self) << 24;
+    value |= (uint64_t)InputStream_readByte(self) << 32;
+    value |= (uint64_t)InputStream_readByte(self) << 40;
+    value |= (uint64_t)InputStream_readByte(self) << 48;
+    value |= (uint64_t)InputStream_readByte(self) << 56;
+    return value;
+}
+
+static float InputStream_readLittle_f32(struct InputStream *self) {
+    uint32_t value = InputStream_readLittle_u32(self);
+    float result;
+    memcpy(&result, &value, sizeof(result));
+    return result;
+}
+
+static double InputStream_readLittle_f64(struct InputStream *self) {
+    uint64_t value = InputStream_readLittle_u64(self);
+    double result;
+    memcpy(&result, &value, sizeof(result));
+    return result;
+}
+
+static uint32_t InputStream_readLeb128_u32(struct InputStream *self) {
+    uint32_t value = 0;
+    uint8_t shift = 0;
+    uint8_t byte;
+    do {
+        byte = InputStream_readByte(self);
+        assert(shift < 32);
+        value |= (uint32_t)(byte & 0x7F) << shift;
+        shift += 7;
+    } while (byte & 0x80);
+    return value;
+}
+
+static int32_t InputStream_readLeb128_i32(struct InputStream *self) {
+    uint32_t value = 0;
+    uint8_t shift = 0;
+    uint8_t byte;
+    do {
+        byte = InputStream_readByte(self);
+        assert(shift < 64);
+        value |= (uint32_t)(byte & 0x7F) << shift;
+        shift += 7;
+    } while (byte & 0x80);
+    if (shift < 32) {
+        uint32_t mask = -((uint32_t)1 << shift);
+        if (byte & 0x40) value |= mask; else value &= ~mask;
+    }
+    return (int32_t)value;
+}
+
+static int64_t InputStream_readLeb128_u64(struct InputStream *self) {
+    uint64_t value = 0;
+    uint8_t shift = 0;
+    uint8_t byte;
+    do {
+        byte = InputStream_readByte(self);
+        assert(shift < 64);
+        value |= (uint64_t)(byte & 0x7F) << shift;
+        shift += 7;
+    } while (byte & 0x80);
+    return value;
+}
+
+static int64_t InputStream_readLeb128_i64(struct InputStream *self) {
+    uint64_t value = 0;
+    uint8_t shift = 0;
+    uint8_t byte;
+    do {
+        byte = InputStream_readByte(self);
+        assert(shift < 64);
+        value |= (uint64_t)(byte & 0x7F) << shift;
+        shift += 7;
+    } while (byte & 0x80);
+    if (shift < 64) {
+        uint64_t mask = -((uint64_t)1 << shift);
+        if (byte & 0x40) value |= mask; else value &= ~mask;
+    }
+    return (int64_t)value;
+}
+
+static char *InputStream_readName(struct InputStream *self) {
+    uint32_t len = InputStream_readLeb128_u32(self);
+    char *name = malloc(len + 1);
+    if (name == NULL) panic("out of memory");
+    for (uint32_t i = 0; i < len; ) {
+        if (InputStream_atEnd(self)) panic("unexpected end of input stream");
+        size_t remaining = self->out.pos - self->pos;
+        if (remaining > len - i) remaining = len - i;
+        memcpy(&name[i], &((char *)self->out.dst)[self->pos], remaining);
+        i += remaining;
+        self->pos += remaining;
+    }
+    name[len] = '\0';
+    return name;
+}
+
+static void InputStream_skipBytes(struct InputStream *self, size_t len) {
+    for (size_t i = 0; i < len; ) {
+        if (InputStream_atEnd(self)) panic("unexpected end of input stream");
+        size_t remaining = self->out.pos - self->pos;
+        if (remaining > len - i) remaining = len - i;
+        i += remaining;
+        self->pos += remaining;
+    }
+}
+
+static uint32_t InputStream_skipToSection(struct InputStream *self, uint8_t expected_id) {
+    while (true) {
+        uint8_t id = InputStream_readByte(self);
+        uint32_t size = InputStream_readLeb128_u32(self);
+        if (id == expected_id) return size;
+        InputStream_skipBytes(self, size);
+    }
+}
+
+struct ResultType {
+    uint32_t len;
+    int8_t types[1];
+};
+static struct ResultType *InputStream_readResultType(struct InputStream *self) {
+    uint32_t len = InputStream_readLeb128_u32(self);
+    struct ResultType *result_type = malloc(offsetof(struct ResultType, types) + sizeof(int8_t) * len);
+    if (result_type == NULL) panic("out of memory");
+    result_type->len = len;
+    for (uint32_t i = 0; i < len; i += 1) {
+        int64_t val_type = InputStream_readLeb128_i64(self);
+        switch (val_type) {
+            case WasmValType_i32: case WasmValType_i64:
+            case WasmValType_f32: case WasmValType_f64:
+                break;
+
+            default: panic("unsupported valtype");
+        }
+        result_type->types[i] = val_type;
+    }
+    return result_type;
+}
+
+struct Limits {
+    uint32_t min;
+    uint32_t max;
+};
+static struct Limits InputStream_readLimits(struct InputStream *self) {
+    struct Limits limits;
+    uint8_t kind = InputStream_readByte(self);
+    limits.min = InputStream_readLeb128_u32(self);
+    switch (kind) {
+        case 0x00: limits.max = UINT32_MAX; break;
+        case 0x01: limits.max = InputStream_readLeb128_u32(self); break;
+        default: panic("unsupported limit kind");
+    }
+    return limits;
+}
+
+#endif /* INPUT_STREAM_H */
stage1/panic.h
@@ -0,0 +1,12 @@
+#ifndef PANIC_H
+#define PANIC_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static void panic(const char *reason) {
+    fprintf(stderr, "%s\n", reason);
+    abort();
+}
+
+#endif /* PANIC_H */
stage1/wasi.c
@@ -0,0 +1,948 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "panic.h"
+
+#define LOG_TRACE 0
+
+enum wasi_errno {
+    wasi_errno_success        = 0,
+    wasi_errno_2big           = 1,
+    wasi_errno_acces          = 2,
+    wasi_errno_addrinuse      = 3,
+    wasi_errno_addrnotavail   = 4,
+    wasi_errno_afnosupport    = 5,
+    wasi_errno_again          = 6,
+    wasi_errno_already        = 7,
+    wasi_errno_badf           = 8,
+    wasi_errno_badmsg         = 9,
+    wasi_errno_busy           = 10,
+    wasi_errno_canceled       = 11,
+    wasi_errno_child          = 12,
+    wasi_errno_connaborted    = 13,
+    wasi_errno_connrefused    = 14,
+    wasi_errno_connreset      = 15,
+    wasi_errno_deadlk         = 16,
+    wasi_errno_destaddrreq    = 17,
+    wasi_errno_dom            = 18,
+    wasi_errno_dquot          = 19,
+    wasi_errno_exist          = 20,
+    wasi_errno_fault          = 21,
+    wasi_errno_fbig           = 22,
+    wasi_errno_hostunreach    = 23,
+    wasi_errno_idrm           = 24,
+    wasi_errno_ilseq          = 25,
+    wasi_errno_inprogress     = 26,
+    wasi_errno_intr           = 27,
+    wasi_errno_inval          = 28,
+    wasi_errno_io             = 29,
+    wasi_errno_isconn         = 30,
+    wasi_errno_isdir          = 31,
+    wasi_errno_loop           = 32,
+    wasi_errno_mfile          = 33,
+    wasi_errno_mlink          = 34,
+    wasi_errno_msgsize        = 35,
+    wasi_errno_multihop       = 36,
+    wasi_errno_nametoolong    = 37,
+    wasi_errno_netdown        = 38,
+    wasi_errno_netreset       = 39,
+    wasi_errno_netunreach     = 40,
+    wasi_errno_nfile          = 41,
+    wasi_errno_nobufs         = 42,
+    wasi_errno_nodev          = 43,
+    wasi_errno_noent          = 44,
+    wasi_errno_noexec         = 45,
+    wasi_errno_nolck          = 46,
+    wasi_errno_nolink         = 47,
+    wasi_errno_nomem          = 48,
+    wasi_errno_nomsg          = 49,
+    wasi_errno_noprotoopt     = 50,
+    wasi_errno_nospc          = 51,
+    wasi_errno_nosys          = 52,
+    wasi_errno_notconn        = 53,
+    wasi_errno_notdir         = 54,
+    wasi_errno_notempty       = 55,
+    wasi_errno_notrecoverable = 56,
+    wasi_errno_notsock        = 57,
+    wasi_errno_opnotsupp      = 58,
+    wasi_errno_notty          = 59,
+    wasi_errno_nxio           = 60,
+    wasi_errno_overflow       = 61,
+    wasi_errno_ownerdead      = 62,
+    wasi_errno_perm           = 63,
+    wasi_errno_pipe           = 64,
+    wasi_errno_proto          = 65,
+    wasi_errno_protonosupport = 66,
+    wasi_errno_prototype      = 67,
+    wasi_errno_range          = 68,
+    wasi_errno_rofs           = 69,
+    wasi_errno_spipe          = 70,
+    wasi_errno_srch           = 71,
+    wasi_errno_stale          = 72,
+    wasi_errno_timedout       = 73,
+    wasi_errno_txtbsy         = 74,
+    wasi_errno_xdev           = 75,
+    wasi_errno_notcapable     = 76,
+};
+
+enum wasi_oflags {
+    wasi_oflags_creat     = 1 << 0,
+    wasi_oflags_directory = 1 << 1,
+    wasi_oflags_excl      = 1 << 2,
+    wasi_oflags_trunc     = 1 << 3,
+};
+
+enum wasi_rights {
+    wasi_rights_fd_datasync             = 1ull <<  0,
+    wasi_rights_fd_read                 = 1ull <<  1,
+    wasi_rights_fd_seek                 = 1ull <<  2,
+    wasi_rights_fd_fdstat_set_flags     = 1ull <<  3,
+    wasi_rights_fd_sync                 = 1ull <<  4,
+    wasi_rights_fd_tell                 = 1ull <<  5,
+    wasi_rights_fd_write                = 1ull <<  6,
+    wasi_rights_fd_advise               = 1ull <<  7,
+    wasi_rights_fd_allocate             = 1ull <<  8,
+    wasi_rights_path_create_directory   = 1ull <<  9,
+    wasi_rights_path_create_file        = 1ull << 10,
+    wasi_rights_path_link_source        = 1ull << 11,
+    wasi_rights_path_link_target        = 1ull << 12,
+    wasi_rights_path_open               = 1ull << 13,
+    wasi_rights_fd_readdir              = 1ull << 14,
+    wasi_rights_path_readlink           = 1ull << 15,
+    wasi_rights_path_rename_source      = 1ull << 16,
+    wasi_rights_path_rename_target      = 1ull << 17,
+    wasi_rights_path_filestat_get       = 1ull << 18,
+    wasi_rights_path_filestat_set_size  = 1ull << 19,
+    wasi_rights_path_filestat_set_times = 1ull << 20,
+    wasi_rights_fd_filestat_get         = 1ull << 21,
+    wasi_rights_fd_filestat_set_size    = 1ull << 22,
+    wasi_rights_fd_filestat_set_times   = 1ull << 23,
+    wasi_rights_path_symlink            = 1ull << 24,
+    wasi_rights_path_remove_directory   = 1ull << 25,
+    wasi_rights_path_unlink_file        = 1ull << 26,
+    wasi_rights_poll_fd_readwrite       = 1ull << 27,
+    wasi_rights_sock_shutdown           = 1ull << 28,
+    wasi_rights_sock_accept             = 1ull << 29,
+};
+
+enum wasi_clockid {
+    wasi_clockid_realtime           = 0,
+    wasi_clockid_monotonic          = 1,
+    wasi_clockid_process_cputime_id = 2,
+    wasi_clockid_thread_cputime_id  = 3,
+};
+
+enum wasi_filetype {
+    wasi_filetype_unknown          = 0,
+    wasi_filetype_block_device     = 1,
+    wasi_filetype_character_device = 2,
+    wasi_filetype_directory        = 3,
+    wasi_filetype_regular_file     = 4,
+    wasi_filetype_socket_dgram     = 5,
+    wasi_filetype_socket_stream    = 6,
+    wasi_filetype_symbolic_link    = 7,
+};
+
+enum wasi_fdflags {
+    wasi_fdflags_append   = 1 << 0,
+    wasi_fdflags_dsync    = 1 << 1,
+    wasi_fdflags_nonblock = 1 << 2,
+    wasi_fdflags_rsync    = 1 << 3,
+    wasi_fdflags_sync     = 1 << 4,
+};
+
+struct wasi_filestat {
+    uint64_t dev;
+    uint64_t ino;
+    uint64_t filetype;
+    uint64_t nlink;
+    uint64_t size;
+    uint64_t atim;
+    uint64_t mtim;
+    uint64_t ctim;
+};
+
+struct wasi_fdstat {
+    uint16_t fs_filetype;
+    uint16_t fs_flags;
+    uint32_t padding;
+    uint64_t fs_rights_inheriting;
+};
+
+struct wasi_ciovec {
+    uint32_t ptr;
+    uint32_t len;
+};
+
+extern uint8_t **const wasm_memory;
+extern void wasm__start(void);
+
+static int global_argc;
+static char **global_argv;
+
+static uint32_t de_len;
+struct DirEntry {
+    enum wasi_filetype filetype;
+    time_t atim;
+    time_t mtim;
+    time_t ctim;
+    char *guest_path;
+    char *host_path;
+} *des;
+
+static uint32_t fd_len;
+static struct FileDescriptor {
+    uint32_t de;
+    FILE *stream;
+} *fds;
+
+static void *dupe(const void *data, size_t len) {
+    void *copy = malloc(len);
+    if (copy == NULL) panic("out of memory");
+    memcpy(copy, data, len);
+    return copy;
+}
+
+int main(int argc, char **argv) {
+    if (argc < 2) {
+        fprintf(stderr, "usage: %s <zig-lib-path> <args...>\n", argv[0]);
+        return 1;
+    }
+
+    global_argc = argc;
+    global_argv = argv;
+
+    time_t now = time(NULL);
+    srand((unsigned)now);
+
+    de_len = 4;
+    des = calloc(de_len, sizeof(struct DirEntry));
+    if (des == NULL) panic("out of memory");
+
+    des[0].filetype = wasi_filetype_character_device;
+
+    des[1].filetype = wasi_filetype_directory;
+    des[1].guest_path = dupe(".", sizeof("."));
+    des[1].host_path = dupe(".", sizeof("."));
+
+    des[2].filetype = wasi_filetype_directory;
+    des[2].guest_path = dupe("/cache", sizeof("/cache"));
+    des[2].atim = now;
+    des[2].mtim = now;
+    des[2].ctim = now;
+
+    des[3].filetype = wasi_filetype_directory;
+    des[3].guest_path = dupe("/lib", sizeof("/lib"));
+    des[3].host_path = dupe(argv[1], strlen(argv[1]));
+
+    fd_len = 6;
+    fds = calloc(sizeof(struct FileDescriptor), fd_len);
+    if (fds == NULL) panic("out of memory");
+    fds[0].stream = stdin;
+    fds[1].stream = stdout;
+    fds[2].stream = stderr;
+    fds[3].de = 1;
+    fds[4].de = 2;
+    fds[5].de = 3;
+
+    wasm__start();
+}
+
+static enum wasi_errno DirEntry_create(uint32_t dir_fd, const char *path, uint32_t path_len, enum wasi_filetype filetype, time_t tim, uint32_t *res_de) {
+    if (path[0] != '/') {
+        if (dir_fd >= fd_len || fds[dir_fd].de >= de_len) return wasi_errno_badf;
+        if (des[fds[dir_fd].de].filetype != wasi_filetype_directory) return wasi_errno_notdir;
+    }
+
+    struct DirEntry *new_des = realloc(des, (de_len + 1) * sizeof(struct DirEntry));
+    if (new_des == NULL) return wasi_errno_nomem;
+    des = new_des;
+
+    struct DirEntry *de = &des[de_len];
+    de->filetype = filetype;
+    de->atim = tim;
+    de->mtim = tim;
+    de->ctim = tim;
+    if (path[0] == '/') {
+        de->guest_path = malloc(path_len + 1);
+        if (de->guest_path == NULL) return wasi_errno_nomem;
+        memcpy(&de->guest_path[0], path, path_len);
+        de->guest_path[path_len] = '\0';
+
+        de->host_path = malloc(path_len + 1);
+        if (de->host_path == NULL) return wasi_errno_nomem;
+        memcpy(&de->host_path[0], path, path_len);
+        de->host_path[path_len] = '\0';
+    } else {
+        const struct DirEntry *dir_de = &des[fds[dir_fd].de];
+        if (dir_de->guest_path != NULL) {
+            size_t dir_guest_path_len = strlen(dir_de->guest_path);
+            de->guest_path = malloc(dir_guest_path_len + 1 + path_len + 1);
+            if (de->guest_path == NULL) return wasi_errno_nomem;
+            memcpy(&de->guest_path[0], dir_de->guest_path, dir_guest_path_len);
+            de->guest_path[dir_guest_path_len] = '/';
+            memcpy(&de->guest_path[dir_guest_path_len + 1], path, path_len);
+            de->guest_path[dir_guest_path_len + 1 + path_len] = '\0';
+        } else de->guest_path = NULL;
+
+        if (dir_de->host_path != NULL) {
+            size_t dir_host_path_len = strlen(dir_de->host_path);
+            de->host_path = malloc(dir_host_path_len + 1 + path_len + 1);
+            if (de->host_path == NULL) { free(de->guest_path); return wasi_errno_nomem; }
+            memcpy(&de->host_path[0], dir_de->host_path, dir_host_path_len);
+            de->host_path[dir_host_path_len] = '/';
+            memcpy(&de->host_path[dir_host_path_len + 1], path, path_len);
+            de->host_path[dir_host_path_len + 1 + path_len] = '\0';
+        } else de->host_path = NULL;
+    }
+
+    if (res_de != NULL) *res_de = de_len;
+    de_len += 1;
+    return wasi_errno_success;
+}
+
+static enum wasi_errno DirEntry_lookup(uint32_t dir_fd, uint32_t flags, const char *path, uint32_t path_len, uint32_t *res_de) {
+    (void)flags;
+    if (path[0] == '/') {
+        for (uint32_t de = 0; de < de_len; de += 1) {
+            if (des[de].guest_path == NULL) continue;
+            if (memcmp(&des[de].guest_path[0], path, path_len) != 0) continue;
+            if (des[de].guest_path[path_len] != '\0') continue;
+            if (res_de != NULL) *res_de = de;
+            return wasi_errno_success;
+        }
+    } else {
+        if (dir_fd >= fd_len || fds[dir_fd].de >= de_len) return wasi_errno_badf;
+        const struct DirEntry *dir_de = &des[fds[dir_fd].de];
+        if (dir_de->filetype != wasi_filetype_directory) return wasi_errno_notdir;
+
+        size_t dir_guest_path_len = strlen(dir_de->guest_path);
+        for (uint32_t de = 0; de < de_len; de += 1) {
+            if (des[de].guest_path == NULL) continue;
+            if (memcmp(&des[de].guest_path[0], dir_de->guest_path, dir_guest_path_len) != 0) continue;
+            if (des[de].guest_path[dir_guest_path_len] != '/') continue;
+            if (memcmp(&des[de].guest_path[dir_guest_path_len + 1], path, path_len) != 0) continue;
+            if (des[de].guest_path[dir_guest_path_len + 1 + path_len] != '\0') continue;
+            if (res_de != NULL) *res_de = de;
+            return wasi_errno_success;
+        }
+    }
+    return wasi_errno_noent;
+}
+
+static void DirEntry_filestat(uint32_t de, struct wasi_filestat *res_filestat) {
+    res_filestat->dev = 0;
+    res_filestat->ino = de;
+    res_filestat->filetype = des[de].filetype;
+    res_filestat->nlink = 1;
+    res_filestat->size = 0;
+    res_filestat->atim = des[de].atim * UINT64_C(1000000000);
+    res_filestat->mtim = des[de].mtim * UINT64_C(1000000000);
+    res_filestat->ctim = des[de].ctim * UINT64_C(1000000000);
+}
+
+static void DirEntry_unlink(uint32_t de) {
+    free(des[de].guest_path);
+    des[de].guest_path = NULL;
+    free(des[de].host_path);
+    des[de].host_path = NULL;
+}
+
+uint32_t wasi_snapshot_preview1_args_sizes_get(uint32_t argv_size, uint32_t argv_buf_size) {
+    uint8_t *const m = *wasm_memory;
+    uint32_t *argv_size_ptr = (uint32_t *)&m[argv_size];
+    uint32_t *argv_buf_size_ptr = (uint32_t *)&m[argv_buf_size];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_args_sizes_get()\n");
+#endif
+
+    int c_argc = global_argc;
+    char **c_argv = global_argv;
+    uint32_t size = 0;
+    for (int i = 0; i < c_argc; i += 1) {
+        if (i == 1) continue;
+        size += strlen(c_argv[i]) + 1;
+    }
+    *argv_size_ptr = c_argc - 1;
+    *argv_buf_size_ptr = size;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_args_get(uint32_t argv, uint32_t argv_buf) {
+    uint8_t *const m = *wasm_memory;
+    uint32_t *argv_ptr = (uint32_t *)&m[argv];
+    char *argv_buf_ptr = (char *)&m[argv_buf];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_args_get()\n");
+#endif
+
+    int c_argc = global_argc;
+    char **c_argv = global_argv;
+    uint32_t dst_i = 0;
+    uint32_t argv_buf_i = 0;
+    for (int src_i = 0; src_i < c_argc; src_i += 1) {
+        if (src_i == 1) continue;
+        argv_ptr[dst_i] = argv_buf + argv_buf_i;
+        dst_i += 1;
+        strcpy(&argv_buf_ptr[argv_buf_i], c_argv[src_i]);
+        argv_buf_i += strlen(c_argv[src_i]) + 1;
+    }
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_prestat_get(uint32_t fd, uint32_t res_prestat) {
+    uint8_t *const m = *wasm_memory;
+    uint32_t *res_prestat_ptr = (uint32_t *)&m[res_prestat];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_prestat_get(%u)\n", fd);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+
+    res_prestat_ptr[0] = 0;
+    res_prestat_ptr[1] = strlen(des[fds[fd].de].guest_path);
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_prestat_dir_name(uint32_t fd, uint32_t path, uint32_t path_len) {
+    uint8_t *const m = *wasm_memory;
+    char *path_ptr = (char *)&m[path];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_prestat_dir_name(%u, \"%.*s\")\n", fd, (int)path_len, path_ptr);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    strncpy(path_ptr, des[fds[fd].de].guest_path, path_len);
+    return wasi_errno_success;
+}
+
+void wasi_snapshot_preview1_proc_exit(uint32_t rval) {
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_proc_exit(%u)\n", rval);
+#endif
+
+    exit(rval);
+}
+
+uint32_t wasi_snapshot_preview1_fd_close(uint32_t fd) {
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_close(%u)\n", fd);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    if (fds[fd].stream != NULL) fclose(fds[fd].stream);
+
+    fds[fd].de = ~0;
+    fds[fd].stream = NULL;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_path_create_directory(uint32_t fd, uint32_t path, uint32_t path_len) {
+    uint8_t *const m = *wasm_memory;
+    const char *path_ptr = (const char *)&m[path];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_path_create_directory(%u, \"%.*s\")\n", fd, (int)path_len, path_ptr);
+#endif
+
+    enum wasi_errno lookup_errno = DirEntry_lookup(fd, 0, path_ptr, path_len, NULL);
+    switch (lookup_errno) {
+        case wasi_errno_success: return wasi_errno_exist;
+        case wasi_errno_noent: break;
+        default: return lookup_errno;
+    }
+    return DirEntry_create(fd, path_ptr, path_len, wasi_filetype_directory, time(NULL), NULL);
+}
+
+uint32_t wasi_snapshot_preview1_fd_read(uint32_t fd, uint32_t iovs, uint32_t iovs_len, uint32_t res_size) {
+    uint8_t *const m = *wasm_memory;
+    struct wasi_ciovec *iovs_ptr = (struct wasi_ciovec *)&m[iovs];
+    uint32_t *res_size_ptr = (uint32_t *)&m[res_size];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_read(%u, 0x%X, %u)\n", fd, iovs, iovs_len);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    switch (des[fds[fd].de].filetype) {
+        case wasi_filetype_character_device: break;
+        case wasi_filetype_regular_file: break;
+        case wasi_filetype_directory: return wasi_errno_inval;
+        default: panic("unimplemented");
+    }
+
+    size_t size = 0;
+    for (uint32_t i = 0; i < iovs_len; i += 1) {
+        size_t read_size = 0;
+        if (fds[fd].stream != NULL)
+            read_size = fread(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream);
+        else
+            panic("unimplemented");
+        size += read_size;
+        if (read_size < iovs_ptr[i].len) break;
+    }
+
+    if (size > 0) des[fds[fd].de].atim = time(NULL);
+    *res_size_ptr = size;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_filestat_get(uint32_t fd, uint32_t res_filestat) {
+    uint8_t *const m = *wasm_memory;
+    struct wasi_filestat *res_filestat_ptr = (struct wasi_filestat *)&m[res_filestat];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_filestat_get(%u)\n", fd);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    DirEntry_filestat(fds[fd].de, res_filestat_ptr);
+    if (des[fds[fd].de].filetype != wasi_filetype_regular_file) return wasi_errno_success;
+    if (fds[fd].stream == NULL) return wasi_errno_success;
+    fpos_t pos;
+    if (fgetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+    if (fseek(fds[fd].stream, 0, SEEK_END) < 0) return wasi_errno_io;
+    long size = ftell(fds[fd].stream);
+    if (size < 0) return wasi_errno_io;
+    res_filestat_ptr->size = size;
+    if (fsetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_path_rename(uint32_t fd, uint32_t old_path, uint32_t old_path_len, uint32_t new_fd, uint32_t new_path, uint32_t new_path_len) {
+    uint8_t *const m = *wasm_memory;
+    const char *old_path_ptr = (const char *)&m[old_path];
+    const char *new_path_ptr = (const char *)&m[new_path];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_path_rename(%u, \"%.*s\", %u, \"%.*s\")\n", fd, (int)old_path_len, old_path_ptr, new_fd, (int)new_path_len, new_path_ptr);
+#endif
+
+    uint32_t old_de;
+    enum wasi_errno old_lookup_errno = DirEntry_lookup(fd, 0, old_path_ptr, old_path_len, &old_de);
+    if (old_lookup_errno != wasi_errno_success) return old_lookup_errno;
+    DirEntry_unlink(old_de);
+
+    uint32_t de;
+    enum wasi_errno new_lookup_errno = DirEntry_lookup(new_fd, 0, new_path_ptr, new_path_len, &de);
+    switch (new_lookup_errno) {
+        case wasi_errno_success: DirEntry_unlink(de); break;
+        case wasi_errno_noent: break;
+        default: return new_lookup_errno;
+    }
+
+    uint32_t new_de;
+    enum wasi_errno create_errno =
+        DirEntry_create(new_fd, new_path_ptr, new_path_len, des[old_de].filetype, 0, &new_de);
+    if (create_errno != wasi_errno_success) return create_errno;
+    des[new_de].atim = des[old_de].atim;
+    des[new_de].mtim = des[old_de].mtim;
+    des[new_de].ctim = time(NULL);
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_filestat_set_size(uint32_t fd, uint64_t size) {
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_filestat_set_size(%u, %llu)\n", fd, (unsigned long long)size);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    if (des[fds[fd].de].filetype != wasi_filetype_regular_file) return wasi_errno_inval;
+
+    if (fds[fd].stream == NULL) return wasi_errno_success;
+    fpos_t pos;
+    if (fgetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+    if (fseek(fds[fd].stream, 0, SEEK_END) < 0) return wasi_errno_io;
+    long old_size = ftell(fds[fd].stream);
+    if (old_size < 0) return wasi_errno_io;
+    if (size > (unsigned long)old_size) {
+        if (fseek(fds[fd].stream, size - 1, SEEK_SET) < 0) return wasi_errno_io;
+        fputc(0, fds[fd].stream);
+    } else if (size < (unsigned long)old_size) panic("unimplemented");
+    if (fsetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_pwrite(uint32_t fd, uint32_t iovs, uint32_t iovs_len, uint64_t offset, uint32_t res_size) {
+    uint8_t *const m = *wasm_memory;
+    struct wasi_ciovec *iovs_ptr = (struct wasi_ciovec *)&m[iovs];
+    uint32_t *res_size_ptr = (uint32_t *)&m[res_size];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_pwrite(%u, 0x%X, %u)\n", fd, iovs, iovs_len);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    switch (des[fds[fd].de].filetype) {
+        case wasi_filetype_character_device: break;
+        case wasi_filetype_regular_file: break;
+        case wasi_filetype_directory: return wasi_errno_inval;
+        default: panic("unimplemented");
+    }
+
+    fpos_t pos;
+    if (fgetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+    if (fseek(fds[fd].stream, offset, SEEK_SET) < 0) return wasi_errno_io;
+
+    size_t size = 0;
+    for (uint32_t i = 0; i < iovs_len; i += 1) {
+        size_t written_size = 0;
+        if (fds[fd].stream != NULL)
+            written_size = fwrite(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream);
+        else
+            written_size = iovs_ptr[i].len;
+        size += written_size;
+        if (written_size < iovs_ptr[i].len) break;
+    }
+
+    if (fsetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+
+    if (size > 0) {
+        time_t now = time(NULL);
+        des[fds[fd].de].atim = now;
+        des[fds[fd].de].mtim = now;
+    }
+    *res_size_ptr = size;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_random_get(uint32_t buf, uint32_t buf_len) {
+    uint8_t *const m = *wasm_memory;
+    uint8_t *buf_ptr = (uint8_t *)&m[buf];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_random_get(%u)\n", buf_len);
+#endif
+
+    for (uint32_t i = 0; i < buf_len; i += 1) buf_ptr[i] = (uint8_t)rand();
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_filestat_set_times(uint32_t fd, uint64_t atim, uint64_t mtim, uint32_t fst_flags) {
+    (void)fd;
+    (void)atim;
+    (void)mtim;
+    (void)fst_flags;
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_filestat_set_times(%u, %llu, %llu, 0x%X)\n", fd, (unsigned long long)atim, (unsigned long long)mtim, fst_flags);
+#endif
+
+    panic("unimplemented");
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_environ_sizes_get(uint32_t environ_size, uint32_t environ_buf_size) {
+    (void)environ_size;
+    (void)environ_buf_size;
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_environ_sizes_get()\n");
+#endif
+
+    panic("unimplemented");
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_environ_get(uint32_t environ, uint32_t environ_buf) {
+    (void)environ;
+    (void)environ_buf;
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_environ_get()\n");
+#endif
+
+    panic("unimplemented");
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_path_filestat_get(uint32_t fd, uint32_t flags, uint32_t path, uint32_t path_len, uint32_t res_filestat) {
+    uint8_t *const m = *wasm_memory;
+    const char *path_ptr = (const char *)&m[path];
+    struct wasi_filestat *res_filestat_ptr = (struct wasi_filestat *)&m[res_filestat];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_path_filestat_get(%u, 0x%X, \"%.*s\")\n", fd, flags, (int)path_len, path_ptr);
+#endif
+
+    uint32_t de;
+    enum wasi_errno lookup_errno = DirEntry_lookup(fd, flags, path_ptr, path_len, &de);
+    if (lookup_errno != wasi_errno_success) return lookup_errno;
+    DirEntry_filestat(de, res_filestat_ptr);
+    if (des[de].filetype == wasi_filetype_regular_file && des[de].host_path != NULL) {
+        FILE *stream = fopen(des[de].host_path, "rb");
+        if (stream != NULL) {
+            if (fseek(stream, 0, SEEK_END) >= 0) {
+                long size = ftell(stream);
+                if (size >= 0) res_filestat_ptr->size = size;
+            }
+            fclose(stream);
+        }
+    }
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_fdstat_get(uint32_t fd, uint32_t res_fdstat) {
+    (void)fd;
+    (void)res_fdstat;
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_fdstat_get(%u)\n", fd);
+#endif
+
+    panic("unimplemented");
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_readdir(uint32_t fd, uint32_t buf, uint32_t buf_len, uint64_t cookie, uint32_t res_size) {
+    (void)fd;
+    (void)buf;
+    (void)buf_len;
+    (void)cookie;
+    (void)res_size;
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_readdir(%u, 0x%X, %u, %llu)\n", fd, buf, buf_len, (unsigned long long)cookie);
+#endif
+
+    panic("unimplemented");
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_write(uint32_t fd, uint32_t iovs, uint32_t iovs_len, uint32_t res_size) {
+    uint8_t *const m = *wasm_memory;
+    struct wasi_ciovec *iovs_ptr = (struct wasi_ciovec *)&m[iovs];
+    uint32_t *res_size_ptr = (uint32_t *)&m[res_size];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_write(%u, 0x%X, %u)\n", fd, iovs, iovs_len);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    switch (des[fds[fd].de].filetype) {
+        case wasi_filetype_character_device: break;
+        case wasi_filetype_regular_file: break;
+        case wasi_filetype_directory: return wasi_errno_inval;
+        default: panic("unimplemented");
+    }
+
+    size_t size = 0;
+    for (uint32_t i = 0; i < iovs_len; i += 1) {
+        size_t written_size = 0;
+        if (fds[fd].stream != NULL)
+            written_size = fwrite(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream);
+        else
+            written_size = iovs_ptr[i].len;
+        size += written_size;
+        if (written_size < iovs_ptr[i].len) break;
+    }
+
+    if (size > 0) {
+        time_t now = time(NULL);
+        des[fds[fd].de].atim = now;
+        des[fds[fd].de].mtim = now;
+    }
+    *res_size_ptr = size;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_path_open(uint32_t fd, uint32_t dirflags, uint32_t path, uint32_t path_len, uint32_t oflags, uint64_t fs_rights_base, uint64_t fs_rights_inheriting, uint32_t fdflags, uint32_t res_fd) {
+    uint8_t *const m = *wasm_memory;
+    const char *path_ptr = (const char *)&m[path];
+    (void)fs_rights_inheriting;
+    uint32_t *res_fd_ptr = (uint32_t *)&m[res_fd];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_path_open(%u, 0x%X, \"%.*s\", 0x%X, 0x%llX, 0x%llX, 0x%X)\n", fd, dirflags, (int)path_len, path_ptr, oflags, (unsigned long long)fs_rights_base, (unsigned long long)fs_rights_inheriting, fdflags);
+#endif
+
+    bool creat = (oflags & wasi_oflags_creat) != 0;
+    bool directory = (oflags & wasi_oflags_directory) != 0;
+    bool excl = (oflags & wasi_oflags_excl) != 0;
+    bool trunc = (oflags & wasi_oflags_trunc) != 0;
+    bool append = (fdflags & wasi_fdflags_append) != 0;
+
+    uint32_t de;
+    enum wasi_errno lookup_errno = DirEntry_lookup(fd, dirflags, path_ptr, path_len, &de);
+    if (lookup_errno == wasi_errno_success) {
+        if (directory && des[de].filetype != wasi_filetype_directory) return wasi_errno_notdir;
+
+        struct FileDescriptor *new_fds = realloc(fds, (fd_len + 1) * sizeof(struct FileDescriptor));
+        if (new_fds == NULL) return wasi_errno_nomem;
+        fds = new_fds;
+
+        fds[fd_len].de = de;
+        switch (des[de].filetype) {
+            case wasi_filetype_directory: fds[fd_len].stream = NULL; break;
+            default: panic("unimplemented");
+        }
+
+#if LOG_TRACE
+        fprintf(stderr, "fd = %u\n", fd_len);
+#endif
+        *res_fd_ptr = fd_len;
+        fd_len += 1;
+    }
+    if (lookup_errno != wasi_errno_noent) return lookup_errno;
+
+    struct FileDescriptor *new_fds = realloc(fds, (fd_len + 1) * sizeof(struct FileDescriptor));
+    if (new_fds == NULL) return wasi_errno_nomem;
+    fds = new_fds;
+
+    enum wasi_filetype filetype = directory ? wasi_filetype_directory : wasi_filetype_regular_file;
+    enum wasi_errno create_errno = DirEntry_create(fd, path_ptr, path_len, filetype, 0, &de);
+    if (create_errno != wasi_errno_success) return create_errno;
+    FILE *stream;
+    if (!directory) {
+        if (des[de].host_path == NULL) {
+            if (!creat) { DirEntry_unlink(de); de_len -= 1; return wasi_errno_noent; }
+            time_t now = time(NULL);
+            des[de].atim = now;
+            des[de].mtim = now;
+            des[de].ctim = now;
+            stream = NULL;
+        } else {
+            if (oflags != (append ? wasi_oflags_creat : wasi_oflags_creat | wasi_oflags_trunc)) {
+                char mode[] = "rb+";
+                if ((fs_rights_base & wasi_rights_fd_write) == 0) mode[2] = '\0';
+                stream = fopen(des[de].host_path, mode);
+                if (stream != NULL) {
+                    if (append || excl || trunc) fclose(stream);
+                    if (excl) {
+                        DirEntry_unlink(de);
+                        de_len -= 1;
+                        return wasi_errno_exist;
+                    }
+                } else if (!creat) { DirEntry_unlink(de); de_len -= 1; return wasi_errno_noent; }
+            }
+            if (append || trunc || stream == NULL) {
+                char mode[] = "wb+";
+                if ((fs_rights_base & wasi_rights_fd_read) == 0) mode[2] = '\0';
+                if (trunc || !append) {
+                    stream = fopen(des[de].host_path, mode);
+                    if (append && stream != NULL) fclose(stream);
+                }
+                if (append) {
+                    mode[0] = 'a';
+                    stream = fopen(des[de].host_path, mode);
+                }
+            }
+            if (stream == NULL) { DirEntry_unlink(de); de_len -= 1; return wasi_errno_isdir; }
+        }
+    } else stream = NULL;
+
+#if LOG_TRACE
+    fprintf(stderr, "fd = %u\n", fd_len);
+#endif
+    fds[fd_len].de = de;
+    fds[fd_len].stream = stream;
+    *res_fd_ptr = fd_len;
+    fd_len += 1;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_clock_time_get(uint32_t id, uint64_t precision, uint32_t res_timestamp) {
+    uint8_t *const m = *wasm_memory;
+    (void)precision;
+    uint64_t *res_timestamp_ptr = (uint64_t *)&m[res_timestamp];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_clock_time_get(%u, %llu)\n", id, (unsigned long long)precision);
+#endif
+
+    switch (id) {
+        case wasi_clockid_realtime:
+            *res_timestamp_ptr = time(NULL) * UINT64_C(1000000000);
+            break;
+        case wasi_clockid_monotonic:
+        case wasi_clockid_process_cputime_id:
+        case wasi_clockid_thread_cputime_id:
+            *res_timestamp_ptr = clock() * (UINT64_C(1000000000) / CLOCKS_PER_SEC);
+            break;
+        default: return wasi_errno_inval;
+    }
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path, uint32_t path_len) {
+    uint8_t *const m = *wasm_memory;
+    const char *path_ptr = (const char *)&m[path];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_path_remove_directory(%u, \"%.*s\")\n", fd, (int)path_len, path_ptr);
+#endif
+
+    uint32_t de;
+    enum wasi_errno lookup_errno = DirEntry_lookup(fd, 0, path_ptr, path_len, &de);
+    if (lookup_errno != wasi_errno_success) return lookup_errno;
+    if (des[de].filetype != wasi_filetype_directory) return wasi_errno_notdir;
+    DirEntry_unlink(de);
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_path_unlink_file(uint32_t fd, uint32_t path, uint32_t path_len) {
+    uint8_t *const m = *wasm_memory;
+    const char *path_ptr = (const char *)&m[path];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_path_unlink_file(%u, \"%.*s\")\n", fd, (int)path_len, path_ptr);
+#endif
+
+    uint32_t de;
+    enum wasi_errno lookup_errno = DirEntry_lookup(fd, 0, path_ptr, path_len, &de);
+    if (lookup_errno != wasi_errno_success) return lookup_errno;
+    if (des[de].filetype == wasi_filetype_directory) return wasi_errno_isdir;
+    if (des[de].filetype != wasi_filetype_regular_file) panic("unimplemented");
+    DirEntry_unlink(de);
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_fd_pread(uint32_t fd, uint32_t iovs, uint32_t iovs_len, uint64_t offset, uint32_t res_size) {
+    uint8_t *const m = *wasm_memory;
+    struct wasi_ciovec *iovs_ptr = (struct wasi_ciovec *)&m[iovs];
+    uint32_t *res_size_ptr = (uint32_t *)&m[res_size];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_fd_pread(%u, 0x%X, %u)\n", fd, iovs, iovs_len);
+#endif
+
+    if (fd >= fd_len || fds[fd].de >= de_len) return wasi_errno_badf;
+    switch (des[fds[fd].de].filetype) {
+        case wasi_filetype_character_device: break;
+        case wasi_filetype_regular_file: break;
+        case wasi_filetype_directory: return wasi_errno_inval;
+        default: panic("unimplemented");
+    }
+
+    fpos_t pos;
+    if (fgetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+    if (fseek(fds[fd].stream, offset, SEEK_SET) < 0) return wasi_errno_io;
+
+    size_t size = 0;
+    for (uint32_t i = 0; i < iovs_len; i += 1) {
+        size_t read_size = 0;
+        if (fds[fd].stream != NULL)
+            read_size = fread(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream);
+        else
+            panic("unimplemented");
+        size += read_size;
+        if (read_size < iovs_ptr[i].len) break;
+    }
+
+    if (fsetpos(fds[fd].stream, &pos) < 0) return wasi_errno_io;
+
+    if (size > 0) des[fds[fd].de].atim = time(NULL);
+    *res_size_ptr = size;
+    return wasi_errno_success;
+}
+
+uint32_t wasi_snapshot_preview1_poll_oneoff(uint32_t in, uint32_t out, uint32_t nsubscriptions, uint32_t res_nevents) {
+    (void)in;
+    (void)out;
+    (void)nsubscriptions;
+    (void)res_nevents;
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_poll_oneoff(%u)\n", nsubscriptions);
+#endif
+
+    panic("unimplemented");
+    return wasi_errno_success;
+}
+
+
+void wasi_snapshot_preview1_debug(uint32_t string, uint64_t x) {
+    uint8_t *const m = *wasm_memory;
+    const char *string_ptr = (const char *)&m[string];
+#if LOG_TRACE
+    fprintf(stderr, "wasi_snapshot_preview1_debug(\"%s\", %llu, 0x%llX)\n", string_ptr, (unsigned long long)x, (unsigned long long)x);
+#endif
+
+    (void)string_ptr;
+    (void)x;
+}
stage1/wasm.h
@@ -0,0 +1,280 @@
+#ifndef WASM_H
+#define WASM_H
+
+#include "panic.h"
+
+enum WasmSectionId {
+    WasmSectionId_type      =  1,
+    WasmSectionId_import    =  2,
+    WasmSectionId_func      =  3,
+    WasmSectionId_table     =  4,
+    WasmSectionId_mem       =  5,
+    WasmSectionId_global    =  6,
+    WasmSectionId_export    =  7,
+    WasmSectionId_start     =  8,
+    WasmSectionId_elem      =  9,
+    WasmSectionId_code      = 10,
+    WasmSectionId_data      = 11,
+    WasmSectionId_datacount = 12,
+};
+
+enum WasmValType {
+    WasmValType_i32       = -0x01,
+    WasmValType_i64       = -0x02,
+    WasmValType_f32       = -0x03,
+    WasmValType_f64       = -0x04,
+    WasmValType_v128      = -0x05,
+    WasmValType_funcref   = -0x10,
+    WasmValType_externref = -0x11,
+    WasmValType_empty     = -0x40,
+};
+static const char *WasmValType_toC(enum WasmValType val_type) {
+    switch (val_type) {
+        case WasmValType_i32: return "uint32_t";
+        case WasmValType_i64: return "uint64_t";
+        case WasmValType_f32: return "float";
+        case WasmValType_f64: return "double";
+        case WasmValType_v128: panic("vector types are unsupported");
+        case WasmValType_funcref: return "void (*)(void)";
+        case WasmValType_externref: return "void *";
+        default: panic("unsupported value type");
+    }
+    return NULL;
+}
+
+enum WasmMut {
+    WasmMut_const = 0x00,
+    WasmMut_var   = 0x01,
+};
+static const char *WasmMut_toC(enum WasmMut val_type) {
+    switch (val_type) {
+        case WasmMut_const: return "const ";
+        case WasmMut_var: return "";
+        default: panic("unsupported mut");
+    }
+}
+
+enum WasmOpcode {
+    WasmOpcode_unreachable         = 0x00,
+    WasmOpcode_nop                 = 0x01,
+    WasmOpcode_block               = 0x02,
+    WasmOpcode_loop                = 0x03,
+    WasmOpcode_if                  = 0x04,
+    WasmOpcode_else                = 0x05,
+    WasmOpcode_end                 = 0x0B,
+    WasmOpcode_br                  = 0x0C,
+    WasmOpcode_br_if               = 0x0D,
+    WasmOpcode_br_table            = 0x0E,
+    WasmOpcode_return              = 0x0F,
+    WasmOpcode_call                = 0x10,
+    WasmOpcode_call_indirect       = 0x11,
+
+    WasmOpcode_drop                = 0x1A,
+    WasmOpcode_select              = 0x1B,
+    WasmOpcode_select_t            = 0x1C,
+
+    WasmOpcode_local_get           = 0x20,
+    WasmOpcode_local_set           = 0x21,
+    WasmOpcode_local_tee           = 0x22,
+    WasmOpcode_global_get          = 0x23,
+    WasmOpcode_global_set          = 0x24,
+
+    WasmOpcode_table_get           = 0x25,
+    WasmOpcode_table_set           = 0x26,
+
+    WasmOpcode_i32_load            = 0x28,
+    WasmOpcode_i64_load            = 0x29,
+    WasmOpcode_f32_load            = 0x2A,
+    WasmOpcode_f64_load            = 0x2B,
+    WasmOpcode_i32_load8_s         = 0x2C,
+    WasmOpcode_i32_load8_u         = 0x2D,
+    WasmOpcode_i32_load16_s        = 0x2E,
+    WasmOpcode_i32_load16_u        = 0x2F,
+    WasmOpcode_i64_load8_s         = 0x30,
+    WasmOpcode_i64_load8_u         = 0x31,
+    WasmOpcode_i64_load16_s        = 0x32,
+    WasmOpcode_i64_load16_u        = 0x33,
+    WasmOpcode_i64_load32_s        = 0x34,
+    WasmOpcode_i64_load32_u        = 0x35,
+    WasmOpcode_i32_store           = 0x36,
+    WasmOpcode_i64_store           = 0x37,
+    WasmOpcode_f32_store           = 0x38,
+    WasmOpcode_f64_store           = 0x39,
+    WasmOpcode_i32_store8          = 0x3A,
+    WasmOpcode_i32_store16         = 0x3B,
+    WasmOpcode_i64_store8          = 0x3C,
+    WasmOpcode_i64_store16         = 0x3D,
+    WasmOpcode_i64_store32         = 0x3E,
+    WasmOpcode_memory_size         = 0x3F,
+    WasmOpcode_memory_grow         = 0x40,
+
+    WasmOpcode_i32_const           = 0x41,
+    WasmOpcode_i64_const           = 0x42,
+    WasmOpcode_f32_const           = 0x43,
+    WasmOpcode_f64_const           = 0x44,
+
+    WasmOpcode_i32_eqz             = 0x45,
+    WasmOpcode_i32_eq              = 0x46,
+    WasmOpcode_i32_ne              = 0x47,
+    WasmOpcode_i32_lt_s            = 0x48,
+    WasmOpcode_i32_lt_u            = 0x49,
+    WasmOpcode_i32_gt_s            = 0x4A,
+    WasmOpcode_i32_gt_u            = 0x4B,
+    WasmOpcode_i32_le_s            = 0x4C,
+    WasmOpcode_i32_le_u            = 0x4D,
+    WasmOpcode_i32_ge_s            = 0x4E,
+    WasmOpcode_i32_ge_u            = 0x4F,
+
+    WasmOpcode_i64_eqz             = 0x50,
+    WasmOpcode_i64_eq              = 0x51,
+    WasmOpcode_i64_ne              = 0x52,
+    WasmOpcode_i64_lt_s            = 0x53,
+    WasmOpcode_i64_lt_u            = 0x54,
+    WasmOpcode_i64_gt_s            = 0x55,
+    WasmOpcode_i64_gt_u            = 0x56,
+    WasmOpcode_i64_le_s            = 0x57,
+    WasmOpcode_i64_le_u            = 0x58,
+    WasmOpcode_i64_ge_s            = 0x59,
+    WasmOpcode_i64_ge_u            = 0x5A,
+
+    WasmOpcode_f32_eq              = 0x5B,
+    WasmOpcode_f32_ne              = 0x5C,
+    WasmOpcode_f32_lt              = 0x5D,
+    WasmOpcode_f32_gt              = 0x5E,
+    WasmOpcode_f32_le              = 0x5F,
+    WasmOpcode_f32_ge              = 0x60,
+
+    WasmOpcode_f64_eq              = 0x61,
+    WasmOpcode_f64_ne              = 0x62,
+    WasmOpcode_f64_lt              = 0x63,
+    WasmOpcode_f64_gt              = 0x64,
+    WasmOpcode_f64_le              = 0x65,
+    WasmOpcode_f64_ge              = 0x66,
+
+    WasmOpcode_i32_clz             = 0x67,
+    WasmOpcode_i32_ctz             = 0x68,
+    WasmOpcode_i32_popcnt          = 0x69,
+    WasmOpcode_i32_add             = 0x6A,
+    WasmOpcode_i32_sub             = 0x6B,
+    WasmOpcode_i32_mul             = 0x6C,
+    WasmOpcode_i32_div_s           = 0x6D,
+    WasmOpcode_i32_div_u           = 0x6E,
+    WasmOpcode_i32_rem_s           = 0x6F,
+    WasmOpcode_i32_rem_u           = 0x70,
+    WasmOpcode_i32_and             = 0x71,
+    WasmOpcode_i32_or              = 0x72,
+    WasmOpcode_i32_xor             = 0x73,
+    WasmOpcode_i32_shl             = 0x74,
+    WasmOpcode_i32_shr_s           = 0x75,
+    WasmOpcode_i32_shr_u           = 0x76,
+    WasmOpcode_i32_rotl            = 0x77,
+    WasmOpcode_i32_rotr            = 0x78,
+
+    WasmOpcode_i64_clz             = 0x79,
+    WasmOpcode_i64_ctz             = 0x7A,
+    WasmOpcode_i64_popcnt          = 0x7B,
+    WasmOpcode_i64_add             = 0x7C,
+    WasmOpcode_i64_sub             = 0x7D,
+    WasmOpcode_i64_mul             = 0x7E,
+    WasmOpcode_i64_div_s           = 0x7F,
+    WasmOpcode_i64_div_u           = 0x80,
+    WasmOpcode_i64_rem_s           = 0x81,
+    WasmOpcode_i64_rem_u           = 0x82,
+    WasmOpcode_i64_and             = 0x83,
+    WasmOpcode_i64_or              = 0x84,
+    WasmOpcode_i64_xor             = 0x85,
+    WasmOpcode_i64_shl             = 0x86,
+    WasmOpcode_i64_shr_s           = 0x87,
+    WasmOpcode_i64_shr_u           = 0x88,
+    WasmOpcode_i64_rotl            = 0x89,
+    WasmOpcode_i64_rotr            = 0x8A,
+
+    WasmOpcode_f32_abs             = 0x8B,
+    WasmOpcode_f32_neg             = 0x8C,
+    WasmOpcode_f32_ceil            = 0x8D,
+    WasmOpcode_f32_floor           = 0x8E,
+    WasmOpcode_f32_trunc           = 0x8F,
+    WasmOpcode_f32_nearest         = 0x90,
+    WasmOpcode_f32_sqrt            = 0x91,
+    WasmOpcode_f32_add             = 0x92,
+    WasmOpcode_f32_sub             = 0x93,
+    WasmOpcode_f32_mul             = 0x94,
+    WasmOpcode_f32_div             = 0x95,
+    WasmOpcode_f32_min             = 0x96,
+    WasmOpcode_f32_max             = 0x97,
+    WasmOpcode_f32_copysign        = 0x98,
+
+    WasmOpcode_f64_abs             = 0x99,
+    WasmOpcode_f64_neg             = 0x9A,
+    WasmOpcode_f64_ceil            = 0x9B,
+    WasmOpcode_f64_floor           = 0x9C,
+    WasmOpcode_f64_trunc           = 0x9D,
+    WasmOpcode_f64_nearest         = 0x9E,
+    WasmOpcode_f64_sqrt            = 0x9F,
+    WasmOpcode_f64_add             = 0xA0,
+    WasmOpcode_f64_sub             = 0xA1,
+    WasmOpcode_f64_mul             = 0xA2,
+    WasmOpcode_f64_div             = 0xA3,
+    WasmOpcode_f64_min             = 0xA4,
+    WasmOpcode_f64_max             = 0xA5,
+    WasmOpcode_f64_copysign        = 0xA6,
+
+    WasmOpcode_i32_wrap_i64        = 0xA7,
+    WasmOpcode_i32_trunc_f32_s     = 0xA8,
+    WasmOpcode_i32_trunc_f32_u     = 0xA9,
+    WasmOpcode_i32_trunc_f64_s     = 0xAA,
+    WasmOpcode_i32_trunc_f64_u     = 0xAB,
+    WasmOpcode_i64_extend_i32_s    = 0xAC,
+    WasmOpcode_i64_extend_i32_u    = 0xAD,
+    WasmOpcode_i64_trunc_f32_s     = 0xAE,
+    WasmOpcode_i64_trunc_f32_u     = 0xAF,
+    WasmOpcode_i64_trunc_f64_s     = 0xB0,
+    WasmOpcode_i64_trunc_f64_u     = 0xB1,
+    WasmOpcode_f32_convert_i32_s   = 0xB2,
+    WasmOpcode_f32_convert_i32_u   = 0xB3,
+    WasmOpcode_f32_convert_i64_s   = 0xB4,
+    WasmOpcode_f32_convert_i64_u   = 0xB5,
+    WasmOpcode_f32_demote_f64      = 0xB6,
+    WasmOpcode_f64_convert_i32_s   = 0xB7,
+    WasmOpcode_f64_convert_i32_u   = 0xB8,
+    WasmOpcode_f64_convert_i64_s   = 0xB9,
+    WasmOpcode_f64_convert_i64_u   = 0xBA,
+    WasmOpcode_f64_promote_f32     = 0xBB,
+    WasmOpcode_i32_reinterpret_f32 = 0xBC,
+    WasmOpcode_i64_reinterpret_f64 = 0xBD,
+    WasmOpcode_f32_reinterpret_i32 = 0xBE,
+    WasmOpcode_f64_reinterpret_i64 = 0xBF,
+
+    WasmOpcode_i32_extend8_s       = 0xC0,
+    WasmOpcode_i32_extend16_s      = 0xC1,
+    WasmOpcode_i64_extend8_s       = 0xC2,
+    WasmOpcode_i64_extend16_s      = 0xC3,
+    WasmOpcode_i64_extend32_s      = 0xC4,
+
+    WasmOpcode_prefixed            = 0xFC,
+};
+
+enum WasmPrefixedOpcode {
+    WasmPrefixedOpcode_i32_trunc_sat_f32_s =  0,
+    WasmPrefixedOpcode_i32_trunc_sat_f32_u =  1,
+    WasmPrefixedOpcode_i32_trunc_sat_f64_s =  2,
+    WasmPrefixedOpcode_i32_trunc_sat_f64_u =  3,
+    WasmPrefixedOpcode_i64_trunc_sat_f32_s =  4,
+    WasmPrefixedOpcode_i64_trunc_sat_f32_u =  5,
+    WasmPrefixedOpcode_i64_trunc_sat_f64_s =  6,
+    WasmPrefixedOpcode_i64_trunc_sat_f64_u =  7,
+
+    WasmPrefixedOpcode_memory_init         =  8,
+    WasmPrefixedOpcode_data_drop           =  9,
+    WasmPrefixedOpcode_memory_copy         = 10,
+    WasmPrefixedOpcode_memory_fill         = 11,
+
+    WasmPrefixedOpcode_table_init          = 12,
+    WasmPrefixedOpcode_elem_drop           = 13,
+    WasmPrefixedOpcode_table_copy          = 14,
+    WasmPrefixedOpcode_table_grow          = 15,
+    WasmPrefixedOpcode_table_size          = 16,
+    WasmPrefixedOpcode_table_fill          = 17,
+};
+
+#endif /* WASM_H */
stage1/wasm2c.c
@@ -0,0 +1,2520 @@
+#include "FuncGen.h"
+#include "InputStream.h"
+#include "panic.h"
+#include "wasm.h"
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct FuncType {
+    const struct ResultType *param;
+    const struct ResultType *result;
+};
+static const struct FuncType *FuncType_blockType(const struct FuncType *types, int64_t block_type) {
+    if (block_type >= 0) return &types[block_type];
+
+    static const struct ResultType none = { 0, { 0 }};
+    static const struct ResultType i32 = { 1, { WasmValType_i32 } };
+    static const struct ResultType i64 = { 1, { WasmValType_i64 } };
+    static const struct ResultType f32 = { 1, { WasmValType_f32 } };
+    static const struct ResultType f64 = { 1, { WasmValType_f64 } };
+
+    static const struct FuncType none_i32 = { &none, &i32 };
+    static const struct FuncType none_i64 = { &none, &i64 };
+    static const struct FuncType none_f32 = { &none, &f32 };
+    static const struct FuncType none_f64 = { &none, &f64 };
+    static const struct FuncType none_none = { &none, &none };
+
+    switch (block_type) {
+        case WasmValType_i32: return &none_i32;
+        case WasmValType_i64: return &none_i64;
+        case WasmValType_f32: return &none_f32;
+        case WasmValType_f64: return &none_f64;
+        case WasmValType_empty: return &none_none;
+        default: panic("unsupported block type");
+    }
+    return NULL;
+}
+
+static uint32_t evalExpr(struct InputStream *in) {
+    uint32_t value;
+    while (true) {
+        switch (InputStream_readByte(in)) {
+            case WasmOpcode_end: return value;
+
+            case WasmOpcode_i32_const:
+                value = (uint32_t)InputStream_readLeb128_i32(in);
+                break;
+
+            default: panic("unsupported expr opcode");
+        }
+    }
+}
+
+static void renderExpr(FILE *out, struct InputStream *in) {
+    while (true) {
+        switch (InputStream_readByte(in)) {
+            case WasmOpcode_end: return;
+
+            case WasmOpcode_i32_const: {
+                uint32_t value = (uint32_t)InputStream_readLeb128_i32(in);
+                fprintf(out, "UINT32_C(0x%" PRIX32 ")", value);
+                break;
+            }
+
+            default: panic("unsupported expr opcode");
+        }
+    }
+}
+
+int main(int argc, char **argv) {
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s in.wasm.zst out.c\n", argv[0]);
+        return 1;
+    }
+
+    const char *mod = "wasm";
+    bool isBigEndian = false; // TODO
+
+    struct InputStream in;
+    InputStream_open(&in, argv[1]);
+
+    if (InputStream_readByte(&in) != '\0' ||
+        InputStream_readByte(&in) != 'a'  ||
+        InputStream_readByte(&in) != 's'  ||
+        InputStream_readByte(&in) != 'm') panic("input is not a zstd-compressed wasm file");
+    if (InputStream_readLittle_u32(&in) != 1) panic("unsupported wasm version");
+
+    FILE *out = fopen(argv[2], "w");
+    if (out == NULL) panic("unable to open output file");
+    fputs("#include <math.h>\n"
+          "#include <stdint.h>\n"
+          "#include <stdlib.h>\n"
+          "#include <string.h>\n"
+          "\n"
+          "static uint32_t i32_reinterpret_f32(const float src) {\n"
+          "    uint32_t dst;\n"
+          "    memcpy(&dst, &src, sizeof(dst));\n"
+          "    return dst;\n"
+          "}\n"
+          "static uint64_t i64_reinterpret_f64(const double src) {\n"
+          "    uint64_t dst;\n"
+          "    memcpy(&dst, &src, sizeof(dst));\n"
+          "    return dst;\n"
+          "}\n"
+          "static float f32_reinterpret_i32(const uint32_t src) {\n"
+          "    float dst;\n"
+          "    memcpy(&dst, &src, sizeof(dst));\n"
+          "    return dst;\n"
+          "}\n"
+          "static double f64_reinterpret_i64(const uint64_t src) {\n"
+          "    double dst;\n"
+          "    memcpy(&dst, &src, sizeof(dst));\n"
+          "    return dst;\n"
+          "}\n"
+          "\n"
+          "static uint32_t memory_grow(uint8_t **m, uint32_t *p, uint32_t n) {\n"
+          "    uint32_t r = *p;\n"
+          "    uint32_t new_p = r + n;\n"
+          "    uint8_t *new_m = realloc(*m, new_p << 16);\n"
+          "    if (new_m == NULL) return UINT32_C(0xFFFFFFF);\n"
+          "    memset(&new_m[r << 16], 0, n << 16);\n"
+          "    *m = new_m;\n"
+          "    *p = new_p;\n"
+          "    return r;\n"
+          "}\n"
+          "\n"
+          "static int inited;\n"
+          "static void init_elem(void);\n"
+          "static void init_data(void);\n"
+          "static void init(void) {\n"
+          "    if (inited != 0) return;\n"
+          "    init_elem();\n"
+          "    init_data();\n"
+          "    inited = 1;\n"
+          "}\n"
+          "\n", out);
+
+    struct FuncType *types;
+    uint32_t max_param_len = 0;
+    (void)InputStream_skipToSection(&in, WasmSectionId_type);
+    {
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        types = malloc(sizeof(struct FuncType) * len);
+        if (types == NULL) panic("out of memory");
+        for (uint32_t i = 0; i < len; i += 1) {
+            if (InputStream_readByte(&in) != 0x60) panic("expected functype");
+            types[i].param = InputStream_readResultType(&in);
+            if (types[i].param->len > max_param_len) max_param_len = types[i].param->len;
+            types[i].result = InputStream_readResultType(&in);
+        }
+    }
+
+    struct Import {
+        const char *mod;
+        const char *name;
+        uint32_t type_idx;
+    } *imports;
+    (void)InputStream_skipToSection(&in, WasmSectionId_import);
+    uint32_t imports_len = InputStream_readLeb128_u32(&in);
+    {
+        imports = malloc(sizeof(struct Import) * imports_len);
+        if (imports == NULL) panic("out of memory");
+        for (uint32_t i = 0; i < imports_len; i += 1) {
+            imports[i].mod = InputStream_readName(&in);
+            imports[i].name = InputStream_readName(&in);
+            switch (InputStream_readByte(&in)) {
+                case 0x00: { // func
+                    imports[i].type_idx = InputStream_readLeb128_u32(&in);
+                    const struct FuncType *func_type = &types[imports[i].type_idx];
+                    switch (func_type->result->len) {
+                        case 0: fputs("void", out); break;
+                        case 1: fputs(WasmValType_toC(func_type->result->types[0]), out); break;
+                        default: panic("multiple function returns not supported");
+                    }
+                    fprintf(out, " %s_%s(", imports[i].mod, imports[i].name);
+                    if (func_type->param->len == 0) fputs("void", out);
+                    for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                        if (param_i > 0) fputs(", ", out);
+                        fputs(WasmValType_toC(func_type->param->types[param_i]), out);
+                    }
+                    fputs(");\n", out);
+                    break;
+                }
+
+                case 0x01: // table
+                case 0x02: // mem
+                case 0x03: // global
+                default:
+                    panic("unsupported import type");
+            }
+        }
+        fputc('\n', out);
+    }
+
+    struct Func {
+        uint32_t type_idx;
+    } *funcs;
+    (void)InputStream_skipToSection(&in, WasmSectionId_func);
+    {
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        funcs = malloc(sizeof(struct Func) * len);
+        if (funcs == NULL) panic("out of memory");
+        for (uint32_t i = 0; i < len; i += 1) {
+            funcs[i].type_idx = InputStream_readLeb128_u32(&in);
+            const struct FuncType *func_type = &types[funcs[i].type_idx];
+            fputs("static ", out);
+            switch (func_type->result->len) {
+                case 0: fputs("void", out); break;
+                case 1: fputs(WasmValType_toC(func_type->result->types[0]), out); break;
+                default: panic("multiple function returns not supported");
+            }
+            fprintf(out, " f%" PRIu32 "(", i);
+            if (func_type->param->len == 0) fputs("void", out);
+            for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                if (param_i > 0) fputs(", ", out);
+                fprintf(out, "%s", WasmValType_toC(func_type->param->types[param_i]));
+            }
+            fputs(");\n", out);
+        }
+        fputc('\n', out);
+    }
+
+    struct Table {
+        int8_t type;
+        struct Limits limits;
+    } *tables;
+    (void)InputStream_skipToSection(&in, WasmSectionId_table);
+    {
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        tables = malloc(sizeof(struct Table) * len);
+        if (tables == NULL) panic("out of memory");
+        for (uint32_t i = 0; i < len; i += 1) {
+            int64_t ref_type = InputStream_readLeb128_i64(&in);
+            switch (ref_type) {
+                case WasmValType_funcref:
+                    break;
+
+                default: panic("unsupported reftype");
+            }
+            tables[i].type = ref_type;
+            tables[i].limits = InputStream_readLimits(&in);
+            if (tables[i].limits.min != tables[i].limits.max) panic("growable table not supported");
+            fprintf(out, "static void (*t%" PRIu32 "[UINT32_C(%" PRIu32 ")])(void);\n",
+                    i, tables[i].limits.min);
+        }
+        fputc('\n', out);
+    }
+
+    struct Mem {
+        struct Limits limits;
+    } *mems;
+    (void)InputStream_skipToSection(&in, WasmSectionId_mem);
+    uint32_t mems_len = InputStream_readLeb128_u32(&in);
+    {
+        mems = malloc(sizeof(struct Mem) * mems_len);
+        if (mems == NULL) panic("out of memory");
+        for (uint32_t i = 0; i < mems_len; i += 1) {
+            mems[i].limits = InputStream_readLimits(&in);
+            fprintf(out, "static uint8_t *m%" PRIu32 ";\n"
+                    "static uint32_t p%" PRIu32 ";\n", i, i);
+        }
+        fputc('\n', out);
+    }
+
+    struct Global {
+        bool mut;
+        int8_t val_type;
+    } *globals;
+    (void)InputStream_skipToSection(&in, WasmSectionId_global);
+    {
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        globals = malloc(sizeof(struct Global) * len);
+        if (globals == NULL) panic("out of memory");
+        for (uint32_t i = 0; i < len; i += 1) {
+            int64_t val_type = InputStream_readLeb128_i64(&in);
+            enum WasmMut mut = InputStream_readByte(&in);
+            fprintf(out, "%s%s g%" PRIu32 " = ", WasmMut_toC(mut), WasmValType_toC(val_type), i);
+            renderExpr(out, &in);
+            fputs(";\n", out);
+            globals[i].mut = mut;
+            globals[i].val_type = val_type;
+        }
+        fputc('\n', out);
+    }
+
+    (void)InputStream_skipToSection(&in, WasmSectionId_export);
+    {
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        for (uint32_t i = 0; i < len; i += 1) {
+            char *name = InputStream_readName(&in);
+            uint8_t kind = InputStream_readByte(&in);
+            uint32_t idx = InputStream_readLeb128_u32(&in);
+            switch (kind) {
+                case 0x00: {
+                    if (idx < imports_len) panic("can't export an import");
+                    const struct FuncType *func_type = &types[funcs[idx - imports_len].type_idx];
+                    switch (func_type->result->len) {
+                        case 0: fputs("void", out); break;
+                        case 1: fputs(WasmValType_toC(func_type->result->types[0]), out); break;
+                        default: panic("multiple function returns not supported");
+                    }
+                    fprintf(out, " %s_%s(", mod, name);
+                    if (func_type->param->len == 0) fputs("void", out);
+                    for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                        if (param_i > 0) fputs(", ", out);
+                        fprintf(out, "%s l%" PRIu32, WasmValType_toC(func_type->param->types[param_i]), param_i);
+                    }
+                    fprintf(out,
+                            ") {\n"
+                            "    init();\n"
+                            "    %sf%" PRIu32 "(",
+                            func_type->result->len > 0 ? "return " : "", idx - imports_len);
+                    for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                        if (param_i > 0) fputs(", ", out);
+                        fprintf(out, "l%" PRIu32, param_i);
+                    }
+                    fputs(");\n}\n", out);
+                    break;
+                }
+
+                case 0x02:
+                    fprintf(out, "uint8_t **const %s_%s = &m%" PRIu32 ";\n", mod, name, idx);
+                    break;
+
+                default: panic("unsupported export kind");
+            }
+            free(name);
+        }
+        fputc('\n', out);
+    }
+
+    (void)InputStream_skipToSection(&in, WasmSectionId_elem);
+    {
+        uint32_t table_i = 0;
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        fputs("static void init_elem(void) {\n", out);
+        for (uint32_t segment_i = 0; segment_i < len; segment_i += 1) {
+            uint32_t table_idx = 0;
+            uint32_t elem_type = InputStream_readLeb128_u32(&in);
+            if (elem_type != 0x00) panic("unsupported elem type");
+            uint32_t offset = evalExpr(&in);
+            uint32_t segment_len = InputStream_readLeb128_u32(&in);
+            for (uint32_t i = 0; i < segment_len; i += 1) {
+                uint32_t func_id = InputStream_readLeb128_u32(&in);
+                fprintf(out, "    t%" PRIu32 "[UINT32_C(%" PRIu32 ")] = (void (*)(void))&",
+                        table_idx, offset + i);
+                if (func_id < imports_len)
+                    fprintf(out, "%s_%s", imports[func_id].mod, imports[func_id].name);
+                else
+                    fprintf(out, "f%" PRIu32, func_id - imports_len);
+                fputs(";\n", out);
+            }
+        }
+        fputs("}\n\n", out);
+    }
+
+    (void)InputStream_skipToSection(&in, WasmSectionId_code);
+    {
+        struct FuncGen fg;
+        FuncGen_init(&fg);
+        bool *param_used = malloc(sizeof(bool) * max_param_len);
+        uint32_t *param_stash = malloc(sizeof(uint32_t) * max_param_len);
+
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        for (uint32_t func_i = 0; func_i < len; func_i += 1) {
+            FuncGen_reset(&fg);
+
+            uint32_t code_len = InputStream_readLeb128_u32(&in);
+            const struct FuncType *func_type = &types[funcs[func_i].type_idx];
+            fputs("static ", out);
+            switch (func_type->result->len) {
+                case 0: fputs("void", out); break;
+                case 1: fputs(WasmValType_toC(func_type->result->types[0]), out); break;
+                default: panic("multiple function returns not supported");
+            }
+            fprintf(out, " f%" PRIu32 "(", func_i);
+            if (func_type->param->len == 0) fputs("void", out);
+            for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                param_used[param_i] = false;
+                int8_t param_type = func_type->param->types[param_i];
+                if (param_i > 0) fputs(", ", out);
+                FuncGen_localDeclare(&fg, out, param_type);
+            }
+            fputs(") {\n", out);
+
+            for (uint32_t local_sets_remaining = InputStream_readLeb128_u32(&in);
+                 local_sets_remaining > 0; local_sets_remaining -= 1) {
+                uint32_t local_set_len = InputStream_readLeb128_u32(&in);
+                int64_t val_type = InputStream_readLeb128_i64(&in);
+                for (; local_set_len > 0; local_set_len -= 1) {
+                    FuncGen_indent(&fg, out);
+                    FuncGen_localDeclare(&fg, out, val_type);
+                    fputs(" = 0;\n", out);
+                }
+            }
+
+            uint32_t unreachable_depth = 0;
+            for (uint32_t result_i = func_type->result->len; result_i > 0; ) {
+                result_i -= 1;
+                FuncGen_indent(&fg, out);
+                (void)FuncGen_localDeclare(&fg, out,
+                                           func_type->result->types[result_i]);
+                fputs(";\n", out);
+            }
+            FuncGen_blockBegin(&fg, out, WasmOpcode_block, funcs[func_i].type_idx);
+            while (!FuncGen_done(&fg)) {
+                //static const char *mnemonics[] = {
+                //    [WasmOpcode_unreachable]         = "unreachable",
+                //    [WasmOpcode_nop]                 = "nop",
+                //    [WasmOpcode_block]               = "block",
+                //    [WasmOpcode_loop]                = "loop",
+                //    [WasmOpcode_if]                  = "if",
+                //    [WasmOpcode_else]                = "else",
+                //    [WasmOpcode_end]                 = "end",
+                //    [WasmOpcode_br]                  = "br",
+                //    [WasmOpcode_br_if]               = "br_if",
+                //    [WasmOpcode_br_table]            = "br_table",
+                //    [WasmOpcode_return]              = "return",
+                //    [WasmOpcode_call]                = "call",
+                //    [WasmOpcode_call_indirect]       = "call_indirect",
+                //
+                //    [WasmOpcode_drop]                = "drop",
+                //    [WasmOpcode_select]              = "select",
+                //    [WasmOpcode_select_t]            = "select t",
+                //
+                //    [WasmOpcode_local_get]           = "local.get",
+                //    [WasmOpcode_local_set]           = "local.set",
+                //    [WasmOpcode_local_tee]           = "local.tee",
+                //    [WasmOpcode_global_get]          = "global.get",
+                //    [WasmOpcode_global_set]          = "global.set",
+                //    [WasmOpcode_table_get]           = "table.get",
+                //    [WasmOpcode_table_set]           = "table.set",
+                //
+                //    [WasmOpcode_i32_load]            = "i32.load",
+                //    [WasmOpcode_i64_load]            = "i64.load",
+                //    [WasmOpcode_f32_load]            = "f32.load",
+                //    [WasmOpcode_f64_load]            = "f64.load",
+                //    [WasmOpcode_i32_load8_s]         = "i32.load8_s",
+                //    [WasmOpcode_i32_load8_u]         = "i32.load8_u",
+                //    [WasmOpcode_i32_load16_s]        = "i32.load16_s",
+                //    [WasmOpcode_i32_load16_u]        = "i32.load16_u",
+                //    [WasmOpcode_i64_load8_s]         = "i64.load8_s",
+                //    [WasmOpcode_i64_load8_u]         = "i64.load8_u",
+                //    [WasmOpcode_i64_load16_s]        = "i64.load16_s",
+                //    [WasmOpcode_i64_load16_u]        = "i64.load16_u",
+                //    [WasmOpcode_i64_load32_s]        = "i64.load32_s",
+                //    [WasmOpcode_i64_load32_u]        = "i64.load32_u",
+                //    [WasmOpcode_i32_store]           = "i32.store",
+                //    [WasmOpcode_i64_store]           = "i64.store",
+                //    [WasmOpcode_f32_store]           = "f32.store",
+                //    [WasmOpcode_f64_store]           = "f64.store",
+                //    [WasmOpcode_i32_store8]          = "i32.store8",
+                //    [WasmOpcode_i32_store16]         = "i32.store16",
+                //    [WasmOpcode_i64_store8]          = "i64.store8",
+                //    [WasmOpcode_i64_store16]         = "i64.store16",
+                //    [WasmOpcode_i64_store32]         = "i64.store32",
+                //    [WasmOpcode_memory_size]         = "memory.size",
+                //    [WasmOpcode_memory_grow]         = "memory.grow",
+                //
+                //    [WasmOpcode_i32_const]           = "i32.const",
+                //    [WasmOpcode_i64_const]           = "i64.const",
+                //    [WasmOpcode_f32_const]           = "f32.const",
+                //    [WasmOpcode_f64_const]           = "f64.const",
+                //
+                //    [WasmOpcode_i32_eqz]             = "i32.eqz",
+                //    [WasmOpcode_i32_eq]              = "i32.eq",
+                //    [WasmOpcode_i32_ne]              = "i32.ne",
+                //    [WasmOpcode_i32_lt_s]            = "i32.lt_s",
+                //    [WasmOpcode_i32_lt_u]            = "i32.lt_u",
+                //    [WasmOpcode_i32_gt_s]            = "i32.gt_s",
+                //    [WasmOpcode_i32_gt_u]            = "i32.gt_u",
+                //    [WasmOpcode_i32_le_s]            = "i32.le_s",
+                //    [WasmOpcode_i32_le_u]            = "i32.le_u",
+                //    [WasmOpcode_i32_ge_s]            = "i32.ge_s",
+                //    [WasmOpcode_i32_ge_u]            = "i32.ge_u",
+                //
+                //    [WasmOpcode_i64_eqz]             = "i64.eqz",
+                //    [WasmOpcode_i64_eq]              = "i64.eq",
+                //    [WasmOpcode_i64_ne]              = "i64.ne",
+                //    [WasmOpcode_i64_lt_s]            = "i64.lt_s",
+                //    [WasmOpcode_i64_lt_u]            = "i64.lt_u",
+                //    [WasmOpcode_i64_gt_s]            = "i64.gt_s",
+                //    [WasmOpcode_i64_gt_u]            = "i64.gt_u",
+                //    [WasmOpcode_i64_le_s]            = "i64.le_s",
+                //    [WasmOpcode_i64_le_u]            = "i64.le_u",
+                //    [WasmOpcode_i64_ge_s]            = "i64.ge_s",
+                //    [WasmOpcode_i64_ge_u]            = "i64.ge_u",
+                //
+                //    [WasmOpcode_f32_eq]              = "f32.eq",
+                //    [WasmOpcode_f32_ne]              = "f32.ne",
+                //    [WasmOpcode_f32_lt]              = "f32.lt",
+                //    [WasmOpcode_f32_gt]              = "f32.gt",
+                //    [WasmOpcode_f32_le]              = "f32.le",
+                //    [WasmOpcode_f32_ge]              = "f32.ge",
+                //
+                //    [WasmOpcode_f64_eq]              = "f64.eq",
+                //    [WasmOpcode_f64_ne]              = "f64.ne",
+                //    [WasmOpcode_f64_lt]              = "f64.lt",
+                //    [WasmOpcode_f64_gt]              = "f64.gt",
+                //    [WasmOpcode_f64_le]              = "f64.le",
+                //    [WasmOpcode_f64_ge]              = "f64.ge",
+                //
+                //    [WasmOpcode_i32_clz]             = "i32.clz",
+                //    [WasmOpcode_i32_ctz]             = "i32.ctz",
+                //    [WasmOpcode_i32_popcnt]          = "i32.popcnt",
+                //    [WasmOpcode_i32_add]             = "i32.add",
+                //    [WasmOpcode_i32_sub]             = "i32.sub",
+                //    [WasmOpcode_i32_mul]             = "i32.mul",
+                //    [WasmOpcode_i32_div_s]           = "i32.div_s",
+                //    [WasmOpcode_i32_div_u]           = "i32.div_u",
+                //    [WasmOpcode_i32_rem_s]           = "i32.rem_s",
+                //    [WasmOpcode_i32_rem_u]           = "i32.rem_u",
+                //    [WasmOpcode_i32_and]             = "i32.and",
+                //    [WasmOpcode_i32_or]              = "i32.or",
+                //    [WasmOpcode_i32_xor]             = "i32.xor",
+                //    [WasmOpcode_i32_shl]             = "i32.shl",
+                //    [WasmOpcode_i32_shr_s]           = "i32.shr_s",
+                //    [WasmOpcode_i32_shr_u]           = "i32.shr_u",
+                //    [WasmOpcode_i32_rotl]            = "i32.rotl",
+                //    [WasmOpcode_i32_rotr]            = "i32.rotr",
+                //
+                //    [WasmOpcode_i64_clz]             = "i64.clz",
+                //    [WasmOpcode_i64_ctz]             = "i64.ctz",
+                //    [WasmOpcode_i64_popcnt]          = "i64.popcnt",
+                //    [WasmOpcode_i64_add]             = "i64.add",
+                //    [WasmOpcode_i64_sub]             = "i64.sub",
+                //    [WasmOpcode_i64_mul]             = "i64.mul",
+                //    [WasmOpcode_i64_div_s]           = "i64.div_s",
+                //    [WasmOpcode_i64_div_u]           = "i64.div_u",
+                //    [WasmOpcode_i64_rem_s]           = "i64.rem_s",
+                //    [WasmOpcode_i64_rem_u]           = "i64.rem_u",
+                //    [WasmOpcode_i64_and]             = "i64.and",
+                //    [WasmOpcode_i64_or]              = "i64.or",
+                //    [WasmOpcode_i64_xor]             = "i64.xor",
+                //    [WasmOpcode_i64_shl]             = "i64.shl",
+                //    [WasmOpcode_i64_shr_s]           = "i64.shr_s",
+                //    [WasmOpcode_i64_shr_u]           = "i64.shr_u",
+                //    [WasmOpcode_i64_rotl]            = "i64.rotl",
+                //    [WasmOpcode_i64_rotr]            = "i64.rotr",
+                //
+                //    [WasmOpcode_f32_abs]             = "f32.abs",
+                //    [WasmOpcode_f32_neg]             = "f32.neg",
+                //    [WasmOpcode_f32_ceil]            = "f32.ceil",
+                //    [WasmOpcode_f32_floor]           = "f32.floor",
+                //    [WasmOpcode_f32_trunc]           = "f32.trunc",
+                //    [WasmOpcode_f32_nearest]         = "f32.nearest",
+                //    [WasmOpcode_f32_sqrt]            = "f32.sqrt",
+                //    [WasmOpcode_f32_add]             = "f32.add",
+                //    [WasmOpcode_f32_sub]             = "f32.sub",
+                //    [WasmOpcode_f32_mul]             = "f32.mul",
+                //    [WasmOpcode_f32_div]             = "f32.div",
+                //    [WasmOpcode_f32_min]             = "f32.min",
+                //    [WasmOpcode_f32_max]             = "f32.max",
+                //    [WasmOpcode_f32_copysign]        = "f32.copysign",
+                //
+                //    [WasmOpcode_f64_abs]             = "f64.abs",
+                //    [WasmOpcode_f64_neg]             = "f64.neg",
+                //    [WasmOpcode_f64_ceil]            = "f64.ceil",
+                //    [WasmOpcode_f64_floor]           = "f64.floor",
+                //    [WasmOpcode_f64_trunc]           = "f64.trunc",
+                //    [WasmOpcode_f64_nearest]         = "f64.nearest",
+                //    [WasmOpcode_f64_sqrt]            = "f64.sqrt",
+                //    [WasmOpcode_f64_add]             = "f64.add",
+                //    [WasmOpcode_f64_sub]             = "f64.sub",
+                //    [WasmOpcode_f64_mul]             = "f64.mul",
+                //    [WasmOpcode_f64_div]             = "f64.div",
+                //    [WasmOpcode_f64_min]             = "f64.min",
+                //    [WasmOpcode_f64_max]             = "f64.max",
+                //    [WasmOpcode_f64_copysign]        = "f64.copysign",
+                //
+                //    [WasmOpcode_i32_wrap_i64]        = "i32.wrap_i64",
+                //    [WasmOpcode_i32_trunc_f32_s]     = "i32.trunc_f32_s",
+                //    [WasmOpcode_i32_trunc_f32_u]     = "i32.trunc_f32_u",
+                //    [WasmOpcode_i32_trunc_f64_s]     = "i32.trunc_f64_s",
+                //    [WasmOpcode_i32_trunc_f64_u]     = "i32.trunc_f64_u",
+                //    [WasmOpcode_i64_extend_i32_s]    = "i64.extend_i32_s",
+                //    [WasmOpcode_i64_extend_i32_u]    = "i64.extend_i32_u",
+                //    [WasmOpcode_i64_trunc_f32_s]     = "i64.trunc_f32_s",
+                //    [WasmOpcode_i64_trunc_f32_u]     = "i64.trunc_f32_u",
+                //    [WasmOpcode_i64_trunc_f64_s]     = "i64.trunc_f64_s",
+                //    [WasmOpcode_i64_trunc_f64_u]     = "i64.trunc_f64_u",
+                //    [WasmOpcode_f32_convert_i32_s]   = "f32.convert_i32_s",
+                //    [WasmOpcode_f32_convert_i32_u]   = "f32.convert_i32_u",
+                //    [WasmOpcode_f32_convert_i64_s]   = "f32.convert_i64_s",
+                //    [WasmOpcode_f32_convert_i64_u]   = "f32.convert_i64_u",
+                //    [WasmOpcode_f32_demote_f64]      = "f32.demote_f64",
+                //    [WasmOpcode_f64_convert_i32_s]   = "f64.convert_i32_s",
+                //    [WasmOpcode_f64_convert_i32_u]   = "f64.convert_i32_u",
+                //    [WasmOpcode_f64_convert_i64_s]   = "f64.convert_i64_s",
+                //    [WasmOpcode_f64_convert_i64_u]   = "f64.convert_i64_u",
+                //    [WasmOpcode_f64_promote_f32]     = "f64.promote_f32",
+                //    [WasmOpcode_i32_reinterpret_f32] = "i32.reinterpret_f32",
+                //    [WasmOpcode_i64_reinterpret_f64] = "i64.reinterpret_f64",
+                //    [WasmOpcode_f32_reinterpret_i32] = "f32.reinterpret_i32",
+                //    [WasmOpcode_f64_reinterpret_i64] = "f64.reinterpret_i64",
+                //
+                //    [WasmOpcode_i32_extend8_s]       = "i32.extend8_s",
+                //    [WasmOpcode_i32_extend16_s]      = "i32.extend16_s",
+                //    [WasmOpcode_i64_extend8_s]       = "i64.extend8_s",
+                //    [WasmOpcode_i64_extend16_s]      = "i64.extend16_s",
+                //    [WasmOpcode_i64_extend32_s]      = "i64.extend32_s",
+                //
+                //    [WasmOpcode_prefixed]            = "prefixed",
+                //};
+                uint8_t opcode = InputStream_readByte(&in);
+                //FuncGen_indent(&fg, out);
+                //fprintf(out, "// %2u: ", fg.stack_i);
+                //if (mnemonics[opcode])
+                //    fprintf(out, "%s\n", mnemonics[opcode]);
+                //else
+                //    fprintf(out, "%02hhX\n", opcode);
+                //fflush(out); // DEBUG
+                switch (opcode) {
+                    case WasmOpcode_unreachable:
+                        if (unreachable_depth == 0) {
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "abort();\n");
+                            unreachable_depth += 1;
+                        }
+                        break;
+                    case WasmOpcode_nop:
+                        break;
+                    case WasmOpcode_block:
+                    case WasmOpcode_loop:
+                    case WasmOpcode_if: {
+                        int64_t block_type = InputStream_readLeb128_i64(&in);
+                        if (unreachable_depth == 0) {
+                            const struct FuncType *func_type = FuncType_blockType(types, block_type);
+                            for (uint32_t param_i = func_type->param->len; param_i > 0; ) {
+                                param_i -= 1;
+                                FuncGen_indent(&fg, out);
+                                param_stash[param_i] =
+                                    FuncGen_localDeclare(&fg, out, func_type->param->types[param_i]);
+                                fprintf(out, " = l%" PRIu32 ";\n", FuncGen_stackPop(&fg));
+                            }
+                            for (uint32_t result_i = func_type->result->len; result_i > 0; ) {
+                                result_i -= 1;
+                                FuncGen_indent(&fg, out);
+                                (void)FuncGen_localDeclare(&fg, out,
+                                                           func_type->result->types[result_i]);
+                                fputs(";\n", out);
+                            }
+                            FuncGen_blockBegin(&fg, out, opcode, block_type);
+                            for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                                FuncGen_stackPush(&fg, out, func_type->param->types[param_i]);
+                                fprintf(out, " = l%" PRIu32 ";\n", param_stash[param_i]);
+                            }
+                        } else unreachable_depth += 1;
+                        break;
+                    }
+                    case WasmOpcode_else:
+                    case WasmOpcode_end:
+                        if (unreachable_depth <= 1) {
+                            const struct ResultType *result_type =
+                                FuncType_blockType(types, FuncGen_blockType(&fg, 0))->result;
+                            uint32_t label = FuncGen_blockLabel(&fg, 0);
+                            if (unreachable_depth == 0) {
+                                const struct ResultType *result_type =
+                                    FuncType_blockType(types, FuncGen_blockType(&fg, 0))->result;
+                                for (uint32_t result_i = result_type->len; result_i > 0; ) {
+                                    result_i -= 1;
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "l%" PRIu32 " = l%" PRIu32 ";\n",
+                                            label - result_type->len + result_i, FuncGen_stackPop(&fg));
+                                }
+                            } else unreachable_depth -= 1;
+                            switch (opcode) {
+                                case WasmOpcode_else:
+                                    FuncGen_outdent(&fg, out);
+                                    fputs("} else {\n", out);
+                                    break;
+                                case WasmOpcode_end:
+                                    FuncGen_blockEnd(&fg, out);
+                                    for (uint32_t result_i = 0; result_i < result_type->len;
+                                         result_i += 1) {
+                                        FuncGen_stackPush(&fg, out, result_type->types[result_i]);
+                                        fprintf(out, "l%" PRIu32 ";\n",
+                                                label - result_type->len + result_i);
+                                    }
+                                    break;
+                            }
+                        } else if (opcode == WasmOpcode_end) unreachable_depth -= 1;
+                        break;
+                    case WasmOpcode_br:
+                    case WasmOpcode_br_if: {
+                        uint32_t label_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            enum WasmOpcode kind = FuncGen_blockKind(&fg, label_idx);
+                            const struct FuncType *func_type =
+                                FuncType_blockType(types, FuncGen_blockType(&fg, label_idx));
+                            uint32_t label = FuncGen_blockLabel(&fg, label_idx);
+
+                            FuncGen_indent(&fg, out);
+                            if (opcode == WasmOpcode_br_if)
+                                fprintf(out, "if (l%" PRIu32 ") ", FuncGen_stackPop(&fg));
+                            fputs("{\n", out);
+                            const struct ResultType *label_type;
+                            uint32_t lhs;
+                            switch (kind) {
+                                case WasmOpcode_loop:
+                                    label_type = func_type->param;
+                                    lhs = label - func_type->result->len - func_type->param->len;
+                                    break;
+                                default:
+                                    label_type = func_type->result;
+                                    lhs = label - func_type->result->len;
+                                    break;
+                            }
+                            for (uint32_t stack_i = 0; stack_i < label_type->len; stack_i += 1) {
+                                uint32_t rhs;
+                                switch (opcode) {
+                                    case WasmOpcode_br:
+                                        rhs = FuncGen_stackPop(&fg);
+                                        break;
+                                    case WasmOpcode_br_if:
+                                        rhs = FuncGen_stackAt(&fg, stack_i);
+                                        break;
+                                    default: panic("unexpected opcode");
+                                }
+                                FuncGen_cont(&fg, out);
+                                fprintf(out, "l%" PRIu32 " = l%" PRIu32 ";\n", lhs, rhs);
+                                lhs += 1;
+                            }
+                            FuncGen_cont(&fg, out);
+                            fprintf(out, "goto l%" PRIu32 ";\n", label);
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "}\n");
+                            if (opcode == WasmOpcode_br) unreachable_depth += 1;
+                        }
+                        break;
+                    }
+                    case WasmOpcode_br_table: {
+                        if (unreachable_depth == 0) {
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "switch (l%" PRIu32 ") {\n", FuncGen_stackPop(&fg));
+                        }
+                        uint32_t label_len = InputStream_readLeb128_u32(&in);
+                        for (uint32_t i = 0; i < label_len; i += 1) {
+                            uint32_t label = InputStream_readLeb128_u32(&in);
+                            if (unreachable_depth == 0) {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "case %u: goto l%" PRIu32 ";\n",
+                                        i, FuncGen_blockLabel(&fg, label));
+                            }
+                        }
+                        uint32_t label = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "default: goto l%" PRIu32 ";\n",
+                                    FuncGen_blockLabel(&fg, label));
+                            FuncGen_indent(&fg, out);
+                            fputs("}\n", out);
+                            unreachable_depth += 1;
+                        }
+                        break;
+                    }
+                    case WasmOpcode_return:
+                        if (unreachable_depth == 0) {
+                            FuncGen_indent(&fg, out);
+                            fputs("return", out);
+                            switch (func_type->result->len) {
+                                case 0: break;
+                                case 1: fprintf(out, " l%" PRIu32, FuncGen_stackPop(&fg)); break;
+                                default: panic("multiple function returns not supported");
+                            }
+                            fputs(";\n", out);
+                            unreachable_depth += 1;
+                        }
+                        break;
+                    case WasmOpcode_call:
+                    case WasmOpcode_call_indirect: {
+                        uint32_t func_id;
+                        uint32_t type_idx;
+                        uint32_t table_idx;
+                        switch (opcode) {
+                            case WasmOpcode_call:
+                                func_id = InputStream_readLeb128_u32(&in);
+                                if (func_id < imports_len)
+                                    type_idx = imports[func_id].type_idx;
+                                else
+                                    type_idx = funcs[func_id - imports_len].type_idx;
+                                break;
+                            case WasmOpcode_call_indirect:
+                                type_idx = InputStream_readLeb128_u32(&in);
+                                table_idx = InputStream_readLeb128_u32(&in);
+                                func_id = FuncGen_stackPop(&fg);
+                                break;
+                        }
+                        if (unreachable_depth == 0) {
+                            const struct FuncType *callee_func_type = &types[type_idx];
+                            for (uint32_t param_i = callee_func_type->param->len; param_i > 0; ) {
+                                param_i -= 1;
+                                param_stash[param_i] = FuncGen_stackPop(&fg);
+                            }
+                            switch (callee_func_type->result->len) {
+                                case 0: FuncGen_indent(&fg, out); break;
+                                case 1: FuncGen_stackPush(&fg, out, callee_func_type->result->types[0]); break;
+                                default: panic("multiple function returns not supported");
+                            }
+                            switch (opcode) {
+                                case WasmOpcode_call:
+                                    if (func_id < imports_len)
+                                        fprintf(out, "%s_%s", imports[func_id].mod, imports[func_id].name);
+                                    else
+                                        fprintf(out, "f%" PRIu32, func_id - imports_len);
+                                    break;
+                                case WasmOpcode_call_indirect:
+                                    fputs("(*(", out);
+                                    switch (callee_func_type->result->len) {
+                                        case 0: fputs("void", out); break;
+                                        case 1: fputs(WasmValType_toC(callee_func_type->result->types[0]), out); break;
+                                        default: panic("multiple function returns not supported");
+                                    }
+                                    fputs(" (*)(", out);
+                                    if (callee_func_type->param->len == 0) fputs("void", out);
+                                    for (uint32_t param_i = 0; param_i < callee_func_type->param->len; param_i += 1) {
+                                        if (param_i > 0) fputs(", ", out);
+                                        fputs(WasmValType_toC(callee_func_type->param->types[param_i]), out);
+                                    }
+                                    fprintf(out, "))t%" PRIu32 "[l%" PRIu32 "])", table_idx, func_id);
+                                    break;
+                            }
+                            fputc('(', out);
+                            for (uint32_t param_i = 0; param_i < callee_func_type->param->len;
+                                 param_i += 1) {
+                                if (param_i > 0) fputs(", ", out);
+                                fprintf(out, "l%" PRIu32, param_stash[param_i]);
+                            }
+                            fputs(");\n", out);
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_drop:
+                        if (unreachable_depth == 0) {
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "(void)l%" PRIu32 ";\n", FuncGen_stackPop(&fg));
+                        }
+                        break;
+                    case WasmOpcode_select:
+                        if (unreachable_depth == 0) {
+                            uint32_t cond = FuncGen_stackPop(&fg);
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, FuncGen_localType(&fg, lhs));
+                            fprintf(out, "l%" PRIu32 " ? l%" PRIu32 " : l%" PRIu32 ";\n",
+                                    cond, lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_local_get: {
+                        uint32_t local_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            if (local_idx < func_type->param->len) param_used[local_idx] = true;
+                            FuncGen_stackPush(&fg, out, FuncGen_localType(&fg, local_idx));
+                            fprintf(out, "l%" PRIu32 ";\n", local_idx);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_local_set: {
+                        uint32_t local_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            if (local_idx < func_type->param->len) param_used[local_idx] = true;
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "l%" PRIu32 " = l%" PRIu32 ";\n",
+                                    local_idx, FuncGen_stackPop(&fg));
+                        }
+                        break;
+                    }
+                    case WasmOpcode_local_tee: {
+                        uint32_t local_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            if (local_idx < func_type->param->len) param_used[local_idx] = true;
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "l%" PRIu32 " = l%" PRIu32 ";\n",
+                                    local_idx, FuncGen_stackAt(&fg, 0));
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_global_get: {
+                        uint32_t global_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_stackPush(&fg, out, globals[global_idx].val_type);
+                            fprintf(out, "g%" PRIu32 ";\n", global_idx);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_global_set: {
+                        uint32_t global_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "g%" PRIu32 " = l%" PRIu32 ";\n",
+                                    global_idx, FuncGen_stackPop(&fg));
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_table_get:
+                    case WasmOpcode_table_set:
+                        (void)InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) panic("unimplemented opcode");
+                        break;
+
+                    case WasmOpcode_i32_load: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            if (align < 2 || isBigEndian) {
+                                fseek(out, -1, SEEK_CUR);
+                                fputc('\n', out);
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint32_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                            } else fprintf(out, "*(const uint32_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            if (align < 3 || isBigEndian) {
+                                fseek(out, -1, SEEK_CUR);
+                                fputc('\n', out);
+                                for (uint8_t byte_i = 0; byte_i < 8; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint64_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                            } else fprintf(out, "*(const uint64_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_f32_load: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            if (align < 2 || isBigEndian) {
+                                fputs("f32_reinterpret_i32(\n", out);
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint32_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                                fputc(')', out);
+                            } else fprintf(out, "*(const float *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_f64_load: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            if (align < 3 || isBigEndian) {
+                                fputs("f64_reinterpret_i64(\n", out);
+                                for (uint8_t byte_i = 0; byte_i < 8; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint64_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                                fputc(')', out);
+                            } else fprintf(out, "*(const double *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i32_load8_s: {
+                        (void)InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int8_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")];\n",
+                                    0, base, offset);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i32_load8_u: {
+                        (void)InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")];\n",
+                                    0, base, offset);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i32_load16_s: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            if (align < 1 || isBigEndian) {
+                                fputs("(int16_t)(\n", out);
+                                for (uint8_t byte_i = 0; byte_i < 2; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint16_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                                fputc(')', out);
+                            } else fprintf(out, "*(const int16_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i32_load16_u: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            if (align < 1 || isBigEndian) {
+                                fseek(out, -1, SEEK_CUR);
+                                fputc('\n', out);
+                                for (uint8_t byte_i = 0; byte_i < 2; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint16_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                            } else fprintf(out, "*(const uint16_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load8_s: {
+                        (void)InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int8_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")];\n",
+                                    0, base, offset);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load8_u: {
+                        (void)InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")];\n",
+                                    0, base, offset);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load16_s: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            if (align < 1 || isBigEndian) {
+                                fputs("(int16_t)(\n", out);
+                                for (uint8_t byte_i = 0; byte_i < 2; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint16_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                                fputc(')', out);
+                            } else fprintf(out, "*(const int16_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load16_u: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            if (align < 1 || isBigEndian) {
+                                fseek(out, -1, SEEK_CUR);
+                                fputc('\n', out);
+                                for (uint8_t byte_i = 0; byte_i < 2; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint16_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                            } else fprintf(out, "*(const uint16_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load32_s: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            if (align < 2 || isBigEndian) {
+                                fputs("(int32_t)(\n", out);
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint32_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                                fputc(')', out);
+                            } else fprintf(out, "*(const int32_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_load32_u: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            if (align < 2 || isBigEndian) {
+                                fseek(out, -1, SEEK_CUR);
+                                fputc('\n', out);
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    if (byte_i > 0) fputs(" |\n", out);
+                                    FuncGen_cont(&fg, out);
+                                    fprintf(out, "(uint32_t)m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%"
+                                            PRIu32 ")] << %2u", 0, base, offset + byte_i, byte_i << 3);
+                                }
+                            } else fprintf(out, "*(const uint32_t *)&m%" PRIu32 "[l%" PRIu32
+                                           " + UINT32_C(%" PRIu32 ")]", 0, base, offset);
+                            fputs(";\n", out);
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_i32_store: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 2 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(l%" PRIu32 " >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(uint32_t *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_store: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 3 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 8; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(l%" PRIu32 " >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(uint64_t *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+                    case WasmOpcode_f32_store: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 2 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(i32_reinterpret_f32(l%" PRIu32 ") >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(float *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+                    case WasmOpcode_f64_store: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 3 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 8; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(i64_reinterpret_f64(l%" PRIu32 ") >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(double *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i32_store8: {
+                        (void)InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32
+                                    ")] = (uint8_t)l%" PRIu32 ";\n", 0, base, offset, value);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i32_store16: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 1 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 2; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(l%" PRIu32 " >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(uint16_t *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = (uint16_t)l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_store8: {
+                        (void)InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            FuncGen_indent(&fg, out);
+                            fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32
+                                    ")] = (uint8_t)l%" PRIu32 ";\n", 0, base, offset, value);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_store16: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 1 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 2; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(l%" PRIu32 " >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(uint16_t *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = (uint16_t)l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_store32: {
+                        uint32_t align = InputStream_readLeb128_u32(&in);
+                        uint32_t offset = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t value = FuncGen_stackPop(&fg);
+                            uint32_t base = FuncGen_stackPop(&fg);
+                            if (align < 2 || isBigEndian) {
+                                for (uint8_t byte_i = 0; byte_i < 4; byte_i += 1) {
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")] = "
+                                            "(uint8_t)(l%" PRIu32 " >> %2u);\n",
+                                            0, base, offset + byte_i, value, byte_i << 3);
+                                }
+                            } else {
+                                FuncGen_indent(&fg, out);
+                                fprintf(out, "*(uint32_t *)&m%" PRIu32 "[l%" PRIu32
+                                        " + UINT32_C(%" PRIu32 ")] = (uint32_t)l%" PRIu32 ";\n",
+                                        0, base, offset, value);
+                            }
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_memory_size: {
+                        uint32_t mem_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "p%" PRIu32 ";\n", mem_idx);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_memory_grow: {
+                        uint32_t mem_idx = InputStream_readLeb128_u32(&in);
+                        if (unreachable_depth == 0) {
+                            uint32_t pages = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "memory_grow(&m%" PRIu32 ", &p%" PRIu32 ", l%" PRIu32 ");\n",
+                                    mem_idx, mem_idx, pages);
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_i32_const: {
+                        uint32_t value = (uint32_t)InputStream_readLeb128_i32(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "UINT32_C(0x%" PRIX32 ");\n", value);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_i64_const: {
+                        uint64_t value = (uint64_t)InputStream_readLeb128_i64(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "UINT64_C(0x%" PRIX64 ");\n", value);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_f32_const: {
+                        uint32_t value = InputStream_readLittle_u32(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "f32_reinterpret_i32(UINT32_C(0x%" PRIX32 "));\n", value);
+                        }
+                        break;
+                    }
+                    case WasmOpcode_f64_const: {
+                        uint64_t value = InputStream_readLittle_u64(&in);
+                        if (unreachable_depth == 0) {
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "f64_reinterpret_i64(UINT64_C(0x%" PRIX64 "));\n", value);
+                        }
+                        break;
+                    }
+
+                    case WasmOpcode_i32_eqz:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "!l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_eq:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " == l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_ne:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " != l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_lt_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " < (int32_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_lt_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " < l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_gt_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " > (int32_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_gt_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " > l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_le_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " <= (int32_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_le_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " <= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_ge_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " >= (int32_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_ge_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " >= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_i64_eqz:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "!l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_eq:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " == l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_ne:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " != l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_lt_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int64_t)l%" PRIu32 " < (int64_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_lt_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " < l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_gt_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int64_t)l%" PRIu32 " > (int64_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_gt_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " > l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_le_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int64_t)l%" PRIu32 " <= (int64_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_le_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " <= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_ge_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int64_t)l%" PRIu32 " >= (int64_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_ge_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " >= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_f32_eq:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " == l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_ne:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " != l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_lt:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " < l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_gt:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " > l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_le:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " <= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_ge:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " >= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_f64_eq:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " == l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_ne:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " != l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_lt:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " < l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_gt:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " > l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_le:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " <= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_ge:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " >= l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_i32_clz:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " != 0 ? __builtin_clz(l%" PRIu32 ") : 32;\n",
+                                    lhs, lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_ctz:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " != 0 ? __builtin_ctz(l%" PRIu32 ") : 32;\n",
+                                    lhs, lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_popcnt:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "__builtin_popcount(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_add:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " + l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_sub:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " - l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_mul:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " * l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_div_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " / (int32_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_div_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " / l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_rem_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " %% (int32_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_rem_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " %% l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_and:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " & l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_or:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " | l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_xor:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " ^ l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_shl:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " << (l%" PRIu32 " & 0x1F);\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_shr_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 " >> (l%" PRIu32 " & 0x1F);\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_shr_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " >> (l%" PRIu32 " & 0x1F);\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_rotl:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " << (l%" PRIu32 " & 0x1F) | "
+                                    "l%" PRIu32 " >> (-l%" PRIu32" & 0x1F);\n", lhs, rhs, lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_rotr:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "l%" PRIu32 " >> (l%" PRIu32 " & 0x1F) | "
+                                    "l%" PRIu32 " << (-l%" PRIu32" & 0x1F);\n", lhs, rhs, lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_i64_clz:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " != 0 ? __builtin_clzll(l%" PRIu32 ") : 64;\n",
+                                    lhs, lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_ctz:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " != 0 ? __builtin_ctzll(l%" PRIu32 ") : 64;\n",
+                                    lhs, lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_popcnt:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "__builtin_popcountll(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_add:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " + l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_sub:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " - l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_mul:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " * l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_div_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int64_t)l%" PRIu32 " / (int64_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_div_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " / l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_rem_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int64_t)l%" PRIu32 " %% (int64_t)l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_rem_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " %% l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_and:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " & l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_or:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " | l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_xor:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " ^ l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_shl:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " << (l%" PRIu32 " & 0x3F);\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_shr_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int64_t)l%" PRIu32 " >> (l%" PRIu32 " & 0x3F);\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_shr_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " >> (l%" PRIu32 " & 0x3F);\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_rotl:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " << (l%" PRIu32 " & 0x3F) | "
+                                    "l%" PRIu32 " >> (-l%" PRIu32" & 0x3F);\n", lhs, rhs, lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_rotr:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "l%" PRIu32 " >> (l%" PRIu32 " & 0x3F) | "
+                                    "l%" PRIu32 " << (-l%" PRIu32" & 0x3F);\n", lhs, rhs, lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_f32_abs:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "fabsf(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_neg:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "-l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_ceil:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "ceilf(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_floor:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "floorf(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_trunc:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "truncf(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_nearest:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "roundf(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_sqrt:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "sqrtf(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_add:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "l%" PRIu32 " + l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_sub:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "l%" PRIu32 " - l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_mul:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "l%" PRIu32 " * l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_div:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "l%" PRIu32 " / l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_min:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "fminf(l%" PRIu32 ", l%" PRIu32 ");\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_max:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "fmaxf(l%" PRIu32 ", l%" PRIu32 ");\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_copysign:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "copysignf(l%" PRIu32 ", l%" PRIu32 ");\n", lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_f64_abs:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "fabs(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_neg:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "-l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_ceil:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "ceil(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_floor:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "floor(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_trunc:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "trunc(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_nearest:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "round(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_sqrt:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "sqrt(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_add:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "l%" PRIu32 " + l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_sub:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "l%" PRIu32 " - l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_mul:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "l%" PRIu32 " * l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_div:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "l%" PRIu32 " / l%" PRIu32 ";\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_min:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "fmin(l%" PRIu32 ", l%" PRIu32 ");\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_max:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "fmax(l%" PRIu32 ", l%" PRIu32 ");\n", lhs, rhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_copysign:
+                        if (unreachable_depth == 0) {
+                            uint32_t rhs = FuncGen_stackPop(&fg);
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "copysign(l%" PRIu32 ", l%" PRIu32 ");\n", lhs, rhs);
+                        }
+                        break;
+
+                    case WasmOpcode_i32_wrap_i64:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(uint32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_trunc_f32_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_trunc_f32_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(uint32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_trunc_f64_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_trunc_f64_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(uint32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_extend_i32_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_extend_i32_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(uint32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_trunc_f32_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_trunc_f32_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(uint64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_trunc_f64_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_trunc_f64_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(uint64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_convert_i32_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "(int32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_convert_i32_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "(uint32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_convert_i64_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "(int64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_convert_i64_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "(uint64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_demote_f64:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "(float)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_convert_i32_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "(int32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_convert_i32_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "(uint32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_convert_i64_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "(int64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_convert_i64_u:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "(uint64_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_promote_f32:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "(double)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_reinterpret_f32:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "i32_reinterpret_f32(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_reinterpret_f64:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "i64_reinterpret_f64(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f32_reinterpret_i32:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f32);
+                            fprintf(out, "f32_reinterpret_i32(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_f64_reinterpret_i64:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_f64);
+                            fprintf(out, "f64_reinterpret_i64(l%" PRIu32 ");\n", lhs);
+                        }
+                        break;
+
+                    case WasmOpcode_i32_extend8_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int8_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i32_extend16_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i32);
+                            fprintf(out, "(int16_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_extend8_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int8_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_extend16_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int16_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+                    case WasmOpcode_i64_extend32_s:
+                        if (unreachable_depth == 0) {
+                            uint32_t lhs = FuncGen_stackPop(&fg);
+                            FuncGen_stackPush(&fg, out, WasmValType_i64);
+                            fprintf(out, "(int32_t)l%" PRIu32 ";\n", lhs);
+                        }
+                        break;
+
+                    case WasmOpcode_prefixed:
+                        switch (InputStream_readLeb128_u32(&in)) {
+                            case WasmPrefixedOpcode_i32_trunc_sat_f32_s:
+                            case WasmPrefixedOpcode_i32_trunc_sat_f32_u:
+                            case WasmPrefixedOpcode_i32_trunc_sat_f64_s:
+                            case WasmPrefixedOpcode_i32_trunc_sat_f64_u:
+                            case WasmPrefixedOpcode_i64_trunc_sat_f32_s:
+                            case WasmPrefixedOpcode_i64_trunc_sat_f32_u:
+                            case WasmPrefixedOpcode_i64_trunc_sat_f64_s:
+                            case WasmPrefixedOpcode_i64_trunc_sat_f64_u:
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_memory_init:
+                                (void)InputStream_readLeb128_u32(&in);
+                                (void)InputStream_readByte(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_data_drop:
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_memory_copy: {
+                                uint32_t dst_mem_idx = InputStream_readLeb128_u32(&in);
+                                uint32_t src_mem_idx = InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) {
+                                    uint32_t n = FuncGen_stackPop(&fg);
+                                    uint32_t src = FuncGen_stackPop(&fg);
+                                    uint32_t dst = FuncGen_stackPop(&fg);
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "memcpy(&m%" PRIu32 "[l%" PRIu32 "], "
+                                            "&m%" PRIu32 "[l%" PRIu32 "], l%" PRIu32 ");\n",
+                                            dst_mem_idx, dst, src_mem_idx, src, n);
+                                }
+                                break;
+                            }
+
+                            case WasmPrefixedOpcode_memory_fill: {
+                                uint32_t mem_idx = InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) {
+                                    uint32_t n = FuncGen_stackPop(&fg);
+                                    uint32_t c = FuncGen_stackPop(&fg);
+                                    uint32_t s = FuncGen_stackPop(&fg);
+                                    FuncGen_indent(&fg, out);
+                                    fprintf(out, "memset(&m%" PRIu32 "[l%" PRIu32 "], "
+                                            "l%" PRIu32 ", l%" PRIu32 ");\n", mem_idx, s, c, n);
+                                }
+                                break;
+                            }
+
+                            case WasmPrefixedOpcode_table_init:
+                                (void)InputStream_readLeb128_u32(&in);
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_elem_drop:
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_table_copy:
+                                (void)InputStream_readLeb128_u32(&in);
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_table_grow:
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_table_size:
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+
+                            case WasmPrefixedOpcode_table_fill:
+                                (void)InputStream_readLeb128_u32(&in);
+                                if (unreachable_depth == 0) panic("unimplemented opcode");
+                        }
+                        break;
+                }
+            }
+            for (uint32_t param_i = 0; param_i < func_type->param->len; param_i += 1) {
+                if (param_used[param_i]) continue;
+                FuncGen_indent(&fg, out);
+                fprintf(out, "(void)l%" PRIu32 ";\n", param_i);
+            }
+            switch (func_type->result->len) {
+                case 0: break;
+                case 1:
+                    FuncGen_indent(&fg, out);
+                    fprintf(out, "return l%" PRIu32 ";\n", FuncGen_stackPop(&fg));
+                    break;
+                default: panic("multiple function returns not supported");
+            }
+            fputs("}\n\n", out);
+        }
+    }
+
+    (void)InputStream_skipToSection(&in, WasmSectionId_data);
+    {
+        uint32_t len = InputStream_readLeb128_u32(&in);
+        fputs("static void init_data(void) {\n", out);
+        for (uint32_t i = 0; i < mems_len; i += 1)
+            fprintf(out, "    p%" PRIu32 " = UINT32_C(%" PRIu32 ");\n"
+                    "    m%" PRIu32 " = calloc(p%" PRIu32 ", UINT32_C(1) << 16);\n",
+                    i, mems[i].limits.min, i, i);
+        for (uint32_t segment_i = 0; segment_i < len; segment_i += 1) {
+            uint32_t mem_idx;
+            switch (InputStream_readLeb128_u32(&in)) {
+                case 0:
+                    mem_idx = 0;
+                    break;
+
+                case 2:
+                    mem_idx = InputStream_readLeb128_u32(&in);
+                    break;
+
+                default: panic("unsupported data kind");
+            }
+            uint32_t offset = evalExpr(&in);
+            uint32_t segment_len = InputStream_readLeb128_u32(&in);
+            fputc('\n', out);
+            fprintf(out, "    static const uint8_t s%" PRIu32 "[UINT32_C(%" PRIu32 ")] = {",
+                    segment_i, segment_len);
+            for (uint32_t i = 0; i < segment_len; i += 1) {
+                if (i % 32 == 0) fputs("\n       ", out);
+                fprintf(out, " 0x%02hhX,", InputStream_readByte(&in));
+            }
+            fprintf(out, "\n"
+                    "    };\n"
+                    "    memcpy(&m%" PRIu32 "[UINT32_C(0x%" PRIX32 ")], s%" PRIu32 ", UINT32_C(%" PRIu32 "));\n",
+                    mem_idx, offset, segment_i, segment_len);
+        }
+        fputs("}\n", out);
+    }
+
+    InputStream_close(&in);
+    fclose(out);
+}
stage1/zig1.c
@@ -1,4567 +0,0 @@
-// TODO get rid of _GNU_SOURCE
-#define _GNU_SOURCE
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-#include <math.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <unistd.h>
-
-#ifdef __linux__
-#include <sys/random.h>
-#endif
-
-#include <zstd.h>
-
-#if defined(__APPLE__)
-#define ZIG_TRIPLE_OS "macos"
-#elif defined(_WIN32)
-#define ZIG_TRIPLE_OS "windows"
-#elif defined(__linux__)
-#define ZIG_TRIPLE_OS "linux"
-#elif defined(__FreeBSD__)
-#define ZIG_TRIPLE_OS "freebsd"
-#elif defined(__NetBSD__)
-#define ZIG_TRIPLE_OS "netbsd"
-#elif defined(__DragonFly__)
-#define ZIG_TRIPLE_OS "dragonfly"
-#elif defined(__OpenBSD__)
-#define ZIG_TRIPLE_OS "openbsd"
-#elif defined(__HAIKU__)
-#define ZIG_TRIPLE_OS "haiku"
-#elif defined(__sun)
-#define ZIG_TRIPLE_OS "solaris"
-#else
-#error please add more os definitions above this line
-#endif
-
-#if defined(__x86_64__)
-#define ZIG_TRIPLE_ARCH "x86_64"
-#elif defined(__aarch64__)
-#define ZIG_TRIPLE_ARCH "aarch64"
-#elif defined(__ARM_EABI__)
-#define ZIG_TRIPLE_ARCH "arm"
-#else
-#error please add more arch definitions above this line
-#endif
-
-enum wasi_errno_t {
-    WASI_ESUCCESS = 0,
-    WASI_E2BIG = 1,
-    WASI_EACCES = 2,
-    WASI_EADDRINUSE = 3,
-    WASI_EADDRNOTAVAIL = 4,
-    WASI_EAFNOSUPPORT = 5,
-    WASI_EAGAIN = 6,
-    WASI_EALREADY = 7,
-    WASI_EBADF = 8,
-    WASI_EBADMSG = 9,
-    WASI_EBUSY = 10,
-    WASI_ECANCELED = 11,
-    WASI_ECHILD = 12,
-    WASI_ECONNABORTED = 13,
-    WASI_ECONNREFUSED = 14,
-    WASI_ECONNRESET = 15,
-    WASI_EDEADLK = 16,
-    WASI_EDESTADDRREQ = 17,
-    WASI_EDOM = 18,
-    WASI_EDQUOT = 19,
-    WASI_EEXIST = 20,
-    WASI_EFAULT = 21,
-    WASI_EFBIG = 22,
-    WASI_EHOSTUNREACH = 23,
-    WASI_EIDRM = 24,
-    WASI_EILSEQ = 25,
-    WASI_EINPROGRESS = 26,
-    WASI_EINTR = 27,
-    WASI_EINVAL = 28,
-    WASI_EIO = 29,
-    WASI_EISCONN = 30,
-    WASI_EISDIR = 31,
-    WASI_ELOOP = 32,
-    WASI_EMFILE = 33,
-    WASI_EMLINK = 34,
-    WASI_EMSGSIZE = 35,
-    WASI_EMULTIHOP = 36,
-    WASI_ENAMETOOLONG = 37,
-    WASI_ENETDOWN = 38,
-    WASI_ENETRESET = 39,
-    WASI_ENETUNREACH = 40,
-    WASI_ENFILE = 41,
-    WASI_ENOBUFS = 42,
-    WASI_ENODEV = 43,
-    WASI_ENOENT = 44,
-    WASI_ENOEXEC = 45,
-    WASI_ENOLCK = 46,
-    WASI_ENOLINK = 47,
-    WASI_ENOMEM = 48,
-    WASI_ENOMSG = 49,
-    WASI_ENOPROTOOPT = 50,
-    WASI_ENOSPC = 51,
-    WASI_ENOSYS = 52,
-    WASI_ENOTCONN = 53,
-    WASI_ENOTDIR = 54,
-    WASI_ENOTEMPTY = 55,
-    WASI_ENOTRECOVERABLE = 56,
-    WASI_ENOTSOCK = 57,
-    WASI_EOPNOTSUPP = 58,
-    WASI_ENOTTY = 59,
-    WASI_ENXIO = 60,
-    WASI_EOVERFLOW = 61,
-    WASI_EOWNERDEAD = 62,
-    WASI_EPERM = 63,
-    WASI_EPIPE = 64,
-    WASI_EPROTO = 65,
-    WASI_EPROTONOSUPPORT = 66,
-    WASI_EPROTOTYPE = 67,
-    WASI_ERANGE = 68,
-    WASI_EROFS = 69,
-    WASI_ESPIPE = 70,
-    WASI_ESRCH = 71,
-    WASI_ESTALE = 72,
-    WASI_ETIMEDOUT = 73,
-    WASI_ETXTBSY = 74,
-    WASI_EXDEV = 75,
-    WASI_ENOTCAPABLE = 76,
-};
-
-static void panic(const char *msg) {
-    fprintf(stderr, "%s\n", msg);
-    abort();
-}
-
-static uint32_t min_u32(uint32_t a, uint32_t b) {
-    return (a < b) ? a : b;
-}
-
-static uint32_t rotl32(uint32_t n, unsigned c) {
-    const unsigned mask = CHAR_BIT * sizeof(n) - 1;
-    c &= mask & 31;
-    return (n << c) | (n >> ((-c) & mask));
-}
-
-static uint32_t rotr32(uint32_t n, unsigned c) {
-    const unsigned mask = CHAR_BIT * sizeof(n) - 1;
-    c &= mask & 31;
-    return (n >> c) | (n << ((-c) & mask));
-}
-
-static uint64_t rotl64(uint64_t n, unsigned c) {
-    const unsigned mask = CHAR_BIT * sizeof(n) - 1;
-    c &= mask & 63;
-    return (n << c) | (n >> ((-c) & mask));
-}
-
-static uint64_t rotr64(uint64_t n, unsigned c) {
-    const unsigned mask = CHAR_BIT * sizeof(n) - 1;
-    c &= mask & 63;
-    return (n >> c) | (n << ((-c) & mask));
-}
-
-static void *arena_alloc(size_t n) {
-    void *ptr = malloc(n);
-    if (!ptr) panic("out of memory");
-#ifndef NDEBUG
-    memset(ptr, 0xaa, n); // to match the zig version
-#endif
-    return ptr;
-}
-
-static int err_wrap(const char *prefix, int rc) {
-    if (rc == -1) {
-        perror(prefix);
-        abort();
-    }
-    return rc;
-}
-
-static bool bs_isSet(const uint32_t *bitset, uint32_t index) {
-    return (bitset[index >> 5] >> (index & 0x1f)) & 1;
-}
-static void bs_set(uint32_t *bitset, uint32_t index) {
-    bitset[index >> 5] |= ((uint32_t)1 << (index & 0x1f));
-}
-static void bs_unset(uint32_t *bitset, uint32_t index) {
-    bitset[index >> 5] &= ~((uint32_t)1 << (index & 0x1f));
-}
-static void bs_setValue(uint32_t *bitset, uint32_t index, bool value) {
-    if (value) bs_set(bitset, index); else bs_unset(bitset, index);
-}
-
-struct ByteSlice {
-    char *ptr;
-    size_t len;
-};
-
-static struct ByteSlice read_file_alloc(const char *file_path) {
-    FILE *f = fopen(file_path, "rb");
-    if (!f) {
-        fprintf(stderr, "failed to read %s: ", file_path);
-        perror("");
-        abort();
-    }
-    if (fseek(f, 0L, SEEK_END) == -1) panic("failed to seek");
-    struct ByteSlice res;
-    res.len = ftell(f);
-    res.ptr = malloc(res.len);
-    rewind(f);
-    size_t amt_read = fread(res.ptr, 1, res.len, f);
-    if (amt_read != res.len) panic("short read");
-    fclose(f);
-    return res;
-}
-
-
-struct Preopen {
-    int wasi_fd;
-    int host_fd;
-    const char *name;
-    size_t name_len;
-};
-
-static struct Preopen preopens_buffer[10];
-static size_t preopens_len = 0;
-
-static void add_preopen(int wasi_fd, const char *name, int host_fd) {
-    preopens_buffer[preopens_len].wasi_fd = wasi_fd;
-    preopens_buffer[preopens_len].host_fd = host_fd;
-    preopens_buffer[preopens_len].name = name;
-    preopens_buffer[preopens_len].name_len = strlen(name);
-    preopens_len += 1;
-}
-
-static const struct Preopen *find_preopen(int32_t wasi_fd) {
-    for (size_t i = 0; i < preopens_len; i += 1) {
-        const struct Preopen *preopen = &preopens_buffer[i];
-        if (preopen->wasi_fd == wasi_fd) {
-            return preopen;
-        }
-    }
-    return NULL;
-}
-
-static const uint32_t max_memory = 2ul * 1024ul * 1024ul * 1024ul; // 2 GiB
-
-static uint16_t read_u16_le(const char *ptr) {
-    const uint8_t *u8_ptr = (const uint8_t *)ptr;
-    return
-        (((uint64_t)u8_ptr[0]) << 0x00) |
-        (((uint64_t)u8_ptr[1]) << 0x08);
-}
-
-static uint32_t read_u32_le(const char *ptr) {
-    const uint8_t *u8_ptr = (const uint8_t *)ptr;
-    return
-        (((uint64_t)u8_ptr[0]) << 0x00) |
-        (((uint64_t)u8_ptr[1]) << 0x08) |
-        (((uint64_t)u8_ptr[2]) << 0x10) |
-        (((uint64_t)u8_ptr[3]) << 0x18);
-}
-
-static uint64_t read_u64_le(const char *ptr) {
-    const uint8_t *u8_ptr = (const uint8_t *)ptr;
-    return
-        (((uint64_t)u8_ptr[0]) << 0x00) |
-        (((uint64_t)u8_ptr[1]) << 0x08) |
-        (((uint64_t)u8_ptr[2]) << 0x10) |
-        (((uint64_t)u8_ptr[3]) << 0x18) |
-        (((uint64_t)u8_ptr[4]) << 0x20) |
-        (((uint64_t)u8_ptr[5]) << 0x28) |
-        (((uint64_t)u8_ptr[6]) << 0x30) |
-        (((uint64_t)u8_ptr[7]) << 0x38);
-}
-
-static void write_u16_le(char *ptr, uint16_t x) {
-    uint8_t *u8_ptr = (uint8_t*)ptr;
-    u8_ptr[0] = (x >> 0x00);
-    u8_ptr[1] = (x >> 0x08);
-}
-
-static void write_u32_le(char *ptr, uint32_t x) {
-    uint8_t *u8_ptr = (uint8_t*)ptr;
-    u8_ptr[0] = (x >> 0x00);
-    u8_ptr[1] = (x >> 0x08);
-    u8_ptr[2] = (x >> 0x10);
-    u8_ptr[3] = (x >> 0x18);
-}
-
-static void write_u64_le(char *ptr, uint64_t x) {
-    uint8_t *u8_ptr = (uint8_t*)ptr;
-    u8_ptr[0] = (x >> 0x00);
-    u8_ptr[1] = (x >> 0x08);
-    u8_ptr[2] = (x >> 0x10);
-    u8_ptr[3] = (x >> 0x18);
-    u8_ptr[4] = (x >> 0x20);
-    u8_ptr[5] = (x >> 0x28);
-    u8_ptr[6] = (x >> 0x30);
-    u8_ptr[7] = (x >> 0x38);
-}
-
-static uint32_t read32_uleb128(const char *ptr, uint32_t *i) {
-    uint32_t result = 0;
-    uint32_t shift = 0;
-
-    for (;;) {
-        uint32_t byte = ptr[*i];
-        *i += 1;
-        result |= ((byte & 0x7f) << shift);
-        shift += 7;
-        if ((byte & 0x80) == 0) return result;
-        if (shift >= 32) panic("read32_uleb128 failed");
-    }
-}
-
-static int64_t read64_ileb128(const char *ptr, uint32_t *i) {
-    int64_t result = 0;
-    uint32_t shift = 0;
-
-    for (;;) {
-        uint64_t byte = ptr[*i];
-        *i += 1;
-        result |= ((byte & 0x7f) << shift);
-        shift += 7;
-        if ((byte & 0x80) == 0) {
-            if ((byte & 0x40) && (shift < 64)) {
-                uint64_t extend = 0;
-                result |= (~extend << shift);
-            }
-            return result;
-        }
-        if (shift >= 64) panic("read64_ileb128 failed");
-    }
-}
-
-static int32_t read32_ileb128(const char *ptr, uint32_t *i) {
-    return read64_ileb128(ptr, i);
-}
-
-static struct ByteSlice read_name(char *ptr, uint32_t *i) {
-    uint32_t len = read32_uleb128(ptr, i);
-    struct ByteSlice res;
-    res.ptr = ptr + *i;
-    res.len = len;
-    *i += len;
-    return res;
-}
-
-enum Section {
-    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,
-};
-
-enum Op {
-    Op_unreachable,
-    Op_br_void,
-    Op_br_32,
-    Op_br_64,
-    Op_br_nez_void,
-    Op_br_nez_32,
-    Op_br_nez_64,
-    Op_br_eqz_void,
-    Op_br_eqz_32,
-    Op_br_eqz_64,
-    Op_br_table_void,
-    Op_br_table_32,
-    Op_br_table_64,
-    Op_return_void,
-    Op_return_32,
-    Op_return_64,
-    Op_call_import,
-    Op_call_func,
-    Op_call_indirect,
-    Op_drop_32,
-    Op_drop_64,
-    Op_select_32,
-    Op_select_64,
-    Op_local_get_32,
-    Op_local_get_64,
-    Op_local_set_32,
-    Op_local_set_64,
-    Op_local_tee_32,
-    Op_local_tee_64,
-    Op_global_get_0_32,
-    Op_global_get_32,
-    Op_global_set_0_32,
-    Op_global_set_32,
-    Op_load_0_8,
-    Op_load_8,
-    Op_load_0_16,
-    Op_load_16,
-    Op_load_0_32,
-    Op_load_32,
-    Op_load_0_64,
-    Op_load_64,
-    Op_store_0_8,
-    Op_store_8,
-    Op_store_0_16,
-    Op_store_16,
-    Op_store_0_32,
-    Op_store_32,
-    Op_store_0_64,
-    Op_store_64,
-    Op_mem_size,
-    Op_mem_grow,
-    Op_const_0_32,
-    Op_const_0_64,
-    Op_const_1_32,
-    Op_const_1_64,
-    Op_const_32,
-    Op_const_64,
-    Op_const_umax_32,
-    Op_const_umax_64,
-    Op_eqz_32,
-    Op_eq_32,
-    Op_ne_32,
-    Op_slt_32,
-    Op_ult_32,
-    Op_sgt_32,
-    Op_ugt_32,
-    Op_sle_32,
-    Op_ule_32,
-    Op_sge_32,
-    Op_uge_32,
-    Op_eqz_64,
-    Op_eq_64,
-    Op_ne_64,
-    Op_slt_64,
-    Op_ult_64,
-    Op_sgt_64,
-    Op_ugt_64,
-    Op_sle_64,
-    Op_ule_64,
-    Op_sge_64,
-    Op_uge_64,
-    Op_feq_32,
-    Op_fne_32,
-    Op_flt_32,
-    Op_fgt_32,
-    Op_fle_32,
-    Op_fge_32,
-    Op_feq_64,
-    Op_fne_64,
-    Op_flt_64,
-    Op_fgt_64,
-    Op_fle_64,
-    Op_fge_64,
-    Op_clz_32,
-    Op_ctz_32,
-    Op_popcnt_32,
-    Op_add_32,
-    Op_sub_32,
-    Op_mul_32,
-    Op_sdiv_32,
-    Op_udiv_32,
-    Op_srem_32,
-    Op_urem_32,
-    Op_and_32,
-    Op_or_32,
-    Op_xor_32,
-    Op_shl_32,
-    Op_ashr_32,
-    Op_lshr_32,
-    Op_rol_32,
-    Op_ror_32,
-    Op_clz_64,
-    Op_ctz_64,
-    Op_popcnt_64,
-    Op_add_64,
-    Op_sub_64,
-    Op_mul_64,
-    Op_sdiv_64,
-    Op_udiv_64,
-    Op_srem_64,
-    Op_urem_64,
-    Op_and_64,
-    Op_or_64,
-    Op_xor_64,
-    Op_shl_64,
-    Op_ashr_64,
-    Op_lshr_64,
-    Op_rol_64,
-    Op_ror_64,
-    Op_fabs_32,
-    Op_fneg_32,
-    Op_ceil_32,
-    Op_floor_32,
-    Op_trunc_32,
-    Op_nearest_32,
-    Op_sqrt_32,
-    Op_fadd_32,
-    Op_fsub_32,
-    Op_fmul_32,
-    Op_fdiv_32,
-    Op_fmin_32,
-    Op_fmax_32,
-    Op_copysign_32,
-    Op_fabs_64,
-    Op_fneg_64,
-    Op_ceil_64,
-    Op_floor_64,
-    Op_trunc_64,
-    Op_nearest_64,
-    Op_sqrt_64,
-    Op_fadd_64,
-    Op_fsub_64,
-    Op_fmul_64,
-    Op_fdiv_64,
-    Op_fmin_64,
-    Op_fmax_64,
-    Op_copysign_64,
-    Op_ftos_32_32,
-    Op_ftou_32_32,
-    Op_ftos_32_64,
-    Op_ftou_32_64,
-    Op_sext_64_32,
-    Op_ftos_64_32,
-    Op_ftou_64_32,
-    Op_ftos_64_64,
-    Op_ftou_64_64,
-    Op_stof_32_32,
-    Op_utof_32_32,
-    Op_stof_32_64,
-    Op_utof_32_64,
-    Op_ftof_32_64,
-    Op_stof_64_32,
-    Op_utof_64_32,
-    Op_stof_64_64,
-    Op_utof_64_64,
-    Op_ftof_64_32,
-    Op_sext8_32,
-    Op_sext16_32,
-    Op_sext8_64,
-    Op_sext16_64,
-    Op_sext32_64,
-    Op_memcpy,
-    Op_memset,
-
-    Op_wrap_32_64 = Op_drop_32,
-    Op_zext_64_32 = Op_const_0_32,
-    Op_last = Op_memset,
-};
-
-enum WasmOp {
-    WasmOp_unreachable = 0x00,
-    WasmOp_nop = 0x01,
-    WasmOp_block = 0x02,
-    WasmOp_loop = 0x03,
-    WasmOp_if = 0x04,
-    WasmOp_else = 0x05,
-    WasmOp_end = 0x0B,
-    WasmOp_br = 0x0C,
-    WasmOp_br_if = 0x0D,
-    WasmOp_br_table = 0x0E,
-    WasmOp_return = 0x0F,
-    WasmOp_call = 0x10,
-    WasmOp_call_indirect = 0x11,
-    WasmOp_drop = 0x1A,
-    WasmOp_select = 0x1B,
-    WasmOp_local_get = 0x20,
-    WasmOp_local_set = 0x21,
-    WasmOp_local_tee = 0x22,
-    WasmOp_global_get = 0x23,
-    WasmOp_global_set = 0x24,
-    WasmOp_i32_load = 0x28,
-    WasmOp_i64_load = 0x29,
-    WasmOp_f32_load = 0x2A,
-    WasmOp_f64_load = 0x2B,
-    WasmOp_i32_load8_s = 0x2C,
-    WasmOp_i32_load8_u = 0x2D,
-    WasmOp_i32_load16_s = 0x2E,
-    WasmOp_i32_load16_u = 0x2F,
-    WasmOp_i64_load8_s = 0x30,
-    WasmOp_i64_load8_u = 0x31,
-    WasmOp_i64_load16_s = 0x32,
-    WasmOp_i64_load16_u = 0x33,
-    WasmOp_i64_load32_s = 0x34,
-    WasmOp_i64_load32_u = 0x35,
-    WasmOp_i32_store = 0x36,
-    WasmOp_i64_store = 0x37,
-    WasmOp_f32_store = 0x38,
-    WasmOp_f64_store = 0x39,
-    WasmOp_i32_store8 = 0x3A,
-    WasmOp_i32_store16 = 0x3B,
-    WasmOp_i64_store8 = 0x3C,
-    WasmOp_i64_store16 = 0x3D,
-    WasmOp_i64_store32 = 0x3E,
-    WasmOp_memory_size = 0x3F,
-    WasmOp_memory_grow = 0x40,
-    WasmOp_i32_const = 0x41,
-    WasmOp_i64_const = 0x42,
-    WasmOp_f32_const = 0x43,
-    WasmOp_f64_const = 0x44,
-    WasmOp_i32_eqz = 0x45,
-    WasmOp_i32_eq = 0x46,
-    WasmOp_i32_ne = 0x47,
-    WasmOp_i32_lt_s = 0x48,
-    WasmOp_i32_lt_u = 0x49,
-    WasmOp_i32_gt_s = 0x4A,
-    WasmOp_i32_gt_u = 0x4B,
-    WasmOp_i32_le_s = 0x4C,
-    WasmOp_i32_le_u = 0x4D,
-    WasmOp_i32_ge_s = 0x4E,
-    WasmOp_i32_ge_u = 0x4F,
-    WasmOp_i64_eqz = 0x50,
-    WasmOp_i64_eq = 0x51,
-    WasmOp_i64_ne = 0x52,
-    WasmOp_i64_lt_s = 0x53,
-    WasmOp_i64_lt_u = 0x54,
-    WasmOp_i64_gt_s = 0x55,
-    WasmOp_i64_gt_u = 0x56,
-    WasmOp_i64_le_s = 0x57,
-    WasmOp_i64_le_u = 0x58,
-    WasmOp_i64_ge_s = 0x59,
-    WasmOp_i64_ge_u = 0x5A,
-    WasmOp_f32_eq = 0x5B,
-    WasmOp_f32_ne = 0x5C,
-    WasmOp_f32_lt = 0x5D,
-    WasmOp_f32_gt = 0x5E,
-    WasmOp_f32_le = 0x5F,
-    WasmOp_f32_ge = 0x60,
-    WasmOp_f64_eq = 0x61,
-    WasmOp_f64_ne = 0x62,
-    WasmOp_f64_lt = 0x63,
-    WasmOp_f64_gt = 0x64,
-    WasmOp_f64_le = 0x65,
-    WasmOp_f64_ge = 0x66,
-    WasmOp_i32_clz = 0x67,
-    WasmOp_i32_ctz = 0x68,
-    WasmOp_i32_popcnt = 0x69,
-    WasmOp_i32_add = 0x6A,
-    WasmOp_i32_sub = 0x6B,
-    WasmOp_i32_mul = 0x6C,
-    WasmOp_i32_div_s = 0x6D,
-    WasmOp_i32_div_u = 0x6E,
-    WasmOp_i32_rem_s = 0x6F,
-    WasmOp_i32_rem_u = 0x70,
-    WasmOp_i32_and = 0x71,
-    WasmOp_i32_or = 0x72,
-    WasmOp_i32_xor = 0x73,
-    WasmOp_i32_shl = 0x74,
-    WasmOp_i32_shr_s = 0x75,
-    WasmOp_i32_shr_u = 0x76,
-    WasmOp_i32_rotl = 0x77,
-    WasmOp_i32_rotr = 0x78,
-    WasmOp_i64_clz = 0x79,
-    WasmOp_i64_ctz = 0x7A,
-    WasmOp_i64_popcnt = 0x7B,
-    WasmOp_i64_add = 0x7C,
-    WasmOp_i64_sub = 0x7D,
-    WasmOp_i64_mul = 0x7E,
-    WasmOp_i64_div_s = 0x7F,
-    WasmOp_i64_div_u = 0x80,
-    WasmOp_i64_rem_s = 0x81,
-    WasmOp_i64_rem_u = 0x82,
-    WasmOp_i64_and = 0x83,
-    WasmOp_i64_or = 0x84,
-    WasmOp_i64_xor = 0x85,
-    WasmOp_i64_shl = 0x86,
-    WasmOp_i64_shr_s = 0x87,
-    WasmOp_i64_shr_u = 0x88,
-    WasmOp_i64_rotl = 0x89,
-    WasmOp_i64_rotr = 0x8A,
-    WasmOp_f32_abs = 0x8B,
-    WasmOp_f32_neg = 0x8C,
-    WasmOp_f32_ceil = 0x8D,
-    WasmOp_f32_floor = 0x8E,
-    WasmOp_f32_trunc = 0x8F,
-    WasmOp_f32_nearest = 0x90,
-    WasmOp_f32_sqrt = 0x91,
-    WasmOp_f32_add = 0x92,
-    WasmOp_f32_sub = 0x93,
-    WasmOp_f32_mul = 0x94,
-    WasmOp_f32_div = 0x95,
-    WasmOp_f32_min = 0x96,
-    WasmOp_f32_max = 0x97,
-    WasmOp_f32_copysign = 0x98,
-    WasmOp_f64_abs = 0x99,
-    WasmOp_f64_neg = 0x9A,
-    WasmOp_f64_ceil = 0x9B,
-    WasmOp_f64_floor = 0x9C,
-    WasmOp_f64_trunc = 0x9D,
-    WasmOp_f64_nearest = 0x9E,
-    WasmOp_f64_sqrt = 0x9F,
-    WasmOp_f64_add = 0xA0,
-    WasmOp_f64_sub = 0xA1,
-    WasmOp_f64_mul = 0xA2,
-    WasmOp_f64_div = 0xA3,
-    WasmOp_f64_min = 0xA4,
-    WasmOp_f64_max = 0xA5,
-    WasmOp_f64_copysign = 0xA6,
-    WasmOp_i32_wrap_i64 = 0xA7,
-    WasmOp_i32_trunc_f32_s = 0xA8,
-    WasmOp_i32_trunc_f32_u = 0xA9,
-    WasmOp_i32_trunc_f64_s = 0xAA,
-    WasmOp_i32_trunc_f64_u = 0xAB,
-    WasmOp_i64_extend_i32_s = 0xAC,
-    WasmOp_i64_extend_i32_u = 0xAD,
-    WasmOp_i64_trunc_f32_s = 0xAE,
-    WasmOp_i64_trunc_f32_u = 0xAF,
-    WasmOp_i64_trunc_f64_s = 0xB0,
-    WasmOp_i64_trunc_f64_u = 0xB1,
-    WasmOp_f32_convert_i32_s = 0xB2,
-    WasmOp_f32_convert_i32_u = 0xB3,
-    WasmOp_f32_convert_i64_s = 0xB4,
-    WasmOp_f32_convert_i64_u = 0xB5,
-    WasmOp_f32_demote_f64 = 0xB6,
-    WasmOp_f64_convert_i32_s = 0xB7,
-    WasmOp_f64_convert_i32_u = 0xB8,
-    WasmOp_f64_convert_i64_s = 0xB9,
-    WasmOp_f64_convert_i64_u = 0xBA,
-    WasmOp_f64_promote_f32 = 0xBB,
-    WasmOp_i32_reinterpret_f32 = 0xBC,
-    WasmOp_i64_reinterpret_f64 = 0xBD,
-    WasmOp_f32_reinterpret_i32 = 0xBE,
-    WasmOp_f64_reinterpret_i64 = 0xBF,
-    WasmOp_i32_extend8_s = 0xC0,
-    WasmOp_i32_extend16_s = 0xC1,
-    WasmOp_i64_extend8_s = 0xC2,
-    WasmOp_i64_extend16_s = 0xC3,
-    WasmOp_i64_extend32_s = 0xC4,
-
-    WasmOp_prefixed = 0xFC,
-};
-
-enum WasmPrefixedOp {
-    WasmPrefixedOp_i32_trunc_sat_f32_s = 0x00,
-    WasmPrefixedOp_i32_trunc_sat_f32_u = 0x01,
-    WasmPrefixedOp_i32_trunc_sat_f64_s = 0x02,
-    WasmPrefixedOp_i32_trunc_sat_f64_u = 0x03,
-    WasmPrefixedOp_i64_trunc_sat_f32_s = 0x04,
-    WasmPrefixedOp_i64_trunc_sat_f32_u = 0x05,
-    WasmPrefixedOp_i64_trunc_sat_f64_s = 0x06,
-    WasmPrefixedOp_i64_trunc_sat_f64_u = 0x07,
-    WasmPrefixedOp_memory_init = 0x08,
-    WasmPrefixedOp_data_drop = 0x09,
-    WasmPrefixedOp_memory_copy = 0x0A,
-    WasmPrefixedOp_memory_fill = 0x0B,
-    WasmPrefixedOp_table_init = 0x0C,
-    WasmPrefixedOp_elem_drop = 0x0D,
-    WasmPrefixedOp_table_copy = 0x0E,
-    WasmPrefixedOp_table_grow = 0x0F,
-    WasmPrefixedOp_table_size = 0x10,
-    WasmPrefixedOp_table_fill = 0x11,
-};
-
-static const uint32_t wasm_page_size = 64 * 1024;
-
-struct ProgramCounter {
-    uint32_t opcode;
-    uint32_t operand;
-};
-
-struct TypeInfo {
-    uint32_t param_count;
-    // bitset with param_count bits, indexed from lsb, 0 -> 32-bit, 1 -> 64-bit
-    uint32_t param_types;
-    uint32_t result_count;
-    // bitset with result_count bits, indexed from lsb, 0 -> 32-bit, 1 -> 64-bit
-    uint32_t result_types;
-};
-
-struct Function {
-    uint32_t id;
-    // Index to start of code in opcodes/operands.
-    struct ProgramCounter entry_pc;
-    uint32_t type_idx;
-    uint32_t locals_size;
-};
-
-enum ImpMod {
-    ImpMod_wasi_snapshot_preview1,
-};
-
-enum ImpName {
-    ImpName_args_get,
-    ImpName_args_sizes_get,
-    ImpName_clock_time_get,
-    ImpName_debug,
-    ImpName_debug_slice,
-    ImpName_environ_get,
-    ImpName_environ_sizes_get,
-    ImpName_fd_close,
-    ImpName_fd_fdstat_get,
-    ImpName_fd_filestat_get,
-    ImpName_fd_filestat_set_size,
-    ImpName_fd_filestat_set_times,
-    ImpName_fd_pread,
-    ImpName_fd_prestat_dir_name,
-    ImpName_fd_prestat_get,
-    ImpName_fd_pwrite,
-    ImpName_fd_read,
-    ImpName_fd_readdir,
-    ImpName_fd_write,
-    ImpName_path_create_directory,
-    ImpName_path_filestat_get,
-    ImpName_path_open,
-    ImpName_path_remove_directory,
-    ImpName_path_rename,
-    ImpName_path_unlink_file,
-    ImpName_proc_exit,
-    ImpName_random_get,
-};
-
-struct Import {
-    enum ImpMod mod;
-    enum ImpName name;
-    uint32_t type_idx;
-};
-
-struct VirtualMachine {
-    uint32_t *stack;
-    /// Points to one after the last stack item.
-    uint32_t stack_top;
-    struct ProgramCounter pc;
-    /// Actual memory usage of the WASI code. The capacity is max_memory.
-    uint32_t memory_len;
-    const char *mod_ptr;
-    uint8_t *opcodes;
-    uint32_t *operands;
-    struct Function *functions;
-    /// Type index to start of type in module_bytes.
-    struct TypeInfo *types;
-    uint64_t *globals;
-    char *memory;
-    struct Import *imports;
-    uint32_t imports_len;
-    const char **args;
-    uint32_t *table;
-};
-
-static int to_host_fd(int32_t wasi_fd) {
-    const struct Preopen *preopen = find_preopen(wasi_fd);
-    if (!preopen) return wasi_fd;
-    return preopen->host_fd;
-}
-
-static enum wasi_errno_t to_wasi_err(int err) {
-    switch (err) {
-        case E2BIG: return WASI_E2BIG;
-        case EACCES: return WASI_EACCES;
-        case EADDRINUSE: return WASI_EADDRINUSE;
-        case EADDRNOTAVAIL: return WASI_EADDRNOTAVAIL;
-        case EAFNOSUPPORT: return WASI_EAFNOSUPPORT;
-        case EAGAIN: return WASI_EAGAIN;
-        case EALREADY: return WASI_EALREADY;
-        case EBADF: return WASI_EBADF;
-        case EBADMSG: return WASI_EBADMSG;
-        case EBUSY: return WASI_EBUSY;
-        case ECANCELED: return WASI_ECANCELED;
-        case ECHILD: return WASI_ECHILD;
-        case ECONNABORTED: return WASI_ECONNABORTED;
-        case ECONNREFUSED: return WASI_ECONNREFUSED;
-        case ECONNRESET: return WASI_ECONNRESET;
-        case EDEADLK: return WASI_EDEADLK;
-        case EDESTADDRREQ: return WASI_EDESTADDRREQ;
-        case EDOM: return WASI_EDOM;
-        case EDQUOT: return WASI_EDQUOT;
-        case EEXIST: return WASI_EEXIST;
-        case EFAULT: return WASI_EFAULT;
-        case EFBIG: return WASI_EFBIG;
-        case EHOSTUNREACH: return WASI_EHOSTUNREACH;
-        case EIDRM: return WASI_EIDRM;
-        case EILSEQ: return WASI_EILSEQ;
-        case EINPROGRESS: return WASI_EINPROGRESS;
-        case EINTR: return WASI_EINTR;
-        case EINVAL: return WASI_EINVAL;
-        case EIO: return WASI_EIO;
-        case EISCONN: return WASI_EISCONN;
-        case EISDIR: return WASI_EISDIR;
-        case ELOOP: return WASI_ELOOP;
-        case EMFILE: return WASI_EMFILE;
-        case EMLINK: return WASI_EMLINK;
-        case EMSGSIZE: return WASI_EMSGSIZE;
-        case EMULTIHOP: return WASI_EMULTIHOP;
-        case ENAMETOOLONG: return WASI_ENAMETOOLONG;
-        case ENETDOWN: return WASI_ENETDOWN;
-        case ENETRESET: return WASI_ENETRESET;
-        case ENETUNREACH: return WASI_ENETUNREACH;
-        case ENFILE: return WASI_ENFILE;
-        case ENOBUFS: return WASI_ENOBUFS;
-        case ENODEV: return WASI_ENODEV;
-        case ENOENT: return WASI_ENOENT;
-        case ENOEXEC: return WASI_ENOEXEC;
-        case ENOLCK: return WASI_ENOLCK;
-        case ENOLINK: return WASI_ENOLINK;
-        case ENOMEM: return WASI_ENOMEM;
-        case ENOMSG: return WASI_ENOMSG;
-        case ENOPROTOOPT: return WASI_ENOPROTOOPT;
-        case ENOSPC: return WASI_ENOSPC;
-        case ENOSYS: return WASI_ENOSYS;
-        case ENOTCONN: return WASI_ENOTCONN;
-        case ENOTDIR: return WASI_ENOTDIR;
-        case ENOTEMPTY: return WASI_ENOTEMPTY;
-        case ENOTRECOVERABLE: return WASI_ENOTRECOVERABLE;
-        case ENOTSOCK: return WASI_ENOTSOCK;
-        case EOPNOTSUPP: return WASI_EOPNOTSUPP;
-        case ENOTTY: return WASI_ENOTTY;
-        case ENXIO: return WASI_ENXIO;
-        case EOVERFLOW: return WASI_EOVERFLOW;
-        case EOWNERDEAD: return WASI_EOWNERDEAD;
-        case EPERM: return WASI_EPERM;
-        case EPIPE: return WASI_EPIPE;
-        case EPROTO: return WASI_EPROTO;
-        case EPROTONOSUPPORT: return WASI_EPROTONOSUPPORT;
-        case EPROTOTYPE: return WASI_EPROTOTYPE;
-        case ERANGE: return WASI_ERANGE;
-        case EROFS: return WASI_EROFS;
-        case ESPIPE: return WASI_ESPIPE;
-        case ESRCH: return WASI_ESRCH;
-        case ESTALE: return WASI_ESTALE;
-        case ETIMEDOUT: return WASI_ETIMEDOUT;
-        case ETXTBSY: return WASI_ETXTBSY;
-        case EXDEV: return WASI_EXDEV;
-        default:
-        fprintf(stderr, "unexpected errno: %s\n", strerror(err));
-        abort();
-    };
-}
-
-enum wasi_filetype_t {
-    wasi_filetype_t_UNKNOWN,
-    wasi_filetype_t_BLOCK_DEVICE,
-    wasi_filetype_t_CHARACTER_DEVICE,
-    wasi_filetype_t_DIRECTORY,
-    wasi_filetype_t_REGULAR_FILE,
-    wasi_filetype_t_SOCKET_DGRAM,
-    wasi_filetype_t_SOCKET_STREAM,
-    wasi_filetype_t_SYMBOLIC_LINK,
-};
-
-static const uint16_t WASI_O_CREAT = 0x0001;
-static const uint16_t WASI_O_DIRECTORY = 0x0002;
-static const uint16_t WASI_O_EXCL = 0x0004;
-static const uint16_t WASI_O_TRUNC = 0x0008;
-
-static const uint16_t WASI_FDFLAG_APPEND = 0x0001;
-static const uint16_t WASI_FDFLAG_DSYNC = 0x0002;
-static const uint16_t WASI_FDFLAG_NONBLOCK = 0x0004;
-static const uint16_t WASI_FDFLAG_SYNC = 0x0010;
-
-static const uint64_t WASI_RIGHT_FD_READ = 0x0000000000000002ull;
-static const uint64_t WASI_RIGHT_FD_WRITE = 0x0000000000000040ull;
-
-static enum wasi_filetype_t to_wasi_filetype(mode_t st_mode) {
-    switch (st_mode & S_IFMT) {
-        case S_IFBLK:
-            return wasi_filetype_t_BLOCK_DEVICE;
-        case S_IFCHR:
-            return wasi_filetype_t_CHARACTER_DEVICE;
-        case S_IFDIR:
-            return wasi_filetype_t_DIRECTORY;
-        case S_IFLNK:
-            return wasi_filetype_t_SYMBOLIC_LINK;
-        case S_IFREG:
-            return wasi_filetype_t_REGULAR_FILE;
-        default:
-            return wasi_filetype_t_UNKNOWN;
-    }
-}
-
-static uint64_t to_wasi_timestamp(struct timespec ts) {
-    return ts.tv_sec * 1000000000ull + ts.tv_nsec;
-}
-
-/// const filestat_t = extern struct {
-///     dev: device_t, u64
-///     ino: inode_t, u64
-///     filetype: filetype_t, u8
-///     nlink: linkcount_t, u64
-///     size: filesize_t, u64
-///     atim: timestamp_t, u64
-///     mtim: timestamp_t, u64
-///     ctim: timestamp_t, u64
-/// };
-static enum wasi_errno_t finish_wasi_stat(struct VirtualMachine *vm,
-        uint32_t buf, struct stat st)
-{
-    write_u64_le(vm->memory + buf + 0x00, 0); // device
-    write_u64_le(vm->memory + buf + 0x08, st.st_ino);
-    write_u64_le(vm->memory + buf + 0x10, to_wasi_filetype(st.st_mode));
-    write_u64_le(vm->memory + buf + 0x18, 1); // nlink
-    write_u64_le(vm->memory + buf + 0x20, st.st_size);
-#if defined(__APPLE__)
-    write_u64_le(vm->memory + buf + 0x28, to_wasi_timestamp(st.st_atimespec));
-    write_u64_le(vm->memory + buf + 0x30, to_wasi_timestamp(st.st_mtimespec));
-    write_u64_le(vm->memory + buf + 0x38, to_wasi_timestamp(st.st_ctimespec));
-#else
-    write_u64_le(vm->memory + buf + 0x28, to_wasi_timestamp(st.st_atim));
-    write_u64_le(vm->memory + buf + 0x30, to_wasi_timestamp(st.st_mtim));
-    write_u64_le(vm->memory + buf + 0x38, to_wasi_timestamp(st.st_ctim));
-#endif
-    return WASI_ESUCCESS;
-}
-
-/// fn args_sizes_get(argc: *usize, argv_buf_size: *usize) errno_t;
-static enum wasi_errno_t wasi_args_sizes_get(struct VirtualMachine *vm,
-    uint32_t argc, uint32_t argv_buf_size)
-{
-    uint32_t args_len = 0;
-    size_t buf_size = 0;
-    while (vm->args[args_len]) {
-        buf_size += strlen(vm->args[args_len]) + 1;
-        args_len += 1;
-    }
-    write_u32_le(vm->memory + argc, args_len);
-    write_u32_le(vm->memory + argv_buf_size, buf_size);
-    return WASI_ESUCCESS;
-}
-
-/// extern fn args_get(argv: [*][*:0]u8, argv_buf: [*]u8) errno_t;
-static enum wasi_errno_t wasi_args_get(struct VirtualMachine *vm,
-    uint32_t argv, uint32_t argv_buf)
-{
-    uint32_t argv_buf_i = 0;
-    uint32_t arg_i = 0;
-    for (;; arg_i += 1) {
-        const char *arg = vm->args[arg_i];
-        if (!arg) break;
-        // Write the arg to the buffer.
-        uint32_t argv_ptr = argv_buf + argv_buf_i;
-        uint32_t arg_len = strlen(arg) + 1;
-        memcpy(vm->memory + argv_buf + argv_buf_i, arg, arg_len);
-        argv_buf_i += arg_len;
-
-        write_u32_le(vm->memory + argv + 4 * arg_i , argv_ptr);
-    }
-    return WASI_ESUCCESS;
-}
-
-/// extern fn random_get(buf: [*]u8, buf_len: usize) errno_t;
-static enum wasi_errno_t wasi_random_get(struct VirtualMachine *vm,
-    uint32_t buf, uint32_t buf_len)
-{
-#ifdef __linux__
-    if (getrandom(vm->memory + buf, buf_len, 0) != buf_len) {
-        panic("getrandom failed");
-    }
-#else
-    for (uint32_t i = 0; i < buf_len; i += 1) {
-        vm->memory[buf + i] = rand();
-    }
-#endif
-    return WASI_ESUCCESS;
-}
-
-/// fn fd_prestat_get(fd: fd_t, buf: *prestat_t) errno_t;
-/// const prestat_t = extern struct {
-///     pr_type: u8,
-///     u: usize,
-/// };
-static enum wasi_errno_t wasi_fd_prestat_get(struct VirtualMachine *vm,
-    int32_t fd, uint32_t buf)
-{
-    const struct Preopen *preopen = find_preopen(fd);
-    if (!preopen) return WASI_EBADF;
-    write_u32_le(vm->memory + buf + 0, 0);
-    write_u32_le(vm->memory + buf + 4, preopen->name_len);
-    return WASI_ESUCCESS;
-}
-
-/// fn fd_prestat_dir_name(fd: fd_t, path: [*]u8, path_len: usize) errno_t;
-static enum wasi_errno_t wasi_fd_prestat_dir_name(struct VirtualMachine *vm,
-        int32_t fd, uint32_t path, uint32_t path_len)
-{
-    const struct Preopen *preopen = find_preopen(fd);
-    if (!preopen) return WASI_EBADF;
-    if (path_len != preopen->name_len)
-        panic("wasi_fd_prestat_dir_name expects correct name_len");
-    memcpy(vm->memory + path, preopen->name, path_len);
-    return WASI_ESUCCESS;
-}
-
-/// extern fn fd_close(fd: fd_t) errno_t;
-static enum wasi_errno_t wasi_fd_close(struct VirtualMachine *vm, int32_t fd) {
-    int host_fd = to_host_fd(fd);
-    close(host_fd);
-    return WASI_ESUCCESS;
-}
-
-static enum wasi_errno_t wasi_fd_read(
-    struct VirtualMachine *vm,
-    int32_t fd,
-    uint32_t iovs, // [*]const iovec_t
-    uint32_t iovs_len, // usize
-    uint32_t nread // *usize
-) {
-    int host_fd = to_host_fd(fd);
-    uint32_t i = 0;
-    size_t total_read = 0;
-    for (; i < iovs_len; i += 1) {
-        uint32_t ptr = read_u32_le(vm->memory + iovs + i * 8 + 0);
-        uint32_t len = read_u32_le(vm->memory + iovs + i * 8 + 4);
-        ssize_t amt_read = read(host_fd, vm->memory + ptr, len);
-        if (amt_read < 0) return to_wasi_err(errno);
-        total_read += amt_read;
-        if (amt_read != len) break;
-    }
-    write_u32_le(vm->memory + nread, total_read);
-    return WASI_ESUCCESS;
-}
-
-/// extern fn fd_write(fd: fd_t, iovs: [*]const ciovec_t, iovs_len: usize, nwritten: *usize) errno_t;
-/// const ciovec_t = extern struct {
-///     base: [*]const u8,
-///     len: usize,
-/// };
-static enum wasi_errno_t wasi_fd_write(struct VirtualMachine *vm,
-        int32_t fd, uint32_t iovs, uint32_t iovs_len, uint32_t nwritten)
-{
-    int host_fd = to_host_fd(fd);
-    size_t total_written = 0;
-    for (uint32_t i = 0; i < iovs_len; i += 1) {
-        uint32_t ptr = read_u32_le(vm->memory + iovs + i * 8 + 0);
-        uint32_t len = read_u32_le(vm->memory + iovs + i * 8 + 4);
-        ssize_t written = write(host_fd, vm->memory + ptr, len);
-        if (written < 0) return to_wasi_err(errno);
-        total_written += written;
-        if (written != len) break;
-    }
-    write_u32_le(vm->memory + nwritten, total_written);
-    return WASI_ESUCCESS;
-}
-
-static enum wasi_errno_t wasi_fd_pwrite(
-    struct VirtualMachine *vm,
-    int32_t fd,
-    uint32_t iovs, // [*]const ciovec_t
-    uint32_t iovs_len, // usize
-    uint64_t offset, // wasi.filesize_t,
-    uint32_t written_ptr // *usize
-) {
-    int host_fd = to_host_fd(fd);
-    uint32_t i = 0;
-    size_t written = 0;
-    for (; i < iovs_len; i += 1) {
-        uint32_t ptr = read_u32_le(vm->memory + iovs + i * 8 + 0);
-        uint32_t len = read_u32_le(vm->memory + iovs + i * 8 + 4);
-        ssize_t w = pwrite(host_fd, vm->memory + ptr, len, offset + written);
-        if (w < 0) return to_wasi_err(errno);
-        written += w;
-        if (w != len) break;
-    }
-    write_u32_le(vm->memory + written_ptr, written);
-    return WASI_ESUCCESS;
-}
-
-///extern fn path_open(
-///    dirfd: fd_t,
-///    dirflags: lookupflags_t,
-///    path: [*]const u8,
-///    path_len: usize,
-///    oflags: oflags_t,
-///    fs_rights_base: rights_t,
-///    fs_rights_inheriting: rights_t,
-///    fs_flags: fdflags_t,
-///    fd: *fd_t,
-///) errno_t;
-static enum wasi_errno_t wasi_path_open(
-    struct VirtualMachine *vm,
-    int32_t dirfd,
-    uint32_t dirflags, // wasi.lookupflags_t,
-    uint32_t path,
-    uint32_t path_len,
-    uint16_t oflags, // wasi.oflags_t,
-    uint64_t fs_rights_base, // wasi.rights_t,
-    uint64_t fs_rights_inheriting, // wasi.rights_t,
-    uint16_t fs_flags, // wasi.fdflags_t,
-    uint32_t fd
-) {
-    char sub_path[PATH_MAX];
-    memcpy(sub_path, vm->memory + path, path_len);
-    sub_path[path_len] = 0;
-
-    int host_fd = to_host_fd(dirfd);
-    uint32_t flags =
-        (((oflags & WASI_O_CREAT) != 0) ? O_CREAT : 0) |
-        (((oflags & WASI_O_DIRECTORY) != 0) ? O_DIRECTORY : 0) |
-        (((oflags & WASI_O_EXCL) != 0) ? O_EXCL : 0) |
-        (((oflags & WASI_O_TRUNC) != 0) ? O_TRUNC : 0) |
-        (((fs_flags & WASI_FDFLAG_APPEND) != 0) ? O_APPEND : 0) |
-        (((fs_flags & WASI_FDFLAG_DSYNC) != 0) ? O_DSYNC : 0) |
-        (((fs_flags & WASI_FDFLAG_NONBLOCK) != 0) ? O_NONBLOCK : 0) |
-        (((fs_flags & WASI_FDFLAG_SYNC) != 0) ? O_SYNC : 0);
-
-    if (((fs_rights_base & WASI_RIGHT_FD_READ) != 0) &&
-        ((fs_rights_base & WASI_RIGHT_FD_WRITE) != 0))
-    {
-        flags |= O_RDWR;
-    } else if ((fs_rights_base & WASI_RIGHT_FD_WRITE) != 0) {
-        flags |= O_WRONLY;
-    } else if ((fs_rights_base & WASI_RIGHT_FD_READ) != 0) {
-        flags |= O_RDONLY; // no-op because O_RDONLY is 0
-    }
-    mode_t mode = 0644;
-    int res_fd = openat(host_fd, sub_path, flags, mode);
-    if (res_fd == -1) return to_wasi_err(errno);
-    write_u32_le(vm->memory + fd, res_fd);
-    return WASI_ESUCCESS;
-}
-
-static enum wasi_errno_t wasi_path_filestat_get(
-    struct VirtualMachine *vm,
-    int32_t fd,
-    uint32_t flags, // wasi.lookupflags_t,
-    uint32_t path, // [*]const u8
-    uint32_t path_len, // usize
-    uint32_t buf // *filestat_t
-) {
-    char sub_path[PATH_MAX];
-    memcpy(sub_path, vm->memory + path, path_len);
-    sub_path[path_len] = 0;
-
-    int host_fd = to_host_fd(fd);
-    struct stat st;
-    if (fstatat(host_fd, sub_path, &st, 0) == -1) return to_wasi_err(errno);
-    return finish_wasi_stat(vm, buf, st);
-}
-
-/// extern fn path_create_directory(fd: fd_t, path: [*]const u8, path_len: usize) errno_t;
-static enum wasi_errno_t wasi_path_create_directory(struct VirtualMachine *vm,
-        int32_t wasi_fd, uint32_t path, uint32_t path_len)
-{
-    char sub_path[PATH_MAX];
-    memcpy(sub_path, vm->memory + path, path_len);
-    sub_path[path_len] = 0;
-
-    int host_fd = to_host_fd(wasi_fd);
-    if (mkdirat(host_fd, sub_path, 0777) == -1) return to_wasi_err(errno);
-    return WASI_ESUCCESS;
-}
-
-static enum wasi_errno_t wasi_path_rename(
-    struct VirtualMachine *vm,
-    int32_t old_fd,
-    uint32_t old_path_ptr, // [*]const u8
-    uint32_t old_path_len, // usize
-    int32_t new_fd,
-    uint32_t new_path_ptr, // [*]const u8
-    uint32_t new_path_len // usize
-) {
-    char old_path[PATH_MAX];
-    memcpy(old_path, vm->memory + old_path_ptr, old_path_len);
-    old_path[old_path_len] = 0;
-
-    char new_path[PATH_MAX];
-    memcpy(new_path, vm->memory + new_path_ptr, new_path_len);
-    new_path[new_path_len] = 0;
-
-    int old_host_fd = to_host_fd(old_fd);
-    int new_host_fd = to_host_fd(new_fd);
-    if (renameat(old_host_fd, old_path, new_host_fd, new_path) == -1) return to_wasi_err(errno);
-    return WASI_ESUCCESS;
-}
-
-/// extern fn fd_filestat_get(fd: fd_t, buf: *filestat_t) errno_t;
-static enum wasi_errno_t wasi_fd_filestat_get(struct VirtualMachine *vm, int32_t fd, uint32_t buf) {
-    int host_fd = to_host_fd(fd);
-    struct stat st;
-    if (fstat(host_fd, &st) == -1) return to_wasi_err(errno);
-    return finish_wasi_stat(vm, buf, st);
-}
-
-static enum wasi_errno_t wasi_fd_filestat_set_size( struct VirtualMachine *vm,
-        int32_t fd, uint64_t size)
-{
-    int host_fd = to_host_fd(fd);
-    if (ftruncate(host_fd, size) == -1) return to_wasi_err(errno);
-    return WASI_ESUCCESS;
-}
-
-/// pub extern "wasi_snapshot_preview1" fn fd_fdstat_get(fd: fd_t, buf: *fdstat_t) errno_t;
-/// pub const fdstat_t = extern struct {
-///     fs_filetype: filetype_t, u8
-///     fs_flags: fdflags_t, u16
-///     fs_rights_base: rights_t, u64
-///     fs_rights_inheriting: rights_t, u64
-/// };
-static enum wasi_errno_t wasi_fd_fdstat_get(struct VirtualMachine *vm, int32_t fd, uint32_t buf) {
-    int host_fd = to_host_fd(fd);
-    struct stat st;
-    if (fstat(host_fd, &st) == -1) return to_wasi_err(errno);
-    write_u16_le(vm->memory + buf + 0x00, to_wasi_filetype(st.st_mode));
-    write_u16_le(vm->memory + buf + 0x02, 0); // flags
-    write_u64_le(vm->memory + buf + 0x08, UINT64_MAX); // rights_base
-    write_u64_le(vm->memory + buf + 0x10, UINT64_MAX); // rights_inheriting
-    return WASI_ESUCCESS;
-}
-
-/// extern fn clock_time_get(clock_id: clockid_t, precision: timestamp_t, timestamp: *timestamp_t) errno_t;
-static enum wasi_errno_t wasi_clock_time_get(struct VirtualMachine *vm,
-        uint32_t clock_id, uint64_t precision, uint32_t timestamp)
-{
-    if (clock_id != 1) panic("expected wasi_clock_time_get to use CLOCK_MONOTONIC");
-    struct timespec ts;
-    if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) return to_wasi_err(errno);
-    uint64_t wasi_ts = to_wasi_timestamp(ts);
-    write_u64_le(vm->memory + timestamp, wasi_ts);
-    return WASI_ESUCCESS;
-}
-
-///pub extern "wasi_snapshot_preview1" fn debug(string: [*:0]const u8, x: u64) void;
-void wasi_debug(struct VirtualMachine *vm, uint32_t text, uint64_t n) {
-    fprintf(stderr, "wasi_debug: '%s' number=%" PRIu64 " %" PRIx64 "\n", vm->memory + text, n, n);
-}
-
-/// pub extern "wasi_snapshot_preview1" fn debug_slice(ptr: [*]const u8, len: usize) void;
-void wasi_debug_slice(struct VirtualMachine *vm, uint32_t ptr, uint32_t len) {
-    fprintf(stderr, "wasi_debug_slice: '%.*s'\n", len, vm->memory + ptr);
-}
-
-enum StackType {
-    ST_32,
-    ST_64,
-};
-
-struct Label {
-    enum WasmOp opcode;
-    uint32_t stack_index;
-    uint32_t stack_offset;
-    struct TypeInfo type_info;
-    // this is a UINT32_MAX terminated linked list that is stored in the operands array
-    uint32_t ref_list;
-    union {
-        struct ProgramCounter loop_pc;
-        uint32_t else_ref;
-    } extra;
-};
-
-static uint32_t Label_operandCount(const struct Label *label) {
-    if (label->opcode == WasmOp_loop) {
-        return label->type_info.param_count;
-    } else {
-        return label->type_info.result_count;
-    }
-}
-
-static enum StackType Label_operandType(const struct Label *label, uint32_t index) {
-    if (label->opcode == WasmOp_loop) {
-        return bs_isSet(&label->type_info.param_types, index);
-    } else {
-        return bs_isSet(&label->type_info.result_types, index);
-    }
-}
-
-#define max_stack_depth (1 << 12)
-
-struct StackInfo {
-    uint32_t top_index;
-    uint32_t top_offset;
-    uint32_t types[max_stack_depth >> 5];
-    uint32_t offsets[max_stack_depth];
-};
-
-static enum StackType si_top(const struct StackInfo *si) {
-    return bs_isSet(si->types, si->top_index - 1);
-}
-
-static enum StackType si_local(const struct StackInfo *si, uint32_t local_idx) {
-    return bs_isSet(si->types, local_idx);
-}
-
-static void si_push(struct StackInfo *si, enum StackType entry_type) {
-    bs_setValue(si->types, si->top_index, entry_type);
-    si->offsets[si->top_index] = si->top_offset;
-    si->top_index += 1;
-    si->top_offset += 1 + entry_type;
-}
-
-static void si_pop(struct StackInfo *si, enum StackType entry_type) {
-    assert(si_top(si) == entry_type);
-    si->top_index -= 1;
-    si->top_offset -= 1 + entry_type;
-    assert(si->top_offset == si->offsets[si->top_index]);
-}
-
-static void vm_decodeCode(struct VirtualMachine *vm, struct TypeInfo *func_type_info,
-    uint32_t *code_i, struct ProgramCounter *pc, struct StackInfo *stack)
-{
-    const char *mod_ptr = vm->mod_ptr;
-    uint8_t *opcodes = vm->opcodes;
-    uint32_t *operands = vm->operands;
-
-    // push return address
-    uint32_t frame_size = stack->top_offset;
-    si_push(stack, ST_32);
-    si_push(stack, ST_32);
-
-    uint32_t unreachable_depth = 0;
-    uint32_t label_i = 0;
-    static struct Label labels[1 << 9];
-#ifndef NDEBUG
-    memset(labels, 0xaa, sizeof(struct Label) * (1 << 9)); // to match the zig version
-#endif
-    labels[label_i].opcode = WasmOp_block;
-    labels[label_i].stack_index = stack->top_index;
-    labels[label_i].stack_offset = stack->top_offset;
-    labels[label_i].type_info = *func_type_info;
-    labels[label_i].ref_list = UINT32_MAX;
-
-    enum {
-        State_default,
-        State_bool_not,
-    } state = State_default;
-
-    for (;;) {
-        assert(stack->top_index >= labels[0].stack_index);
-        assert(stack->top_offset >= labels[0].stack_offset);
-        enum WasmOp opcode = (uint8_t)mod_ptr[*code_i];
-        *code_i += 1;
-        enum WasmPrefixedOp prefixed_opcode;
-        if (opcode == WasmOp_prefixed) prefixed_opcode = read32_uleb128(mod_ptr, code_i);
-
-        //fprintf(stderr, "decodeCode opcode=0x%x pc=%u:%u\n", opcode, pc->opcode, pc->operand);
-        //struct ProgramCounter old_pc = *pc;
-
-        if (unreachable_depth == 0)
-            switch (opcode) {
-                case WasmOp_unreachable:
-                case WasmOp_nop:
-                case WasmOp_block:
-                case WasmOp_loop:
-                case WasmOp_else:
-                case WasmOp_end:
-                case WasmOp_br:
-                case WasmOp_return:
-                case WasmOp_call:
-                case WasmOp_local_get:
-                case WasmOp_local_set:
-                case WasmOp_local_tee:
-                case WasmOp_global_get:
-                case WasmOp_global_set:
-                case WasmOp_drop:
-                case WasmOp_select:
-                break; // handled manually below
-
-                case WasmOp_if:
-                case WasmOp_br_if:
-                case WasmOp_br_table:
-                case WasmOp_call_indirect:
-                si_pop(stack, ST_32);
-                break;
-
-                case WasmOp_memory_size:
-                case WasmOp_i32_const:
-                case WasmOp_f32_const:
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i64_const:
-                case WasmOp_f64_const:
-                si_push(stack, ST_64);
-                break;
-
-                case WasmOp_i32_load:
-                case WasmOp_f32_load:
-                case WasmOp_i32_load8_s:
-                case WasmOp_i32_load8_u:
-                case WasmOp_i32_load16_s:
-                case WasmOp_i32_load16_u:
-                si_pop(stack, ST_32);
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i64_load:
-                case WasmOp_f64_load:
-                case WasmOp_i64_load8_s:
-                case WasmOp_i64_load8_u:
-                case WasmOp_i64_load16_s:
-                case WasmOp_i64_load16_u:
-                case WasmOp_i64_load32_s:
-                case WasmOp_i64_load32_u:
-                si_pop(stack, ST_32);
-                si_push(stack, ST_64);
-                break;
-
-                case WasmOp_memory_grow:
-                case WasmOp_i32_eqz:
-                case WasmOp_i32_clz:
-                case WasmOp_i32_ctz:
-                case WasmOp_i32_popcnt:
-                case WasmOp_f32_abs:
-                case WasmOp_f32_neg:
-                case WasmOp_f32_ceil:
-                case WasmOp_f32_floor:
-                case WasmOp_f32_trunc:
-                case WasmOp_f32_nearest:
-                case WasmOp_f32_sqrt:
-                case WasmOp_i32_trunc_f32_s:
-                case WasmOp_i32_trunc_f32_u:
-                case WasmOp_f32_convert_i32_s:
-                case WasmOp_f32_convert_i32_u:
-                case WasmOp_i32_reinterpret_f32:
-                case WasmOp_f32_reinterpret_i32:
-                case WasmOp_i32_extend8_s:
-                case WasmOp_i32_extend16_s:
-                si_pop(stack, ST_32);
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i64_eqz:
-                case WasmOp_i32_wrap_i64:
-                case WasmOp_i32_trunc_f64_s:
-                case WasmOp_i32_trunc_f64_u:
-                case WasmOp_f32_convert_i64_s:
-                case WasmOp_f32_convert_i64_u:
-                case WasmOp_f32_demote_f64:
-                si_pop(stack, ST_64);
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i64_clz:
-                case WasmOp_i64_ctz:
-                case WasmOp_i64_popcnt:
-                case WasmOp_f64_abs:
-                case WasmOp_f64_neg:
-                case WasmOp_f64_ceil:
-                case WasmOp_f64_floor:
-                case WasmOp_f64_trunc:
-                case WasmOp_f64_nearest:
-                case WasmOp_f64_sqrt:
-                case WasmOp_i64_trunc_f64_s:
-                case WasmOp_i64_trunc_f64_u:
-                case WasmOp_f64_convert_i64_s:
-                case WasmOp_f64_convert_i64_u:
-                case WasmOp_i64_reinterpret_f64:
-                case WasmOp_f64_reinterpret_i64:
-                case WasmOp_i64_extend8_s:
-                case WasmOp_i64_extend16_s:
-                case WasmOp_i64_extend32_s:
-                si_pop(stack, ST_64);
-                si_push(stack, ST_64);
-                break;
-
-                case WasmOp_i64_extend_i32_s:
-                case WasmOp_i64_extend_i32_u:
-                case WasmOp_i64_trunc_f32_s:
-                case WasmOp_i64_trunc_f32_u:
-                case WasmOp_f64_convert_i32_s:
-                case WasmOp_f64_convert_i32_u:
-                case WasmOp_f64_promote_f32:
-                si_pop(stack, ST_32);
-                si_push(stack, ST_64);
-                break;
-
-                case WasmOp_i32_store:
-                case WasmOp_f32_store:
-                case WasmOp_i32_store8:
-                case WasmOp_i32_store16:
-                si_pop(stack, ST_32);
-                si_pop(stack, ST_32);
-                break;
-
-                case WasmOp_i64_store:
-                case WasmOp_f64_store:
-                case WasmOp_i64_store8:
-                case WasmOp_i64_store16:
-                case WasmOp_i64_store32:
-                si_pop(stack, ST_64);
-                si_pop(stack, ST_32);
-                break;
-
-                case WasmOp_i32_eq:
-                case WasmOp_i32_ne:
-                case WasmOp_i32_lt_s:
-                case WasmOp_i32_lt_u:
-                case WasmOp_i32_gt_s:
-                case WasmOp_i32_gt_u:
-                case WasmOp_i32_le_s:
-                case WasmOp_i32_le_u:
-                case WasmOp_i32_ge_s:
-                case WasmOp_i32_ge_u:
-                case WasmOp_f32_eq:
-                case WasmOp_f32_ne:
-                case WasmOp_f32_lt:
-                case WasmOp_f32_gt:
-                case WasmOp_f32_le:
-                case WasmOp_f32_ge:
-                si_pop(stack, ST_32);
-                si_pop(stack, ST_32);
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i64_eq:
-                case WasmOp_i64_ne:
-                case WasmOp_i64_lt_s:
-                case WasmOp_i64_lt_u:
-                case WasmOp_i64_gt_s:
-                case WasmOp_i64_gt_u:
-                case WasmOp_i64_le_s:
-                case WasmOp_i64_le_u:
-                case WasmOp_i64_ge_s:
-                case WasmOp_i64_ge_u:
-                case WasmOp_f64_eq:
-                case WasmOp_f64_ne:
-                case WasmOp_f64_lt:
-                case WasmOp_f64_gt:
-                case WasmOp_f64_le:
-                case WasmOp_f64_ge:
-                si_pop(stack, ST_64);
-                si_pop(stack, ST_64);
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i32_add:
-                case WasmOp_i32_sub:
-                case WasmOp_i32_mul:
-                case WasmOp_i32_div_s:
-                case WasmOp_i32_div_u:
-                case WasmOp_i32_rem_s:
-                case WasmOp_i32_rem_u:
-                case WasmOp_i32_and:
-                case WasmOp_i32_or:
-                case WasmOp_i32_xor:
-                case WasmOp_i32_shl:
-                case WasmOp_i32_shr_s:
-                case WasmOp_i32_shr_u:
-                case WasmOp_i32_rotl:
-                case WasmOp_i32_rotr:
-                case WasmOp_f32_add:
-                case WasmOp_f32_sub:
-                case WasmOp_f32_mul:
-                case WasmOp_f32_div:
-                case WasmOp_f32_min:
-                case WasmOp_f32_max:
-                case WasmOp_f32_copysign:
-                si_pop(stack, ST_32);
-                si_pop(stack, ST_32);
-                si_push(stack, ST_32);
-                break;
-
-                case WasmOp_i64_add:
-                case WasmOp_i64_sub:
-                case WasmOp_i64_mul:
-                case WasmOp_i64_div_s:
-                case WasmOp_i64_div_u:
-                case WasmOp_i64_rem_s:
-                case WasmOp_i64_rem_u:
-                case WasmOp_i64_and:
-                case WasmOp_i64_or:
-                case WasmOp_i64_xor:
-                case WasmOp_i64_shl:
-                case WasmOp_i64_shr_s:
-                case WasmOp_i64_shr_u:
-                case WasmOp_i64_rotl:
-                case WasmOp_i64_rotr:
-                case WasmOp_f64_add:
-                case WasmOp_f64_sub:
-                case WasmOp_f64_mul:
-                case WasmOp_f64_div:
-                case WasmOp_f64_min:
-                case WasmOp_f64_max:
-                case WasmOp_f64_copysign:
-                si_pop(stack, ST_64);
-                si_pop(stack, ST_64);
-                si_push(stack, ST_64);
-                break;
-
-                case WasmOp_prefixed:
-                switch (prefixed_opcode) {
-                    case WasmPrefixedOp_i32_trunc_sat_f32_s:
-                    case WasmPrefixedOp_i32_trunc_sat_f32_u:
-                    si_pop(stack, ST_32);
-                    si_push(stack, ST_32);
-                    break;
-
-                    case WasmPrefixedOp_i32_trunc_sat_f64_s:
-                    case WasmPrefixedOp_i32_trunc_sat_f64_u:
-                    si_pop(stack, ST_64);
-                    si_push(stack, ST_32);
-                    break;
-
-                    case WasmPrefixedOp_i64_trunc_sat_f32_s:
-                    case WasmPrefixedOp_i64_trunc_sat_f32_u:
-                    si_pop(stack, ST_32);
-                    si_push(stack, ST_64);
-                    break;
-
-                    case WasmPrefixedOp_i64_trunc_sat_f64_s:
-                    case WasmPrefixedOp_i64_trunc_sat_f64_u:
-                    si_pop(stack, ST_64);
-                    si_push(stack, ST_64);
-                    break;
-
-                    case WasmPrefixedOp_memory_init:
-                    case WasmPrefixedOp_memory_copy:
-                    case WasmPrefixedOp_memory_fill:
-                    case WasmPrefixedOp_table_init:
-                    case WasmPrefixedOp_table_copy:
-                    si_pop(stack, ST_32);
-                    si_pop(stack, ST_32);
-                    si_pop(stack, ST_32);
-                    break;
-
-                    case WasmPrefixedOp_table_fill:
-                    si_pop(stack, ST_32);
-                    panic("si_pop(stack, unreachable);");
-                    si_pop(stack, ST_32);
-                    break;
-
-                    case WasmPrefixedOp_data_drop:
-                    case WasmPrefixedOp_elem_drop:
-                    break;
-
-                    case WasmPrefixedOp_table_grow:
-                    si_pop(stack, ST_32);
-                    panic("si_pop(stack, unreachable);");
-                    si_push(stack, ST_32);
-                    break;
-
-                    case WasmPrefixedOp_table_size:
-                    si_push(stack, ST_32);
-                    break;
-
-                    default: panic("unexpected prefixed opcode");
-                }
-                break;
-
-                default: panic("unexpected opcode");
-            }
-        switch (opcode) {
-            case WasmOp_unreachable:
-            if (unreachable_depth == 0) {
-                opcodes[pc->opcode] = Op_unreachable;
-                pc->opcode += 1;
-                unreachable_depth += 1;
-            }
-            break;
-
-            case WasmOp_nop:
-            case WasmOp_i32_reinterpret_f32:
-            case WasmOp_i64_reinterpret_f64:
-            case WasmOp_f32_reinterpret_i32:
-            case WasmOp_f64_reinterpret_i64:
-            break;
-
-            case WasmOp_block:
-            case WasmOp_loop:
-            case WasmOp_if:
-            {
-                int64_t block_type = read64_ileb128(mod_ptr, code_i);
-                if (unreachable_depth == 0) {
-                    label_i += 1;
-                    struct Label *label = &labels[label_i];
-                    label->opcode = opcode;
-                    if (block_type < 0) {
-                        label->type_info.param_count = 0;
-                        label->type_info.param_types = 0;
-                        label->type_info.result_count = block_type != -0x40;
-                        switch (block_type) {
-                            case -0x40:
-                            case -1:
-                            case -3:
-                                label->type_info.result_types = 0;
-                                break;
-                            case -2:
-                            case -4:
-                                label->type_info.result_types = UINT32_MAX;
-                                break;
-                            default: panic("unexpected param type");
-                        }
-                    } else label->type_info = vm->types[block_type];
-
-                    uint32_t param_i = label->type_info.param_count;
-                    while (param_i > 0) {
-                        param_i -= 1;
-                        si_pop(stack, bs_isSet(&label->type_info.param_types, param_i));
-                    }
-                    label->stack_index = stack->top_index;
-                    label->stack_offset = stack->top_offset;
-                    label->ref_list = UINT32_MAX;
-                    for (; param_i < label->type_info.param_count; param_i += 1)
-                        si_push(stack, bs_isSet(&label->type_info.param_types, param_i));
-
-                    switch (opcode) {
-                        case WasmOp_block:
-                        break;
-
-                        case WasmOp_loop:
-                        label->extra.loop_pc = *pc;
-                        break;
-
-                        case WasmOp_if:
-                        if (state == State_bool_not) {
-                            pc->opcode -= 1;
-                            opcodes[pc->opcode] = Op_br_nez_void;
-                        } else opcodes[pc->opcode] = Op_br_eqz_void;
-                        pc->opcode += 1;
-                        operands[pc->operand] = 0;
-                        label->extra.else_ref = pc->operand + 1;
-                        pc->operand += 3;
-                        break;
-
-                        default: panic("unexpected label opcode");
-                    }
-                } else unreachable_depth += 1;
-            }
-            break;
-
-            case WasmOp_else:
-            if (unreachable_depth <= 1) {
-                struct Label *label = &labels[label_i];
-                assert(label->opcode == WasmOp_if);
-                label->opcode = WasmOp_else;
-
-                if (unreachable_depth == 0) {
-                    uint32_t operand_count = Label_operandCount(label);
-                    for (uint32_t operand_i = operand_count; operand_i > 0; ) {
-                        operand_i -= 1;
-                        si_pop(stack, Label_operandType(label, operand_i));
-                    }
-                    assert(stack->top_index == label->stack_index);
-                    assert(stack->top_offset == label->stack_offset);
-
-                    switch (operand_count) {
-                        case 0:
-                        opcodes[pc->opcode] = Op_br_void;
-                        break;
-
-                        case 1:
-                        //fprintf(stderr, "label_i=%u operand_type=%d\n",
-                        //        label_i, Label_operandType(label, 0));
-                        switch (Label_operandType(label, 0)) {
-                            case ST_32: opcodes[pc->opcode] = Op_br_32; break;
-                            case ST_64: opcodes[pc->opcode] = Op_br_64; break;
-                        }
-                        break;
-
-                        default: panic("unexpected operand count");
-                    }
-                    pc->opcode += 1;
-                    operands[pc->operand + 0] = stack->top_offset - label->stack_offset;
-                    operands[pc->operand + 1] = label->ref_list;
-                    label->ref_list = pc->operand + 1;
-                    pc->operand += 3;
-                } else unreachable_depth = 0;
-
-                operands[label->extra.else_ref + 0] = pc->opcode;
-                operands[label->extra.else_ref + 1] = pc->operand;
-                for (uint32_t param_i = 0; param_i < label->type_info.param_count; param_i += 1)
-                    si_push(stack, bs_isSet(&label->type_info.param_types, param_i));
-            }
-            break;
-
-            case WasmOp_end:
-            if (unreachable_depth <= 1) {
-                struct Label *label = &labels[label_i];
-                struct ProgramCounter *target_pc = (label->opcode == WasmOp_loop) ? &label->extra.loop_pc : pc;
-                if (label->opcode == WasmOp_if) {
-                    operands[label->extra.else_ref + 0] = target_pc->opcode;
-                    operands[label->extra.else_ref + 1] = target_pc->operand;
-                }
-                uint32_t ref = label->ref_list;
-                while (ref != UINT32_MAX) {
-                    uint32_t next_ref = operands[ref];
-                    operands[ref + 0] = target_pc->opcode;
-                    operands[ref + 1] = target_pc->operand;
-                    ref = next_ref;
-                }
-
-                if (unreachable_depth == 0) {
-                    for (uint32_t result_i = label->type_info.result_count; result_i > 0; ) {
-                        result_i -= 1;
-                        si_pop(stack, bs_isSet(&label->type_info.result_types, result_i));
-                    }
-                } else unreachable_depth = 0;
-
-                if (label_i == 0) {
-                    assert(stack->top_index == label->stack_index);
-                    assert(stack->top_offset == label->stack_offset);
-
-                    switch (labels[0].type_info.result_count) {
-                        case 0:
-                        opcodes[pc->opcode] = Op_return_void;
-                        break;
-
-                        case 1:
-                        switch ((enum StackType)bs_isSet(&labels[0].type_info.result_types, 0)) {
-                            case ST_32: opcodes[pc->opcode] = Op_return_32; break;
-                            case ST_64: opcodes[pc->opcode] = Op_return_64; break;
-                        }
-                        break;
-
-                        default: panic("unexpected operand count");
-                    }
-                    pc->opcode += 1;
-                    operands[pc->operand + 0] = stack->top_offset - labels[0].stack_offset;
-                    operands[pc->operand + 1] = frame_size;
-                    pc->operand += 2;
-                    return;
-                }
-                label_i -= 1;
-
-                stack->top_index = label->stack_index;
-                stack->top_offset = label->stack_offset;
-                for (uint32_t result_i = 0; result_i < label->type_info.result_count; result_i += 1)
-                    si_push(stack, bs_isSet(&label->type_info.result_types, result_i));
-            } else unreachable_depth -= 1;
-            break;
-
-            case WasmOp_br:
-            case WasmOp_br_if:
-            {
-                uint32_t label_idx = read32_uleb128(mod_ptr, code_i);
-                if (unreachable_depth == 0) {
-                    struct Label *label = &labels[label_i - label_idx];
-                    uint32_t operand_count = Label_operandCount(label);
-                    uint32_t operand_i = operand_count;
-                    while (operand_i > 0) {
-                        operand_i -= 1;
-                        si_pop(stack, Label_operandType(label, operand_i));
-                    }
-
-                    switch (opcode) {
-                        case WasmOp_br:
-                        switch (operand_count) {
-                            case 0:
-                            opcodes[pc->opcode] = Op_br_void;
-                            break;
-
-                            case 1:
-                            switch (Label_operandType(label, 0)) {
-                                case ST_32: opcodes[pc->opcode] = Op_br_32; break;
-                                case ST_64: opcodes[pc->opcode] = Op_br_64; break;
-                            }
-                            break;
-
-                            default: panic("unexpected operand count");
-                        }
-                        break;
-
-                        case WasmOp_br_if:
-                        switch (operand_count) {
-                            case 0:
-                            if (state == State_bool_not) {
-                                pc->opcode -= 1;
-                                opcodes[pc->opcode] = Op_br_eqz_void;
-                            } else opcodes[pc->opcode] = Op_br_nez_void;
-                            break;
-
-                            case 1:
-                            switch (Label_operandType(label, 0)) {
-                                case ST_32:
-                                if (state == State_bool_not) {
-                                    pc->opcode -= 1;
-                                    opcodes[pc->opcode] = Op_br_eqz_32;
-                                } else opcodes[pc->opcode] = Op_br_nez_32;
-                                break;
-
-                                case ST_64:
-                                if (state == State_bool_not) {
-                                    pc->opcode -= 1;
-                                    opcodes[pc->opcode] = Op_br_eqz_64;
-                                } else opcodes[pc->opcode] = Op_br_nez_64;
-                                break;
-                            }
-                            break;
-
-                            default: panic("unexpected operand count");
-                        }
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                    pc->opcode += 1;
-                    operands[pc->operand + 0] = stack->top_offset - label->stack_offset;
-                    operands[pc->operand + 1] = label->ref_list;
-                    label->ref_list = pc->operand + 1;
-                    pc->operand += 3;
-
-                    switch (opcode) {
-                        case WasmOp_br:
-                        unreachable_depth += 1;
-                        break;
-
-                        case WasmOp_br_if:
-                        for (; operand_i < operand_count; operand_i += 1)
-                            si_push(stack, Label_operandType(label, operand_i));
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                }
-            }
-            break;
-
-            case WasmOp_br_table:
-            {
-                uint32_t labels_len = read32_uleb128(mod_ptr, code_i);
-                for (uint32_t i = 0; i <= labels_len; i += 1) {
-                    uint32_t label_idx = read32_uleb128(mod_ptr, code_i);
-                    if (unreachable_depth != 0) continue;
-                    struct Label *label = &labels[label_i - label_idx];
-                    if (i == 0) {
-                        uint32_t operand_count = Label_operandCount(label);
-                        for (uint32_t operand_i = operand_count; operand_i > 0; ) {
-                            operand_i -= 1;
-                            si_pop(stack, Label_operandType(label, operand_i));
-                        }
-
-                        switch (operand_count) {
-                            case 0:
-                            opcodes[pc->opcode] = Op_br_table_void;
-                            break;
-
-                            case 1:
-                            switch (Label_operandType(label, 0)) {
-                                case ST_32: opcodes[pc->opcode] = Op_br_table_32; break;
-                                case ST_64: opcodes[pc->opcode] = Op_br_table_64; break;
-                            }
-                            break;
-
-                            default: panic("unexpected operand count");
-                        }
-                        pc->opcode += 1;
-                        operands[pc->operand] = labels_len;
-                        pc->operand += 1;
-                    }
-                    operands[pc->operand + 0] = stack->top_offset - label->stack_offset;
-                    operands[pc->operand + 1] = label->ref_list;
-                    label->ref_list = pc->operand + 1;
-                    pc->operand += 3;
-                }
-                if (unreachable_depth == 0) unreachable_depth += 1;
-            }
-            break;
-
-            case WasmOp_return:
-            if (unreachable_depth == 0) {
-                for (uint32_t result_i = labels[0].type_info.result_count; result_i > 0; ) {
-                    result_i -= 1;
-                    si_pop(stack, bs_isSet(&labels[0].type_info.result_types, result_i));
-                }
-
-                switch (labels[0].type_info.result_count) {
-                    case 0:
-                    opcodes[pc->opcode] = Op_return_void;
-                    break;
-
-                    case 1:
-                    switch ((enum StackType)bs_isSet(&labels[0].type_info.result_types, 0)) {
-                        case ST_32: opcodes[pc->opcode] = Op_return_32; break;
-                        case ST_64: opcodes[pc->opcode] = Op_return_64; break;
-                    }
-                    break;
-
-                    default: panic("unexpected operand count");
-                }
-                pc->opcode += 1;
-                operands[pc->operand + 0] = stack->top_offset - labels[0].stack_offset;
-                operands[pc->operand + 1] = frame_size;
-                pc->operand += 2;
-                unreachable_depth += 1;
-            }
-            break;
-
-            case WasmOp_call:
-            {
-                uint32_t fn_id = read32_uleb128(mod_ptr, code_i);
-                if (unreachable_depth == 0) {
-                    uint32_t type_idx;
-                    if (fn_id < vm->imports_len) {
-                        opcodes[pc->opcode + 0] = Op_call_import;
-                        opcodes[pc->opcode + 1] = fn_id;
-                        pc->opcode += 2;
-                        type_idx = vm->imports[fn_id].type_idx;
-                    } else {
-                        uint32_t fn_idx = fn_id - vm->imports_len;
-                        opcodes[pc->opcode] = Op_call_func;
-                        pc->opcode += 1;
-                        operands[pc->operand] = fn_idx;
-                        pc->operand += 1;
-                        type_idx = vm->functions[fn_idx].type_idx;
-                    }
-                    struct TypeInfo *type_info = &vm->types[type_idx];
-
-                    for (uint32_t param_i = type_info->param_count; param_i > 0; ) {
-                        param_i -= 1;
-                        si_pop(stack, bs_isSet(&type_info->param_types, param_i));
-                    }
-                    for (uint32_t result_i = 0; result_i < type_info->result_count; result_i += 1)
-                        si_push(stack, bs_isSet(&type_info->result_types, result_i));
-                }
-            }
-            break;
-
-            case WasmOp_call_indirect:
-            {
-                uint32_t type_idx = read32_uleb128(mod_ptr, code_i);
-                if (read32_uleb128(mod_ptr, code_i) != 0) panic("unexpected table index");
-                if (unreachable_depth == 0) {
-                    opcodes[pc->opcode] = Op_call_indirect;
-                    pc->opcode += 1;
-
-                    struct TypeInfo *type_info = &vm->types[type_idx];
-                    for (uint32_t param_i = type_info->param_count; param_i > 0; ) {
-                        param_i -= 1;
-                        si_pop(stack, bs_isSet(&type_info->param_types, param_i));
-                    }
-                    for (uint32_t result_i = 0; result_i < type_info->result_count; result_i += 1)
-                        si_push(stack, bs_isSet(&type_info->result_types, result_i));
-                }
-            }
-            break;
-
-            case WasmOp_select:
-            case WasmOp_drop:
-            if (unreachable_depth == 0) {
-                if (opcode == WasmOp_select) si_pop(stack, ST_32);
-                enum StackType operand_type = si_top(stack);
-                si_pop(stack, operand_type);
-                if (opcode == WasmOp_select) {
-                    si_pop(stack, operand_type);
-                    si_push(stack, operand_type);
-                }
-                switch (opcode) {
-                    case WasmOp_select:
-                    switch (operand_type) {
-                        case ST_32: opcodes[pc->opcode] = Op_select_32; break;
-                        case ST_64: opcodes[pc->opcode] = Op_select_64; break;
-                    }
-                    break;
-
-                    case WasmOp_drop:
-                    switch (operand_type) {
-                        case ST_32: opcodes[pc->opcode] = Op_drop_32; break;
-                        case ST_64: opcodes[pc->opcode] = Op_drop_64; break;
-                    }
-                    break;
-
-                    default: panic("unexpected opcode");
-                }
-                pc->opcode += 1;
-            }
-            break;
-
-            case WasmOp_local_get:
-            case WasmOp_local_set:
-            case WasmOp_local_tee:
-            {
-                uint32_t local_idx = read32_uleb128(mod_ptr, code_i);
-                if (unreachable_depth == 0) {
-                    enum StackType local_type = si_local(stack, local_idx);
-                    switch (opcode) {
-                        case WasmOp_local_get:
-                        switch (local_type) {
-                            case ST_32: opcodes[pc->opcode] = Op_local_get_32; break;
-                            case ST_64: opcodes[pc->opcode] = Op_local_get_64; break;
-                        }
-                        break;
-
-                        case WasmOp_local_set:
-                        switch (local_type) {
-                            case ST_32: opcodes[pc->opcode] = Op_local_set_32; break;
-                            case ST_64: opcodes[pc->opcode] = Op_local_set_64; break;
-                        }
-                        break;
-
-                        case WasmOp_local_tee:
-                        switch (local_type) {
-                            case ST_32: opcodes[pc->opcode] = Op_local_tee_32; break;
-                            case ST_64: opcodes[pc->opcode] = Op_local_tee_64; break;
-                        }
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                    pc->opcode += 1;
-                    operands[pc->operand] = stack->top_offset - stack->offsets[local_idx];
-                    pc->operand += 1;
-                    switch (opcode) {
-                        case WasmOp_local_get:
-                        si_push(stack, local_type);
-                        break;
-
-                        case WasmOp_local_set:
-                        si_pop(stack, local_type);
-                        break;
-
-                        case WasmOp_local_tee:
-                        si_pop(stack, local_type);
-                        si_push(stack, local_type);
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                }
-            }
-            break;
-
-            case WasmOp_global_get:
-            case WasmOp_global_set:
-            {
-                uint32_t global_idx = read32_uleb128(mod_ptr, code_i);
-                if (unreachable_depth == 0) {
-                    enum StackType global_type = ST_32; // all globals assumed to be 32-bit
-                    switch (opcode) {
-                        case WasmOp_global_get:
-                        switch (global_idx) {
-                            case 0: opcodes[pc->opcode] = Op_global_get_0_32; break;
-                            default: opcodes[pc->opcode] = Op_global_get_32; break;
-                        }
-                        break;
-
-                        case WasmOp_global_set:
-                        switch (global_idx) {
-                            case 0: opcodes[pc->opcode] = Op_global_set_0_32; break;
-                            default: opcodes[pc->opcode] = Op_global_set_32; break;
-                        }
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                    pc->opcode += 1;
-                    if (global_idx != 0) {
-                        operands[pc->operand] = global_idx;
-                        pc->operand += 1;
-                    }
-                    switch (opcode) {
-                        case WasmOp_global_get:
-                        si_push(stack, global_type);
-                        break;
-
-                        case WasmOp_global_set:
-                        si_pop(stack, global_type);
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                }
-            }
-            break;
-
-            case WasmOp_i32_load:
-            case WasmOp_i64_load:
-            case WasmOp_f32_load:
-            case WasmOp_f64_load:
-            case WasmOp_i32_load8_s:
-            case WasmOp_i32_load8_u:
-            case WasmOp_i32_load16_s:
-            case WasmOp_i32_load16_u:
-            case WasmOp_i64_load8_s:
-            case WasmOp_i64_load8_u:
-            case WasmOp_i64_load16_s:
-            case WasmOp_i64_load16_u:
-            case WasmOp_i64_load32_s:
-            case WasmOp_i64_load32_u:
-            case WasmOp_i32_store:
-            case WasmOp_i64_store:
-            case WasmOp_f32_store:
-            case WasmOp_f64_store:
-            case WasmOp_i32_store8:
-            case WasmOp_i32_store16:
-            case WasmOp_i64_store8:
-            case WasmOp_i64_store16:
-            case WasmOp_i64_store32:
-            {
-                uint32_t alignment = read32_uleb128(mod_ptr, code_i);
-                uint32_t offset = read32_uleb128(mod_ptr, code_i);
-                (void)alignment;
-                if (unreachable_depth == 0) {
-                    switch (opcode) {
-                        default: break;
-
-                        case WasmOp_i64_store8: case WasmOp_i64_store16: case WasmOp_i64_store32:
-                        opcodes[pc->opcode] = Op_drop_32;
-                        pc->opcode += 1;
-                        break;
-                    }
-                    switch (opcode) {
-                        case WasmOp_i32_load8_s: case WasmOp_i32_load8_u:
-                        case WasmOp_i64_load8_s: case WasmOp_i64_load8_u:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_load_0_8; break;
-                            default: opcodes[pc->opcode] = Op_load_8; break;
-                        }
-                        break;
-
-                        case WasmOp_i32_load16_s: case WasmOp_i32_load16_u:
-                        case WasmOp_i64_load16_s: case WasmOp_i64_load16_u:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_load_0_16; break;
-                            default: opcodes[pc->opcode] = Op_load_16; break;
-                        }
-                        break;
-
-                        case WasmOp_i32_load: case WasmOp_f32_load:
-                        case WasmOp_i64_load32_s: case WasmOp_i64_load32_u:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_load_0_32; break;
-                            default: opcodes[pc->opcode] = Op_load_32; break;
-                        }
-                        break;
-
-                        case WasmOp_i64_load: case WasmOp_f64_load:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_load_0_64; break;
-                            default: opcodes[pc->opcode] = Op_load_64; break;
-                        }
-                        break;
-
-                        case WasmOp_i32_store8: case WasmOp_i64_store8:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_store_0_8; break;
-                            default: opcodes[pc->opcode] = Op_store_8; break;
-                        }
-                        break;
-
-                        case WasmOp_i32_store16: case WasmOp_i64_store16:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_store_0_16; break;
-                            default: opcodes[pc->opcode] = Op_store_16; break;
-                        }
-                        break;
-
-                        case WasmOp_i32_store: case WasmOp_f32_store: case WasmOp_i64_store32:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_store_0_32; break;
-                            default: opcodes[pc->opcode] = Op_store_32; break;
-                        }
-                        break;
-
-                        case WasmOp_i64_store: case WasmOp_f64_store:
-                        switch (offset) {
-                            case 0: opcodes[pc->opcode] = Op_store_0_64; break;
-                            default: opcodes[pc->opcode] = Op_store_64; break;
-                        }
-                        break;
-
-                        default: panic("unexpected opcode");
-                    }
-                    pc->opcode += 1;
-                    switch (offset) {
-                        case 0: break;
-
-                        default:
-                        operands[pc->operand] = offset;
-                        pc->operand += 1;
-                        break;
-                    }
-                    switch (opcode) {
-                        default: break;
-
-                        case WasmOp_i32_load8_s: case WasmOp_i64_load8_s:
-                        opcodes[pc->opcode] = Op_sext8_32;
-                        pc->opcode += 1;
-                        break;
-
-                        case WasmOp_i32_load16_s: case WasmOp_i64_load16_s:
-                        opcodes[pc->opcode] = Op_sext16_32;
-                        pc->opcode += 1;
-                        break;
-                    }
-                    switch (opcode) {
-                        default: break;
-
-                        case WasmOp_i64_load8_s: case WasmOp_i64_load16_s: case WasmOp_i64_load32_s:
-                        opcodes[pc->opcode] = Op_sext_64_32;
-                        pc->opcode += 1;
-                        break;
-
-                        case WasmOp_i64_load8_u: case WasmOp_i64_load16_u: case WasmOp_i64_load32_u:
-                        opcodes[pc->opcode] = Op_zext_64_32;
-                        pc->opcode += 1;
-                        break;
-                    }
-                }
-            }
-            break;
-
-            case WasmOp_memory_size:
-            case WasmOp_memory_grow:
-            {
-                if (mod_ptr[*code_i] != 0) panic("unexpected memory index");
-                *code_i += 1;
-                if (unreachable_depth == 0) {
-                    switch (opcode) {
-                        case WasmOp_memory_size: opcodes[pc->opcode] = Op_mem_size; break;
-                        case WasmOp_memory_grow: opcodes[pc->opcode] = Op_mem_grow; break;
-                        default: panic("unexpected opcode");
-                    }
-                    pc->opcode += 1;
-                }
-            }
-            break;
-
-            case WasmOp_i32_const:
-            case WasmOp_f32_const:
-            {
-                uint32_t value;
-                switch (opcode) {
-                    case WasmOp_i32_const: value = read32_ileb128(mod_ptr, code_i); break;
-
-                    case WasmOp_f32_const:
-                    value = read_u32_le(&mod_ptr[*code_i]);
-                    *code_i += sizeof(value);
-                    break;
-
-                    default: panic("unexpected opcode");
-                }
-                if (unreachable_depth == 0) {
-                    switch (value) {
-                        case 0: opcodes[pc->opcode] = Op_const_0_32; break;
-                        case 1: opcodes[pc->opcode] = Op_const_1_32; break;
-
-                        default:
-                        opcodes[pc->opcode] = Op_const_32;
-                        operands[pc->operand] = value;
-                        pc->operand += 1;
-                        break;
-
-                        case UINT32_MAX: opcodes[pc->opcode] = Op_const_umax_32; break;
-                    }
-                    pc->opcode += 1;
-                }
-            }
-            break;
-
-            case WasmOp_i64_const:
-            case WasmOp_f64_const:
-            {
-                uint64_t value;
-                switch (opcode) {
-                    case WasmOp_i64_const: value = read64_ileb128(mod_ptr, code_i); break;
-
-                    case WasmOp_f64_const:
-                    value = read_u64_le(&mod_ptr[*code_i]);
-                    *code_i += sizeof(value);
-                    break;
-
-                    default: panic("unexpected opcode");
-                }
-
-                if (unreachable_depth == 0) {
-                    switch (value) {
-                        case 0: opcodes[pc->opcode] = Op_const_0_64; break;
-                        case 1: opcodes[pc->opcode] = Op_const_1_64; break;
-
-                        default:
-                        opcodes[pc->opcode] = Op_const_64;
-                        operands[pc->operand + 0] = (uint32_t)(value >> 0);
-                        operands[pc->operand + 1] = (uint32_t)(value >> 32);
-                        pc->operand += 2;
-                        break;
-
-                        case UINT64_MAX: opcodes[pc->opcode] = Op_const_umax_64; break;
-                    }
-                    pc->opcode += 1;
-                }
-            }
-            break;
-
-            default:
-            if (unreachable_depth == 0) {
-                switch (opcode) {
-                    case WasmOp_i32_eqz:           opcodes[pc->opcode] = Op_eqz_32;      break;
-                    case WasmOp_i32_eq:            opcodes[pc->opcode] = Op_eq_32;       break;
-                    case WasmOp_i32_ne:            opcodes[pc->opcode] = Op_ne_32;       break;
-                    case WasmOp_i32_lt_s:          opcodes[pc->opcode] = Op_slt_32;      break;
-                    case WasmOp_i32_lt_u:          opcodes[pc->opcode] = Op_ult_32;      break;
-                    case WasmOp_i32_gt_s:          opcodes[pc->opcode] = Op_sgt_32;      break;
-                    case WasmOp_i32_gt_u:          opcodes[pc->opcode] = Op_ugt_32;      break;
-                    case WasmOp_i32_le_s:          opcodes[pc->opcode] = Op_sle_32;      break;
-                    case WasmOp_i32_le_u:          opcodes[pc->opcode] = Op_ule_32;      break;
-                    case WasmOp_i32_ge_s:          opcodes[pc->opcode] = Op_sge_32;      break;
-                    case WasmOp_i32_ge_u:          opcodes[pc->opcode] = Op_uge_32;      break;
-                    case WasmOp_i64_eqz:           opcodes[pc->opcode] = Op_eqz_64;      break;
-                    case WasmOp_i64_eq:            opcodes[pc->opcode] = Op_eq_64;       break;
-                    case WasmOp_i64_ne:            opcodes[pc->opcode] = Op_ne_64;       break;
-                    case WasmOp_i64_lt_s:          opcodes[pc->opcode] = Op_slt_64;      break;
-                    case WasmOp_i64_lt_u:          opcodes[pc->opcode] = Op_ult_64;      break;
-                    case WasmOp_i64_gt_s:          opcodes[pc->opcode] = Op_sgt_64;      break;
-                    case WasmOp_i64_gt_u:          opcodes[pc->opcode] = Op_ugt_64;      break;
-                    case WasmOp_i64_le_s:          opcodes[pc->opcode] = Op_sle_64;      break;
-                    case WasmOp_i64_le_u:          opcodes[pc->opcode] = Op_ule_64;      break;
-                    case WasmOp_i64_ge_s:          opcodes[pc->opcode] = Op_sge_64;      break;
-                    case WasmOp_i64_ge_u:          opcodes[pc->opcode] = Op_uge_64;      break;
-                    case WasmOp_f32_eq:            opcodes[pc->opcode] = Op_feq_32;      break;
-                    case WasmOp_f32_ne:            opcodes[pc->opcode] = Op_fne_32;      break;
-                    case WasmOp_f32_lt:            opcodes[pc->opcode] = Op_flt_32;      break;
-                    case WasmOp_f32_gt:            opcodes[pc->opcode] = Op_fgt_32;      break;
-                    case WasmOp_f32_le:            opcodes[pc->opcode] = Op_fle_32;      break;
-                    case WasmOp_f32_ge:            opcodes[pc->opcode] = Op_fge_32;      break;
-                    case WasmOp_f64_eq:            opcodes[pc->opcode] = Op_feq_64;      break;
-                    case WasmOp_f64_ne:            opcodes[pc->opcode] = Op_fne_64;      break;
-                    case WasmOp_f64_lt:            opcodes[pc->opcode] = Op_flt_64;      break;
-                    case WasmOp_f64_gt:            opcodes[pc->opcode] = Op_fgt_64;      break;
-                    case WasmOp_f64_le:            opcodes[pc->opcode] = Op_fle_64;      break;
-                    case WasmOp_f64_ge:            opcodes[pc->opcode] = Op_fge_64;      break;
-                    case WasmOp_i32_clz:           opcodes[pc->opcode] = Op_clz_32;      break;
-                    case WasmOp_i32_ctz:           opcodes[pc->opcode] = Op_ctz_32;      break;
-                    case WasmOp_i32_popcnt:        opcodes[pc->opcode] = Op_popcnt_32;   break;
-                    case WasmOp_i32_add:           opcodes[pc->opcode] = Op_add_32;      break;
-                    case WasmOp_i32_sub:           opcodes[pc->opcode] = Op_sub_32;      break;
-                    case WasmOp_i32_mul:           opcodes[pc->opcode] = Op_mul_32;      break;
-                    case WasmOp_i32_div_s:         opcodes[pc->opcode] = Op_sdiv_32;     break;
-                    case WasmOp_i32_div_u:         opcodes[pc->opcode] = Op_udiv_32;     break;
-                    case WasmOp_i32_rem_s:         opcodes[pc->opcode] = Op_srem_32;     break;
-                    case WasmOp_i32_rem_u:         opcodes[pc->opcode] = Op_urem_32;     break;
-                    case WasmOp_i32_and:           opcodes[pc->opcode] = Op_and_32;      break;
-                    case WasmOp_i32_or:            opcodes[pc->opcode] = Op_or_32;       break;
-                    case WasmOp_i32_xor:           opcodes[pc->opcode] = Op_xor_32;      break;
-                    case WasmOp_i32_shl:           opcodes[pc->opcode] = Op_shl_32;      break;
-                    case WasmOp_i32_shr_s:         opcodes[pc->opcode] = Op_ashr_32;     break;
-                    case WasmOp_i32_shr_u:         opcodes[pc->opcode] = Op_lshr_32;     break;
-                    case WasmOp_i32_rotl:          opcodes[pc->opcode] = Op_rol_32;      break;
-                    case WasmOp_i32_rotr:          opcodes[pc->opcode] = Op_ror_32;      break;
-                    case WasmOp_i64_clz:           opcodes[pc->opcode] = Op_clz_64;      break;
-                    case WasmOp_i64_ctz:           opcodes[pc->opcode] = Op_ctz_64;      break;
-                    case WasmOp_i64_popcnt:        opcodes[pc->opcode] = Op_popcnt_64;   break;
-                    case WasmOp_i64_add:           opcodes[pc->opcode] = Op_add_64;      break;
-                    case WasmOp_i64_sub:           opcodes[pc->opcode] = Op_sub_64;      break;
-                    case WasmOp_i64_mul:           opcodes[pc->opcode] = Op_mul_64;      break;
-                    case WasmOp_i64_div_s:         opcodes[pc->opcode] = Op_sdiv_64;     break;
-                    case WasmOp_i64_div_u:         opcodes[pc->opcode] = Op_udiv_64;     break;
-                    case WasmOp_i64_rem_s:         opcodes[pc->opcode] = Op_srem_64;     break;
-                    case WasmOp_i64_rem_u:         opcodes[pc->opcode] = Op_urem_64;     break;
-                    case WasmOp_i64_and:           opcodes[pc->opcode] = Op_and_64;      break;
-                    case WasmOp_i64_or:            opcodes[pc->opcode] = Op_or_64;       break;
-                    case WasmOp_i64_xor:           opcodes[pc->opcode] = Op_xor_64;      break;
-                    case WasmOp_i64_shl:           opcodes[pc->opcode] = Op_shl_64;      break;
-                    case WasmOp_i64_shr_s:         opcodes[pc->opcode] = Op_ashr_64;     break;
-                    case WasmOp_i64_shr_u:         opcodes[pc->opcode] = Op_lshr_64;     break;
-                    case WasmOp_i64_rotl:          opcodes[pc->opcode] = Op_rol_64;      break;
-                    case WasmOp_i64_rotr:          opcodes[pc->opcode] = Op_ror_64;      break;
-                    case WasmOp_f32_abs:           opcodes[pc->opcode] = Op_fabs_32;     break;
-                    case WasmOp_f32_neg:           opcodes[pc->opcode] = Op_fneg_32;     break;
-                    case WasmOp_f32_ceil:          opcodes[pc->opcode] = Op_ceil_32;     break;
-                    case WasmOp_f32_floor:         opcodes[pc->opcode] = Op_floor_32;    break;
-                    case WasmOp_f32_trunc:         opcodes[pc->opcode] = Op_trunc_32;    break;
-                    case WasmOp_f32_nearest:       opcodes[pc->opcode] = Op_nearest_32;  break;
-                    case WasmOp_f32_sqrt:          opcodes[pc->opcode] = Op_sqrt_32;     break;
-                    case WasmOp_f32_add:           opcodes[pc->opcode] = Op_fadd_32;     break;
-                    case WasmOp_f32_sub:           opcodes[pc->opcode] = Op_fsub_32;     break;
-                    case WasmOp_f32_mul:           opcodes[pc->opcode] = Op_fmul_32;     break;
-                    case WasmOp_f32_div:           opcodes[pc->opcode] = Op_fdiv_32;     break;
-                    case WasmOp_f32_min:           opcodes[pc->opcode] = Op_fmin_32;     break;
-                    case WasmOp_f32_max:           opcodes[pc->opcode] = Op_fmax_32;     break;
-                    case WasmOp_f32_copysign:      opcodes[pc->opcode] = Op_copysign_32; break;
-                    case WasmOp_f64_abs:           opcodes[pc->opcode] = Op_fabs_64;     break;
-                    case WasmOp_f64_neg:           opcodes[pc->opcode] = Op_fneg_64;     break;
-                    case WasmOp_f64_ceil:          opcodes[pc->opcode] = Op_ceil_64;     break;
-                    case WasmOp_f64_floor:         opcodes[pc->opcode] = Op_floor_64;    break;
-                    case WasmOp_f64_trunc:         opcodes[pc->opcode] = Op_trunc_64;    break;
-                    case WasmOp_f64_nearest:       opcodes[pc->opcode] = Op_nearest_64;  break;
-                    case WasmOp_f64_sqrt:          opcodes[pc->opcode] = Op_sqrt_64;     break;
-                    case WasmOp_f64_add:           opcodes[pc->opcode] = Op_fadd_64;     break;
-                    case WasmOp_f64_sub:           opcodes[pc->opcode] = Op_fsub_64;     break;
-                    case WasmOp_f64_mul:           opcodes[pc->opcode] = Op_fmul_64;     break;
-                    case WasmOp_f64_div:           opcodes[pc->opcode] = Op_fdiv_64;     break;
-                    case WasmOp_f64_min:           opcodes[pc->opcode] = Op_fmin_64;     break;
-                    case WasmOp_f64_max:           opcodes[pc->opcode] = Op_fmax_64;     break;
-                    case WasmOp_f64_copysign:      opcodes[pc->opcode] = Op_copysign_64; break;
-                    case WasmOp_i32_wrap_i64:      opcodes[pc->opcode] = Op_wrap_32_64;  break;
-                    case WasmOp_i32_trunc_f32_s:   opcodes[pc->opcode] = Op_ftos_32_32;  break;
-                    case WasmOp_i32_trunc_f32_u:   opcodes[pc->opcode] = Op_ftou_32_32;  break;
-                    case WasmOp_i32_trunc_f64_s:   opcodes[pc->opcode] = Op_ftos_32_64;  break;
-                    case WasmOp_i32_trunc_f64_u:   opcodes[pc->opcode] = Op_ftou_32_64;  break;
-                    case WasmOp_i64_extend_i32_s:  opcodes[pc->opcode] = Op_sext_64_32;  break;
-                    case WasmOp_i64_extend_i32_u:  opcodes[pc->opcode] = Op_zext_64_32;  break;
-                    case WasmOp_i64_trunc_f32_s:   opcodes[pc->opcode] = Op_ftos_64_32;  break;
-                    case WasmOp_i64_trunc_f32_u:   opcodes[pc->opcode] = Op_ftou_64_32;  break;
-                    case WasmOp_i64_trunc_f64_s:   opcodes[pc->opcode] = Op_ftos_64_64;  break;
-                    case WasmOp_i64_trunc_f64_u:   opcodes[pc->opcode] = Op_ftou_64_64;  break;
-                    case WasmOp_f32_convert_i32_s: opcodes[pc->opcode] = Op_stof_32_32;  break;
-                    case WasmOp_f32_convert_i32_u: opcodes[pc->opcode] = Op_utof_32_32;  break;
-                    case WasmOp_f32_convert_i64_s: opcodes[pc->opcode] = Op_stof_32_64;  break;
-                    case WasmOp_f32_convert_i64_u: opcodes[pc->opcode] = Op_utof_32_64;  break;
-                    case WasmOp_f32_demote_f64:    opcodes[pc->opcode] = Op_ftof_32_64;  break;
-                    case WasmOp_f64_convert_i32_s: opcodes[pc->opcode] = Op_stof_64_32;  break;
-                    case WasmOp_f64_convert_i32_u: opcodes[pc->opcode] = Op_utof_64_32;  break;
-                    case WasmOp_f64_convert_i64_s: opcodes[pc->opcode] = Op_stof_64_64;  break;
-                    case WasmOp_f64_convert_i64_u: opcodes[pc->opcode] = Op_utof_64_64;  break;
-                    case WasmOp_f64_promote_f32:   opcodes[pc->opcode] = Op_ftof_64_32;  break;
-                    case WasmOp_i32_extend8_s:     opcodes[pc->opcode] = Op_sext8_32;    break;
-                    case WasmOp_i32_extend16_s:    opcodes[pc->opcode] = Op_sext16_32;   break;
-                    case WasmOp_i64_extend8_s:     opcodes[pc->opcode] = Op_sext8_64;    break;
-                    case WasmOp_i64_extend16_s:    opcodes[pc->opcode] = Op_sext16_64;   break;
-                    case WasmOp_i64_extend32_s:    opcodes[pc->opcode] = Op_sext32_64;   break;
-                    default: panic("unexpected opcode");
-                }
-                pc->opcode += 1;
-            }
-            break;
-
-            case WasmOp_prefixed:
-            switch (prefixed_opcode) {
-                case WasmPrefixedOp_memory_copy:
-                if (mod_ptr[*code_i + 0] != 0 || mod_ptr[*code_i + 1] != 0)
-                    panic("unexpected memory index");
-                *code_i += 2;
-                if (unreachable_depth == 0) {
-                    opcodes[pc->opcode] = Op_memcpy;
-                    pc->opcode += 1;
-                }
-                break;
-
-                case WasmPrefixedOp_memory_fill:
-                if (mod_ptr[*code_i] != 0) panic("unexpected memory index");
-                *code_i += 1;
-                if (unreachable_depth == 0) {
-                    opcodes[pc->opcode] = Op_memset;
-                    pc->opcode += 1;
-                }
-                break;
-
-                default: panic("unexpected opcode");
-            }
-            break;
-        }
-        switch (opcode) {
-            default:             state = State_default;  break;
-            case WasmOp_i32_eqz: state = State_bool_not; break;
-        }
-
-        //for (uint32_t i = old_pc.opcode; i < pc->opcode; i += 1) {
-        //    fprintf(stderr, "decoded opcode[%u] = %u\n", i, opcodes[i]);
-        //}
-        //for (uint32_t i = old_pc.operand; i < pc->operand; i += 1) {
-        //    fprintf(stderr, "decoded operand[%u] = %u\n", i, operands[i]);
-        //}
-    }
-}
-
-static void vm_push_u32(struct VirtualMachine *vm, uint32_t value) {
-    vm->stack[vm->stack_top + 0] = value;
-    vm->stack_top += 1;
-}
-
-static void vm_push_i32(struct VirtualMachine *vm, int32_t value) {
-    vm_push_u32(vm, (uint32_t)value);
-}
-
-static void vm_push_u64(struct VirtualMachine *vm, uint64_t value) {
-    vm->stack[vm->stack_top + 0] = (uint32_t)(value >> 0);
-    vm->stack[vm->stack_top + 1] = (uint32_t)(value >> 32);
-    vm->stack_top += 2;
-}
-
-static void vm_push_i64(struct VirtualMachine *vm, int64_t value) {
-    vm_push_u64(vm, (uint64_t)value);
-}
-
-static void vm_push_f32(struct VirtualMachine *vm, float value) {
-    uint32_t integer;
-    memcpy(&integer, &value, sizeof(integer));
-    vm_push_u32(vm, integer);
-}
-
-static void vm_push_f64(struct VirtualMachine *vm, double value) {
-    uint64_t integer;
-    memcpy(&integer, &value, sizeof(integer));
-    vm_push_u64(vm, integer);
-}
-
-static uint32_t vm_pop_u32(struct VirtualMachine *vm) {
-    vm->stack_top -= 1;
-    return vm->stack[vm->stack_top + 0];
-}
-
-static int32_t vm_pop_i32(struct VirtualMachine *vm) {
-    return (int32_t)vm_pop_u32(vm);
-}
-
-static uint64_t vm_pop_u64(struct VirtualMachine *vm) {
-    vm->stack_top -= 2;
-    return vm->stack[vm->stack_top + 0] | (uint64_t)vm->stack[vm->stack_top + 1] << 32;
-}
-
-static int64_t vm_pop_i64(struct VirtualMachine *vm) {
-    return (int64_t)vm_pop_u64(vm);
-}
-
-static float vm_pop_f32(struct VirtualMachine *vm) {
-    uint32_t integer = vm_pop_u32(vm);
-    float result;
-    memcpy(&result, &integer, sizeof(result));
-    return result;
-}
-
-static double vm_pop_f64(struct VirtualMachine *vm) {
-    uint64_t integer = vm_pop_u64(vm);
-    double result;
-    memcpy(&result, &integer, sizeof(result));
-    return result;
-}
-
-static void vm_callImport(struct VirtualMachine *vm, const struct Import *import) {
-    switch (import->mod) {
-        case ImpMod_wasi_snapshot_preview1: switch (import->name) {
-            case ImpName_fd_prestat_get:
-            {
-                uint32_t buf = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_prestat_get(vm, fd, buf));
-            }
-            break;
-            case ImpName_fd_prestat_dir_name:
-            {
-                uint32_t path_len = vm_pop_u32(vm);
-                uint32_t path = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_prestat_dir_name(vm, fd, path, path_len));
-            }
-            break;
-            case ImpName_fd_close:
-            {
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_close(vm, fd));
-            }
-            break;
-            case ImpName_fd_read:
-            {
-                uint32_t nread = vm_pop_u32(vm);
-                uint32_t iovs_len = vm_pop_u32(vm);
-                uint32_t iovs = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_read(vm, fd, iovs, iovs_len, nread));
-            }
-            break;
-            case ImpName_fd_filestat_get:
-            {
-                uint32_t buf = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_filestat_get(vm, fd, buf));
-            }
-            break;
-            case ImpName_fd_filestat_set_size:
-            {
-                uint64_t size = vm_pop_u64(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_filestat_set_size(vm, fd, size));
-            }
-            break;
-            case ImpName_fd_filestat_set_times:
-            {
-                panic("unexpected call to fd_filestat_set_times");
-            }
-            break;
-            case ImpName_fd_fdstat_get:
-            {
-                uint32_t buf = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_fdstat_get(vm, fd, buf));
-            }
-            break;
-            case ImpName_fd_readdir:
-            {
-                panic("unexpected call to fd_readdir");
-            }
-            break;
-            case ImpName_fd_write:
-            {
-                uint32_t nwritten = vm_pop_u32(vm);
-                uint32_t iovs_len = vm_pop_u32(vm);
-                uint32_t iovs = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_write(vm, fd, iovs, iovs_len, nwritten));
-            }
-            break;
-            case ImpName_fd_pwrite:
-            {
-                uint32_t nwritten = vm_pop_u32(vm);
-                uint64_t offset = vm_pop_u64(vm);
-                uint32_t iovs_len = vm_pop_u32(vm);
-                uint32_t iovs = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_fd_pwrite(vm, fd, iovs, iovs_len, offset, nwritten));
-            }
-            break;
-            case ImpName_proc_exit:
-            {
-                uint32_t code = vm_pop_u32(vm);
-                exit(code);
-            }
-            break;
-            case ImpName_args_sizes_get:
-            {
-                uint32_t argv_buf_size = vm_pop_u32(vm);
-                uint32_t argc = vm_pop_u32(vm);
-                vm_push_u32(vm, wasi_args_sizes_get(vm, argc, argv_buf_size));
-            }
-            break;
-            case ImpName_args_get:
-            {
-                uint32_t argv_buf = vm_pop_u32(vm);
-                uint32_t argv = vm_pop_u32(vm);
-                vm_push_u32(vm, wasi_args_get(vm, argv, argv_buf));
-            }
-            break;
-            case ImpName_random_get:
-            {
-                uint32_t buf_len = vm_pop_u32(vm);
-                uint32_t buf = vm_pop_u32(vm);
-                vm_push_u32(vm, wasi_random_get(vm, buf, buf_len));
-            }
-            break;
-            case ImpName_environ_sizes_get:
-            {
-                panic("unexpected call to environ_sizes_get");
-            }
-            break;
-            case ImpName_environ_get:
-            {
-                panic("unexpected call to environ_get");
-            }
-            break;
-            case ImpName_path_filestat_get:
-            {
-                uint32_t buf = vm_pop_u32(vm);
-                uint32_t path_len = vm_pop_u32(vm);
-                uint32_t path = vm_pop_u32(vm);
-                uint32_t flags = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_path_filestat_get(vm, fd, flags, path, path_len, buf));
-            }
-            break;
-            case ImpName_path_create_directory:
-            {
-                uint32_t path_len = vm_pop_u32(vm);
-                uint32_t path = vm_pop_u32(vm);
-                int32_t fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_path_create_directory(vm, fd, path, path_len));
-            }
-            break;
-            case ImpName_path_rename:
-            {
-                uint32_t new_path_len = vm_pop_u32(vm);
-                uint32_t new_path = vm_pop_u32(vm);
-                int32_t new_fd = vm_pop_i32(vm);
-                uint32_t old_path_len = vm_pop_u32(vm);
-                uint32_t old_path = vm_pop_u32(vm);
-                int32_t old_fd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_path_rename(
-                    vm,
-                    old_fd,
-                    old_path,
-                    old_path_len,
-                    new_fd,
-                    new_path,
-                    new_path_len
-                ));
-            }
-            break;
-            case ImpName_path_open:
-            {
-                uint32_t fd = vm_pop_u32(vm);
-                uint32_t fs_flags = vm_pop_u32(vm);
-                uint64_t fs_rights_inheriting = vm_pop_u64(vm);
-                uint64_t fs_rights_base = vm_pop_u64(vm);
-                uint32_t oflags = vm_pop_u32(vm);
-                uint32_t path_len = vm_pop_u32(vm);
-                uint32_t path = vm_pop_u32(vm);
-                uint32_t dirflags = vm_pop_u32(vm);
-                int32_t dirfd = vm_pop_i32(vm);
-                vm_push_u32(vm, wasi_path_open(
-                    vm,
-                    dirfd,
-                    dirflags,
-                    path,
-                    path_len,
-                    oflags,
-                    fs_rights_base,
-                    fs_rights_inheriting,
-                    fs_flags,
-                    fd
-                ));
-            }
-            break;
-            case ImpName_path_remove_directory:
-            {
-                panic("unexpected call to path_remove_directory");
-            }
-            break;
-            case ImpName_path_unlink_file:
-            {
-                panic("unexpected call to path_unlink_file");
-            }
-            break;
-            case ImpName_clock_time_get:
-            {
-                uint32_t timestamp = vm_pop_u32(vm);
-                uint64_t precision = vm_pop_u64(vm);
-                uint32_t clock_id = vm_pop_u32(vm);
-                vm_push_u32(vm, wasi_clock_time_get(vm, clock_id, precision, timestamp));
-            }
-            break;
-            case ImpName_fd_pread:
-            {
-                panic("unexpected call to fd_pread");
-            }
-            break;
-            case ImpName_debug:
-            {
-                uint64_t number = vm_pop_u64(vm);
-                uint32_t text = vm_pop_u32(vm);
-                wasi_debug(vm, text, number);
-            }
-            break;
-            case ImpName_debug_slice:
-            {
-                uint32_t len = vm_pop_u32(vm);
-                uint32_t ptr = vm_pop_u32(vm);
-                wasi_debug_slice(vm, ptr, len);
-            }
-            break;
-        }
-        break;
-    }
-}
-
-static void vm_call(struct VirtualMachine *vm, const struct Function *func) {
-    //struct TypeInfo *type_info = &vm->types[func->type_idx];
-    //fprintf(stderr, "enter fn_id: %u, param_count: %u, result_count: %u, locals_size: %u\n",
-    //    func->id, type_info->param_count, type_info->result_count, func->locals_size);
-
-    // Push zeroed locals to stack
-    memset(&vm->stack[vm->stack_top], 0, func->locals_size * sizeof(uint32_t));
-    vm->stack_top += func->locals_size;
-
-    vm_push_u32(vm, vm->pc.opcode);
-    vm_push_u32(vm, vm->pc.operand);
-
-    vm->pc = func->entry_pc;
-}
-
-static void vm_br_void(struct VirtualMachine *vm) {
-    uint32_t stack_adjust = vm->operands[vm->pc.operand];
-
-    vm->stack_top -= stack_adjust;
-
-    vm->pc.opcode = vm->operands[vm->pc.operand + 1];
-    vm->pc.operand = vm->operands[vm->pc.operand + 2];
-}
-
-static void vm_br_u32(struct VirtualMachine *vm) {
-    uint32_t stack_adjust = vm->operands[vm->pc.operand];
-
-    uint32_t result = vm_pop_u32(vm);
-    vm->stack_top -= stack_adjust;
-    vm_push_u32(vm, result);
-
-    vm->pc.opcode = vm->operands[vm->pc.operand + 1];
-    vm->pc.operand = vm->operands[vm->pc.operand + 2];
-}
-
-static void vm_br_u64(struct VirtualMachine *vm) {
-    uint32_t stack_adjust = vm->operands[vm->pc.operand];
-
-    uint64_t result = vm_pop_u64(vm);
-    vm->stack_top -= stack_adjust;
-    vm_push_u64(vm, result);
-
-    vm->pc.opcode = vm->operands[vm->pc.operand + 1];
-    vm->pc.operand = vm->operands[vm->pc.operand + 2];
-}
-
-static void vm_return_void(struct VirtualMachine *vm) {
-    uint32_t stack_adjust = vm->operands[vm->pc.operand + 0];
-    uint32_t frame_size = vm->operands[vm->pc.operand + 1];
-
-    vm->stack_top -= stack_adjust;
-    vm->pc.operand = vm_pop_u32(vm);
-    vm->pc.opcode = vm_pop_u32(vm);
-
-    vm->stack_top -= frame_size;
-}
-
-static void vm_return_u32(struct VirtualMachine *vm) {
-    uint32_t stack_adjust = vm->operands[vm->pc.operand + 0];
-    uint32_t frame_size = vm->operands[vm->pc.operand + 1];
-
-    uint32_t result = vm_pop_u32(vm);
-
-    vm->stack_top -= stack_adjust;
-    vm->pc.operand = vm_pop_u32(vm);
-    vm->pc.opcode = vm_pop_u32(vm);
-
-    vm->stack_top -= frame_size;
-    vm_push_u32(vm, result);
-}
-
-static void vm_return_u64(struct VirtualMachine *vm) {
-    uint32_t stack_adjust = vm->operands[vm->pc.operand + 0];
-    uint32_t frame_size = vm->operands[vm->pc.operand + 1];
-
-    uint64_t result = vm_pop_u64(vm);
-
-    vm->stack_top -= stack_adjust;
-    vm->pc.operand = vm_pop_u32(vm);
-    vm->pc.opcode = vm_pop_u32(vm);
-
-    vm->stack_top -= frame_size;
-    vm_push_u64(vm, result);
-}
-
-static void vm_run(struct VirtualMachine *vm) {
-    uint8_t *opcodes = vm->opcodes;
-    uint32_t *operands = vm->operands;
-    struct ProgramCounter *pc = &vm->pc;
-    uint32_t global_0 = vm->globals[0];
-    for (;;) {
-        enum Op op = opcodes[pc->opcode];
-        //fprintf(stderr, "stack[%u:%u]=%x:%x pc=%x:%x op=%u\n",
-        //    vm->stack_top - 2, vm->stack_top - 1,
-        //    vm->stack[vm->stack_top - 2], vm->stack[vm->stack_top - 1],
-        //    pc->opcode, pc->operand, op);
-        pc->opcode += 1;
-        switch (op) {
-            case Op_unreachable:
-                panic("unreachable reached");
-            case Op_br_void:
-                vm_br_void(vm);
-                break;
-            case Op_br_32:
-                vm_br_u32(vm);
-                break;
-            case Op_br_64:
-                vm_br_u64(vm);
-                break;
-            case Op_br_nez_void:
-                if (vm_pop_u32(vm) != 0) {
-                    vm_br_void(vm);
-                } else {
-                    pc->operand += 3;
-                }
-                break;
-            case Op_br_nez_32:
-                if (vm_pop_u32(vm) != 0) {
-                    vm_br_u32(vm);
-                } else {
-                    pc->operand += 3;
-                }
-                break;
-            case Op_br_nez_64:
-                if (vm_pop_u32(vm) != 0) {
-                    vm_br_u64(vm);
-                } else {
-                    pc->operand += 3;
-                }
-                break;
-            case Op_br_eqz_void:
-                if (vm_pop_u32(vm) == 0) {
-                    vm_br_void(vm);
-                } else {
-                    pc->operand += 3;
-                }
-                break;
-            case Op_br_eqz_32:
-                if (vm_pop_u32(vm) == 0) {
-                    vm_br_u32(vm);
-                } else {
-                    pc->operand += 3;
-                }
-                break;
-            case Op_br_eqz_64:
-                if (vm_pop_u32(vm) == 0) {
-                    vm_br_u64(vm);
-                } else {
-                    pc->operand += 3;
-                }
-                break;
-            case Op_br_table_void:
-                {
-                    uint32_t index = min_u32(vm_pop_u32(vm), operands[pc->operand]);
-                    pc->operand += 1 + index * 3;
-                    vm_br_void(vm);
-                }
-                break;
-            case Op_br_table_32:
-                {
-                    uint32_t index = min_u32(vm_pop_u32(vm), operands[pc->operand]);
-                    pc->operand += 1 + index * 3;
-                    vm_br_u32(vm);
-                }
-                break;
-            case Op_br_table_64:
-                {
-                    uint32_t index = min_u32(vm_pop_u32(vm), operands[pc->operand]);
-                    pc->operand += 1 + index * 3;
-                    vm_br_u64(vm);
-                }
-                break;
-            case Op_return_void:
-                vm_return_void(vm);
-                break;
-            case Op_return_32:
-                vm_return_u32(vm);
-                break;
-            case Op_return_64:
-                vm_return_u64(vm);
-                break;
-            case Op_call_import:
-                {
-                    uint8_t import_idx = opcodes[pc->opcode];
-                    pc->opcode += 1;
-                    vm_callImport(vm, &vm->imports[import_idx]);
-                }
-                break;
-            case Op_call_func:
-                {
-                    uint32_t func_idx = operands[pc->operand];
-                    pc->operand += 1;
-                    vm_call(vm, &vm->functions[func_idx]);
-                }
-                break;
-            case Op_call_indirect:
-                {
-                    uint32_t fn_id = vm->table[vm_pop_u32(vm)];
-                    if (fn_id < vm->imports_len)
-                        vm_callImport(vm, &vm->imports[fn_id]);
-                    else
-                        vm_call(vm, &vm->functions[fn_id - vm->imports_len]);
-                }
-                break;
-
-            case Op_drop_32:
-                vm->stack_top -= 1;
-                break;
-            case Op_drop_64:
-                vm->stack_top -= 2;
-                break;
-            case Op_select_32:
-                {
-                    uint32_t c = vm_pop_u32(vm);
-                    uint32_t b = vm_pop_u32(vm);
-                    uint32_t a = vm_pop_u32(vm);
-                    uint32_t result = (c != 0) ? a : b;
-                    vm_push_u32(vm, result);
-                }
-                break;
-            case Op_select_64:
-                {
-                    uint32_t c = vm_pop_u32(vm);
-                    uint64_t b = vm_pop_u64(vm);
-                    uint64_t a = vm_pop_u64(vm);
-                    uint64_t result = (c != 0) ? a : b;
-                    vm_push_u64(vm, result);
-                }
-                break;
-
-            case Op_local_get_32:
-                {
-                    uint32_t *local = &vm->stack[vm->stack_top - operands[pc->operand]];
-                    pc->operand += 1;
-                    vm_push_u32(vm, *local);
-                }
-                break;
-            case Op_local_get_64:
-                {
-                    uint32_t *local = &vm->stack[vm->stack_top - operands[pc->operand]];
-                    pc->operand += 1;
-                    vm_push_u64(vm, local[0] | (uint64_t)local[1] << 32);
-                }
-                break;
-            case Op_local_set_32:
-                {
-                    uint32_t *local = &vm->stack[vm->stack_top - operands[pc->operand]];
-                    pc->operand += 1;
-                    *local = vm_pop_u32(vm);
-                }
-                break;
-            case Op_local_set_64:
-                {
-                    uint32_t *local = &vm->stack[vm->stack_top - operands[pc->operand]];
-                    pc->operand += 1;
-                    uint64_t value = vm_pop_u64(vm);
-                    local[0] = (uint32_t)(value >> 0);
-                    local[1] = (uint32_t)(value >> 32);
-                }
-                break;
-            case Op_local_tee_32:
-                {
-                    uint32_t *local = &vm->stack[vm->stack_top - operands[pc->operand]];
-                    pc->operand += 1;
-                    *local = vm->stack[vm->stack_top - 1];
-                }
-                break;
-            case Op_local_tee_64:
-                {
-                    uint32_t *local = &vm->stack[vm->stack_top - operands[pc->operand]];
-                    pc->operand += 1;
-                    local[0] = vm->stack[vm->stack_top - 2];
-                    local[1] = vm->stack[vm->stack_top - 1];
-                }
-                break;
-
-            case Op_global_get_0_32:
-                vm_push_u32(vm, global_0);
-                break;
-            case Op_global_get_32:
-                {
-                    uint32_t idx = operands[pc->operand];
-                    pc->operand += 1;
-                    vm_push_u32(vm, vm->globals[idx]);
-                }
-                break;
-            case Op_global_set_0_32:
-                global_0 = vm_pop_u32(vm);
-                break;
-            case Op_global_set_32:
-                {
-                    uint32_t idx = operands[pc->operand];
-                    pc->operand += 1;
-                    vm->globals[idx] = vm_pop_u32(vm);
-                }
-                break;
-
-            case Op_load_0_8:
-                {
-                    uint32_t address = vm_pop_u32(vm);
-                    vm_push_u32(vm, (uint8_t)vm->memory[address]);
-                }
-                break;
-            case Op_load_8:
-                {
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    vm_push_u32(vm, (uint8_t)vm->memory[address]);
-                }
-                break;
-            case Op_load_0_16:
-                {
-                    uint32_t address = vm_pop_u32(vm);
-                    vm_push_u32(vm, read_u16_le(&vm->memory[address]));
-                }
-                break;
-            case Op_load_16:
-                {
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    vm_push_u32(vm, read_u16_le(&vm->memory[address]));
-                }
-                break;
-            case Op_load_0_32:
-                {
-                    uint32_t address = vm_pop_u32(vm);
-                    vm_push_u32(vm, read_u32_le(&vm->memory[address]));
-                }
-                break;
-            case Op_load_32:
-                {
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    vm_push_u32(vm, read_u32_le(&vm->memory[address]));
-                }
-                break;
-            case Op_load_0_64:
-                {
-                    uint32_t address = vm_pop_u32(vm);
-                    vm_push_u64(vm, read_u64_le(&vm->memory[address]));
-                }
-                break;
-            case Op_load_64:
-                {
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    vm_push_u64(vm, read_u64_le(&vm->memory[address]));
-                }
-                break;
-            case Op_store_0_8:
-                {
-                    uint8_t value = (uint8_t)vm_pop_u32(vm);
-                    uint32_t address = vm_pop_u32(vm);
-                    vm->memory[address] = value;
-                }
-                break;
-            case Op_store_8:
-                {
-                    uint8_t value = (uint8_t)vm_pop_u32(vm);
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    vm->memory[address] = value;
-                }
-                break;
-            case Op_store_0_16:
-                {
-                    uint16_t value = (uint16_t)vm_pop_u32(vm);
-                    uint32_t address = vm_pop_u32(vm);
-                    write_u16_le(&vm->memory[address], value);
-                }
-                break;
-            case Op_store_16:
-                {
-                    uint16_t value = (uint16_t)vm_pop_u32(vm);
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    write_u16_le(&vm->memory[address], value);
-                }
-                break;
-            case Op_store_0_32:
-                {
-                    uint32_t value = vm_pop_u32(vm);
-                    uint32_t address = vm_pop_u32(vm);
-                    write_u32_le(&vm->memory[address], value);
-                }
-                break;
-            case Op_store_32:
-                {
-                    uint32_t value = vm_pop_u32(vm);
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    write_u32_le(&vm->memory[address], value);
-                }
-                break;
-            case Op_store_0_64:
-                {
-                    uint64_t value = vm_pop_u64(vm);
-                    uint32_t address = vm_pop_u32(vm);
-                    write_u64_le(&vm->memory[address], value);
-                }
-                break;
-            case Op_store_64:
-                {
-                    uint64_t value = vm_pop_u64(vm);
-                    uint32_t address = vm_pop_u32(vm) + operands[pc->operand];
-                    pc->operand += 1;
-                    write_u64_le(&vm->memory[address], value);
-                }
-                break;
-            case Op_mem_size:
-                vm_push_u32(vm, vm->memory_len / wasm_page_size);
-                break;
-            case Op_mem_grow:
-                {
-                    uint32_t page_count = vm_pop_u32(vm);
-                    uint32_t old_page_count = vm->memory_len / wasm_page_size;
-                    uint32_t new_len = vm->memory_len + page_count * wasm_page_size;
-                    if (new_len > max_memory) {
-                        vm_push_i32(vm, -1);
-                    } else {
-                        vm->memory_len = new_len;
-                        vm_push_u32(vm, old_page_count);
-                    }
-                }
-                break;
-
-            case Op_const_0_32:
-                vm_push_i32(vm, 0);
-                break;
-            case Op_const_0_64:
-                vm_push_i64(vm, 0);
-                break;
-            case Op_const_1_32:
-                vm_push_i32(vm, 1);
-                break;
-            case Op_const_1_64:
-                vm_push_i64(vm, 1);
-                break;
-            case Op_const_32:
-                {
-                    uint32_t value = operands[pc->operand];
-                    pc->operand += 1;
-                    vm_push_i32(vm, value);
-                }
-                break;
-            case Op_const_64:
-                {
-                    uint64_t value = ((uint64_t)operands[pc->operand]) |
-                        (((uint64_t)operands[pc->operand + 1]) << 32);
-                    pc->operand += 2;
-                    vm_push_i64(vm, value);
-                }
-                break;
-            case Op_const_umax_32:
-                vm_push_i32(vm, -1);
-                break;
-            case Op_const_umax_64:
-                vm_push_i64(vm, -1);
-                break;
-
-            case Op_eqz_32:
-                {
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs == 0);
-                }
-                break;
-            case Op_eq_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs == rhs);
-                }
-                break;
-            case Op_ne_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs != rhs);
-                }
-                break;
-            case Op_slt_32:
-                {
-                    int32_t rhs = vm_pop_i32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_u32(vm, lhs < rhs);
-                }
-                break;
-            case Op_ult_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs < rhs);
-                }
-                break;
-            case Op_sgt_32:
-                {
-                    int32_t rhs = vm_pop_i32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_u32(vm, lhs > rhs);
-                }
-                break;
-            case Op_ugt_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs > rhs);
-                }
-                break;
-            case Op_sle_32:
-                {
-                    int32_t rhs = vm_pop_i32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_ule_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_sge_32:
-                {
-                    int32_t rhs = vm_pop_i32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_u32(vm, lhs >= rhs);
-                }
-                break;
-            case Op_uge_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs >= rhs);
-                }
-                break;
-
-            case Op_eqz_64:
-                {
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs == 0);
-                }
-                break;
-            case Op_eq_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs == rhs);
-                }
-                break;
-            case Op_ne_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs != rhs);
-                }
-                break;
-            case Op_slt_64:
-                {
-                    int64_t rhs = vm_pop_i64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_u32(vm, lhs < rhs);
-                }
-                break;
-            case Op_ult_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs < rhs);
-                }
-                break;
-            case Op_sgt_64:
-                {
-                    int64_t rhs = vm_pop_i64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_u32(vm, lhs > rhs);
-                }
-                break;
-            case Op_ugt_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs > rhs);
-                }
-                break;
-            case Op_sle_64:
-                {
-                    int64_t rhs = vm_pop_i64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_ule_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_sge_64:
-                {
-                    int64_t rhs = vm_pop_i64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_u32(vm, lhs >= rhs);
-                }
-                break;
-            case Op_uge_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u32(vm, lhs >= rhs);
-                }
-                break;
-
-            case Op_feq_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_u32(vm, lhs == rhs);
-                }
-                break;
-            case Op_fne_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_u32(vm, lhs != rhs);
-                }
-                break;
-            case Op_flt_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_u32(vm, lhs < rhs);
-                }
-                break;
-            case Op_fgt_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_u32(vm, lhs > rhs);
-                }
-                break;
-            case Op_fle_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_fge_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_u32(vm, lhs >= rhs);
-                }
-                break;
-
-            case Op_feq_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_u32(vm, lhs == rhs);
-                }
-                break;
-            case Op_fne_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_u32(vm, lhs != rhs);
-                }
-                break;
-            case Op_flt_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_fgt_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_u32(vm, lhs > rhs);
-                }
-                break;
-            case Op_fle_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_u32(vm, lhs <= rhs);
-                }
-                break;
-            case Op_fge_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_u32(vm, lhs >= rhs);
-                }
-                break;
-
-            case Op_clz_32:
-                {
-                    uint32_t operand = vm_pop_u32(vm);
-                    uint32_t result = (operand == 0) ? 32 : __builtin_clz(operand);
-                    vm_push_u32(vm, result);
-                }
-                break;
-            case Op_ctz_32:
-                {
-                    uint32_t operand = vm_pop_u32(vm);
-                    uint32_t result = (operand == 0) ? 32 : __builtin_ctz(operand);
-                    vm_push_u32(vm, result);
-                }
-                break;
-            case Op_popcnt_32:
-                {
-                    uint32_t operand = vm_pop_u32(vm);
-                    uint32_t result = __builtin_popcount(operand);
-                    vm_push_u32(vm, result);
-                }
-                break;
-            case Op_add_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs + rhs);
-                }
-                break;
-            case Op_sub_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs - rhs);
-                }
-                break;
-            case Op_mul_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs * rhs);
-                }
-                break;
-            case Op_sdiv_32:
-                {
-                    int32_t rhs = vm_pop_i32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_i32(vm, lhs / rhs);
-                }
-                break;
-            case Op_udiv_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs / rhs);
-                }
-                break;
-            case Op_srem_32:
-                {
-                    int32_t rhs = vm_pop_i32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_i32(vm, lhs % rhs);
-                }
-                break;
-            case Op_urem_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs % rhs);
-                }
-                break;
-            case Op_and_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs & rhs);
-                }
-                break;
-            case Op_or_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs | rhs);
-                }
-                break;
-            case Op_xor_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs ^ rhs);
-                }
-                break;
-            case Op_shl_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs << (rhs & 0x1f));
-                }
-                break;
-            case Op_ashr_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    int32_t lhs = vm_pop_i32(vm);
-                    vm_push_i32(vm, lhs >> (rhs & 0x1f));
-                }
-                break;
-            case Op_lshr_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, lhs >> (rhs & 0x1f));
-                }
-                break;
-            case Op_rol_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, rotl32(lhs, rhs));
-                }
-                break;
-            case Op_ror_32:
-                {
-                    uint32_t rhs = vm_pop_u32(vm);
-                    uint32_t lhs = vm_pop_u32(vm);
-                    vm_push_u32(vm, rotr32(lhs, rhs));
-                }
-                break;
-
-            case Op_clz_64:
-                {
-                    uint64_t operand = vm_pop_u64(vm);
-                    uint64_t result = (operand == 0) ? 64 : __builtin_clzll(operand);
-                    vm_push_u64(vm, result);
-                }
-                break;
-            case Op_ctz_64:
-                {
-                    uint64_t operand = vm_pop_u64(vm);
-                    uint64_t result = (operand == 0) ? 64 : __builtin_ctzll(operand);
-                    vm_push_u64(vm, result);
-                }
-                break;
-            case Op_popcnt_64:
-                {
-                    uint64_t operand = vm_pop_u64(vm);
-                    uint64_t result = __builtin_popcountll(operand);
-                    vm_push_u64(vm, result);
-                }
-                break;
-            case Op_add_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs + rhs);
-                }
-                break;
-            case Op_sub_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs - rhs);
-                }
-                break;
-            case Op_mul_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs * rhs);
-                }
-                break;
-            case Op_sdiv_64:
-                {
-                    int64_t rhs = vm_pop_i64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_i64(vm, lhs / rhs);
-                }
-                break;
-            case Op_udiv_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs / rhs);
-                }
-                break;
-            case Op_srem_64:
-                {
-                    int64_t rhs = vm_pop_i64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_i64(vm, lhs % rhs);
-                }
-                break;
-            case Op_urem_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs % rhs);
-                }
-                break;
-            case Op_and_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs & rhs);
-                }
-                break;
-            case Op_or_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs | rhs);
-                }
-                break;
-            case Op_xor_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs ^ rhs);
-                }
-                break;
-            case Op_shl_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs << (rhs & 0x3f));
-                }
-                break;
-            case Op_ashr_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    int64_t lhs = vm_pop_i64(vm);
-                    vm_push_i64(vm, lhs >> (rhs & 0x3f));
-                }
-                break;
-            case Op_lshr_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, lhs >> (rhs & 0x3f));
-                }
-                break;
-            case Op_rol_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, rotl64(lhs, rhs));
-                }
-                break;
-            case Op_ror_64:
-                {
-                    uint64_t rhs = vm_pop_u64(vm);
-                    uint64_t lhs = vm_pop_u64(vm);
-                    vm_push_u64(vm, rotr64(lhs, rhs));
-                }
-                break;
-
-            case Op_fabs_32:
-                vm_push_f32(vm, fabsf(vm_pop_f32(vm)));
-                break;
-            case Op_fneg_32:
-                vm_push_f32(vm, -vm_pop_f32(vm));
-                break;
-            case Op_ceil_32:
-                vm_push_f32(vm, ceilf(vm_pop_f32(vm)));
-                break;
-            case Op_floor_32:
-                vm_push_f32(vm, floorf(vm_pop_f32(vm)));
-                break;
-            case Op_trunc_32:
-                vm_push_f32(vm, truncf(vm_pop_f32(vm)));
-                break;
-            case Op_nearest_32:
-                vm_push_f32(vm, roundf(vm_pop_f32(vm)));
-                break;
-            case Op_sqrt_32:
-                vm_push_f32(vm, sqrtf(vm_pop_f32(vm)));
-                break;
-            case Op_fadd_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, lhs + rhs);
-                }
-                break;
-            case Op_fsub_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, lhs - rhs);
-                }
-                break;
-            case Op_fmul_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, lhs * rhs);
-                }
-                break;
-            case Op_fdiv_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, lhs / rhs);
-                }
-                break;
-            case Op_fmin_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, fminf(lhs, rhs));
-                }
-                break;
-            case Op_fmax_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, fmaxf(lhs, rhs));
-                }
-                break;
-            case Op_copysign_32:
-                {
-                    float rhs = vm_pop_f32(vm);
-                    float lhs = vm_pop_f32(vm);
-                    vm_push_f32(vm, copysignf(lhs, rhs));
-                }
-                break;
-
-            case Op_fabs_64:
-                vm_push_f64(vm, fabs(vm_pop_f64(vm)));
-                break;
-            case Op_fneg_64:
-                vm_push_f64(vm, -vm_pop_f64(vm));
-                break;
-            case Op_ceil_64:
-                vm_push_f64(vm, ceil(vm_pop_f64(vm)));
-                break;
-            case Op_floor_64:
-                vm_push_f64(vm, floor(vm_pop_f64(vm)));
-                break;
-            case Op_trunc_64:
-                vm_push_f64(vm, trunc(vm_pop_f64(vm)));
-                break;
-            case Op_nearest_64:
-                vm_push_f64(vm, round(vm_pop_f64(vm)));
-                break;
-            case Op_sqrt_64:
-                vm_push_f64(vm, sqrt(vm_pop_f64(vm)));
-                break;
-            case Op_fadd_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, lhs + rhs);
-                }
-                break;
-            case Op_fsub_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, lhs - rhs);
-                }
-                break;
-            case Op_fmul_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, lhs * rhs);
-                }
-                break;
-            case Op_fdiv_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, lhs / rhs);
-                }
-                break;
-            case Op_fmin_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, fmin(lhs, rhs));
-                }
-                break;
-            case Op_fmax_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, fmax(lhs, rhs));
-                }
-                break;
-            case Op_copysign_64:
-                {
-                    double rhs = vm_pop_f64(vm);
-                    double lhs = vm_pop_f64(vm);
-                    vm_push_f64(vm, copysign(lhs, rhs));
-                }
-                break;
-
-            case Op_ftos_32_32: vm_push_f32(vm,    (float)vm_pop_i32(vm)); break;
-            case Op_ftou_32_32: vm_push_f32(vm,    (float)vm_pop_u32(vm)); break;
-            case Op_ftos_32_64: vm_push_f32(vm,    (float)vm_pop_i64(vm)); break;
-            case Op_ftou_32_64: vm_push_f32(vm,    (float)vm_pop_u64(vm)); break;
-            case Op_sext_64_32: vm_push_i64(vm,           vm_pop_i32(vm)); break;
-            case Op_ftos_64_32: vm_push_i64(vm,  (int64_t)vm_pop_f32(vm)); break;
-            case Op_ftou_64_32: vm_push_u64(vm, (uint64_t)vm_pop_f32(vm)); break;
-            case Op_ftos_64_64: vm_push_i64(vm,  (int64_t)vm_pop_f64(vm)); break;
-            case Op_ftou_64_64: vm_push_u64(vm, (uint64_t)vm_pop_f64(vm)); break;
-            case Op_stof_32_32: vm_push_f32(vm,    (float)vm_pop_i32(vm)); break;
-            case Op_utof_32_32: vm_push_f32(vm,    (float)vm_pop_u32(vm)); break;
-            case Op_stof_32_64: vm_push_f32(vm,    (float)vm_pop_i64(vm)); break;
-            case Op_utof_32_64: vm_push_f32(vm,    (float)vm_pop_u64(vm)); break;
-            case Op_ftof_32_64: vm_push_f32(vm,    (float)vm_pop_f64(vm)); break;
-            case Op_stof_64_32: vm_push_f64(vm,   (double)vm_pop_i32(vm)); break;
-            case Op_utof_64_32: vm_push_f64(vm,   (double)vm_pop_u32(vm)); break;
-            case Op_stof_64_64: vm_push_f64(vm,   (double)vm_pop_i64(vm)); break;
-            case Op_utof_64_64: vm_push_f64(vm,   (double)vm_pop_u64(vm)); break;
-            case Op_ftof_64_32: vm_push_f64(vm,   (double)vm_pop_f32(vm)); break;
-            case Op_sext8_32:   vm_push_i32(vm,   (int8_t)vm_pop_i32(vm)); break;
-            case Op_sext16_32:  vm_push_i32(vm,  (int16_t)vm_pop_i32(vm)); break;
-            case Op_sext8_64:   vm_push_i64(vm,   (int8_t)vm_pop_i64(vm)); break;
-            case Op_sext16_64:  vm_push_i64(vm,  (int16_t)vm_pop_i64(vm)); break;
-            case Op_sext32_64:  vm_push_i64(vm,  (int32_t)vm_pop_i64(vm)); break;
-
-            case Op_memcpy:
-                {
-                    uint32_t n = vm_pop_u32(vm);
-                    uint32_t src = vm_pop_u32(vm);
-                    uint32_t dest = vm_pop_u32(vm);
-                    assert(dest + n <= vm->memory_len);
-                    assert(src + n <= vm->memory_len);
-                    assert(src + n <= dest || dest + n <= src); // overlapping
-                    memcpy(vm->memory + dest, vm->memory + src, n);
-                }
-                break;
-            case Op_memset:
-                {
-                    uint32_t n = vm_pop_u32(vm);
-                    uint8_t value = (uint8_t)vm_pop_u32(vm);
-                    uint32_t dest = vm_pop_u32(vm);
-                    assert(dest + n <= vm->memory_len);
-                    memset(vm->memory + dest, value, n);
-                }
-                break;
-        }
-    }
-}
-
-static size_t common_prefix(const char *a, const char *b) {
-    size_t i = 0;
-    for (; a[i] == b[i]; i += 1) {}
-    return i;
-}
-
-int main(int argc, char **argv) {
-    char *memory = mmap( NULL, max_memory, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
-
-    const char *zig_lib_dir_path = argv[1];
-    const char *cmake_binary_dir_path = argv[2];
-    const char *root_name = argv[3];
-    size_t argv_i = 4;
-    const char *wasm_file = argv[argv_i];
-
-    size_t cwd_path_len = common_prefix(zig_lib_dir_path, cmake_binary_dir_path);
-    const char *rel_cmake_bin_path = cmake_binary_dir_path + cwd_path_len;
-
-    size_t rel_cmake_bin_path_len = strlen(rel_cmake_bin_path);
-
-    const char *new_argv[30];
-    char new_argv_buf[PATH_MAX + 1024];
-    uint32_t new_argv_i = 0;
-    uint32_t new_argv_buf_i = 0;
-
-    int cache_dir = -1;
-    {
-        char cache_dir_buf[PATH_MAX * 2];
-        size_t i = 0;
-        size_t cmake_binary_dir_path_len = strlen(cmake_binary_dir_path);
-
-        memcpy(cache_dir_buf + i, cmake_binary_dir_path, cmake_binary_dir_path_len);
-        i += cmake_binary_dir_path_len;
-
-        cache_dir_buf[i] = '/';
-        i += 1;
-
-        memcpy(cache_dir_buf + i, "zig1-cache", strlen("zig1-cache"));
-        i += strlen("zig1-cache");
-
-        cache_dir_buf[i] = 0;
-
-        mkdir(cache_dir_buf, 0777);
-        cache_dir = err_wrap("opening cache dir",
-                open(cache_dir_buf, O_DIRECTORY|O_RDONLY|O_CLOEXEC));
-    }
-
-    // Construct a new argv for the WASI code which has absolute paths
-    // converted to relative paths, and has the target and terminal status
-    // autodetected.
-
-    // wasm file path
-    new_argv[new_argv_i] = argv[argv_i];
-    new_argv_i += 1;
-    argv_i += 1;
-
-    for (; argv[argv_i]; argv_i += 1) {
-        new_argv[new_argv_i] = argv[argv_i];
-        new_argv_i += 1;
-    }
-
-    {
-        new_argv[new_argv_i] = "--name";
-        new_argv_i += 1;
-
-        new_argv[new_argv_i] = root_name;
-        new_argv_i += 1;
-
-        char *emit_bin_arg = new_argv_buf + new_argv_buf_i;
-        memcpy(new_argv_buf + new_argv_buf_i, "-femit-bin=", strlen("-femit-bin="));
-        new_argv_buf_i += strlen("-femit-bin=");
-        memcpy(new_argv_buf + new_argv_buf_i, rel_cmake_bin_path, rel_cmake_bin_path_len);
-        new_argv_buf_i += rel_cmake_bin_path_len;
-        new_argv_buf[new_argv_buf_i] = '/';
-        new_argv_buf_i += 1;
-        memcpy(new_argv_buf + new_argv_buf_i, root_name, strlen(root_name));
-        new_argv_buf_i += strlen(root_name);
-        memcpy(new_argv_buf + new_argv_buf_i, ".c", 3);
-        new_argv_buf_i += 3;
-
-        new_argv[new_argv_i] = emit_bin_arg;
-        new_argv_i += 1;
-    }
-
-    {
-        new_argv[new_argv_i] = "--pkg-begin";
-        new_argv_i += 1;
-
-        new_argv[new_argv_i] = "build_options";
-        new_argv_i += 1;
-
-        char *build_options_path = new_argv_buf + new_argv_buf_i;
-        memcpy(new_argv_buf + new_argv_buf_i, rel_cmake_bin_path, rel_cmake_bin_path_len);
-        new_argv_buf_i += rel_cmake_bin_path_len;
-        new_argv_buf[new_argv_buf_i] = '/';
-        new_argv_buf_i += 1;
-        memcpy(new_argv_buf + new_argv_buf_i, "config.zig", strlen("config.zig"));
-        new_argv_buf_i += strlen("config.zig");
-        new_argv_buf[new_argv_buf_i] = 0;
-        new_argv_buf_i += 1;
-
-        new_argv[new_argv_i] = build_options_path;
-        new_argv_i += 1;
-
-        new_argv[new_argv_i] = "--pkg-end";
-        new_argv_i += 1;
-    }
-
-    {
-        new_argv[new_argv_i] = "-target";
-        new_argv_i += 1;
-
-        new_argv[new_argv_i] = ZIG_TRIPLE_ARCH "-" ZIG_TRIPLE_OS;
-        new_argv_i += 1;
-    }
-
-    if (isatty(STDERR_FILENO) != 0) {
-        new_argv[new_argv_i] = "--color";
-        new_argv_i += 1;
-
-        new_argv[new_argv_i] = "on";
-        new_argv_i += 1;
-    }
-
-    new_argv[new_argv_i] = NULL;
-
-    const struct ByteSlice compressed_bytes = read_file_alloc(wasm_file);
-
-    const size_t max_uncompressed_size = 2500000;
-    char *mod_ptr = arena_alloc(max_uncompressed_size);
-    size_t mod_len = ZSTD_decompress(mod_ptr, max_uncompressed_size,
-            compressed_bytes.ptr, compressed_bytes.len);
-
-    int cwd = err_wrap("opening cwd", open(".", O_DIRECTORY|O_RDONLY|O_CLOEXEC));
-    int zig_lib_dir = err_wrap("opening zig lib dir", open(zig_lib_dir_path, O_DIRECTORY|O_RDONLY|O_CLOEXEC));
-
-    add_preopen(0, "stdin", STDIN_FILENO);
-    add_preopen(1, "stdout", STDOUT_FILENO);
-    add_preopen(2, "stderr", STDERR_FILENO);
-    add_preopen(3, ".", cwd);
-    add_preopen(4, "/cache", cache_dir);
-    add_preopen(5, "/lib", zig_lib_dir);
-
-    uint32_t i = 0;
-
-    if (mod_ptr[0] != 0 || mod_ptr[1] != 'a' || mod_ptr[2] != 's' || mod_ptr[3] != 'm') {
-        panic("bad magic");
-    }
-    i += 4;
-
-    uint32_t version = read_u32_le(mod_ptr + i);
-    i += 4;
-    if (version != 1) panic("bad wasm version");
-
-    uint32_t section_starts[13];
-    memset(&section_starts, 0, sizeof(uint32_t) * 13);
-
-    while (i < mod_len) {
-        uint8_t section_id = mod_ptr[i];
-        i += 1;
-        uint32_t section_len = read32_uleb128(mod_ptr, &i);
-        section_starts[section_id] = i;
-        i += section_len;
-    }
-
-    // Map type indexes to offsets into the module.
-    struct TypeInfo *types;
-    {
-        i = section_starts[Section_type];
-        uint32_t types_len = read32_uleb128(mod_ptr, &i);
-        types = arena_alloc(sizeof(struct TypeInfo) * types_len);
-        for (size_t type_i = 0; type_i < types_len; type_i += 1) {
-            struct TypeInfo *info = &types[type_i];
-            if (mod_ptr[i] != 0x60) panic("bad type byte");
-            i += 1;
-
-            info->param_count = read32_uleb128(mod_ptr, &i);
-            if (info->param_count > 32) panic("found a type with over 32 parameters");
-            info->param_types = 0;
-            for (uint32_t param_i = 0; param_i < info->param_count; param_i += 1) {
-                int64_t param_type = read64_ileb128(mod_ptr, &i);
-                switch (param_type) {
-                    case -1: case -3: bs_unset(&info->param_types, param_i); break;
-                    case -2: case -4:   bs_set(&info->param_types, param_i); break;
-                    default: panic("unexpected param type");
-                }
-            }
-
-            info->result_count = read32_uleb128(mod_ptr, &i);
-            info->result_types = 0;
-            for (uint32_t result_i = 0; result_i < info->result_count; result_i += 1) {
-                int64_t result_type = read64_ileb128(mod_ptr, &i);
-                switch (result_type) {
-                    case -1: case -3: bs_unset(&info->result_types, result_i); break;
-                    case -2: case -4:   bs_set(&info->result_types, result_i); break;
-                    default: panic("unexpected result type");
-                }
-            }
-        }
-    }
-
-    // Count the imported functions so we can correct function references.
-    struct Import *imports;
-    uint32_t imports_len;
-    {
-        i = section_starts[Section_import];
-        imports_len = read32_uleb128(mod_ptr, &i);
-        imports = arena_alloc(sizeof(struct Import) * imports_len);
-        for (size_t imp_i = 0; imp_i < imports_len; imp_i += 1) {
-            struct Import *imp = &imports[imp_i];
-
-            struct ByteSlice mod_name = read_name(mod_ptr, &i);
-            if (mod_name.len == strlen("wasi_snapshot_preview1") &&
-                memcmp(mod_name.ptr, "wasi_snapshot_preview1", mod_name.len) == 0) {
-                imp->mod = ImpMod_wasi_snapshot_preview1;
-            } else panic("unknown import module");
-
-            struct ByteSlice sym_name = read_name(mod_ptr, &i);
-            if (sym_name.len == strlen("args_get") &&
-                memcmp(sym_name.ptr, "args_get", sym_name.len) == 0) {
-                imp->name = ImpName_args_get;
-            } else if (sym_name.len == strlen("args_sizes_get") &&
-                memcmp(sym_name.ptr, "args_sizes_get", sym_name.len) == 0) {
-                imp->name = ImpName_args_sizes_get;
-            } else if (sym_name.len == strlen("clock_time_get") &&
-                memcmp(sym_name.ptr, "clock_time_get", sym_name.len) == 0) {
-                imp->name = ImpName_clock_time_get;
-            } else if (sym_name.len == strlen("debug") &&
-                memcmp(sym_name.ptr, "debug", sym_name.len) == 0) {
-                imp->name = ImpName_debug;
-            } else if (sym_name.len == strlen("debug_slice") &&
-                memcmp(sym_name.ptr, "debug_slice", sym_name.len) == 0) {
-                imp->name = ImpName_debug_slice;
-            } else if (sym_name.len == strlen("environ_get") &&
-                memcmp(sym_name.ptr, "environ_get", sym_name.len) == 0) {
-                imp->name = ImpName_environ_get;
-            } else if (sym_name.len == strlen("environ_sizes_get") &&
-                memcmp(sym_name.ptr, "environ_sizes_get", sym_name.len) == 0) {
-                imp->name = ImpName_environ_sizes_get;
-            } else if (sym_name.len == strlen("fd_close") &&
-                memcmp(sym_name.ptr, "fd_close", sym_name.len) == 0) {
-                imp->name = ImpName_fd_close;
-            } else if (sym_name.len == strlen("fd_fdstat_get") &&
-                memcmp(sym_name.ptr, "fd_fdstat_get", sym_name.len) == 0) {
-                imp->name = ImpName_fd_fdstat_get;
-            } else if (sym_name.len == strlen("fd_filestat_get") &&
-                memcmp(sym_name.ptr, "fd_filestat_get", sym_name.len) == 0) {
-                imp->name = ImpName_fd_filestat_get;
-            } else if (sym_name.len == strlen("fd_filestat_set_size") &&
-                memcmp(sym_name.ptr, "fd_filestat_set_size", sym_name.len) == 0) {
-                imp->name = ImpName_fd_filestat_set_size;
-            } else if (sym_name.len == strlen("fd_filestat_set_times") &&
-                memcmp(sym_name.ptr, "fd_filestat_set_times", sym_name.len) == 0) {
-                imp->name = ImpName_fd_filestat_set_times;
-            } else if (sym_name.len == strlen("fd_pread") &&
-                memcmp(sym_name.ptr, "fd_pread", sym_name.len) == 0) {
-                imp->name = ImpName_fd_pread;
-            } else if (sym_name.len == strlen("fd_prestat_dir_name") &&
-                memcmp(sym_name.ptr, "fd_prestat_dir_name", sym_name.len) == 0) {
-                imp->name = ImpName_fd_prestat_dir_name;
-            } else if (sym_name.len == strlen("fd_prestat_get") &&
-                memcmp(sym_name.ptr, "fd_prestat_get", sym_name.len) == 0) {
-                imp->name = ImpName_fd_prestat_get;
-            } else if (sym_name.len == strlen("fd_pwrite") &&
-                memcmp(sym_name.ptr, "fd_pwrite", sym_name.len) == 0) {
-                imp->name = ImpName_fd_pwrite;
-            } else if (sym_name.len == strlen("fd_read") &&
-                memcmp(sym_name.ptr, "fd_read", sym_name.len) == 0) {
-                imp->name = ImpName_fd_read;
-            } else if (sym_name.len == strlen("fd_readdir") &&
-                memcmp(sym_name.ptr, "fd_readdir", sym_name.len) == 0) {
-                imp->name = ImpName_fd_readdir;
-            } else if (sym_name.len == strlen("fd_write") &&
-                memcmp(sym_name.ptr, "fd_write", sym_name.len) == 0) {
-                imp->name = ImpName_fd_write;
-            } else if (sym_name.len == strlen("path_create_directory") &&
-                memcmp(sym_name.ptr, "path_create_directory", sym_name.len) == 0) {
-                imp->name = ImpName_path_create_directory;
-            } else if (sym_name.len == strlen("path_filestat_get") &&
-                memcmp(sym_name.ptr, "path_filestat_get", sym_name.len) == 0) {
-                imp->name = ImpName_path_filestat_get;
-            } else if (sym_name.len == strlen("path_open") &&
-                memcmp(sym_name.ptr, "path_open", sym_name.len) == 0) {
-                imp->name = ImpName_path_open;
-            } else if (sym_name.len == strlen("path_remove_directory") &&
-                memcmp(sym_name.ptr, "path_remove_directory", sym_name.len) == 0) {
-                imp->name = ImpName_path_remove_directory;
-            } else if (sym_name.len == strlen("path_rename") &&
-                memcmp(sym_name.ptr, "path_rename", sym_name.len) == 0) {
-                imp->name = ImpName_path_rename;
-            } else if (sym_name.len == strlen("path_unlink_file") &&
-                memcmp(sym_name.ptr, "path_unlink_file", sym_name.len) == 0) {
-                imp->name = ImpName_path_unlink_file;
-            } else if (sym_name.len == strlen("proc_exit") &&
-                memcmp(sym_name.ptr, "proc_exit", sym_name.len) == 0) {
-                imp->name = ImpName_proc_exit;
-            } else if (sym_name.len == strlen("random_get") &&
-                memcmp(sym_name.ptr, "random_get", sym_name.len) == 0) {
-                imp->name = ImpName_random_get;
-            } else panic("unknown import name");
-
-            uint32_t desc = read32_uleb128(mod_ptr, &i);
-            if (desc != 0) panic("external kind not function");
-            imp->type_idx = read32_uleb128(mod_ptr, &i);
-        }
-    }
-
-    // Find _start in the exports
-    uint32_t start_fn_idx;
-    {
-        i = section_starts[Section_export];
-        uint32_t count = read32_uleb128(mod_ptr, &i);
-        for (; count > 0; count -= 1) {
-            struct ByteSlice name = read_name(mod_ptr, &i);
-            uint32_t desc = read32_uleb128(mod_ptr, &i);
-            start_fn_idx = read32_uleb128(mod_ptr, &i);
-            if (desc == 0 && name.len == strlen("_start") &&
-                memcmp(name.ptr, "_start", name.len) == 0)
-            {
-                break;
-            }
-        }
-        if (count == 0) panic("_start symbol not found");
-    }
-
-    // Map function indexes to offsets into the module and type index.
-    struct Function *functions;
-    uint32_t functions_len;
-    {
-        i = section_starts[Section_function];
-        functions_len = read32_uleb128(mod_ptr, &i);
-        functions = arena_alloc(sizeof(struct Function) * functions_len);
-        for (size_t func_i = 0; func_i < functions_len; func_i += 1) {
-            struct Function *func = &functions[func_i];
-            func->id = imports_len + func_i;
-            func->type_idx = read32_uleb128(mod_ptr, &i);
-        }
-    }
-
-    // Allocate and initialize globals.
-    uint64_t *globals;
-    {
-        i = section_starts[Section_global];
-        uint32_t globals_len = read32_uleb128(mod_ptr, &i);
-        globals = arena_alloc(sizeof(uint64_t) * globals_len);
-        for (size_t glob_i = 0; glob_i < globals_len; glob_i += 1) {
-            uint64_t *global = &globals[glob_i];
-            uint32_t content_type = read32_uleb128(mod_ptr, &i);
-            uint32_t mutability = read32_uleb128(mod_ptr, &i);
-            if (mutability != 1) panic("expected mutable global");
-            if (content_type != 0x7f) panic("unexpected content type");
-            uint8_t opcode = mod_ptr[i];
-            i += 1;
-            if (opcode != WasmOp_i32_const) panic("expected i32_const op");
-            uint32_t init = read32_ileb128(mod_ptr, &i);
-            *global = (uint32_t)init;
-        }
-    }
-
-    // Allocate and initialize memory.
-    uint32_t memory_len;
-    {
-        i = section_starts[Section_memory];
-        uint32_t memories_len = read32_uleb128(mod_ptr, &i);
-        if (memories_len != 1) panic("unexpected memory count");
-        uint32_t flags = read32_uleb128(mod_ptr, &i);
-        (void)flags;
-        memory_len = read32_uleb128(mod_ptr, &i) * wasm_page_size;
-
-        i = section_starts[Section_data];
-        uint32_t datas_count = read32_uleb128(mod_ptr, &i);
-        for (; datas_count > 0; datas_count -= 1) {
-            uint32_t mode = read32_uleb128(mod_ptr, &i);
-            if (mode != 0) panic("expected mode 0");
-            enum WasmOp opcode = mod_ptr[i];
-            i += 1;
-            if (opcode != WasmOp_i32_const) panic("expected opcode i32_const");
-            uint32_t offset = read32_uleb128(mod_ptr, &i);
-            enum WasmOp end = mod_ptr[i];
-            if (end != WasmOp_end) panic("expected end opcode");
-            i += 1;
-            uint32_t bytes_len = read32_uleb128(mod_ptr, &i);
-            memcpy(memory + offset, mod_ptr + i, bytes_len);
-            i += bytes_len;
-        }
-    }
-
-    uint32_t *table = NULL;
-    {
-        i = section_starts[Section_table];
-        uint32_t table_count = read32_uleb128(mod_ptr, &i);
-        if (table_count > 1) {
-            panic("expected only one table section");
-        } else if (table_count == 1) {
-            uint32_t element_type = read32_uleb128(mod_ptr, &i);
-            (void)element_type;
-            uint32_t has_max = read32_uleb128(mod_ptr, &i);
-            if (has_max != 1) panic("expected has_max==1");
-            uint32_t initial = read32_uleb128(mod_ptr, &i);
-            (void)initial;
-            uint32_t maximum = read32_uleb128(mod_ptr, &i);
-
-            i = section_starts[Section_element];
-            uint32_t element_section_count = read32_uleb128(mod_ptr, &i);
-            if (element_section_count != 1) panic("expected one element section");
-            uint32_t flags = read32_uleb128(mod_ptr, &i);
-            (void)flags;
-            enum WasmOp opcode = mod_ptr[i];
-            i += 1;
-            if (opcode != WasmOp_i32_const) panic("expected op i32_const");
-            uint32_t offset = read32_uleb128(mod_ptr, &i);
-            enum WasmOp end = mod_ptr[i];
-            if (end != WasmOp_end) panic("expected op end");
-            i += 1;
-            uint32_t elem_count = read32_uleb128(mod_ptr, &i);
-
-            table = arena_alloc(sizeof(uint32_t) * maximum);
-            memset(table, 0, sizeof(uint32_t) * maximum);
-
-            for (uint32_t elem_i = 0; elem_i < elem_count; elem_i += 1) {
-                table[elem_i + offset] = read32_uleb128(mod_ptr, &i);
-            }
-        }
-    }
-
-    struct VirtualMachine vm;
-#ifndef NDEBUG
-    memset(&vm, 0xaa, sizeof(struct VirtualMachine)); // to match the zig version
-#endif
-    vm.stack = arena_alloc(sizeof(uint32_t) * 10000000),
-    vm.mod_ptr = mod_ptr;
-    vm.opcodes = arena_alloc(2000000);
-    vm.operands = arena_alloc(sizeof(uint32_t) * 2000000);
-    vm.stack_top = 0;
-    vm.functions = functions;
-    vm.types = types;
-    vm.globals = globals;
-    vm.memory = memory;
-    vm.memory_len = memory_len;
-    vm.imports = imports;
-    vm.imports_len = imports_len;
-    vm.args = new_argv;
-    vm.table = table;
-
-    {
-        uint32_t code_i = section_starts[Section_code];
-        uint32_t codes_len = read32_uleb128(mod_ptr, &code_i);
-        if (codes_len != functions_len) panic("code/function length mismatch");
-        struct ProgramCounter pc;
-        pc.opcode = 0;
-        pc.operand = 0;
-        struct StackInfo stack;
-        for (uint32_t func_i = 0; func_i < functions_len; func_i += 1) {
-            struct Function *func = &functions[func_i];
-            uint32_t size = read32_uleb128(mod_ptr, &code_i);
-            uint32_t code_begin = code_i;
-
-            stack.top_index = 0;
-            stack.top_offset = 0;
-            struct TypeInfo *type_info = &vm.types[func->type_idx];
-            for (uint32_t param_i = 0; param_i < type_info->param_count; param_i += 1)
-                si_push(&stack, bs_isSet(&type_info->param_types, param_i));
-            uint32_t params_size = stack.top_offset;
-
-            for (uint32_t local_sets_count = read32_uleb128(mod_ptr, &code_i);
-                 local_sets_count > 0; local_sets_count -= 1)
-            {
-                uint32_t local_set_count = read32_uleb128(mod_ptr, &code_i);
-                enum StackType local_type;
-                switch (read64_ileb128(mod_ptr, &code_i)) {
-                    case -1: case -3: local_type = ST_32; break;
-                    case -2: case -4: local_type = ST_64; break;
-                    default: panic("unexpected local type");
-                }
-                for (; local_set_count > 0; local_set_count -= 1)
-                    si_push(&stack, local_type);
-            }
-            func->locals_size = stack.top_offset - params_size;
-
-            func->entry_pc = pc;
-            //fprintf(stderr, "decoding func id %u with pc %u:%u\n", func->id, pc.opcode, pc.operand);
-            vm_decodeCode(&vm, type_info, &code_i, &pc, &stack);
-            if (code_i != code_begin + size) panic("bad code size");
-        }
-        //fprintf(stderr, "%u opcodes\n%u operands\n", pc.opcode, pc.operand);
-    }
-
-    vm_call(&vm, &vm.functions[start_fn_idx - imports_len]);
-    vm_run(&vm);
-
-    return 0;
-}
CMakeLists.txt
@@ -179,8 +179,8 @@ set(ZIG_STD_DEST "${ZIG_LIB_DIR}/std")
 set(ZIG_CONFIG_H_OUT "${CMAKE_BINARY_DIR}/config.h")
 set(ZIG_CONFIG_ZIG_OUT "${CMAKE_BINARY_DIR}/config.zig")
 
-set(STAGE1_SOURCES
-    "${CMAKE_SOURCE_DIR}/stage1/zig1.c"
+set(ZIG_WASM2C_SOURCES
+    "${CMAKE_SOURCE_DIR}/stage1/wasm2c.c"
     "${CMAKE_SOURCE_DIR}/stage1/zstd/lib/decompress/huf_decompress.c"
     "${CMAKE_SOURCE_DIR}/stage1/zstd/lib/decompress/zstd_ddict.c"
     "${CMAKE_SOURCE_DIR}/stage1/zstd/lib/decompress/zstd_decompress.c"
@@ -709,30 +709,53 @@ target_link_libraries(zigcpp LINK_PUBLIC
 )
 
 if(MSVC)
-  set(ZIG1_COMPILE_FLAGS "/std:c99")
-  set(ZIG2_COMPILE_FLAGS "/std:c99")
+  set(ZIG_WASM2C_COMPILE_FLAGS "/std:c99 /O2")
+  set(ZIG1_COMPILE_FLAGS "/std:c99 /Os")
+  set(ZIG2_COMPILE_FLAGS "/std:c99 /O0")
   set(ZIG2_LINK_FLAGS "/STACK:16777216")
 else()
+  set(ZIG_WASM2C_COMPILE_FLAGS "-std=c99 -O2")
   set(ZIG1_COMPILE_FLAGS "-std=c99")
   set(ZIG2_COMPILE_FLAGS "-std=c99")
   set(ZIG2_LINK_FLAGS "-Wl,-z,stack-size=0x10000000")
 endif()
 
-add_executable(zig1 ${STAGE1_SOURCES})
-set_target_properties(zig1 PROPERTIES COMPILE_FLAGS ${ZIG1_COMPILE_FLAGS})
+string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}-${CMAKE_HOST_SYSTEM_NAME}" HOST_TARGET_TRIPLE)
+set(ZIG1_WASM_SOURCE "${CMAKE_SOURCE_DIR}/stage1/zig1.wasm.zst")
+set(ZIG1_C_SOURCE "${CMAKE_BINARY_DIR}/zig1.c")
+set(ZIG2_C_SOURCE "${CMAKE_BINARY_DIR}/zig2.c")
+set(ZIG_COMPILER_RT_C_SOURCE "${CMAKE_BINARY_DIR}/compiler_rt.c")
+
+add_executable(zig-wasm2c ${ZIG_WASM2C_SOURCES})
+set_target_properties(zig-wasm2c PROPERTIES COMPILE_FLAGS ${ZIG_WASM2C_COMPILE_FLAGS})
+target_include_directories(zig-wasm2c PUBLIC "${CMAKE_SOURCE_DIR}/stage1/zstd/lib")
+target_compile_definitions(zig-wasm2c PRIVATE ZSTD_DISABLE_ASM)
+
+add_custom_command(
+  OUTPUT "${ZIG1_C_SOURCE}"
+  COMMAND zig-wasm2c "${ZIG1_WASM_SOURCE}" "${ZIG1_C_SOURCE}"
+  DEPENDS zig-wasm2c "${ZIG1_WASM_SOURCE}"
+  COMMENT STATUS "Converting ${ZIG1_WASM_SOURCE} to ${ZIG1_C_SOURCE}"
+  WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+)
+
+add_executable(zig1 ${ZIG1_C_SOURCE} "${CMAKE_SOURCE_DIR}/stage1/wasi.c")
+set_target_properties(zig1 PROPERTIES
+  COMPILE_FLAGS ${ZIG1_COMPILE_FLAGS}
+  LINK_FLAGS ${ZIG2_LINK_FLAGS})
 target_link_libraries(zig1 LINK_PUBLIC m)
 target_include_directories(zig1 PUBLIC "${CMAKE_SOURCE_DIR}/stage1/zstd/lib")
 target_compile_definitions(zig1 PRIVATE ZSTD_DISABLE_ASM)
 
-set(ZIG2_C_SOURCE "${CMAKE_BINARY_DIR}/zig2.c")
 set(ZIG1_WASM_ZST_SOURCE "${CMAKE_SOURCE_DIR}/stage1/zig1.wasm.zst")
 set(BUILD_ZIG2_ARGS
   "${CMAKE_SOURCE_DIR}/lib"
-  "${CMAKE_BINARY_DIR}"
-  zig2
-  "${ZIG1_WASM_ZST_SOURCE}"
   build-exe src/main.zig -ofmt=c -lc
   -OReleaseSmall
+  --name zig2 -femit-bin="${ZIG2_C_SOURCE}"
+  --pkg-begin build_options "${CMAKE_BINARY_DIR}/config.zig" --pkg-end
+  -target "${HOST_TARGET_TRIPLE}"
+  --color on
 )
  
 add_custom_command(
@@ -743,14 +766,14 @@ add_custom_command(
   WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
 )
 
-set(ZIG_COMPILER_RT_C_SOURCE "${CMAKE_BINARY_DIR}/compiler_rt.c")
 set(BUILD_COMPILER_RT_ARGS
   "${CMAKE_SOURCE_DIR}/lib"
-  "${CMAKE_BINARY_DIR}"
-  compiler_rt
-  "${CMAKE_SOURCE_DIR}/stage1/zig1.wasm.zst"
   build-obj lib/compiler_rt.zig -ofmt=c
   -OReleaseSmall
+  --name compiler_rt -femit-bin="${ZIG_COMPILER_RT_C_SOURCE}"
+  --pkg-begin build_options "${CMAKE_BINARY_DIR}/config.zig" --pkg-end
+  -target "${HOST_TARGET_TRIPLE}"
+  --color on
 )
  
 add_custom_command(