├── .github └── workflows │ └── test.yaml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.zig ├── build.zig.zon ├── docs ├── CONTRIBUTING.md └── REFERENCES.md ├── examples └── fib │ ├── build.zig │ └── src │ ├── fib.wasm │ └── fib.zig ├── src ├── error.zig ├── instance.zig ├── instance │ └── vm.zig ├── main.zig ├── module.zig ├── module │ ├── parser.zig │ └── validator.zig ├── opcode.zig ├── rr.zig ├── store.zig ├── store │ ├── data.zig │ ├── elem.zig │ ├── function.zig │ ├── global.zig │ ├── memory.zig │ └── table.zig ├── test │ ├── fact.wasm │ ├── fib.wasm │ └── test.wasm ├── valtype.zig └── wasi │ └── wasi.zig ├── test ├── .gitattributes ├── fact.wasm ├── fact.wat ├── fib.wasm ├── fib.wat ├── fuzzer │ ├── build.zig │ └── src │ │ └── fuzzer.zig ├── interface │ ├── README.md │ ├── build.zig │ └── src │ │ └── interface.zig ├── run.sh ├── test.wasm ├── test.wat └── testrunner │ └── src │ └── testrunner.zig └── tools ├── README.md ├── zware-gen.zig └── zware-run.zig /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: zware tests 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | source-code-tests: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | runs-on: ${{matrix.os}} 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: mlugg/setup-zig@v1.2.1 18 | with: 19 | version: 0.14.0 20 | - run: zig build unittest 21 | testsuite: 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, macos-latest, windows-latest] 25 | runs-on: ${{matrix.os}} 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: mlugg/setup-zig@v1.2.1 29 | with: 30 | version: 0.14.0 31 | - name: Run testsuite 32 | run: zig build testsuite 33 | lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: mlugg/setup-zig@v1.2.1 38 | with: 39 | version: 0.14.0 40 | - run: zig fmt --check src/*.zig 41 | fib: 42 | strategy: 43 | matrix: 44 | os: [ubuntu-latest, macos-latest, windows-latest] 45 | runs-on: ${{matrix.os}} 46 | steps: 47 | - uses: actions/checkout@v2 48 | - uses: mlugg/setup-zig@v1.2.1 49 | with: 50 | version: 0.14.0 51 | - name: Build fib 52 | working-directory: ./examples/fib 53 | run: zig build 54 | build: 55 | strategy: 56 | matrix: 57 | os: [ubuntu-latest, macos-latest, windows-latest] 58 | runs-on: ${{matrix.os}} 59 | steps: 60 | - uses: actions/checkout@v2 61 | - uses: mlugg/setup-zig@v1.2.1 62 | with: 63 | version: 0.14.0 64 | - name: Build zware-gen, zware-run and libzware.a 65 | run: zig build 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | .zig-cache 3 | zig-out 4 | test/testrunner/bin -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Malcolm Still 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

zware

2 | 3 |
4 | WebAssembly logo: a purple-blue square containing the uppercase letters W A. The square has a semicirclular notch on the top edge in the middle 5 |
6 | Zig WebAssembly Runtime Engine 7 |
8 | 9 | ## About 10 | 11 | `zware` is a library for executing WebAssembly embedded in [Zig](https://ziglang.org) programs. 12 | 13 | ## Example 14 | 15 | From `examples/fib`: 16 | 17 | ```zig 18 | const std = @import("std"); 19 | const zware = @import("zware"); 20 | const Store = zware.Store; 21 | const Module = zware.Module; 22 | const Instance = zware.Instance; 23 | const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; 24 | var gpa = GeneralPurposeAllocator(.{}){}; 25 | 26 | pub fn main() !void { 27 | defer _ = gpa.deinit(); 28 | const alloc = gpa.allocator(); 29 | 30 | const bytes = @embedFile("fib.wasm"); 31 | 32 | var store = Store.init(alloc); 33 | defer store.deinit(); 34 | 35 | var module = Module.init(alloc, bytes); 36 | defer module.deinit(); 37 | try module.decode(); 38 | 39 | var instance = Instance.init(alloc, &store, module); 40 | try instance.instantiate(); 41 | defer instance.deinit(); 42 | 43 | const n = 39; 44 | var in = [1]u64{n}; 45 | var out = [1]u64{0}; 46 | 47 | try instance.invoke("fib", in[0..], out[0..], .{}); 48 | 49 | const result: i32 = @bitCast(@as(u32, @truncate(out[0]))); 50 | std.debug.print("fib({}) = {}\n", .{ n, result }); 51 | } 52 | ``` 53 | 54 | ## Requirements 55 | 56 | ### Compile-time 57 | 58 | - Zig 0.12 (master) 59 | 60 | ### Run-time 61 | 62 | - None, zig generates static binaries: 63 | 64 | ```bash 65 | ➜ zware git:(master) ✗ ldd fib 66 | not a dynamic executable 67 | ``` 68 | 69 | ## Goals 70 | 71 | - Embed WebAssembly programs in other zig programs 72 | - Be fast enough to be useful 73 | 74 | ## Status 75 | 76 | - The project is very much alpha quality 77 | - WebAssembly 2.0 supported (apart from the vector / SIMD support which is WIP) 78 | - The WebAssembly official testsuite passes (not including SIMD tests) 79 | - Partial WASI support 80 | 81 | ## Running tests 82 | 83 | Use `zig build --help` to see all the test targets, here's a summary of the important ones: 84 | 85 | ```sh 86 | zig build test # Run all the tests (includes unittest and testsuite) 87 | zig build unittest # Run the library unittests 88 | zig build testsuite # Run all the testsuite tests 89 | zig build test-NAME # Run the NAME testsuite test, i.e. test-type 90 | ``` 91 | 92 | ## Does it run doom? 93 | 94 | Yes, [yes it does](https://github.com/malcolmstill/zware-doom) 95 | 96 | https://github.com/malcolmstill/zware/assets/2567177/c9acdcb2-69e7-495f-b3f1-89cf6b807a43 97 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const Build = @import("std").Build; 2 | 3 | pub fn build(b: *Build) !void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const zware_module = b.createModule(.{ 8 | .root_source_file = b.path("src/main.zig"), 9 | }); 10 | 11 | try b.modules.put(b.dupe("zware"), zware_module); 12 | 13 | const lib = b.addStaticLibrary(.{ 14 | .name = "zware", 15 | .root_source_file = b.path("src/main.zig"), 16 | .target = target, 17 | .optimize = optimize, 18 | }); 19 | b.installArtifact(lib); 20 | 21 | const main_tests = b.addTest(.{ 22 | .root_source_file = b.path("src/main.zig"), 23 | .optimize = optimize, 24 | }); 25 | 26 | const run_main_tests = b.addRunArtifact(main_tests); 27 | const unittest_step = b.step("unittest", "Run the library unittests"); 28 | unittest_step.dependOn(&run_main_tests.step); 29 | 30 | const wast2json = addWast2Json(b); 31 | 32 | const testrunner = b.addExecutable(.{ 33 | .name = "testrunner", 34 | .root_source_file = b.path("test/testrunner/src/testrunner.zig"), 35 | .target = target, 36 | .optimize = optimize, 37 | }); 38 | testrunner.root_module.addImport("zware", zware_module); 39 | 40 | const testsuite_dep = b.dependency("testsuite", .{}); 41 | 42 | const testsuite_step = b.step("testsuite", "Run all the testsuite tests"); 43 | for (test_names) |test_name| { 44 | const run_wast2json = b.addRunArtifact(wast2json); 45 | run_wast2json.addFileArg(testsuite_dep.path(b.fmt("{s}.wast", .{test_name}))); 46 | run_wast2json.addArg("-o"); 47 | const json_file = run_wast2json.addOutputFileArg(b.fmt("{s}.json", .{test_name})); 48 | 49 | const run_test = b.addRunArtifact(testrunner); 50 | run_test.addFileArg(json_file); 51 | run_test.cwd = json_file.dirname(); 52 | const step = b.step(b.fmt("test-{s}", .{test_name}), b.fmt("Run the '{s}' test", .{test_name})); 53 | step.dependOn(&run_test.step); 54 | testsuite_step.dependOn(&run_test.step); 55 | } 56 | 57 | const test_step = b.step("test", "Run all the tests"); 58 | test_step.dependOn(unittest_step); 59 | test_step.dependOn(testsuite_step); 60 | 61 | { 62 | const exe = b.addExecutable(.{ 63 | .name = "zware-run", 64 | .root_source_file = b.path("tools/zware-run.zig"), 65 | .target = target, 66 | .optimize = optimize, 67 | }); 68 | exe.root_module.addImport("zware", zware_module); 69 | const install = b.addInstallArtifact(exe, .{}); 70 | b.getInstallStep().dependOn(&install.step); 71 | const run = b.addRunArtifact(exe); 72 | run.step.dependOn(&install.step); 73 | if (b.args) |args| { 74 | run.addArgs(args); 75 | } 76 | b.step("run", "Run the cmdline runner zware-run").dependOn(&run.step); 77 | } 78 | 79 | { 80 | const exe = b.addExecutable(.{ 81 | .name = "zware-gen", 82 | .root_source_file = b.path("tools/zware-gen.zig"), 83 | .target = target, 84 | .optimize = optimize, 85 | }); 86 | exe.root_module.addImport("zware", zware_module); 87 | const install = b.addInstallArtifact(exe, .{}); 88 | b.getInstallStep().dependOn(&install.step); 89 | const run = b.addRunArtifact(exe); 90 | run.step.dependOn(&install.step); 91 | if (b.args) |args| { 92 | run.addArgs(args); 93 | } 94 | b.step("gen", "Run the cmdline runner zware-gen").dependOn(&run.step); 95 | } 96 | } 97 | 98 | fn addWast2Json(b: *Build) *Build.Step.Compile { 99 | const wabt_dep = b.dependency("wabt", .{}); 100 | 101 | const wabt_debug = b.option(enum { 102 | debug, 103 | no_debug, 104 | }, "wabt-debug", "build wabt with debug on") orelse .no_debug; 105 | 106 | const wabt_config_h = b.addConfigHeader(.{ 107 | .style = .{ .cmake = wabt_dep.path("src/config.h.in") }, 108 | .include_path = "wabt/config.h", 109 | }, .{ 110 | .WABT_VERSION_STRING = "1.0.34", 111 | .WABT_DEBUG = @as(?isize, if (wabt_debug == .debug) 1 else null), 112 | .HAVE_SNPRINTF = 1, 113 | .HAVE_SSIZE_T = 1, 114 | .HAVE_STRCASECMP = 1, 115 | .COMPILER_IS_CLANG = 1, 116 | .SIZEOF_SIZE_T = @sizeOf(usize), 117 | }); 118 | 119 | const wabt_lib = b.addStaticLibrary(.{ 120 | .name = "wabt", 121 | .target = b.graph.host, 122 | .optimize = .Debug, 123 | }); 124 | wabt_lib.addConfigHeader(wabt_config_h); 125 | wabt_lib.addIncludePath(wabt_dep.path("include")); 126 | wabt_lib.addCSourceFiles(.{ 127 | .root = wabt_dep.path("."), 128 | .files = &wabt_files, 129 | }); 130 | wabt_lib.linkLibCpp(); 131 | 132 | const wast2json = b.addExecutable(.{ 133 | .name = "wast2json", 134 | .target = b.graph.host, 135 | }); 136 | wast2json.addConfigHeader(wabt_config_h); 137 | wast2json.addIncludePath(wabt_dep.path("include")); 138 | wast2json.addCSourceFile(.{ 139 | .file = wabt_dep.path("src/tools/wast2json.cc"), 140 | }); 141 | wast2json.linkLibCpp(); 142 | wast2json.linkLibrary(wabt_lib); 143 | return wast2json; 144 | } 145 | 146 | const test_names = [_][]const u8{ 147 | "address", 148 | "align", 149 | "binary-leb128", 150 | "binary", 151 | "block", 152 | "br_if", 153 | "br_table", 154 | "br", 155 | "bulk", 156 | "call_indirect", 157 | "call", 158 | "comments", 159 | "const", 160 | "conversions", 161 | "custom", 162 | "data", 163 | "elem", 164 | "endianness", 165 | "exports", 166 | "f32_bitwise", 167 | "f32_cmp", 168 | "f32", 169 | "f64_bitwise", 170 | "f64_cmp", 171 | "f64", 172 | "fac", 173 | "float_exprs", 174 | "float_literals", 175 | "float_memory", 176 | "float_misc", 177 | "forward", 178 | "func_ptrs", 179 | "func", 180 | "global", 181 | "i32", 182 | "i64", 183 | "if", 184 | "imports", 185 | "inline-module", 186 | "int_exprs", 187 | "int_literals", 188 | "labels", 189 | "left-to-right", 190 | "linking", 191 | "load", 192 | "local_get", 193 | "local_set", 194 | "local_tee", 195 | "loop", 196 | "memory_copy", 197 | "memory_fill", 198 | "memory_grow", 199 | "memory_init", 200 | "memory_redundancy", 201 | "memory_size", 202 | "memory_trap", 203 | "memory", 204 | "names", 205 | "nop", 206 | "ref_func", 207 | "ref_is_null", 208 | "ref_null", 209 | "return", 210 | "select", 211 | "skip-stack-guard-page", 212 | "stack", 213 | "start", 214 | "store", 215 | "switch", 216 | "table_copy", 217 | "table_fill", 218 | "table_get", 219 | "table_grow", 220 | "table_init", 221 | "table_set", 222 | "table_size", 223 | "table-sub", 224 | "table", 225 | "token", 226 | "traps", 227 | "type", 228 | "unreachable", 229 | "unreached-invalid", 230 | "unreached-valid", 231 | "unwind", 232 | "utf8-custom-section-id", 233 | "utf8-import-field", 234 | "utf8-import-module", 235 | "utf8-invalid-encoding", 236 | }; 237 | 238 | const wabt_files = [_][]const u8{ 239 | "src/binary-reader-ir.cc", 240 | "src/binary-reader-logging.cc", 241 | "src/binary-reader.cc", 242 | "src/binary-writer-spec.cc", 243 | "src/binary-writer.cc", 244 | "src/binary.cc", 245 | "src/binding-hash.cc", 246 | "src/color.cc", 247 | "src/common.cc", 248 | "src/error-formatter.cc", 249 | "src/expr-visitor.cc", 250 | "src/feature.cc", 251 | "src/filenames.cc", 252 | "src/ir.cc", 253 | "src/leb128.cc", 254 | "src/lexer-source-line-finder.cc", 255 | "src/lexer-source.cc", 256 | "src/literal.cc", 257 | "src/opcode-code-table.c", 258 | "src/opcode.cc", 259 | "src/option-parser.cc", 260 | "src/resolve-names.cc", 261 | "src/shared-validator.cc", 262 | "src/stream.cc", 263 | "src/token.cc", 264 | "src/type-checker.cc", 265 | "src/utf8.cc", 266 | "src/validator.cc", 267 | "src/wast-lexer.cc", 268 | "src/wast-parser.cc", 269 | }; 270 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .zware, 3 | .version = "0.0.1", 4 | .fingerprint = 0xea6cb8e2e9e30e64, 5 | .minimum_zig_version = "0.14.0", 6 | .dependencies = .{ 7 | .wabt = .{ 8 | .url = "https://github.com/WebAssembly/wabt/archive/39f85a791cbbad91a253a851841a29777efdc2cd.tar.gz", 9 | .hash = "N-V-__8AAPRtdgAJe1z7Np-VSTp1OwsSUl8GXiz2B9YuMU1U", 10 | }, 11 | .testsuite = .{ 12 | .url = "https://github.com/WebAssembly/testsuite/archive/e25ae159357c055b3a6fac99043644e208d26d2a.tar.gz", 13 | .hash = "N-V-__8AAKtbtgBFkB_BMIKSUqC-temKvHmuqBSvjBlf4hD6", 14 | }, 15 | }, 16 | .paths = .{ 17 | "build.zig", 18 | "build.zig.zon", 19 | "src", 20 | "tools", 21 | "examples", 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## How do I update the testsuite? 4 | 5 | - Copy https://github.com/WebAssembly/testsuite into `test/testuite/ 6 | - 7 | -------------------------------------------------------------------------------- /docs/REFERENCES.md: -------------------------------------------------------------------------------- 1 | - https://webassembly.github.io/spec/core/exec/index.html 2 | - https://github.com/WebAssembly/design/blob/master/Semantics.md 3 | - https://github.com/WebAssembly/spec/tree/master/test/core 4 | - http://troubles.md/wasm-is-not-a-stack-machine/ 5 | - https://stackoverflow.com/questions/48638653/can-anyone-help-explain-the-one-pass-verification-process-shows-in-webassembly 6 | - https://binji.github.io/posts/webassembly-type-checking/ 7 | - https://webassembly.github.io/spec/core/appendix/algorithm.html 8 | - https://00f.net/2018/11/25/webassembly-doesnt-make-unsafe-languages-safe/ 9 | - https://news.ycombinator.com/item?id=18527535 10 | - https://webassembly.github.io/spec/core/appendix/algorithm.html 11 | - https://news.ycombinator.com/item?id=22399368 12 | - https://coinexchain.medium.com/wasm-introduction-part-2-instruction-set-operand-stack-38e5171b52e6 13 | - https://docs.google.com/document/d/1CieRxPy3Fp62LQdtWfhymikb_veZI7S9MnuCZw7biII/edit 14 | - https://stanford-cs242.github.io/f19/assignments/assign5/ 15 | - https://github.com/WebAssembly/design/issues/736 16 | - https://www.cl.cam.ac.uk/~caw77/papers/mechanising-and-verifying-the-webassembly-specification-draft.pdf 17 | - https://cseweb.ucsd.edu/classes/fa19/cse127-ab/slides/3-lowlevelmitigations.pdf 18 | - https://arxiv.org/pdf/2010.01723.pdf 19 | - https://blog.veitheller.de/Speeding_up_an_Interpreter.html 20 | - https://medium.com/bumble-tech/when-pigs-fly-optimising-bytecode-interpreters-f64fb6bfa20f 21 | - https://chromium.googlesource.com/external/github.com/WebAssembly/binaryen/+/daf0b9cf0a5cf32727bfd06960fac48b30813d93/README.md 22 | - https://bytecodealliance.org/articles/multi-value-all-the-wasm 23 | - https://hackernoon.com/webassembly-the-journey-jit-compilers-dfa4081a6ffb 24 | - https://wingolog.org/archives/2011/06/21/security-implications-of-jit-compilation 25 | - https://blog.cloudflare.com/building-fast-interpreters-in-rust/ 26 | - https://news.ycombinator.com/item?id=22024758 27 | - http://www.complang.tuwien.ac.at/forth/threaded-code.html 28 | - http://www.software-lab.org/publications/Wasabi_arXiv_1808.10652.pdf 29 | - https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html 30 | - https://github.com/rhmoller/wasm-by-hand 31 | - https://core.ac.uk/download/pdf/333888641.pdf 32 | - http://lua-users.org/lists/lua-l/2011-02/msg00742.html 33 | - https://hacks.mozilla.org/2017/07/webassembly-table-imports-what-are-they/ 34 | - https://cfallin.org/blog/2021/01/22/cranelift-isel-2/ 35 | -------------------------------------------------------------------------------- /examples/fib/build.zig: -------------------------------------------------------------------------------- 1 | const Build = @import("std").Build; 2 | 3 | pub fn build(b: *Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const exe = b.addExecutable(.{ 8 | .name = "fib", 9 | .root_source_file = b.path("src/fib.zig"), 10 | .target = target, 11 | .optimize = optimize, 12 | }); 13 | exe.root_module.addAnonymousImport("zware", .{ 14 | .root_source_file = b.path("../../src/main.zig"), 15 | }); 16 | b.installArtifact(exe); 17 | 18 | const run_cmd = b.addRunArtifact(exe); 19 | run_cmd.step.dependOn(b.getInstallStep()); 20 | if (b.args) |args| { 21 | run_cmd.addArgs(args); 22 | } 23 | 24 | const run_step = b.step("run", "Run the app"); 25 | run_step.dependOn(&run_cmd.step); 26 | } 27 | -------------------------------------------------------------------------------- /examples/fib/src/fib.wasm: -------------------------------------------------------------------------------- 1 | asm`fib 2 | *( AF@A AF@A Ak Akj -------------------------------------------------------------------------------- /examples/fib/src/fib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zware = @import("zware"); 3 | const Store = zware.Store; 4 | const Module = zware.Module; 5 | const Instance = zware.Instance; 6 | const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; 7 | var gpa = GeneralPurposeAllocator(.{}){}; 8 | 9 | pub fn main() !void { 10 | defer _ = gpa.deinit(); 11 | const alloc = gpa.allocator(); 12 | 13 | const bytes = @embedFile("fib.wasm"); 14 | 15 | var store = Store.init(alloc); 16 | defer store.deinit(); 17 | 18 | var module = Module.init(alloc, bytes); 19 | defer module.deinit(); 20 | try module.decode(); 21 | 22 | var instance = Instance.init(alloc, &store, module); 23 | try instance.instantiate(); 24 | defer instance.deinit(); 25 | 26 | const n = 39; 27 | var in = [1]u64{n}; 28 | var out = [1]u64{0}; 29 | 30 | try instance.invoke("fib", in[0..], out[0..], .{}); 31 | 32 | const result: i32 = @bitCast(@as(u32, @truncate(out[0]))); 33 | std.debug.print("fib({}) = {}\n", .{ n, result }); 34 | } 35 | -------------------------------------------------------------------------------- /src/error.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const module = @import("module.zig"); 4 | 5 | /// A function can take a reference to this to pass extra error information to the caller. 6 | /// A function that does this guarantees the reference will be populated if it returns error.SeeContext. 7 | /// Error implements a format function. 8 | /// The same error instance can be re-used for multiple calls. 9 | /// 10 | /// Example usage: 11 | /// ---- 12 | /// var zware_error: Error = undefined; 13 | /// foo(&zware_error) catch |err| switch (err) { 14 | /// error.SeeContext => std.log.err("foo failed: {}", .{zware_error}), 15 | /// else => |err| return err, 16 | /// }; 17 | /// --- 18 | pub const Error = union(enum) { 19 | missing_import: module.Import, 20 | any: anyerror, 21 | 22 | /// Called by a function that wants to both populate this error instance and let the caller 23 | /// know it's been populated by returning error.SeeContext. 24 | pub fn set(self: *Error, e: Error) error{SeeContext} { 25 | self.* = e; 26 | return error.SeeContext; 27 | } 28 | pub fn format( 29 | self: Error, 30 | comptime fmt: []const u8, 31 | options: std.fmt.FormatOptions, 32 | writer: anytype, 33 | ) !void { 34 | _ = fmt; 35 | _ = options; 36 | switch (self) { 37 | .missing_import => |import| try writer.print( 38 | "missing {s} import '{s}' from module '{s}'", 39 | .{ @tagName(import.desc_tag), import.name, import.module }, 40 | ), 41 | .any => |e| try writer.print("{s}", .{@errorName(e)}), 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/instance.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const math = std.math; 4 | const posix = std.posix; 5 | const wasi = std.os.wasi; 6 | const ArrayList = std.ArrayList; 7 | const Error = @import("error.zig").Error; 8 | const Module = @import("module.zig").Module; 9 | const Store = @import("store.zig").ArrayListStore; 10 | const Function = @import("store/function.zig").Function; 11 | const Memory = @import("store/memory.zig").Memory; 12 | const Table = @import("store/table.zig").Table; 13 | const Global = @import("store/global.zig").Global; 14 | const Elem = @import("store/elem.zig").Elem; 15 | const Data = @import("store/data.zig").Data; 16 | const VirtualMachine = @import("instance/vm.zig").VirtualMachine; 17 | 18 | const VirtualMachineOptions = struct { 19 | frame_stack_size: comptime_int = 1024, 20 | label_stack_size: comptime_int = 1024, 21 | operand_stack_size: comptime_int = 1024, 22 | }; 23 | 24 | // Instance 25 | // 26 | // An Instance represents the runtime instantiation of a particular module. 27 | // 28 | // It contains: 29 | // - a copy of the module it is an instance of 30 | // - a pointer to the Store shared amongst modules 31 | // - `memaddrs`: a set of addresses in the store to map a modules 32 | // definitions to those in the store 33 | // - `tableaddrs`: as per `memaddrs` but for tables 34 | // - `globaladdrs`: as per `memaddrs` but for globals 35 | pub const Instance = struct { 36 | module: Module, 37 | store: *Store, 38 | funcaddrs: ArrayList(usize), 39 | memaddrs: ArrayList(usize), 40 | tableaddrs: ArrayList(usize), 41 | globaladdrs: ArrayList(usize), 42 | // TODO: exports (this was in 1.0...why didn't we need it) 43 | elemaddrs: ArrayList(usize), 44 | dataaddrs: ArrayList(usize), 45 | 46 | // wasi-specific fields 47 | // 48 | // They are defined on an instance but only really on an 49 | // initial instance that is invoked. When initialising a 50 | // VirtualMachine this initial instance will pass its wasi 51 | // data to the VirtualMachine (via pointers). 52 | // 53 | // The wasi implementations can (and must) then lookup this data via 54 | // the VirtualMachine, it shouldn't call e.g. `vm.inst...` because 55 | // a VirtualMachine swaps out its `inst` (instance) pointer as 56 | // it executes; an arbitrary `inst` will not contain the correct 57 | // data. 58 | wasi_preopens: std.AutoHashMap(wasi.fd_t, WasiPreopen), 59 | wasi_args: std.ArrayList([:0]u8), 60 | wasi_env: std.StringHashMap([]const u8), 61 | 62 | pub fn init(alloc: mem.Allocator, store: *Store, module: Module) Instance { 63 | return Instance{ 64 | .module = module, 65 | .store = store, 66 | .funcaddrs = ArrayList(usize).init(alloc), 67 | .memaddrs = ArrayList(usize).init(alloc), 68 | .tableaddrs = ArrayList(usize).init(alloc), 69 | .globaladdrs = ArrayList(usize).init(alloc), 70 | .elemaddrs = ArrayList(usize).init(alloc), 71 | .dataaddrs = ArrayList(usize).init(alloc), 72 | 73 | .wasi_preopens = std.AutoHashMap(wasi.fd_t, WasiPreopen).init(alloc), 74 | .wasi_args = ArrayList([:0]u8).init(alloc), 75 | .wasi_env = std.StringHashMap([]const u8).init(alloc), 76 | }; 77 | } 78 | 79 | pub fn deinit(self: *Instance) void { 80 | self.funcaddrs.deinit(); 81 | self.memaddrs.deinit(); 82 | self.tableaddrs.deinit(); 83 | self.globaladdrs.deinit(); 84 | self.elemaddrs.deinit(); 85 | self.dataaddrs.deinit(); 86 | 87 | self.wasi_preopens.deinit(); 88 | self.wasi_args.deinit(); 89 | self.wasi_env.deinit(); 90 | } 91 | 92 | pub fn getFunc(self: *Instance, funcidx: usize) !Function { 93 | if (funcidx >= self.funcaddrs.items.len) return error.FunctionIndexOutOfBounds; 94 | const funcaddr = self.funcaddrs.items[funcidx]; 95 | return try self.store.function(funcaddr); 96 | } 97 | 98 | // Lookup a memory in store via the modules index 99 | pub fn getMemory(self: *Instance, index: usize) !*Memory { 100 | // TODO: with a verified program we shouldn't need to check this 101 | if (index >= self.memaddrs.items.len) return error.MemoryIndexOutOfBounds; 102 | const memaddr = self.memaddrs.items[index]; 103 | return try self.store.memory(memaddr); 104 | } 105 | 106 | pub fn getTable(self: *Instance, index: usize) !*Table { 107 | // TODO: with a verified program we shouldn't need to check this 108 | if (index >= self.tableaddrs.items.len) return error.TableIndexOutOfBounds; 109 | const tableaddr = self.tableaddrs.items[index]; 110 | return try self.store.table(tableaddr); 111 | } 112 | 113 | pub fn getGlobal(self: *Instance, index: usize) !*Global { 114 | if (index >= self.globaladdrs.items.len) return error.GlobalIndexOutOfBounds; 115 | const globaladdr = self.globaladdrs.items[index]; 116 | return try self.store.global(globaladdr); 117 | } 118 | 119 | pub fn getElem(self: *Instance, elemidx: usize) !*Elem { 120 | if (elemidx >= self.elemaddrs.items.len) return error.ElemIndexOutOfBounds; 121 | const elemaddr = self.elemaddrs.items[elemidx]; 122 | return try self.store.elem(elemaddr); 123 | } 124 | 125 | pub fn getData(self: *Instance, dataidx: usize) !*Data { 126 | if (dataidx >= self.dataaddrs.items.len) return error.DataIndexOutOfBounds; 127 | const dataaddr = self.dataaddrs.items[dataidx]; 128 | return try self.store.data(dataaddr); 129 | } 130 | 131 | pub fn instantiate(self: *Instance) !void { 132 | var context: Error = undefined; 133 | self.instantiateWithError(&context) catch |err| switch (err) { 134 | error.SeeContext => switch (context) { 135 | .missing_import => return error.ImportNotFound, 136 | .any => |e| return e, 137 | }, 138 | else => |e| return e, 139 | }; 140 | } 141 | 142 | pub fn instantiateWithError(self: *Instance, err: *Error) !void { 143 | if (self.module.decoded == false) return error.ModuleNotDecoded; 144 | 145 | try self.instantiateImports(err); 146 | try self.instantiateFunctions(); 147 | try self.instantiateGlobals(); 148 | try self.instantiateMemories(); 149 | try self.instantiateTables(); 150 | try self.instantiateData(); 151 | try self.instantiateElements(); 152 | 153 | if (self.module.start) |start_function| { 154 | try self.invokeStart(start_function, .{}); 155 | } 156 | } 157 | 158 | fn instantiateImports(self: *Instance, err: *Error) error{ OutOfMemory, SeeContext }!void { 159 | for (self.module.imports.list.items) |import| { 160 | const import_handle = self.store.import(import.module, import.name, import.desc_tag) catch |e| switch (e) { 161 | error.ImportNotFound => return err.set(.{ .missing_import = import }), 162 | }; 163 | switch (import.desc_tag) { 164 | .Func => try self.funcaddrs.append(import_handle), 165 | .Mem => try self.memaddrs.append(import_handle), 166 | .Table => try self.tableaddrs.append(import_handle), 167 | .Global => try self.globaladdrs.append(import_handle), 168 | } 169 | } 170 | } 171 | 172 | fn instantiateFunctions(self: *Instance) !void { 173 | // Initialise (internal) functions 174 | // 175 | // We have two possibilities: 176 | // 1. the function is imported 177 | // 2. the function is defined in this module (i.e. there is an associatd code section) 178 | // 179 | // In the case of 1 we need to check that the type of import matches the type 180 | // of the actual function in the store 181 | // 182 | // For 2, we need to add the function to the store 183 | const imported_function_count = self.funcaddrs.items.len; 184 | for (self.module.functions.list.items, 0..) |function_def, i| { 185 | if (function_def.import) |_| { 186 | // Check that the function defintion (which this module expects) 187 | // is the same as the function in the store 188 | const functype = try self.module.types.lookup(function_def.typeidx); 189 | const external_function = try self.getFunc(i); 190 | 191 | try external_function.checkSignatures(functype); 192 | } else { 193 | const code = try self.module.codes.lookup(i - imported_function_count); 194 | const func = try self.module.functions.lookup(i); 195 | const functype = try self.module.types.lookup(func.typeidx); 196 | 197 | const handle = try self.store.addFunction(Function{ 198 | .params = functype.params, 199 | .results = functype.results, 200 | .subtype = .{ 201 | .function = .{ 202 | .start = code.start, 203 | .required_stack_space = code.required_stack_space, 204 | .locals_count = code.locals_count, 205 | .instance = self, 206 | }, 207 | }, 208 | }); 209 | 210 | // Need to do this regardless of if import or internal 211 | try self.funcaddrs.append(handle); 212 | } 213 | } 214 | } 215 | 216 | fn instantiateGlobals(self: *Instance) !void { 217 | for (self.module.globals.list.items, 0..) |global_def, i| { 218 | if (global_def.import != null) { 219 | const imported_global = try self.getGlobal(i); 220 | if (imported_global.mutability != global_def.mutability) return error.MismatchedMutability; 221 | if (imported_global.valtype != global_def.valtype) return error.MismatchedGlobalType; 222 | } else { 223 | const value = if (global_def.start) |start| try self.invokeExpression(start, u64, .{}) else 0; 224 | const handle = try self.store.addGlobal(Global{ 225 | .value = value, 226 | .mutability = global_def.mutability, 227 | .valtype = global_def.valtype, 228 | }); 229 | try self.globaladdrs.append(handle); 230 | } 231 | } 232 | } 233 | 234 | fn instantiateMemories(self: *Instance) !void { 235 | for (self.module.memories.list.items, 0..) |memtype, i| { 236 | if (memtype.import != null) { 237 | const imported_mem = try self.getMemory(i); 238 | // Use the current size of the imported mem as min (rather than imported_mem.min). See https://github.com/WebAssembly/spec/pull/1293 239 | try memtype.limits.checkMatch(imported_mem.size(), imported_mem.max); 240 | } else { 241 | const handle = try self.store.addMemory(memtype.limits.min, memtype.limits.max); 242 | try self.memaddrs.append(handle); 243 | } 244 | } 245 | } 246 | 247 | fn instantiateTables(self: *Instance) !void { 248 | for (self.module.tables.list.items, 0..) |tabletype, i| { 249 | if (tabletype.import != null) { 250 | const imported_table = try self.getTable(i); 251 | if (imported_table.reftype != tabletype.reftype) return error.ImportedTableRefTypeMismatch; 252 | try tabletype.limits.checkMatch(imported_table.min, imported_table.max); 253 | } else { 254 | const handle = try self.store.addTable(tabletype.reftype, tabletype.limits.min, tabletype.limits.max); 255 | try self.tableaddrs.append(handle); 256 | } 257 | } 258 | } 259 | 260 | fn instantiateData(self: *Instance) !void { 261 | for (self.module.datas.list.items) |datatype| { 262 | const dataddr = try self.store.addData(datatype.count); 263 | try self.dataaddrs.append(dataddr); 264 | var data = try self.store.data(dataddr); 265 | 266 | // TODO: Do we actually need to copy the data or just close over module bytes? 267 | for (datatype.data, 0..) |byte, j| { 268 | try data.set(j, byte); 269 | } 270 | 271 | switch (datatype.mode) { 272 | .Passive => continue, 273 | .Active => |active| { 274 | const memaddr = self.memaddrs.items[active.memidx]; 275 | const memory = try self.store.memory(memaddr); 276 | 277 | const offset = try self.invokeExpression(active.offset, u32, .{}); 278 | try memory.copy(offset, data.data); 279 | data.dropped = true; 280 | }, 281 | } 282 | } 283 | } 284 | 285 | fn instantiateElements(self: *Instance) !void { 286 | for (self.module.elements.list.items) |elemtype| { 287 | const elemaddr = try self.store.addElem(elemtype.reftype, elemtype.count); 288 | try self.elemaddrs.append(elemaddr); 289 | var elem = try self.store.elem(elemaddr); 290 | 291 | for (self.module.element_init_offsets.items[elemtype.init .. elemtype.init + elemtype.count], 0..) |expr, j| { 292 | const funcaddr = try self.invokeExpression(expr, u32, .{}); 293 | try elem.set(@intCast(j), funcaddr); 294 | } 295 | 296 | if (elemtype.mode != .Passive) { 297 | elem.dropped = true; 298 | } 299 | 300 | if (elemtype.mode == .Active) { 301 | const table = try self.getTable(elemtype.mode.Active.tableidx); 302 | const offset = try self.invokeExpression(elemtype.mode.Active.offset, u32, .{}); 303 | 304 | const index = math.add(u32, offset, elemtype.count) catch return error.OutOfBoundsMemoryAccess; 305 | if (index > table.size()) return error.OutOfBoundsMemoryAccess; 306 | 307 | for (elem.elem, 0..) |funcaddr, i| { 308 | try table.set(@intCast(offset + i), funcaddr); 309 | } 310 | } 311 | } 312 | } 313 | 314 | // invoke 315 | // 316 | // Similar to invoke, but without some type checking 317 | pub fn invoke(self: *Instance, name: []const u8, in: []u64, out: []u64, comptime options: VirtualMachineOptions) !void { 318 | const funcidx = try self.module.getExport(.Func, name); 319 | if (funcidx >= self.module.functions.list.items.len) return error.FuncIndexExceedsTypesLength; 320 | 321 | const function = try self.getFunc(funcidx); 322 | 323 | var frame_stack: [options.frame_stack_size]VirtualMachine.Frame = [_]VirtualMachine.Frame{undefined} ** options.frame_stack_size; 324 | var label_stack: [options.label_stack_size]VirtualMachine.Label = [_]VirtualMachine.Label{undefined} ** options.label_stack_size; 325 | var op_stack: [options.operand_stack_size]u64 = [_]u64{0} ** options.operand_stack_size; 326 | 327 | switch (function.subtype) { 328 | .function => |f| { 329 | if (function.params.len != in.len) return error.ParamCountMismatch; 330 | if (function.results.len != out.len) return error.ResultCountMismatch; 331 | 332 | // 6. set up our stacks 333 | var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], f.instance); 334 | 335 | const locals_start = vm.op_ptr; 336 | 337 | // 7b. push params 338 | for (in) |arg| { 339 | try vm.pushOperand(u64, arg); 340 | } 341 | 342 | // 7c. push (i.e. make space for) locals 343 | var i: usize = 0; 344 | while (i < f.locals_count) : (i += 1) { 345 | try vm.pushOperand(u64, 0); 346 | } 347 | 348 | // Check we have enough stack space 349 | try vm.checkStackSpace(f.required_stack_space); 350 | 351 | // 7a. push control frame 352 | try vm.pushFrame(VirtualMachine.Frame{ 353 | .op_stack_len = locals_start, 354 | .label_stack_len = vm.label_ptr, 355 | .return_arity = function.results.len, 356 | .inst = f.instance, 357 | }, f.locals_count + function.params.len); 358 | 359 | // 7a.2. push label for our implicit function block. We know we don't have 360 | // any code to execute after calling invoke, but we will need to 361 | // pop a Label 362 | try vm.pushLabel(VirtualMachine.Label{ 363 | .return_arity = function.results.len, 364 | .op_stack_len = locals_start, 365 | .branch_target = 0, 366 | }); 367 | 368 | // 8. Execute our function 369 | try vm.invoke(f.start); 370 | 371 | // 9. 372 | for (out, 0..) |_, out_index| { 373 | out[out_index] = vm.popOperand(u64); 374 | } 375 | }, 376 | .host_function => |host_func| { 377 | var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], self); 378 | try host_func.func(&vm, host_func.context); 379 | }, 380 | } 381 | } 382 | 383 | pub fn invokeStart(self: *Instance, index: u32, comptime options: VirtualMachineOptions) !void { 384 | const function = try self.getFunc(index); 385 | 386 | var frame_stack: [options.frame_stack_size]VirtualMachine.Frame = [_]VirtualMachine.Frame{undefined} ** options.frame_stack_size; 387 | var label_stack: [options.label_stack_size]VirtualMachine.Label = [_]VirtualMachine.Label{undefined} ** options.label_stack_size; 388 | var op_stack: [options.operand_stack_size]u64 = [_]u64{0} ** options.operand_stack_size; 389 | 390 | switch (function.subtype) { 391 | .function => |f| { 392 | var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], f.instance); 393 | 394 | const locals_start = vm.op_ptr; 395 | 396 | var i: usize = 0; 397 | while (i < f.locals_count) : (i += 1) { 398 | try vm.pushOperand(u64, 0); 399 | } 400 | 401 | // Check we have enough stack space 402 | try vm.checkStackSpace(f.required_stack_space); 403 | 404 | try vm.pushFrame(VirtualMachine.Frame{ 405 | .op_stack_len = locals_start, 406 | .label_stack_len = vm.label_ptr, 407 | .return_arity = 0, 408 | .inst = f.instance, 409 | }, f.locals_count); 410 | 411 | try vm.pushLabel(VirtualMachine.Label{ 412 | .return_arity = 0, 413 | .op_stack_len = locals_start, 414 | .branch_target = 0, 415 | }); 416 | 417 | try vm.invoke(f.start); 418 | }, 419 | .host_function => |host_func| { 420 | var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], self); 421 | try host_func.func(&vm, host_func.context); 422 | }, 423 | } 424 | } 425 | 426 | pub fn invokeExpression(self: *Instance, start: usize, comptime Result: type, comptime options: VirtualMachineOptions) !Result { 427 | var frame_stack: [options.frame_stack_size]VirtualMachine.Frame = [_]VirtualMachine.Frame{undefined} ** options.frame_stack_size; 428 | var label_stack: [options.label_stack_size]VirtualMachine.Label = [_]VirtualMachine.Label{undefined} ** options.label_stack_size; 429 | var op_stack: [options.operand_stack_size]u64 = [_]u64{0} ** options.operand_stack_size; 430 | 431 | var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], self); 432 | 433 | const locals_start = vm.op_ptr; 434 | 435 | try vm.pushFrame(VirtualMachine.Frame{ 436 | .op_stack_len = locals_start, 437 | .label_stack_len = vm.label_ptr, 438 | .return_arity = 1, 439 | .inst = self, 440 | }, 0); 441 | 442 | try vm.pushLabel(VirtualMachine.Label{ 443 | .return_arity = 1, 444 | .op_stack_len = locals_start, 445 | }); 446 | 447 | try vm.invoke(start); 448 | 449 | switch (Result) { 450 | u64 => return vm.popAnyOperand(), 451 | else => return vm.popOperand(Result), 452 | } 453 | } 454 | 455 | pub fn addWasiPreopen(self: *Instance, wasi_fd: wasi.fd_t, name: []const u8, host_fd: posix.fd_t) !void { 456 | return self.wasi_preopens.put(wasi_fd, .{ 457 | .wasi_fd = wasi_fd, 458 | .name = name, 459 | .host_fd = host_fd, 460 | }); 461 | } 462 | 463 | // FIXME: hide any allocation / deinit inside Instance 464 | // Caller must call std.process.argsFree on returned args 465 | // 466 | // This forwards all the processes's command line args to the 467 | // virtual machine. 468 | // 469 | // TODO: we probably want to allow consumers of zware more fine-grained 470 | // control of which arguments get exposed to an instance. A similar 471 | // thing would be desirable for env vars. 472 | pub fn forwardArgs(self: *Instance, alloc: mem.Allocator) ![][:0]u8 { 473 | const args = try std.process.argsAlloc(alloc); 474 | 475 | for (args) |arg| { 476 | try self.wasi_args.append(arg); 477 | } 478 | 479 | return args; 480 | } 481 | }; 482 | 483 | pub const WasiPreopen = struct { 484 | wasi_fd: wasi.fd_t, 485 | name: []const u8, 486 | host_fd: std.posix.fd_t, 487 | }; 488 | 489 | const _ = std.testing.refAllDecls(); 490 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | pub const Error = @import("error.zig").Error; 2 | pub const Module = @import("module.zig").Module; 3 | pub const FuncType = @import("module.zig").FuncType; 4 | pub const Instance = @import("instance.zig").Instance; 5 | pub const VirtualMachine = @import("instance/vm.zig").VirtualMachine; 6 | pub const WasmError = @import("instance/vm.zig").WasmError; 7 | pub const Store = @import("store.zig").ArrayListStore; 8 | pub const Function = @import("store/function.zig").Function; 9 | pub const Global = @import("store/global.zig").Global; 10 | pub const Memory = @import("store/memory.zig").Memory; 11 | pub const ValType = @import("valtype.zig").ValType; 12 | pub const wasi = @import("wasi/wasi.zig"); 13 | 14 | test { 15 | _ = @import("module/validator.zig"); 16 | _ = @import("instance/vm.zig"); 17 | _ = @import("module.zig"); 18 | } 19 | -------------------------------------------------------------------------------- /src/module.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const leb = std.leb; 4 | const math = std.math; 5 | const unicode = std.unicode; 6 | const ArrayList = std.ArrayList; 7 | const Rr = @import("rr.zig").Rr; 8 | const RrOpcode = @import("rr.zig").RrOpcode; 9 | const Instance = @import("instance.zig").Instance; 10 | const Parser = @import("module/parser.zig").Parser; 11 | const Parsed = @import("module/parser.zig").Parsed; 12 | const NumType = @import("valtype.zig").NumType; 13 | const ValType = @import("valtype.zig").ValType; 14 | const RefType = @import("valtype.zig").RefType; 15 | 16 | pub const Module = struct { 17 | decoded: bool = false, 18 | alloc: mem.Allocator, 19 | wasm_bin: []const u8, 20 | customs: Section(Custom), 21 | types: Section(FuncType), 22 | imports: Section(Import), 23 | functions: Section(Function), 24 | tables: Section(TableType), 25 | memories: Section(MemType), 26 | globals: Section(GlobalType), 27 | exports: Section(Export), 28 | elements: Section(ElementSegment), 29 | codes: Section(Code), 30 | datas: Section(DataSegment), 31 | start: ?u32 = null, 32 | function_index_start: ?usize = null, 33 | data_count: ?u32 = null, 34 | element_init_offsets: ArrayList(usize), 35 | parsed_code: ArrayList(Rr), 36 | local_types: ArrayList(LocalType), 37 | br_table_indices: ArrayList(u32), 38 | references: ArrayList(u32), 39 | 40 | pub fn init(alloc: mem.Allocator, wasm_bin: []const u8) Module { 41 | return Module{ 42 | .alloc = alloc, 43 | .wasm_bin = wasm_bin, 44 | .customs = Section(Custom).init(alloc), 45 | .types = Section(FuncType).init(alloc), 46 | .imports = Section(Import).init(alloc), 47 | .functions = Section(Function).init(alloc), 48 | .tables = Section(TableType).init(alloc), 49 | .memories = Section(MemType).init(alloc), 50 | .globals = Section(GlobalType).init(alloc), 51 | .exports = Section(Export).init(alloc), 52 | .elements = Section(ElementSegment).init(alloc), 53 | .codes = Section(Code).init(alloc), 54 | .datas = Section(DataSegment).init(alloc), 55 | .element_init_offsets = ArrayList(usize).init(alloc), 56 | .parsed_code = ArrayList(Rr).init(alloc), 57 | .local_types = ArrayList(LocalType).init(alloc), 58 | .br_table_indices = ArrayList(u32).init(alloc), 59 | .references = ArrayList(u32).init(alloc), 60 | }; 61 | } 62 | 63 | pub fn deinit(self: *Module) void { 64 | self.customs.deinit(); 65 | self.types.deinit(); 66 | self.imports.deinit(); 67 | self.functions.deinit(); 68 | self.tables.deinit(); 69 | self.memories.deinit(); 70 | self.globals.deinit(); 71 | self.exports.deinit(); 72 | self.elements.deinit(); 73 | self.codes.deinit(); 74 | self.datas.deinit(); 75 | 76 | self.element_init_offsets.deinit(); 77 | self.parsed_code.deinit(); 78 | self.local_types.deinit(); 79 | self.br_table_indices.deinit(); 80 | self.references.deinit(); 81 | } 82 | 83 | pub fn decode(self: *Module) !void { 84 | if (self.decoded) return error.AlreadyDecoded; 85 | var decoder = Decoder{ 86 | .fbs = .{ .pos = 0, .buffer = self.wasm_bin }, 87 | }; 88 | const rd = decoder.fbs.reader(); 89 | 90 | const magic = try rd.readBytesNoEof(4); 91 | if (!mem.eql(u8, magic[0..], "\x00asm")) return error.MagicNumberNotFound; 92 | 93 | const version = try rd.readInt(u32, .little); 94 | if (version != 1) return error.UnknownBinaryVersion; 95 | 96 | // FIXME: in hindsight I don't like this: 97 | // Push an initial return instruction so we don't have to 98 | // track the end of a function to use its return on invoke 99 | // See https://github.com/malcolmstill/zware/pull/133 100 | try self.parsed_code.append(.@"return"); 101 | 102 | var i: usize = 0; 103 | while (true) : (i += 1) { 104 | decoder.decodeSection(self) catch |err| switch (err) { 105 | error.WasmFileEnd => break, 106 | else => return err, 107 | }; 108 | try self.verify(); 109 | } 110 | 111 | try self.verify(); 112 | if (self.codes.list.items.len != nonImportCount(self.functions.list.items)) return error.FunctionCodeSectionsInconsistent; 113 | 114 | self.decoded = true; 115 | } 116 | 117 | pub fn verify(self: *Module) !void { 118 | if (self.types.count != self.types.list.items.len) return error.TypeCountMismatch; 119 | if (self.imports.count != self.imports.list.items.len) return error.ImportsCountMismatch; 120 | if (self.functions.count != nonImportCount(self.functions.list.items)) return error.FunctionsCountMismatch; 121 | if (self.tables.count != nonImportCount(self.tables.list.items)) return error.TablesCountMismatch; 122 | if (self.memories.count != nonImportCount(self.memories.list.items)) return error.MemoriesCountMismatch; 123 | if (self.globals.count != nonImportCount(self.globals.list.items)) return error.GlobalsCountMismatch; 124 | if (self.exports.count != self.exports.list.items.len) return error.ExportsCountMismatch; 125 | if (self.elements.count != self.elements.list.items.len) return error.ElementsCountMismatch; 126 | if (self.codes.count != self.codes.list.items.len) return error.CodesCountMismatch; 127 | if (self.datas.count != self.datas.list.items.len) return error.DatasCountMismatch; 128 | } 129 | 130 | // Some types can be imported. For validation we want to check the non-imported 131 | // counts of each type match the what is stated in the binary. This function 132 | // counts the non-imports. 133 | fn nonImportCount(imported_type: anytype) usize { 134 | var count: usize = 0; 135 | for (imported_type) |import| { 136 | if (import.import == null) count += 1; 137 | } 138 | return count; 139 | } 140 | 141 | pub fn getExport(self: *const Module, tag: Tag, name: []const u8) !usize { 142 | for (self.exports.list.items) |exported| { 143 | if (tag == exported.tag and mem.eql(u8, name, exported.name)) return exported.index; 144 | } 145 | 146 | return error.ExportNotFound; 147 | } 148 | }; 149 | 150 | pub const Decoder = struct { 151 | fbs: std.io.FixedBufferStream([]const u8), 152 | 153 | pub fn decodeSection(self: *Decoder, module: *Module) !void { 154 | const id: SectionType = self.readEnum(SectionType) catch |err| switch (err) { 155 | error.EndOfStream => return error.WasmFileEnd, 156 | else => return err, 157 | }; 158 | 159 | const size = try self.readLEB128(u32); 160 | 161 | const section_start = self.fbs.pos; 162 | 163 | switch (id) { 164 | .Custom => try self.decodeCustomSection(module, size), 165 | .Type => try self.decodeTypeSection(module), 166 | .Import => try self.decodeImportSection(module), 167 | .Function => try self.decodeFunctionSection(module), 168 | .Table => try self.decodeTableSection(module), 169 | .Memory => try self.decodeMemorySection(module), 170 | .Global => try self.decodeGlobalSection(module), 171 | .Export => try self.decodeExportSection(module), 172 | .Start => try self.decodeStartSection(module), 173 | .Element => try self.decodeElementSection(module), 174 | .Code => try self.decodeCodeSection(module), 175 | .Data => try self.decodeDataSection(module), 176 | .DataCount => try self.decodeDataCountSection(module, size), 177 | } 178 | 179 | const section_end = self.fbs.pos; 180 | if (section_end - section_start != size) return error.MalformedSectionMismatchedSize; 181 | } 182 | 183 | fn decodeTypeSection(self: *Decoder, module: *Module) !void { 184 | const count = try self.readLEB128(u32); 185 | module.types.count = count; 186 | 187 | var f: usize = 0; 188 | while (f < count) : (f += 1) { 189 | const tag = try self.readByte(); 190 | if (tag != 0x60) return error.ExpectedFuncTypeTag; 191 | 192 | const param_count = try self.readLEB128(u32); 193 | const params_start = self.fbs.pos; 194 | { 195 | var i: usize = 0; 196 | while (i < param_count) : (i += 1) { 197 | _ = try self.readEnum(ValType); 198 | } 199 | } 200 | const params_end = self.fbs.pos; 201 | 202 | const results_count = try self.readLEB128(u32); 203 | const results_start = self.fbs.pos; 204 | { 205 | var i: usize = 0; 206 | while (i < results_count) : (i += 1) { 207 | _ = try self.readEnum(ValType); 208 | } 209 | } 210 | const results_end = self.fbs.pos; 211 | 212 | const params = module.wasm_bin[params_start..params_end]; 213 | const results = module.wasm_bin[results_start..results_end]; 214 | 215 | var params_valtype: []const ValType = undefined; 216 | params_valtype.ptr = @ptrCast(params.ptr); 217 | params_valtype.len = params.len; 218 | 219 | var results_valtype: []const ValType = undefined; 220 | results_valtype.ptr = @ptrCast(results.ptr); 221 | results_valtype.len = results.len; 222 | 223 | try module.types.list.append(FuncType{ 224 | .params = params_valtype, 225 | .results = results_valtype, 226 | }); 227 | } 228 | } 229 | 230 | fn decodeImportSection(self: *Decoder, module: *Module) !void { 231 | const count = try self.readLEB128(u32); 232 | module.imports.count = count; 233 | 234 | var i: usize = 0; 235 | while (i < count) : (i += 1) { 236 | const module_name_length = try self.readLEB128(u32); 237 | const module_name = try self.readSlice(module_name_length); 238 | 239 | if (!unicode.utf8ValidateSlice(module_name)) return error.NameNotUTF8; 240 | 241 | const name_length = try self.readLEB128(u32); 242 | const name = try self.readSlice(name_length); 243 | 244 | if (!unicode.utf8ValidateSlice(name)) return error.NameNotUTF8; 245 | 246 | const tag = try self.readEnum(Tag); 247 | 248 | if (i > math.maxInt(u32)) return error.ExpectedU32Index; 249 | const import_index: u32 = @truncate(i); 250 | _ = switch (tag) { 251 | .Func => try self.decodeFunction(module, import_index), 252 | .Table => try self.decodeTable(module, import_index), 253 | .Mem => try self.decodeMemory(module, import_index), 254 | .Global => try self.decodeGlobal(module, import_index), 255 | }; 256 | 257 | try module.imports.list.append(Import{ 258 | .module = module_name, 259 | .name = name, 260 | .desc_tag = tag, 261 | }); 262 | } 263 | } 264 | 265 | fn decodeFunctionSection(self: *Decoder, module: *Module) !void { 266 | const count = try self.readLEB128(u32); 267 | module.functions.count = count; 268 | 269 | var i: usize = 0; 270 | while (i < count) : (i += 1) { 271 | try self.decodeFunction(module, null); 272 | } 273 | } 274 | 275 | fn decodeFunction(self: *Decoder, module: *Module, import: ?u32) !void { 276 | const typeidx = try self.readLEB128(u32); 277 | 278 | if (typeidx >= module.types.list.items.len) return error.ValidatorInvalidTypeIndex; 279 | 280 | if (import == null and module.function_index_start == null) { 281 | module.function_index_start = module.functions.list.items.len; 282 | } 283 | 284 | try module.functions.list.append(Function{ 285 | .typeidx = typeidx, 286 | .import = import, 287 | }); 288 | } 289 | 290 | fn decodeTableSection(self: *Decoder, module: *Module) !void { 291 | const count = try self.readLEB128(u32); 292 | module.tables.count = count; 293 | 294 | var i: usize = 0; 295 | while (i < count) : (i += 1) { 296 | try self.decodeTable(module, null); 297 | } 298 | } 299 | 300 | fn decodeTable(self: *Decoder, module: *Module, import: ?u32) !void { 301 | const reftype = try self.readEnum(RefType); 302 | const limit_type = try self.readEnum(LimitType); 303 | const min = try self.readLEB128(u32); 304 | const max: ?u32 = blk: { 305 | switch (limit_type) { 306 | .Min => break :blk null, 307 | .MinMax => { 308 | const max = try self.readLEB128(u32); 309 | if (min > max) return error.ValidatorTableMinGreaterThanMax; 310 | break :blk max; 311 | }, 312 | } 313 | }; 314 | try module.tables.list.append(TableType{ 315 | .import = import, 316 | .reftype = reftype, 317 | .limits = Limit{ 318 | .min = min, 319 | .max = max, 320 | }, 321 | }); 322 | } 323 | 324 | fn decodeMemorySection(self: *Decoder, module: *Module) !void { 325 | const count = try self.readLEB128(u32); 326 | module.memories.count = count; 327 | 328 | var i: usize = 0; 329 | while (i < count) : (i += 1) { 330 | try self.decodeMemory(module, null); 331 | } 332 | } 333 | 334 | fn decodeMemory(self: *Decoder, module: *Module, import: ?u32) !void { 335 | if (module.memories.list.items.len > 0) return error.ValidatorMultipleMemories; 336 | 337 | const limit_type = try self.readEnum(LimitType); 338 | const min = try self.readLEB128(u32); 339 | if (min > 65536) return error.ValidatorMemoryMinTooLarge; 340 | const max: ?u32 = blk: { 341 | switch (limit_type) { 342 | .Min => break :blk null, 343 | .MinMax => { 344 | const max = try self.readLEB128(u32); 345 | if (min > max) return error.ValidatorMemoryMinGreaterThanMax; 346 | if (max > 65536) return error.ValidatorMemoryMaxTooLarge; 347 | break :blk max; 348 | }, 349 | } 350 | }; 351 | try module.memories.list.append(MemType{ 352 | .import = import, 353 | .limits = Limit{ 354 | .min = min, 355 | .max = max, 356 | }, 357 | }); 358 | } 359 | 360 | fn decodeGlobalSection(self: *Decoder, module: *Module) !void { 361 | const count = try self.readLEB128(u32); 362 | module.globals.count = count; 363 | 364 | var i: usize = 0; 365 | while (i < count) : (i += 1) { 366 | try self.decodeGlobal(module, null); 367 | } 368 | } 369 | 370 | fn decodeGlobal(self: *Decoder, module: *Module, import: ?u32) !void { 371 | const global_type = try self.readEnum(ValType); 372 | const mutability = try self.readEnum(Mutability); 373 | 374 | var parsed_code: ?Parsed = null; 375 | 376 | // If we're not importing the global we will expect 377 | // an expression 378 | if (import == null) { 379 | parsed_code = try self.readConstantExpression(module, global_type); 380 | } 381 | 382 | if (parsed_code == null and import == null) return error.ExpectedOneOrTheOther; 383 | if (parsed_code != null and import != null) return error.ExpectedOneOrTheOther; 384 | 385 | try module.globals.list.append(GlobalType{ 386 | .valtype = global_type, 387 | .mutability = mutability, 388 | .start = if (parsed_code) |pc| pc.start else null, 389 | .import = import, 390 | }); 391 | } 392 | 393 | fn decodeExportSection(self: *Decoder, module: *Module) !void { 394 | const count = try self.readLEB128(u32); 395 | module.exports.count = count; 396 | 397 | var i: usize = 0; 398 | while (i < count) : (i += 1) { 399 | const name_length = try self.readLEB128(u32); 400 | const name = try self.readSlice(name_length); 401 | 402 | if (!unicode.utf8ValidateSlice(name)) return error.NameNotUTF8; 403 | 404 | for (module.exports.list.items) |exprt| { 405 | if (mem.eql(u8, name, exprt.name)) return error.ValidatorDuplicateExportName; 406 | } 407 | 408 | const tag = try self.readEnum(Tag); 409 | const index = try self.readLEB128(u32); 410 | 411 | switch (tag) { 412 | .Func => { 413 | if (index >= module.functions.list.items.len) return error.ValidatorExportUnknownFunction; 414 | try module.references.append(index); 415 | }, 416 | .Table => if (index >= module.tables.list.items.len) return error.ValidatorExportUnknownTable, 417 | .Mem => if (index >= module.memories.list.items.len) return error.ValidatorExportUnknownMemory, 418 | .Global => if (index >= module.globals.list.items.len) return error.ValidatorExportUnknownGlobal, 419 | } 420 | 421 | try module.exports.list.append(Export{ 422 | .name = name, 423 | .tag = tag, 424 | .index = index, 425 | }); 426 | } 427 | } 428 | 429 | fn decodeStartSection(self: *Decoder, module: *Module) !void { 430 | if (module.start != null) return error.MultipleStartSections; 431 | 432 | const funcidx = try self.readLEB128(u32); 433 | const func = try module.functions.lookup(funcidx); 434 | const functype = try module.types.lookup(func.typeidx); 435 | 436 | if (functype.params.len != 0 or functype.results.len != 0) return error.ValidatorNotStartFunctionType; 437 | 438 | module.start = funcidx; 439 | } 440 | 441 | fn decodeElementSection(self: *Decoder, module: *Module) !void { 442 | const count = try self.readLEB128(u32); 443 | module.elements.count = count; 444 | 445 | var i: usize = 0; 446 | while (i < count) : (i += 1) { 447 | const elem_type = try self.readLEB128(u32); 448 | 449 | switch (elem_type) { 450 | 0 => { 451 | const tableidx = 0; 452 | if (tableidx >= module.tables.list.items.len) return error.ValidatorElemUnknownTable; 453 | 454 | const parsed_offset_code = try self.readConstantExpression(module, .I32); 455 | 456 | const data_length = try self.readLEB128(u32); 457 | 458 | const first_init_offset = module.element_init_offsets.items.len; 459 | 460 | var j: usize = 0; 461 | while (j < data_length) : (j += 1) { 462 | const funcidx = try self.readLEB128(u32); 463 | 464 | if (funcidx >= module.functions.list.items.len) return error.ValidatorElemUnknownFunctionIndex; 465 | 466 | try module.references.append(funcidx); 467 | 468 | const init_offset = module.parsed_code.items.len; 469 | try module.parsed_code.append(Rr{ .@"ref.func" = funcidx }); 470 | try module.parsed_code.append(Rr.@"return"); 471 | try module.element_init_offsets.append(init_offset); 472 | } 473 | 474 | try module.elements.list.append(ElementSegment{ 475 | .reftype = .FuncRef, 476 | .init = first_init_offset, 477 | .count = data_length, 478 | .mode = ElementSegmentMode{ .Active = .{ 479 | .tableidx = 0, 480 | .offset = parsed_offset_code.start, 481 | } }, 482 | }); 483 | }, 484 | 1 => { 485 | _ = try self.readEnum(ElemKind); 486 | 487 | const data_length = try self.readLEB128(u32); 488 | 489 | const first_init_offset = module.element_init_offsets.items.len; 490 | 491 | var j: usize = 0; 492 | while (j < data_length) : (j += 1) { 493 | const funcidx = try self.readLEB128(u32); 494 | 495 | if (funcidx >= module.functions.list.items.len) return error.ValidatorElemUnknownFunctionIndex; 496 | 497 | try module.references.append(funcidx); 498 | 499 | const init_offset = module.parsed_code.items.len; 500 | try module.parsed_code.append(Rr{ .@"ref.func" = funcidx }); 501 | try module.parsed_code.append(Rr.@"return"); 502 | try module.element_init_offsets.append(init_offset); 503 | } 504 | 505 | try module.elements.list.append(ElementSegment{ 506 | .reftype = .FuncRef, 507 | .init = first_init_offset, 508 | .count = data_length, 509 | .mode = ElementSegmentMode.Passive, 510 | }); 511 | }, 512 | 2 => { 513 | const tableidx = try self.readLEB128(u32); 514 | 515 | if (tableidx >= module.tables.list.items.len) return error.ValidatorElemUnknownTable; 516 | 517 | const parsed_offset_code = try self.readConstantExpression(module, .I32); 518 | 519 | _ = try self.readEnum(ElemKind); 520 | const data_length = try self.readLEB128(u32); 521 | 522 | const first_init_offset = module.element_init_offsets.items.len; 523 | 524 | var j: usize = 0; 525 | while (j < data_length) : (j += 1) { 526 | const funcidx = try self.readLEB128(u32); 527 | 528 | if (funcidx >= module.functions.list.items.len) return error.ValidatorElemUnknownFunctionIndex; 529 | 530 | try module.references.append(funcidx); 531 | 532 | const init_offset = module.parsed_code.items.len; 533 | try module.parsed_code.append(Rr{ .@"ref.func" = funcidx }); 534 | try module.parsed_code.append(Rr.@"return"); 535 | try module.element_init_offsets.append(init_offset); 536 | } 537 | 538 | try module.elements.list.append(ElementSegment{ 539 | .reftype = .FuncRef, 540 | .init = first_init_offset, 541 | .count = data_length, 542 | .mode = ElementSegmentMode{ .Active = .{ 543 | .tableidx = tableidx, 544 | .offset = parsed_offset_code.start, 545 | } }, 546 | }); 547 | }, 548 | 3 => { 549 | _ = try self.readEnum(ElemKind); 550 | const data_length = try self.readLEB128(u32); 551 | 552 | const first_init_offset = module.element_init_offsets.items.len; 553 | 554 | var j: usize = 0; 555 | while (j < data_length) : (j += 1) { 556 | const funcidx = try self.readLEB128(u32); 557 | 558 | if (funcidx >= module.functions.list.items.len) return error.ValidatorElemUnknownFunctionIndex; 559 | 560 | try module.references.append(funcidx); 561 | 562 | const init_offset = module.parsed_code.items.len; 563 | try module.parsed_code.append(Rr{ .@"ref.func" = funcidx }); 564 | try module.parsed_code.append(Rr.@"return"); 565 | try module.element_init_offsets.append(init_offset); 566 | } 567 | 568 | try module.elements.list.append(ElementSegment{ 569 | .reftype = .FuncRef, 570 | .init = first_init_offset, 571 | .count = data_length, 572 | .mode = ElementSegmentMode.Declarative, 573 | }); 574 | }, 575 | 4 => { 576 | const tableidx = 0; 577 | if (tableidx >= module.tables.list.items.len) return error.ValidatorElemUnknownTable; 578 | 579 | const parsed_offset_code = try self.readConstantExpression(module, .I32); 580 | 581 | const init_expression_count = try self.readLEB128(u32); 582 | 583 | const first_init_offset = module.element_init_offsets.items.len; 584 | 585 | var j: usize = 0; 586 | while (j < init_expression_count) : (j += 1) { 587 | const parsed_init_code = try self.readConstantExpression(module, .FuncRef); 588 | try module.element_init_offsets.append(parsed_init_code.start); 589 | } 590 | 591 | try module.elements.list.append(ElementSegment{ 592 | .reftype = .FuncRef, 593 | .init = first_init_offset, 594 | .count = init_expression_count, 595 | .mode = ElementSegmentMode{ .Active = .{ 596 | .tableidx = 0, 597 | .offset = parsed_offset_code.start, 598 | } }, 599 | }); 600 | }, 601 | 5 => { // Passive 602 | const reftype = try self.readEnum(RefType); 603 | const expr_count = try self.readLEB128(u32); 604 | 605 | const first_init_offset = module.element_init_offsets.items.len; 606 | 607 | var j: usize = 0; 608 | while (j < expr_count) : (j += 1) { 609 | const init_offset = module.parsed_code.items.len; 610 | _ = try self.readConstantExpression(module, .FuncRef); 611 | try module.element_init_offsets.append(init_offset); 612 | } 613 | 614 | try module.elements.list.append(ElementSegment{ 615 | .reftype = reftype, 616 | .init = first_init_offset, 617 | .count = expr_count, 618 | .mode = ElementSegmentMode.Passive, 619 | }); 620 | }, 621 | 7 => { // Declarative 622 | const reftype = try self.readEnum(RefType); 623 | const expr_count = try self.readLEB128(u32); 624 | 625 | const first_init_offset = module.element_init_offsets.items.len; 626 | 627 | var j: usize = 0; 628 | while (j < expr_count) : (j += 1) { 629 | const parsed_init_code = try self.readConstantExpression(module, .FuncRef); 630 | try module.element_init_offsets.append(parsed_init_code.start); 631 | } 632 | 633 | try module.elements.list.append(ElementSegment{ 634 | .reftype = reftype, 635 | .init = first_init_offset, 636 | .count = expr_count, 637 | .mode = ElementSegmentMode.Declarative, 638 | }); 639 | }, 640 | else => { 641 | return error.ValidatorElemTypeNotImplemented; 642 | }, 643 | } 644 | } 645 | } 646 | 647 | fn decodeDataCountSection(self: *Decoder, module: *Module, size: u32) !void { 648 | if (size == 0) return; 649 | module.data_count = try self.readLEB128(u32); 650 | } 651 | 652 | fn decodeCodeSection(self: *Decoder, module: *Module) !void { 653 | const count = try self.readLEB128(u32); 654 | module.codes.count = count; 655 | 656 | try module.parsed_code.ensureTotalCapacity(count * 32); 657 | 658 | if (count == 0) return; 659 | 660 | const function_index_start = module.function_index_start orelse return error.FunctionCodeSectionsInconsistent; 661 | 662 | var i: usize = 0; 663 | while (i < count) : (i += 1) { 664 | // size: the number of bytes defining the function, includes bytes defining locals 665 | _ = try self.readLEB128(u32); 666 | 667 | const locals_definitions_count = try self.readLEB128(u32); 668 | 669 | const locals_start = module.local_types.items.len; 670 | 671 | // Iterate over local definitions counting them 672 | var j: usize = 0; 673 | var locals_count: usize = 0; 674 | while (j < locals_definitions_count) : (j += 1) { 675 | const type_count = try self.readLEB128(u32); 676 | const local_type = try self.readEnum(ValType); 677 | locals_count += type_count; 678 | 679 | try module.local_types.append(.{ .count = type_count, .valtype = local_type }); 680 | } 681 | if (locals_count >= 0x100000000) return error.TooManyLocals; 682 | 683 | const locals = module.local_types.items[locals_start .. locals_start + locals_definitions_count]; 684 | 685 | if (function_index_start + i >= module.functions.list.items.len) return error.FunctionCodeSectionsInconsistent; 686 | const parsed_code = try self.readFunction(module, locals, function_index_start + i); 687 | 688 | try module.codes.list.append(Code{ 689 | .locals_count = locals_count, 690 | .start = parsed_code.start, 691 | .required_stack_space = parsed_code.max_depth, 692 | }); 693 | } 694 | } 695 | 696 | fn decodeDataSection(self: *Decoder, module: *Module) !void { 697 | const count = try self.readLEB128(u32); 698 | 699 | if (module.data_count) |data_count| { 700 | if (count != data_count) return error.DataCountSectionDataSectionCountMismatch; 701 | } 702 | 703 | module.datas.count = count; 704 | 705 | var i: usize = 0; 706 | while (i < count) : (i += 1) { 707 | const data_section_type = try self.readLEB128(u32); 708 | 709 | switch (data_section_type) { 710 | 0 => { 711 | const memidx = 0; 712 | 713 | if (memidx >= module.memories.list.items.len) return error.ValidatorDataMemoryReferenceInvalid; 714 | 715 | const parsed_code = try self.readConstantExpression(module, .I32); 716 | 717 | const data_length = try self.readLEB128(u32); 718 | const data = try self.readSlice(data_length); 719 | 720 | try module.datas.list.append(DataSegment{ 721 | .count = data_length, 722 | .data = data, 723 | .mode = DataSegmentMode{ .Active = .{ 724 | .memidx = 0, 725 | .offset = parsed_code.start, 726 | } }, 727 | }); 728 | }, 729 | 1 => { 730 | const data_length = try self.readLEB128(u32); 731 | const data = try self.readSlice(data_length); 732 | 733 | try module.datas.list.append(DataSegment{ 734 | .count = data_length, 735 | .data = data, 736 | .mode = .Passive, 737 | }); 738 | }, 739 | 2 => { 740 | const memidx = try self.readLEB128(u32); 741 | 742 | if (memidx >= module.memories.list.items.len) return error.ValidatorDataMemoryReferenceInvalid; 743 | 744 | const parsed_code = try self.readConstantExpression(module, .I32); 745 | 746 | const data_length = try self.readLEB128(u32); 747 | const data = try self.readSlice(data_length); 748 | 749 | try module.datas.list.append(DataSegment{ 750 | .count = data_length, 751 | .data = data, 752 | .mode = DataSegmentMode{ .Active = .{ 753 | .memidx = 0, 754 | .offset = parsed_code.start, 755 | } }, 756 | }); 757 | }, 758 | else => { 759 | return error.UnknownDataSectionType; 760 | }, 761 | } 762 | } 763 | } 764 | 765 | fn decodeCustomSection(self: *Decoder, module: *Module, size: u32) !void { 766 | const offset = self.fbs.pos; 767 | 768 | const name_length = try self.readLEB128(u32); 769 | const name = try self.readSlice(name_length); 770 | 771 | if (!unicode.utf8ValidateSlice(name)) return error.NameNotUTF8; 772 | 773 | const data_length = try math.sub(usize, size, (self.fbs.pos - offset)); 774 | const data = try self.readSlice(data_length); 775 | 776 | try module.customs.list.append(Custom{ 777 | .name = name, 778 | .data = data, 779 | }); 780 | } 781 | 782 | pub fn readConstantExpression(self: *Decoder, module: *Module, valtype: ValType) !Parsed { 783 | const rd = self.fbs.reader(); 784 | const code = module.wasm_bin[rd.context.pos..]; 785 | 786 | var parser = Parser.init(module, self); 787 | defer parser.deinit(); 788 | 789 | return parser.parseConstantExpression(valtype, code); 790 | } 791 | 792 | pub fn readFunction(self: *Decoder, module: *Module, locals: []LocalType, funcidx: usize) !Parsed { 793 | const code = module.wasm_bin[self.fbs.pos..]; 794 | 795 | var parser = Parser.init(module, self); 796 | defer parser.deinit(); 797 | 798 | return parser.parseFunction(funcidx, locals, code); 799 | } 800 | 801 | fn readByte(self: *Decoder) !u8 { 802 | return self.fbs.reader().readByte(); 803 | } 804 | 805 | fn readEnum(self: *Decoder, comptime T: type) !T { 806 | return self.fbs.reader().readEnum(T, .little); 807 | } 808 | 809 | fn readLEB128(self: *Decoder, comptime T: type) !T { 810 | const readFn = switch (@typeInfo(T).int.signedness) { 811 | .signed => std.leb.readILEB128, 812 | .unsigned => std.leb.readULEB128, 813 | }; 814 | return readFn(T, self.fbs.reader()); 815 | } 816 | 817 | pub fn readSlice(self: *Decoder, count: usize) ![]const u8 { 818 | const start = self.fbs.pos; 819 | const end = self.fbs.pos + count; 820 | if (end > self.fbs.buffer.len) 821 | return error.EndOfStream; 822 | self.fbs.pos = end; 823 | return self.fbs.buffer[start..self.fbs.pos]; 824 | } 825 | }; 826 | 827 | fn Section(comptime T: type) type { 828 | return struct { 829 | count: usize = 0, 830 | list: ArrayList(T), 831 | 832 | const Self = @This(); 833 | 834 | pub fn init(alloc: mem.Allocator) Self { 835 | return Self{ 836 | .list = ArrayList(T).init(alloc), 837 | }; 838 | } 839 | 840 | pub fn deinit(self: *Self) void { 841 | self.list.deinit(); 842 | } 843 | 844 | pub fn itemsSlice(self: *Self) []T { 845 | return self.list.items; 846 | } 847 | 848 | pub fn lookup(self: *const Self, idx: anytype) !T { 849 | const index = switch (@TypeOf(idx)) { 850 | u32 => idx, 851 | usize => math.cast(u32, idx) orelse return error.IndexTooLarge, 852 | else => @compileError("only u32 / usize supported"), 853 | }; 854 | 855 | if (index >= self.list.items.len) return error.ValidatorInvalidIndex; 856 | 857 | return self.list.items[index]; 858 | } 859 | }; 860 | } 861 | 862 | const Custom = struct { 863 | name: []const u8, 864 | data: []const u8, 865 | }; 866 | 867 | const SectionType = enum(u8) { 868 | Custom = 0x00, 869 | Type = 0x01, 870 | Import = 0x02, 871 | Function = 0x03, 872 | Table = 0x04, 873 | Memory = 0x05, 874 | Global = 0x06, 875 | Export = 0x07, 876 | Start = 0x08, 877 | Element = 0x09, 878 | Code = 0x0a, 879 | Data = 0x0b, 880 | DataCount = 0x0c, 881 | }; 882 | 883 | const ElemKind = enum(u8) { 884 | FuncRef = 0x0, 885 | }; 886 | 887 | const Code = struct { 888 | start: usize, 889 | locals_count: usize, 890 | required_stack_space: usize, 891 | }; 892 | 893 | pub const FuncType = struct { 894 | params: []const ValType, 895 | results: []const ValType, 896 | }; 897 | 898 | pub const GlobalType = struct { 899 | valtype: ValType, 900 | mutability: Mutability, 901 | start: ?usize, 902 | import: ?u32, 903 | }; 904 | 905 | const MemType = struct { 906 | import: ?u32, 907 | limits: Limit, 908 | }; 909 | 910 | const TableType = struct { 911 | import: ?u32, 912 | reftype: RefType, 913 | limits: Limit, 914 | }; 915 | 916 | const DataSegment = struct { 917 | count: u32, 918 | data: []const u8, 919 | mode: DataSegmentMode, 920 | }; 921 | 922 | const DataSegmentType = enum { 923 | Passive, 924 | Active, 925 | }; 926 | 927 | const DataSegmentMode = union(DataSegmentType) { 928 | Passive: void, 929 | Active: struct { 930 | memidx: u32, 931 | offset: usize, // index of parsed code representing offset 932 | }, 933 | }; 934 | 935 | pub const ElementSegment = struct { 936 | reftype: RefType, 937 | init: usize, // Offset into element_init_offset of first init expression code offset 938 | count: u32, // Number of element_init_offset values for this segment (we have an array of initialisation functions) 939 | mode: ElementSegmentMode, 940 | }; 941 | 942 | pub const ElementSegmentType = enum { 943 | Passive, 944 | Active, 945 | Declarative, 946 | }; 947 | 948 | pub const ElementSegmentMode = union(ElementSegmentType) { 949 | Passive: void, 950 | Active: struct { 951 | tableidx: u32, 952 | offset: usize, // index of parsed code representing offset 953 | }, 954 | Declarative: void, 955 | }; 956 | 957 | const LimitType = enum(u8) { 958 | Min, 959 | MinMax, 960 | }; 961 | 962 | pub const Limit = struct { 963 | min: u32, 964 | max: ?u32, 965 | 966 | pub fn checkMatch(self: Limit, min_imported: u32, max_imported: ?u32) !void { 967 | if (min_imported < self.min) return error.LimitMismatch; 968 | if (self.max) |defined_max| { 969 | if (max_imported) |imported_max| { 970 | if (!(imported_max <= defined_max)) { 971 | return error.LimitMismatch; 972 | } 973 | } else { 974 | return error.LimitMismatch; 975 | } 976 | } 977 | } 978 | }; 979 | 980 | pub const Mutability = enum(u8) { 981 | Immutable, 982 | Mutable, 983 | }; 984 | 985 | pub const Function = struct { 986 | typeidx: u32, 987 | import: ?u32, 988 | }; 989 | 990 | pub const Import = struct { 991 | module: []const u8, 992 | name: []const u8, 993 | desc_tag: Tag, 994 | // desc: u8, 995 | }; 996 | 997 | pub const Export = struct { 998 | name: []const u8, 999 | tag: Tag, 1000 | index: u32, 1001 | }; 1002 | 1003 | pub const Tag = enum(u8) { 1004 | Func, 1005 | Table, 1006 | Mem, 1007 | Global, 1008 | }; 1009 | 1010 | pub const LocalType = struct { 1011 | count: u32, 1012 | valtype: ValType, 1013 | }; 1014 | 1015 | const testing = std.testing; 1016 | 1017 | test "module loading (simple add function)" { 1018 | const Store = @import("store.zig").ArrayListStore; 1019 | const ArenaAllocator = std.heap.ArenaAllocator; 1020 | var arena = ArenaAllocator.init(testing.allocator); 1021 | defer _ = arena.deinit(); 1022 | 1023 | const alloc = arena.allocator(); 1024 | 1025 | const bytes = @embedFile("test/test.wasm"); 1026 | 1027 | var store: Store = Store.init(alloc); 1028 | 1029 | var module = Module.init(alloc, bytes); 1030 | try module.decode(); 1031 | 1032 | var instance = Instance.init(alloc, &store, module); 1033 | try instance.instantiate(); 1034 | 1035 | var in = [2]u64{ 22, 23 }; 1036 | var out = [1]u64{0}; 1037 | try instance.invoke("add", in[0..], out[0..], .{}); 1038 | try testing.expectEqual(@as(i32, 45), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1039 | } 1040 | 1041 | test "module loading (fib)" { 1042 | const Store = @import("store.zig").ArrayListStore; 1043 | const ArenaAllocator = std.heap.ArenaAllocator; 1044 | var arena = ArenaAllocator.init(testing.allocator); 1045 | defer _ = arena.deinit(); 1046 | 1047 | const alloc = arena.allocator(); 1048 | 1049 | const bytes = @embedFile("test/fib.wasm"); 1050 | 1051 | var store: Store = Store.init(alloc); 1052 | 1053 | var module = Module.init(alloc, bytes); 1054 | try module.decode(); 1055 | 1056 | var instance = Instance.init(alloc, &store, module); 1057 | try instance.instantiate(); 1058 | 1059 | var in = [1]u64{0}; 1060 | var out = [1]u64{0}; 1061 | try instance.invoke("fib", in[0..], out[0..], .{}); 1062 | try testing.expectEqual(@as(i32, 0), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1063 | 1064 | in[0] = 1; 1065 | try instance.invoke("fib", in[0..], out[0..], .{}); 1066 | try testing.expectEqual(@as(i32, 1), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1067 | 1068 | in[0] = 2; 1069 | try instance.invoke("fib", in[0..], out[0..], .{}); 1070 | try testing.expectEqual(@as(i32, 1), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1071 | 1072 | in[0] = 3; 1073 | try instance.invoke("fib", in[0..], out[0..], .{}); 1074 | try testing.expectEqual(@as(i32, 2), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1075 | 1076 | in[0] = 4; 1077 | try instance.invoke("fib", in[0..], out[0..], .{}); 1078 | try testing.expectEqual(@as(i32, 3), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1079 | 1080 | in[0] = 5; 1081 | try instance.invoke("fib", in[0..], out[0..], .{}); 1082 | try testing.expectEqual(@as(i32, 5), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1083 | 1084 | in[0] = 6; 1085 | try instance.invoke("fib", in[0..], out[0..], .{}); 1086 | try testing.expectEqual(@as(i32, 8), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1087 | } 1088 | 1089 | test "module loading (fact)" { 1090 | const Store = @import("store.zig").ArrayListStore; 1091 | const ArenaAllocator = std.heap.ArenaAllocator; 1092 | var arena = ArenaAllocator.init(testing.allocator); 1093 | defer _ = arena.deinit(); 1094 | 1095 | const alloc = arena.allocator(); 1096 | 1097 | const bytes = @embedFile("test/fact.wasm"); 1098 | 1099 | var store: Store = Store.init(alloc); 1100 | 1101 | var module = Module.init(alloc, bytes); 1102 | try module.decode(); 1103 | 1104 | var instance = Instance.init(alloc, &store, module); 1105 | try instance.instantiate(); 1106 | 1107 | var in = [1]u64{1}; 1108 | var out = [1]u64{0}; 1109 | try instance.invoke("fact", in[0..], out[0..], .{}); 1110 | try testing.expectEqual(@as(i32, 1), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1111 | 1112 | in[0] = 2; 1113 | try instance.invoke("fact", in[0..], out[0..], .{}); 1114 | try testing.expectEqual(@as(i32, 2), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1115 | 1116 | in[0] = 3; 1117 | try instance.invoke("fact", in[0..], out[0..], .{}); 1118 | try testing.expectEqual(@as(i32, 6), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1119 | 1120 | in[0] = 4; 1121 | try instance.invoke("fact", in[0..], out[0..], .{}); 1122 | try testing.expectEqual(@as(i32, 24), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1123 | 1124 | in[0] = 12; 1125 | try instance.invoke("fact", in[0..], out[0..], .{}); 1126 | try testing.expectEqual(@as(i32, 479001600), @as(i32, @bitCast(@as(u32, @truncate(out[0]))))); 1127 | } 1128 | -------------------------------------------------------------------------------- /src/module/validator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const LinearFifo = std.fifo.LinearFifo; 4 | const ArrayList = std.ArrayList; 5 | const FuncType = @import("../module.zig").FuncType; 6 | const ValType = @import("../valtype.zig").ValType; 7 | const RefType = @import("../valtype.zig").RefType; 8 | const GlobalType = @import("../module.zig").GlobalType; 9 | const Opcode = @import("../opcode.zig").Opcode; 10 | const MiscOpcode = @import("../opcode.zig").MiscOpcode; 11 | const Module = @import("../module.zig").Module; 12 | 13 | pub const Validator = struct { 14 | op_stack: OperandStack = undefined, 15 | ctrl_stack: ControlStack = undefined, 16 | max_depth: usize = 0, 17 | is_constant: bool, 18 | 19 | pub fn init(alloc: mem.Allocator, is_constant: bool) Validator { 20 | return Validator{ 21 | .op_stack = OperandStack.init(alloc), 22 | .ctrl_stack = ControlStack.init(alloc), 23 | .is_constant = is_constant, 24 | }; 25 | } 26 | 27 | pub fn deinit(self: *Validator) void { 28 | // Static allocation? 29 | self.op_stack.deinit(); 30 | self.ctrl_stack.deinit(); 31 | } 32 | 33 | pub fn validateBlock(v: *Validator, in_operands: []const ValType, out_operands: []const ValType) !void { 34 | try v.popOperands(in_operands); 35 | try v.pushControlFrame(.block, in_operands, out_operands); 36 | } 37 | 38 | pub fn validateLoop(v: *Validator, in_operands: []const ValType, out_operands: []const ValType) !void { 39 | try v.popOperands(in_operands); 40 | try v.pushControlFrame(.loop, in_operands, out_operands); 41 | } 42 | 43 | pub fn validateSelectT(v: *Validator, valuetype: ValType) !void { 44 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 45 | _ = try v.popOperandExpecting(Type{ .Known = valuetype }); 46 | _ = try v.popOperandExpecting(Type{ .Known = valuetype }); 47 | try v.pushOperand(Type{ .Known = valuetype }); 48 | } 49 | 50 | pub fn validateIf(v: *Validator, in_operands: []const ValType, out_operands: []const ValType) !void { 51 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 52 | try v.popOperands(in_operands); 53 | try v.pushControlFrame(.@"if", in_operands, out_operands); 54 | } 55 | 56 | pub fn validateBr(v: *Validator, label: usize) !void { 57 | if (label >= v.ctrl_stack.items.len) return error.ValidateBrInvalidLabel; 58 | const frame = v.ctrl_stack.items[v.ctrl_stack.items.len - 1 - label]; 59 | try v.popOperands(labelTypes(frame)); 60 | try v.setUnreachable(); 61 | } 62 | 63 | pub fn validateBrIf(v: *Validator, label: usize) !void { 64 | if (label >= v.ctrl_stack.items.len) return error.ValidateBrIfInvalidLabel; 65 | const frame = v.ctrl_stack.items[v.ctrl_stack.items.len - 1 - label]; 66 | 67 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 68 | try v.popOperands(labelTypes(frame)); 69 | try v.pushOperands(labelTypes(frame)); 70 | } 71 | 72 | pub fn validateBrTable(v: *Validator, n_star: []u32, label: u32) !void { 73 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 74 | if (label >= v.ctrl_stack.items.len) return error.ValidateBrTableInvalidLabel; 75 | const frame = v.ctrl_stack.items[v.ctrl_stack.items.len - 1 - label]; 76 | 77 | const arity = labelTypes(frame).len; 78 | 79 | for (n_star) |n| { 80 | if (n >= v.ctrl_stack.items.len) return error.ValidateBrTableInvalidLabelN; 81 | const frame_n = v.ctrl_stack.items[v.ctrl_stack.items.len - 1 - n]; 82 | if (labelTypes(frame_n).len != arity) return error.ValidateBrTableInvalidLabelWrongArity; 83 | 84 | if (!(arity <= 64)) return error.TODOAllocation; 85 | 86 | var temp = [_]Type{.{ .Known = .I32 }} ** 64; // TODO: allocate some memory for this 87 | for (labelTypes(frame_n), 0..) |_, i| { 88 | temp[i] = try v.popOperandExpecting(Type{ .Known = labelTypes(frame_n)[arity - i - 1] }); 89 | } 90 | 91 | for (labelTypes(frame_n), 0..) |_, i| { 92 | try v.pushOperand(temp[arity - 1 - i]); 93 | } 94 | } 95 | 96 | try v.popOperands(labelTypes(frame)); 97 | try v.setUnreachable(); 98 | } 99 | 100 | pub fn validateCall(v: *Validator, func_type: FuncType) !void { 101 | try v.popOperands(func_type.params); 102 | try v.pushOperands(func_type.results); 103 | } 104 | 105 | pub fn validateCallIndirect(v: *Validator, func_type: FuncType) !void { 106 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 107 | try v.popOperands(func_type.params); 108 | try v.pushOperands(func_type.results); 109 | } 110 | 111 | pub fn validateLocalGet(v: *Validator, local_type: ValType) !void { 112 | try v.pushOperand(Type{ .Known = local_type }); 113 | } 114 | 115 | pub fn validateLocalSet(v: *Validator, local_type: ValType) !void { 116 | _ = try v.popOperandExpecting(Type{ .Known = local_type }); 117 | } 118 | 119 | pub fn validateLocalTee(v: *Validator, local_type: ValType) !void { 120 | const t = try v.popOperandExpecting(Type{ .Known = local_type }); 121 | try v.pushOperand(t); 122 | } 123 | 124 | pub fn validateGlobalGet(v: *Validator, globaltype: GlobalType) !void { 125 | if (v.is_constant and globaltype.mutability == .Mutable) return error.ValidatorMutableGlobalInConstantExpr; 126 | try v.pushOperand(Type{ .Known = globaltype.valtype }); 127 | } 128 | 129 | pub fn validateGlobalSet(v: *Validator, globaltype: GlobalType) !void { 130 | if (globaltype.mutability == .Immutable) return error.ValidatorAttemptToMutateImmutableGlobal; 131 | _ = try v.popOperandExpecting(Type{ .Known = globaltype.valtype }); 132 | } 133 | 134 | pub fn validateRefNull(v: *Validator, reftype: RefType) !void { 135 | switch (reftype) { 136 | .FuncRef => _ = try v.pushOperand(Type{ .Known = .FuncRef }), 137 | .ExternRef => _ = try v.pushOperand(Type{ .Known = .ExternRef }), 138 | } 139 | } 140 | 141 | pub fn validateMisc(v: *Validator, misc_type: MiscOpcode) !void { 142 | switch (misc_type) { 143 | .@"i32.trunc_sat_f32_s", 144 | .@"i32.trunc_sat_f32_u", 145 | => { 146 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 147 | try v.pushOperand(Type{ .Known = .I32 }); 148 | }, 149 | .@"i32.trunc_sat_f64_s", 150 | .@"i32.trunc_sat_f64_u", 151 | => { 152 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 153 | try v.pushOperand(Type{ .Known = .I32 }); 154 | }, 155 | .@"i64.trunc_sat_f32_s", 156 | .@"i64.trunc_sat_f32_u", 157 | => { 158 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 159 | try v.pushOperand(Type{ .Known = .I64 }); 160 | }, 161 | .@"i64.trunc_sat_f64_s", 162 | .@"i64.trunc_sat_f64_u", 163 | => { 164 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 165 | try v.pushOperand(Type{ .Known = .I64 }); 166 | }, 167 | .@"memory.init" => { 168 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 169 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 170 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 171 | }, 172 | .@"data.drop" => { 173 | // 174 | }, 175 | .@"memory.copy" => { 176 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 177 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 178 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 179 | }, 180 | .@"memory.fill" => { 181 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 182 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 183 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 184 | }, 185 | .@"table.init" => { 186 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 187 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 188 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 189 | }, 190 | .@"elem.drop" => { 191 | // 192 | }, 193 | .@"table.copy" => { 194 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 195 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 196 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 197 | }, 198 | .@"table.grow" => {}, 199 | .@"table.size" => { 200 | try v.pushOperand(Type{ .Known = .I32 }); 201 | }, 202 | .@"table.fill" => {}, 203 | } 204 | } 205 | 206 | pub fn validate(v: *Validator, opcode: Opcode) !void { 207 | switch (opcode) { 208 | .block, 209 | .loop, 210 | .@"if", 211 | .br, 212 | .br_if, 213 | .br_table, 214 | .call, 215 | .call_indirect, 216 | .@"global.get", 217 | .@"global.set", 218 | .@"local.get", 219 | .@"local.set", 220 | .@"local.tee", 221 | .@"ref.null", 222 | .misc, 223 | .select_t, 224 | .@"table.get", 225 | .@"table.set", 226 | => {}, 227 | .@"unreachable" => try v.setUnreachable(), 228 | .nop => {}, 229 | .end => { 230 | const frame = try v.popControlFrame(); 231 | _ = try v.pushOperands(frame.end_types); 232 | }, 233 | .@"else" => { 234 | const frame = try v.popControlFrame(); 235 | if (frame.opcode != .@"if") return error.ElseMustOnlyOccurAfterIf; 236 | try v.pushControlFrame(.@"else", frame.start_types, frame.end_types); 237 | }, 238 | .@"return" => { 239 | const frame = v.ctrl_stack.items[0]; 240 | try v.popOperands(labelTypes(frame)); 241 | try v.setUnreachable(); 242 | }, 243 | .drop => { 244 | _ = try v.popOperand(); 245 | }, 246 | .select => { 247 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 248 | const t1 = try v.popOperand(); 249 | const t2 = try v.popOperand(); 250 | 251 | if (!(isNum(t1) and isNum(t2))) return error.ExpectingBothNum; 252 | if (!typeEqual(t1, t2) and !typeEqual(t1, Type.Unknown) and !typeEqual(t2, Type.Unknown)) return error.ValidatorSelect; 253 | if (typeEqual(t1, Type.Unknown)) { 254 | try v.pushOperand(t2); 255 | } else { 256 | try v.pushOperand(t1); 257 | } 258 | }, 259 | .@"memory.size" => { 260 | _ = try v.pushOperand(Type{ .Known = .I32 }); 261 | }, 262 | .@"i32.extend8_s", 263 | .@"i32.extend16_s", 264 | .@"i32.eqz", 265 | .@"memory.grow", 266 | .@"i32.load", 267 | .@"i32.load8_u", 268 | .@"i32.load8_s", 269 | .@"i32.load16_u", 270 | .@"i32.load16_s", 271 | => { 272 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 273 | _ = try v.pushOperand(Type{ .Known = .I32 }); 274 | }, 275 | .@"i32.add", 276 | .@"i32.sub", 277 | .@"i32.eq", 278 | .@"i32.ne", 279 | .@"i32.le_s", 280 | .@"i32.le_u", 281 | .@"i32.lt_s", 282 | .@"i32.lt_u", 283 | .@"i32.ge_s", 284 | .@"i32.ge_u", 285 | .@"i32.gt_s", 286 | .@"i32.gt_u", 287 | .@"i32.xor", 288 | .@"i32.and", 289 | .@"i32.div_s", 290 | .@"i32.div_u", 291 | .@"i32.mul", 292 | .@"i32.or", 293 | .@"i32.rem_s", 294 | .@"i32.rem_u", 295 | .@"i32.rotl", 296 | .@"i32.rotr", 297 | .@"i32.shl", 298 | .@"i32.shr_s", 299 | .@"i32.shr_u", 300 | => { 301 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 302 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 303 | _ = try v.pushOperand(Type{ .Known = .I32 }); 304 | }, 305 | .@"i32.store", 306 | .@"i32.store8", 307 | .@"i32.store16", 308 | => { 309 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 310 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 311 | }, 312 | .@"i32.clz", .@"i32.ctz", .@"i32.popcnt" => { 313 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 314 | _ = try v.pushOperand(Type{ .Known = .I32 }); 315 | }, 316 | .@"i32.wrap_i64" => { 317 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 318 | _ = try v.pushOperand(Type{ .Known = .I32 }); 319 | }, 320 | .@"i32.reinterpret_f32", 321 | .@"i32.trunc_f32_s", 322 | .@"i32.trunc_f32_u", 323 | => { 324 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 325 | _ = try v.pushOperand(Type{ .Known = .I32 }); 326 | }, 327 | .@"i32.trunc_f64_s", 328 | .@"i32.trunc_f64_u", 329 | => { 330 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 331 | _ = try v.pushOperand(Type{ .Known = .I32 }); 332 | }, 333 | .@"i64.extend8_s", 334 | .@"i64.extend16_s", 335 | .@"i64.extend32_s", 336 | => { 337 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 338 | _ = try v.pushOperand(Type{ .Known = .I64 }); 339 | }, 340 | .@"i64.add", 341 | .@"i64.sub", 342 | .@"i64.xor", 343 | .@"i64.and", 344 | .@"i64.div_s", 345 | .@"i64.div_u", 346 | .@"i64.mul", 347 | .@"i64.or", 348 | .@"i64.rem_s", 349 | .@"i64.rem_u", 350 | .@"i64.rotl", 351 | .@"i64.rotr", 352 | .@"i64.shl", 353 | .@"i64.shr_s", 354 | .@"i64.shr_u", 355 | => { 356 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 357 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 358 | _ = try v.pushOperand(Type{ .Known = .I64 }); 359 | }, 360 | .@"i64.store", 361 | .@"i64.store8", 362 | .@"i64.store16", 363 | .@"i64.store32", 364 | => { 365 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 366 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 367 | }, 368 | .@"i64.eq", 369 | .@"i64.ne", 370 | .@"i64.le_s", 371 | .@"i64.le_u", 372 | .@"i64.lt_s", 373 | .@"i64.lt_u", 374 | .@"i64.ge_s", 375 | .@"i64.ge_u", 376 | .@"i64.gt_s", 377 | .@"i64.gt_u", 378 | => { 379 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 380 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 381 | _ = try v.pushOperand(Type{ .Known = .I32 }); 382 | }, 383 | .@"i64.eqz" => { 384 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 385 | _ = try v.pushOperand(Type{ .Known = .I32 }); 386 | }, 387 | .@"i64.clz", .@"i64.ctz", .@"i64.popcnt" => { 388 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 389 | _ = try v.pushOperand(Type{ .Known = .I64 }); 390 | }, 391 | .@"i64.load", 392 | .@"i64.load8_s", 393 | .@"i64.load8_u", 394 | .@"i64.load16_s", 395 | .@"i64.load16_u", 396 | .@"i64.load32_s", 397 | .@"i64.load32_u", 398 | .@"i64.extend_i32_s", 399 | .@"i64.extend_i32_u", 400 | => { 401 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 402 | _ = try v.pushOperand(Type{ .Known = .I64 }); 403 | }, 404 | .@"i64.trunc_f32_s", 405 | .@"i64.trunc_f32_u", 406 | => { 407 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 408 | _ = try v.pushOperand(Type{ .Known = .I64 }); 409 | }, 410 | .@"i64.reinterpret_f64", 411 | .@"i64.trunc_f64_s", 412 | .@"i64.trunc_f64_u", 413 | => { 414 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 415 | _ = try v.pushOperand(Type{ .Known = .I64 }); 416 | }, 417 | .@"f32.add", 418 | .@"f32.sub", 419 | .@"f32.mul", 420 | .@"f32.div", 421 | .@"f32.min", 422 | .@"f32.max", 423 | .@"f32.copysign", 424 | => { 425 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 426 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 427 | _ = try v.pushOperand(Type{ .Known = .F32 }); 428 | }, 429 | .@"f32.abs", 430 | .@"f32.neg", 431 | .@"f32.sqrt", 432 | .@"f32.ceil", 433 | .@"f32.floor", 434 | .@"f32.trunc", 435 | .@"f32.nearest", 436 | => { 437 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 438 | _ = try v.pushOperand(Type{ .Known = .F32 }); 439 | }, 440 | .@"f32.eq", 441 | .@"f32.ne", 442 | .@"f32.lt", 443 | .@"f32.le", 444 | .@"f32.gt", 445 | .@"f32.ge", 446 | => { 447 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 448 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 449 | _ = try v.pushOperand(Type{ .Known = .I32 }); 450 | }, 451 | .@"f32.store" => { 452 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 453 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 454 | }, 455 | .@"f32.load", 456 | .@"f32.convert_i32_s", 457 | .@"f32.convert_i32_u", 458 | .@"f32.reinterpret_i32", 459 | => { 460 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 461 | _ = try v.pushOperand(Type{ .Known = .F32 }); 462 | }, 463 | .@"f32.demote_f64" => { 464 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 465 | _ = try v.pushOperand(Type{ .Known = .F32 }); 466 | }, 467 | .@"f32.convert_i64_s", 468 | .@"f32.convert_i64_u", 469 | => { 470 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 471 | _ = try v.pushOperand(Type{ .Known = .F32 }); 472 | }, 473 | .@"f64.add", 474 | .@"f64.sub", 475 | .@"f64.mul", 476 | .@"f64.div", 477 | .@"f64.min", 478 | .@"f64.max", 479 | .@"f64.copysign", 480 | => { 481 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 482 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 483 | _ = try v.pushOperand(Type{ .Known = .F64 }); 484 | }, 485 | .@"f64.abs", 486 | .@"f64.neg", 487 | .@"f64.sqrt", 488 | .@"f64.ceil", 489 | .@"f64.floor", 490 | .@"f64.trunc", 491 | .@"f64.nearest", 492 | => { 493 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 494 | _ = try v.pushOperand(Type{ .Known = .F64 }); 495 | }, 496 | .@"f64.eq", 497 | .@"f64.ne", 498 | .@"f64.lt", 499 | .@"f64.le", 500 | .@"f64.gt", 501 | .@"f64.ge", 502 | => { 503 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 504 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 505 | _ = try v.pushOperand(Type{ .Known = .I32 }); 506 | }, 507 | .@"f64.store" => { 508 | _ = try v.popOperandExpecting(Type{ .Known = .F64 }); 509 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 510 | }, 511 | .@"f64.load", 512 | .@"f64.convert_i32_s", 513 | .@"f64.convert_i32_u", 514 | => { 515 | _ = try v.popOperandExpecting(Type{ .Known = .I32 }); 516 | _ = try v.pushOperand(Type{ .Known = .F64 }); 517 | }, 518 | .@"f64.reinterpret_i64", 519 | .@"f64.convert_i64_s", 520 | .@"f64.convert_i64_u", 521 | => { 522 | _ = try v.popOperandExpecting(Type{ .Known = .I64 }); 523 | _ = try v.pushOperand(Type{ .Known = .F64 }); 524 | }, 525 | .@"f64.promote_f32" => { 526 | _ = try v.popOperandExpecting(Type{ .Known = .F32 }); 527 | _ = try v.pushOperand(Type{ .Known = .F64 }); 528 | }, 529 | .@"i32.const" => { 530 | _ = try v.pushOperand(Type{ .Known = .I32 }); 531 | }, 532 | .@"i64.const" => { 533 | _ = try v.pushOperand(Type{ .Known = .I64 }); 534 | }, 535 | .@"f32.const" => { 536 | _ = try v.pushOperand(Type{ .Known = .F32 }); 537 | }, 538 | .@"f64.const" => { 539 | _ = try v.pushOperand(Type{ .Known = .F64 }); 540 | }, 541 | .@"ref.is_null" => { 542 | _ = try v.popOperandExpecting(Type.Unknown); // Is this right? Do we need UnknownRefType + UnknownValType 543 | _ = try v.pushOperand(Type{ .Known = .I32 }); 544 | }, 545 | .@"ref.func" => { 546 | _ = try v.pushOperand(Type{ .Known = .FuncRef }); 547 | }, 548 | } 549 | } 550 | 551 | pub fn pushOperand(v: *Validator, t: Type) !void { 552 | defer v.trackMaxDepth(); 553 | try v.op_stack.append(t); 554 | } 555 | 556 | fn popOperand(v: *Validator) !Type { 557 | if (v.ctrl_stack.items.len == 0) return error.ControlStackEmpty; 558 | const ctrl_frame = v.ctrl_stack.items[v.ctrl_stack.items.len - 1]; 559 | if (v.op_stack.items.len == ctrl_frame.height and ctrl_frame.unreachable_flag) { 560 | return Type.Unknown; 561 | } 562 | if (v.op_stack.items.len == ctrl_frame.height) return error.ValidatorPopOperandError; 563 | return v.op_stack.pop() orelse error.OperandNotInOpstack; 564 | } 565 | 566 | pub fn popOperandExpecting(v: *Validator, expected: Type) !Type { 567 | const actual = try v.popOperand(); 568 | 569 | if (!typeEqual(actual, expected) and !typeEqual(actual, Type.Unknown) and !typeEqual(expected, Type.Unknown)) { 570 | return error.MismatchedTypes; 571 | } else { 572 | return actual; 573 | } 574 | } 575 | 576 | fn pushOperands(v: *Validator, operands: []const ValType) !void { 577 | for (operands) |op| { 578 | try v.pushOperand(Type{ .Known = op }); 579 | } 580 | } 581 | 582 | fn popOperands(v: *Validator, operands: []const ValType) !void { 583 | const len = operands.len; 584 | for (operands, 0..) |_, i| { 585 | _ = try v.popOperandExpecting(Type{ .Known = operands[len - i - 1] }); 586 | } 587 | } 588 | 589 | fn trackMaxDepth(v: *Validator) void { 590 | if (v.op_stack.items.len > v.max_depth) { 591 | v.max_depth = v.op_stack.items.len; 592 | } 593 | } 594 | 595 | pub fn pushControlFrame(v: *Validator, opcode: Opcode, in: []const ValType, out: []const ValType) !void { 596 | const frame = ControlFrame{ 597 | .opcode = opcode, 598 | .start_types = in, 599 | .end_types = out, 600 | .height = v.op_stack.items.len, 601 | .unreachable_flag = false, 602 | }; 603 | try v.ctrl_stack.append(frame); 604 | try v.pushOperands(in); 605 | } 606 | 607 | fn popControlFrame(v: *Validator) !ControlFrame { 608 | if (v.ctrl_stack.items.len == 0) return error.ValidatorPopControlFrameControlStackEmpty; 609 | const frame = v.ctrl_stack.items[v.ctrl_stack.items.len - 1]; 610 | try v.popOperands(frame.end_types); 611 | if (v.op_stack.items.len != frame.height) return error.ValidatorPopControlFrameMismatchedSizes; 612 | _ = v.ctrl_stack.pop(); 613 | return frame; 614 | } 615 | 616 | fn labelTypes(frame: ControlFrame) []const ValType { 617 | if (frame.opcode == Opcode.loop) { 618 | return frame.start_types; 619 | } else { 620 | return frame.end_types; 621 | } 622 | } 623 | 624 | fn setUnreachable(v: *Validator) !void { 625 | if (v.ctrl_stack.items.len == 0) return error.ValidatorPopControlFrameControlStackEmpty; 626 | const frame = &v.ctrl_stack.items[v.ctrl_stack.items.len - 1]; 627 | v.op_stack.shrinkRetainingCapacity(frame.height); 628 | frame.unreachable_flag = true; 629 | } 630 | }; 631 | 632 | fn isNum(valuetype: Type) bool { 633 | return switch (valuetype) { 634 | .Unknown => true, 635 | .Known => |k| switch (k) { 636 | .I32, .I64, .F32, .F64 => true, 637 | else => false, 638 | }, 639 | }; 640 | } 641 | 642 | fn typeEqual(v1: Type, v2: Type) bool { 643 | switch (v1) { 644 | .Known => |t1| { 645 | switch (v2) { 646 | .Known => |t2| return t1 == t2, 647 | .Unknown => return false, 648 | } 649 | }, 650 | .Unknown => { 651 | switch (v2) { 652 | .Known => return false, 653 | .Unknown => return true, 654 | } 655 | }, 656 | } 657 | } 658 | 659 | const TypeTag = enum { 660 | Known, 661 | Unknown, 662 | }; 663 | 664 | pub const Type = union(TypeTag) { 665 | Known: ValType, 666 | Unknown: void, 667 | }; 668 | 669 | const OperandStack = ArrayList(Type); 670 | const ControlStack = ArrayList(ControlFrame); 671 | 672 | const ControlFrame = struct { 673 | opcode: Opcode = undefined, 674 | start_types: []const ValType, 675 | end_types: []const ValType, 676 | height: usize = 0, 677 | unreachable_flag: bool = false, 678 | }; 679 | 680 | const testing = std.testing; 681 | test "validate add i32" { 682 | const ArenaAllocator = std.heap.ArenaAllocator; 683 | var arena = ArenaAllocator.init(testing.allocator); 684 | defer arena.deinit(); 685 | var v = Validator.init(arena.allocator(), false); 686 | defer v.deinit(); 687 | 688 | var in: [0]ValType = [_]ValType{} ** 0; 689 | var out: [1]ValType = [_]ValType{.I32} ** 1; 690 | _ = try v.pushControlFrame(.block, in[0..], out[0..]); 691 | _ = try v.validate(.@"i32.const"); 692 | _ = try v.validate(.drop); 693 | _ = try v.validate(.@"i32.const"); 694 | _ = try v.validate(.@"i32.const"); 695 | _ = try v.validate(.@"i32.add"); 696 | _ = try v.validate(.end); 697 | } 698 | 699 | test "validate add i64" { 700 | const ArenaAllocator = std.heap.ArenaAllocator; 701 | var arena = ArenaAllocator.init(testing.allocator); 702 | defer arena.deinit(); 703 | var v = Validator.init(arena.allocator(), false); 704 | defer v.deinit(); 705 | 706 | var in: [0]ValType = [_]ValType{} ** 0; 707 | var out: [1]ValType = [_]ValType{.I64} ** 1; 708 | _ = try v.pushControlFrame(.block, in[0..], out[0..]); 709 | _ = try v.validate(.@"i64.const"); 710 | _ = try v.validate(.@"i64.const"); 711 | _ = try v.validate(.@"i64.add"); 712 | _ = try v.validate(.end); 713 | } 714 | 715 | test "validate add f32" { 716 | const ArenaAllocator = std.heap.ArenaAllocator; 717 | var arena = ArenaAllocator.init(testing.allocator); 718 | defer arena.deinit(); 719 | var v = Validator.init(arena.allocator(), false); 720 | defer v.deinit(); 721 | 722 | var in: [0]ValType = [_]ValType{} ** 0; 723 | var out: [1]ValType = [_]ValType{.F32} ** 1; 724 | _ = try v.pushControlFrame(.block, in[0..], out[0..]); 725 | _ = try v.validate(.@"f32.const"); 726 | _ = try v.validate(.@"f32.const"); 727 | _ = try v.validate(.@"f32.add"); 728 | _ = try v.validate(.end); 729 | } 730 | 731 | test "validate add f64" { 732 | const ArenaAllocator = std.heap.ArenaAllocator; 733 | var arena = ArenaAllocator.init(testing.allocator); 734 | defer arena.deinit(); 735 | var v = Validator.init(arena.allocator(), false); 736 | defer v.deinit(); 737 | 738 | var in: [0]ValType = [_]ValType{} ** 0; 739 | var out: [1]ValType = [_]ValType{.F64} ** 1; 740 | _ = try v.pushControlFrame(.block, in[0..], out[0..]); 741 | _ = try v.validate(.@"f64.const"); 742 | _ = try v.validate(.@"f64.const"); 743 | _ = try v.validate(.@"f64.add"); 744 | _ = try v.validate(.end); 745 | } 746 | 747 | test "validate: add error on mismatched types" { 748 | const ArenaAllocator = std.heap.ArenaAllocator; 749 | var arena = ArenaAllocator.init(testing.allocator); 750 | defer arena.deinit(); 751 | var v = Validator.init(arena.allocator(), false); 752 | defer v.deinit(); 753 | 754 | var in: [0]ValType = [_]ValType{} ** 0; 755 | var out: [1]ValType = [_]ValType{.I32} ** 1; 756 | _ = try v.pushControlFrame(.block, in[0..], out[0..]); 757 | _ = try v.validate(.@"i64.const"); 758 | _ = try v.validate(.@"i32.const"); 759 | _ = v.validate(.@"i32.add") catch |err| { 760 | if (err == error.MismatchedTypes) return; 761 | }; 762 | return error.ExpectedFailure; 763 | } 764 | -------------------------------------------------------------------------------- /src/opcode.zig: -------------------------------------------------------------------------------- 1 | pub const Opcode = enum(u8) { 2 | @"unreachable" = 0x0, 3 | nop = 0x01, 4 | block = 0x02, 5 | loop = 0x03, 6 | @"if" = 0x04, 7 | @"else" = 0x05, 8 | end = 0x0b, 9 | br = 0x0c, 10 | br_if = 0x0d, 11 | br_table = 0x0e, 12 | @"return" = 0x0f, 13 | call = 0x10, 14 | call_indirect = 0x11, 15 | drop = 0x1a, 16 | select = 0x1b, 17 | select_t = 0x1c, 18 | @"local.get" = 0x20, 19 | @"local.set" = 0x21, 20 | @"local.tee" = 0x22, 21 | @"global.get" = 0x23, 22 | @"global.set" = 0x24, 23 | @"table.get" = 0x25, 24 | @"table.set" = 0x26, 25 | @"i32.load" = 0x28, 26 | @"i64.load" = 0x29, 27 | @"f32.load" = 0x2a, 28 | @"f64.load" = 0x2b, 29 | @"i32.load8_s" = 0x2c, 30 | @"i32.load8_u" = 0x2d, 31 | @"i32.load16_s" = 0x2e, 32 | @"i32.load16_u" = 0x2f, 33 | @"i64.load8_s" = 0x30, 34 | @"i64.load8_u" = 0x31, 35 | @"i64.load16_s" = 0x32, 36 | @"i64.load16_u" = 0x33, 37 | @"i64.load32_s" = 0x34, 38 | @"i64.load32_u" = 0x35, 39 | @"i32.store" = 0x36, 40 | @"i64.store" = 0x37, 41 | @"f32.store" = 0x38, 42 | @"f64.store" = 0x39, 43 | @"i32.store8" = 0x3a, 44 | @"i32.store16" = 0x3b, 45 | @"i64.store8" = 0x3c, 46 | @"i64.store16" = 0x3d, 47 | @"i64.store32" = 0x3e, 48 | @"memory.size" = 0x3f, 49 | @"memory.grow" = 0x40, 50 | @"i32.const" = 0x41, 51 | @"i64.const" = 0x42, 52 | @"f32.const" = 0x43, 53 | @"f64.const" = 0x44, 54 | @"i32.eqz" = 0x45, 55 | @"i32.eq" = 0x46, 56 | @"i32.ne" = 0x47, 57 | @"i32.lt_s" = 0x48, 58 | @"i32.lt_u" = 0x49, 59 | @"i32.gt_s" = 0x4a, 60 | @"i32.gt_u" = 0x4b, 61 | @"i32.le_s" = 0x4c, 62 | @"i32.le_u" = 0x4d, 63 | @"i32.ge_s" = 0x4e, 64 | @"i32.ge_u" = 0x4f, 65 | @"i64.eqz" = 0x50, 66 | @"i64.eq" = 0x51, 67 | @"i64.ne" = 0x52, 68 | @"i64.lt_s" = 0x53, 69 | @"i64.lt_u" = 0x54, 70 | @"i64.gt_s" = 0x55, 71 | @"i64.gt_u" = 0x56, 72 | @"i64.le_s" = 0x57, 73 | @"i64.le_u" = 0x58, 74 | @"i64.ge_s" = 0x59, 75 | @"i64.ge_u" = 0x5a, 76 | @"f32.eq" = 0x5b, 77 | @"f32.ne" = 0x5c, 78 | @"f32.lt" = 0x5d, 79 | @"f32.gt" = 0x5e, 80 | @"f32.le" = 0x5f, 81 | @"f32.ge" = 0x60, 82 | @"f64.eq" = 0x61, 83 | @"f64.ne" = 0x62, 84 | @"f64.lt" = 0x63, 85 | @"f64.gt" = 0x64, 86 | @"f64.le" = 0x65, 87 | @"f64.ge" = 0x66, 88 | @"i32.clz" = 0x67, 89 | @"i32.ctz" = 0x68, 90 | @"i32.popcnt" = 0x69, 91 | @"i32.add" = 0x6a, 92 | @"i32.sub" = 0x6b, 93 | @"i32.mul" = 0x6c, 94 | @"i32.div_s" = 0x6d, 95 | @"i32.div_u" = 0x6e, 96 | @"i32.rem_s" = 0x6f, 97 | @"i32.rem_u" = 0x70, 98 | @"i32.and" = 0x71, 99 | @"i32.or" = 0x72, 100 | @"i32.xor" = 0x73, 101 | @"i32.shl" = 0x74, 102 | @"i32.shr_s" = 0x75, 103 | @"i32.shr_u" = 0x76, 104 | @"i32.rotl" = 0x77, 105 | @"i32.rotr" = 0x78, 106 | @"i64.clz" = 0x79, 107 | @"i64.ctz" = 0x7a, 108 | @"i64.popcnt" = 0x7b, 109 | @"i64.add" = 0x7c, 110 | @"i64.sub" = 0x7d, 111 | @"i64.mul" = 0x7e, 112 | @"i64.div_s" = 0x7f, 113 | @"i64.div_u" = 0x80, 114 | @"i64.rem_s" = 0x81, 115 | @"i64.rem_u" = 0x82, 116 | @"i64.and" = 0x83, 117 | @"i64.or" = 0x84, 118 | @"i64.xor" = 0x85, 119 | @"i64.shl" = 0x86, 120 | @"i64.shr_s" = 0x87, 121 | @"i64.shr_u" = 0x88, 122 | @"i64.rotl" = 0x89, 123 | @"i64.rotr" = 0x8a, 124 | @"f32.abs" = 0x8b, 125 | @"f32.neg" = 0x8c, 126 | @"f32.ceil" = 0x8d, 127 | @"f32.floor" = 0x8e, 128 | @"f32.trunc" = 0x8f, 129 | @"f32.nearest" = 0x90, 130 | @"f32.sqrt" = 0x91, 131 | @"f32.add" = 0x92, 132 | @"f32.sub" = 0x93, 133 | @"f32.mul" = 0x94, 134 | @"f32.div" = 0x95, 135 | @"f32.min" = 0x96, 136 | @"f32.max" = 0x97, 137 | @"f32.copysign" = 0x98, 138 | @"f64.abs" = 0x99, 139 | @"f64.neg" = 0x9a, 140 | @"f64.ceil" = 0x9b, 141 | @"f64.floor" = 0x9c, 142 | @"f64.trunc" = 0x9d, 143 | @"f64.nearest" = 0x9e, 144 | @"f64.sqrt" = 0x9f, 145 | @"f64.add" = 0xa0, 146 | @"f64.sub" = 0xa1, 147 | @"f64.mul" = 0xa2, 148 | @"f64.div" = 0xa3, 149 | @"f64.min" = 0xa4, 150 | @"f64.max" = 0xa5, 151 | @"f64.copysign" = 0xa6, 152 | @"i32.wrap_i64" = 0xa7, 153 | @"i32.trunc_f32_s" = 0xa8, 154 | @"i32.trunc_f32_u" = 0xa9, 155 | @"i32.trunc_f64_s" = 0xaa, 156 | @"i32.trunc_f64_u" = 0xab, 157 | @"i64.extend_i32_s" = 0xac, 158 | @"i64.extend_i32_u" = 0xad, 159 | @"i64.trunc_f32_s" = 0xae, 160 | @"i64.trunc_f32_u" = 0xaf, 161 | @"i64.trunc_f64_s" = 0xb0, 162 | @"i64.trunc_f64_u" = 0xb1, 163 | @"f32.convert_i32_s" = 0xb2, 164 | @"f32.convert_i32_u" = 0xb3, 165 | @"f32.convert_i64_s" = 0xb4, 166 | @"f32.convert_i64_u" = 0xb5, 167 | @"f32.demote_f64" = 0xb6, 168 | @"f64.convert_i32_s" = 0xb7, 169 | @"f64.convert_i32_u" = 0xb8, 170 | @"f64.convert_i64_s" = 0xb9, 171 | @"f64.convert_i64_u" = 0xba, 172 | @"f64.promote_f32" = 0xbb, 173 | @"i32.reinterpret_f32" = 0xbc, 174 | @"i64.reinterpret_f64" = 0xbd, 175 | @"f32.reinterpret_i32" = 0xbe, 176 | @"f64.reinterpret_i64" = 0xbf, 177 | @"i32.extend8_s" = 0xc0, 178 | @"i32.extend16_s" = 0xc1, 179 | @"i64.extend8_s" = 0xc2, 180 | @"i64.extend16_s" = 0xc3, 181 | @"i64.extend32_s" = 0xc4, 182 | @"ref.null" = 0xd0, 183 | @"ref.is_null" = 0xd1, 184 | @"ref.func" = 0xd2, 185 | misc = 0xfc, 186 | }; 187 | 188 | pub const MiscOpcode = enum(u8) { 189 | @"i32.trunc_sat_f32_s" = 0x00, 190 | @"i32.trunc_sat_f32_u" = 0x01, 191 | @"i32.trunc_sat_f64_s" = 0x02, 192 | @"i32.trunc_sat_f64_u" = 0x03, 193 | @"i64.trunc_sat_f32_s" = 0x04, 194 | @"i64.trunc_sat_f32_u" = 0x05, 195 | @"i64.trunc_sat_f64_s" = 0x06, 196 | @"i64.trunc_sat_f64_u" = 0x07, 197 | @"memory.init" = 0x08, 198 | @"data.drop" = 0x09, 199 | @"memory.copy" = 0x0a, 200 | @"memory.fill" = 0x0b, 201 | @"table.init" = 0x0c, 202 | @"elem.drop" = 0x0d, 203 | @"table.copy" = 0x0e, 204 | @"table.grow" = 0x0f, 205 | @"table.size" = 0x10, 206 | @"table.fill" = 0x11, 207 | }; 208 | -------------------------------------------------------------------------------- /src/rr.zig: -------------------------------------------------------------------------------- 1 | const MiscOpcode = @import("opcode.zig").MiscOpcode; 2 | const RefType = @import("valtype.zig").RefType; 3 | 4 | pub const RrOpcode = enum(u8) { 5 | @"unreachable" = 0x0, 6 | nop = 0x01, 7 | block = 0x02, 8 | loop = 0x03, 9 | @"if" = 0x04, 10 | @"else" = 0x05, 11 | if_no_else = 0x06, 12 | end = 0x0b, 13 | br = 0x0c, 14 | br_if = 0x0d, 15 | br_table = 0x0e, 16 | @"return" = 0x0f, 17 | call = 0x10, 18 | call_indirect = 0x11, 19 | fast_call = 0x12, 20 | drop = 0x1a, 21 | select = 0x1b, 22 | @"local.get" = 0x20, 23 | @"local.set" = 0x21, 24 | @"local.tee" = 0x22, 25 | @"global.get" = 0x23, 26 | @"global.set" = 0x24, 27 | @"table.get" = 0x25, 28 | @"table.set" = 0x26, 29 | @"i32.load" = 0x28, 30 | @"i64.load" = 0x29, 31 | @"f32.load" = 0x2a, 32 | @"f64.load" = 0x2b, 33 | @"i32.load8_s" = 0x2c, 34 | @"i32.load8_u" = 0x2d, 35 | @"i32.load16_s" = 0x2e, 36 | @"i32.load16_u" = 0x2f, 37 | @"i64.load8_s" = 0x30, 38 | @"i64.load8_u" = 0x31, 39 | @"i64.load16_s" = 0x32, 40 | @"i64.load16_u" = 0x33, 41 | @"i64.load32_s" = 0x34, 42 | @"i64.load32_u" = 0x35, 43 | @"i32.store" = 0x36, 44 | @"i64.store" = 0x37, 45 | @"f32.store" = 0x38, 46 | @"f64.store" = 0x39, 47 | @"i32.store8" = 0x3a, 48 | @"i32.store16" = 0x3b, 49 | @"i64.store8" = 0x3c, 50 | @"i64.store16" = 0x3d, 51 | @"i64.store32" = 0x3e, 52 | @"memory.size" = 0x3f, 53 | @"memory.grow" = 0x40, 54 | @"i32.const" = 0x41, 55 | @"i64.const" = 0x42, 56 | @"f32.const" = 0x43, 57 | @"f64.const" = 0x44, 58 | @"i32.eqz" = 0x45, 59 | @"i32.eq" = 0x46, 60 | @"i32.ne" = 0x47, 61 | @"i32.lt_s" = 0x48, 62 | @"i32.lt_u" = 0x49, 63 | @"i32.gt_s" = 0x4a, 64 | @"i32.gt_u" = 0x4b, 65 | @"i32.le_s" = 0x4c, 66 | @"i32.le_u" = 0x4d, 67 | @"i32.ge_s" = 0x4e, 68 | @"i32.ge_u" = 0x4f, 69 | @"i64.eqz" = 0x50, 70 | @"i64.eq" = 0x51, 71 | @"i64.ne" = 0x52, 72 | @"i64.lt_s" = 0x53, 73 | @"i64.lt_u" = 0x54, 74 | @"i64.gt_s" = 0x55, 75 | @"i64.gt_u" = 0x56, 76 | @"i64.le_s" = 0x57, 77 | @"i64.le_u" = 0x58, 78 | @"i64.ge_s" = 0x59, 79 | @"i64.ge_u" = 0x5a, 80 | @"f32.eq" = 0x5b, 81 | @"f32.ne" = 0x5c, 82 | @"f32.lt" = 0x5d, 83 | @"f32.gt" = 0x5e, 84 | @"f32.le" = 0x5f, 85 | @"f32.ge" = 0x60, 86 | @"f64.eq" = 0x61, 87 | @"f64.ne" = 0x62, 88 | @"f64.lt" = 0x63, 89 | @"f64.gt" = 0x64, 90 | @"f64.le" = 0x65, 91 | @"f64.ge" = 0x66, 92 | @"i32.clz" = 0x67, 93 | @"i32.ctz" = 0x68, 94 | @"i32.popcnt" = 0x69, 95 | @"i32.add" = 0x6a, 96 | @"i32.sub" = 0x6b, 97 | @"i32.mul" = 0x6c, 98 | @"i32.div_s" = 0x6d, 99 | @"i32.div_u" = 0x6e, 100 | @"i32.rem_s" = 0x6f, 101 | @"i32.rem_u" = 0x70, 102 | @"i32.and" = 0x71, 103 | @"i32.or" = 0x72, 104 | @"i32.xor" = 0x73, 105 | @"i32.shl" = 0x74, 106 | @"i32.shr_s" = 0x75, 107 | @"i32.shr_u" = 0x76, 108 | @"i32.rotl" = 0x77, 109 | @"i32.rotr" = 0x78, 110 | @"i64.clz" = 0x79, 111 | @"i64.ctz" = 0x7a, 112 | @"i64.popcnt" = 0x7b, 113 | @"i64.add" = 0x7c, 114 | @"i64.sub" = 0x7d, 115 | @"i64.mul" = 0x7e, 116 | @"i64.div_s" = 0x7f, 117 | @"i64.div_u" = 0x80, 118 | @"i64.rem_s" = 0x81, 119 | @"i64.rem_u" = 0x82, 120 | @"i64.and" = 0x83, 121 | @"i64.or" = 0x84, 122 | @"i64.xor" = 0x85, 123 | @"i64.shl" = 0x86, 124 | @"i64.shr_s" = 0x87, 125 | @"i64.shr_u" = 0x88, 126 | @"i64.rotl" = 0x89, 127 | @"i64.rotr" = 0x8a, 128 | @"f32.abs" = 0x8b, 129 | @"f32.neg" = 0x8c, 130 | @"f32.ceil" = 0x8d, 131 | @"f32.floor" = 0x8e, 132 | @"f32.trunc" = 0x8f, 133 | @"f32.nearest" = 0x90, 134 | @"f32.sqrt" = 0x91, 135 | @"f32.add" = 0x92, 136 | @"f32.sub" = 0x93, 137 | @"f32.mul" = 0x94, 138 | @"f32.div" = 0x95, 139 | @"f32.min" = 0x96, 140 | @"f32.max" = 0x97, 141 | @"f32.copysign" = 0x98, 142 | @"f64.abs" = 0x99, 143 | @"f64.neg" = 0x9a, 144 | @"f64.ceil" = 0x9b, 145 | @"f64.floor" = 0x9c, 146 | @"f64.trunc" = 0x9d, 147 | @"f64.nearest" = 0x9e, 148 | @"f64.sqrt" = 0x9f, 149 | @"f64.add" = 0xa0, 150 | @"f64.sub" = 0xa1, 151 | @"f64.mul" = 0xa2, 152 | @"f64.div" = 0xa3, 153 | @"f64.min" = 0xa4, 154 | @"f64.max" = 0xa5, 155 | @"f64.copysign" = 0xa6, 156 | @"i32.wrap_i64" = 0xa7, 157 | @"i32.trunc_f32_s" = 0xa8, 158 | @"i32.trunc_f32_u" = 0xa9, 159 | @"i32.trunc_f64_s" = 0xaa, 160 | @"i32.trunc_f64_u" = 0xab, 161 | @"i64.extend_i32_s" = 0xac, 162 | @"i64.extend_i32_u" = 0xad, 163 | @"i64.trunc_f32_s" = 0xae, 164 | @"i64.trunc_f32_u" = 0xaf, 165 | @"i64.trunc_f64_s" = 0xb0, 166 | @"i64.trunc_f64_u" = 0xb1, 167 | @"f32.convert_i32_s" = 0xb2, 168 | @"f32.convert_i32_u" = 0xb3, 169 | @"f32.convert_i64_s" = 0xb4, 170 | @"f32.convert_i64_u" = 0xb5, 171 | @"f32.demote_f64" = 0xb6, 172 | @"f64.convert_i32_s" = 0xb7, 173 | @"f64.convert_i32_u" = 0xb8, 174 | @"f64.convert_i64_s" = 0xb9, 175 | @"f64.convert_i64_u" = 0xba, 176 | @"f64.promote_f32" = 0xbb, 177 | @"i32.reinterpret_f32" = 0xbc, 178 | @"i64.reinterpret_f64" = 0xbd, 179 | @"f32.reinterpret_i32" = 0xbe, 180 | @"f64.reinterpret_i64" = 0xbf, 181 | @"i32.extend8_s" = 0xc0, 182 | @"i32.extend16_s" = 0xc1, 183 | @"i64.extend8_s" = 0xc2, 184 | @"i64.extend16_s" = 0xc3, 185 | @"i64.extend32_s" = 0xc4, 186 | @"ref.null" = 0xd0, 187 | @"ref.is_null" = 0xd1, 188 | @"ref.func" = 0xd2, 189 | misc = 0xfc, 190 | }; 191 | 192 | pub const Rr = union(RrOpcode) { 193 | @"unreachable": void, 194 | nop: void, 195 | block: struct { 196 | param_arity: u16, 197 | return_arity: u16, 198 | branch_target: u32, 199 | }, 200 | loop: struct { 201 | param_arity: u16, 202 | return_arity: u16, 203 | branch_target: u32, 204 | }, 205 | @"if": struct { 206 | param_arity: u16, 207 | return_arity: u16, 208 | branch_target: u32, 209 | else_ip: u32, 210 | }, 211 | @"else": void, 212 | if_no_else: struct { 213 | param_arity: u16, 214 | return_arity: u16, 215 | branch_target: u32, 216 | }, 217 | end: void, 218 | br: u32, 219 | br_if: u32, 220 | br_table: struct { 221 | ls: Range, 222 | ln: u32, 223 | }, 224 | @"return": void, 225 | call: usize, // u32? 226 | call_indirect: struct { 227 | typeidx: u32, 228 | tableidx: u32, 229 | }, 230 | fast_call: struct { 231 | start: u32, 232 | locals: u16, 233 | params: u16, 234 | results: u16, 235 | required_stack_space: u16, 236 | }, 237 | drop: void, 238 | select: void, 239 | @"local.get": u32, 240 | @"local.set": u32, 241 | @"local.tee": u32, 242 | @"global.get": u32, 243 | @"global.set": u32, 244 | @"table.get": u32, // tableidx 245 | @"table.set": u32, // tableidx 246 | @"i32.load": struct { 247 | alignment: u32, 248 | offset: u32, 249 | }, 250 | @"i64.load": struct { 251 | alignment: u32, 252 | offset: u32, 253 | }, 254 | @"f32.load": struct { 255 | alignment: u32, 256 | offset: u32, 257 | }, 258 | @"f64.load": struct { 259 | alignment: u32, 260 | offset: u32, 261 | }, 262 | @"i32.load8_s": struct { 263 | alignment: u32, 264 | offset: u32, 265 | }, 266 | @"i32.load8_u": struct { 267 | alignment: u32, 268 | offset: u32, 269 | }, 270 | @"i32.load16_s": struct { 271 | alignment: u32, 272 | offset: u32, 273 | }, 274 | @"i32.load16_u": struct { 275 | alignment: u32, 276 | offset: u32, 277 | }, 278 | @"i64.load8_s": struct { 279 | alignment: u32, 280 | offset: u32, 281 | }, 282 | @"i64.load8_u": struct { 283 | alignment: u32, 284 | offset: u32, 285 | }, 286 | @"i64.load16_s": struct { 287 | alignment: u32, 288 | offset: u32, 289 | }, 290 | @"i64.load16_u": struct { 291 | alignment: u32, 292 | offset: u32, 293 | }, 294 | @"i64.load32_s": struct { 295 | alignment: u32, 296 | offset: u32, 297 | }, 298 | @"i64.load32_u": struct { 299 | alignment: u32, 300 | offset: u32, 301 | }, 302 | @"i32.store": struct { 303 | alignment: u32, 304 | offset: u32, 305 | }, 306 | @"i64.store": struct { 307 | alignment: u32, 308 | offset: u32, 309 | }, 310 | @"f32.store": struct { 311 | alignment: u32, 312 | offset: u32, 313 | }, 314 | @"f64.store": struct { 315 | alignment: u32, 316 | offset: u32, 317 | }, 318 | @"i32.store8": struct { 319 | alignment: u32, 320 | offset: u32, 321 | }, 322 | @"i32.store16": struct { 323 | alignment: u32, 324 | offset: u32, 325 | }, 326 | @"i64.store8": struct { 327 | alignment: u32, 328 | offset: u32, 329 | }, 330 | @"i64.store16": struct { 331 | alignment: u32, 332 | offset: u32, 333 | }, 334 | @"i64.store32": struct { 335 | alignment: u32, 336 | offset: u32, 337 | }, 338 | @"memory.size": u32, 339 | @"memory.grow": u32, 340 | @"i32.const": i32, 341 | @"i64.const": i64, 342 | @"f32.const": f32, 343 | @"f64.const": f64, 344 | @"i32.eqz": void, 345 | @"i32.eq": void, 346 | @"i32.ne": void, 347 | @"i32.lt_s": void, 348 | @"i32.lt_u": void, 349 | @"i32.gt_s": void, 350 | @"i32.gt_u": void, 351 | @"i32.le_s": void, 352 | @"i32.le_u": void, 353 | @"i32.ge_s": void, 354 | @"i32.ge_u": void, 355 | @"i64.eqz": void, 356 | @"i64.eq": void, 357 | @"i64.ne": void, 358 | @"i64.lt_s": void, 359 | @"i64.lt_u": void, 360 | @"i64.gt_s": void, 361 | @"i64.gt_u": void, 362 | @"i64.le_s": void, 363 | @"i64.le_u": void, 364 | @"i64.ge_s": void, 365 | @"i64.ge_u": void, 366 | @"f32.eq": void, 367 | @"f32.ne": void, 368 | @"f32.lt": void, 369 | @"f32.gt": void, 370 | @"f32.le": void, 371 | @"f32.ge": void, 372 | @"f64.eq": void, 373 | @"f64.ne": void, 374 | @"f64.lt": void, 375 | @"f64.gt": void, 376 | @"f64.le": void, 377 | @"f64.ge": void, 378 | @"i32.clz": void, 379 | @"i32.ctz": void, 380 | @"i32.popcnt": void, 381 | @"i32.add": void, 382 | @"i32.sub": void, 383 | @"i32.mul": void, 384 | @"i32.div_s": void, 385 | @"i32.div_u": void, 386 | @"i32.rem_s": void, 387 | @"i32.rem_u": void, 388 | @"i32.and": void, 389 | @"i32.or": void, 390 | @"i32.xor": void, 391 | @"i32.shl": void, 392 | @"i32.shr_s": void, 393 | @"i32.shr_u": void, 394 | @"i32.rotl": void, 395 | @"i32.rotr": void, 396 | @"i64.clz": void, 397 | @"i64.ctz": void, 398 | @"i64.popcnt": void, 399 | @"i64.add": void, 400 | @"i64.sub": void, 401 | @"i64.mul": void, 402 | @"i64.div_s": void, 403 | @"i64.div_u": void, 404 | @"i64.rem_s": void, 405 | @"i64.rem_u": void, 406 | @"i64.and": void, 407 | @"i64.or": void, 408 | @"i64.xor": void, 409 | @"i64.shl": void, 410 | @"i64.shr_s": void, 411 | @"i64.shr_u": void, 412 | @"i64.rotl": void, 413 | @"i64.rotr": void, 414 | @"f32.abs": void, 415 | @"f32.neg": void, 416 | @"f32.ceil": void, 417 | @"f32.floor": void, 418 | @"f32.trunc": void, 419 | @"f32.nearest": void, 420 | @"f32.sqrt": void, 421 | @"f32.add": void, 422 | @"f32.sub": void, 423 | @"f32.mul": void, 424 | @"f32.div": void, 425 | @"f32.min": void, 426 | @"f32.max": void, 427 | @"f32.copysign": void, 428 | @"f64.abs": void, 429 | @"f64.neg": void, 430 | @"f64.ceil": void, 431 | @"f64.floor": void, 432 | @"f64.trunc": void, 433 | @"f64.nearest": void, 434 | @"f64.sqrt": void, 435 | @"f64.add": void, 436 | @"f64.sub": void, 437 | @"f64.mul": void, 438 | @"f64.div": void, 439 | @"f64.min": void, 440 | @"f64.max": void, 441 | @"f64.copysign": void, 442 | @"i32.wrap_i64": void, 443 | @"i32.trunc_f32_s": void, 444 | @"i32.trunc_f32_u": void, 445 | @"i32.trunc_f64_s": void, 446 | @"i32.trunc_f64_u": void, 447 | @"i64.extend_i32_s": void, 448 | @"i64.extend_i32_u": void, 449 | @"i64.trunc_f32_s": void, 450 | @"i64.trunc_f32_u": void, 451 | @"i64.trunc_f64_s": void, 452 | @"i64.trunc_f64_u": void, 453 | @"f32.convert_i32_s": void, 454 | @"f32.convert_i32_u": void, 455 | @"f32.convert_i64_s": void, 456 | @"f32.convert_i64_u": void, 457 | @"f32.demote_f64": void, 458 | @"f64.convert_i32_s": void, 459 | @"f64.convert_i32_u": void, 460 | @"f64.convert_i64_s": void, 461 | @"f64.convert_i64_u": void, 462 | @"f64.promote_f32": void, 463 | @"i32.reinterpret_f32": void, 464 | @"i64.reinterpret_f64": void, 465 | @"f32.reinterpret_i32": void, 466 | @"f64.reinterpret_i64": void, 467 | @"i32.extend8_s": void, 468 | @"i32.extend16_s": void, 469 | @"i64.extend8_s": void, 470 | @"i64.extend16_s": void, 471 | @"i64.extend32_s": void, 472 | @"ref.null": RefType, 473 | @"ref.is_null": void, 474 | @"ref.func": u32, 475 | misc: MiscRr, 476 | }; 477 | 478 | pub const MiscRr = union(MiscOpcode) { 479 | @"i32.trunc_sat_f32_s": void, 480 | @"i32.trunc_sat_f32_u": void, 481 | @"i32.trunc_sat_f64_s": void, 482 | @"i32.trunc_sat_f64_u": void, 483 | @"i64.trunc_sat_f32_s": void, 484 | @"i64.trunc_sat_f32_u": void, 485 | @"i64.trunc_sat_f64_s": void, 486 | @"i64.trunc_sat_f64_u": void, 487 | @"memory.init": struct { 488 | dataidx: u32, 489 | memidx: u32, 490 | }, 491 | @"data.drop": u32, 492 | @"memory.copy": struct { 493 | src_memidx: u8, 494 | dest_memidx: u8, 495 | }, 496 | @"memory.fill": u8, 497 | @"table.init": struct { 498 | tableidx: u32, 499 | elemidx: u32, 500 | }, 501 | @"elem.drop": struct { 502 | elemidx: u32, 503 | }, 504 | @"table.copy": struct { 505 | dest_tableidx: u32, 506 | src_tableidx: u32, 507 | }, 508 | @"table.grow": struct { 509 | tableidx: u32, 510 | }, 511 | @"table.size": struct { 512 | tableidx: u32, 513 | }, 514 | @"table.fill": struct { 515 | tableidx: u32, 516 | }, 517 | }; 518 | 519 | pub const Range = struct { 520 | offset: usize = 0, 521 | count: usize = 0, 522 | }; 523 | -------------------------------------------------------------------------------- /src/store.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const math = std.math; 4 | const ArrayList = std.ArrayList; 5 | const Function = @import("store/function.zig").Function; 6 | const Memory = @import("store/memory.zig").Memory; 7 | const Table = @import("store/table.zig").Table; 8 | const Global = @import("store/global.zig").Global; 9 | const Elem = @import("store/elem.zig").Elem; 10 | const Data = @import("store/data.zig").Data; 11 | const Import = @import("module.zig").Import; 12 | const Tag = @import("module.zig").Tag; 13 | const Mutability = @import("module.zig").Mutability; 14 | const RefType = @import("valtype.zig").RefType; 15 | const ValType = @import("valtype.zig").ValType; 16 | const Instance = @import("instance.zig").Instance; 17 | const VirtualMachine = @import("instance/vm.zig").VirtualMachine; 18 | const WasmError = @import("instance/vm.zig").WasmError; 19 | 20 | // - Stores provide the runtime memory shared between modules 21 | // - For different applications you may want to use a store that 22 | // that allocates individual items differently. For example, 23 | // if you know ahead of time that you will only load a fixed 24 | // number of modules during the lifetime of the program, then 25 | // you might be quite happy with a store that uses ArrayLists 26 | // with an arena allocator, i.e. you are going to deallocate 27 | // everything at the same time. 28 | 29 | pub const ImportExport = struct { 30 | import: Import, 31 | handle: usize, 32 | }; 33 | 34 | pub const ArrayListStore = struct { 35 | alloc: mem.Allocator, 36 | functions: ArrayList(Function), 37 | memories: ArrayList(Memory), 38 | tables: ArrayList(Table), 39 | globals: ArrayList(Global), 40 | elems: ArrayList(Elem), 41 | datas: ArrayList(Data), 42 | imports: ArrayList(ImportExport), 43 | 44 | pub fn init(alloc: mem.Allocator) ArrayListStore { 45 | const store = ArrayListStore{ 46 | .alloc = alloc, 47 | .functions = ArrayList(Function).init(alloc), 48 | .memories = ArrayList(Memory).init(alloc), 49 | .tables = ArrayList(Table).init(alloc), 50 | .globals = ArrayList(Global).init(alloc), 51 | .elems = ArrayList(Elem).init(alloc), 52 | .datas = ArrayList(Data).init(alloc), 53 | .imports = ArrayList(ImportExport).init(alloc), 54 | }; 55 | 56 | return store; 57 | } 58 | 59 | pub fn deinit(self: *ArrayListStore) void { 60 | for (self.memories.items) |*m| { 61 | m.deinit(); 62 | } 63 | for (self.tables.items) |*t| { 64 | t.deinit(); 65 | } 66 | for (self.elems.items) |*e| { 67 | e.deinit(); 68 | } 69 | for (self.datas.items) |*d| { 70 | d.deinit(); 71 | } 72 | 73 | self.functions.deinit(); 74 | self.memories.deinit(); 75 | self.tables.deinit(); 76 | self.globals.deinit(); 77 | self.elems.deinit(); 78 | self.datas.deinit(); 79 | self.imports.deinit(); 80 | } 81 | 82 | // import 83 | // 84 | // import attempts to find in the store, the given module.name pair 85 | // for the given type 86 | pub fn import(self: *ArrayListStore, module: []const u8, name: []const u8, tag: Tag) !usize { 87 | for (self.imports.items) |importexport| { 88 | if (tag != importexport.import.desc_tag) continue; 89 | if (!mem.eql(u8, module, importexport.import.module)) continue; 90 | if (!mem.eql(u8, name, importexport.import.name)) continue; 91 | 92 | return importexport.handle; 93 | } 94 | 95 | return error.ImportNotFound; 96 | } 97 | 98 | pub fn @"export"(self: *ArrayListStore, module: []const u8, name: []const u8, tag: Tag, handle: usize) !void { 99 | try self.imports.append(ImportExport{ 100 | .import = Import{ 101 | .module = module, 102 | .name = name, 103 | .desc_tag = tag, 104 | }, 105 | .handle = handle, 106 | }); 107 | } 108 | 109 | /// Given a funcaddr return a Function if the funcaddr exists 110 | /// otherwise error with BadFunctionIndex. 111 | pub fn function(self: *ArrayListStore, funcaddr: usize) !Function { 112 | if (funcaddr >= self.functions.items.len) return error.BadFunctionIndex; 113 | return self.functions.items[funcaddr]; 114 | } 115 | 116 | pub fn addFunction(self: *ArrayListStore, func: Function) !usize { 117 | const fun_ptr = try self.functions.addOne(); 118 | fun_ptr.* = func; 119 | return self.functions.items.len - 1; 120 | } 121 | 122 | /// Given a memaddr return a pointer to the Memory if the memaddr exists 123 | /// otherwise error with BadMemoryIndex. 124 | pub fn memory(self: *ArrayListStore, memaddr: usize) !*Memory { 125 | if (memaddr >= self.memories.items.len) return error.BadMemoryIndex; 126 | return &self.memories.items[memaddr]; 127 | } 128 | 129 | /// Allocate a new Memory with min / max size and add to store. 130 | pub fn addMemory(self: *ArrayListStore, min: u32, max: ?u32) !usize { 131 | const mem_ptr = try self.memories.addOne(); 132 | mem_ptr.* = Memory.init(self.alloc, min, max); 133 | _ = try mem_ptr.grow(min); 134 | return self.memories.items.len - 1; 135 | } 136 | 137 | /// Given a tableaddr return a pointer to the Table if the tableaddr exists 138 | /// otherwise error with BadTableIndex. 139 | pub fn table(self: *ArrayListStore, tableaddr: usize) !*Table { 140 | if (tableaddr >= self.tables.items.len) return error.BadTableIndex; 141 | return &self.tables.items[tableaddr]; 142 | } 143 | 144 | pub fn addTable(self: *ArrayListStore, reftype: RefType, entries: u32, max: ?u32) !usize { 145 | const tbl_ptr = try self.tables.addOne(); 146 | tbl_ptr.* = try Table.init(self.alloc, reftype, entries, max); 147 | return self.tables.items.len - 1; 148 | } 149 | 150 | /// Given a globaladdr return a pointer to the Global if the globaladdr exists 151 | /// otherwise error with BadGlobalIndex. 152 | pub fn global(self: *ArrayListStore, globaladdr: usize) !*Global { 153 | if (globaladdr >= self.globals.items.len) return error.BadGlobalIndex; 154 | return &self.globals.items[globaladdr]; 155 | } 156 | 157 | /// Add a Global to the store and return its globaladdr 158 | pub fn addGlobal(self: *ArrayListStore, value: Global) !usize { 159 | const glbl_ptr = try self.globals.addOne(); 160 | glbl_ptr.* = value; 161 | 162 | return self.globals.items.len - 1; 163 | } 164 | 165 | /// Given a elemaddr return a pointer to the Elem if the elemaddr exists 166 | /// otherwise error with BadElemAddr. 167 | pub fn elem(self: *ArrayListStore, elemaddr: usize) !*Elem { 168 | if (elemaddr >= self.elems.items.len) return error.BadElemAddr; 169 | return &self.elems.items[elemaddr]; 170 | } 171 | 172 | pub fn addElem(self: *ArrayListStore, reftype: RefType, count: u32) !usize { 173 | const elem_ptr = try self.elems.addOne(); 174 | elem_ptr.* = try Elem.init(self.alloc, reftype, count); 175 | return self.elems.items.len - 1; 176 | } 177 | 178 | /// Given a dataaddr return a pointer to the Data if the dataaddr exists 179 | /// otherwise error with BadDataAddr. 180 | pub fn data(self: *ArrayListStore, dataaddr: usize) !*Data { 181 | if (dataaddr >= self.datas.items.len) return error.BadDataAddr; 182 | return &self.datas.items[dataaddr]; 183 | } 184 | 185 | pub fn addData(self: *ArrayListStore, count: u32) !usize { 186 | const data_ptr = try self.datas.addOne(); 187 | data_ptr.* = try Data.init(self.alloc, count); 188 | return self.datas.items.len - 1; 189 | } 190 | 191 | // Helper functions for exposing values 192 | 193 | pub fn exposeHostFunction( 194 | self: *ArrayListStore, 195 | module: []const u8, 196 | function_name: []const u8, 197 | host_function_pointer: *const fn (*VirtualMachine, usize) WasmError!void, 198 | host_function_context: usize, 199 | params: []const ValType, 200 | results: []const ValType, 201 | ) !void { 202 | const funcaddr = try self.addFunction(Function{ 203 | .params = params, 204 | .results = results, 205 | .subtype = .{ 206 | .host_function = .{ 207 | .func = host_function_pointer, 208 | .context = host_function_context, 209 | }, 210 | }, 211 | }); 212 | 213 | try self.@"export"(module[0..], function_name[0..], .Func, funcaddr); 214 | } 215 | 216 | pub fn exposeMemory(self: *ArrayListStore, module: []const u8, name: []const u8, min: u32, max: ?u32) !void { 217 | const memaddr = try self.addMemory(min, max); 218 | 219 | try self.@"export"(module, name, .Mem, memaddr); 220 | } 221 | 222 | pub fn exposeTable(self: *ArrayListStore, module: []const u8, name: []const u8, reftype: RefType, entries: u32, max: ?u32) !void { 223 | const tableaddr = try self.addTable(reftype, entries, max); 224 | 225 | try self.@"export"(module, name, .Table, tableaddr); 226 | } 227 | 228 | pub fn exposeGlobal(self: *ArrayListStore, module: []const u8, name: []const u8, value: u64, valtype: ValType, mutability: Mutability) !void { 229 | const globaladdr = try self.addGlobal(.{ 230 | .value = value, 231 | .valtype = valtype, 232 | .mutability = mutability, 233 | }); 234 | 235 | try self.@"export"(module, name, .Global, globaladdr); 236 | } 237 | 238 | pub fn exposeElem(self: *ArrayListStore, module: []const u8, name: []const u8, reftype: RefType, count: u32) !void { 239 | const elemaddr = try self.addElem(reftype, count); 240 | 241 | try self.@"export"(module, name, .Elem, elemaddr); 242 | } 243 | 244 | pub fn exposeData(self: *ArrayListStore, module: []const u8, name: []const u8, count: u32) !void { 245 | const dataaddr = try self.addData(count); 246 | 247 | try self.@"export"(module, name, .Data, dataaddr); 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /src/store/data.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | 4 | pub const Data = struct { 5 | data: []u8, 6 | alloc: mem.Allocator, 7 | dropped: bool = false, 8 | 9 | pub fn init(alloc: mem.Allocator, count: u32) !Data { 10 | const data = try alloc.alloc(u8, count); 11 | 12 | return Data{ 13 | .data = data, 14 | .alloc = alloc, 15 | }; 16 | } 17 | 18 | pub fn deinit(self: *Data) void { 19 | self.alloc.free(self.data); 20 | } 21 | 22 | pub fn set(self: *Data, index: usize, value: u8) !void { 23 | if (index >= self.data.len) return error.DataIndexOutOfBounds; 24 | self.data[index] = value; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/store/elem.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const RefType = @import("../valtype.zig").RefType; 4 | 5 | pub const Elem = struct { 6 | type: RefType, 7 | elem: []u32, 8 | alloc: mem.Allocator, 9 | dropped: bool = false, 10 | 11 | pub fn init(alloc: mem.Allocator, reftype: RefType, count: u32) !Elem { 12 | const elem = try alloc.alloc(u32, count); 13 | 14 | return Elem{ 15 | .type = reftype, 16 | .elem = elem, 17 | .alloc = alloc, 18 | }; 19 | } 20 | 21 | pub fn deinit(self: *Elem) void { 22 | self.alloc.free(self.elem); 23 | } 24 | 25 | pub fn set(self: *Elem, index: usize, value: u32) !void { 26 | if (index >= self.elem.len) return error.ElemIndexOutOfBounds; 27 | self.elem[index] = value; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/store/function.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ValType = @import("../valtype.zig").ValType; 3 | const VirtualMachine = @import("../instance/vm.zig").VirtualMachine; 4 | const WasmError = @import("../instance/vm.zig").WasmError; 5 | const Instance = @import("../instance.zig").Instance; 6 | const FuncType = @import("../module.zig").FuncType; 7 | 8 | pub const Function = struct { 9 | params: []const ValType, 10 | results: []const ValType, 11 | subtype: union(enum) { 12 | function: struct { 13 | locals_count: usize, 14 | start: usize, 15 | required_stack_space: usize, 16 | instance: *Instance, 17 | }, 18 | host_function: struct { 19 | func: *const fn (*VirtualMachine, usize) WasmError!void, 20 | context: usize, 21 | }, 22 | }, 23 | 24 | pub fn checkSignatures(self: Function, b: FuncType) !void { 25 | if (self.params.len != b.params.len) return error.MismatchedSignatures; 26 | if (self.results.len != b.results.len) return error.MismatchedSignatures; 27 | 28 | for (self.params, 0..) |p_a, i| { 29 | if (p_a != b.params[i]) return error.MismatchedSignatures; 30 | } 31 | 32 | for (self.results, 0..) |r_a, i| { 33 | if (r_a != b.results[i]) return error.MismatchedSignatures; 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/store/global.zig: -------------------------------------------------------------------------------- 1 | const ValType = @import("../valtype.zig").ValType; 2 | const Mutability = @import("../module.zig").Mutability; 3 | 4 | pub const Global = struct { 5 | valtype: ValType, 6 | mutability: Mutability, 7 | value: u64, 8 | }; 9 | -------------------------------------------------------------------------------- /src/store/memory.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const ArrayList = std.ArrayList; 4 | 5 | pub const MAX_PAGES = 64 * 1024; 6 | pub const PAGE_SIZE = 64 * 1024; 7 | 8 | pub const Memory = struct { 9 | alloc: mem.Allocator, 10 | min: u32, 11 | max: ?u32 = null, 12 | data: ArrayList(u8), 13 | 14 | pub fn init(alloc: mem.Allocator, min: u32, max: ?u32) Memory { 15 | return Memory{ 16 | .alloc = alloc, 17 | .data = ArrayList(u8).init(alloc), 18 | .min = min, 19 | .max = max, 20 | }; 21 | } 22 | 23 | pub fn deinit(self: *Memory) void { 24 | self.data.deinit(); 25 | } 26 | 27 | // Return the size of the memory (in pages) 28 | pub fn size(self: *Memory) u32 { 29 | std.debug.assert(self.data.items.len % PAGE_SIZE == 0); 30 | 31 | return @truncate(self.data.items.len / PAGE_SIZE); 32 | } 33 | 34 | pub fn sizeBytes(self: *Memory) u33 { 35 | // FIXME: add assertion that len fits in u33 36 | return @truncate(self.data.items.len); 37 | } 38 | 39 | pub fn grow(self: *Memory, num_pages: u32) !usize { 40 | if (self.size() + num_pages > @min(self.max orelse MAX_PAGES, MAX_PAGES)) return error.OutOfBoundsMemoryAccess; 41 | 42 | const old_size_in_bytes = self.data.items.len; 43 | const old_size_in_pages = self.size(); 44 | _ = try self.data.resize(self.data.items.len + PAGE_SIZE * num_pages); 45 | 46 | // Zero memory. FIXME: I don't think this is required (maybe do this only in debug build) 47 | @memset(self.data.items[old_size_in_bytes .. old_size_in_bytes + PAGE_SIZE * num_pages], 0); 48 | 49 | return old_size_in_pages; 50 | } 51 | 52 | pub fn copy(self: *Memory, address: u32, data: []const u8) !void { 53 | if (address + data.len > self.data.items.len) return error.OutOfBoundsMemoryAccess; 54 | 55 | mem.copyForwards(u8, self.data.items[address .. address + data.len], data); 56 | } 57 | 58 | pub fn uncheckedFill(self: *Memory, dst_address: u32, n: u32, value: u8) void { 59 | @memset(self.data.items[dst_address .. dst_address + n], value); 60 | } 61 | 62 | pub fn uncheckedCopy(self: *Memory, dst_address: u32, data: []const u8) void { 63 | mem.copyForwards(u8, self.data.items[dst_address .. dst_address + data.len], data); 64 | } 65 | 66 | pub fn uncheckedCopyBackwards(self: *Memory, dst_address: u32, data: []const u8) void { 67 | mem.copyBackwards(u8, self.data.items[dst_address .. dst_address + data.len], data); 68 | } 69 | 70 | // as per copy but don't actually mutate 71 | pub fn check(self: *Memory, address: u32, data: []const u8) !void { 72 | if (address + data.len > self.data.items.len) return error.OutOfBoundsMemoryAccess; 73 | } 74 | 75 | pub fn read(self: *Memory, comptime T: type, offset: u32, address: u32) !T { 76 | const effective_address = @as(u33, offset) + @as(u33, address); 77 | if (effective_address + @sizeOf(T) - 1 >= self.data.items.len) return error.OutOfBoundsMemoryAccess; 78 | 79 | switch (T) { 80 | u8, 81 | u16, 82 | u32, 83 | u64, 84 | i8, 85 | i16, 86 | i32, 87 | i64, 88 | => return mem.readInt(T, @as(*const [@sizeOf(T)]u8, @ptrCast(&self.data.items[effective_address])), .little), 89 | f32 => { 90 | const x = mem.readInt(u32, @as(*const [@sizeOf(T)]u8, @ptrCast(&self.data.items[effective_address])), .little); 91 | return @bitCast(x); 92 | }, 93 | f64 => { 94 | const x = mem.readInt(u64, @as(*const [@sizeOf(T)]u8, @ptrCast(&self.data.items[effective_address])), .little); 95 | return @bitCast(x); 96 | }, 97 | else => @compileError("Memory.read unsupported type (not int/float): " ++ @typeName(T)), 98 | } 99 | } 100 | 101 | pub fn write(self: *Memory, comptime T: type, offset: u32, address: u32, value: T) !void { 102 | const effective_address = @as(u33, offset) + @as(u33, address); 103 | if (effective_address + @sizeOf(T) - 1 >= self.data.items.len) return error.OutOfBoundsMemoryAccess; 104 | 105 | switch (T) { 106 | u8, 107 | u16, 108 | u32, 109 | u64, 110 | i8, 111 | i16, 112 | i32, 113 | i64, 114 | => std.mem.writeInt(T, @as(*[@sizeOf(T)]u8, @ptrCast(&self.data.items[effective_address])), value, .little), 115 | f32 => { 116 | const x: u32 = @bitCast(value); 117 | std.mem.writeInt(u32, @as(*[@sizeOf(u32)]u8, @ptrCast(&self.data.items[effective_address])), x, .little); 118 | }, 119 | f64 => { 120 | const x: u64 = @bitCast(value); 121 | std.mem.writeInt(u64, @as(*[@sizeOf(u64)]u8, @ptrCast(&self.data.items[effective_address])), x, .little); 122 | }, 123 | else => @compileError("Memory.read unsupported type (not int/float): " ++ @typeName(T)), 124 | } 125 | } 126 | 127 | pub fn memory(self: *Memory) []u8 { 128 | return self.data.items[0..]; 129 | } 130 | }; 131 | 132 | const testing = std.testing; 133 | test "Memory test" { 134 | const ArrayListStore = @import("../store.zig").ArrayListStore; 135 | const ArenaAllocator = std.heap.ArenaAllocator; 136 | var arena = ArenaAllocator.init(testing.allocator); 137 | defer _ = arena.deinit(); 138 | 139 | const alloc = arena.allocator(); 140 | 141 | var store = ArrayListStore.init(alloc); 142 | const mem_handle = try store.addMemory(0, null); 143 | var mem0 = try store.memory(mem_handle); 144 | try testing.expectEqual(@as(usize, 0), mem0.sizeBytes()); 145 | 146 | _ = try mem0.grow(1); 147 | 148 | try testing.expectEqual(@as(usize, 1 * PAGE_SIZE), mem0.sizeBytes()); 149 | try testing.expectEqual(@as(usize, 1), mem0.size()); 150 | 151 | try testing.expectEqual(@as(u8, 0x00), try mem0.read(u8, 0, 0)); 152 | try testing.expectEqual(@as(u32, 0x00000000), try mem0.read(u32, 0, 0)); 153 | try mem0.write(u8, 0, 0, 15); 154 | try testing.expectEqual(@as(u8, 15), try mem0.read(u8, 0, 0)); 155 | try testing.expectEqual(@as(u32, 0x0000000F), try mem0.read(u32, 0, 0)); 156 | 157 | try mem0.write(u8, 0, 0xFFFF, 42); 158 | try testing.expectEqual(@as(u8, 42), try mem0.read(u8, 0, 0xFFFF)); 159 | try testing.expectError(error.OutOfBoundsMemoryAccess, mem0.read(u16, 0, 0xFFFF)); 160 | try testing.expectError(error.OutOfBoundsMemoryAccess, mem0.read(u8, 0, 0xFFFF + 1)); 161 | 162 | _ = try mem0.grow(1); 163 | try testing.expectEqual(@as(usize, 2 * PAGE_SIZE), mem0.sizeBytes()); 164 | try testing.expectEqual(@as(usize, 2), mem0.size()); 165 | 166 | try testing.expectEqual(@as(u8, 0x00), try mem0.read(u8, 0, 0xFFFF + 1)); 167 | // Write across page boundary 168 | try mem0.write(u16, 0, 0xFFFF, 0xDEAD); 169 | try testing.expectEqual(@as(u8, 0xAD), try mem0.read(u8, 0, 0xFFFF)); 170 | try testing.expectEqual(@as(u8, 0xDE), try mem0.read(u8, 0, 0xFFFF + 1)); 171 | try testing.expectEqual(@as(u16, 0xDEAD), try mem0.read(u16, 0, 0xFFFF)); 172 | const slice = mem0.memory(); 173 | try testing.expectEqual(@as(u8, 0xAD), slice[0xFFFF]); 174 | try testing.expectEqual(@as(u8, 0xDE), slice[0xFFFF + 1]); 175 | 176 | try testing.expectEqual(@as(u8, 0x00), try mem0.read(u8, 0, 0x1FFFF)); 177 | try testing.expectError(error.OutOfBoundsMemoryAccess, mem0.read(u8, 0, 0x1FFFF + 1)); 178 | 179 | mem0.max = 2; 180 | try testing.expectError(error.OutOfBoundsMemoryAccess, mem0.grow(1)); 181 | 182 | mem0.max = null; 183 | _ = try mem0.grow(1); 184 | try testing.expectEqual(@as(usize, 3), mem0.size()); 185 | 186 | try testing.expectEqual(@as(usize, 3 * PAGE_SIZE), mem0.sizeBytes()); 187 | } 188 | -------------------------------------------------------------------------------- /src/store/table.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const math = std.math; 4 | const RefType = @import("../valtype.zig").RefType; 5 | const ArrayList = std.ArrayList; 6 | 7 | pub const Table = struct { 8 | alloc: mem.Allocator, 9 | data: ArrayList(?usize), 10 | min: u32, 11 | max: ?u32, 12 | reftype: RefType, 13 | 14 | pub fn init(alloc: mem.Allocator, reftype: RefType, min: u32, max: ?u32) !Table { 15 | var data = ArrayList(?usize).init(alloc); 16 | try data.appendNTimes(null, min); 17 | 18 | return Table{ 19 | .alloc = alloc, 20 | .data = data, 21 | .min = min, 22 | .max = max, 23 | .reftype = reftype, 24 | }; 25 | } 26 | 27 | pub fn deinit(self: *Table) void { 28 | self.data.deinit(); 29 | } 30 | 31 | pub fn lookup(self: *Table, index: u32) !usize { 32 | if (index >= self.data.items.len) return error.OutOfBoundsMemoryAccess; 33 | return self.data.items[index] orelse return error.UndefinedElement; 34 | } 35 | 36 | pub fn get(self: *Table, index: u32) !?usize { 37 | if (index >= self.data.items.len) return error.OutOfBoundsMemoryAccess; 38 | return self.data.items[index]; 39 | } 40 | 41 | pub fn set(self: *Table, index: u32, value: ?usize) !void { 42 | if (index >= self.data.items.len) return error.OutOfBoundsMemoryAccess; 43 | self.data.items[index] = value; 44 | } 45 | 46 | pub fn size(self: *Table) usize { 47 | return self.data.items.len; 48 | } 49 | 50 | pub fn grow(self: *Table, n: u32) !u32 { 51 | const len = math.cast(u32, self.data.items.len) orelse return error.TableGrowToolarge; 52 | _ = try math.add(u32, len, n); 53 | 54 | if (self.max) |max| { 55 | if (self.data.items.len + n > max) return error.TableGrowWouldExceedMax; 56 | } 57 | 58 | const old_size = math.cast(u32, self.data.items.len) orelse return error.TableGrowToolarge; 59 | 60 | try self.data.appendNTimes(null, n); 61 | 62 | return old_size; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /src/test/fact.wasm: -------------------------------------------------------------------------------- 1 | asm`fact 2 | 53A N A!A!@  l! Aj! N  -------------------------------------------------------------------------------- /src/test/fib.wasm: -------------------------------------------------------------------------------- 1 | asm`fib 2 | *( AF@A AF@A Ak Akj -------------------------------------------------------------------------------- /src/test/test.wasm: -------------------------------------------------------------------------------- 1 | asm`add 2 |  j -------------------------------------------------------------------------------- /src/valtype.zig: -------------------------------------------------------------------------------- 1 | pub const NumType = enum(u8) { 2 | I32 = 0x7F, 3 | I64 = 0x7E, 4 | F32 = 0x7D, 5 | F64 = 0x7C, 6 | V128 = 0x7B, 7 | }; 8 | 9 | pub const RefType = enum(u8) { 10 | FuncRef = 0x70, 11 | ExternRef = 0x6F, 12 | }; 13 | 14 | pub const ValType = enum(u8) { 15 | I32 = 0x7F, 16 | I64 = 0x7E, 17 | F32 = 0x7D, 18 | F64 = 0x7C, 19 | V128 = 0x7B, 20 | FuncRef = 0x70, 21 | ExternRef = 0x6F, 22 | }; 23 | -------------------------------------------------------------------------------- /src/wasi/wasi.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const fs = std.fs; 4 | const posix = std.posix; 5 | const math = std.math; 6 | const wasi = std.os.wasi; 7 | 8 | const VirtualMachine = @import("../instance/vm.zig").VirtualMachine; 9 | const WasmError = @import("../instance/vm.zig").WasmError; 10 | 11 | // I've largely used the implementations from https://github.com/andrewrk/zig-wasi (shoutout to Andrew and Jacob!). 12 | // 13 | // Thought: could we move the wasi implementations into the zig standard library, or are they too specific to a particular 14 | // WebAssembly implementation? E.g. some of the wasi functions only seem to depend on a slice of memory + the zig std lib 15 | // functions. 16 | 17 | pub fn args_get(vm: *VirtualMachine) WasmError!void { 18 | const argv_buf_ptr = vm.popOperand(u32); 19 | const argv_ptr = vm.popOperand(u32); 20 | 21 | const memory = try vm.inst.getMemory(0); 22 | const data = memory.memory(); 23 | 24 | var argv_buf_i: usize = 0; 25 | for (vm.wasi_args.items, 0..) |arg, i| { 26 | const argv_i_ptr = argv_buf_ptr + argv_buf_i; 27 | const arg_len = arg.len + 1; 28 | 29 | @memcpy(data[argv_i_ptr .. argv_i_ptr + arg_len], arg[0..arg_len]); 30 | argv_buf_i += arg_len; 31 | 32 | try memory.write(u32, argv_ptr, 4 * @as(u32, @intCast(i)), @as(u32, @intCast(argv_i_ptr))); 33 | } 34 | 35 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 36 | } 37 | 38 | pub fn args_sizes_get(vm: *VirtualMachine) WasmError!void { 39 | const argv_buf_size_ptr = vm.popOperand(u32); 40 | const argc_ptr = vm.popOperand(u32); 41 | 42 | const memory = try vm.inst.getMemory(0); 43 | 44 | const argc = vm.wasi_args.items.len; 45 | try memory.write(u32, argc_ptr, 0, @as(u32, @intCast(argc))); 46 | 47 | var buf_size: usize = 0; 48 | for (vm.wasi_args.items) |arg| { 49 | buf_size += arg.len + 1; 50 | } 51 | try memory.write(u32, argv_buf_size_ptr, 0, @as(u32, @intCast(buf_size))); 52 | 53 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 54 | } 55 | 56 | pub fn clock_time_get(vm: *VirtualMachine) WasmError!void { 57 | const timestamp_ptr = vm.popOperand(u32); 58 | const precision = vm.popOperand(i64); // FIXME: we should probably be using this 59 | _ = precision; 60 | const clock_id = vm.popOperand(i32); // FIXME: we should probably be using this 61 | _ = clock_id; 62 | 63 | const memory = try vm.inst.getMemory(0); 64 | 65 | const timestamp = toWasiTimestamp(std.time.nanoTimestamp()); 66 | 67 | try memory.write(u64, timestamp_ptr, 0, timestamp); 68 | 69 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 70 | } 71 | 72 | pub fn fd_close(vm: *VirtualMachine) WasmError!void { 73 | const fd = vm.popOperand(i32); 74 | 75 | const host_fd = vm.getHostFd(fd); 76 | posix.close(host_fd); 77 | 78 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 79 | } 80 | 81 | pub fn fd_fdstat_get(vm: *VirtualMachine) WasmError!void { 82 | const stat_ptr = vm.popOperand(u32); 83 | const fd = vm.popOperand(i32); 84 | 85 | const memory = try vm.inst.getMemory(0); 86 | 87 | const host_fd = vm.getHostFd(fd); 88 | const file = fs.File{ .handle = host_fd }; 89 | const stat = file.stat() catch |err| { 90 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 91 | return; 92 | }; 93 | 94 | try memory.write(u16, stat_ptr, 0x00, @intFromEnum(toWasiFileType(stat.kind))); 95 | try memory.write(u16, stat_ptr, 0x02, 0); 96 | try memory.write(u64, stat_ptr, 0x08, math.maxInt(u64)); 97 | try memory.write(u64, stat_ptr, 0x10, math.maxInt(u64)); 98 | 99 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 100 | } 101 | 102 | // FIXME: implement 103 | pub fn fd_fdstat_set_flags(vm: *VirtualMachine) WasmError!void { 104 | const param0 = vm.popOperand(i32); 105 | const param1 = vm.popOperand(i32); 106 | std.debug.print("Unimplemented: fd_fdstat_set_flags({}, {})\n", .{ param0, param1 }); 107 | try vm.pushOperand(u64, 0); 108 | @panic("Unimplemented: fd_fdstat_set_flags"); 109 | } 110 | 111 | pub fn fd_filestat_get(vm: *VirtualMachine) WasmError!void { 112 | const stat_ptr = vm.popOperand(u32); 113 | const fd = vm.popOperand(i32); 114 | 115 | const memory = try vm.inst.getMemory(0); 116 | 117 | const host_fd = vm.getHostFd(fd); 118 | const file = std.fs.File{ .handle = host_fd }; 119 | const stat = file.stat() catch |err| 120 | { 121 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 122 | return; 123 | }; 124 | 125 | try memory.write(u64, stat_ptr, 0, 0); // device id 126 | try memory.write(u64, stat_ptr, 8, stat.inode); // inode 127 | try memory.write(u64, stat_ptr, 16, @intFromEnum(toWasiFileType(stat.kind))); // filetype 128 | try memory.write(u64, stat_ptr, 24, 1); // nlink - hard links refering to this file count 129 | try memory.write(u64, stat_ptr, 32, stat.size); // size in bytes 130 | try memory.write(u64, stat_ptr, 40, @as(u64, @intCast(stat.atime))); // atime - last access time 131 | try memory.write(u64, stat_ptr, 48, @as(u64, @intCast(stat.mtime))); // mtime - last modified time 132 | try memory.write(u64, stat_ptr, 56, @as(u64, @intCast(stat.ctime))); // ctime - last status change time 133 | 134 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 135 | } 136 | 137 | pub fn fd_prestat_get(vm: *VirtualMachine) WasmError!void { 138 | const prestat_ptr = vm.popOperand(u32); 139 | const fd = vm.popOperand(i32); 140 | 141 | const memory = try vm.inst.getMemory(0); 142 | 143 | if (vm.lookupWasiPreopen(fd)) |preopen| { 144 | const some_other_ptr = try memory.read(u32, prestat_ptr, 0); 145 | const name_len_ptr = try memory.read(u32, prestat_ptr, 4); 146 | try memory.write(u32, some_other_ptr, 0, 0); 147 | try memory.write(u32, name_len_ptr, 0, @as(u32, @intCast(preopen.name.len))); 148 | 149 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 150 | } else { 151 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); 152 | } 153 | } 154 | 155 | pub fn fd_prestat_dir_name(vm: *VirtualMachine) WasmError!void { 156 | const path_len = vm.popOperand(u32); // FIXME: we should probably be using this 157 | _ = path_len; 158 | const path_ptr = vm.popOperand(u32); 159 | const fd = vm.popOperand(i32); 160 | 161 | const memory = try vm.inst.getMemory(0); 162 | 163 | const preopen = vm.lookupWasiPreopen(fd) orelse return WasmError.Trap; 164 | try memory.copy(path_ptr, preopen.name); 165 | 166 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 167 | } 168 | 169 | pub fn fd_read(vm: *VirtualMachine) WasmError!void { 170 | const n_read_ptr = vm.popOperand(u32); 171 | const iovs_len = vm.popOperand(u32); 172 | const iovs_ptr = vm.popOperand(u32); 173 | const fd = vm.popOperand(i32); 174 | 175 | const memory = try vm.inst.getMemory(0); 176 | const data = memory.memory(); 177 | 178 | const host_fd = vm.getHostFd(fd); 179 | 180 | var i: u32 = 0; 181 | var total_read: usize = 0; 182 | while (i < iovs_len) : (i += 1) { 183 | const offset: u32 = i * 8; // Each iov is 8 bytes... 184 | const iov_i_ptr = try memory.read(u32, iovs_ptr, offset); // 4 bytes (u32) for the ith ptr of where to read into 185 | const iov_i_len = try memory.read(u32, iovs_ptr, offset + 4); // 4 bytes (u32) for the length of data to read 186 | 187 | const buf = data[iov_i_ptr .. iov_i_ptr + iov_i_len]; 188 | 189 | // read data from fd into buffer defined by iov 190 | const read = posix.read(host_fd, buf) catch |err| { 191 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 192 | return; 193 | }; 194 | 195 | total_read += read; 196 | if (read != buf.len) break; 197 | } 198 | 199 | try memory.write(u32, n_read_ptr, 0, @as(u32, @intCast(total_read))); 200 | 201 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 202 | } 203 | 204 | pub fn fd_seek(vm: *VirtualMachine) WasmError!void { 205 | const new_offset_ptr = vm.popOperand(u32); 206 | const relative_to: wasi.whence_t = @enumFromInt(vm.popOperand(i32)); 207 | const offset = vm.popOperand(i64); 208 | const fd = vm.popOperand(i32); 209 | 210 | switch (relative_to) { 211 | wasi.whence_t.CUR => { 212 | posix.lseek_CUR(fd, offset) catch |err| { 213 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 214 | return; 215 | }; 216 | }, 217 | wasi.whence_t.END => { 218 | posix.lseek_END(fd, offset) catch |err| { 219 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 220 | return; 221 | }; 222 | }, 223 | wasi.whence_t.SET => { 224 | posix.lseek_SET(fd, @intCast(offset)) catch |err| { 225 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 226 | return; 227 | }; 228 | }, 229 | } 230 | 231 | const new_offset = posix.lseek_CUR_get(fd) catch |err| { 232 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 233 | return; 234 | }; 235 | 236 | const memory = try vm.inst.getMemory(0); 237 | try memory.write(u64, new_offset_ptr, 0, new_offset); 238 | 239 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 240 | } 241 | 242 | pub fn fd_write(vm: *VirtualMachine) WasmError!void { 243 | const ret_ptr = vm.popOperand(u32); 244 | const iovs_len = vm.popOperand(u32); 245 | const iovs_ptr = vm.popOperand(u32); 246 | const fd = vm.popOperand(i32); 247 | 248 | const memory = try vm.inst.getMemory(0); 249 | const data = memory.memory(); 250 | 251 | const host_fd = vm.getHostFd(fd); 252 | 253 | var n: usize = 0; 254 | var i: u32 = 0; 255 | while (i < iovs_len) : (i += 1) { 256 | const offset: u32 = i * 8; 257 | const iov_i_ptr = try memory.read(u32, iovs_ptr, offset); 258 | const iov_i_len = try memory.read(u32, iovs_ptr, offset + 4); 259 | 260 | const bytes = data[iov_i_ptr .. iov_i_ptr + iov_i_len]; 261 | 262 | const written = posix.write(host_fd, bytes) catch |err| { 263 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 264 | return; 265 | }; 266 | 267 | n += written; 268 | 269 | if (written != bytes.len) break; 270 | } 271 | 272 | try memory.write(u32, ret_ptr, 0, @as(u32, @intCast(n))); 273 | 274 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 275 | } 276 | 277 | // FIXME: implement 278 | pub fn path_create_directory(vm: *VirtualMachine) WasmError!void { 279 | const param0 = vm.popOperand(i32); 280 | const param1 = vm.popOperand(i32); 281 | const param2 = vm.popOperand(i32); 282 | std.debug.print("Unimplemented: path_create_directory({}, {}, {})\n", .{ param0, param1, param2 }); 283 | try vm.pushOperand(u64, 0); 284 | @panic("Unimplemented: path_create_directory"); 285 | } 286 | 287 | pub fn path_filestat_get(vm: *VirtualMachine) WasmError!void { 288 | const stat_ptr = vm.popOperand(u32); 289 | const path_len = vm.popOperand(u32); 290 | const path_ptr = vm.popOperand(u32); 291 | const flags = vm.popOperand(u32); // FIXME: we should probably be using this 292 | _ = flags; 293 | const fd = vm.popOperand(i32); 294 | 295 | const memory = try vm.inst.getMemory(0); 296 | const data = memory.memory(); 297 | 298 | const sub_path = data[path_ptr .. path_ptr + path_len]; 299 | 300 | const host_fd = vm.getHostFd(fd); 301 | const dir: fs.Dir = .{ .fd = host_fd }; 302 | const stat = dir.statFile(sub_path) catch |err| { 303 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 304 | return; 305 | }; 306 | 307 | try memory.write(u64, stat_ptr, 0, 0); 308 | try memory.write(u64, stat_ptr, 0x08, stat.inode); 309 | try memory.write(u64, stat_ptr, 0x10, @intFromEnum(toWasiFileType(stat.kind))); 310 | try memory.write(u64, stat_ptr, 0x18, 1); 311 | try memory.write(u64, stat_ptr, 0x20, stat.size); 312 | try memory.write(u64, stat_ptr, 0x28, toWasiTimestamp(stat.atime)); 313 | try memory.write(u64, stat_ptr, 0x30, toWasiTimestamp(stat.mtime)); 314 | try memory.write(u64, stat_ptr, 0x38, toWasiTimestamp(stat.ctime)); 315 | 316 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 317 | } 318 | 319 | pub fn path_open(vm: *VirtualMachine) WasmError!void { 320 | const fd_ptr = vm.popOperand(u32); 321 | 322 | const fs_flags: wasi.fdflags_t = @bitCast(@as(u16, @truncate(vm.popOperand(u32)))); 323 | 324 | const fs_rights_inheriting = vm.popOperand(u64); // FIXME: we should probably be using this 325 | _ = fs_rights_inheriting; 326 | 327 | const fs_rights_base: wasi.rights_t = @bitCast(vm.popOperand(u64)); 328 | 329 | const oflags: wasi.oflags_t = @bitCast(@as(u16, @truncate(vm.popOperand(u32)))); 330 | 331 | const path_len = vm.popOperand(u32); 332 | const path_ptr = vm.popOperand(u32); 333 | const dir_flags = vm.popOperand(u32); // FIXME: we should probably be using this 334 | _ = dir_flags; 335 | const dir_fd = vm.popOperand(i32); 336 | 337 | const memory = try vm.inst.getMemory(0); 338 | const data = memory.memory(); 339 | 340 | const sub_path = data[path_ptr .. path_ptr + path_len]; 341 | 342 | const host_fd = vm.getHostFd(dir_fd); 343 | 344 | const flags = posix.O{ 345 | .CREAT = oflags.CREAT, 346 | .DIRECTORY = oflags.DIRECTORY, 347 | .EXCL = oflags.EXCL, 348 | .TRUNC = oflags.TRUNC, 349 | 350 | .APPEND = fs_flags.APPEND, 351 | .DSYNC = fs_flags.DSYNC, 352 | .NONBLOCK = fs_flags.NONBLOCK, 353 | .SYNC = fs_flags.SYNC, 354 | 355 | .ACCMODE = if (fs_rights_base.FD_READ and fs_rights_base.FD_WRITE) blk: { 356 | break :blk .RDWR; 357 | } else if (fs_rights_base.FD_WRITE) blk: { 358 | break :blk .WRONLY; 359 | } else if (fs_rights_base.FD_READ) blk: { 360 | break :blk .RDONLY; 361 | } else unreachable, 362 | }; 363 | 364 | const mode = 0o644; 365 | const opened_fd = posix.openat(host_fd, sub_path, flags, mode) catch |err| { 366 | try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); 367 | return; 368 | }; 369 | 370 | try memory.write(i32, fd_ptr, 0, opened_fd); 371 | 372 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 373 | } 374 | 375 | // FIXME: implement 376 | pub fn poll_oneoff(vm: *VirtualMachine) WasmError!void { 377 | const param0 = vm.popOperand(i32); 378 | const param1 = vm.popOperand(i32); 379 | const param2 = vm.popOperand(i32); 380 | const param3 = vm.popOperand(i32); 381 | std.debug.print("Unimplemented: poll_oneoff({}, {}, {}, {})\n", .{ param0, param1, param2, param3 }); 382 | try vm.pushOperand(u64, 0); 383 | @panic("Unimplemented: poll_oneoff"); 384 | } 385 | 386 | pub fn proc_exit(vm: *VirtualMachine) WasmError!void { 387 | const param0 = vm.popOperand(i32); 388 | const code: u32 = @abs(param0); 389 | posix.exit(@truncate(code)); 390 | } 391 | 392 | pub fn random_get(vm: *VirtualMachine) WasmError!void { 393 | const buf_len = vm.popOperand(u32); 394 | const buf_ptr = vm.popOperand(u32); 395 | 396 | const memory = try vm.inst.getMemory(0); 397 | const data = memory.memory(); 398 | 399 | std.crypto.random.bytes(data[buf_ptr .. buf_ptr + buf_len]); 400 | 401 | try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); 402 | } 403 | 404 | // FIXME: figure out complete mapping of errors we expect from 405 | // zig std lib and how they map to the complete wasi.errno_t 406 | // struct. Such a complete mapping would be useful to add to 407 | // the zig std lib? 408 | fn toWasiError(err: anyerror) wasi.errno_t { 409 | return switch (err) { 410 | error.AccessDenied => .ACCES, 411 | error.DiskQuota => .DQUOT, 412 | error.InputOutput => .IO, 413 | error.FileTooBig => .FBIG, 414 | error.NoSpaceLeft => .NOSPC, 415 | error.BrokenPipe => .PIPE, 416 | error.NotOpenForWriting => .BADF, 417 | error.SystemResources => .NOMEM, 418 | error.FileNotFound => .NOENT, 419 | error.PathAlreadyExists => .EXIST, 420 | error.IsDir => .ISDIR, 421 | else => std.debug.panic("WASI: Unhandled zig stdlib error: {s}", .{@errorName(err)}), 422 | }; 423 | } 424 | 425 | // FIXME: can we write a program that exercises all of the zig file kinds and wasi filetypes? 426 | // Again, such a mapping would be useful to add to the zig std lib? 427 | fn toWasiFileType(kind: fs.File.Kind) wasi.filetype_t { 428 | return switch (kind) { 429 | .block_device => .BLOCK_DEVICE, 430 | .character_device => .CHARACTER_DEVICE, 431 | .directory => .DIRECTORY, 432 | .sym_link => .SYMBOLIC_LINK, 433 | .file => .REGULAR_FILE, 434 | .unknown => .UNKNOWN, 435 | 436 | .named_pipe, 437 | .unix_domain_socket, 438 | .whiteout, 439 | .door, 440 | .event_port, 441 | => .UNKNOWN, 442 | }; 443 | } 444 | 445 | fn toWasiTimestamp(ns: i128) u64 { 446 | return @as(u64, @intCast(ns)); 447 | } 448 | 449 | const _ = std.testing.refAllDecls(); 450 | -------------------------------------------------------------------------------- /test/.gitattributes: -------------------------------------------------------------------------------- 1 | testsuite/** linguist-vendored 2 | -------------------------------------------------------------------------------- /test/fact.wasm: -------------------------------------------------------------------------------- 1 | asm`fact 2 | 53A N A!A!@  l! Aj! N  -------------------------------------------------------------------------------- /test/fact.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t0 (func (param i32) (result i32))) 3 | (func $fact (type $t0) (param $p0 i32) (result i32) 4 | (local $l1 i32) (local $l2 i32) 5 | i32.const 2 6 | local.get $p0 7 | i32.ge_s 8 | if $I0 (result i32) 9 | local.get $p0 10 | return 11 | else 12 | i32.const 1 13 | local.set $l2 14 | i32.const 2 15 | local.set $l1 16 | loop $L1 17 | local.get $l1 18 | local.get $l2 19 | i32.mul 20 | local.set $l2 21 | local.get $l1 22 | i32.const 1 23 | i32.add 24 | local.set $l1 25 | local.get $p0 26 | local.get $l1 27 | i32.ge_s 28 | br_if $L1 29 | end 30 | local.get $l2 31 | return 32 | end) 33 | (export "fact" (func $fact))) 34 | -------------------------------------------------------------------------------- /test/fib.wasm: -------------------------------------------------------------------------------- 1 | asm`fib 2 | *( AF@A AF@A Ak Akj -------------------------------------------------------------------------------- /test/fib.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type $t (func (param i32) (result i32))) 3 | (func $fib (type $t) (param $n i32) (result i32) 4 | local.get $n 5 | i32.const 0 6 | i32.eq 7 | if $I0 8 | i32.const 0 9 | return 10 | end 11 | local.get $n 12 | i32.const 1 13 | i32.eq 14 | if $I1 15 | i32.const 1 16 | return 17 | end 18 | local.get $n 19 | i32.const 2 20 | i32.sub 21 | call $fib 22 | local.get $n 23 | i32.const 1 24 | i32.sub 25 | call $fib 26 | i32.add 27 | return) 28 | (export "fib" (func $fib))) -------------------------------------------------------------------------------- /test/fuzzer/build.zig: -------------------------------------------------------------------------------- 1 | const Builder = @import("std").build.Builder; 2 | 3 | pub fn build(b: *Builder) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const exe = b.addExecutable(.{ 8 | .name = "fuzzer", 9 | .root_source_file = .{ .path = "src/fuzzer.zig" }, 10 | .target = target, 11 | .optimize = optimize, 12 | }); 13 | exe.addAnonymousModule("zware", .{ 14 | .source_file = .{ .path = "../../src/main.zig" }, 15 | }); 16 | b.installArtifact(exe); 17 | 18 | const run_cmd = b.addRunArtifact(exe); 19 | run_cmd.step.dependOn(b.getInstallStep()); 20 | if (b.args) |args| { 21 | run_cmd.addArgs(args); 22 | } 23 | 24 | const run_step = b.step("run", "Run the app"); 25 | run_step.dependOn(&run_cmd.step); 26 | } 27 | -------------------------------------------------------------------------------- /test/fuzzer/src/fuzzer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const fs = std.fs; 4 | const fmt = std.fmt; 5 | const process = std.process; 6 | const zware = @import("zware"); 7 | const ArrayList = std.ArrayList; 8 | const Module = zware.Module; 9 | const Store = zware.Store; 10 | const Instance = zware.Instance; 11 | const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; 12 | const ArenaAllocator = std.heap.ArenaAllocator; 13 | var gpa = GeneralPurposeAllocator(.{}){}; 14 | 15 | var prng = std.rand.DefaultPrng.init(0x12345678); 16 | var program: []u8 = undefined; 17 | var fuzzed_name: []u8 = undefined; 18 | var fuzzed_dir: [:0]const u8 = undefined; 19 | 20 | pub fn main() !void { 21 | var args = process.args(); 22 | _ = args.skip(); 23 | const directory = args.next() orelse return error.NoFilename; 24 | fuzzed_dir = args.next() orelse return error.NoFilename; 25 | 26 | defer _ = gpa.deinit(); 27 | 28 | var name_arena = ArenaAllocator.init(gpa.allocator()); 29 | defer _ = name_arena.deinit(); 30 | const name_alloc = name_arena.allocator(); 31 | 32 | var wasm_files = ArrayList([]const u8).init(gpa.allocator()); 33 | defer wasm_files.deinit(); 34 | 35 | var dir = try std.fs.cwd().openIterableDir(directory, .{}); 36 | defer dir.close(); 37 | 38 | var it = dir.iterate(); 39 | 40 | var file_count: usize = 0; 41 | while (try it.next()) |entry| { 42 | if (entry.kind == .file) file_count += 1; 43 | if (mem.endsWith(u8, entry.name, ".wasm")) { 44 | const s = try name_alloc.alloc(u8, entry.name.len); 45 | mem.copy(u8, s, entry.name); 46 | try wasm_files.append(s); 47 | } 48 | } 49 | 50 | if (file_count == 0) return error.NoWasmFiles; 51 | 52 | const random = prng.random(); 53 | 54 | const flips = [8]u8{ 55 | 0b00000001, 56 | 0b00000010, 57 | 0b00000100, 58 | 0b00001000, 59 | 0b00010000, 60 | 0b00100000, 61 | 0b01000000, 62 | 0b10000000, 63 | }; 64 | 65 | var i: usize = 0; 66 | while (true) : (i += 1) { 67 | if (i % 5000 == 0) { 68 | std.log.info("i = {}", .{i}); 69 | } 70 | var arena = ArenaAllocator.init(gpa.allocator()); 71 | defer _ = arena.deinit(); 72 | const alloc = arena.allocator(); 73 | 74 | // 1. Randomly choose .wasm from testsuite-generated 75 | random.shuffle([]const u8, wasm_files.items); 76 | const wasm_file = wasm_files.items[0]; 77 | 78 | // 2. Load file 79 | program = try dir.dir.readFileAlloc(alloc, wasm_file, 0xFFFFFFF); 80 | if (program.len == 0) continue; 81 | 82 | // 3. Flip a bit (maybe we should flip bits somewhat proportional to the file size) 83 | const byte_to_change = random.uintLessThan(usize, program.len); 84 | const bit_to_change = random.uintLessThan(u6, 8); 85 | program[byte_to_change] = program[byte_to_change] ^ flips[bit_to_change]; 86 | fuzzed_name = try fmt.allocPrint(name_alloc, "{}_{}.{s}", .{ byte_to_change, bit_to_change, wasm_file }); 87 | defer name_alloc.free(fuzzed_name); 88 | 89 | var store: Store = Store.init(alloc); 90 | 91 | var module = Module.init(alloc, program); 92 | module.decode() catch continue; 93 | 94 | var instance = Instance.init(alloc, &store, module); 95 | instance.instantiate() catch continue; 96 | } 97 | } 98 | 99 | pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { 100 | var out_dir = std.fs.cwd().openDir(fuzzed_dir, .{}) catch unreachable; 101 | defer out_dir.close(); 102 | 103 | std.log.info("Fuzzer found bug: {s}", .{fuzzed_name}); 104 | out_dir.writeFile(fuzzed_name, program) catch unreachable; 105 | std.builtin.default_panic(msg, error_return_trace, ret_addr); 106 | } 107 | -------------------------------------------------------------------------------- /test/interface/README.md: -------------------------------------------------------------------------------- 1 | # interface 2 | 3 | Provide `interface` with a `.wasm` file path and it will print out the imports / exports of the `.wasm` file. 4 | -------------------------------------------------------------------------------- /test/interface/build.zig: -------------------------------------------------------------------------------- 1 | const Build = @import("std").Build; 2 | 3 | pub fn build(b: *Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const exe = b.addExecutable(.{ 8 | .name = "interface", 9 | .root_source_file = .{ .path = "src/interface.zig" }, 10 | .target = target, 11 | .optimize = optimize, 12 | }); 13 | exe.root_module.addAnonymousImport("zware", .{ 14 | .root_source_file = .{ .path = "../../src/main.zig" }, 15 | }); 16 | b.installArtifact(exe); 17 | 18 | const run_cmd = b.addRunArtifact(exe); 19 | run_cmd.step.dependOn(b.getInstallStep()); 20 | if (b.args) |args| { 21 | run_cmd.addArgs(args); 22 | } 23 | 24 | const run_step = b.step("run", "Run the app"); 25 | run_step.dependOn(&run_cmd.step); 26 | } 27 | -------------------------------------------------------------------------------- /test/interface/src/interface.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const fs = std.fs; 4 | const fmt = std.fmt; 5 | const process = std.process; 6 | const zware = @import("zware"); 7 | const ArrayList = std.ArrayList; 8 | const Module = zware.Module; 9 | const Store = zware.Store; 10 | const Instance = zware.Instance; 11 | const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; 12 | var gpa = GeneralPurposeAllocator(.{}){}; 13 | 14 | pub fn main() !void { 15 | defer _ = gpa.deinit(); 16 | var alloc = gpa.allocator(); 17 | 18 | var args = try process.argsWithAllocator(alloc); 19 | defer args.deinit(); 20 | _ = args.skip(); 21 | const filename = args.next() orelse return error.NoFilename; 22 | 23 | const program = try fs.cwd().readFileAlloc(alloc, filename, 0xFFFFFFF); 24 | defer alloc.free(program); 25 | 26 | var module = Module.init(alloc, program); 27 | defer module.deinit(); 28 | try module.decode(); 29 | 30 | std.log.info("Imports:", .{}); 31 | for (module.imports.list.items, 0..) |import, i| { 32 | std.log.info("{}: import = {s}, tag = {}", .{ i, import.name, import.desc_tag }); 33 | } 34 | 35 | std.log.info("Exports:", .{}); 36 | for (module.exports.list.items, 0..) |exprt, i| { 37 | std.log.info("{}: export = {s}, tag = {}", .{ i, exprt.name, exprt.tag }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function cleanup { 6 | rm *.json 7 | rm *.wat 8 | rm *.wasm 9 | } 10 | 11 | trap cleanup EXIT 12 | 13 | wast2json --version 14 | 15 | wast2json test/testsuite/address.wast 16 | bin/testrunner address.json 17 | 18 | wast2json test/testsuite/align.wast 19 | bin/testrunner align.json 20 | 21 | wast2json test/testsuite/binary-leb128.wast 22 | bin/testrunner binary-leb128.json 23 | 24 | wast2json test/testsuite/binary.wast 25 | bin/testrunner binary.json 26 | 27 | wast2json test/testsuite/block.wast 28 | bin/testrunner block.json 29 | 30 | wast2json test/testsuite/br_if.wast 31 | bin/testrunner br_if.json 32 | 33 | wast2json test/testsuite/br.wast 34 | bin/testrunner br.json 35 | 36 | wast2json test/testsuite/br_table.wast 37 | bin/testrunner br_table.json 38 | 39 | wast2json test/testsuite/call_indirect.wast 40 | bin/testrunner call_indirect.json 41 | 42 | wast2json test/testsuite/call.wast 43 | bin/testrunner call.json 44 | 45 | wast2json test/testsuite/comments.wast 46 | bin/testrunner comments.json 47 | 48 | wast2json test/testsuite/const.wast 49 | bin/testrunner const.json 50 | 51 | wast2json test/testsuite/conversions.wast 52 | bin/testrunner conversions.json 53 | 54 | wast2json test/testsuite/custom.wast 55 | bin/testrunner custom.json 56 | 57 | wast2json test/testsuite/data.wast 58 | bin/testrunner data.json 59 | 60 | wast2json test/testsuite/elem.wast 61 | bin/testrunner elem.json 62 | 63 | wast2json test/testsuite/endianness.wast 64 | bin/testrunner endianness.json 65 | 66 | wast2json test/testsuite/exports.wast 67 | bin/testrunner exports.json 68 | 69 | wast2json test/testsuite/f32_bitwise.wast 70 | bin/testrunner f32_bitwise.json 71 | 72 | wast2json test/testsuite/f32_cmp.wast 73 | bin/testrunner f32_cmp.json 74 | 75 | wast2json test/testsuite/f32.wast 76 | bin/testrunner f32.json 77 | 78 | wast2json test/testsuite/f64_bitwise.wast 79 | bin/testrunner f64_bitwise.json 80 | 81 | wast2json test/testsuite/f64_cmp.wast 82 | bin/testrunner f64_cmp.json 83 | 84 | wast2json test/testsuite/f64.wast 85 | bin/testrunner f64.json 86 | 87 | wast2json test/testsuite/fac.wast 88 | bin/testrunner fac.json 89 | 90 | wast2json test/testsuite/float_exprs.wast 91 | bin/testrunner float_exprs.json 92 | 93 | wast2json test/testsuite/float_literals.wast 94 | bin/testrunner float_literals.json 95 | 96 | wast2json test/testsuite/float_memory.wast 97 | bin/testrunner float_memory.json 98 | 99 | wast2json test/testsuite/float_misc.wast 100 | bin/testrunner float_misc.json 101 | 102 | wast2json test/testsuite/forward.wast 103 | bin/testrunner forward.json 104 | 105 | wast2json test/testsuite/func_ptrs.wast 106 | bin/testrunner func_ptrs.json 107 | 108 | wast2json test/testsuite/func.wast 109 | bin/testrunner func.json 110 | 111 | wast2json test/testsuite/global.wast 112 | bin/testrunner global.json 113 | 114 | wast2json test/testsuite/i32.wast 115 | bin/testrunner i32.json 116 | 117 | wast2json test/testsuite/i64.wast 118 | bin/testrunner i64.json 119 | 120 | wast2json test/testsuite/if.wast 121 | bin/testrunner if.json 122 | 123 | wast2json test/testsuite/imports.wast 124 | bin/testrunner imports.json 125 | 126 | wast2json test/testsuite/inline-module.wast 127 | bin/testrunner inline-module.json 128 | 129 | wast2json test/testsuite/int_exprs.wast 130 | bin/testrunner int_exprs.json 131 | 132 | wast2json test/testsuite/int_literals.wast 133 | bin/testrunner int_literals.json 134 | 135 | wast2json test/testsuite/labels.wast 136 | bin/testrunner labels.json 137 | 138 | wast2json test/testsuite/left-to-right.wast 139 | bin/testrunner left-to-right.json 140 | 141 | wast2json test/testsuite/linking.wast 142 | bin/testrunner linking.json 143 | 144 | wast2json test/testsuite/load.wast 145 | bin/testrunner load.json 146 | 147 | wast2json test/testsuite/local_get.wast 148 | bin/testrunner local_get.json 149 | 150 | wast2json test/testsuite/local_set.wast 151 | bin/testrunner local_set.json 152 | 153 | wast2json test/testsuite/local_tee.wast 154 | bin/testrunner local_tee.json 155 | 156 | wast2json test/testsuite/loop.wast 157 | bin/testrunner loop.json 158 | 159 | wast2json test/testsuite/memory_grow.wast 160 | bin/testrunner memory_grow.json 161 | 162 | wast2json test/testsuite/memory_redundancy.wast 163 | bin/testrunner memory_redundancy.json 164 | 165 | wast2json test/testsuite/memory_size.wast 166 | bin/testrunner memory_size.json 167 | 168 | wast2json test/testsuite/memory_trap.wast 169 | bin/testrunner memory_trap.json 170 | 171 | wast2json test/testsuite/memory.wast 172 | bin/testrunner memory.json 173 | 174 | wast2json test/testsuite/names.wast 175 | bin/testrunner names.json 176 | 177 | wast2json test/testsuite/nop.wast 178 | bin/testrunner nop.json 179 | 180 | wast2json test/testsuite/return.wast 181 | bin/testrunner return.json 182 | 183 | wast2json test/testsuite/select.wast 184 | bin/testrunner select.json 185 | 186 | wast2json test/testsuite/skip-stack-guard-page.wast 187 | bin/testrunner skip-stack-guard-page.json 188 | 189 | wast2json test/testsuite/stack.wast 190 | bin/testrunner stack.json 191 | 192 | wast2json test/testsuite/start.wast 193 | bin/testrunner start.json 194 | 195 | wast2json test/testsuite/store.wast 196 | bin/testrunner store.json 197 | 198 | wast2json test/testsuite/switch.wast 199 | bin/testrunner switch.json 200 | 201 | wast2json test/testsuite/table.wast 202 | bin/testrunner table.json 203 | 204 | wast2json test/testsuite/token.wast 205 | bin/testrunner token.json 206 | 207 | wast2json test/testsuite/traps.wast 208 | bin/testrunner traps.json 209 | 210 | wast2json test/testsuite/type.wast 211 | bin/testrunner type.json 212 | 213 | wast2json test/testsuite/unreachable.wast 214 | bin/testrunner unreachable.json 215 | 216 | wast2json test/testsuite/unreached-invalid.wast 217 | bin/testrunner unreached-invalid.json 218 | 219 | wast2json test/testsuite/unwind.wast 220 | bin/testrunner unwind.json 221 | 222 | wast2json test/testsuite/utf8-custom-section-id.wast 223 | bin/testrunner utf8-custom-section-id.json 224 | 225 | wast2json test/testsuite/utf8-import-field.wast 226 | bin/testrunner utf8-import-field.json 227 | 228 | wast2json test/testsuite/utf8-import-module.wast 229 | bin/testrunner utf8-import-module.json 230 | 231 | wast2json test/testsuite/utf8-invalid-encoding.wast 232 | bin/testrunner utf8-invalid-encoding.json -------------------------------------------------------------------------------- /test/test.wasm: -------------------------------------------------------------------------------- 1 | asm`add 2 |  j -------------------------------------------------------------------------------- /test/test.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $add (param $lhs i32) (param $rhs i32) (result i32) 3 | local.get $lhs 4 | local.get $rhs 5 | i32.add) 6 | (export "add" (func $add)) 7 | ) -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # zware-gen 2 | 3 | Give me a `.wasm` and I will generate zig code for its interface (wrappers to call into the `.wasm` module and stubs for host functions). Implement 4 | the host functions correctly and your `.wasm` module will run 5 | 6 | # zware-run 7 | 8 | A command line runtime for `.wasm` files 9 | -------------------------------------------------------------------------------- /tools/zware-gen.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const fs = std.fs; 4 | const fmt = std.fmt; 5 | const process = std.process; 6 | const zware = @import("zware"); 7 | const ArrayList = std.ArrayList; 8 | const Module = zware.Module; 9 | const Store = zware.Store; 10 | const Instance = zware.Instance; 11 | const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; 12 | var gpa = GeneralPurposeAllocator(.{}){}; 13 | 14 | pub fn main() !void { 15 | defer _ = gpa.deinit(); 16 | var alloc = gpa.allocator(); 17 | 18 | var args = try process.argsWithAllocator(alloc); 19 | defer args.deinit(); 20 | _ = args.skip(); 21 | const filename = args.next() orelse return error.NoFilename; 22 | 23 | const program = try fs.cwd().readFileAlloc(alloc, filename, 0xFFFFFFF); 24 | defer alloc.free(program); 25 | 26 | var module = Module.init(alloc, program); 27 | defer module.deinit(); 28 | try module.decode(); 29 | 30 | const stdout_file = std.io.getStdOut().writer(); 31 | var bw = std.io.bufferedWriter(stdout_file); 32 | const stdout = bw.writer(); 33 | 34 | try stdout.print("const std = @import(\"std\");\n", .{}); 35 | try stdout.print("const zware = @import(\"zware\");\n\n", .{}); 36 | 37 | // Generate loader 38 | try stdout.print("pub fn initHostFunctions(store: *zware.Store) !void {{\n", .{}); 39 | for (module.functions.list.items) |function| { 40 | const import_index = function.import orelse continue; 41 | 42 | const function_import = module.imports.list.items[import_index]; 43 | 44 | const function_type = module.types.list.items[function.typeidx]; 45 | 46 | if (mem.eql(u8, function_import.module, "wasi_snapshot_preview1")) { 47 | try stdout.print("\ttry store.exposeHostFunction(\"{s}\", \"{s}\", zware.wasi.{s}, &[_]zware.ValType{{", .{ function_import.module, function_import.name, function_import.name }); 48 | } else { 49 | try stdout.print("\ttry store.exposeHostFunction(\"{s}\", \"{s}\", {s}, &[_]zware.ValType{{", .{ function_import.module, function_import.name, function_import.name }); 50 | } 51 | for (function_type.params, 0..) |param, i| { 52 | try stdout.print(".{s}", .{@tagName(param)}); 53 | 54 | if (i < function_type.params.len - 1) try stdout.print(", ", .{}); 55 | } 56 | try stdout.print("}}, &[_]zware.ValType{{", .{}); 57 | for (function_type.results, 0..) |result, i| { 58 | try stdout.print(".{s}", .{@tagName(result)}); 59 | 60 | if (i < function_type.results.len - 1) try stdout.print(", ", .{}); 61 | } 62 | try stdout.print("}});\n", .{}); 63 | } 64 | try stdout.print("}}\n\n", .{}); 65 | 66 | // Generate stubs 67 | for (module.functions.list.items) |function| { 68 | const import_index = function.import orelse continue; 69 | 70 | const function_import = module.imports.list.items[import_index]; 71 | 72 | const function_type = module.types.list.items[function.typeidx]; 73 | 74 | // Do not generate a stub for WASI functions 75 | if (mem.eql(u8, function_import.module, "wasi_snapshot_preview1")) continue; 76 | 77 | try stdout.print("pub fn {s}(vm: *zware.VirtualMachine) zware.WasmError!void {{\n", .{function_import.name}); 78 | 79 | if (function_type.params.len == 0 and function_type.results.len == 0) { 80 | try stdout.print("\t_ = vm;\n", .{}); 81 | } 82 | 83 | // Insert pops. Note our first argument to a function is the _last_ argument that will be popped off 84 | // the stack, so we pop the last argument first which is why this is working backwards through params. 85 | for (function_type.params, 0..) |_, i| { 86 | const j = function_type.params.len - 1 - i; 87 | const param = function_type.params[j]; 88 | try stdout.print("\tconst param{} = vm.popOperand({s});\n", .{ j, zigType(param) }); 89 | } 90 | 91 | try stdout.print("\tstd.debug.print(\"Unimplemented: {s}(", .{function_import.name}); 92 | for (function_type.params, 0..) |_, i| { 93 | try stdout.print("{{}}", .{}); 94 | 95 | if (i < function_type.params.len - 1) try stdout.print(", ", .{}); 96 | } 97 | try stdout.print(")\\n\", .{{", .{}); 98 | for (function_type.params, 0..) |_, i| { 99 | try stdout.print("param{}", .{i}); 100 | 101 | if (i < function_type.params.len - 1) try stdout.print(", ", .{}); 102 | } 103 | try stdout.print("}});\n", .{}); 104 | 105 | for (function_type.results) |_| { 106 | try stdout.print("\ttry vm.pushOperand(u64, 0);\n", .{}); 107 | } 108 | 109 | try stdout.print("\t@panic(\"Unimplemented: {s}\");\n", .{function_import.name}); 110 | try stdout.print("}}\n\n", .{}); 111 | } 112 | 113 | // Generate api 114 | try stdout.print("pub const Api = struct {{\n", .{}); 115 | try stdout.print("\tinstance: *zware.Instance,\n\n", .{}); 116 | try stdout.print("\tconst Self = @This();\n\n", .{}); 117 | try stdout.print("\tpub fn init(instance: *zware.Instance) Self {{\n", .{}); 118 | try stdout.print("\t\treturn .{{ .instance = instance }};\n", .{}); 119 | try stdout.print("\t}}\n\n", .{}); 120 | for (module.exports.list.items, 0..) |exprt, j| { 121 | if (exprt.tag != .Func) continue; 122 | 123 | const function = module.functions.list.items[exprt.index]; 124 | 125 | const function_type = module.types.list.items[function.typeidx]; 126 | 127 | // Emit function definition 128 | try stdout.print("\tpub fn {s}(self: *Self", .{exprt.name}); 129 | 130 | // Emit function params 131 | for (function_type.params, 0..) |param, i| { 132 | try stdout.print(", param{}: {s}", .{ i, zigType(param) }); 133 | } 134 | 135 | try stdout.print(") !", .{}); 136 | 137 | if (function_type.results.len == 0) { 138 | try stdout.print("void", .{}); 139 | } else if (function_type.results.len == 1) { 140 | try stdout.print("{s}", .{zigType(function_type.results[0])}); 141 | } else { 142 | try stdout.print("struct {{", .{}); 143 | 144 | for (function_type.results, 0..) |_, i| { 145 | try stdout.print("result{}: u64,\n", .{i}); 146 | } 147 | 148 | try stdout.print("}}", .{}); 149 | } 150 | 151 | try stdout.print(" {{\n", .{}); 152 | 153 | // Push params into input array 154 | try stdout.print("\t\tvar in = [_]u64{{", .{}); 155 | for (function_type.params, 0..) |param, i| { 156 | switch (param) { 157 | .I32 => try stdout.print("@bitCast(@as(i64, param{}))", .{i}), 158 | .I64 => try stdout.print("@bitCast(param{})", .{i}), 159 | .F32 => try stdout.print("@bitCast(@as(f64, param{}))", .{i}), 160 | .F64 => try stdout.print("@bitCast(param{})", .{i}), 161 | .V128 => try stdout.print("FIXME{}", .{i}), 162 | .FuncRef => try stdout.print("@bitCast(param{})", .{i}), 163 | .ExternRef => try stdout.print("@bitCast(param{})", .{i}), 164 | } 165 | 166 | if (i < function_type.params.len - 1) try stdout.print(", ", .{}); 167 | } 168 | try stdout.print("}};\n", .{}); 169 | 170 | try stdout.print("\t\tvar out = [_]u64{{", .{}); 171 | for (function_type.results, 0..) |_, i| { 172 | try stdout.print("0", .{}); 173 | if (i < function_type.results.len - 1) try stdout.print(", ", .{}); 174 | } 175 | try stdout.print("}};\n", .{}); 176 | 177 | try stdout.print("\t\ttry self.instance.invoke(\"{s}\", in[0..], out[0..], .{{}});\n", .{exprt.name}); 178 | 179 | // Return results 180 | if (function_type.results.len == 0) { 181 | // 182 | } else if (function_type.results.len == 1) { 183 | try stdout.print("\t\treturn ", .{}); 184 | const i = 0; 185 | 186 | switch (function_type.results[0]) { 187 | .I32 => try stdout.print("@bitCast(@as(u32, @truncate(out[{}])))", .{i}), 188 | .I64 => try stdout.print("@bitCast(out[{}])", .{i}), 189 | .F32 => try stdout.print("@bitCast(@as(u32, @truncate(out[{}])))", .{i}), 190 | .F64 => try stdout.print("@bitCast(out[{}])", .{i}), 191 | .V128 => try stdout.print("FIXME{}", .{i}), // FIXME 192 | .FuncRef => try stdout.print("@bitCast(out[{}])", .{i}), // FIXME 193 | .ExternRef => try stdout.print("@bitCast(out[{}])", .{i}), // FIXME 194 | } 195 | 196 | try stdout.print(";\n", .{}); 197 | } else { 198 | try stdout.print("\treturn .{{", .{}); 199 | 200 | for (function_type.results, 0..) |result, i| { 201 | try stdout.print(".result{} = ", .{i}); 202 | 203 | switch (result) { 204 | .I32 => try stdout.print("@bitCast(@as(u32, @truncate(out[{}])))", .{i}), 205 | .I64 => try stdout.print("@bitCast(out[{}])", .{i}), 206 | .F32 => try stdout.print("@bitCast(@as(u32, @truncate(out[{}])))", .{i}), 207 | .F64 => try stdout.print("@bitCast(out[{}])", .{i}), 208 | .V128 => try stdout.print("FIXME{}", .{i}), // FIXME 209 | .FuncRef => try stdout.print("@bitCast(out[{}])", .{i}), // FIXME 210 | .ExternRef => try stdout.print("@bitCast(out[{}])", .{i}), // FIXME 211 | } 212 | 213 | try stdout.print(",\n", .{}); 214 | } 215 | 216 | try stdout.print("}};\n", .{}); 217 | } 218 | 219 | try stdout.print("\t}}\n", .{}); 220 | 221 | if (j < module.exports.list.items.len - 1) try stdout.print("\n", .{}); 222 | } 223 | try stdout.print("}};\n\n", .{}); 224 | 225 | try bw.flush(); 226 | } 227 | 228 | fn zigType(v: zware.ValType) []const u8 { 229 | return switch (v) { 230 | .I32 => "i32", 231 | .I64 => "i64", 232 | .F32 => "f32", 233 | .F64 => "f64", 234 | .V128 => "u128", 235 | .FuncRef => "anyopaque", 236 | .ExternRef => "anyopaque", 237 | }; 238 | } 239 | -------------------------------------------------------------------------------- /tools/zware-run.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zware = @import("zware"); 3 | 4 | fn oom(e: error{OutOfMemory}) noreturn { 5 | @panic(@errorName(e)); 6 | } 7 | 8 | const ImportStub = struct { 9 | module: []const u8, 10 | name: []const u8, 11 | type: zware.FuncType, 12 | }; 13 | 14 | const enable_leak_detection = false; 15 | const global = struct { 16 | var allocator_instance = if (enable_leak_detection) std.heap.GeneralPurposeAllocator(.{ 17 | .retain_metadata = true, 18 | //.verbose_log = true, 19 | }){} else std.heap.ArenaAllocator.init(std.heap.page_allocator); 20 | const alloc = allocator_instance.allocator(); 21 | var import_stubs: std.ArrayListUnmanaged(ImportStub) = .{}; 22 | }; 23 | 24 | pub fn main() !void { 25 | try main2(); 26 | if (enable_leak_detection) { 27 | switch (global.allocator_instance.deinit()) { 28 | .ok => {}, 29 | .leak => @panic("memory leak"), 30 | } 31 | } 32 | } 33 | fn main2() !void { 34 | defer global.import_stubs.deinit(global.alloc); 35 | 36 | const full_cmdline = try std.process.argsAlloc(global.alloc); 37 | defer std.process.argsFree(global.alloc, full_cmdline); 38 | if (full_cmdline.len <= 1) { 39 | try std.io.getStdErr().writer().writeAll("Usage: zware-run FILE.wasm FUNCTION\n"); 40 | std.process.exit(0xff); 41 | } 42 | 43 | const pos_args = full_cmdline[1..]; 44 | if (pos_args.len != 2) { 45 | std.log.err("expected {} positional cmdline arguments but got {}", .{ 2, pos_args.len }); 46 | std.process.exit(0xff); 47 | } 48 | const wasm_path = pos_args[0]; 49 | const wasm_func_name = pos_args[1]; 50 | 51 | var store = zware.Store.init(global.alloc); 52 | defer store.deinit(); 53 | 54 | const wasm_content = content_blk: { 55 | var file = std.fs.cwd().openFile(wasm_path, .{}) catch |e| { 56 | std.log.err("failed to open '{s}': {s}", .{ wasm_path, @errorName(e) }); 57 | std.process.exit(0xff); 58 | }; 59 | defer file.close(); 60 | break :content_blk try file.readToEndAlloc(global.alloc, std.math.maxInt(usize)); 61 | }; 62 | defer global.alloc.free(wasm_content); 63 | 64 | var module = zware.Module.init(global.alloc, wasm_content); 65 | defer module.deinit(); 66 | try module.decode(); 67 | 68 | const export_funcidx = try getExportFunction(&module, wasm_func_name); 69 | const export_funcdef = module.functions.list.items[export_funcidx]; 70 | const export_functype = try module.types.lookup(export_funcdef.typeidx); 71 | if (export_functype.params.len != 0) { 72 | std.log.err("calling a function with parameters is not implemented", .{}); 73 | std.process.exit(0xff); 74 | } 75 | 76 | var instance = zware.Instance.init(global.alloc, &store, module); 77 | defer if (enable_leak_detection) instance.deinit(); 78 | 79 | try populateMissingImports(&store, &module); 80 | 81 | var zware_error: zware.Error = undefined; 82 | instance.instantiateWithError(&zware_error) catch |err| switch (err) { 83 | error.SeeContext => { 84 | std.log.err("failed to instantiate the module: {}", .{zware_error}); 85 | std.process.exit(0xff); 86 | }, 87 | else => |e| return e, 88 | }; 89 | defer instance.deinit(); 90 | 91 | var in = [_]u64{}; 92 | const out_args = try global.alloc.alloc(u64, export_functype.results.len); 93 | defer global.alloc.free(out_args); 94 | try instance.invoke(wasm_func_name, &in, out_args, .{}); 95 | std.log.info("{} output(s)", .{out_args.len}); 96 | for (out_args, 0..) |out_arg, out_index| { 97 | std.log.info("output {} {}", .{ out_index, fmtValue(export_functype.results[out_index], out_arg) }); 98 | } 99 | } 100 | 101 | fn getExportFunction(module: *const zware.Module, func_name: []const u8) !usize { 102 | return module.getExport(.Func, func_name) catch |err| switch (err) { 103 | error.ExportNotFound => { 104 | const stderr = std.io.getStdErr().writer(); 105 | var export_func_count: usize = 0; 106 | for (module.exports.list.items) |exp| { 107 | if (exp.tag == .Func) { 108 | export_func_count += 1; 109 | } 110 | } 111 | if (export_func_count == 0) { 112 | try stderr.print("error: this wasm binary has no function exports\n", .{}); 113 | } else { 114 | try stderr.print( 115 | "error: no export function named '{s}', pick from one of the following {} export(s):\n", 116 | .{ func_name, export_func_count }, 117 | ); 118 | for (module.exports.list.items) |exp| { 119 | if (exp.tag == .Func) { 120 | try stderr.print(" {s}\n", .{exp.name}); 121 | } 122 | } 123 | } 124 | std.process.exit(0xff); 125 | }, 126 | }; 127 | } 128 | 129 | fn populateMissingImports(store: *zware.Store, module: *const zware.Module) !void { 130 | var import_funcidx: u32 = 0; 131 | var import_memidx: u32 = 0; 132 | for (module.imports.list.items, 0..) |import, import_index| { 133 | defer switch (import.desc_tag) { 134 | .Func => import_funcidx += 1, 135 | .Mem => import_memidx += 1, 136 | else => @panic("todo"), 137 | }; 138 | 139 | if (store.import(import.module, import.name, import.desc_tag)) |_| { 140 | continue; 141 | } else |err| switch (err) { 142 | error.ImportNotFound => {}, 143 | } 144 | 145 | switch (import.desc_tag) { 146 | .Func => { 147 | const funcdef = module.functions.list.items[import_funcidx]; 148 | std.debug.assert(funcdef.import.? == import_index); 149 | const functype = try module.types.lookup(funcdef.typeidx); 150 | global.import_stubs.append(global.alloc, .{ 151 | .module = import.module, 152 | .name = import.name, 153 | .type = functype, 154 | }) catch |e| oom(e); 155 | store.exposeHostFunction( 156 | import.module, 157 | import.name, 158 | onMissingImport, 159 | global.import_stubs.items.len - 1, 160 | functype.params, 161 | functype.results, 162 | ) catch |e2| oom(e2); 163 | }, 164 | .Mem => { 165 | const memdef = module.memories.list.items[import_memidx]; 166 | std.debug.assert(memdef.import.? == import_index); 167 | try store.exposeMemory(import.module, import.name, memdef.limits.min, memdef.limits.max); 168 | }, 169 | else => |tag| std.debug.panic("todo: handle import {s}", .{@tagName(tag)}), 170 | } 171 | } 172 | } 173 | 174 | fn onMissingImport(vm: *zware.VirtualMachine, context: usize) zware.WasmError!void { 175 | const stub = global.import_stubs.items[context]; 176 | std.log.info("import function '{s}.{s}' called", .{ stub.module, stub.name }); 177 | for (stub.type.params, 0..) |param_type, i| { 178 | const value = vm.popAnyOperand(); 179 | std.log.info(" param {} {}", .{ i, fmtValue(param_type, value) }); 180 | } 181 | for (stub.type.results, 0..) |result_type, i| { 182 | std.log.info(" result {} {}", .{ i, fmtValue(result_type, 0) }); 183 | try vm.pushOperand(u64, 0); 184 | } 185 | } 186 | 187 | pub fn Native(comptime self: zware.ValType) type { 188 | return switch (self) { 189 | .I32 => i32, 190 | .I64 => i64, 191 | .F32 => f32, 192 | .F64 => f64, 193 | .V128 => u64, 194 | .FuncRef => u64, 195 | .ExternRef => u64, 196 | }; 197 | } 198 | 199 | fn cast(comptime val_type: zware.ValType, value: u64) Native(val_type) { 200 | return switch (val_type) { 201 | .I32 => @bitCast(@as(u32, @intCast(value))), 202 | .I64 => @bitCast(value), 203 | .F32 => @bitCast(@as(u32, @intCast(value))), 204 | .F64 => @bitCast(value), 205 | .V128 => value, 206 | .FuncRef => value, 207 | .ExternRef => value, 208 | }; 209 | } 210 | 211 | fn fmtValue(val_type: zware.ValType, value: u64) FmtValue { 212 | return .{ .type = val_type, .value = value }; 213 | } 214 | const FmtValue = struct { 215 | type: zware.ValType, 216 | value: u64, 217 | pub fn format( 218 | self: FmtValue, 219 | comptime fmt: []const u8, 220 | options: std.fmt.FormatOptions, 221 | writer: anytype, 222 | ) !void { 223 | _ = fmt; 224 | _ = options; 225 | switch (self.type) { 226 | inline else => |t2| try writer.print("({s}) {}", .{ @tagName(t2), cast(t2, self.value) }), 227 | } 228 | } 229 | }; 230 | --------------------------------------------------------------------------------