#define EXTRA_BRACES 0

#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");
        }
    }
}

static const uint32_t big_endian = 0xff000000;

int main(int argc, char **argv) {
    if (argc != 3 && argc != 4) {
        fprintf(stderr, "usage: %s <in.wasm.zst> <out.c> [endian]\n", argv[0]);
        return 1;
    }

    bool is_big_endian;

    if (argc >= 4) {
        if (!strcmp(argv[3], "big")) {
            is_big_endian = true;
        } else if (!strcmp(argv[3], "little")) {
            is_big_endian = false;
        } else {
            fprintf(stderr, "endianness must be 'big' or 'little'\n");
            return 1;
        }
    } else {
        is_big_endian = *(uint8_t *)&big_endian; // Infer from host endianness.
    }

    const char *mod = "wasm";

    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], "wb");
    if (out == NULL) panic("unable to open output file");
    fputs("#include <float.h>\n"
          "#include <math.h>\n"
          "#include <stdint.h>\n"
          "#include <stdlib.h>\n"
          "#include <string.h>\n"
          "\n"
          "static uint16_t i16_byteswap(uint16_t src) {\n"
          "    return (uint16_t)(uint8_t)(src >> 0) << 8 |\n"
          "           (uint16_t)(uint8_t)(src >> 8) << 0;\n"
          "}\n"
          "static uint32_t i32_byteswap(uint32_t src) {\n"
          "    return (uint32_t)i16_byteswap(src >>  0) << 16 |\n"
          "           (uint32_t)i16_byteswap(src >> 16) <<  0;\n"
          "}\n"
          "static uint64_t i64_byteswap(uint64_t src) {\n"
          "    return (uint64_t)i32_byteswap(src >>  0) << 32 |\n"
          "           (uint64_t)i32_byteswap(src >> 32) <<  0;\n"
          "}\n"
          "\n", out);
    fputs("uint16_t load16_align0(const uint8_t *ptr) {\n"
          "    uint16_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i16_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint16_t load16_align1(const uint16_t *ptr) {\n"
          "    uint16_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i16_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint32_t load32_align0(const uint8_t *ptr) {\n"
          "    uint32_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i32_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint32_t load32_align1(const uint16_t *ptr) {\n"
          "    uint32_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i32_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint32_t load32_align2(const uint32_t *ptr) {\n"
          "    uint32_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i32_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint64_t load64_align0(const uint8_t *ptr) {\n"
          "    uint64_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint64_t load64_align1(const uint16_t *ptr) {\n"
          "    uint64_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint64_t load64_align2(const uint32_t *ptr) {\n"
          "    uint64_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "uint64_t load64_align3(const uint64_t *ptr) {\n"
          "    uint64_t val;\n"
          "    memcpy(&val, ptr, sizeof(val));\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    return val;\n"
          "}\n"
          "\n"
          "static uint32_t i32_popcnt(uint32_t lhs) {\n"
          "    lhs = lhs - ((lhs >> 1) & UINT32_C(0x55555555));\n"
          "    lhs = (lhs & UINT32_C(0x33333333)) + ((lhs >> 2) & UINT32_C(0x33333333));\n"
          "    lhs = (lhs + (lhs >> 4)) & UINT32_C(0x0F0F0F0F);\n"
          "    return (lhs * UINT32_C(0x01010101)) >> 24;\n"
          "}\n"
          "static uint32_t i32_ctz(uint32_t lhs) {\n"
          "    return i32_popcnt(~lhs & (lhs - 1));\n"
          "}\n"
          "static uint32_t i32_clz(uint32_t lhs) {\n"
          "    lhs = i32_byteswap(lhs);\n"
          "    lhs = (lhs & UINT32_C(0x0F0F0F0F)) << 4 | (lhs & UINT32_C(0xF0F0F0F0)) >> 4;\n"
          "    lhs = (lhs & UINT32_C(0x33333333)) << 2 | (lhs & UINT32_C(0xCCCCCCCC)) >> 2;\n"
          "    lhs = (lhs & UINT32_C(0x55555555)) << 1 | (lhs & UINT32_C(0xAAAAAAAA)) >> 1;\n"
          "    return i32_ctz(lhs);\n"
          "}\n"
          "static uint64_t i64_popcnt(uint64_t lhs) {\n"
          "    lhs = lhs - ((lhs >> 1) & UINT64_C(0x5555555555555555));\n"
          "    lhs = (lhs & UINT64_C(0x3333333333333333)) + ((lhs >> 2) & UINT64_C(0x3333333333333333));\n"
          "    lhs = (lhs + (lhs >> 4)) & UINT64_C(0x0F0F0F0F0F0F0F0F);\n"
          "    return (lhs * UINT64_C(0x0101010101010101)) >> 56;\n"
          "}\n"
          "static uint64_t i64_ctz(uint64_t lhs) {\n"
          "    return i64_popcnt(~lhs & (lhs - 1));\n"
          "}\n"
          "static uint64_t i64_clz(uint64_t lhs) {\n"
          "    lhs = i64_byteswap(lhs);\n"
          "    lhs = (lhs & UINT64_C(0x0F0F0F0F0F0F0F0F)) << 4 | (lhs & UINT32_C(0xF0F0F0F0F0F0F0F0)) >> 4;\n"
          "    lhs = (lhs & UINT64_C(0x3333333333333333)) << 2 | (lhs & UINT32_C(0xCCCCCCCCCCCCCCCC)) >> 2;\n"
          "    lhs = (lhs & UINT64_C(0x5555555555555555)) << 1 | (lhs & UINT32_C(0xAAAAAAAAAAAAAAAA)) >> 1;\n"
          "    return i64_ctz(lhs);\n"
          "}\n"
          "\n"
          "void store16_align0(uint8_t *ptr, uint16_t val) {\n", out);
    if (is_big_endian) fputs("    val = i16_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store16_align1(uint16_t *ptr, uint16_t val) {\n", out);
    if (is_big_endian) fputs("    val = i16_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store32_align0(uint8_t *ptr, uint32_t val) {\n", out);
    if (is_big_endian) fputs("    val = i32_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store32_align1(uint16_t *ptr, uint32_t val) {\n", out);
    if (is_big_endian) fputs("    val = i32_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store32_align2(uint32_t *ptr, uint32_t val) {\n", out);
    if (is_big_endian) fputs("    val = i32_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store64_align0(uint8_t *ptr, uint64_t val) {\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store64_align1(uint16_t *ptr, uint64_t val) {\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store64_align2(uint32_t *ptr, uint64_t val) {\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\n"
          "void store64_align3(uint64_t *ptr, uint64_t val) {\n", out);
    if (is_big_endian) fputs("    val = i64_byteswap(val);", out);
    fputs("    memcpy(ptr, &val, sizeof(val));\n"
          "}\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 i32_trunc_sat_f32(const float src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return (uint32_t)(signbit(src) == 0 ? INT32_MAX : INT32_MIN);\n"
          "    return (uint32_t)(int32_t)src;\n"
          "}\n"
          "static uint32_t u32_trunc_sat_f32(const float src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return signbit(src) == 0 ? UINT32_MAX : 0;\n"
          "    return (uint32_t)src;\n"
          "}\n"
          "static uint32_t i32_trunc_sat_f64(const double src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return (uint32_t)(signbit(src) == 0 ? INT32_MAX : INT32_MIN);\n"
          "    return (uint32_t)(int32_t)src;\n"
          "}\n"
          "static uint32_t u32_trunc_sat_f64(const double src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return signbit(src) == 0 ? UINT32_MAX : 0;\n"
          "    return (uint32_t)src;\n"
          "}\n"
          "static uint64_t i64_trunc_sat_f32(const float src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return (uint64_t)(signbit(src) == 0 ? INT64_MAX : INT64_MIN);\n"
          "    return (uint64_t)(int64_t)src;\n"
          "}\n"
          "static uint64_t u64_trunc_sat_f32(const float src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return signbit(src) == 0 ? UINT64_MAX : 0;\n"
          "    return (uint64_t)src;\n"
          "}\n"
          "static uint64_t i64_trunc_sat_f64(const double src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return (uint64_t)(signbit(src) == 0 ? INT64_MAX : INT64_MIN);\n"
          "    return (uint64_t)(int64_t)src;\n"
          "}\n"
          "static uint64_t u64_trunc_sat_f64(const double src) {\n"
          "    if (isnan(src)) return 0;\n"
          "    if (isinf(src)) return signbit(src) == 0 ? UINT64_MAX : 0;\n"
          "    return (uint64_t)src;\n"
          "}\n"
          "\n"
          "static uint32_t memory_grow(uint8_t **m, uint32_t *p, uint32_t *c, uint32_t n) {\n"
          "    uint32_t r = *p;\n"
          "    uint32_t new_p = r + n;\n"
          "    if (new_p > UINT32_C(0xFFFF)) return UINT32_C(0xFFFFFFFF);\n"
          "    uint8_t *new_m = *m;\n"
          "    uint32_t new_c = *c;\n"
          "    if (new_c < new_p) {\n"
          "        do new_c += new_c / 2 + 8; while (new_c < new_p);\n"
          "        if (new_c > UINT32_C(0xFFFF)) new_c = UINT32_C(0xFFFF);\n"
          "        new_m = realloc(new_m, new_c << 16);\n"
          "        if (new_m == NULL) return UINT32_C(0xFFFFFFFF);\n"
          "        *m = new_m;\n"
          "        *c = new_c;\n"
          "    }\n"
          "    *p = new_p;\n"
          "    memset(&new_m[r << 16], 0, n << 16);\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"
                    "static uint32_t c%" PRIu32 ";\n", i, 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 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);

            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_reuseReset(&fg);
                                    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);

                            if (opcode == WasmOpcode_br_if) {
                                FuncGen_indent(&fg, out);
                                fprintf(out, "if (l%" PRIu32 ") {\n", FuncGen_stackPop(&fg));
                            } else if (EXTRA_BRACES) {
                                FuncGen_indent(&fg, out);
                                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);
                            if (EXTRA_BRACES || opcode == WasmOpcode_br_if) {
                                FuncGen_indent(&fg, out);
                                fputs("}\n", out);
                            }
                            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_stackAt(&fg, 0);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%"PRIu32" = l%" PRIu32 " ? l%" PRIu32 " : l%" PRIu32 ";\n",
                                    lhs, 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);
                            fprintf(out, "load32_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%" PRIu32
                                    "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "load64_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%" PRIu32
                                    "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "f32_reinterpret_i32(load32_align%" PRIu32 "((const uint%" PRIu32
                                    "_t *)&m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]));\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "f64_reinterpret_i64(load64_align%" PRIu32 "((const uint%" PRIu32
                                    "_t *)&m%" PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]));\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "(int16_t)load16_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "load16_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "(int16_t)load16_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "load16_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "(int32_t)load32_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            fprintf(out, "load32_align%" PRIu32 "((const uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")]);\n",
                                    align, 8 << align, 0, base, offset);
                        }
                        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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store32_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], l%" PRIu32 ");\n",
                                    align, 8 << align, 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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store64_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], l%" PRIu32 ");\n",
                                    align, 8 << align, 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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store32_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], "
                                    "i32_reinterpret_f32(l%" PRIu32 "));\n",
                                    align, 8 << align, 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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store64_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], "
                                    "i64_reinterpret_f64(l%" PRIu32 "));\n",
                                    align, 8 << align, 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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store16_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], "
                                    "(uint16_t)l%" PRIu32 ");\n",
                                    align, 8 << align, 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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store16_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], "
                                    "(uint16_t)l%" PRIu32 ");\n",
                                    align, 8 << align, 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);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "store32_align%" PRIu32 "((uint%" PRIu32 "_t *)&m%"
                                    PRIu32 "[l%" PRIu32 " + UINT32_C(%" PRIu32 ")], "
                                    "(uint32_t)l%" PRIu32 ");\n",
                                    align, 8 << align, 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 ", &c%" PRIu32
                                    ", l%" PRIu32 ");\n", mem_idx, 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_stackAt(&fg, 0);
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = !l%" PRIu32 ";\n", lhs, lhs);
                        }
                        break;
                    case WasmOpcode_i32_eq:
                    case WasmOpcode_i32_ne:
                    case WasmOpcode_i32_lt_u:
                    case WasmOpcode_i32_gt_u:
                    case WasmOpcode_i32_le_u:
                    case WasmOpcode_i32_ge_u:
                        // i32 unsigned comparisons
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            const char *operator;
                            switch (opcode) {
                                case WasmOpcode_i32_eq:   operator = "=="; break;
                                case WasmOpcode_i32_ne:   operator = "!="; break;
                                case WasmOpcode_i32_lt_u: operator = "<";  break;
                                case WasmOpcode_i32_gt_u: operator = ">";  break;
                                case WasmOpcode_i32_le_u: operator = "<="; break;
                                case WasmOpcode_i32_ge_u: operator = ">="; break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = l%" PRIu32 " %s l%" PRIu32 ";\n",
                                    lhs, lhs, operator, rhs);
                        }
                        break;
                    case WasmOpcode_i32_lt_s:
                    case WasmOpcode_i32_gt_s:
                    case WasmOpcode_i32_le_s:
                    case WasmOpcode_i32_ge_s:
                        // i32 signed comparisons
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            const char *operator;
                            switch (opcode) {
                                case WasmOpcode_i32_lt_s: operator = "<";  break;
                                case WasmOpcode_i32_gt_s: operator = ">";  break;
                                case WasmOpcode_i32_le_s: operator = "<="; break;
                                case WasmOpcode_i32_ge_s: operator = ">="; break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = (int32_t)l%" PRIu32 " %s (int32_t)l%" PRIu32
                                    ";\n", lhs, lhs, operator, 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:
                    case WasmOpcode_i64_ne:
                    case WasmOpcode_i64_lt_u:
                    case WasmOpcode_i64_gt_u:
                    case WasmOpcode_i64_le_u:
                    case WasmOpcode_i64_ge_u:
                    case WasmOpcode_f32_eq:
                    case WasmOpcode_f32_ne:
                    case WasmOpcode_f32_lt:
                    case WasmOpcode_f32_gt:
                    case WasmOpcode_f32_le:
                    case WasmOpcode_f32_ge:
                    case WasmOpcode_f64_eq:
                    case WasmOpcode_f64_ne:
                    case WasmOpcode_f64_lt:
                    case WasmOpcode_f64_gt:
                    case WasmOpcode_f64_le:
                    case WasmOpcode_f64_ge:
                        // non-i32 unsigned comparisons
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackPop(&fg);
                            const char *operator;
                            switch (opcode) {
                                case WasmOpcode_i64_eq:
                                case WasmOpcode_f32_eq:
                                case WasmOpcode_f64_eq:
                                    operator = "==";
                                    break;
                                case WasmOpcode_i64_ne:
                                case WasmOpcode_f32_ne:
                                case WasmOpcode_f64_ne:
                                    operator = "!=";
                                    break;
                                case WasmOpcode_i64_lt_u:
                                case WasmOpcode_f32_lt:
                                case WasmOpcode_f64_lt:
                                    operator = "<";
                                    break;
                                case WasmOpcode_i64_gt_u:
                                case WasmOpcode_f32_gt:
                                case WasmOpcode_f64_gt:
                                    operator = ">";
                                    break;
                                case WasmOpcode_i64_le_u:
                                case WasmOpcode_f32_le:
                                case WasmOpcode_f64_le:
                                    operator = "<=";
                                    break;
                                case WasmOpcode_i64_ge_u:
                                case WasmOpcode_f32_ge:
                                case WasmOpcode_f64_ge:
                                    operator = ">=";
                                    break;
                                default: panic("unreachable");
                            }
                            FuncGen_stackPush(&fg, out, WasmValType_i32);
                            fprintf(out, "l%" PRIu32 " = l%" PRIu32 " %s l%" PRIu32 ";\n",
                                    lhs, lhs, operator, rhs);
                        }
                        break;
                    case WasmOpcode_i64_lt_s:
                    case WasmOpcode_i64_gt_s:
                    case WasmOpcode_i64_le_s:
                    case WasmOpcode_i64_ge_s:
                        // i64 signed comparisons
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackPop(&fg);
                            const char *operator;
                            switch (opcode) {
                                case WasmOpcode_i64_lt_s: operator = "<";  break;
                                case WasmOpcode_i64_gt_s: operator = ">";  break;
                                case WasmOpcode_i64_le_s: operator = "<="; break;
                                case WasmOpcode_i64_ge_s: operator = ">="; break;
                                default: panic("unreachable");
                            }
                            FuncGen_stackPush(&fg, out, WasmValType_i32);
                            fprintf(out, "l%" PRIu32 " = (int64_t)l%" PRIu32 " %s (int64_t)l%" PRIu32
                                    ";\n", lhs, lhs, operator, rhs);
                        }
                        break;

                    case WasmOpcode_i32_clz:
                    case WasmOpcode_i32_ctz:
                    case WasmOpcode_i32_popcnt:
                    case WasmOpcode_i64_clz:
                    case WasmOpcode_i64_ctz:
                    case WasmOpcode_i64_popcnt:
                    case WasmOpcode_f32_abs:
                    case WasmOpcode_f32_neg:
                    case WasmOpcode_f32_ceil:
                    case WasmOpcode_f32_floor:
                    case WasmOpcode_f32_trunc:
                    case WasmOpcode_f32_nearest:
                    case WasmOpcode_f32_sqrt:
                    case WasmOpcode_f64_abs:
                    case WasmOpcode_f64_neg:
                    case WasmOpcode_f64_ceil:
                    case WasmOpcode_f64_floor:
                    case WasmOpcode_f64_trunc:
                    case WasmOpcode_f64_nearest:
                    case WasmOpcode_f64_sqrt:
                        // unary functions
                        if (unreachable_depth == 0) {
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            const char *function;
                            switch (opcode) {
                                case WasmOpcode_i32_clz:     function = "i32_clz";    break;
                                case WasmOpcode_i32_ctz:     function = "i32_ctz";    break;
                                case WasmOpcode_i32_popcnt:  function = "i32_popcnt"; break;
                                case WasmOpcode_i64_clz:     function = "i64_clz";    break;
                                case WasmOpcode_i64_ctz:     function = "i64_ctz";    break;
                                case WasmOpcode_i64_popcnt:  function = "i64_popcnt"; break;
                                case WasmOpcode_f32_abs:     function = "fabsf";      break;
                                case WasmOpcode_f32_neg:
                                case WasmOpcode_f64_neg:     function = "-";          break;
                                case WasmOpcode_f32_ceil:    function = "ceilf";      break;
                                case WasmOpcode_f32_floor:   function = "floorf";     break;
                                case WasmOpcode_f32_trunc:   function = "truncf";     break;
                                case WasmOpcode_f32_nearest: function = "roundf";     break;
                                case WasmOpcode_f32_sqrt:    function = "sqrtf";      break;
                                case WasmOpcode_f64_abs:     function = "fabs";       break;
                                case WasmOpcode_f64_ceil:    function = "ceil";       break;
                                case WasmOpcode_f64_floor:   function = "floor";      break;
                                case WasmOpcode_f64_trunc:   function = "trunc";      break;
                                case WasmOpcode_f64_nearest: function = "round";      break;
                                case WasmOpcode_f64_sqrt:    function = "sqrt";       break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = %s(l%" PRIu32 ");\n", lhs, function, lhs);
                        }
                        break;
                    case WasmOpcode_i32_add:
                    case WasmOpcode_i32_sub:
                    case WasmOpcode_i32_mul:
                    case WasmOpcode_i32_div_u:
                    case WasmOpcode_i32_rem_u:
                    case WasmOpcode_i32_and:
                    case WasmOpcode_i32_or:
                    case WasmOpcode_i32_xor:
                    case WasmOpcode_i64_add:
                    case WasmOpcode_i64_sub:
                    case WasmOpcode_i64_mul:
                    case WasmOpcode_i64_div_u:
                    case WasmOpcode_i64_rem_u:
                    case WasmOpcode_i64_and:
                    case WasmOpcode_i64_or:
                    case WasmOpcode_i64_xor:
                    case WasmOpcode_f32_add:
                    case WasmOpcode_f32_sub:
                    case WasmOpcode_f32_mul:
                    case WasmOpcode_f32_div:
                    case WasmOpcode_f64_add:
                    case WasmOpcode_f64_sub:
                    case WasmOpcode_f64_mul:
                    case WasmOpcode_f64_div:
                        // unsigned binary operators
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            char operator;
                            switch (opcode) {
                                case WasmOpcode_i32_add:
                                case WasmOpcode_i64_add:
                                case WasmOpcode_f32_add:
                                case WasmOpcode_f64_add:
                                    operator = '+';
                                    break;
                                case WasmOpcode_i32_sub:
                                case WasmOpcode_i64_sub:
                                case WasmOpcode_f32_sub:
                                case WasmOpcode_f64_sub:
                                    operator = '-';
                                    break;
                                case WasmOpcode_i32_mul:
                                case WasmOpcode_i64_mul:
                                case WasmOpcode_f32_mul:
                                case WasmOpcode_f64_mul:
                                    operator = '*';
                                    break;
                                case WasmOpcode_i32_div_u:
                                case WasmOpcode_i64_div_u:
                                case WasmOpcode_f32_div:
                                case WasmOpcode_f64_div:
                                    operator = '/';
                                    break;
                                case WasmOpcode_i32_rem_u:
                                case WasmOpcode_i64_rem_u:
                                    operator = '%';
                                    break;
                                case WasmOpcode_i32_and:
                                case WasmOpcode_i64_and:
                                    operator = '&';
                                    break;
                                case WasmOpcode_i32_or:
                                case WasmOpcode_i64_or:
                                    operator = '|';
                                    break;
                                case WasmOpcode_i32_xor:
                                case WasmOpcode_i64_xor:
                                    operator = '^';
                                    break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " %c= l%" PRIu32 ";\n", lhs, operator, rhs);
                        }
                        break;
                    case WasmOpcode_i32_div_s:
                    case WasmOpcode_i32_rem_s:
                    case WasmOpcode_i64_div_s:
                    case WasmOpcode_i64_rem_s:
                        // signed binary operators
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            char operator;
                            unsigned width;
                            switch (opcode) {
                                case WasmOpcode_i32_div_s:
                                case WasmOpcode_i64_div_s:
                                    operator = '/';
                                    break;
                                case WasmOpcode_i32_rem_s:
                                case WasmOpcode_i64_rem_s:
                                    operator = '%';
                                    break;
                                default: panic("unreachable");
                            }
                            switch (opcode) {
                                case WasmOpcode_i32_div_s:
                                case WasmOpcode_i32_rem_s:
                                    width = 32;
                                    break;
                                case WasmOpcode_i64_div_s:
                                case WasmOpcode_i64_rem_s:
                                    width = 64;
                                    break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = (uint%u_t)((int%u_t)l%" PRIu32 " %c "
                                    "(int%u_t)l%" PRIu32 ");\n",
                                    lhs, width, width, lhs, operator, width, rhs);
                        }
                        break;
                    case WasmOpcode_i32_shl:
                    case WasmOpcode_i32_shr_u:
                    case WasmOpcode_i64_shl:
                    case WasmOpcode_i64_shr_u:
                        // unsigned shift operators
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            char operator;
                            unsigned width;
                            switch (opcode) {
                                case WasmOpcode_i32_shl:
                                case WasmOpcode_i64_shl:
                                    operator = '<';
                                    break;
                                case WasmOpcode_i32_shr_u:
                                case WasmOpcode_i64_shr_u:
                                    operator = '>';
                                    break;
                                default: panic("unreachable");
                            }
                            switch (opcode) {
                                case WasmOpcode_i32_shl:
                                case WasmOpcode_i32_shr_u:
                                    width = 32;
                                    break;
                                case WasmOpcode_i64_shl:
                                case WasmOpcode_i64_shr_u:
                                    width = 64;
                                    break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " %c%c= l%" PRIu32 " & 0x%X;\n",
                                    lhs, operator, operator, rhs, width - 1);
                        }
                        break;
                    case WasmOpcode_i32_shr_s:
                    case WasmOpcode_i64_shr_s:
                        // signed shift operators
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            char operator;
                            unsigned width;
                            switch (opcode) {
                                case WasmOpcode_i32_shr_s:
                                case WasmOpcode_i64_shr_s:
                                    operator = '>';
                                    break;
                                default: panic("unreachable");
                            }
                            switch (opcode) {
                                case WasmOpcode_i32_shr_s:
                                    width = 32;
                                    break;
                                case WasmOpcode_i64_shr_s:
                                    width = 64;
                                    break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = (uint%u_t)((int%u_t)l%" PRIu32 " %c%c "
                                    "(l%" PRIu32 " & 0x%X));\n",
                                    lhs, width, width, lhs, operator, operator, rhs, width - 1);
                        }
                        break;
                    case WasmOpcode_i32_rotl:
                    case WasmOpcode_i32_rotr:
                    case WasmOpcode_i64_rotl:
                    case WasmOpcode_i64_rotr:
                        // rotate operators
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            char forward_operator;
                            char reverse_operator;
                            unsigned width;
                            switch (opcode) {
                                case WasmOpcode_i32_rotl:
                                case WasmOpcode_i64_rotl:
                                    forward_operator = '<';
                                    reverse_operator = '>';
                                    break;
                                case WasmOpcode_i32_rotr:
                                case WasmOpcode_i64_rotr:
                                    forward_operator = '>';
                                    reverse_operator = '<';
                                    break;
                                default: panic("unreachable");
                            }
                            switch (opcode) {
                                case WasmOpcode_i32_rotl:
                                case WasmOpcode_i32_rotr:
                                    width = 32;
                                    break;
                                case WasmOpcode_i64_rotl:
                                case WasmOpcode_i64_rotr:
                                    width = 64;
                                    break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32" = l%" PRIu32 " %c%c (l%" PRIu32 " & 0x%X) | "
                                    "l%" PRIu32 " %c%c (-l%" PRIu32" & 0x%X);\n", lhs,
                                    lhs, forward_operator, forward_operator, rhs, width - 1,
                                    lhs, reverse_operator, reverse_operator, rhs, width - 1);
                        }
                        break;
                    case WasmOpcode_f32_min:
                    case WasmOpcode_f32_max:
                    case WasmOpcode_f32_copysign:
                    case WasmOpcode_f64_min:
                    case WasmOpcode_f64_max:
                    case WasmOpcode_f64_copysign:
                        // binary functions
                        if (unreachable_depth == 0) {
                            uint32_t rhs = FuncGen_stackPop(&fg);
                            uint32_t lhs = FuncGen_stackAt(&fg, 0);
                            const char *function;
                            switch (opcode) {
                                case WasmOpcode_f32_min:      function = "fminf";     break;
                                case WasmOpcode_f32_max:      function = "fmaxf";     break;
                                case WasmOpcode_f32_copysign: function = "copysignf"; break;
                                case WasmOpcode_f64_min:      function = "fmin";      break;
                                case WasmOpcode_f64_max:      function = "fmax";      break;
                                case WasmOpcode_f64_copysign: function = "copysign";  break;
                                default: panic("unreachable");
                            }
                            FuncGen_indent(&fg, out);
                            fprintf(out, "l%" PRIu32 " = %s(l%" PRIu32 ", l%" PRIu32 ");\n",
                                    lhs, function, 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:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "i32_trunc_sat_f32(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i32_trunc_sat_f32_u:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "u32_trunc_sat_f32(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i32_trunc_sat_f64_s:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "i32_trunc_sat_f64(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i32_trunc_sat_f64_u:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "u32_trunc_sat_f64(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i64_trunc_sat_f32_s:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "i64_trunc_sat_f32(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i64_trunc_sat_f32_u:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "u64_trunc_sat_f32(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i64_trunc_sat_f64_s:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "i64_trunc_sat_f64(l%" PRIu32 ");\n", lhs);
                                }
                                break;
                            case WasmPrefixedOpcode_i64_trunc_sat_f64_u:
                                if (unreachable_depth == 0) {
                                    uint32_t lhs = FuncGen_stackPop(&fg);
                                    FuncGen_stackPush(&fg, out, WasmValType_i32);
                                    fprintf(out, "u64_trunc_sat_f64(l%" PRIu32 ");\n", lhs);
                                }
                                break;

                            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, "memmove(&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"
                    "    c%" PRIu32 " = p%" PRIu32 ";\n"
                    "    m%" PRIu32 " = calloc(c%" PRIu32 ", UINT32_C(1) << 16);\n",
                    i, mems[i].limits.min, i, i, 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);
}
