├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── examples ├── instance.zig ├── memory.zig └── qjs.wasm ├── gyro.zzz └── src └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | .gyro 2 | gyro.lock 3 | deps.zig 4 | zig-cache 5 | zig-out 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jakub Konka & Luuk de Graam 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasmer-zig 2 | 3 | Zig bindings for the [Wasmer] WebAssembly runtime. 4 | 5 | [Wasmer]: https://github.com/wasmerio/wasmer 6 | 7 | ## Disclaimer 8 | 9 | This is a work-in-progress library so things will change without notice! Furthermore, building 10 | this library and examples requires the latest nightly version of Zig `0.8.0`, and [`gyro`] package 11 | manager. 12 | 13 | [`gyro`]: https://github.com/mattnite/gyro 14 | 15 | ## Building 16 | 17 | This library consumes the Wasmer's C API which is auto-installed with each release of Wasmer. 18 | The current stable release of Wasmer this embedding relies on is [v1.0.2]. Therefore, make sure 19 | you have `wasmer` binary installed and in your `PATH`. 20 | 21 | [v1.0.2]: https://github.com/wasmerio/wasmer/releases/tag/1.0.2 22 | 23 | To build this library, simply run 24 | 25 | ``` 26 | gyro build 27 | ``` 28 | 29 | Tests can be invoked as follows 30 | 31 | ``` 32 | gyro build test 33 | ``` 34 | 35 | ## Running examples 36 | 37 | You can find a few examples of how this library can be used to embed Wasmer in your app and 38 | instantiate Wasm modules in the `examples/` dir. You can run any example with 39 | 40 | ``` 41 | gyro build run -Dexample= 42 | ``` 43 | 44 | In particular, you will find there `examples/instance.zig` which is a Zig port of Wasmer's [instance.c] 45 | example. 46 | 47 | [instance.c]: https://github.com/wasmerio/wasmer/blob/master/lib/c-api/examples/instance.c 48 | 49 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pkgs = @import("deps.zig").pkgs; 3 | const mem = std.mem; 4 | 5 | fn detectWasmerLibDir(b: *std.build.Builder) ?[]const u8 { 6 | const argv = &[_][]const u8{ "wasmer", "config", "--libdir" }; 7 | const result = std.ChildProcess.exec(.{ 8 | .allocator = b.allocator, 9 | .argv = argv, 10 | }) catch return null; 11 | 12 | if (result.stderr.len != 0 or result.term.Exited != 0) return null; 13 | 14 | const lib_dir = mem.trimRight(u8, result.stdout, "\r\n"); 15 | return lib_dir; 16 | } 17 | 18 | pub fn build(b: *std.build.Builder) !void { 19 | // Standard release options allow the person running `zig build` to select 20 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 21 | const mode = b.standardReleaseOptions(); 22 | 23 | // Try detecting Wasmer lib dir. 24 | const wasmer_lib_dir = detectWasmerLibDir(b); 25 | 26 | const lib = b.addStaticLibrary("wasmer-zig", "src/main.zig"); 27 | lib.setBuildMode(mode); 28 | lib.addPackage(pkgs.wasm); 29 | lib.install(); 30 | 31 | var main_tests = b.addTest("src/main.zig"); 32 | main_tests.setBuildMode(mode); 33 | main_tests.addPackage(pkgs.wasm); 34 | if (wasmer_lib_dir) |lib_dir| { 35 | main_tests.addRPath(lib_dir); 36 | main_tests.addLibPath(lib_dir); 37 | } 38 | main_tests.linkSystemLibrary("wasmer"); 39 | main_tests.linkLibC(); 40 | 41 | const test_step = b.step("test", "Run library tests"); 42 | test_step.dependOn(&main_tests.step); 43 | 44 | const example = b.option([]const u8, "example", "Specify example to run from examples/ dir"); 45 | const example_path = example_path: { 46 | const basename = example orelse "instance"; 47 | const with_ext = try std.fmt.allocPrint(b.allocator, "{s}.zig", .{basename}); 48 | const full_path = try std.fs.path.join(b.allocator, &[_][]const u8{ "examples", with_ext }); 49 | break :example_path full_path; 50 | }; 51 | 52 | const executable = b.addExecutable(example orelse "instance", example_path); 53 | executable.setBuildMode(mode); 54 | executable.addPackage(.{ 55 | .name = "wasmer", 56 | .path = .{.path = "src/main.zig"}, 57 | .dependencies = &.{pkgs.wasm}, 58 | }); 59 | if (wasmer_lib_dir) |lib_dir| { 60 | executable.addRPath(lib_dir); 61 | executable.addLibPath(lib_dir); 62 | } 63 | executable.linkSystemLibrary("wasmer"); 64 | executable.linkLibC(); 65 | executable.step.dependOn(b.getInstallStep()); 66 | 67 | const run_executable = executable.run(); 68 | const run_step = b.step("run", "Run an example specified with -Dexample (defaults to examples/instance.zig)"); 69 | run_step.dependOn(&run_executable.step); 70 | } 71 | -------------------------------------------------------------------------------- /examples/instance.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const wasmer = @import("wasmer"); 3 | const assert = std.debug.assert; 4 | 5 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 6 | const allocator = gpa.allocator(); 7 | 8 | const wat = 9 | \\(module 10 | \\ (type $add_one_t (func (param i32) (result i32))) 11 | \\ (func $add_one_f (type $add_one_t) (param $value i32) (result i32) 12 | \\ local.get $value 13 | \\ i32.const 1 14 | \\ i32.add) 15 | \\ (export "add_one" (func $add_one_f))) 16 | ; 17 | 18 | pub fn main() !void { 19 | run () catch |err| { 20 | const err_msg = try wasmer.lastError(std.heap.c_allocator); 21 | defer std.heap.c_allocator.free(err_msg); 22 | 23 | std.log.err("{s}", .{err_msg}); 24 | 25 | return err; 26 | }; 27 | } 28 | 29 | fn run() !void { 30 | var wasm_bytes = try wasmer.watToWasm(wat); 31 | defer wasm_bytes.deinit(); 32 | 33 | std.log.info("creating the store...", .{}); 34 | 35 | const engine = try wasmer.Engine.init(); 36 | defer engine.deinit(); 37 | const store = try wasmer.Store.init(engine); 38 | defer store.deinit(); 39 | 40 | std.log.info("compiling module...", .{}); 41 | 42 | const module = try wasmer.Module.init(store, wasm_bytes.toSlice()); 43 | 44 | defer module.deinit(); 45 | 46 | std.log.info("instantiating module...", .{}); 47 | 48 | const instance = try wasmer.Instance.init(store, module, &.{}); 49 | defer instance.deinit(); 50 | 51 | std.log.info("retrieving exports...", .{}); 52 | 53 | const add_one = instance.getExportFunc(module, "add_one") orelse { 54 | std.log.err("failed to retrieve \"add_one\" export from instance", .{}); 55 | return error.ExportNotFound; 56 | }; 57 | defer add_one.deinit(); 58 | 59 | std.log.info("calling \"add_one\" export fn...", .{}); 60 | 61 | const res = try add_one.call(i32, .{@as(i32, 1)}); 62 | assert(res == 2); 63 | 64 | std.log.info("result of \"add_one(1)\" = {}", .{res}); 65 | } 66 | -------------------------------------------------------------------------------- /examples/memory.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const wasmer = @import("wasmer"); 3 | const assert = std.debug.assert; 4 | 5 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 6 | const allocator = gpa.allocator(); 7 | 8 | const wat = 9 | \\(module 10 | \\ (type $mem_size_t (func (result i32))) 11 | \\ (type $get_at_t (func (param i32) (result i32))) 12 | \\ (type $set_at_t (func (param i32) (param i32))) 13 | \\ (memory $mem 1) 14 | \\ (func $get_at (type $get_at_t) (param $idx i32) (result i32) 15 | \\ (i32.load (local.get $idx))) 16 | \\ (func $set_at (type $set_at_t) (param $idx i32) (param $val i32) 17 | \\ (i32.store (local.get $idx) (local.get $val))) 18 | \\ (func $mem_size (type $mem_size_t) (result i32) 19 | \\ (memory.size)) 20 | \\ (export "get_at" (func $get_at)) 21 | \\ (export "set_at" (func $set_at)) 22 | \\ (export "mem_size" (func $mem_size)) 23 | \\ (export "memory" (memory $mem))) 24 | ; 25 | 26 | pub fn main() !void { 27 | run () catch |err| { 28 | const err_msg = try wasmer.lastError(std.heap.c_allocator); 29 | defer std.heap.c_allocator.free(err_msg); 30 | 31 | std.log.err("{s}", .{err_msg}); 32 | 33 | return err; 34 | }; 35 | } 36 | 37 | pub fn run() !void { 38 | var wasm_bytes = try wasmer.watToWasm(wat); 39 | defer wasm_bytes.deinit(); 40 | 41 | std.log.info("creating the store...", .{}); 42 | 43 | const engine = try wasmer.Engine.init(); 44 | defer engine.deinit(); 45 | const store = try wasmer.Store.init(engine); 46 | defer store.deinit(); 47 | 48 | std.log.info("compiling module...", .{}); 49 | 50 | const module = try wasmer.Module.init(store, wasm_bytes.toSlice()); 51 | defer module.deinit(); 52 | 53 | std.log.info("instantiating module...", .{}); 54 | 55 | const instance = try wasmer.Instance.init(store, module, &.{}); 56 | defer instance.deinit(); 57 | 58 | std.log.info("retrieving exports...", .{}); 59 | 60 | const get_at = instance.getExportFunc(module, "get_at") orelse { 61 | std.log.err("failed to retrieve \"get_at\" export from instance", .{}); 62 | return error.ExportNotFound; 63 | }; 64 | defer get_at.deinit(); 65 | const set_at = instance.getExportFunc(module, "set_at") orelse { 66 | std.log.err("failed to retrieve \"set_at\" export from instance", .{}); 67 | return error.ExportNotFound; 68 | }; 69 | defer set_at.deinit(); 70 | const mem_size = instance.getExportFunc(module, "mem_size") orelse { 71 | std.log.err("failed to retrieve \"mem_size\" export from instance", .{}); 72 | return error.ExportNotFound; 73 | }; 74 | defer mem_size.deinit(); 75 | 76 | const memory = instance.getExportMem(module, "memory") orelse { 77 | std.log.err("failed to retrieve \"memory\" export from instance", .{}); 78 | return error.ExportNotFound; 79 | }; 80 | defer memory.deinit(); 81 | 82 | memory.grow(2) catch |err| { 83 | std.log.err("Error growing memory!", .{}); 84 | return err; 85 | }; 86 | 87 | const new_pages = memory.pages(); 88 | const new_size = memory.size(); 89 | std.log.info("New memory size (byted)/(pages): {d}/{d}", .{new_size, new_pages}); 90 | 91 | const mem_addr: i32 = 0x2220; 92 | const val: i32 = 0xFEFEFFE; 93 | 94 | set_at.call(void, .{ mem_addr, val }) catch |err| { 95 | std.log.err("Failed to call \"set_at\": {s}", .{err}); 96 | return err; 97 | }; 98 | 99 | const result = get_at.call(i32, .{mem_addr}) catch |err| { 100 | std.log.err("Failed to call \"get_at\": {s}", .{err}); 101 | return err; 102 | }; 103 | 104 | std.log.info("Vale at 0x{x:0>4}: {d}", .{ mem_addr, result }); 105 | } 106 | -------------------------------------------------------------------------------- /examples/qjs.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zigwasm/wasmer-zig/29d87f563d2b1ed88d680e94bdea37c355e6e4c2/examples/qjs.wasm -------------------------------------------------------------------------------- /gyro.zzz: -------------------------------------------------------------------------------- 1 | pkgs: 2 | wasmer: 3 | version: 0.0.0 4 | root: src/main.zig 5 | description: Zig bindings for the Wasmer WebAssembly runtime 6 | license: MIT 7 | source_url: "https://github.com/zigwasm/wasmer-zig" 8 | deps: 9 | wasm: 10 | src: 11 | github: 12 | user: zigwasm 13 | repo: wasm-zig 14 | ref: main 15 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const wasm = @import("wasm"); 3 | 4 | // Re-exports 5 | pub const ByteVec = wasm.ByteVec; 6 | pub const Engine = wasm.Engine; 7 | pub const Store = wasm.Store; 8 | pub const Module = wasm.Module; 9 | pub const Instance = wasm.Instance; 10 | pub const Extern = wasm.Extern; 11 | pub const Func = wasm.Func; 12 | 13 | pub fn lastError(allocator: std.mem.Allocator) ![:0]u8 { 14 | const buf_len = @intCast(usize, wasmer_last_error_length()); 15 | const buf = try allocator.alloc(u8, buf_len); 16 | _ = wasmer_last_error_message(buf.ptr, @intCast(c_int, buf_len)); 17 | return buf[0..buf_len-1:0]; 18 | } 19 | pub extern "c" fn wasmer_last_error_length() c_int; 20 | pub extern "c" fn wasmer_last_error_message([*]const u8, c_int) c_int; 21 | 22 | // Helpers 23 | 24 | pub fn watToWasm(wat: []const u8) !ByteVec { 25 | var wat_bytes = ByteVec.fromSlice(wat); 26 | defer wat_bytes.deinit(); 27 | 28 | var wasm_bytes: ByteVec = undefined; 29 | wat2wasm(&wat_bytes, &wasm_bytes); 30 | 31 | if(wasm_bytes.size == 0) return error.WatParse; 32 | return wasm_bytes; 33 | } 34 | 35 | extern "c" fn wat2wasm(*const ByteVec, *ByteVec) void; 36 | 37 | test "" { 38 | _ = std.testing.refAllDecls(@This()); 39 | } 40 | --------------------------------------------------------------------------------