aboutsummaryrefslogtreecommitdiff
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
First commit
-rw-r--r--.gitignore2
-rw-r--r--CMakeLists.txt67
-rw-r--r--COMPARE.md261
-rw-r--r--LICENSE373
-rw-r--r--README.md90
-rw-r--r--doc/frame30
-rw-r--r--examples/CMakeLists.txt10
-rw-r--r--examples/example.wasmbin0 -> 26749 bytes
-rw-r--r--examples/file.c659
-rw-r--r--examples/minimal.c136
-rw-r--r--examples/proc_exit.c484
-rw-r--r--include/nanowasm/dbg.h33
-rw-r--r--include/nanowasm/linear.h20
-rw-r--r--include/nanowasm/nw.h32
-rw-r--r--include/nanowasm/private.h675
-rw-r--r--include/nanowasm/types.h180
-rw-r--r--private_include/nw/global.h20
-rw-r--r--private_include/nw/inst.h17
-rw-r--r--private_include/nw/interp.h39
-rw-r--r--private_include/nw/io.h40
-rw-r--r--private_include/nw/linear.h21
-rw-r--r--private_include/nw/log.h17
-rw-r--r--private_include/nw/mem.h20
-rw-r--r--private_include/nw/opcodes.h192
-rw-r--r--private_include/nw/ops.h90
-rw-r--r--private_include/nw/routines.h71
-rw-r--r--private_include/nw/stack.h24
-rw-r--r--private_include/nw/types.h19
-rw-r--r--src/CMakeLists.txt23
-rw-r--r--src/dbg/CMakeLists.txt15
-rw-r--r--src/dbg/global.c47
-rw-r--r--src/dbg/init.c19
-rw-r--r--src/dbg/local.c71
-rw-r--r--src/dbg/mem_read.c47
-rw-r--r--src/dbg/param.c71
-rw-r--r--src/dbg/value.c17
-rw-r--r--src/init.c28
-rw-r--r--src/inst/CMakeLists.txt10
-rw-r--r--src/inst/run.c17
-rw-r--r--src/interp/CMakeLists.txt21
-rw-r--r--src/interp/data_set.c24
-rw-r--r--src/interp/global/CMakeLists.txt11
-rw-r--r--src/interp/global/load.c34
-rw-r--r--src/interp/global/store.c34
-rw-r--r--src/interp/initexpr_set.c28
-rw-r--r--src/interp/linear/CMakeLists.txt11
-rw-r--r--src/interp/linear/load.c39
-rw-r--r--src/interp/linear/store.c40
-rw-r--r--src/interp/mem/CMakeLists.txt11
-rw-r--r--src/interp/mem/load.c27
-rw-r--r--src/interp/mem/store.c27
-rw-r--r--src/interp/ops.c278
-rw-r--r--src/interp/resume.c20
-rw-r--r--src/interp/routines/CMakeLists.txt13
-rw-r--r--src/interp/routines/execute.c83
-rw-r--r--src/interp/routines/find_export.c229
-rw-r--r--src/interp/routines/full.c63
-rw-r--r--src/interp/routines/limited.c54
-rw-r--r--src/interp/run.c16
-rw-r--r--src/interp/stack/CMakeLists.txt14
-rw-r--r--src/interp/stack/pop.c37
-rw-r--r--src/interp/stack/ptr.c21
-rw-r--r--src/interp/stack/push.c37
-rw-r--r--src/interp/stack/read.c37
-rw-r--r--src/interp/stack/write.c38
-rw-r--r--src/interp/start.c28
-rw-r--r--src/io/CMakeLists.txt23
-rw-r--r--src/io/leb128.c75
-rw-r--r--src/io/leuint32.c17
-rw-r--r--src/io/leuint64.c24
-rw-r--r--src/io/read.c31
-rw-r--r--src/io/toleuint32.c19
-rw-r--r--src/io/toleuint64.c23
-rw-r--r--src/io/varint1.c27
-rw-r--r--src/io/varint32.c27
-rw-r--r--src/io/varint64.c28
-rw-r--r--src/io/varint7.c27
-rw-r--r--src/io/varuint1.c27
-rw-r--r--src/io/varuint32.c27
-rw-r--r--src/io/varuint64.c27
-rw-r--r--src/io/varuint7.c27
-rw-r--r--src/load.c21
-rw-r--r--src/log/CMakeLists.txt10
-rw-r--r--src/log/log.c23
-rw-r--r--src/op/CMakeLists.txt45
-rw-r--r--src/op/block.c17
-rw-r--r--src/op/br.c36
-rw-r--r--src/op/br_if.c95
-rw-r--r--src/op/call.c50
-rw-r--r--src/op/call_indirect.c64
-rw-r--r--src/op/check/CMakeLists.txt24
-rw-r--r--src/op/check/block.c43
-rw-r--r--src/op/check/br_table.c78
-rw-r--r--src/op/check/call.c40
-rw-r--r--src/op/check/end.c36
-rw-r--r--src/op/check/global_index.c40
-rw-r--r--src/op/check/i64_const.c32
-rw-r--r--src/op/check/local_index.c43
-rw-r--r--src/op/check/memory_immediate.c47
-rw-r--r--src/op/check/misc.c34
-rw-r--r--src/op/check/no_immediate.c23
-rw-r--r--src/op/check/relative_depth.c40
-rw-r--r--src/op/check/uint32.c36
-rw-r--r--src/op/check/uint64.c36
-rw-r--r--src/op/check/varint32.c32
-rw-r--r--src/op/check/varuint1.c32
-rw-r--r--src/op/drop.c101
-rw-r--r--src/op/end.c67
-rw-r--r--src/op/get_global.c93
-rw-r--r--src/op/get_local.c150
-rw-r--r--src/op/i32_add.c23
-rw-r--r--src/op/i32_and.c23
-rw-r--r--src/op/i32_const.c58
-rw-r--r--src/op/i32_eq.c23
-rw-r--r--src/op/i32_eqz.c22
-rw-r--r--src/op/i32_ge_s.c23
-rw-r--r--src/op/i32_ge_u.c23
-rw-r--r--src/op/i32_load.c89
-rw-r--r--src/op/i32_load8_u.c89
-rw-r--r--src/op/i32_lt_s.c23
-rw-r--r--src/op/i32_mul.c23
-rw-r--r--src/op/i32_ne.c23
-rw-r--r--src/op/i32_or.c23
-rw-r--r--src/op/i32_store.c90
-rw-r--r--src/op/i32_sub.c23
-rw-r--r--src/op/i64_const.c58
-rw-r--r--src/op/i64_store.c87
-rw-r--r--src/op/loop.c16
-rw-r--r--src/op/nop.c17
-rw-r--r--src/op/return.c19
-rw-r--r--src/op/set_global.c122
-rw-r--r--src/op/set_local.c24
-rw-r--r--src/op/tee_local.c64
-rw-r--r--src/op/tostr.c278
-rw-r--r--src/op/unreachable.c38
-rw-r--r--src/rexc.c15
-rw-r--r--src/routines/CMakeLists.txt32
-rw-r--r--src/routines/arithm.c119
-rw-r--r--src/routines/break.c243
-rw-r--r--src/routines/call.c17
-rw-r--r--src/routines/call_function.c242
-rw-r--r--src/routines/call_import.c477
-rw-r--r--src/routines/call_indirect.c48
-rw-r--r--src/routines/check_magic.c48
-rw-r--r--src/routines/check_version.c48
-rw-r--r--src/routines/find_function.c100
-rw-r--r--src/routines/find_local.c125
-rw-r--r--src/routines/find_param.c104
-rw-r--r--src/routines/get_function_type.c331
-rw-r--r--src/routines/get_import_type.c123
-rw-r--r--src/routines/i32_arithm.c39
-rw-r--r--src/routines/init_data.c196
-rw-r--r--src/routines/init_globals.c181
-rw-r--r--src/routines/mem_imm.c51
-rw-r--r--src/routines/section.c105
-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
-rw-r--r--src/routines/set_local.c162
-rw-r--r--src/routines/start_block.c49
-rw-r--r--src/routines/unary.c86
-rw-r--r--src/routines/unwind.c391
-rw-r--r--src/run.c16
-rw-r--r--src/start.c81
-rw-r--r--src/types/CMakeLists.txt11
-rw-r--r--src/types/get.c42
-rw-r--r--src/types/sz.c29
186 files changed, 14162 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bdcf7af
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.cache/
+*build*/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..37a4912
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,67 @@
+# 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/.
+
+cmake_minimum_required(VERSION 3.19)
+project(nanowasm LANGUAGES C VERSION 0.0.0)
+option(NW_EXAMPLES "Build examples")
+option(NW_LOG "Enables logging to stderr")
+option(NW_LOG_CUSTOM "Allows user code to define nwp_log. Enables NW_LOG")
+option(NW_CHECK_CODE "Check opcodes inside code section (slow).")
+add_library(${PROJECT_NAME})
+target_include_directories(${PROJECT_NAME} PUBLIC include
+ PRIVATE private_include)
+
+set(compilers
+ "GNU"
+ "Clang"
+ "TinyCC"
+)
+
+if(NW_LOG_CUSTOM)
+ set(NW_LOG ON)
+endif()
+
+foreach(c ${compilers})
+ if(CMAKE_C_COMPILER_ID STREQUAL ${c})
+ set(cflags_np
+ -pedantic
+ -Wall
+ )
+
+ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(cflags_np ${cflags_np} -Og -g)
+ else()
+ set(cflags_np ${cflags_np} -Os)
+ endif()
+
+ break()
+ endif()
+endforeach()
+
+include(CheckCompilerFlag)
+
+foreach(f ${cflags_np})
+ string(REPLACE "-" "_" var supported_${f})
+ check_compiler_flag(C ${f} ${var})
+
+ if(${var})
+ set(sup_cflags ${sup_cflags} ${f})
+ endif()
+endforeach()
+
+if(NW_LOG)
+ target_compile_definitions(${PROJECT_NAME} PRIVATE NW_LOG)
+endif()
+
+if(NW_EXAMPLES)
+ add_subdirectory(examples)
+endif()
+
+target_compile_options(${PROJECT_NAME} PRIVATE ${sup_cflags})
+set_target_properties(${PROJECT_NAME} PROPERTIES C_STANDARD 90 C_EXTENSIONS OFF)
+add_subdirectory(src)
+install(TARGETS ${PROJECT_NAME})
diff --git a/COMPARE.md b/COMPARE.md
new file mode 100644
index 0000000..635f6ff
--- /dev/null
+++ b/COMPARE.md
@@ -0,0 +1,261 @@
+# Comparing WebAssembly/Wasm interpreters
+
+This document provides a more detailed explanation behind the
+[comparison chart](README.md#comparison-chart).
+
+- **Note:** `N/A` means no measurements have been made yet.
+
+## Asynchronous interface
+
+As of the time of this writing, there is no known asynchronous WebAssembly
+interpreter other than `nanowasm`. All other interpreters implement a
+function that will run the WebAssembly application until it either
+finishes or traps (e.g.: `wasm_application_execute_main` in
+`wasm-micro-runtime`).
+
+In the scope of `nanowasm`, it was deemed interesting to design it as an
+asynchronous library for several reasons:
+
+- Running several module instances synchronously requires OS-level threading,
+which might not be desirable or even feasible to implement under some
+resource-constrained environments.
+- An asynchronous interface allows hosts to stop the execution for a module
+instance as easily as stop calling `nw_run`. Otherwise, interpreters must
+provide an `terminate`-like interface that must be called from a separate
+context (e.g.: `wasm_runtime_terminate` in `wasm-micro-runtime`).
+- The reasons above require a synchronous interpreter to be aware of the
+underlying platform, as it must be aware of primitives such as locks in order
+to remain thread-safe. However, this restricts portability towards new, unknown
+platforms.
+
+Despite its advantages, an asynchronous interface requires a more careful
+design and, more importantly, it incurs a larger memory footprint. However,
+`nanowasm` strives to remain smaller compared to its synchronous counterparts.
+
+## I/O-agnostic
+
+### Module bytecode
+
+As of the time of this writing, all interpreters other than `nanowasm` require
+the module bytecode be memory-mapped. Typically, this requires to either:
+
+- Dump the bytecode into memory.
+- Allocate a memory-mapped file.
+
+Whereas the former is inefficient memory-wise and probably unacceptable on
+resource-constrained environments, the latter is just not possible unless
+a hardware [MMU](https://en.wikipedia.org/wiki/Memory_management_unit) is
+present.
+
+In the context of `nanowasm`, it was considered interesting not to assume
+_where_ the module bytecode comes from, and instead access it via file-like
+semantics. For example, this would allow MMU-less systems to store module
+bytecode on non-volatile memory, which is often larger and less expensive,
+albeit slower.
+
+### Memories
+
+WebAssembly defines four different memory areas:
+
+- Table memory.
+- Linear memory.
+- Global memory.
+- Stack.
+
+All interpreters other than `nanowasm` allocate these memory areas internally
+via the system heap, or a custom heap defined by the user. This raises the
+following concerns:
+
+- Some resource-constrained environments might prefer to avoid the use of
+a heap, or maybe no heap implementation is even available.
+- It forces each of these memory areas to remain contiguous. On environments
+with segmented memory, this might limit the amount of contiguous memory that
+can be allocated.
+
+In the context of `nanowasm`, MMU-less systems were considered a priority for
+its design, and therefore it was conceived so that these memory areas are
+never allocated by `nanowasm` itself. Instead, `nanowasm` provides to the
+host a series of interfaces (i.e., callbacks) to implement in order to define
+how these areas are accessed. Therefore, whether accessing those areas
+requires the use of a heap, and how it is used, is entirely up to the host
+implementation.
+
+While possibly a bit cumbersome from a first glance, this flexible design
+brings in many new possibilities. For example, it allows MMU-less systems to
+store memory pages into non-contiguous memory areas, or even store them into
+larger, non-volatile memory, similarly to how fully-fledged operating systems
+implement virtual memory.
+
+## No heap required
+
+All interpreters other than `nanowasm` would allocate many internal data
+structures, as well as arbitrarily large chunks of data in order to accomodate
+the different memory areas defined by the WebAssembly standard. Aside from the
+limitations [explained above](#memories), this means the memory required by a
+module or a module instance cannot be known at compile-time, since it depends
+on how the heap is implemented, and even the module bytecode itself.
+
+On the other hand, `nanowasm` was designed with resource-constrained
+environments in mind, where a heap implementation might be either undesired
+or just unavailable. Therefore, it had to be implemented so that the memory
+required by modules and module instances remained static. This is achieved
+efficiently by storing all data structures for all possible states into a
+`union`.
+
+This design allows hosts to allocate modules and module instances in any way,
+be it:
+
+- Automatically i.e., from the stack.
+- Statically i.e., via the `static` qualifier.
+- Dynamically i.e., from the heap.
+
+## Big-endian support
+
+Even if little-endian architectures, such as `amd64`, are arguably more popular
+as of the time of this writing, big-endian counterparts are still being
+produced and are therefore considered equally relevant by `nanowasm`.
+
+### [`wasm-micro-runtime`]
+
+Despite the fact that `wasm-micro-runtime` seems to byte-swap integers
+according to the platform endianness, no big-endian platforms are listed so far
+on its `README.md`. Also, due to its big code base, it is difficult to ensure
+whether all integer reads and write are done in an endianness-agnostic way.
+
+### [`wac`]
+
+`wac` naively compares the `\0asm` magic string as a little-endian integer.
+
+## No compiler-specific extensions
+
+All interpreters other than `nanowasm` rely extensively on system-specific
+macros and/or extensions to the C language. These might restrict their use on
+less popular compilers and/or new environments.
+
+On the other hand, `nanowasm` is written in standard ANSI C (C89/C90)
+i.e., without any language extensions, as well as no system-specific macros.
+On a broader sense, the use of macros and/or other preprocessor directives is
+restricted to a minimum on `nanowasm`, as opposed to other interpreters such
+as `wasm3`.
+
+Such reduced use of the preprocessor is considered to enhance readability,
+even if it might incur some extra boilerplate code.
+
+## Public functions
+
+### `nanowasm`
+
+The numbers were extracted from [`nw.h`](include/nanowasm/nw.h).
+
+### [`wasm-micro-runtime`]
+
+- Commit: `4e50d2191ca8f177ad03a9d80eebc44b59a932db`
+
+The numbers were extracted from `wasm_export.h`.
+
+### [`wasm3`]
+
+- Commit: `35b5e2fb53c5cbc1ff3d7e42c381cd7cfa14f308`
+
+The numbers were extracted from `wasm3.h`.
+
+## Minimal memory footprint
+
+### `nanowasm`
+
+The numbers were extracted from the `test` application built by the project
+by default, which links the `nanowasm` library.
+
+The project was built with:
+
+```
+cmake -B build
+cmake --build build
+```
+
+Then, the size for the `test` executable was obtained via:
+
+```
+$ size build/test/test
+ text data bss dec hex filename
+ 50590 3576 16 54182 d3a6 build/test/test
+```
+
+Of course, these numbers are subject to change since many opcodes are still
+not implemented in `nanowasm`.
+
+### [`wasm-micro-runtime`]
+
+- Commit: `4e50d2191ca8f177ad03a9d80eebc44b59a932db`
+
+A minimal application, namely `wamr-ex`, was written with
+`wasm-micro-runtime`'s `iwasm` library. This application:
+
+1. Dumps a `.wasm` file into memory, since `wasm-micro-runtime` requires
+module code to either reside in memory or belong to a memory-mapped file.
+2. Calls the following functions:
+ - `wasm_runtime_init`
+ - `wasm_runtime_load`
+ - `wasm_runtime_instantiate`
+ - `wasm_application_execute_main`
+ - `wasm_runtime_unload`
+ - `wasm_runtime_deinstantiate`
+
+The example was built with the default CMake flags i.e.:
+
+```
+cmake -B build
+cmake --build build
+```
+
+The executable size was the obtained via:
+
+```
+$ size build/wamr-ex
+ text data bss dec hex filename
+ 463165 9224 932 473321 738e9 build/wamr-ex
+```
+
+### [`wasm3`]
+
+- Commit: `35b5e2fb53c5cbc1ff3d7e42c381cd7cfa14f308`
+
+`wasm3` provides a sample application, also called `wasm3`, in its source tree.
+This application allows to run any `.wasm` file, along with some extra command
+line options.
+
+The project was built with the default CMake flags i.e.:
+
+```
+cmake -B build
+cmake --build build
+```
+
+The executable size was the obtained via:
+
+```
+$ size build/wasm3
+ text data bss dec hex filename
+ 531667 20332 6720 558719 8867f build/wasm3
+```
+
+## Per-module memory usage
+
+### `nanowasm`
+
+The numbers were extracted by looking up `sizeof (struct nw_mod)` via `gdb(1)`,
+from an `x86_64-linux-gnu` machine. Results might vary depending on the
+target platform.
+
+## Per-instance memory usage
+
+### `nanowasm`
+
+The numbers were extracted by looking up `sizeof (struct nw_inst)` via `gdb(1)`,
+from an `x86_64-linux-gnu` machine. Results might vary depending on the
+target platform.
+
+[`wasm-micro-runtime`]: https://github.com/bytecodealliance/wasm-micro-runtime
+[`wasm3`]: https://github.com/wasm3/wasm3
+[`wac`]: https://github.com/kanaka/wac
+[`toywasm`]: https://github.com/yamt/toywasm
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d0a1fa1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..44065a4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+# `nanowasm`, the tiny Wasm interpreter that does not block you
+
+**This project is still unfinished and is not meant for production use.**
+
+This project aims to provide the most minimal Wasm interpreter possible in
+strictly portable ANSI C (C89/C90). It can run one or more Wasm applications
+concurrently, without threading. This project requires the
+[Nanowasm extensions](https://gitea.privatedns.org/xavi/nanowasm-design/)
+to the WebAssembly language. A NanoWasm-compatible program can be built from
+a MVP WebAssembly program with [`nwc`](https://gitea.privatedns.org/xavi/nwc/).
+
+## Features
+
+- Portable **ANSI C** implementation. **Runs on bare metal environments**.
+- **No dependencies** other than an ANSI C (C89/C90) compiler.
+- Asynchronous interface that allows to **run multiple applications**
+**concurrently, without threads**.
+- **I/O-agnostic** implementation for maximum flexibility: read modules
+from a network, write the stack into a file, implement a MMU in software...
+Anything is possible!
+- Suitable for **resource-constrained devices**, such as microcontrollers.
+- **No memory allocator required**.
+- Supports both **big-endian and little-endian architectures**.
+
+### Comparison chart
+
+| Feature | `nanowasm` | [`wasm-micro-runtime`] | [`wasm3`] | [`wac`] | [`toywasm`] |
+| ------- | ------- | ------- | ------- | ------- | ------- |
+| **Asynchronous interface** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| **I/O-agnostic** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| **No heap required** | ✅ | ❌ | ❌ | ❌ | ❌ |
+| **Big-endian support** | ✅ | ⚠️ | ✅ | ❌ | ✅ |
+| **No compiler-specific extensions** | ✅ | ❌ | ❌ | ❌ | ❌ |
+| **C dialect** | **`c89`** | `gnu99` | `c99` | `gnu99` | `c11` |
+| **Public functions** | **7** | 156 | 44 | N/A | N/A |
+| **Minimal `.text` footprint** | **49 KiB** | 452 KiB | 519 KiB | N/A | N/A |
+| **Per-module memory usage** | ~1016 bytes | N/A | N/A | N/A | N/A |
+| **Per-instance memory usage** | ~728 bytes | N/A | N/A | N/A | N/A |
+
+Also, see a [detailed explanation](COMPARE.md) behind the numbers above.
+
+## What `nanowasm` is not
+
+As opposed to other interpreters, `nanowasm` prefers lower memory usage
+rather than run-time performance. Therefore, it should not be unfairly
+compared _performance_-wise against other interpreters.
+
+## How to build
+
+Use the conventional process in CMake projects:
+
+```
+cmake -B <dir>
+cmake --build <dir>
+```
+
+## Examples
+
+- [`example/minimal.c`](examples/minimal.c) provides the essential minimum code
+required to work with `nanowasm` i.e., without any imports. This is only meant
+as a starting point for implementations, and not as a standalone program.
+
+- [`example/proc_exit.c`](examples/proc_exit.c) defines the code required to
+support the following minimal C program built with
+[`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk):
+
+```c
+int main()
+{
+ return 0;
+}
+```
+
+## License
+
+```
+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/.
+```
+
+Also, see [LICENSE](LICENSE).
+
+[`wasm-micro-runtime`]: https://github.com/bytecodealliance/wasm-micro-runtime
+[`wasm3`]: https://github.com/wasm3/wasm3
+[`wac`]: https://github.com/kanaka/wac
+[`toywasm`]: https://github.com/yamt/toywasm
diff --git a/doc/frame b/doc/frame
new file mode 100644
index 0000000..62f387c
--- /dev/null
+++ b/doc/frame
@@ -0,0 +1,30 @@
+[ previous frame ]
+[ meta local 0 ]
+[ entry 0 local 0 ] <- local_start
+[ entry 1 local 0 ]
+[ ... ]
+[ entry n local 0 ]
+[ meta local 1 ]
+[ entry 0 local 1 ]
+[ entry 1 local 1 ]
+[ ... ]
+[ entry m local 1 ]
+[ meta local j ]
+[ ... ]
+[ entry i local j ]
+[ frame 0 ] <- addr
+[ return value 0 ]
+
+[ previous frame ]
+[ entry 0 local 0 ] <- local_start
+[ entry 1 local 0 ]
+[ ... ]
+[ entry n local 0 ]
+[ entry 0 local 1 ]
+[ entry 1 local 1 ]
+[ ... ]
+[ entry m local 1 ]
+[ ... ]
+[ entry i local j ]
+[ frame 1 ] <- addr
+[ return value 0 ]
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..c182e37
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,10 @@
+function(nw_example example)
+ add_executable(nw_${example} ${example}.c)
+ target_link_libraries(nw_${example} PRIVATE ${PROJECT_NAME})
+ install(TARGETS nw_${example})
+ target_compile_options(nw_${example} PRIVATE ${sup_cflags})
+endfunction()
+
+nw_example(minimal)
+nw_example(proc_exit)
+nw_example(file)
diff --git a/examples/example.wasm b/examples/example.wasm
new file mode 100644
index 0000000..25871e0
--- /dev/null
+++ b/examples/example.wasm
Binary files differ
diff --git a/examples/file.c b/examples/file.c
new file mode 100644
index 0000000..c913830
--- /dev/null
+++ b/examples/file.c
@@ -0,0 +1,659 @@
+/*
+ * 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 <errno.h>
+#include <stddef.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct inst
+{
+ long retval;
+ size_t n_files;
+
+ struct file
+ {
+ FILE *f;
+ int fd;
+ } *files;
+
+ struct mem
+ {
+ void *p;
+ size_t n;
+ } stack, linear, global;
+};
+
+static int io_read(void *const buf, const size_t n, void *const user)
+{
+ FILE *const f = user;
+ const size_t r = fread(buf, 1, 1, f);
+
+ if (!r && ferror(f))
+ {
+ fprintf(stderr, "ferror\n");
+ return -1;
+ }
+
+ return r;
+}
+
+static enum nw_state io_seek(const long offset, void *const user)
+{
+ if (fseek(user, offset, SEEK_SET))
+ {
+ fprintf(stderr, "fseek(3): %s\n", strerror(errno));
+ return NW_FATAL;
+ }
+
+ return NW_OK;
+}
+
+static enum nw_state io_tell(long *const out, void *const user)
+{
+ const long offset = ftell(user);
+
+ if (offset < 0)
+ {
+ fprintf(stderr, "ftell(3): %s\n", strerror(errno));
+ return NW_FATAL;
+ }
+
+ *out = offset;
+ return NW_OK;
+}
+
+static int io_eof(void *const user)
+{
+ FILE *const f = user;
+
+ return feof(f);
+}
+
+static int push(const void *const src, const size_t n, void *const user)
+{
+ struct inst *const inst = user;
+ struct mem *const s = &inst->stack;
+ char *const p = realloc(s->p, s->n + n);
+
+ if (!p)
+ {
+ fprintf(stderr, "realloc(3): %s\n", strerror(errno));
+ return -1;
+ }
+
+ fprintf(stderr, "pushed %zu bytes: {", n);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ memcpy(&p[s->n], src, n);
+ s->p = p;
+ s->n += n;
+ fprintf(stderr, "}, stack size=%zu\n", s->n);
+ return n;
+}
+
+static int pop(void *const dst, const size_t n, void *const user)
+{
+ struct inst *const inst = user;
+ struct mem *const s = &inst->stack;
+
+ if (s->n < n)
+ {
+ fprintf(stderr, "stack underflow\n");
+ return -1;
+ }
+
+ const char *const src = (const char *)s->p + (s->n - n);
+
+ fprintf(stderr, "popped %zu bytes: {", n);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ if (dst)
+ memcpy(dst, src, n);
+
+ if (s->n == n)
+ {
+ free(s->p);
+ s->p = NULL;
+ }
+ else
+ {
+ char *const p = realloc(s->p, s->n - n);
+
+ if (!p)
+ {
+ fprintf(stderr, "realloc(3): %s\n", strerror(errno));
+ return -1;
+ }
+
+ s->p = p;
+ }
+
+ s->n -= n;
+ fprintf(stderr, "}, stack size=%zu\n", s->n);
+ return n;
+}
+
+static size_t ptr(void *const user)
+{
+ return ((const struct inst *)user)->stack.n;
+}
+
+static int load(const nw_varuint32 offset, void *const dst, const size_t n,
+ const struct mem *const m)
+{
+ if (n > m->n || offset > m->n - n)
+ {
+ fprintf(stderr, "out-of-bounds access, offset=%lu, n=%zu\n",
+ (unsigned long)offset, (unsigned long)n);
+ return -1;
+ }
+
+ fprintf(stderr, "loaded %zu bytes from offset %lu: {", (unsigned long)n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy(dst, (const char *)m->p + offset, n);
+ return n;
+}
+
+static int store(const nw_varuint32 offset, const void *const src,
+ const size_t n, const struct mem *const m)
+{
+ if (n > m->n || offset > m->n - n)
+ {
+ fprintf(stderr, "out-of-bounds access, offset=%lu, n=%lu\n",
+ (unsigned long)offset, (unsigned long)n);
+ return -1;
+ }
+
+ fprintf(stderr, "stored %lu bytes into offset %lu: {", (unsigned long)n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy((char *)m->p + offset, src, n);
+ return n;
+}
+
+static int stread(const size_t offset, void *const dst, const size_t n,
+ void *const user)
+{
+ const struct inst *const inst = user;
+
+ return load(offset, dst, n, &inst->stack);
+}
+
+static int stwrite(const size_t offset, const void *const src, const size_t n,
+ void *const user)
+{
+ const struct inst *const inst = user;
+
+ return store(offset, src, n, &inst->stack);
+}
+
+static int ensure_linear(struct mem *const m, const unsigned long offset,
+ const size_t n)
+{
+ void *const p = realloc(m->p, offset + n);
+
+ if (!p)
+ {
+ fprintf(stderr, "realloc(3): %s\n", strerror(errno));
+ return -1;
+ }
+
+ m->p = p;
+ m->n = offset + n;
+ memset((char *)m->p + offset, 0, n);
+ return 0;
+}
+
+static int lnload(const unsigned long offset, void *const dst, const size_t n,
+ void *const user)
+{
+ struct inst *const i = user;
+ struct mem *const m = &i->linear;
+
+ if (offset >= m->n && ensure_linear(m, offset, n))
+ {
+ fprintf(stderr, "ensure_linear failed\n");
+ return -1;
+ }
+
+ fprintf(stderr, "loaded %lu bytes from offset %lu: {", (unsigned long)n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy(dst, (const char *)m->p + offset, n);
+ return n;
+}
+
+static int lnstore(const unsigned long offset, const void *const src,
+ const size_t n, void *const user)
+{
+ struct inst *const i = user;
+ struct mem *const m = &i->linear;
+
+ if (offset >= m->n && ensure_linear(m, offset, n))
+ {
+ fprintf(stderr, "ensure_linear failed\n");
+ return -1;
+ }
+
+ fprintf(stderr, "stored %lu bytes into offset %lu: {", (unsigned long)n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy((char *)m->p + offset, src, n);
+ return n;
+}
+
+static int glload(const unsigned long offset, void *const dst,
+ const size_t n, void *const user)
+{
+ const struct inst *const i = user;
+
+ return load(offset, dst, n, &i->global);
+}
+
+static int glstore(const unsigned long offset, const void *const src,
+ const size_t n, void *const user)
+{
+ const struct inst *const i = user;
+
+ return store(offset, src, n, &i->global);
+}
+
+static enum nw_state wasi_exit(const union nw_value *const params,
+ union nw_value *const ret, void *user)
+{
+ struct inst *const i = user;
+
+ i->retval = params->i32;
+ return NW_OK;
+}
+
+static enum nw_state wasi_fd_close(const union nw_value *const params,
+ union nw_value *const ret, void *user)
+{
+ return NW_FATAL;
+}
+
+static enum nw_state wasi_fd_fdstat_get(const union nw_value *const params,
+ union nw_value *const ret, void *user)
+{
+ return NW_FATAL;
+}
+
+static enum nw_state wasi_fd_prestat_get(const union nw_value *const params,
+ union nw_value *const ret, void *user)
+{
+ /*
+ * From wasi/phases/old/snapshot_0/docs.md:
+ *
+ * fd_prestat_get(fd: fd) -> (errno, prestat)
+ * Return a description of the given preopened file descriptor.
+ * Params
+ * fd: fd
+ * Results
+ * error: errno
+ * buf: prestat The buffer where the description is stored.
+ *
+ * Offset: 0
+ * prestat: Union
+ * Information about a pre-opened capability.
+ * Size: 8
+ * Alignment: 4
+ * Union Layout
+ * tag_size: 1
+ * tag_align: 1
+ * contents_offset: 4
+ * contents_size: 4
+ * contents_align: 4
+ */
+
+ const long fd = params[0].i32;
+ const unsigned long prestat = params[1].i32;
+ const struct file *fl = NULL;
+ struct inst *const inst = user;
+
+ fprintf(stderr, "fd=%lu, prestat=%#lx\n", fd, prestat);
+
+ for (size_t i = 0; i < inst->n_files; i++)
+ {
+ const struct file *const f = &inst->files[i];
+
+ if (fd == (unsigned long)f->fd)
+ {
+ fl = f;
+ break;
+ }
+ }
+
+ if (!fl)
+ {
+ /* __WASI_EBADF */
+ ret->i32 = 8;
+ return NW_OK;
+ }
+
+ return NW_OK;
+}
+
+static enum nw_state wasi_fd_prestat_dir_name(
+ const union nw_value *const params, union nw_value *const ret, void *user)
+{
+ return NW_FATAL;
+}
+
+static enum nw_state wasi_fd_read( const union nw_value *const params,
+ union nw_value *const ret, void *user)
+{
+ const long fd = params[0].i32;
+ const unsigned long iovec = params[1].i32, n = params[2].i32,
+ nread = params[3].i32;
+
+ fprintf(stderr, "fd=%ld, iovec=%#lx, n=%lu, nread=%#lx\n",
+ fd, iovec, n, nread);
+ return NW_FATAL;
+}
+
+static enum nw_state wasi_path_open( const union nw_value *const params,
+ union nw_value *const ret, void *user)
+{
+ return NW_FATAL;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ FILE *f = NULL;
+ struct inst ins = {0};
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "%s <wasm>\n", *argv);
+ goto end;
+ }
+
+ const char *const path = argv[1];
+
+ if (!(f = fopen(path, "rb")))
+ {
+ fprintf(stderr, "fopen(3) %s: %s\n", path, strerror(errno));
+ goto end;
+ }
+
+ static const struct nw_import imports[] =
+ {
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "proc_exit",
+ .u.function =
+ {
+ .fn = wasi_exit,
+ .signature = "(i)"
+ }
+ },
+
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "fd_close",
+ .u.function =
+ {
+ .fn = wasi_fd_close,
+ .signature = "i(i)"
+ }
+ },
+
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "fd_fdstat_get",
+ .u.function =
+ {
+ .fn = wasi_fd_fdstat_get,
+ .signature = "i(ii)"
+ }
+ },
+
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "fd_prestat_get",
+ .u.function =
+ {
+ .fn = wasi_fd_prestat_get,
+ .signature = "i(ii)"
+ }
+ },
+
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "fd_prestat_dir_name",
+ .u.function =
+ {
+ .fn = wasi_fd_prestat_dir_name,
+ .signature = "i(ii)"
+ }
+ },
+
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "fd_read",
+ .u.function =
+ {
+ .fn = wasi_fd_read,
+ .signature = "i(iiii)"
+ }
+ },
+
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "path_open",
+ .u.function =
+ {
+ .fn = wasi_path_open,
+ .signature = "i(iiiiiIIii)"
+ }
+ },
+ };
+
+ const struct nw_io_cfg io =
+ {
+ .read = io_read,
+ .seek = io_seek,
+ .tell = io_tell,
+ .eof = io_eof,
+ .user = f
+ };
+
+ enum {N_IMPORTS = sizeof imports / sizeof *imports};
+
+ const struct nw_mod_cfg cfg =
+ {
+ .imports = imports,
+ .imp_indexes = (struct nw_import_index[N_IMPORTS]){{0}},
+ .n_imports = N_IMPORTS,
+ .io = io
+ };
+
+ struct nw_mod m;
+ struct nw_mod_out mout;
+
+ nw_init(&m, &cfg);
+
+again:
+
+ switch (nw_load(&m, &mout))
+ {
+ case NW_OK:
+ break;
+
+ case NW_AGAIN:
+ goto again;
+
+ case NW_FATAL:
+ fprintf(stderr, "nw_load failed\n");
+ goto end;
+ }
+
+ if (!(ins.global.p = malloc(ins.global.n = mout.global)))
+ {
+ fprintf(stderr, "malloc(3): %s\n", strerror(errno));
+ goto end;
+ }
+
+ enum {ARGS = 10};
+ const struct nw_inst_cfg icfg =
+ {
+ .entry = "_start",
+ .interp_cfg =
+ {
+ .io = io,
+ .m = &m,
+ .user = &ins,
+ .args = (union nw_value[ARGS]){{0}},
+ .n_args = ARGS,
+ .stack =
+ {
+ .push = push,
+ .pop = pop,
+ .ptr = ptr,
+ .read = stread,
+ .write = stwrite
+ },
+
+ .linear =
+ {
+ .load = lnload,
+ .store = lnstore
+ },
+
+ .global =
+ {
+ .load = glload,
+ .store = glstore
+ }
+ }
+ };
+
+ struct nw_inst inst;
+
+ if (nw_start(&inst, &icfg))
+ {
+ fprintf(stderr, "nw_start failed\n");
+ goto end;
+ }
+
+again2:
+
+ switch (nw_run(&inst))
+ {
+ case NW_OK:
+ break;
+
+ case NW_AGAIN:
+
+ if (ins.retval)
+ {
+ ret = ins.retval;
+ fprintf(stderr, "instance exited with status %d\n", ret);
+ goto end;
+ }
+
+ goto again2;
+
+ case NW_FATAL:
+ fprintf(stderr, "nw_run failed: %s\n", nw_rexc(&inst));
+ goto end;
+ }
+
+ ret = EXIT_SUCCESS;
+
+end:
+
+ for (size_t i = 0; i < ins.n_files; i++)
+ {
+ const struct file *const f = &ins.files[i];
+
+ if (fclose(f->f))
+ {
+ fprintf(stderr, "fclose(3) [%lu]: %s\n", (unsigned long)i,
+ strerror(errno));
+ ret = EXIT_FAILURE;
+ }
+ }
+
+ if (f && fclose(f))
+ {
+ fprintf(stderr, "fclose(3) %s: %s\n", path, strerror(errno));
+ goto end;
+ }
+
+ free(ins.files);
+ free(ins.stack.p);
+ free(ins.linear.p);
+ free(ins.global.p);
+ return ret;
+}
diff --git a/examples/minimal.c b/examples/minimal.c
new file mode 100644
index 0000000..8812ac5
--- /dev/null
+++ b/examples/minimal.c
@@ -0,0 +1,136 @@
+/*
+ * 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 <stddef.h>
+#include <stdlib.h>
+
+static int io_read(void *const buf, const size_t n, void *const user)
+{
+ return -1;
+}
+
+static enum nw_state io_seek(const long offset, void *const user)
+{
+ return -1;
+}
+
+static enum nw_state io_tell(long *const out, void *const user)
+{
+ return -1;
+}
+
+static int io_eof(void *const user)
+{
+ return -1;
+}
+
+static int push(const void *const src, const size_t n, void *const user)
+{
+ return -1;
+}
+
+static int pop(void *const dst, const size_t n, void *const user)
+{
+ return -1;
+}
+
+static size_t ptr(void *const user)
+{
+ return 0;
+}
+
+static int load(const nw_varuint32 offset, void *const dst, const size_t n,
+ void *const user)
+{
+ return -1;
+}
+
+static int store(const nw_varuint32 offset, const void *const src,
+ const size_t n, void *const user)
+{
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ const struct nw_io_cfg io =
+ {
+ .read = io_read,
+ .seek = io_seek,
+ .tell = io_tell,
+ .eof = io_eof
+ };
+
+ const struct nw_mod_cfg cfg =
+ {
+ .io = io
+ };
+
+ struct nw_mod m;
+ struct nw_mod_out mout;
+
+ nw_init(&m, &cfg);
+
+again:
+
+ switch (nw_load(&m, &mout))
+ {
+ case NW_OK:
+ break;
+
+ case NW_AGAIN:
+ goto again;
+
+ case NW_FATAL:
+ return EXIT_FAILURE;
+ }
+
+ const struct nw_inst_cfg icfg =
+ {
+ .interp_cfg =
+ {
+ .io = io,
+ .m = &m,
+ .stack =
+ {
+ .push = push,
+ .pop = pop,
+ .ptr = ptr
+ },
+
+ .linear =
+ {
+ .load = load,
+ .store = store
+ }
+ }
+ };
+
+ struct nw_inst inst;
+
+ if (nw_start(&inst, &icfg))
+ return EXIT_FAILURE;
+
+again2:
+
+ switch (nw_run(&inst))
+ {
+ case NW_OK:
+ break;
+
+ case NW_AGAIN:
+ goto again2;
+
+ case NW_FATAL:
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/proc_exit.c b/examples/proc_exit.c
new file mode 100644
index 0000000..d4a580e
--- /dev/null
+++ b/examples/proc_exit.c
@@ -0,0 +1,484 @@
+/*
+ * 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 <errno.h>
+#include <stddef.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct inst
+{
+ long retval;
+
+ struct mem
+ {
+ void *p;
+ size_t n;
+ } stack, linear, global;
+};
+
+static int io_read(void *const buf, const size_t n, void *const user)
+{
+ FILE *const f = user;
+ const size_t r = fread(buf, 1, 1, f);
+
+ if (!r && ferror(f))
+ {
+ fprintf(stderr, "%s: ferror\n", __func__);
+ return -1;
+ }
+
+ return r;
+}
+
+static enum nw_state io_seek(const long offset, void *const user)
+{
+ if (fseek(user, offset, SEEK_SET))
+ {
+ fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno));
+ return NW_FATAL;
+ }
+
+ return NW_OK;
+}
+
+static enum nw_state io_tell(long *const out, void *const user)
+{
+ const long offset = ftell(user);
+
+ if (offset < 0)
+ {
+ fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno));
+ return NW_FATAL;
+ }
+
+ *out = offset;
+ return NW_OK;
+}
+
+static int io_eof(void *const user)
+{
+ FILE *const f = user;
+
+ return feof(f);
+}
+
+static int push(const void *const src, const size_t n, void *const user)
+{
+ struct inst *const inst = user;
+ struct mem *const s = &inst->stack;
+ char *const p = realloc(s->p, s->n + n);
+
+ if (!p)
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ fprintf(stderr, "pushed %zu bytes: {", n);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ memcpy(&p[s->n], src, n);
+ s->p = p;
+ s->n += n;
+ fprintf(stderr, "}, stack size=%zu\n", s->n);
+ return n;
+}
+
+static int pop(void *const dst, const size_t n, void *const user)
+{
+ struct inst *const inst = user;
+ struct mem *const s = &inst->stack;
+
+ if (s->n < n)
+ {
+ fprintf(stderr, "%s: stack underflow\n", __func__);
+ return -1;
+ }
+
+ const char *const src = (const char *)s->p + (s->n - n);
+
+ fprintf(stderr, "popped %zu bytes: {", n);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ memcpy(dst, src, n);
+
+ if (s->n == n)
+ {
+ free(s->p);
+ s->p = NULL;
+ }
+ else
+ {
+ char *const p = realloc(s->p, s->n - n);
+
+ if (!p)
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ s->p = p;
+ }
+
+ s->n -= n;
+ fprintf(stderr, "}, stack size=%zu\n", s->n);
+ return n;
+}
+
+static size_t ptr(void *const user)
+{
+ return ((const struct inst *)user)->stack.n;
+}
+
+static int load(const nw_varuint32 offset, void *const dst, const size_t n,
+ const struct mem *const m)
+{
+ if (n > m->n || offset > m->n - n)
+ {
+ fprintf(stderr, "%s: out-of-bounds access, offset=%lu, n=%zu\n",
+ __func__, (unsigned long)offset, n);
+ return -1;
+ }
+
+ fprintf(stderr, "%s: loaded %zu bytes from offset %lu: {", __func__, n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy(dst, (const char *)m->p + offset, n);
+ return n;
+}
+
+static int store(const nw_varuint32 offset, const void *const src,
+ const size_t n, const struct mem *const m)
+{
+ if (n > m->n || offset > m->n - n)
+ {
+ fprintf(stderr, "%s: out-of-bounds access, offset=%lu, n=%zu\n",
+ __func__, (unsigned long)offset, n);
+ return -1;
+ }
+
+ fprintf(stderr, "%s: stored %zu bytes into offset %lu: {", __func__, n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy((char *)m->p + offset, src, n);
+ return n;
+}
+
+static int stread(const size_t offset, void *const dst, const size_t n,
+ void *const user)
+{
+ const struct inst *const inst = user;
+
+ return load(offset, dst, n, &inst->stack);
+}
+
+static int stwrite(const size_t offset, const void *const src, const size_t n,
+ void *const user)
+{
+ const struct inst *const inst = user;
+
+ return store(offset, src, n, &inst->stack);
+}
+
+static int ensure_linear(struct mem *const m, const unsigned long offset,
+ const size_t n)
+{
+ void *const p = realloc(m->p, offset + n);
+
+ if (!p)
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ m->p = p;
+ m->n = offset + n;
+ memset((char *)m->p + offset, 0, n);
+ return 0;
+}
+
+static int lnload(const unsigned long offset, void *const dst, const size_t n,
+ void *const user)
+{
+ struct inst *const i = user;
+ struct mem *const m = &i->linear;
+
+ if (offset >= m->n && ensure_linear(m, offset, n))
+ {
+ fprintf(stderr, "%s: ensure_linear failed\n", __func__);
+ return -1;
+ }
+
+ fprintf(stderr, "%s: loaded %zu bytes from offset %lu: {", __func__, n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy(dst, (const char *)m->p + offset, n);
+ return n;
+}
+
+static int lnstore(const unsigned long offset, const void *const src,
+ const size_t n, void *const user)
+{
+ struct inst *const i = user;
+ struct mem *const m = &i->linear;
+
+ if (offset >= m->n && ensure_linear(m, offset, n))
+ {
+ fprintf(stderr, "%s: ensure_linear failed\n", __func__);
+ return -1;
+ }
+
+ fprintf(stderr, "%s: stored %zu bytes into offset %lu: {", __func__, n,
+ (unsigned long)offset);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ fprintf(stderr, "%hhu", ((const char *)src)[i]);
+
+ if (i + 1 < n)
+ fputs(", ", stderr);
+ }
+
+ fputs("}\n", stderr);
+ memcpy((char *)m->p + offset, src, n);
+ return n;
+}
+
+static int glload(const unsigned long offset, void *const dst,
+ const size_t n, void *const user)
+{
+ const struct inst *const i = user;
+
+ return load(offset, dst, n, &i->global);
+}
+
+static int glstore(const unsigned long offset, const void *const src,
+ const size_t n, void *const user)
+{
+ const struct inst *const i = user;
+
+ return store(offset, src, n, &i->global);
+}
+
+static enum nw_state wasi_exit(const union nw_value *const params,
+ union nw_value *const ret, void *user, struct nw_next *const next)
+{
+ struct inst *const i = user;
+
+ i->retval = params->i32;
+ return NW_OK;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ FILE *f = NULL;
+ struct inst ins = {0};
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "%s <wasm>\n", *argv);
+ goto end;
+ }
+
+ const char *const path = argv[1];
+
+ if (!(f = fopen(path, "rb")))
+ {
+ fprintf(stderr, "%s: fopen(3) %s: %s\n", __func__, path,
+ strerror(errno));
+ goto end;
+ }
+
+ static const struct nw_import imports[] =
+ {
+ {
+ .kind = NW_KIND_FUNCTION,
+ .module = "wasi_snapshot_preview1",
+ .field = "proc_exit",
+ .u.function =
+ {
+ .fn = wasi_exit,
+ .signature = "(i)"
+ }
+ }
+ };
+
+ const struct nw_io_cfg io =
+ {
+ .read = io_read,
+ .seek = io_seek,
+ .tell = io_tell,
+ .eof = io_eof,
+ .user = f
+ };
+
+ enum {N_IMPORTS = sizeof imports / sizeof *imports};
+
+ const struct nw_mod_cfg cfg =
+ {
+ .imports = imports,
+ .imp_indexes = (struct nw_import_index[N_IMPORTS]){{0}},
+ .n_imports = N_IMPORTS,
+ .io = io
+ };
+
+ struct nw_mod m;
+ struct nw_mod_out mout;
+
+ nw_init(&m, &cfg);
+
+again:
+
+ switch (nw_load(&m, &mout))
+ {
+ case NW_OK:
+ break;
+
+ case NW_AGAIN:
+ goto again;
+
+ case NW_FATAL:
+ fprintf(stderr, "%s: nw_load failed\n", __func__);
+ goto end;
+ }
+
+ if (!(ins.global.p = malloc(ins.global.n = mout.global)))
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ goto end;
+ }
+
+ enum {ARGS = 1};
+ const struct nw_inst_cfg icfg =
+ {
+ .entry = "_start",
+ .interp_cfg =
+ {
+ .io = io,
+ .m = &m,
+ .user = &ins,
+ .args = (union nw_value[ARGS]){{0}},
+ .n_args = ARGS,
+ .stack =
+ {
+ .push = push,
+ .pop = pop,
+ .ptr = ptr,
+ .read = stread,
+ .write = stwrite
+ },
+
+ .linear =
+ {
+ .load = lnload,
+ .store = lnstore
+ },
+
+ .global =
+ {
+ .load = glload,
+ .store = glstore
+ }
+ }
+ };
+
+ struct nw_inst inst;
+
+ if (nw_start(&inst, &icfg))
+ {
+ fprintf(stderr, "%s: nw_start failed\n", __func__);
+ goto end;
+ }
+
+again2:
+
+ switch (nw_run(&inst))
+ {
+ case NW_OK:
+ break;
+
+ case NW_AGAIN:
+
+ if (ins.retval)
+ {
+ ret = ins.retval;
+ fprintf(stderr, "instance exited with status %d\n", ret);
+ goto end;
+ }
+
+ goto again2;
+
+ case NW_FATAL:
+ fprintf(stderr, "%s: nw_run failed: %s\n", __func__,
+ nw_rexc(&inst));
+ goto end;
+ }
+
+ ret = EXIT_SUCCESS;
+
+end:
+
+ if (f && fclose(f))
+ {
+ fprintf(stderr, "%s: fclose(3) %s: %s\n", __func__, path,
+ strerror(errno));
+ goto end;
+ }
+
+ free(ins.stack.p);
+ free(ins.linear.p);
+ free(ins.global.p);
+ return ret;
+}
diff --git a/include/nanowasm/dbg.h b/include/nanowasm/dbg.h
new file mode 100644
index 0000000..010a5c8
--- /dev/null
+++ b/include/nanowasm/dbg.h
@@ -0,0 +1,33 @@
+/*
+ * 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/.
+ */
+
+#ifndef NW_DBG_H
+#define NW_DBG_H
+
+#include <nanowasm/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void nw_dbg_init(struct nw_dbg *d, const struct nw_dbg_cfg *cfg);
+void nw_dbg_local(struct nw_dbg *d, unsigned long index);
+void nw_dbg_param(struct nw_dbg *d, unsigned long index);
+void nw_dbg_global(struct nw_dbg *d, unsigned long index);
+void nw_dbg_mem_load(struct nw_dbg *d, enum nw_type t, unsigned long offset);
+void nw_dbg_bt(struct nw_dbg *d, unsigned depth);
+int nw_dbg_value(const struct nw_dbg *d, struct nw_dbg_value *v);
+int nw_dbg_pc(const struct nw_dbg *d, long *pc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/nanowasm/linear.h b/include/nanowasm/linear.h
new file mode 100644
index 0000000..71abf1c
--- /dev/null
+++ b/include/nanowasm/linear.h
@@ -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/.
+ */
+
+#ifndef NW_LINEAR_H
+#define NW_LINEAR_H
+
+#include <nanowasm/types.h>
+
+enum nw_state nw_linear_load(struct nw_inst *i, struct nw_sm_io *io,
+ unsigned long offset);
+enum nw_state nw_linear_store(struct nw_inst *i, struct nw_sm_io *io,
+ unsigned long offset);
+
+#endif
diff --git a/include/nanowasm/nw.h b/include/nanowasm/nw.h
new file mode 100644
index 0000000..093fa21
--- /dev/null
+++ b/include/nanowasm/nw.h
@@ -0,0 +1,32 @@
+/*
+ * 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/.
+ */
+
+#ifndef NW_H
+#define NW_H
+
+#include <nanowasm/types.h>
+#include <nanowasm/linear.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void nw_init(struct nw_mod *m, const struct nw_mod_cfg *cfg);
+enum nw_state nw_load(struct nw_mod *m, struct nw_mod_out *out);
+int nw_start(struct nw_inst *i, const struct nw_inst_cfg *icfg);
+enum nw_state nw_run(struct nw_inst *i);
+const char *nw_lexc(const struct nw_mod *m);
+const char *nw_rexc(const struct nw_inst *i);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/nanowasm/private.h b/include/nanowasm/private.h
new file mode 100644
index 0000000..60fe383
--- /dev/null
+++ b/include/nanowasm/private.h
@@ -0,0 +1,675 @@
+/*
+ * 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/.
+ */
+
+#ifndef NANOWASM_PRIVATE_H
+#define NANOWASM_PRIVATE_H
+
+#if !defined(NW_H) && !defined(NW_TYPES_H)
+#error Do not #include <nanowasm/private.h> directly. \
+ Please either #include <nanowasm/nw.h> or <nanowasm/types.h>
+#endif
+
+#include <stddef.h>
+
+typedef signed char nw_varint1;
+typedef unsigned char nw_varuint1;
+typedef signed char nw_varint7;
+typedef unsigned char nw_varuint7;
+typedef unsigned long nw_varuint32;
+typedef long nw_varint32;
+typedef struct nw_ull nw_varuint64;
+typedef struct nw_ll nw_varint64;
+
+struct nw_interp;
+
+enum
+{
+ NW_SECTION_CUSTOM,
+ NW_SECTION_TYPE,
+ NW_SECTION_IMPORT,
+ NW_SECTION_FUNCTION,
+ NW_SECTION_TABLE,
+ NW_SECTION_MEMORY,
+ NW_SECTION_GLOBAL,
+ NW_SECTION_EXPORT,
+ NW_SECTION_START,
+ NW_SECTION_ELEMENT,
+ NW_SECTION_CODE,
+ NW_SECTION_DATA,
+ NW_SECTION_DATA_COUNT,
+
+ NW_SECTIONS
+};
+
+enum
+{
+ NW_CUSTOM_TO,
+ NW_CUSTOM_FTI,
+ NW_CUSTOM_FBO,
+ NW_CUSTOM_LO,
+ NW_CUSTOM_ITI,
+
+ NW_CUSTOM_SECTIONS
+};
+
+struct nw_sm_leb128
+{
+ unsigned shift, bcnt;
+ struct nw_ull result;
+};
+
+struct nw_leuint32
+{
+ unsigned char v[4];
+};
+
+struct nw_leuint64
+{
+ unsigned char v[8];
+};
+
+struct nw_global
+{
+ nw_varuint1 mutability;
+ enum nw_type type;
+ union nw_value value;
+};
+
+struct nw_table
+{
+ unsigned long initial, max;
+ unsigned char data;
+};
+
+struct nw_return
+{
+ nw_varuint1 count;
+ enum nw_type type;
+};
+
+struct nw_fn
+{
+ nw_varuint32 index, param_count;
+ long param_types;
+ struct nw_return ret;
+};
+
+struct nw_frame
+{
+ nw_varuint32 local_count, body_size, block_i;
+ struct nw_fn fn;
+ struct nw_return prev_ret;
+ int child;
+ unsigned char prev_op;
+ size_t fr_start, local_start, local_end;
+ long start, pc;
+};
+
+struct nw_local_meta
+{
+ nw_varuint32 entry_count;
+ enum nw_type type;
+};
+
+struct nw_find_local
+{
+ nw_varuint32 index, entry_i;
+ struct nw_sm_io io;
+ struct nw_local_meta meta;
+ size_t addr;
+ enum nw_state (*next)(struct nw_interp *);
+};
+
+struct nw_find_param
+{
+ const struct nw_fn *fn;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ long pc;
+ nw_varuint32 index, param_i;
+ size_t sz;
+ enum nw_state (*next)(struct nw_interp *);
+
+ struct nw_find_param_out
+ {
+ enum nw_type type;
+ size_t addr;
+ } out;
+};
+
+struct nw_get_import_type
+{
+ nw_varuint32 index;
+ struct nw_sm_io io;
+ struct nw_leuint32 value;
+ unsigned char kind;
+ enum nw_state (*next)(struct nw_interp *);
+
+ struct nw_get_import_type_out
+ {
+ enum nw_kind kind;
+ } out;
+};
+
+union nw_i_sm
+{
+ struct nw_i_sm_b
+ {
+ unsigned char op;
+ struct nw_sm_io io;
+ struct nw_next next;
+ long pc;
+ void (*f)(struct nw_interp *);
+ } bytecode;
+
+ struct nw_i_sm_unreachable
+ {
+ long offset;
+ } unreachable;
+
+ struct nw_i_sm_i32_const
+ {
+ nw_varint32 value;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ } i32_const;
+
+ struct nw_i_sm_i64_const
+ {
+ nw_varuint64 value;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ } i64_const;
+
+ struct nw_i_sm_exp
+ {
+ enum nw_kind kind;
+ struct nw_sm_leb128 leb128;
+ nw_varuint32 count, entry_i, len, len_i;
+ const char *sym;
+ nw_varuint32 index;
+ long offset;
+ enum nw_state (*next)(struct nw_interp *);
+ } export;
+
+ struct nw_i_sm_type
+ {
+ long pc;
+ struct nw_sm_io io;
+ struct nw_sm_leb128 leb128;
+ nw_varuint32 param_i, fn_index;
+ struct nw_leuint32 fti, to;
+ struct nw_get_import_type git;
+ enum nw_state (*next)(struct nw_interp *);
+ struct nw_fn out;
+ } type;
+
+ struct nw_i_sm_ffn
+ {
+ void (*next)(struct nw_interp *);
+ struct nw_sm_io io;
+ struct nw_fn fn;
+ struct nw_leuint32 fbo;
+ } ffn;
+
+ struct nw_i_sm_pl
+ {
+ long body_start;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ struct nw_frame fr;
+ nw_varuint32 param_i, local_i, local_count, entry_i;
+ union nw_value value;
+ enum nw_type type;
+ struct nw_local_meta meta;
+ } pl;
+
+ struct nw_i_sm_sb
+ {
+ void (*next)(struct nw_interp *);
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ } start_block;
+
+ struct nw_i_sm_imm
+ {
+ void (*next)(struct nw_interp *);
+ struct nw_sm_leb128 leb128;
+
+ struct nw_i_sm_imm_out
+ {
+ nw_varuint32 flags, offset;
+ } out;
+ } imm;
+
+ struct nw_i_sm_load
+ {
+ unsigned long addr;
+ struct nw_sm_io io;
+ struct nw_i_sm_imm_out imm;
+
+ union
+ {
+ char i8;
+ long i32;
+ struct nw_ll i64;
+ float f32;
+ double f64;
+ struct nw_leuint32 v32;
+ } value;
+ } load;
+
+ struct nw_i_sm_store
+ {
+ unsigned long addr;
+ struct nw_sm_io io;
+ struct nw_i_sm_imm_out imm;
+
+ union
+ {
+ long i32;
+ struct nw_ull i64;
+ float f32;
+ double f64;
+ struct nw_leuint32 v32;
+ struct nw_leuint64 v64;
+ } value;
+ } store;
+
+ struct nw_i_sm_br
+ {
+ nw_varuint32 relative_depth;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ } br;
+
+ struct nw_i_sm_br_if
+ {
+ nw_varuint32 relative_depth;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ union nw_value condition;
+ } br_if;
+
+ struct nw_i_sm_call
+ {
+ nw_varuint32 index;
+ struct nw_sm_leb128 leb128;
+ } call;
+
+ struct nw_i_sm_call_indirect
+ {
+ nw_varuint32 index, value;
+ struct nw_sm_leb128 leb128;
+ } call_indirect;
+
+ struct nw_i_sm_end
+ {
+ long offset;
+ struct nw_sm_leb128 leb128;
+ } end;
+
+ struct nw_i_sm_unwind
+ {
+ nw_varuint32 pending, entry_i;
+ size_t sz;
+ struct nw_sm_io io;
+ struct nw_sm_leb128 leb128;
+ struct nw_frame fr;
+ union nw_value retval, value;
+ struct nw_find_local fl;
+ } unwind;
+
+ struct nw_i_sm_set_global
+ {
+ nw_varuint32 index;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ struct nw_global gl;
+ } set_global;
+
+ struct nw_i_sm_get_global
+ {
+ nw_varuint32 index;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ struct nw_global gl;
+ } get_global;
+
+ struct nw_i_sm_set_local
+ {
+ nw_varuint32 index;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ enum nw_state (*next)(struct nw_interp *);
+
+ union
+ {
+ struct nw_find_local l;
+ struct nw_find_param p;
+ } f;
+
+ struct nw_i_sm_set_local_out
+ {
+ union nw_value value;
+ enum nw_type type;
+ } out;
+ } set_local;
+
+ struct nw_i_sm_tee_local
+ {
+ struct nw_sm_io io;
+ struct nw_i_sm_set_local_out in;
+ } tee_local;
+
+ struct nw_i_sm_get_local
+ {
+ nw_varuint32 index;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+
+ union
+ {
+ struct nw_find_local l;
+ struct nw_find_param p;
+ } f;
+
+ union nw_value value;
+ enum nw_type type;
+ } get_local;
+
+ struct nw_i_sm_break
+ {
+ nw_varuint32 relative_depth, label_i;
+ struct nw_sm_io io;
+ long pc, lo;
+ struct nw_leuint32 offset, lpc, dst, n;
+ } brk;
+
+ struct nw_i_sm_arithm
+ {
+ enum nw_type type;
+ struct nw_sm_io io;
+ union nw_value value;
+
+ struct nw_i_sm_arithm_out
+ {
+ union nw_value left, right;
+ } out;
+
+ int (*op)(const struct nw_i_sm_arithm_out *, union nw_value *);
+ } arithm;
+
+ struct nw_i_sm_unary
+ {
+ enum nw_type type;
+ struct nw_sm_io io;
+ union nw_value in, out;
+ int (*op)(const union nw_value *, union nw_value *);
+ } unary;
+
+ struct nw_i_sm_drop
+ {
+ struct nw_sm_io io;
+ union nw_value value;
+ } drop;
+
+ struct nw_i_sm_call_import
+ {
+ enum nw_type type;
+ nw_varuint32 type_i, param_i;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ struct nw_fn fn;
+ struct nw_next next;
+ union nw_value value;
+ long pc;
+ size_t addr, sz;
+ const struct nw_import *imp;
+ } call_import;
+};
+
+struct nw_interp
+{
+ const void *set;
+ const char *exception;
+ int exit;
+ int retval;
+ struct nw_interp_cfg cfg;
+ enum nw_state (*next)(struct nw_interp *);
+ struct nw_frame fr;
+ union nw_i_sm sm;
+ void *state, *args;
+ enum nw_type push_type;
+
+ struct
+ {
+ nw_varuint32 n_pages;
+ } linear, table;
+};
+
+union nw_sm
+{
+ struct nw_sm_cm
+ {
+ unsigned char buf[sizeof "\0asm" - 1];
+ struct nw_sm_io io;
+ } check_magic;
+
+ struct nw_sm_cv
+ {
+ unsigned char version[sizeof (struct nw_leuint32)];
+ struct nw_sm_io io;
+ } check_version;
+
+ struct nw_sm_st
+ {
+ nw_varuint32 entry_i, param_count, p_i;
+ struct nw_sm_leb128 leb128;
+ } type;
+
+ struct nw_sm_imp
+ {
+ long mod_off, field_off;
+ nw_varuint32 entry_i, mod_len, field_len, len_i, imp_i;
+ struct nw_sm_leb128 leb128;
+ } import;
+
+ struct nw_sm_fn
+ {
+ nw_varuint32 entry_i;
+ struct nw_sm_leb128 leb128;
+ } function;
+
+ struct nw_sm_tb
+ {
+ nw_varuint32 count, entry_i;
+ nw_varint7 elem_type;
+ nw_varuint1 flags;
+ size_t out_sz;
+ struct nw_sm_leb128 leb128;
+ } table;
+
+ struct nw_sm_mem
+ {
+ nw_varuint32 count;
+ nw_varuint1 flags;
+ struct nw_sm_leb128 leb128;
+ } memory;
+
+ struct nw_sm_gl
+ {
+ nw_varuint32 entry_i;
+ struct nw_sm_leb128 leb128;
+ struct nw_interp interp;
+ union nw_value value;
+ } global;
+
+ struct nw_sm_d
+ {
+ nw_varuint32 entry_i, index, size;
+ struct nw_sm_leb128 leb128;
+ struct nw_interp interp;
+ long value, offset;
+ } data;
+
+ struct nw_sm_dc
+ {
+ struct nw_sm_leb128 leb128;
+ } data_count;
+
+ struct nw_sm_exp
+ {
+ nw_varuint32 count, entry_i, field_len, len_i;
+ struct nw_sm_io io;
+ struct nw_sm_leb128 leb128;
+ } export;
+
+ struct nw_sm_start
+ {
+ struct nw_sm_leb128 leb128;
+ } start;
+
+ struct nw_sm_c
+ {
+ nw_varuint32 count, entry_i, body_size;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ long start, body_start, op_off;
+ unsigned long rem;
+ enum nw_state (*next)(struct nw_mod *);
+ unsigned char op;
+
+ union
+ {
+ unsigned long f32;
+ struct nw_ull f64;
+
+ struct nw_sm_c_t
+ {
+ nw_varuint32 count, i;
+ } target;
+ } u;
+
+ struct nw_sm_c_fn
+ {
+ nw_varuint32 local_count, local_i, local_total;
+ unsigned blocks;
+ } fn;
+ } code;
+
+ struct nw_sm_custom
+ {
+ nw_varuint32 name_len, len_i;
+ struct nw_sm_leb128 leb128;
+ unsigned char byte;
+ int candidate[NW_CUSTOM_SECTIONS];
+ long start;
+ } custom;
+
+ struct nw_sm_lo
+ {
+ struct nw_sm_io io;
+ } lo;
+};
+
+struct nw_mod
+{
+ nw_varuint32 type_count, data_count, global_count, import_count,
+ function_count;
+ long sections[NW_SECTIONS], c_sections[NW_CUSTOM_SECTIONS];
+ enum nw_state (*next)(struct nw_mod *);
+ struct nw_mod_cfg cfg;
+ union nw_sm sm;
+ struct nw_mod_out out;
+ const char *exception;
+
+ struct nw_mod_section
+ {
+ nw_varuint7 section;
+ nw_varuint32 len;
+ long offset, cur;
+ struct nw_sm_leb128 leb128;
+ } section;
+};
+
+struct nw_inst;
+
+union nw_inst_sm
+{
+ struct nw_inst_sm_gl
+ {
+ nw_varuint32 count, entry_i;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ struct nw_global out;
+ enum nw_state (*next)(struct nw_inst *);
+ } global;
+
+ struct nw_inst_sm_d
+ {
+ char b;
+ nw_varuint32 count, index, size, entry_i, bytes_i;
+ struct nw_sm_leb128 leb128;
+ struct nw_sm_io io;
+ struct nw_global out;
+ unsigned long offset;
+ enum nw_state (*next)(struct nw_inst *);
+ } data;
+};
+
+struct nw_inst
+{
+ const char *entry;
+ struct nw_interp interp;
+ union nw_inst_sm sm;
+ enum nw_state (*next)(struct nw_inst *);
+};
+
+union nw_dbg_sm
+{
+ struct nw_dbg_sm_local
+ {
+ struct nw_sm_io io;
+ struct nw_find_local fl;
+ enum nw_state (*inext)(struct nw_interp *);
+ } local;
+
+ struct nw_dbg_sm_param
+ {
+ struct nw_sm_io io;
+ struct nw_find_param fp;
+ enum nw_state (*inext)(struct nw_interp *);
+ } param;
+
+ struct nw_dbg_sm_global
+ {
+ struct nw_sm_io io;
+ unsigned long index;
+ struct nw_global gl;
+ enum nw_state (*inext)(struct nw_interp *);
+ } global;
+
+ struct nw_dbg_sm_mem_load
+ {
+ struct nw_sm_io io;
+ unsigned long offset;
+ enum nw_state (*inext)(struct nw_interp *);
+ } mem_load;
+};
+
+struct nw_dbg
+{
+ long pc;
+ struct nw_dbg_cfg cfg;
+ struct nw_dbg_value v;
+ union nw_dbg_sm sm;
+};
+
+#endif
diff --git a/include/nanowasm/types.h b/include/nanowasm/types.h
new file mode 100644
index 0000000..23cd4fa
--- /dev/null
+++ b/include/nanowasm/types.h
@@ -0,0 +1,180 @@
+/*
+ * 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/.
+ */
+
+#ifndef NW_TYPES_H
+#define NW_TYPES_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct nw_inst;
+
+enum nw_state
+{
+ NW_OK,
+ NW_AGAIN,
+ NW_FATAL
+};
+
+enum nw_kind
+{
+ NW_KIND_FUNCTION,
+ NW_KIND_TABLE,
+ NW_KIND_MEMORY,
+ NW_KIND_GLOBAL,
+
+ NW_KINDS
+};
+
+struct nw_ull
+{
+ unsigned long low, hi;
+};
+
+struct nw_ll
+{
+ long low, hi;
+};
+
+union nw_value
+{
+ long i32;
+ struct nw_ll i64;
+ float f32;
+ double f64;
+};
+
+enum nw_type
+{
+ NW_TYPE_I32,
+ NW_TYPE_I64,
+ NW_TYPE_F32,
+ NW_TYPE_F64
+};
+
+struct nw_args
+{
+ const union nw_value *args;
+ size_t n;
+};
+
+struct nw_next
+{
+ enum nw_state (*fn)(void *args, struct nw_next *next);
+ void *user;
+};
+
+struct nw_io_cfg
+{
+ int (*read)(void *buf, size_t n, void *user);
+ int (*eof)(void *user);
+ enum nw_state (*pc)(long offset, struct nw_next * next, void *user);
+ enum nw_state (*seek)(long offset, void *user);
+ enum nw_state (*tell)(long *offset, void *user);
+ void *user;
+};
+
+struct nw_sm_io
+{
+ void *buf;
+ size_t n, read;
+};
+
+struct nw_import
+{
+ enum nw_kind kind;
+ const char *module, *field;
+
+ union
+ {
+ struct nw_import_fn
+ {
+ const char *signature;
+ enum nw_state (*fn)(const union nw_value *params,
+ union nw_value *ret, void *user, struct nw_next *next);
+ } function;
+ } u;
+};
+
+struct nw_import_index
+{
+ unsigned long index;
+};
+
+struct nw_mod_cfg
+{
+ const struct nw_import *imports;
+ struct nw_import_index *imp_indexes;
+ struct nw_io_cfg io;
+ size_t n_imports;
+};
+
+struct nw_interp_cfg
+{
+ size_t n_args;
+ void *user;
+
+ struct nw_mem_cfg
+ {
+ int (*load)(unsigned long pos, void *src, size_t n, void *user);
+ int (*store)(unsigned long pos, const void *src, size_t n, void *user);
+ } global, linear, table;
+
+ struct nw_fifo_cfg
+ {
+ int (*push)(const void *src, size_t n, void *user);
+ int (*pop)(void *dst, size_t n, void *user);
+ int (*read)(size_t offset, void *dst, size_t n, void *user);
+ int (*write)(size_t offset, const void *dst, size_t n, void *user);
+ size_t (*ptr)(void *user);
+ } stack;
+
+ struct nw_io_cfg io;
+ const struct nw_mod *m;
+ union nw_value *args;
+};
+
+struct nw_inst_cfg
+{
+ const char *entry;
+ struct nw_interp_cfg interp_cfg;
+};
+
+struct nw_mod_out
+{
+ struct nw_mod_out_mem
+ {
+ unsigned long initial, max;
+ } linear, table;
+
+ size_t global;
+};
+
+struct nw_dbg_cfg
+{
+ struct nw_inst *inst;
+};
+
+struct nw_dbg_value
+{
+ enum nw_type type;
+ union nw_value value;
+};
+
+#include <nanowasm/private.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/private_include/nw/global.h b/private_include/nw/global.h
new file mode 100644
index 0000000..7f289d2
--- /dev/null
+++ b/private_include/nw/global.h
@@ -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/.
+ */
+
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <nanowasm/nw.h>
+
+enum nw_state nwp_global_load(struct nw_interp *i, struct nw_sm_io *io,
+ nw_varuint32 index);
+enum nw_state nwp_global_store(struct nw_interp *i, struct nw_sm_io *io,
+ nw_varuint32 index);
+
+#endif
diff --git a/private_include/nw/inst.h b/private_include/nw/inst.h
new file mode 100644
index 0000000..3a68440
--- /dev/null
+++ b/private_include/nw/inst.h
@@ -0,0 +1,17 @@
+/*
+ * 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/.
+ */
+
+#ifndef INST_H
+#define INST_H
+
+#include <nanowasm/nw.h>
+
+enum nw_state nwp_inst_run(struct nw_inst *i);
+
+#endif
diff --git a/private_include/nw/interp.h b/private_include/nw/interp.h
new file mode 100644
index 0000000..a6f0d74
--- /dev/null
+++ b/private_include/nw/interp.h
@@ -0,0 +1,39 @@
+/*
+ * 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/.
+ */
+
+#ifndef INTERP_H
+#define INTERP_H
+
+#include <nanowasm/nw.h>
+#include <nw/opcodes.h>
+#include <stddef.h>
+
+struct nwp_interp_set
+{
+ const enum opcode *opcodes;
+ size_t n;
+};
+
+struct nwp_ops
+{
+ void (*const *ops)(struct nw_interp *);
+ size_t n;
+};
+
+extern const struct nwp_interp_set nwp_interp_initexpr_set, nwp_interp_data_set;
+extern const struct nwp_ops nwp_ops;
+
+int nwp_interp_start(struct nw_interp *i,
+ const struct nw_interp_cfg *cfg, const struct nwp_interp_set *set);
+void nwp_interp_resume(struct nw_interp *i);
+void nwp_interp_limited(struct nw_interp *i);
+void nwp_interp_full(struct nw_interp *i);
+enum nw_state nwp_interp_run(struct nw_interp *i);
+
+#endif
diff --git a/private_include/nw/io.h b/private_include/nw/io.h
new file mode 100644
index 0000000..2b28847
--- /dev/null
+++ b/private_include/nw/io.h
@@ -0,0 +1,40 @@
+/*
+ * 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/.
+ */
+
+#ifndef IO_H
+#define IO_H
+
+#include <nanowasm/nw.h>
+
+enum nw_state nwp_io_read(const struct nw_io_cfg *cfg, struct nw_sm_io *io,
+ void *user);
+enum nw_state nwp_varint1(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varint1 *out, void *user);
+enum nw_state nwp_varint7(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varint7 *out, void *user);
+enum nw_state nwp_varint32(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varint32 *out, void *user);
+enum nw_state nwp_varint64(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varint64 *out, void *user);
+enum nw_state nwp_varuint1(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varuint1 *out, void *user);
+enum nw_state nwp_varuint7(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varuint7 *out, void *user);
+enum nw_state nwp_varuint32(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varuint32 *out, void *user);
+enum nw_state nwp_varuint64(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ nw_varuint64 *out, void *user);
+enum nw_state nwp_leb128(const struct nw_io_cfg *cfg, struct nw_sm_leb128 *l,
+ unsigned maxbits, int sign, void *user);
+unsigned long nwp_leuint32(const struct nw_leuint32 *v);
+void nwp_toleuint32(unsigned long v, struct nw_leuint32 *out);
+void nwp_leuint64(const struct nw_leuint64 *v, struct nw_ull *out);
+void nwp_toleuint64(const struct nw_ull *v, struct nw_leuint64 *out);
+
+#endif
diff --git a/private_include/nw/linear.h b/private_include/nw/linear.h
new file mode 100644
index 0000000..a316e77
--- /dev/null
+++ b/private_include/nw/linear.h
@@ -0,0 +1,21 @@
+
+/*
+ * 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/.
+ */
+
+#ifndef LINEAR_H
+#define LINEAR_H
+
+#include <nanowasm/types.h>
+
+enum nw_state nwp_linear_load(struct nw_interp *i, struct nw_sm_io *io,
+ unsigned long offset);
+enum nw_state nwp_linear_store(struct nw_interp *i, struct nw_sm_io *io,
+ unsigned long offset);
+
+#endif
diff --git a/private_include/nw/log.h b/private_include/nw/log.h
new file mode 100644
index 0000000..c5e2870
--- /dev/null
+++ b/private_include/nw/log.h
@@ -0,0 +1,17 @@
+/*
+ * 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/.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#ifdef NW_LOG
+int nwp_log(const char *fmt, ...);
+#endif
+
+#endif
diff --git a/private_include/nw/mem.h b/private_include/nw/mem.h
new file mode 100644
index 0000000..b1aa7ce
--- /dev/null
+++ b/private_include/nw/mem.h
@@ -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/.
+ */
+
+#ifndef MEM_H
+#define MEM_H
+
+#include <nanowasm/nw.h>
+
+enum nw_state nwp_mem_store(const struct nw_mem_cfg *const cfg,
+ struct nw_sm_io *io, nw_varuint32 offset, void *user);
+enum nw_state nwp_mem_load(const struct nw_mem_cfg *const cfg,
+ struct nw_sm_io *io, nw_varuint32 offset, void *user);
+
+#endif
diff --git a/private_include/nw/opcodes.h b/private_include/nw/opcodes.h
new file mode 100644
index 0000000..410bd97
--- /dev/null
+++ b/private_include/nw/opcodes.h
@@ -0,0 +1,192 @@
+/*
+ * 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/.
+ */
+
+#ifndef OPCODES_H
+#define OPCODES_H
+
+enum opcode
+{
+ OP_UNREACHABLE,
+ OP_NOP,
+ OP_BLOCK,
+ OP_LOOP,
+ OP_IF,
+ OP_ELSE,
+ OP_END = 0xb,
+ OP_BR,
+ OP_BR_IF,
+ OP_BR_TABLE,
+ OP_RETURN,
+ OP_CALL,
+ OP_CALL_INDIRECT,
+ OP_DROP = 0x1a,
+ OP_SELECT,
+ OP_GET_LOCAL = 0x20,
+ OP_SET_LOCAL,
+ OP_TEE_LOCAL,
+ OP_GET_GLOBAL,
+ OP_SET_GLOBAL,
+ OP_I32_LOAD = 0x28,
+ OP_I64_LOAD,
+ OP_F32_LOAD,
+ OP_F64_LOAD,
+ OP_I32_LOAD8_S,
+ OP_I32_LOAD8_U,
+ OP_I32_LOAD16_S,
+ OP_I32_LOAD16_U,
+ OP_I64_LOAD8_S,
+ OP_I64_LOAD8_U,
+ OP_I64_LOAD16_S,
+ OP_I64_LOAD16_U,
+ OP_I64_LOAD32_S,
+ OP_I64_LOAD32_U,
+ OP_I32_STORE,
+ OP_I64_STORE,
+ OP_F32_STORE,
+ OP_F64_STORE,
+ OP_I32_STORE8,
+ OP_I32_STORE16,
+ OP_I64_STORE8,
+ OP_I64_STORE16,
+ OP_I64_STORE32,
+ OP_CURRENT_MEMORY,
+ OP_GROW_MEMORY,
+ OP_I32_CONST,
+ OP_I64_CONST,
+ OP_F32_CONST,
+ OP_F64_CONST,
+ OP_I32_EQZ,
+ OP_I32_EQ,
+ OP_I32_NE,
+ OP_I32_LT_S,
+ OP_I32_LT_U,
+ OP_I32_GT_S,
+ OP_I32_GT_U,
+ OP_I32_LE_S,
+ OP_I32_LE_U,
+ OP_I32_GE_S,
+ OP_I32_GE_U,
+ OP_I64_EQZ,
+ OP_I64_EQ,
+ OP_I64_NE,
+ OP_I64_LT_S,
+ OP_I64_LT_U,
+ OP_I64_GT_S,
+ OP_I64_GT_U,
+ OP_I64_LE_S,
+ OP_I64_LE_U,
+ OP_I64_GE_S,
+ OP_I64_GE_U,
+ OP_F32_EQ,
+ OP_F32_NE,
+ OP_F32_LT,
+ OP_F32_GT,
+ OP_F32_LE,
+ OP_F32_GE,
+ OP_F64_EQ,
+ OP_F64_NE,
+ OP_F64_LT,
+ OP_F64_GT,
+ OP_F64_LE,
+ OP_F64_GE,
+ OP_I32_CLZ,
+ OP_I32_CTZ,
+ OP_I32_POPCNT,
+ OP_I32_ADD,
+ OP_I32_SUB,
+ OP_I32_MUL,
+ OP_I32_DIV_S,
+ OP_I32_DIV_U,
+ OP_I32_REM_S,
+ OP_I32_REM_U,
+ OP_I32_AND,
+ OP_I32_OR,
+ OP_I32_XOR,
+ OP_I32_SHL,
+ OP_I32_SHR_S,
+ OP_I32_SHR_U,
+ OP_I32_ROTL,
+ OP_I32_ROTR,
+ OP_I64_CLZ,
+ OP_I64_CTZ,
+ OP_I64_POPCNT,
+ OP_I64_ADD,
+ OP_I64_SUB,
+ OP_I64_MUL,
+ OP_I64_DIV_S,
+ OP_I64_DIV_U,
+ OP_I64_REM_S,
+ OP_I64_REM_U,
+ OP_I64_AND,
+ OP_I64_OR,
+ OP_I64_XOR,
+ OP_I64_SHL,
+ OP_I64_SHR_S,
+ OP_I64_SHR_U,
+ OP_I64_ROTL,
+ OP_I64_ROTR,
+ OP_F32_ABS,
+ OP_F32_NEG,
+ OP_F32_CEIL,
+ OP_F32_FLOOR,
+ OP_F32_TRUNC,
+ OP_F32_NEAREST,
+ OP_F32_SQRT,
+ OP_F32_ADD,
+ OP_F32_SUB,
+ OP_F32_MUL,
+ OP_F32_DIV,
+ OP_F32_MIN,
+ OP_F32_MAX,
+ OP_F32_COPYSIGN,
+ OP_F64_ABS,
+ OP_F64_NEG,
+ OP_F64_CEIL,
+ OP_F64_FLOOR,
+ OP_F64_TRUNC,
+ OP_F64_NEAREST,
+ OP_F64_SQRT,
+ OP_F64_ADD,
+ OP_F64_SUB,
+ OP_F64_MUL,
+ OP_F64_DIV,
+ OP_F64_MIN,
+ OP_F64_MAX,
+ OP_F64_COPYSIGN,
+ OP_I32_WRAP_I64,
+ OP_I32_TRUNC_S_F32,
+ OP_I32_TRUNC_U_F32,
+ OP_I32_TRUNC_S_F64,
+ OP_I32_TRUNC_U_F64,
+ OP_I64_EXTEND_S_I32,
+ OP_I64_EXTEND_U_I32,
+ OP_I64_TRUNC_S_F32,
+ OP_I64_TRUNC_U_F32,
+ OP_I64_TRUNC_S_F64,
+ OP_I64_TRUNC_U_F64,
+ OP_F32_CONVERT_S_I32,
+ OP_F32_CONVERT_U_I32,
+ OP_F32_CONVERT_S_I64,
+ OP_F32_CONVERT_U_I64,
+ OP_F32_DEMOTE_F64,
+ OP_F64_CONVERT_S_I32,
+ OP_F64_CONVERT_U_I32,
+ OP_F64_CONVERT_S_I64,
+ OP_F64_CONVERT_U_I64,
+ OP_F64_PROMOTE_F32,
+ OP_I32_REINTERPRET_F32,
+ OP_I64_REINTERPRET_F64,
+ OP_F32_REINTERPRET_I32,
+ OP_F64_REINTERPRET_I64,
+ OP_MISC = 0xfc
+};
+
+const char *nwp_op_tostr(enum opcode op);
+
+#endif
diff --git a/private_include/nw/ops.h b/private_include/nw/ops.h
new file mode 100644
index 0000000..dce95a6
--- /dev/null
+++ b/private_include/nw/ops.h
@@ -0,0 +1,90 @@
+/*
+ * 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/.
+ */
+
+#ifndef OPS_H
+#define OPS_H
+
+#include <nanowasm/nw.h>
+#include <nw/opcodes.h>
+#include <nw/types.h>
+
+void nwp_op_unreachable(struct nw_interp *i);
+void nwp_op_nop(struct nw_interp *i);
+void nwp_op_block(struct nw_interp *i);
+void nwp_op_loop(struct nw_interp *i);
+void nwp_op_if(struct nw_interp *i);
+void nwp_op_else(struct nw_interp *i);
+void nwp_op_end(struct nw_interp *i);
+void nwp_op_br(struct nw_interp *i);
+void nwp_op_br_if(struct nw_interp *i);
+void nwp_op_br_table(struct nw_interp *i);
+void nwp_op_return(struct nw_interp *i);
+void nwp_op_call(struct nw_interp *i);
+void nwp_op_call_indirect(struct nw_interp *i);
+void nwp_op_drop(struct nw_interp *i);
+void nwp_op_get_local(struct nw_interp *i);
+void nwp_op_set_local(struct nw_interp *i);
+void nwp_op_tee_local(struct nw_interp *i);
+void nwp_op_set_global(struct nw_interp *i);
+void nwp_op_get_global(struct nw_interp *i);
+void nwp_op_set_global(struct nw_interp *i);
+void nwp_op_i32_load(struct nw_interp *i);
+void nwp_op_i32_load8_u(struct nw_interp *i);
+void nwp_op_i32_store(struct nw_interp *i);
+void nwp_op_i64_store(struct nw_interp *i);
+void nwp_op_current_memory(struct nw_interp *i);
+void nwp_op_i32_const(struct nw_interp *i);
+void nwp_op_i64_const(struct nw_interp *i);
+void nwp_op_f32_const(struct nw_interp *i);
+void nwp_op_f64_const(struct nw_interp *i);
+void nwp_op_i32_add(struct nw_interp *i);
+void nwp_op_i32_sub(struct nw_interp *i);
+void nwp_op_i32_mul(struct nw_interp *i);
+void nwp_op_i32_and(struct nw_interp *i);
+void nwp_op_i32_or(struct nw_interp *i);
+void nwp_op_i32_eqz(struct nw_interp *i);
+void nwp_op_i32_eq(struct nw_interp *i);
+void nwp_op_i32_ne(struct nw_interp *i);
+void nwp_op_i32_lt_s(struct nw_interp *i);
+void nwp_op_i32_ge_s(struct nw_interp *i);
+void nwp_op_i32_ge_u(struct nw_interp *i);
+
+void nwp_op_check_no_immediate(struct nw_mod *m);
+void nwp_op_check_block(struct nw_mod *m);
+void nwp_op_check_loop(struct nw_mod *m);
+void nwp_op_check_end(struct nw_mod *m);
+void nwp_op_check_relative_depth(struct nw_mod *m);
+void nwp_op_check_br_table(struct nw_mod *m);
+void nwp_op_check_call(struct nw_mod *m);
+void nwp_op_check_call_indirect(struct nw_mod *m);
+void nwp_op_check_local_index(struct nw_mod *m);
+void nwp_op_check_global_index(struct nw_mod *m);
+void nwp_op_check_memory_immediate(struct nw_mod *m);
+void nwp_op_check_varuint1(struct nw_mod *m);
+void nwp_op_check_varint32(struct nw_mod *m);
+void nwp_op_check_varint64(struct nw_mod *m);
+void nwp_op_check_uint32(struct nw_mod *m);
+void nwp_op_check_uint64(struct nw_mod *m);
+void nwp_op_check_misc(struct nw_mod *m);
+
+struct nwp_check_op
+{
+ enum opcode start, end;
+ void (*f)(struct nw_mod *);
+};
+
+struct nwp_check_ops
+{
+ const struct nwp_check_op *ops;
+ size_t n;
+};
+
+extern const struct nwp_check_ops nwp_check_ops;
+
+#endif
diff --git a/private_include/nw/routines.h b/private_include/nw/routines.h
new file mode 100644
index 0000000..974f6da
--- /dev/null
+++ b/private_include/nw/routines.h
@@ -0,0 +1,71 @@
+/*
+ * 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/.
+ */
+
+#ifndef ROUTINES_H
+#define ROUTINES_H
+
+#include <nanowasm/nw.h>
+
+void nwp_check_magic(struct nw_mod *m);
+void nwp_check_version(struct nw_mod *m);
+void nwp_section(struct nw_mod *m);
+void nwp_section_custom(struct nw_mod *m);
+void nwp_section_type(struct nw_mod *m);
+void nwp_section_import(struct nw_mod *m);
+void nwp_section_function(struct nw_mod *m);
+void nwp_section_table(struct nw_mod *m);
+void nwp_section_memory(struct nw_mod *m);
+void nwp_section_global(struct nw_mod *m);
+void nwp_section_export(struct nw_mod *m);
+void nwp_section_start(struct nw_mod *m);
+void nwp_section_element(struct nw_mod *m);
+void nwp_section_code(struct nw_mod *m);
+void nwp_section_data(struct nw_mod *m);
+void nwp_section_data_count(struct nw_mod *m);
+void nwp_section_to(struct nw_mod *m);
+void nwp_section_fti(struct nw_mod *m);
+void nwp_section_fbo(struct nw_mod *m);
+void nwp_section_lo(struct nw_mod *m);
+void nwp_section_iti(struct nw_mod *m);
+void nwp_section_skip(struct nw_mod *m);
+enum nw_state nwp_section_exit(struct nw_mod *m);
+
+void nwp_init_globals(struct nw_inst *i,
+ enum nw_state (*next)(struct nw_inst *));
+void nwp_init_data(struct nw_inst *i,
+ enum nw_state (*next)(struct nw_inst *));
+
+enum nw_state nwp_execute(struct nw_interp *i);
+void nwp_find_export(struct nw_interp *i, const char *sym,
+ enum nw_state (*next)(struct nw_interp *));
+void nwp_call(struct nw_interp *i, nw_varuint32 index);
+void nwp_call_function(struct nw_interp *i, nw_varuint32 index);
+void nwp_call_import(struct nw_interp *i, nw_varuint32 index);
+void nwp_find_function(struct nw_interp *i, const struct nw_fn *fn,
+ void (*next)(struct nw_interp *));
+void nwp_get_function_type(struct nw_interp *i, nw_varuint32 index,
+ enum nw_state (*next)(struct nw_interp *));
+void nwp_get_import_type(struct nw_interp *i, struct nw_get_import_type *t,
+ nw_varuint32 index, enum nw_state (*next)(struct nw_interp *));
+void nwp_mem_imm(struct nw_interp *i, void (*next)(struct nw_interp *));
+void nwp_unwind(struct nw_interp *i);
+void nwp_break(struct nw_interp *i, nw_varuint32 relative_depth);
+void nwp_find_param(struct nw_interp *i, struct nw_find_param *f,
+ nw_varuint32 index, enum nw_state (*next)(struct nw_interp *), void *args);
+void nwp_find_local(struct nw_interp *i, struct nw_find_local *f,
+ nw_varuint32 index, enum nw_state (*next)(struct nw_interp *), void *args);
+void nwp_set_local(struct nw_interp *i,
+ enum nw_state (*next)(struct nw_interp *));
+void nwp_arithm(struct nw_interp *i, enum nw_type t,
+ int (*op)(const struct nw_i_sm_arithm_out *, union nw_value *));
+void nwp_unary(struct nw_interp *i, enum nw_type t,
+ int (*op)(const union nw_value *, union nw_value *));
+void nwp_start_block(struct nw_interp *i);
+
+#endif
diff --git a/private_include/nw/stack.h b/private_include/nw/stack.h
new file mode 100644
index 0000000..332b4f5
--- /dev/null
+++ b/private_include/nw/stack.h
@@ -0,0 +1,24 @@
+/*
+ * 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/.
+ */
+
+#ifndef STACK_H
+#define STACK_H
+
+#include <nanowasm/nw.h>
+#include <stddef.h>
+
+size_t nwp_stack_ptr(const struct nw_interp *i);
+enum nw_state nwp_stack_push(struct nw_interp *i, struct nw_sm_io *io);
+enum nw_state nwp_stack_pop(struct nw_interp *i, struct nw_sm_io *io);
+enum nw_state nwp_stack_read(struct nw_interp *i, struct nw_sm_io *io,
+ size_t offset);
+enum nw_state nwp_stack_write(struct nw_interp *i, struct nw_sm_io *io,
+ size_t offset);
+
+#endif
diff --git a/private_include/nw/types.h b/private_include/nw/types.h
new file mode 100644
index 0000000..99e6f55
--- /dev/null
+++ b/private_include/nw/types.h
@@ -0,0 +1,19 @@
+/*
+ * 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/.
+ */
+
+#ifndef TYPES_H
+#define TYPES_H
+
+#include <nanowasm/nw.h>
+#include <stddef.h>
+
+int nwp_get_type(nw_varint7 type, enum nw_type *vtype);
+int nwp_type_sz(enum nw_type type, size_t *sz);
+
+#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..4151987
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,23 @@
+# 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
+ init.c
+ load.c
+ run.c
+ rexc.c
+ start.c
+)
+
+add_subdirectory(dbg)
+add_subdirectory(inst)
+add_subdirectory(interp)
+add_subdirectory(io)
+add_subdirectory(log)
+add_subdirectory(op)
+add_subdirectory(routines)
+add_subdirectory(types)
diff --git a/src/dbg/CMakeLists.txt b/src/dbg/CMakeLists.txt
new file mode 100644
index 0000000..c68a3c8
--- /dev/null
+++ b/src/dbg/CMakeLists.txt
@@ -0,0 +1,15 @@
+# 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
+ global.c
+ init.c
+ local.c
+ mem_read.c
+ param.c
+ value.c
+)
diff --git a/src/dbg/global.c b/src/dbg/global.c
new file mode 100644
index 0000000..cad17ae
--- /dev/null
+++ b/src/dbg/global.c
@@ -0,0 +1,47 @@
+/*
+ * 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 <nanowasm/dbg.h>
+#include <nw/global.h>
+#include <nw/log.h>
+#include <nw/types.h>
+
+static enum nw_state read_global(struct nw_interp *const i)
+{
+ struct nw_dbg *const d = i->args;
+ struct nw_dbg_sm_global *const pg = &d->sm.global;
+ struct nw_dbg_value *const v = &d->v;
+ const struct nw_global *const gl = &pg->gl;
+ const enum nw_state n = nwp_global_load(i, &pg->io, pg->index);
+
+ if (n)
+ return n;
+
+ v->type = gl->type;
+ v->value = gl->value;
+ i->next = pg->inext;
+ return NW_AGAIN;
+}
+
+void nw_dbg_global(struct nw_dbg *const d, const unsigned long index)
+{
+ const struct nw_dbg_sm_global gl = {0};
+ struct nw_dbg_sm_global *const pg = &d->sm.global;
+ struct nw_sm_io *const io = &pg->io;
+ struct nw_interp *const i = &d->cfg.inst->interp;
+
+ *pg = gl;
+ pg->index = index;
+ pg->inext = i->next;
+ io->buf = &pg->gl;
+ io->n = sizeof pg->gl;
+ i->args = d;
+ i->next = read_global;
+}
diff --git a/src/dbg/init.c b/src/dbg/init.c
new file mode 100644
index 0000000..6169413
--- /dev/null
+++ b/src/dbg/init.c
@@ -0,0 +1,19 @@
+/*
+ * 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 <nanowasm/dbg.h>
+
+void nw_dbg_init(struct nw_dbg *const pd, const struct nw_dbg_cfg *const cfg)
+{
+ const struct nw_dbg d = {0};
+
+ *pd = d;
+ pd->cfg = *cfg;
+}
diff --git a/src/dbg/local.c b/src/dbg/local.c
new file mode 100644
index 0000000..b48ac92
--- /dev/null
+++ b/src/dbg/local.c
@@ -0,0 +1,71 @@
+/*
+ * 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 <nanowasm/dbg.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state read_local(struct nw_interp *const i)
+{
+ struct nw_dbg *const d = i->args;
+ struct nw_dbg_sm_local *const pl = &d->sm.local;
+ const struct nw_find_local *const fl = &pl->fl;
+ const enum nw_state n = nwp_stack_read(i, &pl->io, fl->addr);
+
+ if (n)
+ return n;
+
+ i->next = pl->inext;
+ return NW_AGAIN;
+}
+
+static enum nw_state done(struct nw_interp *const i)
+{
+ struct nw_dbg *const d = i->args;
+ struct nw_dbg_sm_local *const pl = &d->sm.local;
+ const struct nw_local_meta *const m = &pl->fl.meta;
+ const enum nw_type t = m->type;
+ size_t sz;
+
+ if (nwp_type_sz(t, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)t);
+#endif
+ return -1;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &d->v.value;
+ io.n = sz;
+ pl->io = io;
+ }
+
+ i->next = read_local;
+ return NW_AGAIN;
+}
+
+void nw_dbg_local(struct nw_dbg *const d, const unsigned long index)
+{
+ const struct nw_dbg_sm_local l = {0};
+ struct nw_dbg_sm_local *const pl = &d->sm.local;
+ struct nw_interp *const i = &d->cfg.inst->interp;
+
+ *pl = l;
+ pl->inext = i->next;
+ nwp_find_local(i, &pl->fl, index, done, d);
+}
diff --git a/src/dbg/mem_read.c b/src/dbg/mem_read.c
new file mode 100644
index 0000000..d1f419c
--- /dev/null
+++ b/src/dbg/mem_read.c
@@ -0,0 +1,47 @@
+/*
+ * 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 <nanowasm/dbg.h>
+#include <nw/linear.h>
+#include <nw/types.h>
+
+static enum nw_state load_mem(struct nw_interp *const i)
+{
+ struct nw_dbg *const d = i->args;
+ struct nw_dbg_sm_mem_load *const pm = &d->sm.mem_load;
+ const enum nw_state n = nwp_linear_load(i, &pm->io, pm->offset);
+
+ if (n)
+ return n;
+
+ i->next = pm->inext;
+ return NW_AGAIN;
+}
+
+void nw_dbg_mem_load(struct nw_dbg *const d, const enum nw_type t,
+ const unsigned long offset)
+{
+ const struct nw_dbg_sm_mem_load ml = {0};
+ struct nw_dbg_sm_mem_load *const pm = &d->sm.mem_load;
+ struct nw_sm_io *const io = &pm->io;
+ struct nw_interp *const i = &d->cfg.inst->interp;
+ struct nw_dbg_value *const v = &d->v;
+ size_t sz;
+
+ nwp_type_sz(t, &sz);
+ *pm = ml;
+ pm->inext = i->next;
+ pm->offset = offset;
+ v->type = t;
+ io->buf = &v->value;
+ io->n = sz;
+ i->args = d;
+ i->next = load_mem;
+}
diff --git a/src/dbg/param.c b/src/dbg/param.c
new file mode 100644
index 0000000..db0a599
--- /dev/null
+++ b/src/dbg/param.c
@@ -0,0 +1,71 @@
+/*
+ * 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 <nanowasm/dbg.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state read_param(struct nw_interp *const i)
+{
+ struct nw_dbg *const d = i->args;
+ struct nw_dbg_sm_param *const p = &d->sm.param;
+ const struct nw_find_param_out *const out = &p->fp.out;
+ const enum nw_state n = nwp_stack_read(i, &p->io, out->addr);
+
+ if (n)
+ return n;
+
+ i->next = p->inext;
+ return NW_AGAIN;
+}
+
+static enum nw_state done(struct nw_interp *const i)
+{
+ struct nw_dbg *const d = i->args;
+ struct nw_dbg_sm_param *const p = &d->sm.param;
+ const struct nw_find_param_out *const po = &p->fp.out;
+ const enum nw_type t = po->type;
+ size_t sz;
+
+ if (nwp_type_sz(t, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)t);
+#endif
+ return -1;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &d->v.value;
+ io.n = sz;
+ p->io = io;
+ }
+
+ i->next = read_param;
+ return NW_AGAIN;
+}
+
+void nw_dbg_param(struct nw_dbg *const d, const unsigned long index)
+{
+ const struct nw_dbg_sm_param p = {0};
+ struct nw_dbg_sm_param *const pp = &d->sm.param;
+ struct nw_interp *const i = &d->cfg.inst->interp;
+
+ *pp = p;
+ pp->inext = i->next;
+ nwp_find_param(i, &pp->fp, index, done, d);
+}
diff --git a/src/dbg/value.c b/src/dbg/value.c
new file mode 100644
index 0000000..633c5f7
--- /dev/null
+++ b/src/dbg/value.c
@@ -0,0 +1,17 @@
+/*
+ * 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 <nanowasm/dbg.h>
+
+int nw_dbg_value(const struct nw_dbg *const d, struct nw_dbg_value *const v)
+{
+ *v = d->v;
+ return 0;
+}
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 0000000..d0358e9
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,28 @@
+/*
+ * 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>
+
+void nw_init(struct nw_mod *const m, const struct nw_mod_cfg *const cfg)
+{
+ const struct nw_mod mm = {0};
+ size_t i;
+
+ *m = mm;
+ m->cfg = *cfg;
+
+ for (i = 0; i < cfg->n_imports; i++)
+ cfg->imp_indexes[i].index = -1;
+
+ nwp_check_magic(m);
+}
diff --git a/src/inst/CMakeLists.txt b/src/inst/CMakeLists.txt
new file mode 100644
index 0000000..a082191
--- /dev/null
+++ b/src/inst/CMakeLists.txt
@@ -0,0 +1,10 @@
+# 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
+ run.c
+)
diff --git a/src/inst/run.c b/src/inst/run.c
new file mode 100644
index 0000000..5a8e411
--- /dev/null
+++ b/src/inst/run.c
@@ -0,0 +1,17 @@
+/*
+ * 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/inst.h>
+#include <nw/interp.h>
+
+enum nw_state nwp_inst_run(struct nw_inst *const i)
+{
+ return nwp_interp_run(&i->interp);
+}
diff --git a/src/interp/CMakeLists.txt b/src/interp/CMakeLists.txt
new file mode 100644
index 0000000..c38da8c
--- /dev/null
+++ b/src/interp/CMakeLists.txt
@@ -0,0 +1,21 @@
+# 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
+ data_set.c
+ initexpr_set.c
+ ops.c
+ run.c
+ resume.c
+ start.c
+)
+
+add_subdirectory(global)
+add_subdirectory(linear)
+add_subdirectory(mem)
+add_subdirectory(routines)
+add_subdirectory(stack)
diff --git a/src/interp/data_set.c b/src/interp/data_set.c
new file mode 100644
index 0000000..266852b
--- /dev/null
+++ b/src/interp/data_set.c
@@ -0,0 +1,24 @@
+/*
+ * 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/opcodes.h>
+
+static const enum opcode opcodes[] =
+{
+ OP_END,
+ OP_I32_CONST,
+};
+
+const struct nwp_interp_set nwp_interp_data_set =
+{
+ opcodes,
+ sizeof opcodes / sizeof *opcodes
+};
diff --git a/src/interp/global/CMakeLists.txt b/src/interp/global/CMakeLists.txt
new file mode 100644
index 0000000..0f0e056
--- /dev/null
+++ b/src/interp/global/CMakeLists.txt
@@ -0,0 +1,11 @@
+# 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
+ load.c
+ store.c
+)
diff --git a/src/interp/global/load.c b/src/interp/global/load.c
new file mode 100644
index 0000000..26d8c8d
--- /dev/null
+++ b/src/interp/global/load.c
@@ -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/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/interp.h>
+#include <nw/global.h>
+#include <nw/mem.h>
+#include <nw/log.h>
+
+enum nw_state nwp_global_load(struct nw_interp *const i,
+ struct nw_sm_io *const io, const nw_varuint32 index)
+{
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const size_t offset = index * sizeof (struct nw_global);
+ const enum nw_state n = nwp_mem_load(&cfg->global, io, offset, cfg->user);
+
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "failed to load from global memory";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ }
+
+ return n;
+}
diff --git a/src/interp/global/store.c b/src/interp/global/store.c
new file mode 100644
index 0000000..91550d4
--- /dev/null
+++ b/src/interp/global/store.c
@@ -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/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/global.h>
+#include <nw/interp.h>
+#include <nw/mem.h>
+#include <nw/log.h>
+
+enum nw_state nwp_global_store(struct nw_interp *const i,
+ struct nw_sm_io *const io, const nw_varuint32 index)
+{
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const size_t offset = index * sizeof (struct nw_global);
+ const enum nw_state n = nwp_mem_store(&cfg->global, io, offset, cfg->user);
+
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "failed to store into global memory";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ }
+
+ return n;
+}
diff --git a/src/interp/initexpr_set.c b/src/interp/initexpr_set.c
new file mode 100644
index 0000000..1e200f6
--- /dev/null
+++ b/src/interp/initexpr_set.c
@@ -0,0 +1,28 @@
+/*
+ * 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/opcodes.h>
+
+static const enum opcode opcodes[] =
+{
+ OP_NOP,
+ OP_END,
+ OP_I32_CONST,
+ OP_I64_CONST,
+ OP_F32_CONST,
+ OP_F64_CONST
+};
+
+const struct nwp_interp_set nwp_interp_initexpr_set =
+{
+ opcodes,
+ sizeof opcodes / sizeof *opcodes
+};
diff --git a/src/interp/linear/CMakeLists.txt b/src/interp/linear/CMakeLists.txt
new file mode 100644
index 0000000..0f0e056
--- /dev/null
+++ b/src/interp/linear/CMakeLists.txt
@@ -0,0 +1,11 @@
+# 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
+ load.c
+ store.c
+)
diff --git a/src/interp/linear/load.c b/src/interp/linear/load.c
new file mode 100644
index 0000000..afc6154
--- /dev/null
+++ b/src/interp/linear/load.c
@@ -0,0 +1,39 @@
+/*
+ * 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 <nanowasm/linear.h>
+#include <nw/interp.h>
+#include <nw/mem.h>
+#include <nw/log.h>
+
+enum nw_state nwp_linear_load(struct nw_interp *const i,
+ struct nw_sm_io *const io, const unsigned long offset)
+{
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const enum nw_state n = nwp_mem_load(&cfg->linear, io, offset, cfg->user);
+
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "failed to load from linear memory";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ }
+
+ return n;
+}
+
+enum nw_state nw_linear_load(struct nw_inst *const i,
+ struct nw_sm_io *const io, const unsigned long offset)
+{
+ return nwp_linear_load(&i->interp, io, offset);
+}
diff --git a/src/interp/linear/store.c b/src/interp/linear/store.c
new file mode 100644
index 0000000..6edffd9
--- /dev/null
+++ b/src/interp/linear/store.c
@@ -0,0 +1,40 @@
+/*
+ * 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 <nanowasm/linear.h>
+#include <nw/linear.h>
+#include <nw/interp.h>
+#include <nw/mem.h>
+#include <nw/log.h>
+
+enum nw_state nwp_linear_store(struct nw_interp *const i,
+ struct nw_sm_io *const io, const unsigned long offset)
+{
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const enum nw_state n = nwp_mem_store(&cfg->linear, io, offset, cfg->user);
+
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "failed to store into linear memory";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ }
+
+ return n;
+}
+
+enum nw_state nw_linear_store(struct nw_inst *const i,
+ struct nw_sm_io *const io, const unsigned long offset)
+{
+ return nwp_linear_store(&i->interp, io, offset);
+}
diff --git a/src/interp/mem/CMakeLists.txt b/src/interp/mem/CMakeLists.txt
new file mode 100644
index 0000000..0f0e056
--- /dev/null
+++ b/src/interp/mem/CMakeLists.txt
@@ -0,0 +1,11 @@
+# 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
+ load.c
+ store.c
+)
diff --git a/src/interp/mem/load.c b/src/interp/mem/load.c
new file mode 100644
index 0000000..0ccf29b
--- /dev/null
+++ b/src/interp/mem/load.c
@@ -0,0 +1,27 @@
+/*
+ * 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/mem.h>
+#include <nw/log.h>
+
+enum nw_state nwp_mem_load(const struct nw_mem_cfg *const cfg,
+ struct nw_sm_io *const io, const nw_varuint32 offset, void *const user)
+{
+ void *const dst = (unsigned char *)io->buf + io->read;
+ const int n = cfg->load(offset + io->read, dst, io->n - io->read, user);
+
+ if (n < 0)
+ return NW_FATAL;
+ else if ((io->read += n) >= io->n)
+ return NW_OK;
+
+ return NW_AGAIN;
+}
diff --git a/src/interp/mem/store.c b/src/interp/mem/store.c
new file mode 100644
index 0000000..cc58169
--- /dev/null
+++ b/src/interp/mem/store.c
@@ -0,0 +1,27 @@
+/*
+ * 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/mem.h>
+#include <nw/log.h>
+
+enum nw_state nwp_mem_store(const struct nw_mem_cfg *const cfg,
+ struct nw_sm_io *const io, const nw_varuint32 offset, void *const user)
+{
+ const void *const src = (const unsigned char *)io->buf + io->read;
+ const int n = cfg->store(offset + io->read, src, io->n - io->read, user);
+
+ if (n < 0)
+ return NW_FATAL;
+ else if ((io->read += n) >= io->n)
+ return NW_OK;
+
+ return NW_AGAIN;
+}
diff --git a/src/interp/ops.c b/src/interp/ops.c
new file mode 100644
index 0000000..6135f96
--- /dev/null
+++ b/src/interp/ops.c
@@ -0,0 +1,278 @@
+/*
+ * 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/opcodes.h>
+#include <nw/ops.h>
+#include <nw/interp.h>
+
+static void (*const ops[])(struct nw_interp *) =
+{
+ nwp_op_unreachable, /* OP_UNREACHABLE */
+ nwp_op_nop, /* OP_NOP */
+ nwp_op_block, /* OP_BLOCK */
+ nwp_op_loop, /* OP_LOOP */
+ NULL, /* OP_IF */
+ NULL, /* OP_ELSE */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ nwp_op_end, /* OP_END */
+ nwp_op_br, /* OP_BR */
+ nwp_op_br_if, /* OP_BR_IF */
+ NULL, /* OP_BR_TABLE */
+ nwp_op_return, /* OP_RETURN */
+ nwp_op_call, /* OP_CALL */
+ nwp_op_call_indirect, /* OP_CALL_INDIRECT */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ nwp_op_drop, /* OP_DROP */
+ NULL, /* OP_SELECT */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ nwp_op_get_local, /* OP_GET_LOCAL */
+ nwp_op_set_local, /* OP_SET_LOCAL */
+ nwp_op_tee_local, /* OP_TEE_LOCAL */
+ nwp_op_get_global, /* OP_GET_GLOBAL */
+ nwp_op_set_global, /* OP_SET_GLOBAL */
+ NULL,
+ NULL,
+ NULL,
+ nwp_op_i32_load, /* OP_I32_LOAD */
+ NULL, /* OP_I64_LOAD */
+ NULL, /* OP_F32_LOAD */
+ NULL, /* OP_F64_LOAD */
+ NULL, /* OP_I32_LOAD8_S */
+ nwp_op_i32_load8_u, /* OP_I32_LOAD8_U */
+ NULL, /* OP_I32_LOAD16_S */
+ NULL, /* OP_I32_LOAD16_U */
+ NULL, /* OP_I64_LOAD8_S */
+ NULL, /* OP_I64_LOAD8_U */
+ NULL, /* OP_I64_LOAD16_S */
+ NULL, /* OP_I64_LOAD16_U */
+ NULL, /* OP_I64_LOAD32_S */
+ NULL, /* OP_I64_LOAD32_U */
+ nwp_op_i32_store, /* OP_I32_STORE */
+ nwp_op_i64_store, /* OP_I64_STORE */
+ NULL, /* OP_F32_STORE */
+ NULL, /* OP_F64_STORE */
+ NULL, /* OP_I32_STORE8 */
+ NULL, /* OP_I32_STORE16 */
+ NULL, /* OP_I64_STORE8 */
+ NULL, /* OP_I64_STORE16 */
+ NULL, /* OP_I64_STORE32 */
+ NULL, /* OP_CURRENT_MEMORY */
+ NULL, /* OP_GROW_MEMORY */
+ nwp_op_i32_const, /* OP_I32_CONST */
+ nwp_op_i64_const, /* OP_I64_CONST */
+ NULL, /* OP_F32_CONST */
+ NULL, /* OP_F64_CONST */
+ nwp_op_i32_eqz, /* OP_I32_EQZ */
+ nwp_op_i32_eq, /* OP_I32_EQ */
+ nwp_op_i32_ne, /* OP_I32_NE */
+ nwp_op_i32_lt_s, /* OP_I32_LT_S */
+ NULL, /* OP_I32_LT_U */
+ NULL, /* OP_I32_GT_S */
+ NULL, /* OP_I32_GT_U */
+ NULL, /* OP_I32_LE_S */
+ NULL, /* OP_I32_LE_U */
+ nwp_op_i32_ge_s, /* OP_I32_GE_S */
+ nwp_op_i32_ge_u, /* OP_I32_GE_U */
+ NULL, /* OP_I64_EQZ */
+ NULL, /* OP_I64_EQ */
+ NULL, /* OP_I64_NE */
+ NULL, /* OP_I64_LT_S */
+ NULL, /* OP_I64_LT_U */
+ NULL, /* OP_I64_GT_S */
+ NULL, /* OP_I64_GT_U */
+ NULL, /* OP_I64_LE_S */
+ NULL, /* OP_I64_LE_U */
+ NULL, /* OP_I64_GE_S */
+ NULL, /* OP_I64_GE_U */
+ NULL, /* OP_F32_EQ */
+ NULL, /* OP_F32_NE */
+ NULL, /* OP_F32_LT */
+ NULL, /* OP_F32_GT */
+ NULL, /* OP_F32_LE */
+ NULL, /* OP_F32_GE */
+ NULL, /* OP_F64_EQ */
+ NULL, /* OP_F64_NE */
+ NULL, /* OP_F64_LT */
+ NULL, /* OP_F64_GT */
+ NULL, /* OP_F64_LE */
+ NULL, /* OP_F64_GE */
+ NULL, /* OP_I32_CLZ */
+ NULL, /* OP_I32_CTZ */
+ NULL, /* OP_I32_POPCNT */
+ nwp_op_i32_add, /* OP_I32_ADD */
+ nwp_op_i32_sub, /* OP_I32_SUB */
+ nwp_op_i32_mul, /* OP_I32_MUL */
+ NULL, /* OP_I32_DIV_S */
+ NULL, /* OP_I32_DIV_U */
+ NULL, /* OP_I32_REM_S */
+ NULL, /* OP_I32_REM_U */
+ nwp_op_i32_and, /* OP_I32_AND */
+ nwp_op_i32_or, /* OP_I32_OR */
+ NULL, /* OP_I32_XOR */
+ NULL, /* OP_I32_SHL */
+ NULL, /* OP_I32_SHR_S */
+ NULL, /* OP_I32_SHR_U */
+ NULL, /* OP_I32_ROTL */
+ NULL, /* OP_I32_ROTR */
+ NULL, /* OP_I64_CLZ */
+ NULL, /* OP_I64_CTZ */
+ NULL, /* OP_I64_POPCNT */
+ NULL, /* OP_I64_ADD */
+ NULL, /* OP_I64_SUB */
+ NULL, /* OP_I64_MUL */
+ NULL, /* OP_I64_DIV_S */
+ NULL, /* OP_I64_DIV_U */
+ NULL, /* OP_I64_REM_S */
+ NULL, /* OP_I64_REM_U */
+ NULL, /* OP_I64_AND */
+ NULL, /* OP_I64_OR */
+ NULL, /* OP_I64_XOR */
+ NULL, /* OP_I64_SHL */
+ NULL, /* OP_I64_SHR_S */
+ NULL, /* OP_I64_SHR_U */
+ NULL, /* OP_I64_ROTL */
+ NULL, /* OP_I64_ROTR */
+ NULL, /* OP_F32_ABS */
+ NULL, /* OP_F32_NEG */
+ NULL, /* OP_F32_CEIL */
+ NULL, /* OP_F32_FLOOR */
+ NULL, /* OP_F32_TRUNC */
+ NULL, /* OP_F32_NEAREST */
+ NULL, /* OP_F32_SQRT */
+ NULL, /* OP_F32_ADD */
+ NULL, /* OP_F32_SUB */
+ NULL, /* OP_F32_MUL */
+ NULL, /* OP_F32_DIV */
+ NULL, /* OP_F32_MIN */
+ NULL, /* OP_F32_MAX */
+ NULL, /* OP_F32_COPYSIGN */
+ NULL, /* OP_F64_ABS */
+ NULL, /* OP_F64_NEG */
+ NULL, /* OP_F64_CEIL */
+ NULL, /* OP_F64_FLOOR */
+ NULL, /* OP_F64_TRUNC */
+ NULL, /* OP_F64_NEAREST */
+ NULL, /* OP_F64_SQRT */
+ NULL, /* OP_F64_ADD */
+ NULL, /* OP_F64_SUB */
+ NULL, /* OP_F64_MUL */
+ NULL, /* OP_F64_DIV */
+ NULL, /* OP_F64_MIN */
+ NULL, /* OP_F64_MAX */
+ NULL, /* OP_F64_COPYSIGN */
+ NULL, /* OP_I32_WRAP_I64 */
+ NULL, /* OP_I32_TRUNC_S_F32 */
+ NULL, /* OP_I32_TRUNC_U_F32 */
+ NULL, /* OP_I32_TRUNC_S_F64 */
+ NULL, /* OP_I32_TRUNC_U_F64 */
+ NULL, /* OP_I64_EXTEND_S_I32 */
+ NULL, /* OP_I64_EXTEND_U_I32 */
+ NULL, /* OP_I64_TRUNC_S_F32 */
+ NULL, /* OP_I64_TRUNC_U_F32 */
+ NULL, /* OP_I64_TRUNC_S_F64 */
+ NULL, /* OP_I64_TRUNC_U_F64 */
+ NULL, /* OP_F32_CONVERT_S_I32 */
+ NULL, /* OP_F32_CONVERT_U_I32 */
+ NULL, /* OP_F32_CONVERT_S_I64 */
+ NULL, /* OP_F32_CONVERT_U_I64 */
+ NULL, /* OP_F32_DEMOTE_F64 */
+ NULL, /* OP_F64_CONVERT_S_I32 */
+ NULL, /* OP_F64_CONVERT_U_I32 */
+ NULL, /* OP_F64_CONVERT_S_I64 */
+ NULL, /* OP_F64_CONVERT_U_I64 */
+ NULL, /* OP_F64_PROMOTE_F32 */
+ NULL, /* OP_I32_REINTERPRET_F32 */
+ NULL, /* OP_I64_REINTERPRET_F64 */
+ NULL, /* OP_F32_REINTERPRET_I32 */
+ NULL, /* OP_F64_REINTERPRET_I64 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL /* OP_MISC */
+};
+
+const struct nwp_ops nwp_ops =
+{
+ ops,
+ sizeof ops / sizeof *ops
+};
diff --git a/src/interp/resume.c b/src/interp/resume.c
new file mode 100644
index 0000000..cf9ece2
--- /dev/null
+++ b/src/interp/resume.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 <nanowasm/types.h>
+#include <nw/interp.h>
+
+void nwp_interp_resume(struct nw_interp *const i)
+{
+ if (i->set)
+ nwp_interp_limited(i);
+ else
+ nwp_interp_full(i);
+}
diff --git a/src/interp/routines/CMakeLists.txt b/src/interp/routines/CMakeLists.txt
new file mode 100644
index 0000000..b27172b
--- /dev/null
+++ b/src/interp/routines/CMakeLists.txt
@@ -0,0 +1,13 @@
+# 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
+ execute.c
+ find_export.c
+ full.c
+ limited.c
+)
diff --git a/src/interp/routines/execute.c b/src/interp/routines/execute.c
new file mode 100644
index 0000000..49711ae
--- /dev/null
+++ b/src/interp/routines/execute.c
@@ -0,0 +1,83 @@
+/*
+ * 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 <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 execute(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+
+#ifdef NW_LOG
+ nwp_log("opcode: %s, pc=%#lx\n", nwp_op_tostr(b->op), b->pc);
+#endif
+ b->f(i);
+ i->fr.prev_op = b->op;
+ return NW_AGAIN;
+}
+
+static enum nw_state repeat(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+ struct nw_next *const next = &b->next;
+ const enum nw_state n = next->fn(next->user, next);
+
+ if (n)
+ return n;
+
+ return execute(i);
+}
+
+static enum nw_state exec_pc(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const long pc = b->pc;
+
+ if (cfg->pc)
+ {
+ const enum nw_state n = cfg->pc(pc, &b->next, cfg->user);
+
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "pc callback failed";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, pc: %ld\n", exc, pc);
+#endif
+ return NW_FATAL;
+ }
+ else if (n)
+ {
+ i->next = repeat;
+ return n;
+ }
+ }
+
+ return execute(i);
+}
+
+enum nw_state nwp_execute(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&b->pc, cfg->user);
+
+ if (n)
+ return n;
+
+ b->pc -= sizeof b->op;
+ i->next = exec_pc;
+ return NW_AGAIN;
+}
diff --git a/src/interp/routines/find_export.c b/src/interp/routines/find_export.c
new file mode 100644
index 0000000..effa8d1
--- /dev/null
+++ b/src/interp/routines/find_export.c
@@ -0,0 +1,229 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <string.h>
+
+static enum nw_state entry_loop(struct nw_interp *i);
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_exp *const e = &i->sm.export;
+ struct nw_sm_leb128 *const l = &e->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &e->index, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = e->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_kind(struct nw_interp *const i)
+{
+ unsigned char kind;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_exp *const e = &i->sm.export;
+ 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 >= NW_KINDS)
+ {
+ static const char *const exc = "invalid export kind";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)kind);
+#endif
+ return NW_FATAL;
+ }
+
+ e->kind = kind;
+ i->next = get_index;
+ return NW_AGAIN;
+}
+
+static enum nw_state skip_index(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_exp *const e = &i->sm.export;
+ struct nw_sm_leb128 *const l = &e->leb128;
+ nw_varuint32 index;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+
+ e->entry_i++;
+ i->next = entry_loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state skip_kind(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ unsigned char b;
+ 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;
+
+ i->next = skip_index;
+ return NW_AGAIN;
+}
+
+static enum nw_state skip_field_str(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const struct nw_i_sm_exp *const e = &i->sm.export;
+ const long offset = e->offset + (e->len - e->len_i);
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = skip_kind;
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_interp *const i)
+{
+ struct nw_i_sm_exp *const e = &i->sm.export;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&e->offset, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = skip_field_str;
+ return NW_AGAIN;
+}
+
+static enum nw_state compare(struct nw_interp *const i)
+{
+ unsigned char b;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_exp *const e = &i->sm.export;
+ 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 (b != e->sym[e->len_i++])
+ i->next = tell;
+ else if (e->len_i >= e->len)
+ i->next = get_kind;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_len(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_exp *const e = &i->sm.export;
+ struct nw_sm_leb128 *const l = &e->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &e->len, cfg->user);
+
+ if (n)
+ return n;
+ else if (e->len != strlen(e->sym))
+ i->next = skip_field_str;
+ else
+ i->next = compare;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state entry_loop(struct nw_interp *const i)
+{
+ struct nw_i_sm_exp *const e = &i->sm.export;
+
+ if (e->entry_i >= e->count)
+ {
+ static const char *const exc = "failed to find symbol";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %s\n", exc, e->sym);
+#endif
+ return NW_FATAL;
+ }
+
+ e->len_i = 0;
+ i->next = get_len;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_exp *const e = &i->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;
+
+ i->next = entry_loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek(struct nw_interp *const i)
+{
+ const struct nw_mod *const m = i->cfg.m;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const long offset = m->sections[NW_SECTION_EXPORT];
+ enum nw_state n;
+
+ if (!offset)
+ {
+ static const char *const exc = "section not found";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %s\n", exc, "export");
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+
+ i->next = get_count;
+ return NW_AGAIN;
+}
+
+void nwp_find_export(struct nw_interp *const i, const char *const sym,
+ enum nw_state (*const next)(struct nw_interp *))
+{
+ const struct nw_i_sm_exp e = {0};
+ struct nw_i_sm_exp *const pe = &i->sm.export;
+
+ *pe = e;
+ pe->sym = sym;
+ pe->next = next;
+ i->next = seek;
+}
diff --git a/src/interp/routines/full.c b/src/interp/routines/full.c
new file mode 100644
index 0000000..c00f11f
--- /dev/null
+++ b/src/interp/routines/full.c
@@ -0,0 +1,63 @@
+/*
+ * 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 <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 tell(struct nw_interp *const i)
+{
+ long offset;
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+ static const char *const exc = "invalid opcode";
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&offset, cfg->user);
+
+ if (n)
+ return n;
+
+ offset -= sizeof b->op;
+
+#ifdef NW_LOG
+ nwp_log("%s: %#x, offset=%#lx\n", exc, (unsigned)b->op, offset);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+}
+
+static enum nw_state get_bytecode(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &b->io, cfg->user);
+
+ if (n)
+ return n;
+ else if (b->op >= nwp_ops.n || !(b->f = nwp_ops.ops[b->op]))
+ {
+ i->next = tell;
+ return NW_AGAIN;
+ }
+
+ i->next = nwp_execute;
+ return NW_AGAIN;
+}
+
+void nwp_interp_full(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const pb = &i->sm.bytecode, b = {0};
+
+ b.io.buf = &pb->op;
+ b.io.n = sizeof pb->op;
+ *pb = b;
+ i->next = get_bytecode;
+}
diff --git a/src/interp/routines/limited.c b/src/interp/routines/limited.c
new file mode 100644
index 0000000..dc18ded
--- /dev/null
+++ b/src/interp/routines/limited.c
@@ -0,0 +1,54 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <stddef.h>
+
+static enum nw_state get_bytecode(struct nw_interp *const in)
+{
+ const struct nw_io_cfg *const cfg = &in->cfg.io;
+ struct nw_i_sm_b *const b = &in->sm.bytecode;
+ const struct nwp_interp_set *const set = in->set;
+ static const char *const exc = "invalid opcode";
+ const enum nw_state n = nwp_io_read(cfg, &b->io, cfg->user);
+ size_t i;
+
+ if (n)
+ return n;
+
+ for (i = 0; i < set->n; i++)
+ if (b->op == set->opcodes[i])
+ {
+ b->f = nwp_ops.ops[b->op];
+ in->next = nwp_execute;
+ return NW_AGAIN;
+ }
+
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)b->op);
+#endif
+ in->exception = exc;
+ return NW_FATAL;
+}
+
+void nwp_interp_limited(struct nw_interp *const i)
+{
+ struct nw_i_sm_b *const b = &i->sm.bytecode;
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->op;
+ io.n = sizeof b->op;
+ b->io = io;
+ i->next = get_bytecode;
+}
diff --git a/src/interp/run.c b/src/interp/run.c
new file mode 100644
index 0000000..f5e80e6
--- /dev/null
+++ b/src/interp/run.c
@@ -0,0 +1,16 @@
+/*
+ * 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>
+
+enum nw_state nwp_interp_run(struct nw_interp *const i)
+{
+ return i->next(i);
+}
diff --git a/src/interp/stack/CMakeLists.txt b/src/interp/stack/CMakeLists.txt
new file mode 100644
index 0000000..51c7cb5
--- /dev/null
+++ b/src/interp/stack/CMakeLists.txt
@@ -0,0 +1,14 @@
+# 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
+ pop.c
+ push.c
+ ptr.c
+ read.c
+ write.c
+)
diff --git a/src/interp/stack/pop.c b/src/interp/stack/pop.c
new file mode 100644
index 0000000..03ed689
--- /dev/null
+++ b/src/interp/stack/pop.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/log.h>
+#include <nw/stack.h>
+
+enum nw_state nwp_stack_pop(struct nw_interp *const i,
+ struct nw_sm_io *const io)
+{
+ void *const dst = (unsigned char *)io->buf + io->read;
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const struct nw_fifo_cfg *const fc = &cfg->stack;
+ const int n = fc->pop(dst, io->n - io->read, cfg->user);
+
+ if (n < 0)
+ {
+ static const char *const exc = "failed to pop from stack";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+ else if ((io->read += n) >= io->n)
+ return NW_OK;
+
+ return NW_AGAIN;
+}
diff --git a/src/interp/stack/ptr.c b/src/interp/stack/ptr.c
new file mode 100644
index 0000000..d5fa455
--- /dev/null
+++ b/src/interp/stack/ptr.c
@@ -0,0 +1,21 @@
+/*
+ * 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/stack.h>
+#include <stddef.h>
+
+size_t nwp_stack_ptr(const struct nw_interp *const i)
+{
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const struct nw_fifo_cfg *const fc = &cfg->stack;
+
+ return fc->ptr(cfg->user);
+}
diff --git a/src/interp/stack/push.c b/src/interp/stack/push.c
new file mode 100644
index 0000000..b8b48ce
--- /dev/null
+++ b/src/interp/stack/push.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/log.h>
+#include <nw/stack.h>
+
+enum nw_state nwp_stack_push(struct nw_interp *const i,
+ struct nw_sm_io *const io)
+{
+ const void *const src = (const unsigned char *)io->buf + io->read;
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const struct nw_fifo_cfg *const fc = &cfg->stack;
+ const int n = fc->push(src, io->n - io->read, cfg->user);
+
+ if (n < 0)
+ {
+ static const char *const exc = "failed to push to stack";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+ else if ((io->read += n) >= io->n)
+ return NW_OK;
+
+ return NW_AGAIN;
+}
diff --git a/src/interp/stack/read.c b/src/interp/stack/read.c
new file mode 100644
index 0000000..8db6bcc
--- /dev/null
+++ b/src/interp/stack/read.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/log.h>
+#include <nw/stack.h>
+
+enum nw_state nwp_stack_read(struct nw_interp *const i,
+ struct nw_sm_io *const io, const size_t offset)
+{
+ void *const dst = (unsigned char *)io->buf + io->read;
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const struct nw_fifo_cfg *const fc = &cfg->stack;
+ const int n = fc->read(offset + io->read, dst, io->n - io->read, cfg->user);
+
+ if (n < 0)
+ {
+ static const char *const exc = "failed to read from stack";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+ else if ((io->read += n) >= io->n)
+ return NW_OK;
+
+ return NW_AGAIN;
+}
diff --git a/src/interp/stack/write.c b/src/interp/stack/write.c
new file mode 100644
index 0000000..73e749e
--- /dev/null
+++ b/src/interp/stack/write.c
@@ -0,0 +1,38 @@
+/*
+ * 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/log.h>
+#include <nw/stack.h>
+
+enum nw_state nwp_stack_write(struct nw_interp *const i,
+ struct nw_sm_io *const io, const size_t offset)
+{
+ const void *const src = (const unsigned char *)io->buf + io->read;
+ const struct nw_interp_cfg *const cfg = &i->cfg;
+ const struct nw_fifo_cfg *const fc = &cfg->stack;
+ const int n = fc->write(offset + io->read, src, io->n - io->read,
+ cfg->user);
+
+ if (n < 0)
+ {
+ static const char *const exc = "failed to write to stack";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+ else if ((io->read += n) >= io->n)
+ return NW_OK;
+
+ return NW_AGAIN;
+}
diff --git a/src/interp/start.c b/src/interp/start.c
new file mode 100644
index 0000000..2bade41
--- /dev/null
+++ b/src/interp/start.c
@@ -0,0 +1,28 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/interp.h>
+
+int nwp_interp_start(struct nw_interp *const i,
+ const struct nw_interp_cfg *const cfg,
+ const struct nwp_interp_set *const set)
+{
+ const struct nw_interp in = {0};
+ const struct nw_mod_out *const mout = &cfg->m->out;
+
+ *i = in;
+ i->cfg = *cfg;
+ i->set = set;
+ i->table.n_pages = mout->table.initial;
+ i->linear.n_pages = mout->linear.initial;
+ nwp_interp_resume(i);
+ return 0;
+}
diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt
new file mode 100644
index 0000000..b7c19cb
--- /dev/null
+++ b/src/io/CMakeLists.txt
@@ -0,0 +1,23 @@
+# 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
+ leb128.c
+ leuint32.c
+ leuint64.c
+ read.c
+ toleuint32.c
+ toleuint64.c
+ varint1.c
+ varint7.c
+ varint32.c
+ varint64.c
+ varuint1.c
+ varuint7.c
+ varuint32.c
+ varuint64.c
+)
diff --git a/src/io/leb128.c b/src/io/leb128.c
new file mode 100644
index 0000000..efb599b
--- /dev/null
+++ b/src/io/leb128.c
@@ -0,0 +1,75 @@
+/*
+ * 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/.
+ */
+
+
+/* The functions below is a (somewhat heavily) modified version of that
+ * provided by the wac project: https://github.com/kanaka/wac
+ *
+ * Copyright (C) Joel Martin <github@martintribe.org>
+ * The wac project is licensed under the MPL 2.0 (Mozilla Public License
+ * 2.0). The text of the MPL 2.0 license is included below and can be
+ * found at https://www.mozilla.org/MPL/2.0/
+ */
+
+#include <nanowasm/types.h>
+#include <nw/io.h>
+#include <nw/log.h>
+
+enum nw_state nwp_leb128(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, const unsigned maxbits, const int sign,
+ void *const user)
+{
+ unsigned char byte;
+
+ for (;;)
+ {
+ const int n = cfg->read(&byte, sizeof byte, user);
+ unsigned long v;
+
+ if (n < 0)
+ return NW_FATAL;
+ else if (!n)
+ return NW_AGAIN;
+
+ v = (unsigned long)(byte & 0x7f) << l->shift;;
+
+ if (l->shift < 32)
+ l->result.low |= v;
+ else
+ l->result.hi |= v;
+
+ l->shift += 7;
+
+ if (!(byte & 0x80))
+ break;
+ else if (++l->bcnt > (maxbits + 7u - 1u) / 7u)
+ {
+#ifdef NW_LOG
+ long offset;
+ const enum nw_state n = cfg->tell(&offset, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_log("leb128 overflow, offset=%#lx\n", (unsigned long)offset);
+#endif
+ return NW_FATAL;
+ }
+ }
+
+ if (sign && (l->shift < maxbits) && (byte & 0x40))
+ {
+ if (l->shift < 32)
+ l->result.low |= -1l << (l->shift);
+ else
+ l->result.hi |= -1l << (l->shift);
+ }
+
+ return NW_OK;
+}
diff --git a/src/io/leuint32.c b/src/io/leuint32.c
new file mode 100644
index 0000000..41a2e3b
--- /dev/null
+++ b/src/io/leuint32.c
@@ -0,0 +1,17 @@
+/*
+ * 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>
+
+unsigned long nwp_leuint32(const struct nw_leuint32 *const v)
+{
+ return v->v[0] | (v->v[1] << 8) | ((unsigned long)v->v[2] << 16)
+ | ((unsigned long)v->v[3] << 24);
+}
diff --git a/src/io/leuint64.c b/src/io/leuint64.c
new file mode 100644
index 0000000..6d3deb0
--- /dev/null
+++ b/src/io/leuint64.c
@@ -0,0 +1,24 @@
+/*
+ * 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>
+
+void nwp_leuint64(const struct nw_leuint64 *const v,
+ struct nw_ull *const ull)
+{
+ ull->low = v->v[0]
+ | (v->v[1] << 8)
+ | ((unsigned long)v->v[2] << 16ul)
+ | ((unsigned long)v->v[3] << 24ul);
+ ull->hi = v->v[4]
+ | (v->v[5] << 8)
+ | ((unsigned long) v->v[6] << 16ul)
+ | ((unsigned long)v->v[7] << 24ul);
+}
diff --git a/src/io/read.c b/src/io/read.c
new file mode 100644
index 0000000..d4222b5
--- /dev/null
+++ b/src/io/read.c
@@ -0,0 +1,31 @@
+/*
+ * 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 <stddef.h>
+
+enum nw_state nwp_io_read(const struct nw_io_cfg *const cfg,
+ struct nw_sm_io *const io, void *const user)
+{
+ void *const buf = (unsigned char *)io->buf + io->read;
+ const size_t rem = io->n - io->read;
+ const int n = cfg->read(buf, rem, user);
+
+ if (n < 0 || (!n && cfg->eof(user)))
+ return NW_FATAL;
+
+ if ((io->read += n) >= io->n)
+ {
+ io->read = 0;
+ return NW_OK;
+ }
+
+ return NW_AGAIN;
+}
diff --git a/src/io/toleuint32.c b/src/io/toleuint32.c
new file mode 100644
index 0000000..c1c96c7
--- /dev/null
+++ b/src/io/toleuint32.c
@@ -0,0 +1,19 @@
+/*
+ * 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>
+
+void nwp_toleuint32(const unsigned long v, struct nw_leuint32 *const r)
+{
+ r->v[0] = v;
+ r->v[1] = v >> 8;
+ r->v[2] = v >> 16;
+ r->v[3] = v >> 24;
+}
diff --git a/src/io/toleuint64.c b/src/io/toleuint64.c
new file mode 100644
index 0000000..57856c2
--- /dev/null
+++ b/src/io/toleuint64.c
@@ -0,0 +1,23 @@
+/*
+ * 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>
+
+void nwp_toleuint64(const struct nw_ull *const v, struct nw_leuint64 *const r)
+{
+ r->v[0] = v->low;
+ r->v[1] = v->low >> 8;
+ r->v[2] = v->low >> 16;
+ r->v[3] = v->low >> 24;
+ r->v[4] = v->hi;
+ r->v[5] = v->hi >> 8;
+ r->v[6] = v->hi >> 16;
+ r->v[7] = v->hi >> 24;
+}
diff --git a/src/io/varint1.c b/src/io/varint1.c
new file mode 100644
index 0000000..502765b
--- /dev/null
+++ b/src/io/varint1.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varint1(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varint1 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 1, 1, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result.low;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varint32.c b/src/io/varint32.c
new file mode 100644
index 0000000..f1c58cd
--- /dev/null
+++ b/src/io/varint32.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varint32(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varint32 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 32, 1, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result.low;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varint64.c b/src/io/varint64.c
new file mode 100644
index 0000000..5d0a3fd
--- /dev/null
+++ b/src/io/varint64.c
@@ -0,0 +1,28 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varint64(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varint64 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 64, 1, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ out->low = l->result.low;
+ out->hi = l->result.hi;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varint7.c b/src/io/varint7.c
new file mode 100644
index 0000000..d22d25c
--- /dev/null
+++ b/src/io/varint7.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varint7(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varint7 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 7, 1, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result.low;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varuint1.c b/src/io/varuint1.c
new file mode 100644
index 0000000..2f9587c
--- /dev/null
+++ b/src/io/varuint1.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varuint1(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varuint1 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 1, 0, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result.low;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varuint32.c b/src/io/varuint32.c
new file mode 100644
index 0000000..1ad2c35
--- /dev/null
+++ b/src/io/varuint32.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varuint32(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varuint32 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 32, 0, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result.low;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varuint64.c b/src/io/varuint64.c
new file mode 100644
index 0000000..5538bb8
--- /dev/null
+++ b/src/io/varuint64.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varuint64(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varuint64 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 64, 0, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/io/varuint7.c b/src/io/varuint7.c
new file mode 100644
index 0000000..e3c2e91
--- /dev/null
+++ b/src/io/varuint7.c
@@ -0,0 +1,27 @@
+/*
+ * 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/types.h>
+#include <nw/io.h>
+
+enum nw_state nwp_varuint7(const struct nw_io_cfg *const cfg,
+ struct nw_sm_leb128 *const l, nw_varuint7 *const out, void *const user)
+{
+ const enum nw_state ret = nwp_leb128(cfg, l, 7, 0, user);
+
+ if (!ret)
+ {
+ static const struct nw_sm_leb128 ll;
+
+ *out = l->result.low;
+ *l = ll;
+ }
+
+ return ret;
+}
diff --git a/src/load.c b/src/load.c
new file mode 100644
index 0000000..7b08be4
--- /dev/null
+++ b/src/load.c
@@ -0,0 +1,21 @@
+/*
+ * 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/types.h>
+
+enum nw_state nw_load(struct nw_mod *const m, struct nw_mod_out *const out)
+{
+ const enum nw_state n = m->next(m);
+
+ if (!n && out)
+ *out = m->out;
+
+ return n;
+}
diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt
new file mode 100644
index 0000000..0678c22
--- /dev/null
+++ b/src/log/CMakeLists.txt
@@ -0,0 +1,10 @@
+# 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/.
+
+if(NW_LOG AND NOT NW_LOG_CUSTOM)
+ target_sources(${PROJECT_NAME} PRIVATE log.c)
+endif()
diff --git a/src/log/log.c b/src/log/log.c
new file mode 100644
index 0000000..a0408a9
--- /dev/null
+++ b/src/log/log.c
@@ -0,0 +1,23 @@
+/*
+ * 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 <nw/log.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+int nwp_log(const char *const fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ return ret;
+}
diff --git a/src/op/CMakeLists.txt b/src/op/CMakeLists.txt
new file mode 100644
index 0000000..53036f0
--- /dev/null
+++ b/src/op/CMakeLists.txt
@@ -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/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ block.c
+ br.c
+ br_if.c
+ call.c
+ call_indirect.c
+ drop.c
+ end.c
+ get_local.c
+ get_global.c
+ i32_add.c
+ i32_and.c
+ i32_const.c
+ i32_eq.c
+ i32_eqz.c
+ i32_ge_s.c
+ i32_ge_u.c
+ i32_load.c
+ i32_load8_u.c
+ i32_lt_s.c
+ i32_mul.c
+ i32_ne.c
+ i32_or.c
+ i32_store.c
+ i32_sub.c
+ i64_const.c
+ i64_store.c
+ loop.c
+ nop.c
+ return.c
+ set_global.c
+ set_local.c
+ tee_local.c
+ tostr.c
+ unreachable.c
+)
+
+add_subdirectory(check)
diff --git a/src/op/block.c b/src/op/block.c
new file mode 100644
index 0000000..e35a722
--- /dev/null
+++ b/src/op/block.c
@@ -0,0 +1,17 @@
+/*
+ * 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/routines.h>
+
+void nwp_op_block(struct nw_interp *const i)
+{
+ nwp_start_block(i);
+}
diff --git a/src/op/br.c b/src/op/br.c
new file mode 100644
index 0000000..83ceca7
--- /dev/null
+++ b/src/op/br.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/interp.h>
+#include <nw/io.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state get_relative_depth(struct nw_interp *const i)
+{
+ struct nw_i_sm_br *const b = &i->sm.br;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, &b->leb128,
+ &b->relative_depth, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_break(i, b->relative_depth);
+ return NW_AGAIN;
+}
+
+void nwp_op_br(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_br b = {0};
+
+ i->next = get_relative_depth;
+ i->sm.br = b;
+}
diff --git a/src/op/br_if.c b/src/op/br_if.c
new file mode 100644
index 0000000..661c959
--- /dev/null
+++ b/src/op/br_if.c
@@ -0,0 +1,95 @@
+/*
+ * 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/ops.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static int condition(struct nw_interp *const i)
+{
+ const struct nw_i_sm_br_if *const b = &i->sm.br_if;
+
+ switch (i->push_type)
+ {
+ case NW_TYPE_I32:
+ return b->condition.i32;
+
+ case NW_TYPE_I64:
+ return b->condition.i64.low;
+
+ case NW_TYPE_F32:
+ return b->condition.f32;
+
+ case NW_TYPE_F64:
+ return b->condition.f64;
+ }
+
+ return 0;
+}
+
+static enum nw_state pop(struct nw_interp *const i)
+{
+ struct nw_i_sm_br_if *const b = &i->sm.br_if;
+ const enum nw_state n = nwp_stack_pop(i, &b->io);
+
+ if (n)
+ return n;
+ else if (condition(i))
+ nwp_break(i, b->relative_depth);
+ else
+ nwp_interp_resume(i);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_relative_depth(struct nw_interp *const i)
+{
+ struct nw_i_sm_br_if *const b = &i->sm.br_if;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, &b->leb128,
+ &b->relative_depth, cfg->user);
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (nwp_type_sz(i->push_type, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)i->push_type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->condition;
+ io.n = sz;
+ b->io = io;
+ i->next = pop;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_br_if(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_br_if b = {0};
+
+ i->next = get_relative_depth;
+ i->sm.br_if = b;
+}
diff --git a/src/op/call.c b/src/op/call.c
new file mode 100644
index 0000000..11a9183
--- /dev/null
+++ b/src/op/call.c
@@ -0,0 +1,50 @@
+/*
+ * 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/ops.h>
+#include <nw/routines.h>
+#include <nw/types.h>
+
+static enum nw_state get_pc(struct nw_interp *const i)
+{
+ struct nw_i_sm_call *const c = &i->sm.call;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&i->fr.pc, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_call(i, c->index);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_call *const c = &i->sm.call;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &c->index, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_pc;
+ return NW_AGAIN;
+}
+
+void nwp_op_call(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_call c = {0};
+
+ i->sm.call = c;
+ i->next = get_index;
+}
diff --git a/src/op/call_indirect.c b/src/op/call_indirect.c
new file mode 100644
index 0000000..f41a14d
--- /dev/null
+++ b/src/op/call_indirect.c
@@ -0,0 +1,64 @@
+/*
+ * 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/ops.h>
+#include <nw/routines.h>
+#include <nw/types.h>
+
+static enum nw_state get_pc(struct nw_interp *const i)
+{
+ struct nw_i_sm_call *const c = &i->sm.call;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&i->fr.pc, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_call(i, c->index);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_value(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_indirect *const ci = &i->sm.call_indirect;
+ struct nw_sm_leb128 *const l = &ci->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &ci->value, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_pc;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_indirect *const ci = &i->sm.call_indirect;
+ struct nw_sm_leb128 *const l = &ci->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &ci->index, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_value;
+ return NW_AGAIN;
+}
+
+void nwp_op_call_indirect(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_call_indirect ci = {0};
+
+ i->sm.call_indirect = ci;
+ i->next = get_index;
+}
diff --git a/src/op/check/CMakeLists.txt b/src/op/check/CMakeLists.txt
new file mode 100644
index 0000000..0745eb5
--- /dev/null
+++ b/src/op/check/CMakeLists.txt
@@ -0,0 +1,24 @@
+# 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
+ block.c
+ br_table.c
+ call.c
+ end.c
+ global_index.c
+ i64_const.c
+ local_index.c
+ memory_immediate.c
+ misc.c
+ no_immediate.c
+ relative_depth.c
+ varint32.c
+ varuint1.c
+ uint32.c
+ uint64.c
+)
diff --git a/src/op/check/block.c b/src/op/check/block.c
new file mode 100644
index 0000000..7d5176e
--- /dev/null
+++ b/src/op/check/block.c
@@ -0,0 +1,43 @@
+/*
+ * 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/ops.h>
+#include <nw/types.h>
+
+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);
+ enum nw_type t;
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &t) && type != 0x40)
+ {
+#ifdef NW_LOG
+ nwp_log("invalid block_type %#x\n", (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+
+ c->fn.blocks++;
+ m->next = m->sm.code.next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_block(struct nw_mod *const m)
+{
+ m->next = get_type;
+}
diff --git a/src/op/check/br_table.c b/src/op/check/br_table.c
new file mode 100644
index 0000000..d104669
--- /dev/null
+++ b/src/op/check/br_table.c
@@ -0,0 +1,78 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state loop(struct nw_mod *m);
+
+static enum nw_state get_default_target(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;
+ nw_varuint32 target;
+ const enum nw_state n = nwp_varuint32(cfg, l, &target, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_entry(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_t *const t = &c->u.target;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ nw_varuint32 entry;
+ const enum nw_state n = nwp_varuint32(cfg, l, &entry, cfg->user);
+
+ if (n)
+ return n;
+
+ t->i++;
+ m->next = loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state loop(struct nw_mod *const m)
+{
+ const struct nw_sm_c *const c = &m->sm.code;
+ const struct nw_sm_c_t *const t = &c->u.target;
+
+ m->next = t->i >= t->count ? get_default_target : get_entry;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_target_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;
+ struct nw_sm_c_t *const t = &c->u.target;
+ const enum nw_state n = nwp_varuint32(cfg, l, &t->count, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = loop;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_br_table(struct nw_mod *const m)
+{
+ static const struct nw_sm_c_t t = {0};
+
+ m->sm.code.u.target = t;
+ m->next = get_target_count;
+}
diff --git a/src/op/check/call.c b/src/op/check/call.c
new file mode 100644
index 0000000..7b02032
--- /dev/null
+++ b/src/op/check/call.c
@@ -0,0 +1,40 @@
+/*
+ * 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/ops.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_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+ else if (index >= c->count + m->cfg.n_imports)
+ {
+#ifdef NW_LOG
+ nwp_log("invalid function index %lu\n", (unsigned long)index);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = m->sm.code.next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_call(struct nw_mod *const m)
+{
+ m->next = get_index;
+}
diff --git a/src/op/check/end.c b/src/op/check/end.c
new file mode 100644
index 0000000..8ec6bf7
--- /dev/null
+++ b/src/op/check/end.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/ops.h>
+
+static enum nw_state run(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("unexpected end in function %lu\n", (unsigned long)c->entry_i);
+#endif
+ return NW_FATAL;
+ }
+
+ fn->blocks--;
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_end(struct nw_mod *const m)
+{
+ m->next = run;
+}
diff --git a/src/op/check/global_index.c b/src/op/check/global_index.c
new file mode 100644
index 0000000..b3a18bb
--- /dev/null
+++ b/src/op/check/global_index.c
@@ -0,0 +1,40 @@
+/*
+ * 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/ops.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_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+ else if (index >= m->global_count)
+ {
+#ifdef NW_LOG
+ nwp_log("invalid global index %lu\n", (unsigned long)index);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_global_index(struct nw_mod *const m)
+{
+ m->next = get_index;
+}
diff --git a/src/op/check/i64_const.c b/src/op/check/i64_const.c
new file mode 100644
index 0000000..e283642
--- /dev/null
+++ b/src/op/check/i64_const.c
@@ -0,0 +1,32 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state get_value(struct nw_mod *const m)
+{
+ nw_varint64 value;
+ 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_varint64(cfg, l, &value, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_varint64(struct nw_mod *const m)
+{
+ m->next = get_value;
+}
diff --git a/src/op/check/local_index.c b/src/op/check/local_index.c
new file mode 100644
index 0000000..a6df9ad
--- /dev/null
+++ b/src/op/check/local_index.c
@@ -0,0 +1,43 @@
+/*
+ * 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/ops.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_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+#if 0
+ else if (index >= c->fn.local_total)
+ {
+ LOG("%s: invalid local index %lu in function %lu\n", __func__,
+ (unsigned long)index, (unsigned long)c->entry_i);
+ return NW_FATAL;
+ }
+#else
+ /* TODO: take function parameters into account for local_total*/
+#endif
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_local_index(struct nw_mod *const m)
+{
+ m->next = get_index;
+}
diff --git a/src/op/check/memory_immediate.c b/src/op/check/memory_immediate.c
new file mode 100644
index 0000000..98aae52
--- /dev/null
+++ b/src/op/check/memory_immediate.c
@@ -0,0 +1,47 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state get_offset(struct nw_mod *const m)
+{
+ nw_varuint32 offset;
+ 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, &offset, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = m->sm.code.next;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_flags(struct nw_mod *const m)
+{
+ nw_varuint32 flags;
+ 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, &flags, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_offset;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_memory_immediate(struct nw_mod *const m)
+{
+ m->next = get_flags;
+}
diff --git a/src/op/check/misc.c b/src/op/check/misc.c
new file mode 100644
index 0000000..fc89d21
--- /dev/null
+++ b/src/op/check/misc.c
@@ -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/.
+ */
+
+#include <nanowasm/nw.h>
+#include <nw/io.h>
+#include <nw/ops.h>
+
+static enum nw_state run(struct nw_mod *const m)
+{
+ unsigned char op;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &op;
+ io.n = sizeof op;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+
+ m->next = m->sm.code.next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_misc(struct nw_mod *const m)
+{
+ m->next = run;
+}
diff --git a/src/op/check/no_immediate.c b/src/op/check/no_immediate.c
new file mode 100644
index 0000000..86cd22c
--- /dev/null
+++ b/src/op/check/no_immediate.c
@@ -0,0 +1,23 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state run(struct nw_mod *const m)
+{
+ m->next = m->sm.code.next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_no_immediate(struct nw_mod *const m)
+{
+ m->next = run;
+}
diff --git a/src/op/check/relative_depth.c b/src/op/check/relative_depth.c
new file mode 100644
index 0000000..41011b8
--- /dev/null
+++ b/src/op/check/relative_depth.c
@@ -0,0 +1,40 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state get_relative_depth(struct nw_mod *const m)
+{
+ nw_varuint32 relative_depth;
+ 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, &relative_depth, cfg->user);
+
+ if (n)
+ return n;
+ else if (relative_depth >= c->fn.blocks)
+ {
+#ifdef NW_LOG
+ nwp_log("invalid relative depth %lu\n", (unsigned long)relative_depth);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_relative_depth(struct nw_mod *const m)
+{
+ m->next = get_relative_depth;
+}
diff --git a/src/op/check/uint32.c b/src/op/check/uint32.c
new file mode 100644
index 0000000..28aa852
--- /dev/null
+++ b/src/op/check/uint32.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/ops.h>
+
+static enum nw_state get_value(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ const enum nw_state n = nwp_io_read(cfg, &c->io, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_uint32(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_io io = {0};
+
+ io.buf = &c->u;
+ io.n = sizeof (struct nw_leuint32);
+ c->io = io;
+ m->next = get_value;
+}
diff --git a/src/op/check/uint64.c b/src/op/check/uint64.c
new file mode 100644
index 0000000..a925999
--- /dev/null
+++ b/src/op/check/uint64.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/ops.h>
+
+static enum nw_state get_value(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_sm_c *const c = &m->sm.code;
+ const enum nw_state n = nwp_io_read(cfg, &c->io, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_uint64(struct nw_mod *const m)
+{
+ struct nw_sm_c *const c = &m->sm.code;
+ struct nw_sm_io io = {0};
+
+ io.buf = &c->u;
+ io.n = sizeof (struct nw_leuint64);
+ c->io = io;
+ m->next = get_value;
+}
diff --git a/src/op/check/varint32.c b/src/op/check/varint32.c
new file mode 100644
index 0000000..aff3904
--- /dev/null
+++ b/src/op/check/varint32.c
@@ -0,0 +1,32 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state get_value(struct nw_mod *const m)
+{
+ nw_varint32 value;
+ 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_varint32(cfg, l, &value, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_varint32(struct nw_mod *const m)
+{
+ m->next = get_value;
+}
diff --git a/src/op/check/varuint1.c b/src/op/check/varuint1.c
new file mode 100644
index 0000000..87ef6c4
--- /dev/null
+++ b/src/op/check/varuint1.c
@@ -0,0 +1,32 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state get_value(struct nw_mod *const m)
+{
+ nw_varuint1 value;
+ 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_varuint1(cfg, l, &value, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = c->next;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_varuint1(struct nw_mod *const m)
+{
+ m->next = get_value;
+}
diff --git a/src/op/drop.c b/src/op/drop.c
new file mode 100644
index 0000000..3b5dffd
--- /dev/null
+++ b/src/op/drop.c
@@ -0,0 +1,101 @@
+/*
+ * 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 <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/opcodes.h>
+#include <nw/ops.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state pop(struct nw_interp *const i)
+{
+ struct nw_i_sm_drop *const d = &i->sm.drop;
+ const enum nw_state n = nwp_stack_pop(i, &d->io);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state check(struct nw_interp *const i)
+{
+ struct nw_i_sm_drop *const d = &i->sm.drop;
+ const struct nw_frame *const fr = &i->fr;
+ const struct nw_return *const r = &fr->prev_ret;
+ const unsigned char prev = fr->prev_op;
+ size_t sz;
+
+ if (prev != OP_CALL && prev != OP_CALL_INDIRECT)
+ {
+ static const char *const exc =
+ "expected call or call_indirect before drop";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, got: %#x\n", exc, (unsigned)prev);
+#endif
+ return NW_FATAL;
+ }
+ else if (!r->count)
+ {
+ static const char *const exc =
+ "called drop from function without return value";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+ else if (r->type != i->push_type)
+ {
+ static const char *const exc =
+ "mismatch between return type and last push type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, expected: %#hhx, got: %#hhx\n", exc,
+ (unsigned char)i->push_type, (unsigned char)r->type);
+#endif
+ return NW_FATAL;
+ }
+ else if (nwp_type_sz(r->type, &sz))
+ {
+ static const char *const exc = "invalid return type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)r->type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &d->value;
+ io.n = sz;
+ d->io = io;
+ i->next = pop;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_drop(struct nw_interp *const i)
+{
+ const struct nw_i_sm_drop d = {{0}};
+
+ i->sm.drop = d;
+ i->next = check;
+}
diff --git a/src/op/end.c b/src/op/end.c
new file mode 100644
index 0000000..9eb575d
--- /dev/null
+++ b/src/op/end.c
@@ -0,0 +1,67 @@
+/*
+ * 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/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state run(struct nw_interp *const i)
+{
+ const struct nw_i_sm_end *const e = &i->sm.end;
+ struct nw_frame *const f = &i->fr;
+
+ if (i->set)
+ return NW_OK;
+ else if (f->block_i)
+ {
+ f->block_i--;
+ nwp_interp_resume(i);
+ }
+ else
+ {
+ const long expected = f->start + f->body_size;
+
+ if (e->offset != expected)
+ {
+ static const char *const exc = "unexpected end";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+ else if (f->child)
+ nwp_unwind(i);
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_offset(struct nw_interp *const i)
+{
+ struct nw_i_sm_end *const e = &i->sm.end;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&e->offset, cfg->user);
+
+ if (n)
+ return n;
+
+ return run(i);
+}
+
+void nwp_op_end(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_end e = {0};
+
+ i->sm.end = e;
+ i->next = get_offset;
+}
diff --git a/src/op/get_global.c b/src/op/get_global.c
new file mode 100644
index 0000000..eb701b5
--- /dev/null
+++ b/src/op/get_global.c
@@ -0,0 +1,93 @@
+/*
+ * 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/global.h>
+#include <nw/io.h>
+#include <nw/interp.h>
+#include <nw/stack.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ const enum nw_state n = nwp_stack_push(i, &g->io);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state read_global(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ struct nw_sm_io *const io = &g->io;
+ struct nw_global *const gl = &g->gl;
+ const enum nw_state n = nwp_global_load(i, io, g->index);
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (nwp_type_sz(gl->type, &sz))
+ {
+ static const char *const exc = "invalid global type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)gl->type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &gl->value;
+ io.n = sz;
+ g->io = io;
+ i->next = push;
+ i->push_type = gl->type;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ struct nw_sm_leb128 *const l = &g->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &g->index, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &g->gl;
+ io.n = sizeof g->gl;
+ g->io = io;
+ i->next = read_global;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_get_global(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_get_global g = {0};
+
+ i->sm.get_global = g;
+ i->next = get_index;
+}
diff --git a/src/op/get_local.c b/src/op/get_local.c
new file mode 100644
index 0000000..6a9a42a
--- /dev/null
+++ b/src/op/get_local.c
@@ -0,0 +1,150 @@
+
+/*
+ * 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/ops.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static int init_io(struct nw_interp *const i, const enum nw_type t)
+{
+ struct nw_i_sm_get_local *const gl = &i->sm.get_local;
+ size_t sz;
+
+ if (nwp_type_sz(t, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)t);
+#endif
+ return -1;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &gl->value;
+ io.n = sz;
+ gl->io = io;
+ }
+
+ return 0;
+}
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_local *const gl = &i->sm.get_local;
+ const enum nw_state n = nwp_stack_push(i, &gl->io);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_local(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_local *const gl = &i->sm.get_local;
+ const struct nw_local_meta *const m = &gl->f.l.meta;
+ const enum nw_state n = nwp_stack_read(i, &gl->io, gl->f.l.addr);
+
+ if (n)
+ return n;
+ else if (init_io(i, m->type))
+ return NW_FATAL;
+
+ i->next = push;
+ i->push_type = m->type;
+ return NW_AGAIN;
+}
+
+static enum nw_state prepare_local(struct nw_interp *const i)
+{
+ const struct nw_local_meta *const m = &i->sm.get_local.f.l.meta;
+
+ if (init_io(i, m->type))
+ return NW_FATAL;
+
+ i->next = get_local;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_local *const gl = &i->sm.get_local;
+ const struct nw_find_param_out *const o = &gl->f.p.out;
+ const enum nw_state n = nwp_stack_read(i, &gl->io, o->addr);
+
+ if (n)
+ return n;
+ else if (init_io(i, o->type))
+ return NW_FATAL;
+
+ i->next = push;
+ i->push_type = o->type;
+ return NW_AGAIN;
+}
+
+static enum nw_state prepare_param(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_local *const gl = &i->sm.get_local;
+ const struct nw_find_param_out *const o = &gl->f.p.out;
+
+ if (init_io(i, o->type))
+ return NW_FATAL;
+
+ i->next = get_param;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_local *const gl = &i->sm.get_local;
+ struct nw_sm_leb128 *const l = &gl->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const struct nw_frame *const fr = &i->fr;
+ const struct nw_fn *const fn = &fr->fn;
+ const enum nw_state n = nwp_varuint32(cfg, l, &gl->index, cfg->user);
+ nw_varuint32 index;
+
+ if (n)
+ return n;
+ else if (gl->index < fn->param_count)
+ nwp_find_param(i, &gl->f.p, gl->index, prepare_param, NULL);
+ else if ((index = gl->index - fn->param_count) >= fr->local_count)
+ {
+ static const char *const exc = "invalid local index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)gl->index);
+#endif
+ return NW_FATAL;
+ }
+ else
+ nwp_find_local(i, &gl->f.l, index, prepare_local, NULL);
+
+ return NW_AGAIN;
+}
+
+void nwp_op_get_local(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_get_local gl = {0};
+
+ i->sm.get_local = gl;
+ i->next = get_index;
+}
diff --git a/src/op/i32_add.c b/src/op/i32_add.c
new file mode 100644
index 0000000..fe2ec69
--- /dev/null
+++ b/src/op/i32_add.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int add(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = (unsigned long)o->left.i32 + (unsigned long)o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_add(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, add);
+}
diff --git a/src/op/i32_and.c b/src/op/i32_and.c
new file mode 100644
index 0000000..d52bead
--- /dev/null
+++ b/src/op/i32_and.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int and(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = o->left.i32 & o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_and(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, and);
+}
diff --git a/src/op/i32_const.c b/src/op/i32_const.c
new file mode 100644
index 0000000..80dc9dd
--- /dev/null
+++ b/src/op/i32_const.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/io.h>
+#include <nw/interp.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/stack.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_i32_const *const c = &i->sm.i32_const;
+ const enum nw_state n = nwp_stack_push(i, &c->io);
+
+ if (n)
+ return n;
+
+ i->push_type = NW_TYPE_I32;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_value(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_i32_const *const c = &i->sm.i32_const;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varint32(cfg, l, &c->value, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &c->value;
+ io.n = sizeof c->value;
+ c->io = io;
+ i->next = push;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_i32_const(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_i32_const c = {0};
+
+ i->next = get_value;
+ i->sm.i32_const = c;
+}
diff --git a/src/op/i32_eq.c b/src/op/i32_eq.c
new file mode 100644
index 0000000..3f2cc42
--- /dev/null
+++ b/src/op/i32_eq.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int eq(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = o->left.i32 == o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_eq(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, eq);
+}
diff --git a/src/op/i32_eqz.c b/src/op/i32_eqz.c
new file mode 100644
index 0000000..cd533fd
--- /dev/null
+++ b/src/op/i32_eqz.c
@@ -0,0 +1,22 @@
+/*
+ * 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/routines.h>
+
+static int eqz(const union nw_value *const in, union nw_value *const out)
+{
+ out->i32 = !in->i32;
+ return 0;
+}
+
+void nwp_op_i32_eqz(struct nw_interp *const i)
+{
+ nwp_unary(i, NW_TYPE_I32, eqz);
+}
diff --git a/src/op/i32_ge_s.c b/src/op/i32_ge_s.c
new file mode 100644
index 0000000..137b80d
--- /dev/null
+++ b/src/op/i32_ge_s.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int ge_s(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = o->left.i32 >= o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_ge_s(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, ge_s);
+}
diff --git a/src/op/i32_ge_u.c b/src/op/i32_ge_u.c
new file mode 100644
index 0000000..c280dec
--- /dev/null
+++ b/src/op/i32_ge_u.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int ge_u(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = (unsigned long)o->left.i32 >= (unsigned long)o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_ge_u(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, ge_u);
+}
diff --git a/src/op/i32_load.c b/src/op/i32_load.c
new file mode 100644
index 0000000..e2d883b
--- /dev/null
+++ b/src/op/i32_load.c
@@ -0,0 +1,89 @@
+/*
+ * 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/interp.h>
+#include <nw/linear.h>
+#include <nw/stack.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_load *const l = &i->sm.load;
+ const enum nw_state n = nwp_stack_push(i, &l->io);
+
+ if (n)
+ return n;
+
+ i->push_type = NW_TYPE_I32;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state load(struct nw_interp *const i)
+{
+ struct nw_i_sm_load *const l = &i->sm.load;
+ const enum nw_state n = nwp_linear_load(i, &l->io, l->addr + l->imm.offset);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ l->value.i32 = nwp_leuint32(&l->value.v32);
+ io.buf = &l->value.i32;
+ io.n = sizeof l->value.i32;
+ l->io = io;
+ i->next = push;
+ }
+
+ i->next = push;
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_addr(struct nw_interp *const i)
+{
+ struct nw_i_sm_load *const l = &i->sm.load;
+ const enum nw_state n = nwp_stack_pop(i, &l->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &l->value.i32;
+ io.n = sizeof l->value.i32;
+ l->io = io;
+ i->next = load;
+ }
+
+ return NW_AGAIN;
+}
+
+static void prepare(struct nw_interp *const i)
+{
+ struct nw_i_sm_imm_out imm = i->sm.imm.out;
+ struct nw_i_sm_load *const pl = &i->sm.load, l = {0};
+
+ l.imm = imm;
+ l.io.buf = &pl->addr;
+ l.io.n = sizeof pl->addr;
+ *pl = l;
+ i->next = pop_addr;
+}
+
+void nwp_op_i32_load(struct nw_interp *const i)
+{
+ nwp_mem_imm(i, prepare);
+}
diff --git a/src/op/i32_load8_u.c b/src/op/i32_load8_u.c
new file mode 100644
index 0000000..da4282b
--- /dev/null
+++ b/src/op/i32_load8_u.c
@@ -0,0 +1,89 @@
+/*
+ * 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/linear.h>
+#include <nw/io.h>
+#include <nw/interp.h>
+#include <nw/stack.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_load *const l = &i->sm.load;
+ const enum nw_state n = nwp_stack_push(i, &l->io);
+
+ if (n)
+ return n;
+
+ i->push_type = NW_TYPE_I32;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state load(struct nw_interp *const i)
+{
+ struct nw_i_sm_load *const l = &i->sm.load;
+ const enum nw_state n = nwp_linear_load(i, &l->io, l->addr + l->imm.offset);
+
+ if (n)
+ return n;
+ else
+ {
+ const unsigned char b = l->value.i8;
+ struct nw_sm_io io = {0};
+
+ io.buf = &l->value.i32;
+ io.n = sizeof l->value.i32;
+ l->value.i32 = b;
+ l->io = io;
+ i->next = push;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_addr(struct nw_interp *const i)
+{
+ struct nw_i_sm_load *const l = &i->sm.load;
+ const enum nw_state n = nwp_stack_pop(i, &l->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &l->value.i8;
+ io.n = sizeof l->value.i8;
+ l->io = io;
+ i->next = load;
+ }
+
+ return NW_AGAIN;
+}
+
+static void prepare(struct nw_interp *const i)
+{
+ struct nw_i_sm_imm_out imm = i->sm.imm.out;
+ struct nw_i_sm_load *const pl = &i->sm.load, l = {0};
+
+ l.imm = imm;
+ l.io.buf = &pl->addr;
+ l.io.n = sizeof pl->addr;
+ *pl = l;
+ i->next = pop_addr;
+}
+
+void nwp_op_i32_load8_u(struct nw_interp *const i)
+{
+ nwp_mem_imm(i, prepare);
+}
diff --git a/src/op/i32_lt_s.c b/src/op/i32_lt_s.c
new file mode 100644
index 0000000..3e583c3
--- /dev/null
+++ b/src/op/i32_lt_s.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int lt_s(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = o->left.i32 < o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_lt_s(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, lt_s);
+}
diff --git a/src/op/i32_mul.c b/src/op/i32_mul.c
new file mode 100644
index 0000000..8af64de
--- /dev/null
+++ b/src/op/i32_mul.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int mul(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = (unsigned long)o->left.i32 * (unsigned long)o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_mul(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, mul);
+}
diff --git a/src/op/i32_ne.c b/src/op/i32_ne.c
new file mode 100644
index 0000000..c62a2a7
--- /dev/null
+++ b/src/op/i32_ne.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int ne(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = o->left.i32 != o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_ne(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, ne);
+}
diff --git a/src/op/i32_or.c b/src/op/i32_or.c
new file mode 100644
index 0000000..774dbed
--- /dev/null
+++ b/src/op/i32_or.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int or(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = o->left.i32 | o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_or(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, or);
+}
diff --git a/src/op/i32_store.c b/src/op/i32_store.c
new file mode 100644
index 0000000..caff6b9
--- /dev/null
+++ b/src/op/i32_store.c
@@ -0,0 +1,90 @@
+/*
+ * 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/linear.h>
+#include <nw/io.h>
+#include <nw/interp.h>
+#include <nw/stack.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state store(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const s = &i->sm.store;
+ const enum nw_state n = nwp_linear_store(i, &s->io, s->addr + s->imm.offset);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_addr(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const s = &i->sm.store;
+ const enum nw_state n = nwp_stack_pop(i, &s->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &s->value.i32;
+ io.n = sizeof s->value.i32;
+ s->io = io;
+ i->next = store;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_value(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const s = &i->sm.store;
+ const enum nw_state n = nwp_stack_pop(i, &s->io);
+
+ if (n)
+ return n;
+ else
+ {
+ const struct nw_sm_io io = {0};
+
+ s->value.i32 = nwp_leuint32(&s->value.v32);
+ s->io = io;
+ s->io.buf = &s->addr;
+ s->io.n = sizeof s->addr;
+ i->next = pop_addr;
+ }
+
+ return NW_AGAIN;
+}
+
+static void prepare(struct nw_interp *const i)
+{
+ const struct nw_i_sm_store s = {0};
+ struct nw_i_sm_store *const ps = &i->sm.store;
+ struct nw_sm_io *const io = &ps->io;
+ struct nw_i_sm_imm_out out;
+
+ out = i->sm.imm.out;
+ *ps = s;
+ ps->imm = out;
+ io->buf = &ps->value.i32;
+ io->n = sizeof ps->value.i32;
+ i->next = pop_value;
+}
+
+void nwp_op_i32_store(struct nw_interp *const i)
+{
+ nwp_mem_imm(i, prepare);
+}
diff --git a/src/op/i32_sub.c b/src/op/i32_sub.c
new file mode 100644
index 0000000..46c4f66
--- /dev/null
+++ b/src/op/i32_sub.c
@@ -0,0 +1,23 @@
+/*
+ * 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/routines.h>
+
+static int sub(const struct nw_i_sm_arithm_out *const o,
+ union nw_value *const res)
+{
+ res->i32 = (unsigned long)o->left.i32 - (unsigned long)o->right.i32;
+ return 0;
+}
+
+void nwp_op_i32_sub(struct nw_interp *const i)
+{
+ nwp_arithm(i, NW_TYPE_I32, sub);
+}
diff --git a/src/op/i64_const.c b/src/op/i64_const.c
new file mode 100644
index 0000000..20d539b
--- /dev/null
+++ b/src/op/i64_const.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/io.h>
+#include <nw/interp.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/stack.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_i64_const *const c = &i->sm.i64_const;
+ const enum nw_state n = nwp_stack_push(i, &c->io);
+
+ if (n)
+ return n;
+
+ i->push_type = NW_TYPE_I64;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_value(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_i64_const *const c = &i->sm.i64_const;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint64(cfg, l, &c->value, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &c->value;
+ io.n = sizeof c->value;
+ c->io = io;
+ i->next = push;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_i64_const(struct nw_interp *const i)
+{
+ const struct nw_i_sm_i64_const c = {{0}};
+
+ i->next = get_value;
+ i->sm.i64_const = c;
+}
diff --git a/src/op/i64_store.c b/src/op/i64_store.c
new file mode 100644
index 0000000..fbae8b4
--- /dev/null
+++ b/src/op/i64_store.c
@@ -0,0 +1,87 @@
+/*
+* 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/linear.h>
+#include <nw/io.h>
+#include <nw/interp.h>
+#include <nw/stack.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state store(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const s = &i->sm.store;
+ const enum nw_state n = nwp_linear_store(i, &s->io,
+ s->addr + s->imm.offset);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_addr(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const s = &i->sm.store;
+ const enum nw_state n = nwp_stack_pop(i, &s->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &s->value.i64;
+ io.n = sizeof s->value.i64;
+ s->io = io;
+ i->next = store;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_value(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const s = &i->sm.store;
+ const enum nw_state n = nwp_stack_pop(i, &s->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ nwp_leuint64(&s->value.v64, &s->value.i64);
+ io.buf = &s->addr;
+ io.n = sizeof s->addr;
+ s->io = io;
+ i->next = pop_addr;
+ }
+
+ return NW_AGAIN;
+}
+
+static void prepare(struct nw_interp *const i)
+{
+ struct nw_i_sm_store *const ps = &i->sm.store, s = {0};
+
+ s.imm = i->sm.imm.out;
+ s.io.buf = &ps->value.v64;
+ s.io.n = sizeof ps->value.v64;
+ *ps =s;
+ i->next = pop_value;
+}
+
+void nwp_op_i64_store(struct nw_interp *const i)
+{
+ nwp_mem_imm(i, prepare);
+}
diff --git a/src/op/loop.c b/src/op/loop.c
new file mode 100644
index 0000000..e129610
--- /dev/null
+++ b/src/op/loop.c
@@ -0,0 +1,16 @@
+/*
+ * 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/routines.h>
+
+void nwp_op_loop(struct nw_interp *const i)
+{
+ nwp_start_block(i);
+}
diff --git a/src/op/nop.c b/src/op/nop.c
new file mode 100644
index 0000000..b6ef7c2
--- /dev/null
+++ b/src/op/nop.c
@@ -0,0 +1,17 @@
+/*
+ * 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/ops.h>
+
+void nwp_op_nop(struct nw_interp *const i)
+{
+ nwp_interp_resume(i);
+}
diff --git a/src/op/return.c b/src/op/return.c
new file mode 100644
index 0000000..0b2a129
--- /dev/null
+++ b/src/op/return.c
@@ -0,0 +1,19 @@
+/*
+ * 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/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+void nwp_op_return(struct nw_interp *const i)
+{
+ nwp_unwind(i);
+}
diff --git a/src/op/set_global.c b/src/op/set_global.c
new file mode 100644
index 0000000..9448c32
--- /dev/null
+++ b/src/op/set_global.c
@@ -0,0 +1,122 @@
+/*
+ * 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/global.h>
+#include <nw/io.h>
+#include <nw/interp.h>
+#include <nw/stack.h>
+#include <nw/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state set_global(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ const enum nw_state n = nwp_global_store(i, &g->io, g->index);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state pop(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ const enum nw_state n = nwp_stack_pop(i, &g->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &g->gl;
+ io.n = sizeof g->gl;
+ g->io = io;
+ i->next = set_global;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state read_global(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ struct nw_global *const gl = &g->gl;
+ const enum nw_state n = nwp_global_load(i, &g->io, g->index);
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (!gl->mutability)
+ {
+ static const char *const exc = "cannot set non-mutable global";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, index: %lu\n", exc, (unsigned long)g->index);
+#endif
+ return NW_FATAL;
+ }
+ else if (nwp_type_sz(gl->type, &sz))
+ {
+ static const char *const exc = "invalid global type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)gl->type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &gl->value;
+ io.n = sz;
+ g->io = io;
+ i->next = pop;
+ i->push_type = gl->type;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_get_global *const g = &i->sm.get_global;
+ struct nw_sm_leb128 *const l = &g->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &g->index, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &g->gl;
+ io.n = sizeof g->gl;
+ g->io = io;
+ i->next = read_global;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_set_global(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_set_global g = {0};
+
+ i->sm.set_global = g;
+ i->next = get_index;
+}
diff --git a/src/op/set_local.c b/src/op/set_local.c
new file mode 100644
index 0000000..0488e16
--- /dev/null
+++ b/src/op/set_local.c
@@ -0,0 +1,24 @@
+/*
+ * 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/ops.h>
+#include <nw/routines.h>
+
+static enum nw_state resume(struct nw_interp *const i)
+{
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+void nwp_op_set_local(struct nw_interp *const i)
+{
+ nwp_set_local(i, resume);
+}
diff --git a/src/op/tee_local.c b/src/op/tee_local.c
new file mode 100644
index 0000000..b95603c
--- /dev/null
+++ b/src/op/tee_local.c
@@ -0,0 +1,64 @@
+/*
+ * 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/log.h>
+#include <nw/ops.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_tee_local *const t = &i->sm.tee_local;
+ const enum nw_state n = nwp_stack_push(i, &t->io);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state prepare(struct nw_interp *const i)
+{
+ struct nw_i_sm_tee_local *const t = &i->sm.tee_local;
+ struct nw_i_sm_set_local_out o;
+ size_t sz;
+
+ o = i->sm.set_local.out;
+
+ if (nwp_type_sz(o.type, &sz))
+ {
+ static const char *const exc = "invalid local type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)o.type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_i_sm_tee_local tl = {{0}};
+
+ tl.in = o;
+ tl.io.buf = &t->in.value;
+ tl.io.n = sz;
+ *t = tl;
+ i->next = push;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_op_tee_local(struct nw_interp *const i)
+{
+ nwp_set_local(i, prepare);
+}
diff --git a/src/op/tostr.c b/src/op/tostr.c
new file mode 100644
index 0000000..b2ac6dd
--- /dev/null
+++ b/src/op/tostr.c
@@ -0,0 +1,278 @@
+/*
+ * 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/opcodes.h>
+
+const char *nwp_op_tostr(const enum opcode op)
+{
+ static const char *const s[] =
+ {
+ "OP_UNREACHABLE",
+ "OP_NOP",
+ "OP_BLOCK",
+ "OP_LOOP",
+ "OP_IF",
+ "OP_ELSE",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "OP_END",
+ "OP_BR",
+ "OP_BR_IF",
+ "OP_BR_TABLE",
+ "OP_RETURN",
+ "OP_CALL",
+ "OP_CALL_INDIRECT",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "OP_DROP",
+ "OP_SELECT",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "OP_GET_LOCAL",
+ "OP_SET_LOCAL",
+ "OP_TEE_LOCAL",
+ "OP_GET_GLOBAL",
+ "OP_SET_GLOBAL",
+ NULL,
+ NULL,
+ NULL,
+ "OP_I32_LOAD",
+ "OP_I64_LOAD",
+ "OP_F32_LOAD",
+ "OP_F64_LOAD",
+ "OP_I32_LOAD8_S",
+ "OP_I32_LOAD8_U",
+ "OP_I32_LOAD16_S",
+ "OP_I32_LOAD16_U",
+ "OP_I64_LOAD8_S",
+ "OP_I64_LOAD8_U",
+ "OP_I64_LOAD16_S",
+ "OP_I64_LOAD16_U",
+ "OP_I64_LOAD32_S",
+ "OP_I64_LOAD32_U",
+ "OP_I32_STORE",
+ "OP_I64_STORE",
+ "OP_F32_STORE",
+ "OP_F64_STORE",
+ "OP_I32_STORE8",
+ "OP_I32_STORE16",
+ "OP_I64_STORE8",
+ "OP_I64_STORE16",
+ "OP_I64_STORE32",
+ "OP_CURRENT_MEMORY",
+ "OP_GROW_MEMORY",
+ "OP_I32_CONST",
+ "OP_I64_CONST",
+ "OP_F32_CONST",
+ "OP_F64_CONST",
+ "OP_I32_EQZ",
+ "OP_I32_EQ",
+ "OP_I32_NE",
+ "OP_I32_LT_S",
+ "OP_I32_LT_U",
+ "OP_I32_GT_S",
+ "OP_I32_GT_U",
+ "OP_I32_LE_S",
+ "OP_I32_LE_U",
+ "OP_I32_GE_S",
+ "OP_I32_GE_U",
+ "OP_I64_EQZ",
+ "OP_I64_EQ",
+ "OP_I64_NE",
+ "OP_I64_LT_S",
+ "OP_I64_LT_U",
+ "OP_I64_GT_S",
+ "OP_I64_GT_U",
+ "OP_I64_LE_S",
+ "OP_I64_LE_U",
+ "OP_I64_GE_S",
+ "OP_I64_GE_U",
+ "OP_F32_EQ",
+ "OP_F32_NE",
+ "OP_F32_LT",
+ "OP_F32_GT",
+ "OP_F32_LE",
+ "OP_F32_GE",
+ "OP_F64_EQ",
+ "OP_F64_NE",
+ "OP_F64_LT",
+ "OP_F64_GT",
+ "OP_F64_LE",
+ "OP_F64_GE",
+ "OP_I32_CLZ",
+ "OP_I32_CTZ",
+ "OP_I32_POPCNT",
+ "OP_I32_ADD",
+ "OP_I32_SUB",
+ "OP_I32_MUL",
+ "OP_I32_DIV_S",
+ "OP_I32_DIV_U",
+ "OP_I32_REM_S",
+ "OP_I32_REM_U",
+ "OP_I32_AND",
+ "OP_I32_OR",
+ "OP_I32_XOR",
+ "OP_I32_SHL",
+ "OP_I32_SHR_S",
+ "OP_I32_SHR_U",
+ "OP_I32_ROTL",
+ "OP_I32_ROTR",
+ "OP_I64_CLZ",
+ "OP_I64_CTZ",
+ "OP_I64_POPCNT",
+ "OP_I64_ADD",
+ "OP_I64_SUB",
+ "OP_I64_MUL",
+ "OP_I64_DIV_S",
+ "OP_I64_DIV_U",
+ "OP_I64_REM_S",
+ "OP_I64_REM_U",
+ "OP_I64_AND",
+ "OP_I64_OR",
+ "OP_I64_XOR",
+ "OP_I64_SHL",
+ "OP_I64_SHR_S",
+ "OP_I64_SHR_U",
+ "OP_I64_ROTL",
+ "OP_I64_ROTR",
+ "OP_F32_ABS",
+ "OP_F32_NEG",
+ "OP_F32_CEIL",
+ "OP_F32_FLOOR",
+ "OP_F32_TRUNC",
+ "OP_F32_NEAREST",
+ "OP_F32_SQRT",
+ "OP_F32_ADD",
+ "OP_F32_SUB",
+ "OP_F32_MUL",
+ "OP_F32_DIV",
+ "OP_F32_MIN",
+ "OP_F32_MAX",
+ "OP_F32_COPYSIGN",
+ "OP_F64_ABS",
+ "OP_F64_NEG",
+ "OP_F64_CEIL",
+ "OP_F64_FLOOR",
+ "OP_F64_TRUNC",
+ "OP_F64_NEAREST",
+ "OP_F64_SQRT",
+ "OP_F64_ADD",
+ "OP_F64_SUB",
+ "OP_F64_MUL",
+ "OP_F64_DIV",
+ "OP_F64_MIN",
+ "OP_F64_MAX",
+ "OP_F64_COPYSIGN",
+ "OP_I32_WRAP_I64",
+ "OP_I32_TRUNC_S_F32",
+ "OP_I32_TRUNC_U_F32",
+ "OP_I32_TRUNC_S_F64",
+ "OP_I32_TRUNC_U_F64",
+ "OP_I64_EXTEND_S_I32",
+ "OP_I64_EXTEND_U_I32",
+ "OP_I64_TRUNC_S_F32",
+ "OP_I64_TRUNC_U_F32",
+ "OP_I64_TRUNC_S_F64",
+ "OP_I64_TRUNC_U_F64",
+ "OP_F32_CONVERT_S_I32",
+ "OP_F32_CONVERT_U_I32",
+ "OP_F32_CONVERT_S_I64",
+ "OP_F32_CONVERT_U_I64",
+ "OP_F32_DEMOTE_F64",
+ "OP_F64_CONVERT_S_I32",
+ "OP_F64_CONVERT_U_I32",
+ "OP_F64_CONVERT_S_I64",
+ "OP_F64_CONVERT_U_I64",
+ "OP_F64_PROMOTE_F32",
+ "OP_I32_REINTERPRET_F32",
+ "OP_I64_REINTERPRET_F64",
+ "OP_F32_REINTERPRET_I32",
+ "OP_F64_REINTERPRET_I64",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "OP_MISC",
+ };
+
+ if (op < 0 || op >= sizeof s / sizeof *s)
+ return "invalid opcode";
+
+ return s[op];
+}
diff --git a/src/op/unreachable.c b/src/op/unreachable.c
new file mode 100644
index 0000000..6ecd374
--- /dev/null
+++ b/src/op/unreachable.c
@@ -0,0 +1,38 @@
+/*
+ * 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/log.h>
+#include <nw/ops.h>
+
+static enum nw_state tell(struct nw_interp *const i)
+{
+ struct nw_i_sm_unreachable *const u = &i->sm.unreachable;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ static const char *const exc = "unreachable";
+ const enum nw_state n = cfg->tell(&u->offset, cfg->user);
+
+ if (n)
+ return n;
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, offset: %ld\n", exc, u->offset);
+#endif
+ return NW_FATAL;
+}
+
+void nwp_op_unreachable(struct nw_interp *const i)
+{
+ static const struct nw_i_sm_unreachable u = {0};
+
+ i->sm.unreachable = u;
+ i->next = tell;
+}
diff --git a/src/rexc.c b/src/rexc.c
new file mode 100644
index 0000000..b2c73a4
--- /dev/null
+++ b/src/rexc.c
@@ -0,0 +1,15 @@
+/*
+ * 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>
+
+const char *nw_rexc(const struct nw_inst *const i)
+{
+ return i->interp.exception;
+}
diff --git a/src/routines/CMakeLists.txt b/src/routines/CMakeLists.txt
new file mode 100644
index 0000000..05c055b
--- /dev/null
+++ b/src/routines/CMakeLists.txt
@@ -0,0 +1,32 @@
+# 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
+ arithm.c
+ break.c
+ call.c
+ call_function.c
+ call_import.c
+ call_indirect.c
+ check_magic.c
+ check_version.c
+ find_function.c
+ find_local.c
+ find_param.c
+ get_function_type.c
+ get_import_type.c
+ init_data.c
+ init_globals.c
+ mem_imm.c
+ section.c
+ set_local.c
+ start_block.c
+ unary.c
+ unwind.c
+)
+
+add_subdirectory(section)
diff --git a/src/routines/arithm.c b/src/routines/arithm.c
new file mode 100644
index 0000000..6ab4799
--- /dev/null
+++ b/src/routines/arithm.c
@@ -0,0 +1,119 @@
+/*
+ * 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/interp.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_arithm *const s = &i->sm.arithm;
+ const enum nw_state n = nwp_stack_push(i, &s->io);
+
+ if (n)
+ return n;
+
+ i->push_type = s->type;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static int init_io(struct nw_interp *const i)
+{
+ struct nw_i_sm_arithm *const s = &i->sm.arithm;
+ size_t sz;
+
+ if (nwp_type_sz(s->type, &sz))
+ {
+ static const char *const exc = "invalid operand type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)s->type);
+#endif
+ return -1;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &s->value;
+ io.n = sz;
+ s->io = io;
+ }
+
+ return 0;
+}
+
+static enum nw_state pop_left(struct nw_interp *const i)
+{
+ struct nw_i_sm_arithm *const s = &i->sm.arithm;
+ const enum nw_state n = nwp_stack_pop(i, &s->io);
+
+ if (n)
+ return n;
+
+ s->out.left = s->value;
+
+ if (s->op(&s->out, &s->value))
+ {
+ static const char *const exc = "arithmetic operation failed";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+ else if (init_io(i))
+ return NW_FATAL;
+
+ i->next = push;
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_right(struct nw_interp *const i)
+{
+ struct nw_i_sm_arithm *const s = &i->sm.arithm;
+ const enum nw_state n = nwp_stack_pop(i, &s->io);
+
+ if (n)
+ return n;
+ else if (init_io(i))
+ return NW_FATAL;
+
+ s->out.right = s->value;
+ i->next = pop_left;
+ return NW_AGAIN;
+}
+
+static enum nw_state prepare_right(struct nw_interp *const i)
+{
+ if (init_io(i))
+ return NW_FATAL;
+
+ i->next = pop_right;
+ return NW_AGAIN;
+}
+
+void nwp_arithm(struct nw_interp *const i, const enum nw_type t,
+ int (*const op)(const struct nw_i_sm_arithm_out *, union nw_value *))
+{
+ const struct nw_i_sm_arithm s = {0};
+ struct nw_i_sm_arithm *const ps = &i->sm.arithm;
+
+ *ps = s;
+ ps->type = t;
+ ps->op = op;
+ i->next = prepare_right;
+}
diff --git a/src/routines/break.c b/src/routines/break.c
new file mode 100644
index 0000000..3ee94a1
--- /dev/null
+++ b/src/routines/break.c
@@ -0,0 +1,243 @@
+/*
+ * 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_pc(struct nw_interp *);
+
+static enum nw_state seek_dst(struct nw_interp *const i)
+{
+ const struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const unsigned long dst = nwp_leuint32(&b->dst);
+ const enum nw_state n = cfg->seek(dst, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state skip(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const unsigned long n_labels = nwp_leuint32(&b->n);
+
+ if (++b->label_i >= n_labels)
+ {
+ static const char *const exc = "no label found";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s from offset: %ld\n", exc, b->pc);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->lpc;
+ io.n = sizeof b->lpc;
+ b->io = io;
+ i->next = get_pc;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_dst(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const unsigned long pc = nwp_leuint32(&b->lpc);
+ const enum nw_state n = nwp_io_read(cfg, &b->io, cfg->user);
+
+ if (n)
+ return n;
+ else if (pc == b->pc)
+ i->next = seek_dst;
+ else
+ return skip(i);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_pc(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &b->io, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->dst;
+ io.n = sizeof b->dst;
+ b->io = io;
+ i->next = get_dst;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_n(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &b->io, cfg->user);
+
+ if (n)
+ return n;
+ else if (!nwp_leuint32(&b->n))
+ {
+ static const char *const exc = "function has no labels";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->lpc;
+ io.n = sizeof b->lpc;
+ b->io = io;
+ }
+
+ i->next = get_pc;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_start(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const unsigned long offset = nwp_leuint32(&b->offset);
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->n;
+ io.n = sizeof b->n;
+ b->io = io;
+ i->next = get_n;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_start(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &b->io, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_start;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_lo(struct nw_interp *const i)
+{
+ const struct nw_mod *const m = i->cfg.m;
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ long offset = m->c_sections[NW_CUSTOM_LO];
+ enum nw_state n;
+
+ if (!offset)
+ {
+ static const char *const exc = "nw_lo section not found";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+ else if (i->fr.fn.index < m->import_count)
+ {
+ static const char *const exc = "invalid function index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)i->fr.fn.index);
+#endif
+ return NW_FATAL;
+ }
+
+ offset += sizeof b->offset * (i->fr.fn.index - m->import_count);
+
+ if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &b->offset;
+ io.n = sizeof b->offset;
+ b->io = io;
+ i->next = get_start;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_interp *const i)
+{
+ struct nw_i_sm_break *const b = &i->sm.brk;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&b->pc, cfg->user);
+
+ if (n)
+ return n;
+ else if (i->fr.block_i < b->relative_depth)
+ {
+ static const char *const exc = "relative depth exceeds block level";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, rdepth: %lu, blvl: %lu\n", exc,
+ (unsigned long)i->fr.block_i, (unsigned long)b->relative_depth);
+#endif
+ return NW_FATAL;
+ }
+
+ i->fr.block_i -= b->relative_depth;
+ i->next = seek_lo;
+ return NW_AGAIN;
+}
+
+void nwp_break(struct nw_interp *const i, const nw_varuint32 relative_depth)
+{
+ const struct nw_i_sm_break b = {0};
+ struct nw_i_sm_break *const pb = &i->sm.brk;
+
+ *pb = b;
+ pb->relative_depth = relative_depth;
+ i->next = tell;
+}
diff --git a/src/routines/call.c b/src/routines/call.c
new file mode 100644
index 0000000..d564344
--- /dev/null
+++ b/src/routines/call.c
@@ -0,0 +1,17 @@
+/*
+ * 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/routines.h>
+
+void nwp_call(struct nw_interp *const i, const nw_varuint32 index)
+{
+ index >= i->cfg.m->import_count ?
+ nwp_call_function(i, index) : nwp_call_import(i, index);
+}
diff --git a/src/routines/call_function.c b/src/routines/call_function.c
new file mode 100644
index 0000000..eeee2fc
--- /dev/null
+++ b/src/routines/call_function.c
@@ -0,0 +1,242 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/io.h>
+#include <nw/interp.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state get_entry_count(struct nw_interp *);
+
+static enum nw_state push_frame(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ const enum nw_state n = nwp_stack_push(i, &pl->io);
+
+ if (n)
+ return n;
+
+ i->fr = pl->fr;
+#ifdef NW_LOG
+ nwp_log("entering function %lu\n", (unsigned long)i->fr.fn.index);
+#endif
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static void init(struct nw_i_sm_pl *const pl)
+{
+ union nw_value *const v = &pl->value;
+ struct nw_sm_io io = {0};
+
+ io.buf = v;
+
+ switch (pl->meta.type)
+ {
+ case NW_TYPE_I32:
+ v->i32 = 0;
+ io.n = sizeof v->i32;
+ break;
+
+ case NW_TYPE_I64:
+ v->i64.low = 0;
+ v->i64.hi = 0;
+ io.n = sizeof v->i64;
+ break;
+
+ case NW_TYPE_F32:
+ v->f32 = 0;
+ io.n = sizeof v->f32;
+ break;
+
+ case NW_TYPE_F64:
+ v->f64 = 0;
+ io.n = sizeof v->f64;
+ break;
+ }
+
+ pl->io = io;
+}
+
+static enum nw_state get_code_start(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ struct nw_frame *const fr = &pl->fr;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&fr->start, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &i->fr;
+ io.n = sizeof i->fr;
+ pl->io = io;
+ fr->local_end = nwp_stack_ptr(i);
+ fr->body_size -= fr->start - pl->body_start;
+ i->next = push_frame;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state push_local(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ const enum nw_state n = nwp_stack_push(i, &pl->io);
+
+ if (n)
+ return n;
+ else if (++pl->entry_i >= pl->meta.entry_count)
+ i->next = ++pl->local_i >= pl->local_count ?
+ get_code_start : get_entry_count;
+ else
+ init(pl);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state push_meta(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ const enum nw_state n = nwp_stack_push(i, &pl->io);
+
+ if (n)
+ return n;
+
+ init(pl);
+ i->next = push_local;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_type(struct nw_interp *const i)
+{
+ nw_varint7 type;
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ struct nw_sm_leb128 *const l = &pl->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varint7(cfg, l, &type, cfg->user);
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &pl->meta.type))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &pl->meta;
+ io.n = sizeof pl->meta;
+ pl->io = io;
+ i->next = push_meta;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_entry_count(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ struct nw_sm_leb128 *const l = &pl->leb128;
+ nw_varuint32 *const count = &pl->meta.entry_count;
+ struct nw_frame *const fr = &pl->fr;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, count, cfg->user);
+
+ if (n)
+ return n;
+
+ pl->entry_i = 0;
+ fr->local_count += *count;
+ i->next = get_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ struct nw_sm_leb128 *const l = &pl->leb128;
+ struct nw_frame *const fr = &pl->fr;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &pl->local_count, cfg->user);
+
+ if (n)
+ return n;
+
+ fr->local_start = nwp_stack_ptr(i);
+ i->next = pl->local_count ? get_entry_count : get_code_start;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_body_start(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&pl->body_start, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_count;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_body_len(struct nw_interp *const i)
+{
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ struct nw_sm_leb128 *const l = &pl->leb128;
+ struct nw_frame *const fr = &pl->fr;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &fr->body_size, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_body_start;
+ return NW_AGAIN;
+}
+
+static void begin(struct nw_interp *const i)
+{
+ const struct nw_i_sm_pl l = {0};
+ const struct nw_fn fn = i->sm.ffn.fn;
+ struct nw_i_sm_pl *const pl = &i->sm.pl;
+ struct nw_frame *const fr = &pl->fr;
+
+ *pl = l;
+ fr->child = 1;
+ fr->fn = fn;
+ fr->fr_start = nwp_stack_ptr(i);
+ i->next = get_body_len;
+}
+
+static enum nw_state find(struct nw_interp *const i)
+{
+ nwp_find_function(i, &i->sm.type.out, begin);
+ return NW_AGAIN;
+}
+
+void nwp_call_function(struct nw_interp *const i, const nw_varuint32 index)
+{
+ nwp_get_function_type(i, index, find);
+}
diff --git a/src/routines/call_import.c b/src/routines/call_import.c
new file mode 100644
index 0000000..63a736e
--- /dev/null
+++ b/src/routines/call_import.c
@@ -0,0 +1,477 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+#include <stddef.h>
+#include <string.h>
+
+static enum nw_state seek_param_types(struct nw_interp *);
+
+static enum nw_state seek_pc(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(ci->pc, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state push_return(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const enum nw_state n = nwp_stack_push(i, &ci->io);
+
+ if (n)
+ return n;
+
+ i->next = seek_pc;
+ return NW_AGAIN;
+}
+
+static int parse_signature(const struct nw_import *const imp,
+ size_t *const nargs, int *const ret)
+{
+ enum
+ {
+ RETURN,
+ PARAM_BEGIN,
+ PARAM,
+ END
+ } state = 0;
+
+ const char *s;
+
+ if (nargs)
+ *nargs = 0;
+
+ if (ret)
+ *ret = 0;
+
+ for (s = imp->u.function.signature; *s; s++)
+ {
+ switch (state)
+ {
+ case RETURN:
+ if (strchr("iIfF", *s))
+ {
+ if (ret)
+ *ret = 1;
+
+ state++;
+ }
+ else if (*s == '(')
+ state = PARAM;
+
+ break;
+
+ case PARAM_BEGIN:
+ if (*s != '(')
+ return -1;
+
+ state++;
+ break;
+
+ case PARAM:
+ if (strchr("iIfF", *s))
+ {
+ if (nargs)
+ (*nargs)++;
+ }
+ else if (*s == ')')
+ state++;
+ else
+ return -1;
+
+ break;
+
+ case END:
+ goto end;
+ }
+ }
+
+end:
+ return state != END || *s;
+}
+
+static int check_ret(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const struct nw_import *const imp = ci->imp;
+ const struct nw_fn *const fn = &ci->fn;
+ const nw_varuint1 retcnt = fn->ret.count;
+ int hasret;
+
+ if (parse_signature(imp, NULL, &hasret))
+ {
+ static const char *const exc = "invalid imported function signature";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s::%s: %s: %s\n", imp->module, imp->field, exc,
+ imp->u.function.signature);
+#endif
+ return NW_FATAL;
+ }
+ else if (hasret != retcnt)
+ {
+ static const char *const exc = "return count mismatch";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s::%s: %s: expected: %d, got: %d\n", imp->module, imp->field,
+ exc, hasret, retcnt);
+#endif
+ return -1;
+ }
+
+ return 0;
+}
+
+static enum nw_state call_done(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const struct nw_fn *const fn = &ci->fn;
+ const nw_varuint32 pcnt = fn->param_count;
+ const struct nw_interp_cfg *const icfg = &i->cfg;
+ union nw_value *const ret = fn->ret.count ? icfg->args + pcnt : NULL;
+
+ if (ret)
+ {
+ size_t sz;
+
+ if (nwp_type_sz(fn->ret.type, &sz))
+ {
+ static const char *const exc = "invalid return type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)fn->ret.type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = ret;
+ io.n = sz;
+ ci->io = io;
+ i->next = push_return;
+ }
+ }
+ else
+ nwp_interp_resume(i);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state repeat(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ struct nw_next *const next = &ci->next;
+ const struct nw_import *const imp = ci->imp;
+ const enum nw_state n = next->fn(next->user, next);
+
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "import function failed";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %s::%s\n", exc, imp->module, imp->field);
+#endif
+ return NW_FATAL;
+ }
+ else if (n)
+ return n;
+
+ return call_done(i);
+}
+
+static enum nw_state call(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const struct nw_fn *const fn = &ci->fn;
+ const nw_varuint32 pcnt = fn->param_count;
+ const struct nw_interp_cfg *const icfg = &i->cfg;
+ const union nw_value *const args = pcnt ? icfg->args : NULL;
+ union nw_value *const ret = fn->ret.count ? icfg->args + pcnt : NULL;
+ const struct nw_import *const imp = ci->imp;
+ enum nw_state n;
+
+ if (fn->ret.count && check_ret(i))
+ return NW_FATAL;
+ else if ((n = imp->u.function.fn(args, ret, icfg->user, &ci->next)))
+ {
+ if (n == NW_FATAL)
+ {
+ static const char *const exc = "import function failed";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %s::%s\n", exc, imp->module, imp->field);
+#endif
+ }
+ else if (ci->next.fn)
+ i->next = repeat;
+
+ return n;
+ }
+
+ return call_done(i);
+}
+
+static enum nw_state pop_args(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const enum nw_state n = nwp_stack_pop(i, &ci->io);
+
+ if (n)
+ return n;
+
+ i->next = call;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_value(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const enum nw_state n = nwp_stack_read(i, &ci->io, ci->addr);
+ const struct nw_import *const imp = ci->imp;
+ const size_t max = i->cfg.n_args;
+ const nw_varuint32 pcnt = ci->fn.param_count;
+ size_t nargs;
+
+ if (n)
+ return n;
+ else if (parse_signature(imp, &nargs, NULL))
+ {
+ static const char *const exc = "invalid imported function signature";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s::%s: %s: %s\n", imp->module, imp->field, exc,
+ imp->u.function.signature);
+#endif
+ return NW_FATAL;
+ }
+ else if (nargs > max)
+ {
+ static const char *const exc = "exceeded maximum args";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, %s::%s requests %lu, max: %lu\n", exc, imp->module,
+ imp->field, (unsigned long)nargs, (unsigned long)max);
+#endif
+ return NW_FATAL;
+ }
+ else if (pcnt != nargs)
+ {
+ static const char *const exc = "arg number mismatch";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s, %s::%s expected %lu args, got %lu\n", exc, imp->module,
+ imp->field, (unsigned long)nargs, (unsigned long)pcnt);
+#endif
+ return NW_FATAL;
+ }
+
+ i->cfg.args[ci->param_i++] = ci->value;
+
+ if (ci->param_i >= ci->fn.param_count)
+ {
+ struct nw_sm_io io = {0};
+
+ io.n = ci->sz;
+ ci->io = io;
+ i->next = pop_args;
+ }
+ else
+ i->next = seek_param_types;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param_type(struct nw_interp *const i)
+{
+ nw_varint7 type;
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ struct nw_sm_leb128 *const l = &ci->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varint7(cfg, l, &type, cfg->user);
+ enum nw_type vtype;
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &vtype) || nwp_type_sz(vtype, &sz))
+ {
+ static const char *const exc = "invalid param type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+ else if (ci->type_i >= ci->param_i)
+ {
+ if (ci->addr < sz)
+ {
+ static const char *const exc = "stack underflow";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+
+ if (ci->type_i == ci->param_i)
+ ci->type = vtype;
+
+ ci->addr -= sz;
+ }
+
+ ci->sz += sz;
+
+ if (++ci->type_i >= ci->fn.param_count)
+ {
+ struct nw_sm_io io = {0};
+
+ if (nwp_type_sz(ci->type, &sz))
+ {
+ static const char *const exc = "invalid param type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+
+ io.buf = &ci->value;
+ io.n = sz;
+ ci->io = io;
+ i->next = get_value;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_param_types(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(ci->fn.param_types, cfg->user);
+
+ if (n)
+ return n;
+
+ ci->type_i = 0;
+ ci->sz = 0;
+ ci->addr = nwp_stack_ptr(i);
+ i->next = get_param_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_interp *const i)
+{
+ struct nw_i_sm_call_import *const ci = &i->sm.call_import;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&ci->pc, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_param_types;
+ return NW_AGAIN;
+}
+
+static const struct nw_import *find(const struct nw_interp *const i,
+ const nw_varuint32 index)
+{
+ const struct nw_mod_cfg *const cfg = &i->cfg.m->cfg;
+ size_t j;
+
+ for (j = 0; j < cfg->n_imports; j++)
+ {
+ const struct nw_import_index *const ii = &cfg->imp_indexes[j];
+
+ if (ii->index == index)
+ {
+ const struct nw_import *const imp = &cfg->imports[j];
+
+#ifdef NW_LOG
+ nwp_log("import function index %lu matches %s::%s (import index %lu)\n",
+ (unsigned long)index, imp->module, imp->field, (unsigned long)j);
+#endif
+ return &cfg->imports[j];
+ }
+ }
+
+ return NULL;
+}
+
+static enum nw_state prepare(struct nw_interp *const i)
+{
+ const struct nw_fn fn = i->sm.type.out;
+ const struct nw_import *const imp = find(i, fn.index);
+
+ if (!imp)
+ {
+ static const char *const exc = "function import not found";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)fn.index);
+#endif
+ return NW_FATAL;
+ }
+ else if (imp->kind != NW_KIND_FUNCTION)
+ {
+ static const char *const exc = "import not a function";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %s::%s\n", exc, imp->module, imp->field);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ const struct nw_i_sm_call_import ci = {0};
+ struct nw_i_sm_call_import *const pci = &i->sm.call_import;
+
+ *pci = ci;
+ pci->fn = fn;
+ pci->imp = imp;
+ i->next = pci->fn.param_count ? tell : call;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_call_import(struct nw_interp *const i, const nw_varuint32 index)
+{
+ nwp_get_function_type(i, index, prepare);
+}
diff --git a/src/routines/call_indirect.c b/src/routines/call_indirect.c
new file mode 100644
index 0000000..9500969
--- /dev/null
+++ b/src/routines/call_indirect.c
@@ -0,0 +1,48 @@
+/*
+ * 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/ops.h>
+
+static enum nw_state get_value(struct nw_mod *const m)
+{
+ nw_varuint32 index;
+ 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, &index, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = m->sm.code.next;
+ return NW_AGAIN;
+}
+
+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_c *const c = &m->sm.code;
+ struct nw_sm_leb128 *const l = &c->leb128;
+ const enum nw_state n = nwp_varuint32(cfg, l, &index, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_value;
+ return NW_AGAIN;
+}
+
+void nwp_op_check_call_indirect(struct nw_mod *const m)
+{
+ m->next = get_index;
+}
diff --git a/src/routines/check_magic.c b/src/routines/check_magic.c
new file mode 100644
index 0000000..868d669
--- /dev/null
+++ b/src/routines/check_magic.c
@@ -0,0 +1,48 @@
+/*
+ * 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 run(struct nw_mod *const m)
+{
+ static const unsigned char magic[] = {'\0', 'a', 's', 'm'};
+ struct nw_sm_cm *const cm = &m->sm.check_magic;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &cm->io, cfg->user);
+
+ if (n)
+ return n;
+
+ if (memcmp(cm->buf, magic, sizeof cm->buf))
+ {
+#ifdef NW_LOG
+ nwp_log("wrong magic bytes\n");
+#endif
+ return NW_FATAL;
+ }
+
+ nwp_check_version(m);
+ return NW_AGAIN;
+}
+
+void nwp_check_magic(struct nw_mod *const m)
+{
+ const struct nw_sm_cm cm = {0};
+ struct nw_sm_cm *const pcm = &m->sm.check_magic;
+ struct nw_sm_io *const io = &pcm->io;
+
+ *pcm = cm;
+ io->buf = &pcm->buf;
+ io->n = sizeof pcm->buf;
+ m->next = run;
+}
diff --git a/src/routines/check_version.c b/src/routines/check_version.c
new file mode 100644
index 0000000..4f6ee05
--- /dev/null
+++ b/src/routines/check_version.c
@@ -0,0 +1,48 @@
+/*
+ * 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 run(struct nw_mod *const m)
+{
+ static const unsigned char version[] = {1, 0, 0, 0};
+ struct nw_sm_cv *const cv = &m->sm.check_version;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &cv->io, cfg->user);
+
+ if (n)
+ return n;
+
+ if (memcmp(&cv->version, version, sizeof cv->version))
+ {
+#ifdef NW_LOG
+ nwp_log("wrong version bytes\n");
+#endif
+ return NW_FATAL;
+ }
+
+ nwp_section(m);
+ return NW_AGAIN;
+}
+
+void nwp_check_version(struct nw_mod *const m)
+{
+ const struct nw_sm_cv cv = {0};
+ struct nw_sm_cv *const pcv = &m->sm.check_version;
+ struct nw_sm_io *const io = &pcv->io;
+
+ *pcv = cv;
+ io->buf = &pcv->version;
+ io->n = sizeof pcv->version;
+ m->next = run;
+}
diff --git a/src/routines/find_function.c b/src/routines/find_function.c
new file mode 100644
index 0000000..c0273ec
--- /dev/null
+++ b/src/routines/find_function.c
@@ -0,0 +1,100 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+
+static enum nw_state seek_code(struct nw_interp *const i)
+{
+ const struct nw_i_sm_ffn *const f = &i->sm.ffn;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const unsigned long fbo = nwp_leuint32(&f->fbo);
+ const enum nw_state n = cfg->seek(fbo, cfg->user);
+
+ if (n)
+ return n;
+
+ f->next(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_fbo(struct nw_interp *const i)
+{
+ struct nw_i_sm_ffn *const f = &i->sm.ffn;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &f->io, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_code;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_fbo(struct nw_interp *const i)
+{
+ struct nw_i_sm_ffn *const f = &i->sm.ffn;
+ const struct nw_mod *const m = i->cfg.m;
+ long offset = m->c_sections[NW_CUSTOM_FBO];
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ enum nw_state n;
+
+ if (!offset)
+ {
+ static const char *const exc = "nw_fbo section not found";
+
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+ else if (f->fn.index < m->import_count)
+ {
+ static const char *const exc = "invalid function index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)f->fn.index);
+#endif
+ return NW_FATAL;
+ }
+
+ offset += sizeof f->fbo * (f->fn.index - m->import_count);
+
+ if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &f->fbo;
+ io.n = sizeof f->fbo;
+ f->io = io;
+ i->next = get_fbo;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_find_function(struct nw_interp *const i, const struct nw_fn *const fn,
+ void (*const next)(struct nw_interp *))
+{
+ const struct nw_i_sm_ffn f = {0};
+ struct nw_i_sm_ffn *const pf = &i->sm.ffn;
+
+ *pf = f;
+ pf->fn = *fn;
+ pf->next = next;
+ i->next = seek_fbo;
+}
diff --git a/src/routines/find_local.c b/src/routines/find_local.c
new file mode 100644
index 0000000..e5ab9d2
--- /dev/null
+++ b/src/routines/find_local.c
@@ -0,0 +1,125 @@
+/*
+ * 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/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+#include <limits.h>
+
+static enum nw_state read_meta(struct nw_interp *const i)
+{
+ struct nw_find_local *const f = i->state;
+ const struct nw_local_meta *const m = &f->meta;
+ const nw_varuint32 index = f->index - f->entry_i;
+ const enum nw_state n = nwp_stack_read(i, &f->io, f->addr);
+ size_t sz;
+ nw_varuint32 count;
+
+ if (n)
+ return n;
+
+ f->addr += sizeof f->meta;
+ count = m->entry_count;
+
+ if (nwp_type_sz(m->type, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)m->type);
+#endif
+ return NW_FATAL;
+ }
+ else if (index < count)
+ {
+ const size_t offset = sz * index;
+
+ if (f->addr > ULONG_MAX - offset)
+ {
+ static const char *const exc = "out-of-bounds access to local";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: addr: %lu, offset: %lu\n", exc,
+ (unsigned long)f->addr, (unsigned long)offset);
+#endif
+ return NW_FATAL;
+ }
+ else if ((f->addr += offset) >= i->fr.local_end)
+ {
+ static const char *const exc = "invalid local index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)f->index);
+#endif
+ return NW_FATAL;
+ }
+
+ i->next = f->next;
+ }
+ else
+ {
+ const size_t offset = sz * count;
+
+ f->entry_i += count;
+
+ if (f->addr > ULONG_MAX - offset)
+ {
+ static const char *const exc = "out-of-bounds access to local";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: addr: %lu, offset: %lu\n", exc,
+ (unsigned long)f->addr, (unsigned long)offset);
+#endif
+ return NW_FATAL;
+ }
+ else if ((f->addr += offset) >= i->fr.local_end)
+ {
+ static const char *const exc = "invalid local index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)f->index);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &f->meta;
+ io.n = sizeof f->meta;
+ f->io = io;
+ }
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_find_local(struct nw_interp *const i, struct nw_find_local *const f,
+ const nw_varuint32 index, enum nw_state (*const next)(struct nw_interp *),
+ void *const args)
+{
+ const struct nw_find_local fl = {0};
+
+ *f = fl;
+ f->index = index;
+ f->next = next;
+ f->addr = i->fr.local_start;
+ f->io.buf = &f->meta;
+ f->io.n = sizeof f->meta;
+ i->state = f;
+ i->args = args;
+ i->next = read_meta;
+}
diff --git a/src/routines/find_param.c b/src/routines/find_param.c
new file mode 100644
index 0000000..eeae86d
--- /dev/null
+++ b/src/routines/find_param.c
@@ -0,0 +1,104 @@
+/*
+ * 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 <nw/types.h>
+
+static enum nw_state seek_pc(struct nw_interp *const i)
+{
+ struct nw_find_param *const fp = i->state;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(fp->pc, cfg->user);
+
+ if (n)
+ return n;
+
+ fp->out.addr = i->fr.fr_start - fp->sz;
+ i->next = fp->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param_type(struct nw_interp *const i)
+{
+ nw_varint7 type;
+ struct nw_find_param *const fp = i->state;
+ struct nw_sm_leb128 *const l = &fp->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varint7(cfg, l, &type, cfg->user);
+ enum nw_type param_type;
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &param_type)
+ || nwp_type_sz(param_type, &sz))
+ {
+ static const char *const exc = "invalid param type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+
+ if (fp->param_i == fp->index)
+ fp->out.type = param_type;
+
+ if (fp->param_i >= fp->index)
+ fp->sz += sz;
+
+ if (++fp->param_i >= i->fr.fn.param_count)
+ i->next = seek_pc;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_param_types(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const long offset = i->fr.fn.param_types;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_param_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_interp *const i)
+{
+ struct nw_find_param *const fp = i->state;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&fp->pc, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_param_types;
+ return NW_AGAIN;
+}
+
+void nwp_find_param(struct nw_interp *const i, struct nw_find_param *const f,
+ const nw_varuint32 index, enum nw_state (*const next)(struct nw_interp *),
+ void *const args)
+{
+ const struct nw_find_param fp = {0};
+
+ *f = fp;
+ f->index = index;
+ f->next = next;
+ i->state = f;
+ i->next = tell;
+ i->args = args;
+}
diff --git a/src/routines/get_function_type.c b/src/routines/get_function_type.c
new file mode 100644
index 0000000..e30200f
--- /dev/null
+++ b/src/routines/get_function_type.c
@@ -0,0 +1,331 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state get_param_type(struct nw_interp *);
+
+static enum nw_state seek_pc(struct nw_interp *const i)
+{
+ const struct nw_i_sm_type *const t = &i->sm.type;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(t->pc, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = t->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_return_type(struct nw_interp *const i)
+{
+ nw_varint7 type;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varint7(cfg, &t->leb128, &type, cfg->user);
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &t->out.ret.type))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+
+ i->next = seek_pc;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_return_count(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ struct nw_sm_leb128 *const l = &t->leb128;
+ nw_varuint1 *const return_count = &t->out.ret.count;
+ const enum nw_state n = nwp_varuint1(cfg, l, return_count, cfg->user);
+
+ if (n)
+ return n;
+ else if (*return_count)
+ i->next = get_return_type;
+ else
+ t->next(i);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param_type(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ nw_varint7 type;
+ enum nw_type param_type;
+ const enum nw_state n = nwp_varint7(cfg, &t->leb128, &type, cfg->user);
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &param_type))
+ {
+ i->exception = "invalid type";
+ return NW_FATAL;
+ }
+ else if (++t->param_i >= t->out.param_count)
+ i->next = get_return_count;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state tell_param_types(struct nw_interp *const i)
+{
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->tell(&t->out.param_types, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_param_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param_count(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ struct nw_sm_leb128 *const l = &t->leb128;
+ nw_varuint32 *const count = &t->out.param_count;
+ const enum nw_state n = nwp_varuint32(cfg, l, count, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = *count ? tell_param_types : get_return_count;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_form(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ struct nw_sm_leb128 *const l = &t->leb128;
+ nw_varint7 form;
+ const enum nw_state n = nwp_varint7(cfg, l, &form, cfg->user);
+
+ if (n)
+ return n;
+ else if (form != 0x60)
+ {
+ static const char *const exc = "type index not a function";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)form);
+#endif
+ return NW_FATAL;
+ }
+
+ i->next = get_param_count;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_type(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const struct nw_i_sm_type *const t = &i->sm.type;
+ const unsigned long to = nwp_leuint32(&t->to);
+ const enum nw_state n = cfg->seek(to, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_form;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_to(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const enum nw_state n = nwp_io_read(cfg, &t->io, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_to(struct nw_interp *const i)
+{
+ const struct nw_interp_cfg *const icfg = &i->cfg;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const struct nw_mod *const m = icfg->m;
+ long offset = m->c_sections[NW_CUSTOM_TO];
+ const unsigned long fti = nwp_leuint32(&t->fti);
+ const struct nw_io_cfg *const cfg = &icfg->io;
+ enum nw_state n;
+
+ if (fti >= m->type_count)
+ {
+ static const char *const exc = "invalid type index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, fti);
+#endif
+ return NW_FATAL;
+ }
+ else if (!offset)
+ {
+ static const char *const exc = "nw_to section not found";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+
+ offset += sizeof t->to * fti;
+
+ if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &t->to;
+ io.n = sizeof t->to;
+ t->io = io;
+ i->next = get_to;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_fti(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const enum nw_state n = nwp_io_read(cfg, &t->io, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_to;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_fti(struct nw_interp *const i)
+{
+ const struct nw_interp_cfg *const icfg = &i->cfg;
+ const struct nw_io_cfg *const cfg = &icfg->io;
+ struct nw_i_sm_type *const t = &i->sm.type;
+ long offset = icfg->m->c_sections[NW_CUSTOM_FTI];
+ enum nw_state n;
+
+ if (!offset)
+ {
+ static const char *const exc = "nw_fti section not found";
+
+#ifdef NW_LOG
+ nwp_log("%s: %s\n", exc, "function type index");
+#endif
+ i->exception = exc;
+ return NW_FATAL;
+ }
+
+ offset += sizeof t->fti * t->fn_index;
+
+ if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &t->fti;
+ io.n = sizeof t->fti;
+ t->io = io;
+ i->next = get_fti;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const struct nw_get_import_type_out *const out = &t->git.out;
+ struct nw_sm_leb128 *const l = &t->leb128;
+ nw_varuint32 index;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ enum nw_state n;
+
+ if (out->kind != NW_KIND_FUNCTION)
+ {
+ static const char *const exc = "import type not a function";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)out->kind);
+#endif
+ return NW_FATAL;
+ }
+ else if ((n = nwp_varuint32(cfg, l, &index, cfg->user)))
+ return n;
+
+ nwp_toleuint32(index, &t->fti);
+ i->next = seek_to;
+ return NW_AGAIN;
+}
+
+static enum nw_state tell(struct nw_interp *const i)
+{
+ struct nw_i_sm_type *const t = &i->sm.type;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const nw_varuint32 icnt = i->cfg.m->import_count, index = t->out.index;
+ const enum nw_state n = cfg->tell(&t->pc, cfg->user);
+
+ if (n)
+ return n;
+ else if (index >= icnt)
+ {
+ t->fn_index = index - icnt;
+ i->next = seek_fti;
+ }
+ else
+ nwp_get_import_type(i, &t->git, index, get_index);
+
+ return NW_AGAIN;
+}
+
+void nwp_get_function_type(struct nw_interp *const i, const nw_varuint32 index,
+ enum nw_state (*const next)(struct nw_interp *))
+{
+ const struct nw_i_sm_type t = {0};
+ struct nw_i_sm_type *const pt = &i->sm.type;
+
+ *pt = t;
+ pt->out.index = index;
+ pt->next = next;
+ i->next = tell;
+}
diff --git a/src/routines/get_import_type.c b/src/routines/get_import_type.c
new file mode 100644
index 0000000..cf5c371
--- /dev/null
+++ b/src/routines/get_import_type.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 <nanowasm/types.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/types.h>
+
+static enum nw_state get_kind(struct nw_interp *const i)
+{
+ struct nw_get_import_type *const t = i->state;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &t->io, cfg->user);
+
+ if (n)
+ return n;
+ else if (t->kind >= NW_KINDS)
+ {
+ static const char *const exc = "invalid external_kind";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)t->kind);
+#endif
+ return NW_FATAL;
+ }
+
+ t->out.kind = t->kind;
+ i->next = t->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_type(struct nw_interp *const i)
+{
+ struct nw_get_import_type *const t = i->state;
+ const unsigned long offset = nwp_leuint32(&t->value);
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(offset, cfg->user);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &t->kind;
+ io.n = sizeof t->kind;
+ t->io = io;
+ i->next = get_kind;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_value(struct nw_interp *const i)
+{
+ struct nw_get_import_type *const t = i->state;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_io_read(cfg, &t->io, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = seek_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_iti(struct nw_interp *const i)
+{
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_get_import_type *const t = i->state;
+ long offset = i->cfg.m->c_sections[NW_CUSTOM_ITI];
+ enum nw_state n;
+
+ if (!offset)
+ {
+ static const char *const exc = "nw_iti section not found";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+
+ offset += t->index * sizeof t->value;
+
+ if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &t->value;
+ io.n = sizeof t->value;
+ t->io = io;
+ i->next = get_value;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_get_import_type(struct nw_interp *const i,
+ struct nw_get_import_type *const it,
+ const nw_varuint32 index,
+ enum nw_state (*const next)(struct nw_interp *const i))
+{
+ const struct nw_get_import_type t = {0};
+
+ *it = t;
+ it->index = index;
+ it->next = next;
+ i->state = it;
+ i->next = seek_iti;
+}
diff --git a/src/routines/i32_arithm.c b/src/routines/i32_arithm.c
new file mode 100644
index 0000000..e833a11
--- /dev/null
+++ b/src/routines/i32_arithm.c
@@ -0,0 +1,39 @@
+/*
+ * 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/interp.h>
+#include <nw/stack.h>
+#include <nw/routines.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_i32_arithm *const a = &i->sm.i32_arithm;
+ const enum nw_state n = nwp_stack_push(i, &a->io);
+
+ if (n)
+ return n;
+
+ i->push_type = NW_TYPE_I32;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+void nwp_op_i32_arithm(struct nw_interp *const i, const long result)
+{
+ const struct nw_i_sm_i32_arithm a = {0};
+ struct nw_i_sm_i32_arithm *const s = &i->sm.i32_arithm;
+
+ *s = a;
+ s->result = result;
+ s->io.buf = &s->result;
+ s->io.n = sizeof s->result;
+ i->next = push;
+}
diff --git a/src/routines/init_data.c b/src/routines/init_data.c
new file mode 100644
index 0000000..c9482a0
--- /dev/null
+++ b/src/routines/init_data.c
@@ -0,0 +1,196 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/linear.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state get_index(struct nw_inst *);
+static enum nw_state get_byte(struct nw_inst *);
+
+static enum nw_state store_byte(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_d *const d = &inst->sm.data;
+ struct nw_interp *const i = &inst->interp;
+ const unsigned long offset = d->offset + d->bytes_i;
+ const enum nw_state n = nwp_linear_store(i, &d->io, offset);
+
+ if (n)
+ return n;
+ else if (++d->bytes_i >= d->size)
+ inst->next = ++d->entry_i >= d->count ? d->next : get_index;
+ else
+ inst->next = get_byte;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_byte(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_d *const d = &inst->sm.data;
+ struct nw_interp *const i = &inst->interp;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_sm_io io = {0};
+ enum nw_state n;
+
+ io.buf = &d->b;
+ io.n = sizeof d->b;
+
+ if ((n = nwp_io_read(cfg, &io, cfg->user)))
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &d->b;
+ io.n = sizeof d->b;
+ d->io = io;
+ }
+
+ inst->next = store_byte;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_size(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_d *const d = &inst->sm.data;
+ struct nw_sm_leb128 *const l = &d->leb128;
+ struct nw_interp *const i = &inst->interp;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &d->size, cfg->user);
+
+ if (n)
+ return n;
+
+ d->bytes_i = 0;
+ inst->next = get_byte;
+ return NW_AGAIN;
+}
+
+static enum nw_state pop(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_d *const d = &inst->sm.data;
+ const enum nw_state n = nwp_stack_pop(&inst->interp, &d->io);
+
+ if (n)
+ return n;
+
+ inst->next = get_size;
+ return NW_AGAIN;
+}
+
+static enum nw_state loop(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_d *const d = &inst->sm.data;
+ const enum nw_state n = nwp_interp_run(&inst->interp);
+ struct nw_sm_io io = {0};
+
+ if (n)
+ return n;
+
+ io.buf = &d->offset;
+ io.n = sizeof d->offset;
+ d->io = io;
+ inst->next = pop;
+ return NW_AGAIN;
+}
+
+static enum nw_state setup_initexpr(struct nw_inst *const inst)
+{
+ struct nw_interp *const i = &inst->interp;
+ const struct nw_interp_cfg icfg = i->cfg;
+
+ if (nwp_interp_start(i, &icfg, &nwp_interp_data_set))
+ {
+#ifdef NW_LOG
+ nwp_log("nw_interp_start failed\n");
+#endif
+ return NW_FATAL;
+ }
+
+ inst->next = loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_d *const d = &inst->sm.data;
+ struct nw_sm_leb128 *const l = &d->leb128;
+ struct nw_interp *const i = &inst->interp;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &d->index, cfg->user);
+
+ if (n)
+ return n;
+
+ return setup_initexpr(inst);
+}
+
+static enum nw_state get_count(struct nw_inst *const i)
+{
+ struct nw_inst_sm_d *const d = &i->sm.data;
+ struct nw_sm_leb128 *const l = &d->leb128;
+ const struct nw_interp_cfg *const icfg = &i->interp.cfg;
+ const struct nw_io_cfg *const cfg = &icfg->io;
+ const nw_varuint32 expected = icfg->m->data_count;
+ const enum nw_state n = nwp_varuint32(cfg, l, &d->count, cfg->user);
+
+ if (n)
+ return n;
+ else if (d->count != expected)
+ {
+#ifdef NW_LOG
+ static const char *const exc = "data count mismatch";
+
+ nwp_log("%s, expected %lu, got %lu\n", exc,
+ (unsigned long)expected, (unsigned long)d->count);
+#endif
+ return NW_FATAL;
+ }
+
+ i->next = d->count ? get_index : d->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_data(struct nw_inst *const i)
+{
+ struct nw_inst_sm_gl *const d = &i->sm.global;
+ const struct nw_interp_cfg *const icfg = &i->interp.cfg;
+ const struct nw_io_cfg *const cfg = &icfg->io;
+ const long offset = icfg->m->sections[NW_SECTION_DATA];
+ enum nw_state n;
+
+ if (!offset)
+ {
+ i->next = d->next;
+ return NW_AGAIN;
+ }
+ else if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+
+ i->next = get_count;
+ return NW_AGAIN;
+}
+
+void nwp_init_data(struct nw_inst *const i,
+ enum nw_state (*const next)(struct nw_inst *))
+{
+ const struct nw_inst_sm_d d = {0};
+ struct nw_inst_sm_d *const p = &i->sm.data;
+
+ *p = d;
+ p->next = next;
+ i->next = seek_data;
+}
diff --git a/src/routines/init_globals.c b/src/routines/init_globals.c
new file mode 100644
index 0000000..6d1e5e4
--- /dev/null
+++ b/src/routines/init_globals.c
@@ -0,0 +1,181 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/global.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state get_content_type(struct nw_inst *const i);
+
+static enum nw_state store(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_gl *const g = &inst->sm.global;
+ struct nw_interp *const i = &inst->interp;
+ const enum nw_state n = nwp_global_store(i, &g->io, g->entry_i);
+
+ if (n)
+ return n;
+
+ inst->next = ++g->entry_i >= g->count ? g->next : get_content_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state pop(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_gl *const g = &inst->sm.global;
+ struct nw_interp *const i = &inst->interp;
+ const enum nw_state n = nwp_stack_pop(i, &g->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &g->out;
+ io.n = sizeof g->out;
+ g->io = io;
+ inst->next = store;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state loop(struct nw_inst *const i)
+{
+ struct nw_inst_sm_gl *const g = &i->sm.global;
+ const enum nw_state n = nwp_interp_run(&i->interp);
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (nwp_type_sz(g->out.type, &sz))
+ {
+#ifdef NW_LOG
+ static const char *const exc = "invalid type";
+
+ nwp_log("%s: %#x\n", exc, (unsigned)g->out.type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &g->out.value;
+ io.n = sz;
+ g->io = io;
+ i->next = pop;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_mutability(struct nw_inst *const inst)
+{
+ struct nw_inst_sm_gl *const g = &inst->sm.global;
+ struct nw_interp *const i = &inst->interp;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ struct nw_sm_leb128 *const l = &g->leb128;
+ const enum nw_state n = nwp_varuint1(cfg, l, &g->out.mutability, cfg->user);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ inst->next = loop;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_content_type(struct nw_inst *const i)
+{
+ nw_varint7 content_type;
+ struct nw_inst_sm_gl *const g = &i->sm.global;
+ const struct nw_io_cfg *const cfg = &i->interp.cfg.io;
+ struct nw_sm_leb128 *const l = &g->leb128;
+ const enum nw_state n = nwp_varint7(cfg, l, &content_type, cfg->user);
+
+ if (n)
+ return n;
+ else if (nwp_get_type(content_type, &g->out.type))
+ {
+#ifdef NW_LOG
+ static const char *const exc = "invalid type";
+
+ nwp_log("%s: %#x\n", exc, (unsigned)content_type);
+#endif
+ return NW_FATAL;
+ }
+
+ i->next = get_mutability;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_count(struct nw_inst *const i)
+{
+ struct nw_inst_sm_gl *const g = &i->sm.global;
+ struct nw_sm_leb128 *const l = &g->leb128;
+ const struct nw_interp_cfg *const icfg = &i->interp.cfg;
+ const struct nw_io_cfg *const cfg = &icfg->io;
+ const nw_varuint32 expected = icfg->m->global_count;
+ const enum nw_state n = nwp_varuint32(cfg, l, &g->count, cfg->user);
+
+ if (n)
+ return n;
+ else if (g->count != expected)
+ {
+#ifdef NW_LOG
+ static const char *const exc = "global count mismatch";
+
+ nwp_log("%s, expected %lu, got %lu\n", exc,
+ (unsigned long)expected, (unsigned long)g->count);
+#endif
+ return NW_FATAL;
+ }
+
+ i->next = g->count ? get_content_type : g->next;
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_global(struct nw_inst *const i)
+{
+ struct nw_inst_sm_gl *const g = &i->sm.global;
+ const struct nw_interp_cfg *const icfg = &i->interp.cfg;
+ const struct nw_io_cfg *const cfg = &icfg->io;
+ const long offset = icfg->m->sections[NW_SECTION_GLOBAL];
+ enum nw_state n;
+
+ if (!offset)
+ {
+ i->next = g->next;
+ return NW_AGAIN;
+ }
+ else if ((n = cfg->seek(offset, cfg->user)))
+ return n;
+
+ i->next = get_count;
+ return NW_AGAIN;
+}
+
+void nwp_init_globals(struct nw_inst *const i,
+ enum nw_state (*const next)(struct nw_inst *))
+{
+ const struct nw_inst_sm_gl gl = {0};
+ struct nw_inst_sm_gl *const p = &i->sm.global;
+
+ *p = gl;
+ p->next = next;
+ i->next = seek_global;
+}
diff --git a/src/routines/mem_imm.c b/src/routines/mem_imm.c
new file mode 100644
index 0000000..c93ee1a
--- /dev/null
+++ b/src/routines/mem_imm.c
@@ -0,0 +1,51 @@
+/*
+ * 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/routines.h>
+#include <nw/io.h>
+
+static enum nw_state get_offset(struct nw_interp *const i)
+{
+ struct nw_i_sm_imm *const imm = &i->sm.imm;
+ struct nw_sm_leb128 *const l = &imm->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &imm->out.offset, cfg->user);
+
+ if (n)
+ return n;
+
+ imm->next(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state get_flags(struct nw_interp *const i)
+{
+ struct nw_i_sm_imm *const imm = &i->sm.imm;
+ struct nw_sm_leb128 *const l = &imm->leb128;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &imm->out.flags, cfg->user);
+
+ if (n)
+ return n;
+
+ i->next = get_offset;
+ return NW_AGAIN;
+}
+
+void nwp_mem_imm(struct nw_interp *const i,
+ void (*const next)(struct nw_interp *))
+{
+ const struct nw_i_sm_imm imm = {0};
+ struct nw_i_sm_imm *const p = &i->sm.imm;
+
+ *p = imm;
+ p->next = next;
+ i->next = get_flags;
+}
diff --git a/src/routines/section.c b/src/routines/section.c
new file mode 100644
index 0000000..5198424
--- /dev/null
+++ b/src/routines/section.c
@@ -0,0 +1,105 @@
+/*
+ * 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 void (*const fn[])(struct nw_mod *) =
+{
+ nwp_section_custom,
+ nwp_section_type,
+ nwp_section_import,
+ nwp_section_function,
+ nwp_section_table,
+ nwp_section_memory,
+ nwp_section_global,
+ nwp_section_export,
+ nwp_section_start,
+ nwp_section_element,
+ nwp_section_code,
+ nwp_section_data,
+ nwp_section_data_count
+};
+
+static enum nw_state get_offset(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->offset, cfg->user);
+ long *const offset = &m->sections[s->section];
+
+ if (n)
+ return n;
+ /* Custom section can appear more than once. */
+ else if (s->section != NW_SECTION_CUSTOM && *offset)
+ {
+#ifdef NW_LOG
+ nwp_log("ignoring duplicate section %u\n", (unsigned)s->section);
+#endif
+ nwp_section_skip(m);
+ }
+ else
+ {
+ nwp_log("processing section %u\n", (unsigned)s->section);
+ *offset = s->offset;
+ fn[s->section](m);
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_length(struct nw_mod *const m)
+{
+ struct nw_mod_section *const s = &m->section;
+ struct nw_sm_leb128 *const l = &s->leb128;
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &s->len, cfg->user);
+
+ if (n)
+ return n;
+
+ m->next = get_offset;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_section(struct nw_mod *const m)
+{
+ const struct nw_io_cfg *const cfg = &m->cfg.io;
+ struct nw_mod_section *const s = &m->section;
+ struct nw_sm_leb128 *const l = &s->leb128;
+ const enum nw_state n = nwp_varuint7(cfg, l, &s->section, cfg->user);
+
+ if (n)
+ {
+ if (cfg->eof(cfg->user))
+ return NW_OK;
+
+ return n;
+ }
+ else if (s->section >= sizeof fn / sizeof *fn)
+ {
+#ifdef NW_LOG
+ nwp_log("invalid section %u\n", (unsigned)s->section);
+#endif
+ return NW_FATAL;
+ }
+
+ m->next = get_length;
+ return NW_AGAIN;
+}
+
+void nwp_section(struct nw_mod *const m)
+{
+ const struct nw_mod_section s = {0};
+
+ m->section = s;
+ m->next = get_section;
+}
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;
+}
diff --git a/src/routines/set_local.c b/src/routines/set_local.c
new file mode 100644
index 0000000..84c7c26
--- /dev/null
+++ b/src/routines/set_local.c
@@ -0,0 +1,162 @@
+/*
+ * 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/ops.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state set_local(struct nw_interp *const i)
+{
+ struct nw_i_sm_set_local *const sl = &i->sm.set_local;
+ const enum nw_state n = nwp_stack_write(i, &sl->io, sl->f.l.addr);
+
+ if (n)
+ return n;
+
+ i->next = sl->next;
+ return NW_AGAIN;
+}
+
+static int init_io(struct nw_interp *const i, const enum nw_type t)
+{
+ struct nw_i_sm_set_local *const sl = &i->sm.set_local;
+ size_t sz;
+
+ if (nwp_type_sz(t, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)t);
+#endif
+ return -1;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &sl->out.value;
+ io.n = sz;
+ sl->io = io;
+ }
+
+ return 0;
+}
+
+static enum nw_state pop(struct nw_interp *const i, const enum nw_type t,
+ enum nw_state (*const next)(struct nw_interp *))
+{
+ struct nw_i_sm_set_local *const sl = &i->sm.set_local;
+ const enum nw_state n = nwp_stack_pop(i, &sl->io);
+
+ if (n)
+ return n;
+ else if (init_io(i, t))
+ return NW_FATAL;
+
+ sl->out.type = t;
+ i->next = next;
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_local(struct nw_interp *const i)
+{
+ const struct nw_local_meta *const m = &i->sm.set_local.f.l.meta;
+
+ return pop(i, m->type, set_local);
+}
+
+static enum nw_state prepare_local(struct nw_interp *const i)
+{
+ const struct nw_local_meta *const m = &i->sm.set_local.f.l.meta;
+
+ if (init_io(i, m->type))
+ return NW_FATAL;
+
+ i->next = pop_local;
+ return NW_AGAIN;
+}
+
+static enum nw_state set_param(struct nw_interp *const i)
+{
+ struct nw_i_sm_set_local *const sl = &i->sm.set_local;
+ const enum nw_state n = nwp_stack_write(i, &sl->io, sl->f.p.out.addr);
+
+ if (n)
+ return n;
+
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_param(struct nw_interp *const i)
+{
+ const struct nw_find_param_out *const o = &i->sm.set_local.f.p.out;
+
+ return pop(i, o->type, set_param);
+}
+
+static enum nw_state prepare_param(struct nw_interp *const i)
+{
+ struct nw_i_sm_set_local *const sl = &i->sm.set_local;
+ const struct nw_find_param_out *const o = &sl->f.p.out;
+
+ if (init_io(i, o->type))
+ return NW_FATAL;
+
+ i->next = pop_param;
+ return NW_AGAIN;
+}
+
+static enum nw_state get_index(struct nw_interp *const i)
+{
+ struct nw_i_sm_set_local *const sl = &i->sm.set_local;
+ struct nw_sm_leb128 *const l = &sl->leb128;
+ const struct nw_frame *const fr = &i->fr;
+ const struct nw_fn *const fn = &fr->fn;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varuint32(cfg, l, &sl->index, cfg->user);
+ nw_varuint32 index;
+
+ if (n)
+ return n;
+ else if (sl->index < fn->param_count)
+ nwp_find_param(i, &sl->f.p, sl->index, prepare_param, NULL);
+ else if ((index = sl->index - fn->param_count) >= fr->local_count)
+ {
+ static const char *const exc = "invalid local index";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu\n", exc, (unsigned long)sl->index);
+#endif
+ return NW_FATAL;
+ }
+ else
+ nwp_find_local(i, &sl->f.l, index, prepare_local, NULL);
+
+ return NW_AGAIN;
+}
+
+void nwp_set_local(struct nw_interp *const i,
+ enum nw_state (*const next)(struct nw_interp *))
+{
+ const struct nw_i_sm_set_local sl = {0};
+ struct nw_i_sm_set_local *const p = &i->sm.set_local;
+
+ *p = sl;
+ p->next = next;
+ i->next = get_index;
+}
diff --git a/src/routines/start_block.c b/src/routines/start_block.c
new file mode 100644
index 0000000..83be44c
--- /dev/null
+++ b/src/routines/start_block.c
@@ -0,0 +1,49 @@
+/*
+ * 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>
+
+static enum nw_state get_block_type(struct nw_interp *const i)
+{
+ nw_varint7 type;
+ struct nw_i_sm_sb *const sb = &i->sm.start_block;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varint7(cfg, &sb->leb128, &type, cfg->user);
+ enum nw_type block_type;
+
+ if (n)
+ return n;
+ else if (type != 0x40 && nwp_get_type(type, &block_type))
+ {
+ static const char *const exc = "invalid block_type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#hhx\n", exc, (unsigned char)type);
+#endif
+ return NW_FATAL;
+ }
+
+ i->fr.block_i++;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+void nwp_start_block(struct nw_interp *const i)
+{
+ const struct nw_i_sm_sb sb = {0};
+
+ i->sm.start_block = sb;
+ i->next = get_block_type;
+}
diff --git a/src/routines/unary.c b/src/routines/unary.c
new file mode 100644
index 0000000..d65f8aa
--- /dev/null
+++ b/src/routines/unary.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/interp.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ struct nw_i_sm_unary *const u = &i->sm.unary;
+ const enum nw_state n = nwp_stack_push(i, &u->io);
+
+ if (n)
+ return n;
+
+ i->push_type = u->type;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state pop(struct nw_interp *const i)
+{
+ struct nw_i_sm_unary *const u = &i->sm.unary;
+ const enum nw_state n = nwp_stack_pop(i, &u->io);
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (u->op(&u->in, &u->out))
+ {
+ static const char *const exc = "unary operation failed";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+ else if (nwp_type_sz(u->type, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %d\n", exc, u->type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &u->out;
+ io.n = sz;
+ u->io = io;
+ }
+
+ i->next = push;
+ return NW_AGAIN;
+}
+
+void nwp_unary(struct nw_interp *const i, const enum nw_type t,
+ int (*const op)(const union nw_value *, union nw_value *))
+{
+ const struct nw_i_sm_unary u = {0};
+ struct nw_i_sm_unary *const pu = &i->sm.unary;
+ size_t sz;
+
+ *pu = u;
+ pu->type = t;
+ pu->op = op;
+ nwp_type_sz(t, &sz);
+ pu->io.buf = &pu->in;
+ pu->io.n = sz;
+ i->next = pop;
+}
diff --git a/src/routines/unwind.c b/src/routines/unwind.c
new file mode 100644
index 0000000..e4200fd
--- /dev/null
+++ b/src/routines/unwind.c
@@ -0,0 +1,391 @@
+/*
+ * 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/routines.h>
+#include <nw/interp.h>
+#include <nw/io.h>
+#include <nw/log.h>
+#include <nw/stack.h>
+#include <nw/types.h>
+
+static enum nw_state prepare_pop(struct nw_interp *);
+
+static enum nw_state seek_pc(struct nw_interp *const i)
+{
+ const struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(u->fr.pc, cfg->user);
+ struct nw_return r;
+
+ if (n)
+ return n;
+
+#ifdef NW_LOG
+ nwp_log("returning to function %lu\n", u->fr.fn.index);
+#endif
+
+ r = i->fr.fn.ret;
+ i->fr = u->fr;
+ i->fr.prev_ret = r;
+ nwp_interp_resume(i);
+ return NW_AGAIN;
+}
+
+static enum nw_state push_return_value(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const enum nw_state n = nwp_stack_push(i, &u->io);
+
+ if (n)
+ return n;
+ else if (u->fr.child)
+ i->next = seek_pc;
+ else
+ return NW_OK;
+
+ return NW_AGAIN;
+}
+
+static int prepare_retval(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_fn *const fn = &i->fr.fn;
+ const struct nw_return *const r = &fn->ret;
+ const enum nw_type t = r->type;
+ size_t sz;
+
+ if (nwp_type_sz(t, &sz))
+ {
+ static const char *const exc = "invalid return type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)t);
+#endif
+ return -1;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &u->retval;
+ io.n = sz;
+ u->io = io;
+ i->push_type = t;
+ i->next = push_return_value;
+ }
+
+ return 0;
+}
+
+static enum nw_state pop_params(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_fn *const fn = &i->fr.fn;
+ const struct nw_return *const r = &fn->ret;
+ const enum nw_state n = nwp_stack_pop(i, &u->io);
+
+ if (n)
+ return n;
+ else if (r->count)
+ {
+ if (prepare_retval(i))
+ return NW_FATAL;
+ }
+ else if (u->fr.child)
+ i->next = seek_pc;
+ else
+ return NW_OK;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state get_param_type(struct nw_interp *const i)
+{
+ nw_varint7 type;
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = nwp_varint7(cfg, &u->leb128, &type, cfg->user);
+ enum nw_type vtype;
+ size_t sz;
+
+ if (n)
+ return n;
+ else if (nwp_get_type(type, &vtype) || nwp_type_sz(vtype, &sz))
+ {
+ static const char *const exc = "invalid param type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)type);
+#endif
+ return NW_FATAL;
+ }
+
+ u->sz += sz;
+
+ if (++u->entry_i >= i->fr.fn.param_count)
+ {
+ struct nw_sm_io io = {0};
+
+ io.n = u->sz;
+ u->io = io;
+ i->next = pop_params;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state seek_param_types(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_io_cfg *const cfg = &i->cfg.io;
+ const enum nw_state n = cfg->seek(i->fr.fn.param_types, cfg->user);
+
+ if (n)
+ return n;
+
+ u->entry_i = 0;
+ i->next = get_param_type;
+ return NW_AGAIN;
+}
+
+static enum nw_state check_retval(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_fn *const fn = &i->fr.fn;
+ const size_t addr = nwp_stack_ptr(i), start = i->fr.local_start;
+ const struct nw_return *const r = &fn->ret;
+
+ if (addr != start)
+ {
+ static const char *const exc = "mismatched stack address";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: expected: %lu, got: %lu\n", exc,
+ (unsigned long)start, (unsigned long)addr);
+#endif
+ return NW_FATAL;
+ }
+ else if (fn->param_count)
+ i->next = seek_param_types;
+ else if (r->count)
+ {
+ if (prepare_retval(i))
+ return NW_FATAL;
+ }
+ else if (u->fr.child)
+ i->next = seek_pc;
+ else
+ return NW_OK;
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_meta(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const enum nw_state n = nwp_stack_pop(i, &u->io);
+
+ if (n)
+ return n;
+ else if (u->pending)
+ nwp_find_local(i, &u->fl, u->pending - 1, prepare_pop, NULL);
+ else
+ return check_retval(i);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_local(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ struct nw_local_meta *const m = &u->fl.meta;
+ const enum nw_state n = nwp_stack_pop(i, &u->io);
+
+ if (n)
+ return n;
+
+ u->pending--;
+
+ if (++u->entry_i >= m->entry_count)
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = m;
+ io.n = sizeof *m;
+ u->io = io;
+ u->entry_i = 0;
+ i->next = pop_meta;
+ }
+ else if (u->pending)
+ return prepare_pop(i);
+ else
+ {
+ static const char *const exc = "entry and local count mismatch";
+#ifdef NW_LOG
+ const unsigned long pending = m->entry_count - u->entry_i;
+#endif
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %lu locals pending\n", exc, pending);
+#endif
+ return NW_FATAL;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state prepare_pop(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_local_meta *const m = &u->fl.meta;
+ size_t sz;
+
+ if (nwp_type_sz(m->type, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)m->type);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &u->value;
+ io.n = sz;
+ u->io = io;
+ i->next = pop_local;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_frame(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_frame *const fr = &i->fr;
+ const enum nw_state n = nwp_stack_pop(i, &u->io);
+
+ if (n)
+ return n;
+ else if (fr->local_count)
+ {
+ u->pending = fr->local_count;
+ nwp_find_local(i, &u->fl, u->pending - 1, prepare_pop, NULL);
+ }
+ else
+ return check_retval(i);
+
+ return NW_AGAIN;
+}
+
+static enum nw_state pop_return_value(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const enum nw_state n = nwp_stack_pop(i, &u->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &u->fr;
+ io.n = sizeof u->fr;
+ u->io = io;
+ i->next = pop_frame;
+ }
+
+ return NW_AGAIN;
+}
+
+static enum nw_state run(struct nw_interp *const i)
+{
+ struct nw_i_sm_unwind *const u = &i->sm.unwind;
+ const struct nw_frame *const fr = &i->fr;
+ const struct nw_fn *const fn = &fr->fn;
+ const struct nw_return *const r = &fn->ret;
+
+ if (r->count)
+ {
+ size_t sz, end, addr;
+
+ if (nwp_type_sz(r->type, &sz))
+ {
+ static const char *const exc = "invalid type";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %#x\n", exc, (unsigned)r->type);
+#endif
+ return NW_FATAL;
+ }
+
+ end = fr->local_end;
+ addr = nwp_stack_ptr(i);
+
+ if (addr < sz || addr < sizeof *fr)
+ {
+ static const char *const exc = "stack underflow";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s\n", exc);
+#endif
+ return NW_FATAL;
+ }
+ else if ((addr -= sizeof *fr + sz) != end)
+ {
+ static const char *const exc = "stack memory leak";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: expected: %lu, got: %lu\n", exc, (unsigned long)end,
+ (unsigned long)addr);
+#endif
+ return NW_FATAL;
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &u->retval;
+ io.n = sz;
+ u->io = io;
+ i->next = pop_return_value;
+ }
+ }
+ else
+ {
+ struct nw_sm_io io = {0};
+
+ io.buf = &u->fr;
+ io.n = sizeof u->fr;
+ u->io = io;
+ i->next = pop_frame;
+ }
+
+ return NW_AGAIN;
+}
+
+void nwp_unwind(struct nw_interp *const i)
+{
+ const struct nw_i_sm_unwind u = {0};
+
+ i->sm.unwind = u;
+ i->next = run;
+}
diff --git a/src/run.c b/src/run.c
new file mode 100644
index 0000000..2995ece
--- /dev/null
+++ b/src/run.c
@@ -0,0 +1,16 @@
+/*
+ * 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>
+
+enum nw_state nw_run(struct nw_inst *const i)
+{
+ return i->next(i);
+}
diff --git a/src/start.c b/src/start.c
new file mode 100644
index 0000000..788a5ad
--- /dev/null
+++ b/src/start.c
@@ -0,0 +1,81 @@
+/*
+ * 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 <nanowasm/types.h>
+#include <nw/inst.h>
+#include <nw/interp.h>
+#include <nw/log.h>
+#include <nw/routines.h>
+#include <stddef.h>
+
+static enum nw_state push(struct nw_interp *const i)
+{
+ const struct nw_i_sm_exp *const e = &i->sm.export;
+
+ if (e->kind != NW_KIND_FUNCTION)
+ {
+ static const char *const exc = "unexpected import kind";
+
+ i->exception = exc;
+#ifdef NW_LOG
+ nwp_log("%s: %d\n", exc, e->kind);
+#endif
+ return NW_FATAL;
+ }
+
+ nwp_call(i, e->index);
+ return NW_AGAIN;
+}
+
+static enum nw_state find_start(struct nw_inst *const inst)
+{
+ struct nw_interp *const i = &inst->interp;
+ struct nw_interp_cfg cfg;
+
+ cfg = i->cfg;
+
+ if (nwp_interp_start(i, &cfg, NULL))
+ {
+#ifdef NW_LOG
+ nwp_log("nwp_interp_start failed\n");
+#endif
+ return NW_FATAL;
+ }
+
+ nwp_find_export(i, inst->entry, push);
+ inst->next = nwp_inst_run;
+ return NW_AGAIN;
+}
+
+static enum nw_state init_data(struct nw_inst *const i)
+{
+ nwp_init_data(i, find_start);
+ return NW_AGAIN;
+}
+
+int nw_start(struct nw_inst *const i, const struct nw_inst_cfg *const icfg)
+{
+ const struct nw_inst inst = {0};
+
+ *i = inst;
+ i->entry = icfg->entry;
+
+ if (nwp_interp_start(&i->interp, &icfg->interp_cfg,
+ &nwp_interp_initexpr_set))
+ {
+#ifdef NW_LOG
+ nwp_log("nwp_interp_start failed\n");
+#endif
+ return -1;
+ }
+
+ nwp_init_globals(i, init_data);
+ return 0;
+}
diff --git a/src/types/CMakeLists.txt b/src/types/CMakeLists.txt
new file mode 100644
index 0000000..f3f0cb3
--- /dev/null
+++ b/src/types/CMakeLists.txt
@@ -0,0 +1,11 @@
+# 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
+ get.c
+ sz.c
+)
diff --git a/src/types/get.c b/src/types/get.c
new file mode 100644
index 0000000..40ce472
--- /dev/null
+++ b/src/types/get.c
@@ -0,0 +1,42 @@
+/*
+ * 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/types.h>
+#include <nw/log.h>
+
+int nwp_get_type(const nw_varint7 type, enum nw_type *const out)
+{
+ static const struct type
+ {
+ nw_varint7 key;
+ enum nw_type value;
+ } list[] =
+ {
+ {0x7f, NW_TYPE_I32},
+ {0x7e, NW_TYPE_I64},
+ {0x7d, NW_TYPE_F32},
+ {0x7c, NW_TYPE_F64}
+ };
+
+ size_t i;
+
+ for (i = 0; i < sizeof list / sizeof *list; i++)
+ {
+ const struct type *const t = &list[i];
+
+ if (type == t->key)
+ {
+ *out = t->value;
+ return 0;
+ }
+ }
+
+ return -1;
+}
diff --git a/src/types/sz.c b/src/types/sz.c
new file mode 100644
index 0000000..e6660a9
--- /dev/null
+++ b/src/types/sz.c
@@ -0,0 +1,29 @@
+/*
+ * 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/types.h>
+#include <stddef.h>
+
+int nwp_type_sz(const enum nw_type t, size_t *const out)
+{
+ static const size_t sz[] =
+ {
+ sizeof (long),
+ sizeof (struct nw_ll),
+ sizeof (float),
+ sizeof (double)
+ };
+
+ if (t < 0 || t >= sizeof sz / sizeof *sz)
+ return -1;
+
+ *out = sz[t];
+ return 0;
+}