aboutsummaryrefslogtreecommitdiff
path: root/src/routines/section
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-09-07 00:04:38 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-11-06 14:38:40 +0100
commit6d9d80362f9932bbc87e162b8ef7df06c73e27e1 (patch)
treee3e228c63fe26f07503f226de7fb5086b3dc2286 /src/routines/section
downloadnanowasm-6d9d80362f9932bbc87e162b8ef7df06c73e27e1.tar.gz
First commit
Diffstat (limited to 'src/routines/section')
-rw-r--r--src/routines/section/CMakeLists.txt34
-rw-r--r--src/routines/section/code.c298
-rw-r--r--src/routines/section/custom.c145
-rw-r--r--src/routines/section/data.c178
-rw-r--r--src/routines/section/data_count.c35
-rw-r--r--src/routines/section/element.c20
-rw-r--r--src/routines/section/exit.c36
-rw-r--r--src/routines/section/export.c123
-rw-r--r--src/routines/section/fbo.c52
-rw-r--r--src/routines/section/fti.c59
-rw-r--r--src/routines/section/function.c52
-rw-r--r--src/routines/section/global.c148
-rw-r--r--src/routines/section/import.c339
-rw-r--r--src/routines/section/iti.c52
-rw-r--r--src/routines/section/lo.c58
-rw-r--r--src/routines/section/memory.c86
-rw-r--r--src/routines/section/ops.c141
-rw-r--r--src/routines/section/skip.c45
-rw-r--r--src/routines/section/start.c37
-rw-r--r--src/routines/section/table.c117
-rw-r--r--src/routines/section/to.c52
-rw-r--r--src/routines/section/type.c164
22 files changed, 2271 insertions, 0 deletions
diff --git a/src/routines/section/CMakeLists.txt b/src/routines/section/CMakeLists.txt
new file mode 100644
index 0000000..ae78d27
--- /dev/null
+++ b/src/routines/section/CMakeLists.txt
@@ -0,0 +1,34 @@
+# nanowasm, a tiny WebAssembly/Wasm interpreter
+# Copyright (C) 2023-2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ code.c
+ custom.c
+ data.c
+ data_count.c
+ element.c
+ exit.c
+ export.c
+ fbo.c
+ fti.c
+ function.c
+ global.c
+ import.c
+ iti.c
+ lo.c
+ memory.c
+ ops.c
+ skip.c
+ start.c
+ table.c
+ to.c
+ type.c
+)
+
+if(NW_CHECK_CODE)
+ target_compile_definitions(${PROJECT_NAME} PRIVATE NW_CHECK_CODE)
+endif()
diff --git a/src/routines/section/code.c b/src/routines/section/code.c
new file mode 100644
index 0000000..3ee0c5b
--- /dev/null
+++ b/src/routines/section/code.c
@@ -0,0 +1,298 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/opcodes.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state local_loop(struct nw_mod *);
+static enum nw_state get_opcode(struct nw_mod *);
+static enum nw_state entry_loop(struct nw_mod *);
+
+static enum nw_state get_post_offset(struct nw_mod *const m)
+{
+ long offset;
+ unsigned long consumed;
+ struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&offset, cfg->user);
+
+ if (n)
+ return n;
+ else if ((consumed = offset - c->op_off) && consumed >= c->rem)
+ {
+#ifdef NW_LOG
+ nwp_log("parameters exceed expected body length\n");
+#endif
+ return NW_FATAL;
+ }
+
+ c->rem -= consumed;
+ m->next = get_opcode;
+ return NW_AGAIN;
+}
+
+typedef void (*op_fn)(struct nw_mod *);
+
+static op_fn find(unsigned char op)
+{
+ size_t i;
+
+ for (i = 0; i < nwp_check_ops.n; i++)
+ {
+ const struct nwp_check_op *const o = &nwp_check_ops.ops[i];
+
+ if (op >= o->start && op <= o->end)
+ return o->f;
+ }
+
+ return NULL;
+}
+
+static enum nw_state get_op_offset(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&c->op_off, cfg->user);
+ op_fn fn;
+
+ if (n)
+ return n;
+ else if (!(fn = find(c->op)))
+ {
+#ifdef NW_LOG
+ nwp_log("invalid opcode %#hhx\n", (unsigned char)c->op);
+#endif
+ return NW_FATAL;
+ }
+
+#ifdef NW_LOG
+ nwp_log("opcode: %#hhx\n", (unsigned char)c->op);
+#endif
+ c->next = get_post_offset;
+ fn(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state check_exit(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_c_fn *const fn = &c->fn;
+
+ if (fn->blocks)
+ {
+#ifdef NW_LOG
+ nwp_log("mismatched number of blocks in function %lu\n",
+ (unsigned long)c->entry_i);
+#endif
+ return NW_FATAL;
+ }
+
+ c->entry_i++;
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_opcode(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &c->op;
+ io.n = sizeof c->op;
+
+ if (!c->rem)
+ return check_exit(m);
+ else if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+ else if (c->rem == 1 && c->op != OP_END)
+ {
+#ifdef NW_LOG
+ nwp_log("%s: unexpected opcode %#x at body end\n", (unsigned)c->op);
+#endif
+ return NW_FATAL;
+ }
+
+ c->rem--;
+ m->next = get_op_offset;
+ return NW_AGAIN;
+}
+
+#ifndef NW_CHECK_CODE
+static enum nw_state skip(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->seek(c->body_start + c->rem, cfg->user);
+
+ if (n)
+ return n;
+
+ c->entry_i++;
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+#endif
+
+static enum nw_state get_rem(struct nw_mod *const m)
+{
+ unsigned long consumed;
+ struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&c->body_start, cfg->user);
+
+ if (n)
+ return n;
+ else if ((consumed = c->body_start - c->start) >= c->body_size)
+ {
+#ifdef NW_LOG
+ nwp_log("exceeded function body size\n");
+#endif
+ return NW_FATAL;
+ }
+
+ c->rem = c->body_size - consumed;
+#ifdef NW_CHECK_CODE
+ m->next = get_opcode;
+#else
+ m->next = skip;
+#endif
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_type(struct nw_mod *const m)
+{
+ nw_varint7 type;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varint7(cfg, l, &type, cfg->user);
+
+ if (n)
+ return n;
+
+ c->fn.local_i++;
+ m->next = local_loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_group_count(struct nw_mod *const m)
+{
+ nw_varuint32 group_count;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &group_count, cfg->user);
+
+ if (n)
+ return n;
+
+ c->fn.local_total += group_count;
+ m->next = get_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state local_loop(struct nw_mod *const m)
+{
+ const struct nw_sm_c_fn *const fn = &m->sm.code.fn;
+
+ m->next = fn->local_i < fn->local_count ? get_group_count : get_rem;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_local_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_c_fn *const fn = &c->fn;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &fn->local_count,
+ cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = fn->local_count ? local_loop : get_rem;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_start(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&c->start, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_local_count;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_body_size(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &c->body_size, cfg->user);
+
+ if (n)
+ return n;
+ else if (!c->body_size)
+ {
+#ifdef NW_LOG
+ nwp_log("unexpected zero size for function body %lu\n",
+ (unsigned long)c->entry_i);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_start;
+ return NW_AGAIN;
+}
+
+static enum nw_state entry_loop(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_c_fn fn = {0};
+
+ fn.blocks = 1;
+ c->fn = fn;
+ m->next = c->entry_i < c->count ? get_body_size : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &c->count, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+void nwp_section_code(struct nw_mod *const m)
+{
+ const struct nw_sm_c c = {0};
+
+ m->sm.code = c;
+ m->next = get_count;
+}
diff --git a/src/routines/section/custom.c b/src/routines/section/custom.c
new file mode 100644
index 0000000..926f46b
--- /dev/null
+++ b/src/routines/section/custom.c
@@ -0,0 +1,145 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <string.h>
+
+static enum nw_state get_byte(struct nw_mod *);
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_sm_custom *const c = &m->sm.custom;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->seek(c->start, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section_skip(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state compare(struct nw_mod *const m)
+{
+ struct nw_sm_custom *const c = &m->sm.custom;
+ const struct entry *out = NULL;
+ size_t i;
+ static const struct entry
+ {
+ const char *section;
+ void (*f)(struct nw_mod *);
+ } s[] =
+ {
+ {"nw_to", nwp_section_to},
+ {"nw_fti", nwp_section_fti},
+ {"nw_fbo", nwp_section_fbo},
+ {"nw_lo", nwp_section_lo},
+ {"nw_iti", nwp_section_iti}
+ };
+
+ for (i = 0; i < sizeof s / sizeof *s; i++)
+ {
+ int *const candidate = &c->candidate[i];
+ const struct entry *const e = &s[i];
+
+ if (!*candidate)
+ continue;
+ else if (strlen(e->section) != c->name_len
+ || e->section[c->len_i] != c->byte)
+ {
+ *candidate = 0;
+ continue;
+ }
+
+ out = e;
+ break;
+ }
+
+ if (++c->len_i >= c->name_len)
+ {
+ if (out)
+ {
+ if (m->c_sections[out - s])
+ {
+#ifdef NW_LOG
+ nwp_log("duplicate section: %s\n", out->section);
+#endif
+ return NW_FATAL;
+ }
+
+ out->f(m);
+ }
+ else
+ m->next = skip;
+ }
+ else
+ m->next = get_byte;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_byte(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_custom *const c = &m->sm.custom;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &c->byte;
+ io.n = sizeof c->byte;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+
+ m->next = compare;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_name_len(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_custom *const c = &m->sm.custom;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &c->name_len, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_byte;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_start(struct nw_mod *const m)
+{
+ struct nw_sm_custom *const c = &m->sm.custom;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&c->start, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_name_len;
+ return NW_AGAIN;
+}
+
+void nwp_section_custom(struct nw_mod *const m)
+{
+ const struct nw_sm_custom c = {0};
+ struct nw_sm_custom *const pc = &m->sm.custom;
+ size_t i;
+
+ m->next = get_start;
+ *pc = c;
+
+ for (i = 0; i < sizeof pc->candidate / sizeof *pc->candidate; i++)
+ pc->candidate[i] = 1;
+}
diff --git a/src/routines/section/data.c b/src/routines/section/data.c
new file mode 100644
index 0000000..b189c9d
--- /dev/null
+++ b/src/routines/section/data.c
@@ -0,0 +1,178 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <string.h>
+
+static enum nw_state get_index(struct nw_mod *);
+
+static enum nw_state skip_bytes(struct nw_mod *const m)
+{
+ struct nw_sm_d *const d = &m->sm.data;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long offset = d->offset + d->size;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = ++d->entry_i >= m->data_count ? nwp_section_exit : get_index;
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_d *const d = &m->sm.data;
+ const enum nw_state n = cfg->tell(&d->offset, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = skip_bytes;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_size(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_d *const d = &m->sm.data;
+ struct nw_sm_leb128 *const l = &d->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &d->size, cfg->user);
+
+ if (n)
+ return n;
+ else if (!d->size)
+ {
+#ifdef NW_LOG
+ nwp_log("unexpected zero size for data entry %lu\n",
+ (unsigned long)d->entry_i);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = tell;
+ return NW_AGAIN;
+}
+
+static int push(const void *const src, const size_t n, void *const user)
+{
+ struct nw_mod *const m = user;
+ struct nw_sm_d *const d = &m->sm.data;
+
+ if (n > sizeof d->value)
+ {
+#ifdef NW_LOG
+ nwp_log("stack overflow\n");
+#endif
+ return -1;
+ }
+
+ memcpy(&d->value, src, n);
+ return n;
+}
+
+static enum nw_state loop(struct nw_mod *const m)
+{
+ struct nw_sm_d *const d = &m->sm.data;
+ const enum nw_state n = nwp_interp_run(&d->interp);
+
+ if (n)
+ return n;
+
+ m->next = get_size;
+ return NW_AGAIN;
+}
+
+static enum nw_state setup_initexpr(struct nw_mod *const m)
+{
+ struct nw_interp_cfg icfg = {0};
+ struct nw_sm_d *const d = &m->sm.data;
+
+ icfg.m = m;
+ icfg.io = m->cfg.io;
+ icfg.user = m;
+ icfg.stack.push = push;
+
+ if (nwp_interp_start(&d->interp, &icfg, &nwp_interp_data_set))
+ {
+#ifdef NW_LOG
+ nwp_log("nw_interp_start failed\n");
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_d *const d = &m->sm.data;
+ struct nw_sm_leb128 *const l = &d->leb128;
+ nw_varuint32 *const index = &d->index;
+ const enum nw_state n = nwp_varuint32(cfg, l, index, cfg->user);
+
+ if (n)
+ return n;
+ else if (*index)
+ {
+#ifdef NW_LOG
+ nwp_log("expected memory index 0 in data entry %lu, got %lu\n",
+ (unsigned long)d->entry_i, *index);
+#endif
+ return NW_FATAL;
+ }
+
+ return setup_initexpr(m);
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long dc = m->sections[NW_SECTION_DATA_COUNT];
+ struct nw_sm_d *const d = &m->sm.data;
+ struct nw_sm_leb128 *const l = &d->leb128;
+ nw_varuint32 count, *const pcount = dc ? &count : &m->data_count;
+ enum nw_state n;
+
+ if (!m->sections[NW_SECTION_MEMORY])
+ {
+#ifdef NW_LOG
+ nwp_log("data section found before memory section\n");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = nwp_varuint32(cfg, l, pcount, cfg->user)))
+ return n;
+ else if (dc && count != m->data_count)
+ {
+#ifdef NW_LOG
+ nwp_log("data count mismatch, expected %lu, got %lu\n",
+ (unsigned long)m->data_count, (unsigned long)count);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = m->data_count ? get_index : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+void nwp_section_data(struct nw_mod *const m)
+{
+ const struct nw_sm_d d = {0};
+
+ m->sm.data = d;
+ m->next = get_count;
+}
diff --git a/src/routines/section/data_count.c b/src/routines/section/data_count.c
new file mode 100644
index 0000000..9e7ea07
--- /dev/null
+++ b/src/routines/section/data_count.c
@@ -0,0 +1,35 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_dc *const dc = &m->sm.data_count;
+ struct nw_sm_leb128 *const l = &dc->leb128;
+ enum nw_state n;
+
+ if ((n = nwp_varuint32(cfg, l, &m->data_count, cfg->user)))
+ return n;
+
+ m->next = nwp_section_exit;
+ return NW_AGAIN;
+}
+
+void nwp_section_data_count(struct nw_mod *const m)
+{
+ const struct nw_sm_dc dc = {0};
+
+ m->sm.data_count = dc;
+ m->next = get_count;
+}
diff --git a/src/routines/section/element.c b/src/routines/section/element.c
new file mode 100644
index 0000000..7fd2f54
--- /dev/null
+++ b/src/routines/section/element.c
@@ -0,0 +1,20 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+void nwp_section_element(struct nw_mod *const m)
+{
+ /* TODO */
+ nwp_section_skip(m);
+}
diff --git a/src/routines/section/exit.c b/src/routines/section/exit.c
new file mode 100644
index 0000000..93a2446
--- /dev/null
+++ b/src/routines/section/exit.c
@@ -0,0 +1,36 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+enum nw_state nwp_section_exit(struct nw_mod *const m)
+{
+ long offset;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const struct nw_mod_section *const s = &m->section;
+ const enum nw_state n = cfg->tell(&offset, cfg->user);
+ unsigned long size;
+
+ if (n)
+ return n;
+ else if ((size = offset - s->offset) != s->len)
+ {
+#ifdef NW_LOG
+ nwp_log("size mismatch (%lu expected, got %lu)\n",
+ (unsigned long)s->len, size);
+#endif
+ return NW_FATAL;
+ }
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
diff --git a/src/routines/section/export.c b/src/routines/section/export.c
new file mode 100644
index 0000000..643b55f
--- /dev/null
+++ b/src/routines/section/export.c
@@ -0,0 +1,123 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state entry_loop(struct nw_mod *m);
+
+static enum nw_state get_index(struct nw_mod *const m)
+{
+ nw_varuint32 index;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_exp *const e = &m->sm.export;
+ struct nw_sm_leb128 *const l = &e->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+
+ e->entry_i++;
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_kind(struct nw_mod *const m)
+{
+ unsigned char kind;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &kind;
+ io.n = sizeof kind;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+
+ /* TODO: check kind */
+ m->next = get_index;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_name(struct nw_mod *const m)
+{
+ unsigned char b;
+ struct nw_sm_exp *const e = &m->sm.export;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &b;
+ io.n = sizeof b;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+ else if (++e->len_i >= e->field_len)
+ {
+ e->len_i = 0;
+ m->next = get_kind;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_len(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_exp *const e = &m->sm.export;
+ struct nw_sm_leb128 *const l = &e->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &e->field_len, cfg->user);
+
+ if (n)
+ return n;
+ else if (!e->field_len)
+ {
+#ifdef NW_LOG
+ nwp_log("invalid field length\n");
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_name;
+ return NW_AGAIN;
+}
+
+static enum nw_state entry_loop(struct nw_mod *const m)
+{
+ const struct nw_sm_exp *const e = &m->sm.export;
+
+ m->next = e->entry_i < e->count ? get_len : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_exp *const e = &m->sm.export;
+ struct nw_sm_leb128 *const l = &e->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &e->count, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+void nwp_section_export(struct nw_mod *const m)
+{
+ const struct nw_sm_exp e = {0};
+
+ m->sm.export = e;
+ m->next = get_count;
+}
diff --git a/src/routines/section/fbo.c b/src/routines/section/fbo.c
new file mode 100644
index 0000000..82ebd14
--- /dev/null
+++ b/src/routines/section/fbo.c
@@ -0,0 +1,52 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long offset = m->c_sections[NW_CUSTOM_FBO]
+ + sizeof (struct nw_leuint32) * m->function_count;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state check(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ enum nw_state n;
+
+ if (!m->sections[NW_SECTION_CODE])
+ {
+#ifdef NW_LOG
+ nwp_log("code section must be defined first\n");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = cfg->tell(&m->c_sections[NW_CUSTOM_FBO], cfg->user)))
+ return n;
+
+ m->next = skip;
+ return NW_AGAIN;
+}
+
+void nwp_section_fbo(struct nw_mod *const m)
+{
+ m->next = check;
+}
diff --git a/src/routines/section/fti.c b/src/routines/section/fti.c
new file mode 100644
index 0000000..ddc7762
--- /dev/null
+++ b/src/routines/section/fti.c
@@ -0,0 +1,59 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long offset = m->c_sections[NW_CUSTOM_FTI]
+ + sizeof (struct nw_leuint32) * m->function_count;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state check(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ enum nw_state n;
+
+ if (!m->sections[NW_SECTION_FUNCTION])
+ {
+#ifdef NW_LOG
+ nwp_log("function section must be defined first\n");
+#endif
+ return NW_FATAL;
+ }
+ else if (!m->sections[NW_SECTION_IMPORT])
+ {
+#ifdef NW_LOG
+ nwp_log("import section must be defined first\n");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = cfg->tell(&m->c_sections[NW_CUSTOM_FTI], cfg->user)))
+ return n;
+
+ m->next = skip;
+ return NW_AGAIN;
+}
+
+void nwp_section_fti(struct nw_mod *const m)
+{
+ m->next = check;
+}
diff --git a/src/routines/section/function.c b/src/routines/section/function.c
new file mode 100644
index 0000000..7cbcaca
--- /dev/null
+++ b/src/routines/section/function.c
@@ -0,0 +1,52 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state get_entry(struct nw_mod *const m)
+{
+ struct nw_sm_fn *const fn = &m->sm.function;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_leb128 *const l = &fn->leb128;
+ nw_varuint32 type;
+ const enum nw_state n = nwp_varuint32(cfg, l, &type, cfg->user);
+
+ if (n)
+ return n;
+ else if (++fn->entry_i >= m->function_count)
+ m->next = nwp_section_exit;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_fn *const fn = &m->sm.function;
+ struct nw_sm_leb128 *const l = &fn->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &m->function_count,
+ cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_entry;
+ return NW_AGAIN;
+}
+
+void nwp_section_function(struct nw_mod *const m)
+{
+ const struct nw_sm_fn fn = {0};
+
+ m->next = get_count;
+ m->sm.function = fn;
+}
diff --git a/src/routines/section/global.c b/src/routines/section/global.c
new file mode 100644
index 0000000..6223fc9
--- /dev/null
+++ b/src/routines/section/global.c
@@ -0,0 +1,148 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/types.h>
+#include <limits.h>
+#include <string.h>
+
+static enum nw_state get_content_type(struct nw_mod *);
+
+static int push(const void *const src, const size_t n, void *const user)
+{
+ struct nw_mod *const m = user;
+ struct nw_sm_gl *const gl = &m->sm.global;
+
+ if (n > sizeof gl->value)
+ {
+#ifdef NW_LOG
+ nwp_log("stack overflow\n");
+#endif
+ return -1;
+ }
+
+ memcpy(&gl->value, src, n);
+ return n;
+}
+
+static enum nw_state loop(struct nw_mod *const m)
+{
+ struct nw_sm_gl *const gl = &m->sm.global;
+ const enum nw_state n = nwp_interp_run(&gl->interp);
+
+ if (n)
+ return n;
+
+ m->next = ++gl->entry_i >= m->global_count ?
+ nwp_section_exit : get_content_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_global(struct nw_mod *const m)
+{
+ struct nw_sm_gl *const gl = &m->sm.global;
+ struct nw_interp_cfg cfg = {0};
+
+ cfg.m = m;
+ cfg.io = m->cfg.io;
+ cfg.user = m;
+ cfg.stack.push = push;
+
+ if (nwp_interp_start(&gl->interp, &cfg, &nwp_interp_initexpr_set))
+ {
+#ifdef NW_LOG
+ nwp_log("nw_interp_start failed\n");
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_mutability(struct nw_mod *const m)
+{
+ nw_varuint1 mutability;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_gl *const gl = &m->sm.global;
+ struct nw_sm_leb128 *const l = &gl->leb128;
+ const enum nw_state n = nwp_varuint1(cfg, l, &mutability, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_global;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_content_type(struct nw_mod *const m)
+{
+ nw_varint7 content_type;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_gl *const gl = &m->sm.global;
+ struct nw_sm_leb128 *const l = &gl->leb128;
+ const enum nw_state n = nwp_varint7(cfg, l, &content_type, cfg->user);
+ enum nw_type type;
+
+ /* TODO: do not ignore type */
+ if (n)
+ return n;
+ else if (nwp_get_type(content_type, &type))
+ {
+#ifdef NW_LOG
+ nwp_log("nwp_get_type failed\n");
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_mutability;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_gl *const gl = &m->sm.global;
+ struct nw_sm_leb128 *const l = &gl->leb128;
+ enum nw_state n;
+
+ if (!m->sections[NW_SECTION_IMPORT])
+ {
+#ifdef NW_LOG
+ nwp_log("global section found before import section\n");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = nwp_varuint32(cfg, l, &m->global_count, cfg->user)))
+ return n;
+ else if (m->global_count > ULONG_MAX / sizeof (struct nw_global))
+ {
+#ifdef NW_LOG
+ nwp_log("global_count too large: %lu\n",
+ (unsigned long)m->global_count);
+#endif
+ return NW_FATAL;
+ }
+
+ m->out.global = m->global_count * sizeof (struct nw_global);
+ m->next = m->global_count ? get_content_type : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+void nwp_section_global(struct nw_mod *const m)
+{
+ const struct nw_sm_gl g = {0};
+
+ m->sm.global = g;
+ m->next = get_count;
+}
diff --git a/src/routines/section/import.c b/src/routines/section/import.c
new file mode 100644
index 0000000..26ef49a
--- /dev/null
+++ b/src/routines/section/import.c
@@ -0,0 +1,339 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <string.h>
+
+static enum nw_state entry_loop(struct nw_mod *);
+static enum nw_state find_import(struct nw_mod *);
+
+static enum nw_state get_function_type(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ struct nw_sm_leb128 *const l = &imp->leb128;
+ struct nw_import_index *const ii = &m->cfg.imp_indexes[imp->imp_i];
+ nw_varuint32 type;
+ const enum nw_state n = nwp_varuint32(cfg, l, &type, cfg->user);
+
+ if (n)
+ return n;
+ else if (imp->entry_i >= m->cfg.n_imports)
+ {
+#ifdef NW_LOG
+ nwp_log("too many import entries\n");
+#endif
+ return NW_FATAL;
+ }
+
+ ii->index = imp->entry_i++;
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state skip_import(struct nw_mod *const m)
+{
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->seek(imp->mod_off, cfg->user);
+
+ if (n)
+ return n;
+
+ imp->len_i = 0;
+ imp->imp_i++;
+ m->next = find_import;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_kind(struct nw_mod *const m)
+{
+ unsigned char kind;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_import *const i = &m->cfg.imports[imp->imp_i];
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &kind;
+ io.n = sizeof kind;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+ else if (kind != i->kind)
+ m->next = skip_import;
+ /* TODO: process every import type. */
+ else if (kind != NW_KIND_FUNCTION)
+ m->next = nwp_section_exit;
+ else
+ m->next = get_function_type;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state compare_field(struct nw_mod *const m)
+{
+ unsigned char byte;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_import *const i = &m->cfg.imports[imp->imp_i];
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &byte;
+ io.n = sizeof byte;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+ else if (byte != i->field[imp->len_i])
+ m->next = skip_import;
+ else if (++imp->len_i >= imp->field_len)
+ {
+ m->next = get_kind;
+ imp->len_i = 0;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_field_offset(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const enum nw_state n = cfg->tell(&imp->field_off, cfg->user);
+ const struct nw_import *const i = &m->cfg.imports[imp->imp_i];
+
+ if (n)
+ return n;
+
+ m->next = imp->field_len != strlen(i->field) ? skip_import : compare_field;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_field_len(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ struct nw_sm_leb128 *const l = &imp->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &imp->field_len, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_field_offset;
+ return NW_AGAIN;
+}
+
+static enum nw_state compare_name(struct nw_mod *const m)
+{
+ unsigned char byte;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_import *const i = &m->cfg.imports[imp->imp_i];
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &byte;
+ io.n = sizeof byte;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+ else if (byte != i->module[imp->len_i])
+ m->next = skip_import;
+ else if (++imp->len_i >= imp->mod_len)
+ {
+ m->next = get_field_len;
+ imp->len_i = 0;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state dump_import_field_name(struct nw_mod *const m)
+{
+ unsigned char byte;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_io io = {0};
+ struct nw_sm_imp *const imp = &m->sm.import;
+ enum nw_state n;
+
+ io.buf = &byte;
+ io.n = sizeof byte;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+
+#ifdef NW_LOG
+ nwp_log("%c", (char)byte);
+#endif
+
+ if (++imp->len_i >= imp->field_len)
+ {
+#ifdef NW_LOG
+ nwp_log("\n");
+#endif
+ return NW_FATAL;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state rewind_to_field_name(struct nw_mod *const m)
+{
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->seek(imp->field_off, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = dump_import_field_name;
+ return NW_AGAIN;
+}
+
+static enum nw_state dump_import_mod_name(struct nw_mod *const m)
+{
+ unsigned char byte;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &byte;
+ io.n = sizeof byte;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+
+#ifdef NW_LOG
+ nwp_log("%c", (char)byte);
+#endif
+
+ if (++imp->len_i >= imp->mod_len)
+ {
+ imp->len_i = 0;
+
+ if (imp->field_off)
+ {
+#ifdef NW_LOG
+ nwp_log("::");
+#endif
+ m->next = rewind_to_field_name;
+ }
+ else
+ {
+#ifdef NW_LOG
+ nwp_log("\n");
+#endif
+ return NW_FATAL;
+ }
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state rewind_to_mod_name(struct nw_mod *const m)
+{
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->seek(imp->mod_off, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = dump_import_mod_name;
+ return NW_AGAIN;
+}
+
+static enum nw_state find_import(struct nw_mod *const m)
+{
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const struct nw_import *i;
+
+ if (imp->imp_i >= m->cfg.n_imports)
+ {
+#ifdef NW_LOG
+ nwp_log("required import: ");
+#endif
+ m->next = rewind_to_mod_name;
+ return NW_AGAIN;
+ }
+
+ i = &m->cfg.imports[imp->imp_i];
+
+ if (imp->mod_len == strlen(i->module))
+ m->next = compare_name;
+ else
+ imp->imp_i++;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_module_offset(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ const enum nw_state n = cfg->tell(&imp->mod_off, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = find_import;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_module_len(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ struct nw_sm_leb128 *const l = &imp->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &imp->mod_len, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_module_offset;
+ return NW_AGAIN;
+}
+
+static enum nw_state entry_loop(struct nw_mod *const m)
+{
+ struct nw_sm_imp *const imp = &m->sm.import;
+
+ imp->len_i = 0;
+ imp->imp_i = 0;
+ m->next = imp->entry_i < m->import_count ?
+ get_module_len : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_imp *const imp = &m->sm.import;
+ struct nw_sm_leb128 *const l = &imp->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &m->import_count, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+void nwp_section_import(struct nw_mod *const m)
+{
+ const struct nw_sm_imp imp = {0};
+
+ m->next = get_count;
+ m->sm.import = imp;
+}
diff --git a/src/routines/section/iti.c b/src/routines/section/iti.c
new file mode 100644
index 0000000..6fc7abc
--- /dev/null
+++ b/src/routines/section/iti.c
@@ -0,0 +1,52 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long offset = m->c_sections[NW_CUSTOM_ITI]
+ + sizeof (struct nw_leuint32) * m->import_count;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state check(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ enum nw_state n;
+
+ if (!m->sections[NW_SECTION_IMPORT])
+ {
+#ifdef NW_LOG
+ nwp_log("import section must be defined first\n");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = cfg->tell(&m->c_sections[NW_CUSTOM_ITI], cfg->user)))
+ return n;
+
+ m->next = skip;
+ return NW_AGAIN;
+}
+
+void nwp_section_iti(struct nw_mod *const m)
+{
+ m->next = check;
+}
diff --git a/src/routines/section/lo.c b/src/routines/section/lo.c
new file mode 100644
index 0000000..aacfa67
--- /dev/null
+++ b/src/routines/section/lo.c
@@ -0,0 +1,58 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_sm_custom *const c = &m->sm.custom;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->seek(c->start, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section_skip(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_offset(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&m->c_sections[NW_CUSTOM_LO], cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = skip;
+ return NW_AGAIN;
+}
+
+static enum nw_state check(struct nw_mod *const m)
+{
+ if (!m->sections[NW_SECTION_CODE])
+ {
+#ifdef NW_LOG
+ nwp_log("code section must be defined first\n");
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_offset;
+ return NW_AGAIN;
+}
+
+void nwp_section_lo(struct nw_mod *const m)
+{
+ m->next = check;
+}
diff --git a/src/routines/section/memory.c b/src/routines/section/memory.c
new file mode 100644
index 0000000..9277230
--- /dev/null
+++ b/src/routines/section/memory.c
@@ -0,0 +1,86 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state get_maximum(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_mem *const mem = &m->sm.memory;
+ struct nw_mod_out_mem *const lin = &m->out.linear;
+ struct nw_sm_leb128 *const l = &mem->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &lin->max, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_initial(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_mem *const mem = &m->sm.memory;
+ struct nw_mod_out_mem *const lin = &m->out.linear;
+ struct nw_sm_leb128 *const l = &mem->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &lin->initial, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = mem->flags ? get_maximum : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_flags(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_mem *const mem = &m->sm.memory;
+ struct nw_sm_leb128 *const l = &mem->leb128;
+ const enum nw_state n = nwp_varuint1(cfg, l, &mem->flags, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_initial;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_mem *const mem = &m->sm.memory;
+ struct nw_sm_leb128 *const l = &mem->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &mem->count, cfg->user);
+
+ if (n)
+ return n;
+ else if (mem->count > 1)
+ {
+#ifdef NW_LOG
+ nwp_log("MVP WebAssembly supports no more than 1 memory\n");
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = mem->count ? get_flags : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+void nwp_section_memory(struct nw_mod *const m)
+{
+ const struct nw_sm_mem mem = {0};
+
+ m->next = get_count;
+ m->sm.memory = mem;
+}
diff --git a/src/routines/section/ops.c b/src/routines/section/ops.c
new file mode 100644
index 0000000..a81c8e7
--- /dev/null
+++ b/src/routines/section/ops.c
@@ -0,0 +1,141 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/ops.h>
+#include <nw/opcodes.h>
+
+static const struct nwp_check_op ops[] =
+{
+ {
+ OP_UNREACHABLE,
+ OP_NOP,
+ nwp_op_check_no_immediate
+ },
+
+ {
+ OP_BLOCK,
+ OP_IF,
+ nwp_op_check_block
+ },
+
+ {
+ OP_ELSE,
+ OP_ELSE,
+ nwp_op_check_no_immediate
+ },
+
+ {
+ OP_END,
+ OP_END,
+ nwp_op_check_end
+ },
+
+ {
+ OP_BR,
+ OP_BR_IF,
+ nwp_op_check_relative_depth
+ },
+
+ {
+ OP_BR_TABLE,
+ OP_BR_TABLE,
+ nwp_op_check_br_table
+ },
+
+ {
+ OP_RETURN,
+ OP_RETURN,
+ nwp_op_check_no_immediate
+ },
+
+ {
+ OP_CALL,
+ OP_CALL,
+ nwp_op_check_call
+ },
+
+ {
+ OP_CALL_INDIRECT,
+ OP_CALL_INDIRECT,
+ nwp_op_check_call_indirect
+ },
+
+ {
+ OP_DROP,
+ OP_SELECT,
+ nwp_op_check_no_immediate
+ },
+
+ {
+ OP_GET_LOCAL,
+ OP_TEE_LOCAL,
+ nwp_op_check_local_index,
+ },
+
+ {
+ OP_GET_GLOBAL,
+ OP_SET_GLOBAL,
+ nwp_op_check_global_index
+ },
+
+ {
+ OP_I32_LOAD,
+ OP_I64_STORE32,
+ nwp_op_check_memory_immediate
+ },
+
+ {
+ OP_CURRENT_MEMORY,
+ OP_GROW_MEMORY,
+ nwp_op_check_varuint1
+ },
+
+ {
+ OP_I32_CONST,
+ OP_I32_CONST,
+ nwp_op_check_varint32
+ },
+
+ {
+ OP_I64_CONST,
+ OP_I64_CONST,
+ nwp_op_check_varint64
+ },
+
+ {
+ OP_F32_CONST,
+ OP_F32_CONST,
+ nwp_op_check_uint32
+ },
+
+ {
+ OP_F64_CONST,
+ OP_F64_CONST,
+ nwp_op_check_uint64
+ },
+
+ {
+ OP_I32_EQZ,
+ OP_F64_PROMOTE_F32,
+ nwp_op_check_no_immediate
+ },
+
+ {
+ OP_MISC,
+ OP_MISC,
+ nwp_op_check_misc
+ }
+};
+
+const struct nwp_check_ops nwp_check_ops =
+{
+ ops,
+ sizeof ops / sizeof *ops
+};
diff --git a/src/routines/section/skip.c b/src/routines/section/skip.c
new file mode 100644
index 0000000..72296ce
--- /dev/null
+++ b/src/routines/section/skip.c
@@ -0,0 +1,45 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_mod_section *const s = &m->section;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long offset = s->cur + s->len;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_mod *const m)
+{
+ struct nw_mod_section *const s = &m->section;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = cfg->tell(&s->cur, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = skip;
+ return NW_AGAIN;
+}
+
+void nwp_section_skip(struct nw_mod *const m)
+{
+ m->next = tell;
+}
diff --git a/src/routines/section/start.c b/src/routines/section/start.c
new file mode 100644
index 0000000..8ff22f3
--- /dev/null
+++ b/src/routines/section/start.c
@@ -0,0 +1,37 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state get_index(struct nw_mod *const m)
+{
+ nw_varuint32 index;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_start *const st = &m->sm.start;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = nwp_section_exit;
+ return NW_AGAIN;
+}
+
+void nwp_section_start(struct nw_mod *const m)
+{
+ const struct nw_sm_start s = {0};
+
+ m->sm.start = s;
+ m->next = get_index;
+}
diff --git a/src/routines/section/table.c b/src/routines/section/table.c
new file mode 100644
index 0000000..56f62ae
--- /dev/null
+++ b/src/routines/section/table.c
@@ -0,0 +1,117 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state get_maximum(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_tb *const tb = &m->sm.table;
+ struct nw_mod_out_mem *const ot = &m->out.table;
+ struct nw_sm_leb128 *const l = &tb->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &ot->max, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_initial(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_tb *const tb = &m->sm.table;
+ struct nw_mod_out_mem *const ot = &m->out.table;
+ struct nw_sm_leb128 *const l = &tb->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &ot->initial, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = tb->flags ? get_maximum : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_flags(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_tb *const tb = &m->sm.table;
+ struct nw_sm_leb128 *const l = &tb->leb128;
+ const enum nw_state n = nwp_varuint1(cfg, l, &tb->flags, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_initial;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_element_type(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_tb *const tb = &m->sm.table;
+ struct nw_sm_leb128 *const l = &tb->leb128;
+ const enum nw_state n = nwp_varint7(cfg, l, &tb->elem_type, cfg->user);
+
+ if (n)
+ return n;
+ /* TODO: replace with enum? */
+ else if (tb->elem_type != 0x70)
+ {
+#ifdef NW_LOG
+ nwp_log("unexpected elem_type %hhx\n", (char)tb->elem_type);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_flags;
+ return NW_AGAIN;
+}
+
+static enum nw_state entry_loop(struct nw_mod *const m)
+{
+ const struct nw_sm_tb *const tb = &m->sm.table;
+
+ m->next = tb->entry_i < tb->count ? get_element_type : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_tb *const tb = &m->sm.table;
+ struct nw_sm_leb128 *const l = &tb->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &tb->count, cfg->user);
+
+ if (n)
+ return n;
+ else if (tb->count > 1u)
+ {
+#ifdef NW_LOG
+ nwp_log("got %lu tables, but only 1 supported by the MVP\n",
+ (unsigned long)tb->count);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+void nwp_section_table(struct nw_mod *const m)
+{
+ const struct nw_sm_tb t = {0};
+
+ m->sm.table = t;
+ m->next = get_count;
+}
diff --git a/src/routines/section/to.c b/src/routines/section/to.c
new file mode 100644
index 0000000..cf459cd
--- /dev/null
+++ b/src/routines/section/to.c
@@ -0,0 +1,52 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state skip(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const long offset = m->c_sections[NW_CUSTOM_TO]
+ + sizeof (struct nw_leuint32) * m->type_count;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
+
+static enum nw_state check(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ enum nw_state n;
+
+ if (!m->sections[NW_SECTION_TYPE])
+ {
+#ifdef NW_LOG
+ nwp_log("type section must be defined first\n");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = cfg->tell(&m->c_sections[NW_CUSTOM_TO], cfg->user)))
+ return n;
+
+ m->next = skip;
+ return NW_AGAIN;
+}
+
+void nwp_section_to(struct nw_mod *const m)
+{
+ m->next = check;
+}
diff --git a/src/routines/section/type.c b/src/routines/section/type.c
new file mode 100644
index 0000000..c8e5e9c
--- /dev/null
+++ b/src/routines/section/type.c
@@ -0,0 +1,164 @@
+/*
+ * nanowasm, a tiny WebAssembly/Wasm interpreter
+ * Copyright (C) 2023-2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <stddef.h>
+
+static enum nw_state entry_loop(struct nw_mod *);
+
+static enum nw_state get_return_type(struct nw_mod *const m)
+{
+ struct nw_sm_st *const st = &m->sm.type;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ nw_varint7 return_type;
+ const enum nw_state n = nwp_varint7(cfg, l, &return_type, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = entry_loop;
+ st->entry_i++;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_return_count(struct nw_mod *const m)
+{
+ struct nw_sm_st *const st = &m->sm.type;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ nw_varuint1 return_count;
+ const enum nw_state n = nwp_varuint1(cfg, l, &return_count, cfg->user);
+
+ if (n)
+ return n;
+ else if (return_count)
+ m->next = get_return_type;
+ else
+ {
+ m->next = entry_loop;
+ st->entry_i++;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state param_loop(struct nw_mod *const m)
+{
+ struct nw_sm_st *const st = &m->sm.type;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ nw_varint7 type;
+ enum nw_state n;
+
+ if (st->p_i >= st->param_count)
+ {
+ m->next = get_return_count;
+ return NW_AGAIN;
+ }
+ else if ((n = nwp_varint7(cfg, l, &type, cfg->user)))
+ return n;
+
+ st->p_i++;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param_count(struct nw_mod *const m)
+{
+ struct nw_sm_st *const st = &m->sm.type;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &st->param_count,
+ cfg->user);
+
+ if (n)
+ return n;
+
+ st->p_i = 0;
+ m->next = param_loop;
+ return NW_AGAIN;
+}
+
+static int check_form(const nw_varint7 form)
+{
+ static const nw_varint7 v[] =
+ {
+ /* TODO: move values to enums? */
+ 0x7f, /* i32 */
+ 0x7e, /* i64 */
+ 0x7d, /* f32 */
+ 0x7c, /* f64 */
+ 0x70, /* anyfunc */
+ 0x60, /* func */
+ 0x40 /* empty block_type */
+ };
+
+ size_t i;
+
+ for (i = 0; i < sizeof v / sizeof *v; i++)
+ if (form == v[i])
+ return 0;
+
+ return -1;
+}
+
+static enum nw_state get_form(struct nw_mod *const m)
+{
+ struct nw_sm_st *const st = &m->sm.type;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ nw_varint7 form;
+ const enum nw_state n = nwp_varint7(cfg, l, &form, cfg->user);
+
+ if (n)
+ return n;
+ else if (check_form(form))
+ {
+#ifdef NW_LOG
+ nwp_log("invalid form %#x\n", (unsigned)form);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_param_count;
+ return NW_AGAIN;
+}
+
+static enum nw_state entry_loop(struct nw_mod *const m)
+{
+ struct nw_sm_st *const st = &m->sm.type;
+
+ m->next = st->entry_i < m->type_count ? get_form : nwp_section_exit;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_st *const st = &m->sm.type;
+ struct nw_sm_leb128 *const l = &st->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &m->type_count, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = entry_loop;
+ return NW_AGAIN;
+}
+
+void nwp_section_type(struct nw_mod *const m)
+{
+ const struct nw_sm_st st = {0};
+
+ m->next = get_count;
+ m->sm.type = st;
+}