aboutsummaryrefslogtreecommitdiff
path: root/main.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 /main.c
downloadnwc-0086ccf0272fa195dca36845fde9c54149c72f0e.tar.gz
First commit
Diffstat (limited to 'main.c')
-rw-r--r--main.c1228
1 files changed, 1228 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..7ca3321
--- /dev/null
+++ b/main.c
@@ -0,0 +1,1228 @@
+/*
+ * 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 "types.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+static void free_parse(struct parse *const p)
+{
+ if (!p)
+ return;
+
+ free(p->to.offsets);
+ free(p->fti.offsets);
+ free(p->fbo.offsets);
+ free(p->iti.offsets);
+
+ if (p->lo)
+ for (size_t i = 0; i < p->n_lo; i++)
+ free(p->lo[i].entries);
+
+ free(p->lo);
+}
+
+static char *mktmppath(const char *const out)
+{
+ char *path = NULL;
+ static const char fmt[] = "%s.nwc.tmp";
+ const int n = snprintf(NULL, 0, fmt, out);
+
+ if (n < 0)
+ {
+ fprintf(stderr, "%s: snprintf(3) failed\n", __func__);
+ goto failure;
+ }
+
+ const size_t sz = n +1;
+
+ if (!(path = malloc(sz)))
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+
+ const int nn = snprintf(path, sz, fmt, out);
+
+ if (nn < 0 || nn >= sz)
+ {
+ fprintf(stderr, "%s: snprintf(3) failed with %d\n", __func__, nn);
+ goto failure;
+ }
+
+ return path;
+
+failure:
+ free(path);
+ return NULL;
+}
+
+static int dump(FILE *const in, FILE *const out)
+{
+ do
+ {
+ char buf[BUFSIZ];
+ const size_t r = fread(buf, 1, sizeof buf, in);
+
+ if (ferror(in))
+ {
+ fprintf(stderr, "%s: fread(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ const size_t w = fwrite(buf, r, 1, out);
+
+ if (ferror(out))
+ {
+ fprintf(stderr, "%s: fwrite(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ } while (!feof(in));
+
+ return 0;
+}
+
+static int dump_n(const char *const name, const struct offsets *const o,
+ FILE *const f)
+{
+ const varuint32 name_len = strlen(name),
+ payload_len = len_varuint32(name_len) + name_len
+ + (o->n * sizeof *o->offsets);
+
+ if (write_varuint7(f, SECTION_CUSTOM))
+ {
+ fprintf(stderr, "%s: write_varuint7 failed\n", __func__);
+ return -1;
+ }
+ else if (write_varuint32(f, payload_len))
+ {
+ fprintf(stderr, "%s: write_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (write_varuint32(f, name_len))
+ {
+ fprintf(stderr, "%s: write_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (fprintf(f, "%s", name) < 0)
+ {
+ fprintf(stderr, "%s: fprintf(3) failed\n", __func__);
+ return -1;
+ }
+
+ for (size_t i = 0; i < o->n; i++)
+ {
+ const uint32_t off = o->offsets[i];
+ const uint8_t v[] = {off, off >> 8, off >> 16, off >> 24};
+
+ if (!fwrite(&v, sizeof v, 1, f))
+ {
+ fprintf(stderr, "%s: fwrite(3) failed, ferror(3)=%d, feof(3)=%d\n",
+ __func__, ferror(f), feof(f));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int dump_lo_fn(const struct lo *const lo, const size_t n_lo,
+ FILE *const f)
+{
+ long off = ftell(f);
+
+ if (off < 0)
+ {
+ fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ off += n_lo * sizeof (uint32_t);
+
+ for (size_t i = 0; i < n_lo; i++)
+ {
+ const struct lo *const o = &lo[i];
+ const uint8_t v[] = {off, off >> 8, off >> 16, off >> 25};
+
+ if (!fwrite(v, sizeof v, 1, f))
+ {
+ fprintf(stderr, "%s: fwrite(3) failed, ferror=%d, feof=%d\n",
+ __func__, ferror(f), feof(f));
+ return -1;
+ }
+
+ const struct lo_entry *const e = o->entries;
+ static const size_t sz = sizeof e->pc + sizeof e->dst;
+
+ off += sizeof (uint32_t) + o->n * sz;
+ }
+
+ return 0;
+}
+
+static int dump_lo_entry(const struct lo_entry *const e, FILE *const f)
+{
+ const uint32_t p = e->pc;
+ const uint8_t pv[] = {p, p >> 8, p >> 16, p >> 24};
+
+ if (!fwrite(pv, sizeof pv, 1, f))
+ {
+ fprintf(stderr, "%s: fwrite(3) failed, ferror(3)=%d, "
+ "feof(3)=%d\n", __func__, ferror(f), feof(f));
+ return -1;
+ }
+
+ const uint32_t d = e->dst;
+ const uint8_t dv[] = {d, d >> 8, d >> 16, d >> 24};
+
+ if (!fwrite(dv, sizeof dv, 1, f))
+ {
+ fprintf(stderr, "%s: fwrite(3) failed, ferror(3)=%d, "
+ "feof(3)=%d\n", __func__, ferror(f), feof(f));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dump_lo_off(const struct lo *const lo, const size_t n_lo,
+ FILE *const f)
+{
+ for (size_t i = 0; i < n_lo; i++)
+ {
+ const struct lo *const o = &lo[i];
+ const uint8_t n[] = {o->n, o->n >> 8, o->n >> 16, o->n >> 24};
+
+ if (!fwrite(n, sizeof n, 1, f))
+ {
+ fprintf(stderr, "%s: fwrite(3) failed, ferror(3)=%d, feof(3)=%d\n",
+ __func__, ferror(f), feof(f));
+ return -1;
+ }
+
+ for (size_t i = 0; i < o->n; i++)
+ if (dump_lo_entry(&o->entries[i], f))
+ {
+ fprintf(stderr, "%s: dump_lo_entry %zu failed\n", __func__, i);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int dump_lo(const struct lo *const lo, const size_t n_lo,
+ FILE *const f)
+{
+ static const char name[] = "nw_lo";
+ varuint32 name_len = strlen(name),
+ payload_len = len_varuint32(name_len) + name_len
+ + n_lo * sizeof (uint32_t);
+
+ for (size_t i = 0; i < n_lo; i++)
+ {
+ const struct lo *const o = &lo[i];
+ const size_t entrysz = sizeof lo->entries->dst + sizeof lo->entries->pc;
+
+ payload_len += sizeof (uint32_t) + o->n * entrysz;
+ }
+
+ if (write_varuint7(f, SECTION_CUSTOM))
+ {
+ fprintf(stderr, "%s: write_varuint7 failed\n", __func__);
+ return -1;
+ }
+ else if (write_varuint32(f, payload_len))
+ {
+ fprintf(stderr, "%s: write_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (write_varuint32(f, name_len))
+ {
+ fprintf(stderr, "%s: write_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (fprintf(f, "%s", name) < 0)
+ {
+ fprintf(stderr, "%s: fprintf(3) failed\n", __func__);
+ return -1;
+ }
+ else if (dump_lo_fn(lo, n_lo, f))
+ {
+ fprintf(stderr, "%s: dump_lo_fn failed\n", __func__);
+ return -1;
+ }
+ else if (dump_lo_off(lo, n_lo, f))
+ {
+ fprintf(stderr, "%s: dump_lo_off failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dump_parse(const struct parse *const p, FILE *const f)
+{
+ const struct n
+ {
+ const char *name;
+ const struct offsets *o;
+ } ns[] =
+ {
+ {.name = "nw_to", .o = &p->to},
+ {.name = "nw_fti", .o = &p->fti},
+ {.name = "nw_fbo", .o = &p->fbo},
+ {.name = "nw_iti", .o = &p->iti}
+ };
+
+ for (size_t i = 0; i < sizeof ns / sizeof *ns; i++)
+ {
+ const struct n *const n = &ns[i];
+
+ if (dump_n(n->name, n->o, f))
+ {
+ fprintf(stderr, "%s: dump_n %s failed\n", __func__, n->name);
+ return -1;
+ }
+ }
+
+ if (dump_lo(p->lo, p->n_lo, f))
+ {
+ fprintf(stderr, "%s: dump_lo failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static char *gentmp(FILE *const in, const char *out,
+ const struct parse *const p)
+{
+ FILE *f = NULL;
+ char *ret = NULL;
+ char *const path = mktmppath(out);
+
+ if (!path)
+ {
+ fprintf(stderr, "%s: mkttmppath failed\n", __func__);
+ goto end;
+ }
+ else if (!(f = fopen(path, "wb")))
+ {
+ fprintf(stderr, "%s: fopen(3) %s: %s\n", __func__, path,
+ strerror(errno));
+ goto end;
+ }
+ else if (fseek(in, 0, SEEK_SET))
+ {
+ fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+ else if (dump(in, f))
+ {
+ fprintf(stderr, "%s: dump failed\n", __func__);
+ goto end;
+ }
+ else if (dump_parse(p, f))
+ {
+ fprintf(stderr, "%s: dump_parse failed\n", __func__);
+ goto end;
+ }
+
+ ret = path;
+
+end:
+
+ if (f && fclose(f))
+ {
+ fprintf(stderr, "%s: fclose(3) %s: %s\n", __func__, path,
+ strerror(errno));
+ ret = NULL;
+ }
+
+ if (!ret && remove(path))
+ {
+ fprintf(stderr, "%s: remove(3) %s: %s\n", __func__, path,
+ strerror(errno));
+ }
+
+ if (!ret)
+ free(path);
+
+ return ret;
+}
+
+static int check_header(FILE *const f)
+{
+ uint8_t magic[sizeof "asm"], version[sizeof (uint32_t)];
+ static const uint8_t exp[sizeof version] = {1, 0, 0, 0};
+
+ if (!fread(magic, sizeof magic, 1, f))
+ {
+ fprintf(stderr, "%s: fread(3) magic failed, feof=%d, ferror=%d\n",
+ __func__, feof(f), ferror(f));
+ return -1;
+ }
+ else if (memcmp(magic, "\0asm", sizeof magic))
+ {
+ fprintf(stderr, "%s: invalid magic number\n", __func__);
+ return -1;
+ }
+ else if (!fread(version, sizeof version, 1, f))
+ {
+ fprintf(stderr, "%s: fread(3) version failed, feof=%d, ferror=%d\n",
+ __func__, feof(f), ferror(f));
+ return -1;
+ }
+ else if (memcmp(version, exp, sizeof version))
+ {
+ fprintf(stderr, "%s: invalid version number\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+struct section
+{
+ varuint7 code;
+ varuint32 len;
+ char *name;
+};
+
+static char *read_name(FILE *const f)
+{
+ varuint32 name_len;
+ char *name = malloc(1);
+
+ if (!name)
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+ else if (read_varuint32(f, &name_len))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ goto failure;
+ }
+
+ for (varuint32 i = 0; i < name_len; i++)
+ {
+ uint8_t c;
+ const size_t r = fread(&c, sizeof c, 1, f);
+
+ if (!r)
+ {
+ fprintf(stderr, "%s: fread(3) failed, ferror(3)=%d, feof(3)=%d\n",
+ __func__, ferror(f), feof(f));
+ goto failure;
+ }
+
+ char *const newname = realloc(name, i + 2);
+
+ if (!newname)
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+
+ name = newname;
+ name[i] = c;
+ }
+
+ name[name_len] = '\0';
+ return name;
+
+failure:
+ free(name);
+ return NULL;
+}
+
+static int parse_header(FILE *const f, struct section *const s)
+{
+ varuint7 code;
+ varuint32 payload_len;
+ char *name = NULL;
+ long before, after;
+
+ if (read_varuint7(f, &code))
+ {
+ /* Exceptionally, this might not be a fatal error. */
+ if (!feof(f))
+ fprintf(stderr, "%s: read_varuint7 failed\n", __func__);
+
+ goto failure;
+ }
+ else if (read_varuint32(f, &payload_len))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ goto failure;
+ }
+ else if ((before = ftell(f)) < 0)
+ {
+ fprintf(stderr, "%s: ftell(3) before: %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+ else if (!code && !(name = read_name(f)))
+ {
+ fprintf(stderr, "%s: read_name failed\n", __func__);
+ goto failure;
+ }
+ else if ((after = ftell(f)) < 0)
+ {
+ fprintf(stderr, "%s: ftell(3) after: %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+
+ const unsigned long sz = after - before;
+
+ if (payload_len < sz)
+ {
+ fprintf(stderr, "%s: expected payload_len >= %lu, got %lu\n",
+ __func__, sz, (unsigned long)payload_len);
+ goto failure;
+ }
+
+ payload_len -= sz;
+
+ *s = (const struct section)
+ {
+ .code = code,
+ .len = payload_len,
+ .name = name
+ };
+
+ return 0;
+
+failure:
+ free(name);
+ return -1;
+}
+
+static int append(const varuint32 value, struct offsets *const o)
+{
+ const size_t n = o->n + 1;
+ uint32_t *const v = realloc(o->offsets, n * sizeof *o->offsets);
+
+ if (!v)
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ v[o->n++] = value;
+ o->offsets = v;
+ return 0;
+}
+
+static int read_type(FILE *const f, struct parse *const p)
+{
+ const long offset = ftell(f);
+
+ if (offset < 0)
+ {
+ fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ else if (read_varint7(f, &(varint7){0}))
+ {
+ fprintf(stderr, "%s: read_varint7 failed\n", __func__);
+ return -1;
+ }
+
+ varuint32 param_count;
+
+ if (read_varuint32(f, &param_count))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < param_count; i++)
+ if (read_varint7(f, &(value_type){0}))
+ {
+ fprintf(stderr, "%s: read_varint7 failed\n", __func__);
+ return -1;
+ }
+
+ varuint1 return_count;
+ value_type return_type;
+
+ if (read_varuint1(f, &return_count))
+ {
+ fprintf(stderr, "%s: read_varuint1 failed\n", __func__);
+ return -1;
+ }
+ else if (return_count && read_varint7(f, &return_type))
+ {
+ fprintf(stderr, "%s: read_varint7 failed\n", __func__);
+ return -1;
+ }
+ else if (append(offset, &p->to))
+ {
+ fprintf(stderr, "%s: append failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int readstr(FILE *const f, struct parse *const p)
+{
+ varuint32 len;
+
+ if (read_varuint32(f, &len))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < len; i++)
+ {
+ uint8_t b;
+
+ if (!fread(&b, sizeof b, 1, f))
+ {
+ fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n",
+ __func__, feof(f), ferror(f));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int read_resizable_limits(FILE *const f)
+{
+ varuint1 flags;
+
+ if (read_varuint1(f, &flags))
+ {
+ fprintf(stderr, "%s: read_varuint1 failed\n", __func__);
+ return -1;
+ }
+ else if (read_varuint32(f, &(varuint32){0}))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (flags && read_varuint32(f, &(varuint32){0}))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int kind_function(FILE *const f)
+{
+ if (read_varuint32(f, &(varuint32){0}))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int kind_table(FILE *const f)
+{
+ enum {ANYFUNC = 0x70};
+ value_type elem_type;
+
+ if (read_varint7(f, &elem_type))
+ {
+ fprintf(stderr, "%s: read_varint7 failed\n", __func__);
+ return -1;
+ }
+ else if (elem_type != ANYFUNC)
+ {
+ fprintf(stderr, "%s: invalid elem_type, expected=%#x, got=%#hhx\n",
+ __func__, ANYFUNC, (unsigned char)elem_type);
+ return -1;
+ }
+ else if (read_resizable_limits(f))
+ {
+ fprintf(stderr, "%s: read_resizable_limits failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int kind_memory(FILE *const f)
+{
+ return read_resizable_limits(f);
+}
+
+static int kind_global(FILE *const f)
+{
+ if (read_varint7(f, &(varint7){0}))
+ {
+ fprintf(stderr, "%s: read_varint7 failed\n", __func__);
+ return -1;
+ }
+ else if (read_varuint1(f, &(varuint1){0}))
+ {
+ fprintf(stderr, "%s: read_varuint1 failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_external_kind(FILE *const f, struct parse *const p)
+{
+ enum
+ {
+ FUNCTION,
+ TABLE,
+ MEMORY,
+ GLOBAL,
+
+ TYPES
+ };
+
+ uint8_t external_kind;
+
+ if (!fread(&external_kind, sizeof external_kind, 1, f))
+ {
+ fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n",
+ __func__, feof(f), ferror(f));
+ return -1;
+ }
+
+ static int (*const fn[])(FILE *) =
+ {
+ [FUNCTION] = kind_function,
+ [TABLE] = kind_table,
+ [MEMORY] = kind_memory,
+ [GLOBAL] = kind_global
+ };
+
+ if (external_kind >= sizeof fn / sizeof *fn)
+ {
+ fprintf(stderr, "%s: invalid external_kind: %" PRIu8 "\n", __func__,
+ external_kind);
+ return -1;
+ }
+ else if (fn[external_kind](f))
+ {
+ fprintf(stderr, "%s: callback failed, external_kind: %" PRIu8 "\n",
+ __func__, external_kind);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_import(FILE *const f, struct parse *const p)
+{
+ if (readstr(f, p) || readstr(f, p))
+ {
+ fprintf(stderr, "%s: readstr failed\n", __func__);
+ return -1;
+ }
+
+ const long offset = ftell(f);
+
+ if (offset < 0)
+ {
+ fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ else if (read_external_kind(f, p))
+ {
+ fprintf(stderr, "%s: read_external_kind failed\n", __func__);
+ return -1;
+ }
+ else if (append(offset, &p->iti))
+ {
+ fprintf(stderr, "%s: append failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_import(FILE *const f, struct parse *const p)
+{
+ varuint32 count;
+
+ if (read_varuint32(f, &count))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < count; i++)
+ if (read_import(f, p))
+ {
+ fprintf(stderr, "%s: read_import %lu failed\n", __func__,
+ (unsigned long)i);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_type(FILE *const f, struct parse *const p)
+{
+ varuint32 count;
+
+ if (read_varuint32(f, &count))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < count; i++)
+ if (read_type(f, p))
+ {
+ fprintf(stderr, "%s: read_type %lu failed\n", __func__,
+ (unsigned long)i);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_function(FILE *const f, struct parse *const p)
+{
+ varuint32 index;
+
+ if (read_varuint32(f, &index))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (append(index, &p->fti))
+ {
+ fprintf(stderr, "%s: append failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_function(FILE *const f, struct parse *const p)
+{
+ varuint32 count;
+
+ if (read_varuint32(f, &count))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < count; i++)
+ if (read_function(f, p))
+ {
+ fprintf(stderr, "%s: read_function %lu failed\n", __func__,
+ (unsigned long)i);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int skip(FILE *const f, const struct section *const s)
+{
+ const int ret = fseek(f, s->len, SEEK_CUR);
+
+ if (ret)
+ fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno));
+
+ return ret;
+}
+
+static int read_local(FILE *const f)
+{
+ if (read_varuint32(f, &(varuint32){0}))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if (read_varint7(f, &(value_type){0}))
+ {
+ fprintf(stderr, "%s: read_varint7 failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_code(FILE *const f, struct parse *const p,
+ const unsigned long len)
+{
+ long after;
+ const long before = ftell(f);
+
+ if (before < 0)
+ {
+ fprintf(stderr, "%s: ftell(3) before: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ else if (read_instr(f, p))
+ {
+ fprintf(stderr, "%s: read_instr failed\n", __func__);
+ return -1;
+ }
+ else if ((after = ftell(f)) < 0)
+ {
+ fprintf(stderr, "%s: ftell(3) after: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ const unsigned long read = after - before;
+
+ if (read != len)
+ {
+ fprintf(stderr, "%s: expected function body length: %lu, got %lu\n",
+ __func__, len, read);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_body(FILE *const f, struct parse *const p)
+{
+ varuint32 body_size, local_count;
+ long before, after;
+ const long start = ftell(f);
+
+ if (start < 0)
+ {
+ fprintf(stderr, "%s: ftell(3) start: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ else if (read_varuint32(f, &body_size))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+ else if ((before = ftell(f)) < 0)
+ {
+ fprintf(stderr, "%s: ftell(3) before: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ else if (read_varuint32(f, &local_count))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < local_count; i++)
+ if (read_local(f))
+ {
+ fprintf(stderr, "%s: read_local %lu failed\n", __func__,
+ (unsigned long)i);
+ return -1;
+ }
+
+ if ((after = ftell(f)) < 0)
+ {
+ fprintf(stderr, "%s: ftell(3): after %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ else if (read_code(f, p, body_size - (after - before)))
+ {
+ fprintf(stderr, "%s: read_code failed\n", __func__);
+ return -1;
+ }
+ else if (append(start, &p->fbo))
+ {
+ fprintf(stderr, "%s: append failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_code(FILE *const f, struct parse *const p)
+{
+ varuint32 count;
+
+ if (read_varuint32(f, &count))
+ {
+ fprintf(stderr, "%s: read_varuint32 failed\n", __func__);
+ return -1;
+ }
+
+ for (varuint32 i = 0; i < count; i++)
+ if (read_body(f, p))
+ {
+ fprintf(stderr, "%s: read_body %lu failed\n", __func__,
+ (unsigned long)i);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_payload(FILE *const f, struct parse *const p,
+ const struct section *const s)
+{
+ static const struct fn
+ {
+ varuint7 code;
+ int (*fn)(FILE *, struct parse *);
+ } fns[] =
+ {
+ {.code = SECTION_IMPORT, parse_import},
+ {.code = SECTION_TYPE, parse_type},
+ {.code = SECTION_FUNCTION, parse_function},
+ {.code = SECTION_CODE, parse_code}
+ };
+
+ for (size_t i = 0; i < sizeof fns / sizeof *fns; i++)
+ {
+ const struct fn *const fn = &fns[i];
+
+ if (s->code == fn->code)
+ return fn->fn(f, p);
+ }
+
+ fprintf(stderr, "%s: unsupported section %hhu\n", __func__,
+ (unsigned char)s->code);
+ return -1;
+}
+
+static bool relevant(const varuint7 code)
+{
+ switch (code)
+ {
+ case SECTION_CODE:
+ case SECTION_FUNCTION:
+ case SECTION_TYPE:
+ case SECTION_IMPORT:
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static bool repeated(const char *const name)
+{
+ static const char *const s[] =
+ {
+ "nw_lo",
+ "nw_fbo",
+ "nw_to",
+ "nw_iti"
+ };
+
+ for (size_t i = 0; i < sizeof s / sizeof *s; i++)
+ if (!strcmp(name, s[i]))
+ return true;
+
+ return false;
+}
+
+static int read_section(FILE *const f, struct parse *const p)
+{
+ int ret = -1;
+ struct section s = {0};
+
+ if (parse_header(f, &s))
+ {
+ /* Exceptionally, this might not be a fatal error. */
+ if (!feof(f))
+ fprintf(stderr, "%s: parse_header failed\n", __func__);
+
+ goto end;
+ }
+ else if (!relevant(s.code))
+ {
+ if (s.code == SECTION_CUSTOM && repeated(s.name))
+ {
+ fprintf(stderr, "%s: nw extension %s already present in file\n",
+ __func__, s.name);
+ goto end;
+ }
+ else if (skip(f, &s))
+ {
+ fprintf(stderr, "%s: skip failed\n", __func__);
+ goto end;
+ }
+ }
+ else if (parse_payload(f, p, &s))
+ {
+ fprintf(stderr, "%s: parse_payload failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ free(s.name);
+ return ret;
+}
+
+static int parse(FILE *const f, struct parse *const p)
+{
+ if (check_header(f))
+ {
+ fprintf(stderr, "%s: check_magic failed\n", __func__);
+ return -1;
+ }
+
+ for (;;)
+ if (read_section(f, p))
+ {
+ if (feof(f))
+ break;
+
+ fprintf(stderr, "%s: read_section failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int copy(const char *const old, const char *const new)
+{
+ int ret = -1;
+ FILE *const in = fopen(old, "rb"), *const out = fopen(new, "wb");
+
+ if (!in)
+ {
+ fprintf(stderr, "%s: fopen(3) %s: %s\n", __func__, old,
+ strerror(errno));
+ goto end;
+ }
+ else if (!out)
+ {
+ fprintf(stderr, "%s: fopen(3) %s: %s\n", __func__, new,
+ strerror(errno));
+ goto end;
+ }
+ else if (dump(in, out))
+ {
+ fprintf(stderr, "%s: dump failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+
+ if (in && fclose(in))
+ {
+ fprintf(stderr, "%s: fclose(3) %s: %s\n", __func__, old,
+ strerror(errno));
+ ret = -1;
+ }
+
+ if (out && fclose(out))
+ {
+ fprintf(stderr, "%s: fclose(3) %s: %s\n", __func__, new,
+ strerror(errno));
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int move(const char *const old, const char *const new)
+{
+ if (rename(old, new))
+ {
+ if (errno != EXDEV)
+ {
+ fprintf(stderr, "%s: rename(3) %s->%s: %s\n", __func__, old, new,
+ strerror(errno));
+ return -1;
+ }
+ else if (copy(old, new))
+ {
+ fprintf(stderr, "%s: copy %s->%s failed\n", __func__, old, new);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ char *tmp = NULL;
+ FILE *f = NULL;
+ struct parse p = {0};
+
+ if (argc != 3)
+ {
+ fprintf(stderr, "%s <infile> <outfile>\n", *argv);
+ goto end;
+ }
+
+ const char *const in = argv[1], *const out = argv[2];
+
+ if (!(f = fopen(in, "rb")))
+ {
+ fprintf(stderr, "%s: fopen(3) %s: %s\n", __func__, in,
+ strerror(errno));
+ goto end;
+ }
+ else if (parse(f, &p))
+ {
+ fprintf(stderr, "%s: parse failed\n", __func__);
+ goto end;
+ }
+ else if (!(tmp = gentmp(f, out, &p)))
+ {
+ fprintf(stderr, "%s: gettmp failed\n", __func__);
+ goto end;
+ }
+ else if (move(tmp, out))
+ {
+ fprintf(stderr, "%s: move %s->%s failed\n", __func__, tmp, out);
+ goto end;
+ }
+
+ ret = EXIT_SUCCESS;
+
+end:
+
+ if (f && fclose(f))
+ {
+ fprintf(stderr, "%s: fclose(3) %s: %s\n", __func__, in,
+ strerror(errno));
+ ret = EXIT_FAILURE;
+ }
+
+ if (ret != EXIT_SUCCESS && tmp && remove(tmp))
+ fprintf(stderr, "%s: remove(3) %s: %s\n", __func__, tmp,
+ strerror(errno));
+
+ free(tmp);
+ free_parse(&p);
+ return ret;
+}