diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2025-03-06 16:37:12 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-11-07 09:55:58 +0100 |
| commit | 0086ccf0272fa195dca36845fde9c54149c72f0e (patch) | |
| tree | dc025e30b7e0a6830e44c9558f3b800295cc79df /read_instr.c | |
| download | nwc-0086ccf0272fa195dca36845fde9c54149c72f0e.tar.gz | |
First commit
Diffstat (limited to 'read_instr.c')
| -rw-r--r-- | read_instr.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/read_instr.c b/read_instr.c new file mode 100644 index 0000000..805326c --- /dev/null +++ b/read_instr.c @@ -0,0 +1,603 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include "instr.h" +#include "types.h" +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +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; +} |
