├── .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 |

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 | *( A F@A AF@A Ak Ak j
--------------------------------------------------------------------------------
/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 | *( A F@A AF@A Ak Ak j
--------------------------------------------------------------------------------
/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 | *( A F@A AF@A Ak Ak j
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------