/* * nwc, a NanoWasm compiler * Copyright (C) 2025 Xavier Del Campo Romero * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "instr.h" #include "types.h" #include #include #include #include #include #include #include static int imm_none(FILE *const f, varuint32 *v) { return 0; } static int instr_sig(FILE *const f, varuint32 *v) { return read_varint7(f, &(varint7){0}); } static int imm_readvaruint1(FILE *const f, varuint32 *v) { return read_varuint1(f, &(varuint1){0}); } static int imm_varuint32(FILE *const f, varuint32 *v) { return read_varuint32(f, v); } static int imm_varuint64(FILE *const f, varuint32 *v) { return read_varuint64(f, &(varuint64){0}); } static int imm_varint32(FILE *const f, varuint32 *v) { return read_varint32(f, &(varint32){0}); } static int imm_varint64(FILE *const f, varuint32 *v) { return read_varint64(f, &(varint64){0}); } static int imm_memory(FILE *const f, varuint32 *v) { return read_varuint32(f, &(varuint32){0}) || read_varuint32(f, &(varuint32){0}); } static int imm_uint32(FILE *const f, varuint32 *d) { uint32_t v; return !fread(&v, sizeof v, 1, f); } static int imm_uint64(FILE *const f, varuint32 *d) { uint64_t v; return !fread(&v, sizeof v, 1, f); } static int imm_table(FILE *const f, varuint32 *v) { varuint32 table_count; if (read_varuint32(f, &table_count)) { fprintf(stderr, "%s: read_varuint32 failed\n", __func__); return -1; } for (varuint32 i = 0; i < table_count; i++) if (read_varuint32(f, &(varuint32){0})) { fprintf(stderr, "%s: read_varuint32 %lu failed\n", __func__, (unsigned long)i); return -1; } if (read_varuint32(f, &(varuint32){0})) { fprintf(stderr, "%s: read_varuint32 failed\n", __func__); return -1; } return 0; } static int imm_call_indirect(FILE *const f, varuint32 *v) { varuint32 reserved; if (read_varuint32(f, &(varuint32){0})) { fprintf(stderr, "%s: read_varuint32 failed\n", __func__); return -1; } else if (read_varuint32(f, &reserved)) { fprintf(stderr, "%s: read_varuint1 failed\n", __func__); return -1; } else if (reserved) { fprintf(stderr, "%s: expected zero for reserved field\n", __func__); return -1; } return 0; } static int imm_extra(FILE *const f, varuint32 *v) { uint8_t b; return !fread(&b, sizeof b, 1, f); } #define OPS \ X(OP_UNREACHABLE, imm_none) \ X(OP_NOP, imm_none) \ X(OP_BLOCK, instr_sig) \ X(OP_LOOP, instr_sig) \ X(OP_IF, instr_sig) \ X(OP_ELSE, imm_none) \ X(OP_END, imm_none) \ X(OP_BR, imm_varuint32) \ X(OP_BR_IF, imm_varuint32) \ X(OP_BR_TABLE, imm_table) \ X(OP_RETURN, imm_none) \ X(OP_CALL, imm_varuint32) \ X(OP_CALL_INDIRECT, imm_call_indirect) \ X(OP_DROP, imm_none) \ X(OP_SELECT, imm_none) \ X(OP_GET_LOCAL, imm_varuint32) \ X(OP_SET_LOCAL, imm_varuint32) \ X(OP_TEE_LOCAL, imm_varuint32) \ X(OP_GET_GLOBAL, imm_varuint32) \ X(OP_SET_GLOBAL, imm_varuint32) \ X(OP_I32_LOAD, imm_memory) \ X(OP_I64_LOAD, imm_memory) \ X(OP_F32_LOAD, imm_memory) \ X(OP_F64_LOAD, imm_memory) \ X(OP_I32_LOAD8_S, imm_memory) \ X(OP_I32_LOAD8_U, imm_memory) \ X(OP_I32_LOAD16_S, imm_memory) \ X(OP_I32_LOAD16_U, imm_memory) \ X(OP_I64_LOAD8_S, imm_memory) \ X(OP_I64_LOAD8_U, imm_memory) \ X(OP_I64_LOAD16_S, imm_memory) \ X(OP_I64_LOAD16_U, imm_memory) \ X(OP_I64_LOAD32_S, imm_memory) \ X(OP_I64_LOAD32_U, imm_memory) \ X(OP_I32_STORE, imm_memory) \ X(OP_I64_STORE, imm_memory) \ X(OP_F32_STORE, imm_memory) \ X(OP_F64_STORE, imm_memory) \ X(OP_I32_STORE8, imm_memory) \ X(OP_I32_STORE16, imm_memory) \ X(OP_I64_STORE8, imm_memory) \ X(OP_I64_STORE16, imm_memory) \ X(OP_I64_STORE132, imm_memory) \ X(OP_CURRENT_MEMORY, imm_readvaruint1) \ X(OP_GROW_MEMORY, imm_readvaruint1) \ X(OP_I32_CONST, imm_varint32) \ X(OP_I64_CONST, imm_varint64) \ X(OP_F32_CONST, imm_uint32) \ X(OP_F64_CONST, imm_uint64) \ X(OP_I32_EQZ, imm_none) \ X(OP_I32_EQ, imm_none) \ X(OP_I32_NE, imm_none) \ X(OP_I32_LT_S, imm_none) \ X(OP_I32_LT_U, imm_none) \ X(OP_I32_GT_S, imm_none) \ X(OP_I32_GT_U, imm_none) \ X(OP_I32_LE_S, imm_none) \ X(OP_I32_LE_U, imm_none) \ X(OP_I32_GE_S, imm_none) \ X(OP_I32_GE_U, imm_none) \ X(OP_I64_EQZ, imm_none) \ X(OP_I64_EQ, imm_none) \ X(OP_I64_NE, imm_none) \ X(OP_I64_LT_S, imm_none) \ X(OP_I64_LT_U, imm_none) \ X(OP_I64_GT_S, imm_none) \ X(OP_I64_GT_U, imm_none) \ X(OP_I64_LE_S, imm_none) \ X(OP_I64_LE_U, imm_none) \ X(OP_I64_GE_S, imm_none) \ X(OP_I64_GE_U, imm_none) \ X(OP_F32_EQ, imm_none) \ X(OP_F32_NE, imm_none) \ X(OP_F32_LT, imm_none) \ X(OP_F32_GT, imm_none) \ X(OP_F32_LE, imm_none) \ X(OP_F32_GE, imm_none) \ X(OP_F64_EQ, imm_none) \ X(OP_F64_NE, imm_none) \ X(OP_F64_LT, imm_none) \ X(OP_F64_GT, imm_none) \ X(OP_F64_LE, imm_none) \ X(OP_F64_GE, imm_none) \ X(OP_I32_CLZ, imm_none) \ X(OP_I32_CTZ, imm_none) \ X(OP_I32_POPCNT, imm_none) \ X(OP_I32_ADD, imm_none) \ X(OP_I32_SUB, imm_none) \ X(OP_I32_MUL, imm_none) \ X(OP_I32_DIV_S, imm_none) \ X(OP_I32_DIV_U, imm_none) \ X(OP_I32_REM_S, imm_none) \ X(OP_I32_REM_U, imm_none) \ X(OP_I32_AND, imm_none) \ X(OP_I32_OR, imm_none) \ X(OP_I32_XOR, imm_none) \ X(OP_I32_SHL, imm_none) \ X(OP_I32_SHR_S, imm_none) \ X(OP_I32_SHR_U, imm_none) \ X(OP_I32_ROTL, imm_none) \ X(OP_I32_ROTR, imm_none) \ X(OP_I64_CLZ, imm_none) \ X(OP_I64_CTZ, imm_none) \ X(OP_I64_POPCNT, imm_none) \ X(OP_I64_ADD, imm_none) \ X(OP_I64_SUB, imm_none) \ X(OP_I64_MUL, imm_none) \ X(OP_I64_DIV_S, imm_none) \ X(OP_I64_DIV_U, imm_none) \ X(OP_I64_REM_S, imm_none) \ X(OP_I64_REM_U, imm_none) \ X(OP_I64_AND, imm_none) \ X(OP_I64_OR, imm_none) \ X(OP_I64_XOR, imm_none) \ X(OP_I64_SHL, imm_none) \ X(OP_I64_SHR_S, imm_none) \ X(OP_I64_SHR_U, imm_none) \ X(OP_I64_ROTL, imm_none) \ X(OP_I64_ROTR, imm_none) \ X(OP_F32_ABS, imm_none) \ X(OP_F32_NEG, imm_none) \ X(OP_F32_CEIL, imm_none) \ X(OP_F32_FLOOR, imm_none) \ X(OP_F32_TRUNC, imm_none) \ X(OP_F32_NEAREST, imm_none) \ X(OP_F32_SQRT, imm_none) \ X(OP_F32_ADD, imm_none) \ X(OP_F32_SUB, imm_none) \ X(OP_F32_MUL, imm_none) \ X(OP_F32_DIV, imm_none) \ X(OP_F32_MIN, imm_none) \ X(OP_F32_MAX, imm_none) \ X(OP_F32_COPYSIGN, imm_none) \ X(OP_F64_ABS, imm_none) \ X(OP_F64_NEG, imm_none) \ X(OP_F64_CEIL, imm_none) \ X(OP_F64_FLOOR, imm_none) \ X(OP_F64_TRUNC, imm_none) \ X(OP_F64_NEAREST, imm_none) \ X(OP_F64_SQRT, imm_none) \ X(OP_F64_ADD, imm_none) \ X(OP_F64_SUB, imm_none) \ X(OP_F64_MUL, imm_none) \ X(OP_F64_DIV, imm_none) \ X(OP_F64_MIN, imm_none) \ X(OP_F64_MAX, imm_none) \ X(OP_F64_COPYSIGN, imm_none) \ X(OP_I32_WRAP_I64, imm_none) \ X(OP_I32_TRUNC_S_F32, imm_none) \ X(OP_I32_TRUNC_U_F32, imm_none) \ X(OP_I32_TRUNC_S_F64, imm_none) \ X(OP_I32_TRUNC_U_F64, imm_none) \ X(OP_I64_EXTEND_S_I32, imm_none) \ X(OP_I64_EXTEND_U_I32, imm_none) \ X(OP_I64_TRUNC_S_F32, imm_none) \ X(OP_I64_TRUNC_U_F32, imm_none) \ X(OP_I64_TRUNC_S_F64, imm_none) \ X(OP_I64_TRUNC_U_F64, imm_none) \ X(OP_F32_CONVERT_S_I32, imm_none) \ X(OP_F32_CONVERT_U_I32, imm_none) \ X(OP_F32_CONVERT_S_I64, imm_none) \ X(OP_F32_CONVERT_U_I64, imm_none) \ X(OP_F32_DEMOTE_F64, imm_none) \ X(OP_F64_CONVERT_S_I32, imm_none) \ X(OP_F64_CONVERT_U_I32, imm_none) \ X(OP_F64_CONVERT_S_I64, imm_none) \ X(OP_F64_CONVERT_U_I64, imm_none) \ X(OP_F64_PROMOTE_F32, imm_none) \ X(OP_I32_REINTERPRET_F32, imm_none) \ X(OP_I64_REINTERPRET_F64, imm_none) \ X(OP_F32_REINTERPRET_I32, imm_none) \ X(OP_F64_REINTERPRET_I64, imm_none) \ X(OP_EXTRA, imm_extra) static int (*const table[])(FILE *, varuint32 *) = { #define X(x, y) [x] = y, OPS #undef X }; static const char *const op_str[] = { #define X(x, y) [x] = #x, OPS #undef X }; static bool scoped(const uint8_t op) { switch (op) { case OP_BLOCK: case OP_LOOP: case OP_IF: return true; default: break; } return false; } static bool branch(const uint8_t op) { switch (op) { case OP_BR: case OP_BR_IF: return true; default: break; } return false; } struct block { uint8_t op; unsigned level; long pc; struct block *next, *prev; }; struct blocks { struct block *head, *tail; }; static int append_lo(const struct lo *const lo, struct parse *const p) { const size_t n = p->n_lo + 1; struct lo *const o = realloc(p->lo, n * sizeof *p->lo); if (!o) { fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); return -1; } o[p->n_lo++] = *lo; p->lo = o; return 0; } static int add_block(struct blocks *const b, const uint8_t op, const unsigned level, FILE *const f) { const long offset = ftell(f); if (offset < 0) { fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } struct block *const v = malloc(sizeof *v); if (!v) { fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); return -1; } *v = (const struct block) { .op = op, .level = level, .pc = offset, .prev = b->tail }; if (!b->head) b->head = v; else b->tail->next = v; b->tail = v; return 0; } static int add_branch(const uint8_t op, struct lo *const lo, const struct blocks *const b, const varuint32 relative_depth, const unsigned level, FILE *const f) { if (relative_depth >= level) { fprintf(stderr, "%s: invalid relative_depth=%lu, current level=%u\n", __func__, (unsigned long)relative_depth, level); return -1; } const long offset = ftell(f); if (offset < 0) { fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } const struct block *tgt = NULL; for (const struct block *bl = b->tail; bl; bl = bl->prev) if (bl->level == level - relative_depth) { tgt = bl; break; } if (!tgt) { fprintf(stderr, "%s: could not find matching block, pc=%ld\n", __func__, offset); return -1; } const size_t n = lo->n + 1; struct lo_entry *const ve = realloc(lo->entries, n * sizeof *lo->entries); if (!ve) { fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); return -1; } struct lo_entry *const e = &ve[lo->n++]; *e = (const struct lo_entry){.b = tgt, .pc = offset}; if (tgt->op == OP_LOOP) e->dst = tgt->pc; lo->entries = ve; return 0; } static int set_end(struct lo *const lo, const struct blocks *const b, const unsigned level, FILE *const f) { long offset = ftell(f); if (offset < 0) { fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } offset -= sizeof b->head->op; const struct block *tgt = NULL; for (const struct block *bl = b->tail; bl; bl = bl->prev) if (bl->level == level) { tgt = bl; break; } if (!tgt) { fprintf(stderr, "%s: could not find matching block for end, pc=%ld\n", __func__, offset); return -1; } for (size_t i = 0; i < lo->n; i++) { struct lo_entry *const e = &lo->entries[i]; if (e->b == tgt && tgt->op == OP_BLOCK) e->dst = offset; } return 0; } static void free_blocks(struct blocks *const b) { for (struct block *bl = b->head; bl;) { struct block *next = bl->next; free(bl); bl = next; } } int read_instr(FILE *const f, struct parse *const p) { uint8_t op; unsigned level = 0; struct lo lo = {0}; struct blocks b = {0}; for (;;) { const size_t n = fread(&op, sizeof op, 1, f); varuint32 relative_depth; if (!n) { fprintf(stderr, "%s: fread(3) failed, ferror(3)=%d, feof(3)=%d\n", __func__, ferror(f), feof(f)); goto failure; } else if (op >= sizeof table / sizeof *table || !table[op]) { fprintf(stderr, "%s: invalid or unsupported opcode %#" PRIx8 "\n", __func__, op); goto failure; } else if (table[op](f, &relative_depth)) { fprintf(stderr, "%s: failed to read opcode %#" PRIx8 "\n", __func__, op); goto failure; } else if (scoped(op) && add_block(&b, op, ++level, f)) { fprintf(stderr, "%s: add_block failed\n", __func__); goto failure; } else if (branch(op) && add_branch(op, &lo, &b, relative_depth, level, f)) { fprintf(stderr, "%s: add_entry failed\n", __func__); goto failure; } else if (op == OP_END) { if (level) { if (set_end(&lo, &b, level, f)) { fprintf(stderr, "%s: set_end failed\n", __func__); return -1; } level--; } else break; } } if (append_lo(&lo, p)) { fprintf(stderr, "%s: append_lo failed\n", __func__); goto failure; } free_blocks(&b); return 0; failure: free(lo.entries); free_blocks(&b); return -1; }