aboutsummaryrefslogtreecommitdiff
path: root/read_instr.c
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2025-03-06 16:37:12 +0100
committerXavier Del Campo Romero <xavi92@disroot.org>2025-11-07 09:55:58 +0100
commit0086ccf0272fa195dca36845fde9c54149c72f0e (patch)
treedc025e30b7e0a6830e44c9558f3b800295cc79df /read_instr.c
downloadnwc-0086ccf0272fa195dca36845fde9c54149c72f0e.tar.gz
First commit
Diffstat (limited to 'read_instr.c')
-rw-r--r--read_instr.c603
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;
+}