diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98113a3..90081f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,8 @@ on: pull_request: branches: - main + schedule: + - cron: '0 0 * * *' # run at 00:00 UTC jobs: build_nix: diff --git a/.gitignore b/.gitignore index dc7010e..8ea72a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ zig-cache +zig-* +wasmtime* .DS_Store *.swp .gyro diff --git a/README.md b/README.md index 8ee0c29..49ba880 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # wasmtime-zig -[github](https://github.com/kubkon/wasmtime-zig) +[github](https://github.com/kubkon/wasmtime-zig) [build status](https://github.com/kubkon/wasmtime-zig/actions?query=branch%3Amaster) Zig embedding of [Wasmtime] @@ -13,7 +13,7 @@ but expected, and things might just not work as expected yet. ## Building -To build this library, you will need Zig nightly 0.8.0, as well as [`gyro`] package manager. +To build this library, you will need Zig nightly 0.10.0, as well as [`gyro`] package manager (`v0.7.0`). [`gyro`]: https://github.com/mattnite/gyro diff --git a/build.zig b/build.zig index b4755e4..9c5ab2b 100644 --- a/build.zig +++ b/build.zig @@ -30,13 +30,22 @@ pub fn build(b: *std.build.Builder) !void { simple_exe.setBuildMode(mode); simple_exe.addPackage(.{ .name = "wasmtime", - .path = "src/main.zig", + .source = .{ .path = "src/main.zig" }, .dependencies = &.{pkgs.wasm}, }); - if (builtin.os.tag == .windows) { - simple_exe.linkSystemLibrary("wasmtime.dll"); - } else { - simple_exe.linkSystemLibrary("wasmtime"); + switch (builtin.os.tag) { + .windows => { + simple_exe.linkSystemLibrary("wasmtime.dll"); + simple_exe.linkSystemLibrary("unwind"); + }, + .linux => { + simple_exe.linkSystemLibrary("wasmtime"); + simple_exe.linkSystemLibrary("unwind"); + }, + .macos => { + simple_exe.linkSystemLibrary("wasmtime"); + }, + else => unreachable, } simple_exe.linkLibC(); simple_exe.step.dependOn(b.getInstallStep()); diff --git a/ci/install_gyro b/ci/install_gyro new file mode 100755 index 0000000..7adedd4 --- /dev/null +++ b/ci/install_gyro @@ -0,0 +1,10 @@ +#!/bin/sh + +set -x +set -e + +GYRO_VERSION=$1 +GYRO=$2 + +curl -sL "https://github.com/mattnite/gyro/releases/download/$GYRO_VERSION/$GYRO.tar.gz" -o "$GYRO.tar.gz" +tar xf "$GYRO.tar.gz" diff --git a/ci/install_wasmtime b/ci/install_wasmtime new file mode 100755 index 0000000..7b1f174 --- /dev/null +++ b/ci/install_wasmtime @@ -0,0 +1,10 @@ +#!/bin/sh + +set -x +set -e + +WASMTIME_VERSION=$1 +WASMTIME=$2 + +curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/$WASMTIME_VERSION/$WASMTIME.tar.xz" -o "$WASMTIME.tar.xz" +tar xf "$WASMTIME.tar.xz" diff --git a/ci/linux_ci b/ci/linux_ci index 8f4246b..aeaf6ec 100755 --- a/ci/linux_ci +++ b/ci/linux_ci @@ -3,28 +3,29 @@ set -x set -e -ZIG="zig-linux-x86_64-0.8.0-dev.2667+44de88498" -WASMTIME_VERSION="v0.24.0" -WASMTIME="wasmtime-$WASMTIME_VERSION-x86_64-linux-c-api" -GYRO_VERSION="0.2.3" -GYRO="gyro-$GYRO_VERSION-linux-x86_64" +ARCH="x86_64" +OS="linux" +ARCH_OS="${ARCH}-${OS}" +OS_ARCH="${OS}-${ARCH}" -wget -nv "https://ziglang.org/builds/$ZIG.tar.xz" +ZIG_SRC="https://ziglang.org/download/index.json" +ZIG_VERSION=$(curl -s ${ZIG_SRC} | jq -r '.master.version') +ZIG="zig-${OS_ARCH}-$ZIG_VERSION" +ZIG_MASTER=$(curl -s ${ZIG_SRC} | jq -r --arg ARCH_OS "$ARCH_OS" '.master[$ARCH_OS].tarball') + +WASMTIME_VERSION="v0.38.1" +WASMTIME="wasmtime-$WASMTIME_VERSION-${ARCH_OS}-c-api" +GYRO_VERSION="0.7.0" +GYRO="gyro-$GYRO_VERSION-${OS_ARCH}" + +# TODO: make this nice +curl -sL "${ZIG_MASTER}" -o "$ZIG.tar.xz" tar xf "$ZIG.tar.xz" export PATH="$(pwd)/$ZIG:$PATH" -wget -nv "https://github.com/bytecodealliance/wasmtime/releases/download/$WASMTIME_VERSION/$WASMTIME.tar.xz" -tar xf "$WASMTIME.tar.xz" +./ci/install_wasmtime $WASMTIME_VERSION $WASMTIME -wget -nv "https://github.com/mattnite/gyro/releases/download/$GYRO_VERSION/$GYRO.tar.gz" -tar xf "$GYRO.tar.gz" +./ci/install_gyro $GYRO_VERSION $GYRO export PATH="$(pwd)/$GYRO/bin:$PATH" -gyro build -gyro build test -gyro build run -Dexample=simple --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=gcd --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=linking --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=memory --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=interrupt --search-prefix "$(pwd)/$WASMTIME" - +./ci/run_gyro $WASMTIME diff --git a/ci/macos_ci b/ci/macos_ci index e41c5fc..dbac8c8 100755 --- a/ci/macos_ci +++ b/ci/macos_ci @@ -3,28 +3,28 @@ set -x set -e -ZIG="zig-macos-x86_64-0.8.0-dev.2670+d8d92dafe" -WASMTIME_VERSION="v0.24.0" +ARCH="x86_64" +OS="macos" +ARCH_OS="${ARCH}-${OS}" +OS_ARCH="${OS}-${ARCH}" + +ZIG_SRC="https://ziglang.org/download/index.json" +ZIG_VERSION=$(curl -s ${ZIG_SRC} | jq -r '.master.version') +ZIG="zig-${OS_ARCH}-$ZIG_VERSION" +ZIG_MASTER=$(curl -s ${ZIG_SRC} | jq -r --arg ARCH_OS "$ARCH_OS" '.master[$ARCH_OS].tarball') + +WASMTIME_VERSION="v0.38.1" WASMTIME="wasmtime-$WASMTIME_VERSION-x86_64-macos-c-api" -GYRO_VERSION="0.2.3" +GYRO_VERSION="0.7.0" GYRO="gyro-$GYRO_VERSION-macos-x86_64" -curl -L "https://ziglang.org/builds/$ZIG.tar.xz" -o "$ZIG.tar.xz" +curl -sL "${ZIG_MASTER}" -o "$ZIG.tar.xz" tar xf "$ZIG.tar.xz" export PATH="$(pwd)/$ZIG:$PATH" -curl -L "https://github.com/bytecodealliance/wasmtime/releases/download/$WASMTIME_VERSION/$WASMTIME.tar.xz" -o "$WASMTIME.tar.xz" -tar xf "$WASMTIME.tar.xz" +./ci/install_wasmtime $WASMTIME_VERSION $WASMTIME -curl -L "https://github.com/mattnite/gyro/releases/download/$GYRO_VERSION/$GYRO.tar.gz" -o "$GYRO.tar.gz" -tar xf "$GYRO.tar.gz" +./ci/install_gyro $GYRO_VERSION $GYRO export PATH="$(pwd)/$GYRO/bin:$PATH" -gyro build -gyro build test -gyro build run -Dexample=simple --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=gcd --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=linking --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=memory --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=interrupt --search-prefix "$(pwd)/$WASMTIME" - +./ci/run_gyro $WASMTIME diff --git a/ci/run_gyro b/ci/run_gyro new file mode 100755 index 0000000..c01870f --- /dev/null +++ b/ci/run_gyro @@ -0,0 +1,14 @@ +#!/bin/sh + +set -x +set -e + +WASMTIME=$1 + +gyro build +gyro build test +gyro build run -Dexample=simple --search-prefix "$(pwd)/$WASMTIME" +gyro build run -Dexample=gcd --search-prefix "$(pwd)/$WASMTIME" +gyro build run -Dexample=linking --search-prefix "$(pwd)/$WASMTIME" +gyro build run -Dexample=memory --search-prefix "$(pwd)/$WASMTIME" +gyro build run -Dexample=interrupt --search-prefix "$(pwd)/$WASMTIME" diff --git a/ci/win_ci b/ci/win_ci index 531f81c..00ab3b5 100755 --- a/ci/win_ci +++ b/ci/win_ci @@ -3,28 +3,30 @@ set -x set -e -ZIG="zig-windows-x86_64-0.8.0-dev.2667+44de88498" -WASMTIME_VERSION="v0.24.0" -WASMTIME="wasmtime-$WASMTIME_VERSION-x86_64-windows-c-api" -GYRO_VERSION="0.2.3" -GYRO="gyro-$GYRO_VERSION-windows-x86_64" +ARCH="x86_64" +OS="windows" +ARCH_OS="${ARCH}-${OS}" +OS_ARCH="${OS}-${ARCH}" -curl -L "https://ziglang.org/builds/$ZIG.zip" -o "$ZIG.zip" +ZIG_SRC="https://ziglang.org/download/index.json" +ZIG_VERSION=$(curl -s ${ZIG_SRC} | jq -r '.master.version') +ZIG="zig-${OS_ARCH}-$ZIG_VERSION" +ZIG_MASTER=$(curl -s ${ZIG_SRC} | jq -r --arg ARCH_OS "$ARCH_OS" '.master[$ARCH_OS].tarball') + +WASMTIME_VERSION="v0.38.1" +WASMTIME="wasmtime-$WASMTIME_VERSION-${ARCH_OS}-c-api" +GYRO_VERSION="0.7.0" +GYRO="gyro-$GYRO_VERSION-${OS_ARCH}" + +curl -sL "${ZIG_MASTER}" -o "$ZIG.zip" 7z x "$ZIG.zip" export PATH="$(pwd)/$ZIG:$PATH" -curl -L "https://github.com/bytecodealliance/wasmtime/releases/download/$WASMTIME_VERSION/$WASMTIME.zip" -o "$WASMTIME.zip" +curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/$WASMTIME_VERSION/$WASMTIME.zip" -o "$WASMTIME.zip" 7z x "$WASMTIME.zip" -curl -L "https://github.com/mattnite/gyro/releases/download/$GYRO_VERSION/$GYRO.zip" -o "$GYRO.zip" +curl -sL "https://github.com/mattnite/gyro/releases/download/$GYRO_VERSION/$GYRO.zip" -o "$GYRO.zip" 7z x "$GYRO.zip" export PATH="$(pwd)/$GYRO/bin:$PATH" -gyro build -gyro build test -gyro build run -Dexample=simple --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=gcd --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=linking --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=memory --search-prefix "$(pwd)/$WASMTIME" -gyro build run -Dexample=interrupt --search-prefix "$(pwd)/$WASMTIME" - +./ci/run_gyro $WASMTIME diff --git a/examples/interrupt.zig b/examples/interrupt.zig index b96bf0b..db0ff9c 100644 --- a/examples/interrupt.zig +++ b/examples/interrupt.zig @@ -38,7 +38,7 @@ pub fn main() !void { defer instance.deinit(); std.debug.print("Instance initialized...\n", .{}); - const thread = try std.Thread.spawn(interrupt, handle); + const thread = try std.Thread.spawn(.{}, interrupt, .{ handle }); if (instance.getExportFunc(module, "run")) |f| { std.debug.print("Calling export...\n", .{}); @@ -50,5 +50,5 @@ pub fn main() !void { std.debug.print("Export not found...\n", .{}); } - thread.wait(); + thread.join(); } diff --git a/examples/linking.zig b/examples/linking.zig index 9ebf0ad..2ae1ea4 100644 --- a/examples/linking.zig +++ b/examples/linking.zig @@ -1,6 +1,6 @@ const std = @import("std"); const wasmtime = @import("wasmtime"); -const builtin = std.builtin; +const builtin = @import("builtin"); const fs = std.fs; const ga = std.heap.c_allocator; const Allocator = std.mem.Allocator; diff --git a/examples/simple_new.zig b/examples/simple_new.zig new file mode 100644 index 0000000..a670cd4 --- /dev/null +++ b/examples/simple_new.zig @@ -0,0 +1,48 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const wasmtime = @import("wasmtime"); +const fs = std.fs; +const ga = std.heap.c_allocator; +const Allocator = std.mem.Allocator; + +const log = std.log.scoped(.wasmtime_zig); + +fn hello() void { + std.debug.print("Calling back...\n", .{}); + std.debug.print("> Hello World!\n", .{}); +} + +pub fn main() !void { + const wasm_path = if (builtin.os.tag == .windows) "examples\\simple.wat" else "examples/simple.wat"; + const wasm_file = try fs.cwd().openFile(wasm_path, .{}); + const wasm = try wasm_file.readToEndAlloc(ga, std.math.maxInt(u64)); + defer ga.free(wasm); + + var engine = try wasmtime.Engine.init(); + defer engine.deinit(); + std.debug.print("Engine initialized...\n", .{}); + + var store = try wasmtime.Store.init(&engine); + defer store.deinit(); + std.debug.print("Store initialized...\n", .{}); + + var module = try wasmtime.Module.initFromWat(&engine, wasm); + defer module.deinit(); + std.debug.print("Wasm module compiled...\n", .{}); + + var func = try wasmtime.Func.init(&store, hello); + std.debug.print("Func callback prepared...\n", .{}); + + _ = func; + + // var instance = try wasmtime.Instance.init(store, module, &.{func}); + // defer instance.deinit(); + // std.debug.print("Instance initialized...\n", .{}); + + // if (instance.getExportFunc(module, "run")) |f| { + // std.debug.print("Calling export...\n", .{}); + // try f.call(void, .{}); + // } else { + // std.debug.print("Export not found...\n", .{}); + // } +} diff --git a/examples/wat2wasm.zig b/examples/wat2wasm.zig new file mode 100644 index 0000000..0387de7 --- /dev/null +++ b/examples/wat2wasm.zig @@ -0,0 +1,32 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const wasmtime = @import("wasmtime"); +const fs = std.fs; +const ga = std.heap.c_allocator; +const Allocator = std.mem.Allocator; +const unicode = @import("std").unicode; + +const log = std.log.scoped(.wasmtime_zig); + +pub fn main() !void { + log.err("Starting...", .{}); + const wat_path = if (builtin.os.tag == .windows) "examples\\simple.wat" else "examples/simple.wat"; + const wat_file = try fs.cwd().openFile(wat_path, .{}); + const wat = try wat_file.readToEndAlloc(ga, std.math.maxInt(u64)); + log.err("Read wat:\n{s}", .{wat}); + defer ga.free(wat); + + const wasm = try wasmtime.Convert.wat2wasm(wat); + const wasm_slice = wasm.toSlice(); + log.err("Converted wasm:\n{s}", .{wasm_slice}); + + const dir = try std.fs.cwd().openDir(".", .{ .iterate = true }); + var out = try dir.createFile("output.wasm", .{}); + defer out.close(); + if (std.unicode.utf8ValidateSlice(wasm_slice)) { + try out.writeAll(wasm_slice); + log.err("File written.", .{}); + } else { + log.err("No valid utf-8", .{}); + } +} diff --git a/gyro.zzz b/gyro.zzz index f2b7f48..297b6ca 100644 --- a/gyro.zzz +++ b/gyro.zzz @@ -1,7 +1,6 @@ pkgs: wasmtime: version: 0.0.0 - root: src/main.zig description: Zig embedding of Wasmtime license: Apache-2.0 source_url: "https://github.com/zigwasm/wasmtime-zig" @@ -9,10 +8,10 @@ pkgs: wasmtime wasm wasi + root: src/main.zig deps: wasm: - src: - github: - user: zigwasm - repo: wasm-zig - ref: main + git: + url: "https://github.com/zigwasm/wasm-zig.git" + ref: main + root: src/main.zig diff --git a/src/config.zig b/src/config.zig new file mode 100644 index 0000000..cb67d61 --- /dev/null +++ b/src/config.zig @@ -0,0 +1,219 @@ +const std = @import("std"); +const Error = @import("error.zig").Error; +pub const wasm = @import("wasm"); + +// wasmtime_strategy_t +// Strategy is the compilation strategies for wasmtime +pub const Strategy = enum(u8) { + // WASMTIME_STRATEGY_AUTO + // StrategyAuto will let wasmtime automatically pick an appropriate compilation strategy + strategyAuto = 0, + // WASMTIME_STRATEGY_CRANELIFT + // StrategyCranelift will force wasmtime to use the Cranelift backend + strategyCranelift = 1, +}; + +// wasmtime_opt_level_t +// OptLevel decides what degree of optimization wasmtime will perform on generated machine code +pub const OptLevel = enum(u8) { + // WASMTIME_OPT_LEVEL_NONE + // OptLevelNone will perform no optimizations + optLevelNone = 0, + // WASMTIME_OPT_LEVEL_SPEED + // OptLevelSpeed will optimize machine code to be as fast as possible + optLevelSpeed = 1, + // WASMTIME_OPT_LEVEL_SPEED_AND_SIZE + // OptLevelSpeedAndSize will optimize machine code for speed, but also optimize + // to be small, sometimes at the cost of speed. + optLevelSpeedAndSize = 2, +}; + +// wasmtime_profiling_strategy_t +// ProfilingStrategy decides what sort of profiling to enable, if any. +pub const ProfilerStrategy = enum(u8) { + // WASMTIME_PROFILING_STRATEGY_NONE + // profilingStrategyNone means no profiler will be used + profilingStrategyNone = 0, + // WASMTIME_PROFILING_STRATEGY_JITDUMP + // profilingStrategyJitdump will use the "jitdump" linux support + profilingStrategyJitdump = 1, + // WASMTIME_PROFILING_STRATEGY_VTUNE + // Support for VTune will be enabled and the VTune runtime will be informed, + // at runtime, about JIT code. + // + // Note that this isn't always enabled at build time. + profileingStrategyVTune = 2, +}; + +pub const Config = struct { + inner: *wasm.Config, + + pub const Options = struct { + debugInfo: bool = false, + wasmThreads: bool = false, + wasmReferenceTypes: bool = false, + wasmSIMD: bool = false, + wasmBulkMemory: bool = false, + wasmMultiValue: bool = false, + wasmMultiMemory: bool = false, + wasmMemory64: bool = false, + consumeFuel: bool = false, + craneliftDebugVerifier: bool = false, + // craneLiftOptLevel: OptLevel = OptLevel.optLevelNone, + epochInterruption: bool = false, + }; + + pub fn init(options: Options) !Config { + var config = Config{ + .inner = try wasm.Config.init(), + }; + + if (options.debugInfo) { + config.setDebugInfo(true); + } + if (options.wasmThreads) { + config.setWasmThreads(true); + } + if (options.wasmReferenceTypes) { + config.setWasmReferenceTypes(true); + } + if (options.wasmSIMD) { + config.setWasmSIMD(true); + } + if (options.wasmBulkMemory) { + config.setWasmBulkMemory(true); + } + if (options.wasmMultiValue) { + config.setWasmMultiValue(true); + } + if (options.wasmMultiMemory) { + config.setWasmMultiMemory(true); + } + if (options.wasmMemory64) { + config.setWasmMemory64(true); + } + if (options.consumeFuel) { + config.setConsumeFuel(true); + } + if (options.craneliftDebugVerifier) { + config.setCraneliftDebugVerifier(true); + } + // if (options.craneLiftOptLevel != undefined) { config.setCraneLiftOptLevel(options.craneLiftOptLevel); } + if (options.epochInterruption) { + try config.setEpochInterruption(true); + } + + return config; + } + + // pub fn deinit(self: *Config) void { + // wasm_config_delete(*self.inner); + // } + + // setDebugInfo configures whether dwarf debug information for JIT code is enabled + pub fn setDebugInfo(self: *Config, opt: bool) void { + wasmtime_config_debug_info_set(self.inner, opt); + } + + // setWasmThreads configures whether the wasm threads proposal is enabled + pub fn setWasmThreads(self: *Config, opt: bool) void { + wasmtime_config_wasm_threads_set(self.inner, opt); + } + + // setWasmReferenceTypes configures whether the wasm reference types proposal is enabled + pub fn setWasmReferenceTypes(self: *Config, opt: bool) void { + wasmtime_config_wasm_reference_types_set(self.inner, opt); + } + + // setWasmSIMD configures whether the wasm SIMD proposal is enabled + pub fn setWasmSIMD(self: *Config, opt: bool) void { + wasmtime_config_wasm_simd_set(self.inner, opt); + } + + // setWasmBulkMemory configures whether the wasm bulk memory proposal is enabled + pub fn setWasmBulkMemory(self: *Config, opt: bool) void { + wasmtime_config_wasm_bulk_memory_set(self.inner, opt); + } + + // setWasmMultiValue configures whether the wasm multi value proposal is enabled + pub fn setWasmMultiValue(self: *Config, opt: bool) void { + wasmtime_config_wasm_multi_value_set(self.inner, opt); + } + + // setWasmMultiMemory configures whether the wasm multi memory proposal is enabled + pub fn setWasmMultiMemory(self: *Config, opt: bool) void { + wasmtime_config_wasm_multi_memory_set(self.inner, opt); + } + + // setWasmMemory64 configures whether the wasm memory64 proposal is enabled + pub fn setWasmMemory64(self: *Config, opt: bool) void { + wasmtime_config_wasm_memory64_set(self.inner, opt); + } + + // setConsumFuel configures whether fuel is enabled + pub fn setConsumeFuel(self: *Config, opt: bool) void { + wasmtime_config_consume_fuel_set(self.inner, opt); + } + + // setStrategy configures what compilation strategy is used to compile wasm code + pub fn setStrategy(self: *Config, strat: *Strategy) !void { + return wasmtime_config_strategy_set(self.inner, strat) orelse Error.ConfigStrategySet; + } + + // setCraneliftDebugVerifier configures whether the cranelift debug verifier will be active when + // cranelift is used to compile wasm code. + pub fn setCraneliftDebugVerifier(self: *Config, opt: bool) void { + wasmtime_config_cranelift_debug_verifier_set(self.inner, opt); + } + + // setCraneliftOptLevel configures the cranelift optimization level for generated code + pub fn setCraneLiftOptLevel(self: *Config, level: *OptLevel) void { + wasmtime_config_cranelift_opt_level_set(self.inner, level) orelse Error.ConfigOptLevelSet; + } + + // setProfiler configures what profiler strategy to use for generated code + pub fn setProfiler(self: *Config, profiler: *ProfilerStrategy) !void { + return wasmtime_config_profiler_set(self.inner, profiler) orelse Error.ConfigProfilerStrategySet; + } + + // cacheConfigLoadDefault enables compiled code caching for this `Config` using the default settings + // configuration can be found. + // + // For more information about caching see + // https://bytecodealliance.github.io/wasmtime/cli-cache.html + pub fn cacheConfigLoadDefault(self: *Config) !void { + return wasmtime_config_cache_config_load(self.inner, null) orelse Error.ConfigLoadDefault; + } + + // cacheConfigLoad enables compiled code caching for this `Config` using the settings specified + // in the configuration file `path`. + // + // For more information about caching and configuration options see + // https://bytecodealliance.github.io/wasmtime/cli-cache.html + pub fn cacheConfigLoad(self: *Config, path: []const u8) !void { + return wasmtime_config_cache_config_load(self.inner, path); + } + + // setEpochInterruption enables epoch-based instrumentation of generated code to + // interrupt WebAssembly execution when the current engine epoch exceeds a + // defined threshold. + pub fn setEpochInterruption(self: *Config, opt: bool) !void { + wasmtime_config_epoch_interruption_set(self.inner, opt); + } + + extern "c" fn wasmtime_config_debug_info_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_threads_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_reference_types_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_simd_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_bulk_memory_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_multi_value_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_multi_memory_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_wasm_memory64_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_consume_fuel_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_strategy_set(*wasm.Config, *Strategy) void; + extern "c" fn wasmtime_config_cranelift_debug_verifier_set(*wasm.Config, bool) void; + extern "c" fn wasmtime_config_cranelift_opt_level_set(*wasm.Config, *OptLevel) void; + extern "c" fn wasmtime_config_profiler_set(*wasm.Config, *ProfilerStrategy) void; + extern "c" fn wasmtime_config_cache_config_load(*wasm.Config, []const u8) void; // CString?? + extern "c" fn wasmtime_config_epoch_interruption_set(*wasm.Config, bool) void; +}; diff --git a/src/engine.zig b/src/engine.zig new file mode 100644 index 0000000..4b71383 --- /dev/null +++ b/src/engine.zig @@ -0,0 +1,55 @@ +const std = @import("std"); + +pub const Config = @import("./config.zig").Config; + +pub const wasm = @import("wasm"); + +// Engine is an instance of a wasmtime engine which is used to create a `Store`. +// +// Engines are a form of global configuration for wasm compilations and modules +// and such. +pub const Engine = struct { + inner: *wasm.Engine, + + /// TODO: decide: call function "init" or "new"? + /// init creates a new `Engine` with default configuration. + pub fn init() !Engine { + return Engine{ + .inner = try wasm.Engine.init(), + }; + } + + // withConfig creates a new `Engine` with the `Config` provided + // + // Note that once a `Config` is passed to this method it cannot be used again. + pub fn withConfig(config: *Config) !Engine { + return Engine{ + .inner = try wasm.Engine.withConfig(config.inner), + }; + } + + /// Frees the resources of the `Engine` + pub fn deinit(self: *Engine) void { + self.inner.deinit(); + } + + /// IncrementEpoch will increase the current epoch number by 1 within the + /// current engine which will cause any connected stores with their epoch + /// deadline exceeded to now be interrupted. + pub fn incrementEpoch(self: *Engine) void { + wasmtime_engine_increment_epoch(self.inner); + } + + extern "c" fn wasmtime_engine_increment_epoch(*wasm.Engine) void; +}; + +test "withConfig" { + const o = Config.Options{ + .debugInfo = true, + }; + + var c: Config = try Config.init(o); + + var engine = try Engine.withConfig(&c); + defer engine.deinit(); +} diff --git a/src/error.zig b/src/error.zig new file mode 100644 index 0000000..e7fcc83 --- /dev/null +++ b/src/error.zig @@ -0,0 +1,22 @@ +// @TODO: Split these up into own error sets +pub const Error = error{ + /// Failed to initialize a `Config` + ConfigInit, + ConfigStrategySet, + ConfigOptLevelSet, + ConfigProfilerStrategySet, + ConfigLoadDefault, + /// Failed to initialize an `Engine` (i.e. invalid config) + EngineInit, + /// Failed to initialize a `Store` + StoreInit, + StoreContext, + /// Failed to initialize a `Module` + ModuleInit, + ModuleWat2Wasm, + /// Failed to create a wasm function based on + /// the given `Store` and functype + FuncInit, + /// Failed to initialize a new `Instance` + InstanceInit, +}; diff --git a/src/func.zig b/src/func.zig new file mode 100644 index 0000000..ea780c1 --- /dev/null +++ b/src/func.zig @@ -0,0 +1,151 @@ +const std = @import("std"); + +pub const Config = @import("./config.zig").Config; +pub const Error = @import("./error.zig").Error; +pub const Engine = @import("engine.zig").Engine; +pub const Store = @import("./store.zig").Store; +pub const Convert = @import("./utils.zig").Convert; +pub const WasmError = @import("./utils.zig").WasmError; + +pub const wasm = @import("wasm"); + +const log = std.log.scoped(.wasmtime_zig); + +pub const ValVec = wasm.ByteVec; +pub const Trap = wasm.Trap; + +pub const Func = struct { + inner: *wasm.Func, + + pub const CallError = wasm.Func.CallError; + + pub fn init(store: *Store, callback: anytype) !Func { + return Func{ + .inner = try wasm.Func.init(store.inner, callback), + }; + } + + pub fn call(self: Func, store: *Store, comptime ResultType: type, args: anytype) CallError!ResultType { + if (!comptime trait.isTuple(@TypeOf(args))) + @compileError("Expected 'args' to be a tuple, but found type '" ++ @typeName(@TypeOf(args)) ++ "'"); + + const args_len = args.len; + comptime var wasm_args: [args_len]Value = undefined; + inline for (wasm_args) |*arg, i| { + arg.* = switch (@TypeOf(args[i])) { + i32, u32 => .{ .kind = .i32, .of = .{ .i32 = @bitCast(i32, args[i]) } }, + i64, u64 => .{ .kind = .i64, .of = .{ .i64 = @bitCast(i64, args[i]) } }, + f32 => .{ .kind = .f32, .of = .{ .f32 = args[i] } }, + f64 => .{ .kind = .f64, .of = .{ .f64 = args[i] } }, + *Func => .{ .kind = .funcref, .of = .{ .ref = args[i] } }, + *Extern => .{ .kind = .anyref, .of = .{ .ref = args[i] } }, + else => |ty| @compileError("Unsupported argument type '" ++ @typeName(ty) + "'"), + }; + } + + // TODO multiple return values + const result_len: usize = if (ResultType == void) 0 else 1; + if (result_len != wasm_func_result_arity(self.inner)) return CallError.InvalidResultCount; + if (args_len != wasm_func_param_arity(self.inner)) return CallError.InvalidParamCount; + + const final_args = ValVec{ + .size = args_len, + .data = if (args_len == 0) undefined else @ptrCast([*]Value, &wasm_args), + }; + + var trap: ?*wasm.Trap = null; + var result_list = ValVec.initWithCapacity(result_len); + defer result_list.deinit(); + + const err = wasmtime_func_call(store.inner, // wasmtime_context_t *store, + self.inner, // const wasmtime_func_t *func, + &final_args, // const wasmtime_val_t *args, + final_args.size, // size_t nargs, + &result_list, // wasmtime_val_t *results, + result_list.size, // size_t nresults, + &trap // wasm_trap_t **trap + ); + + if (err) |e| { + defer e.deinit(); + var msg = e.getMessage(); + defer msg.deinit(); + + log.err("Unable to call function: '{s}'", .{msg.toSlice()}); + return CallError.InnerError; + } + + if (trap) |t| { + t.deinit(); + // TODO handle trap message + log.err("code unexpectedly trapped", .{}); + return CallError.Trap; + } + + if (ResultType == void) return; + + // TODO: Handle multiple returns + const result_ty = result_list.data[0]; + if (!wasm.Func.matchesKind(ResultType, result_ty.kind)) return CallError.InvalidResultType; + + return switch (ResultType) { + i32, u32 => @intCast(ResultType, result_ty.of.i32), + i64, u64 => @intCast(ResultType, result_ty.of.i64), + f32 => result_ty.of.f32, + f64 => result_ty.of.f64, + *Func => @ptrCast(?*Func, result_ty.of.ref).?, + *Extern => @ptrCast(?*Extern, result_ty.of.ref).?, + else => |ty| @compileError("Unsupported result type '" ++ @typeName(ty) ++ "'"), + }; + } + + pub fn deinit(self: Func) void { + self.inner.deinit(); + } + + // \brief Call a WebAssembly function. + // + // This function is used to invoke a function defined within a store. For + // example this might be used after extracting a function from a + // #wasmtime_instance_t. + // + // \param store the store which owns `func` + // \param func the function to call + // \param args the arguments to the function call + // \param nargs the number of arguments provided + // \param results where to write the results of the function call + // \param nresults the number of results expected + // \param trap where to store a trap, if one happens. + // + // There are three possible return states from this function: + // + // 1. The returned error is non-null. This means `results` + // wasn't written to and `trap` will have `NULL` written to it. This state + // means that programmer error happened when calling the function, for + // example when the size of the arguments/results was wrong, the types of the + // arguments were wrong, or arguments may come from the wrong store. + // 2. The trap pointer is filled in. This means the returned error is `NULL` and + // `results` was not written to. This state means that the function was + // executing but hit a wasm trap while executing. + // 3. The error and trap returned are both `NULL` and `results` are written to. + // This means that the function call succeeded and the specified results were + // produced. + // + // The `trap` pointer cannot be `NULL`. The `args` and `results` pointers may be + // `NULL` if the corresponding length is zero. + // + // Does not take ownership of #wasmtime_val_t arguments. Gives ownership of + // #wasmtime_val_t results. + // + extern "c" fn wasmtime_func_call( + *wasm.Store, // wasmtime_context_t *store, + *wasm.Func, // const wasmtime_func_t *func, + *const wasm.ValVec, // const wasmtime_val_t *args, + usize, // size_t nargs, + *wasm.ValVec, // wasmtime_val_t *results, + usize, // size_t nresults, + *wasm.Trap, // wasm_trap_t **trap + ) ?*WasmError; + extern "c" fn wasm_func_result_arity(*const wasm.Func) usize; + extern "c" fn wasm_func_param_arity(*const wasm.Func) usize; +}; diff --git a/src/main.zig b/src/main.zig index 996f265..13cd159 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,349 +1,16 @@ const std = @import("std"); const testing = std.testing; -const meta = std.meta; -const trait = std.meta.trait; const log = std.log.scoped(.wasmtime_zig); pub const wasm = @import("wasm"); -// Re-exports -pub const ByteVec = wasm.ByteVec; -pub const NameVec = wasm.NameVec; -pub const ValVec = wasm.ValVec; -pub const Value = wasm.Value; -pub const Extern = wasm.Extern; -pub const ExternVec = wasm.ExternVec; -pub const Engine = wasm.Engine; -pub const Store = wasm.Store; -pub const Error = wasm.Error; -pub const Trap = wasm.Trap; -pub const Memory = wasm.Memory; -pub const MemoryType = wasm.MemoryType; -pub const WasiInstance = wasm.WasiInstance; -pub const WasiConfig = wasm.WasiConfig; - -// Helpers -extern "c" fn wasmtime_wat2wasm(wat: *ByteVec, wasm: *ByteVec) ?*WasmError; - -pub const WasmError = opaque { - /// Gets the error message - pub fn getMessage(self: *WasmError) *ByteVec { - var bytes: ?*ByteVec = null; - wasmtime_error_message(self, &bytes); - return bytes.?; - } - - pub fn deinit(self: *WasmError) void { - wasmtime_error_delete(self); - } - - extern "c" fn wasmtime_error_message(*const WasmError, *?*ByteVec) void; - extern "c" fn wasmtime_error_delete(*WasmError) void; -}; - -pub const Config = struct { - inner: *wasm.Config, - - const Options = struct { - interruptable: bool = false, - }; - - pub fn init(options: Options) !Config { - var config = Config{ - .inner = try wasm.Config.init(), - }; - if (options.interruptable) { - config.setInterruptable(true); - } - return config; - } - - pub fn setInterruptable(self: Config, opt: bool) void { - wasmtime_config_interruptable_set(self.inner, opt); - } - - extern "c" fn wasmtime_config_interruptable_set(*wasm.Config, bool) void; -}; - -pub const Module = struct { - inner: *wasm.Module, - - /// Initializes a new `Module` using the supplied engine and wasm bytecode - pub fn initFromWasm(engine: *Engine, wasm: []const u8) !Module { - var wasm_bytes = ByteVec.initWithCapacity(wasm.len); - defer wasm_bytes.deinit(); - - var i: usize = 0; - var ptr = wasm_bytes.data; - while (i < wasm.len) : (i += 1) { - ptr.* = wasm[i]; - ptr += 1; - } - - var module = Module{ - .inner = try Module.initInner(engine, &wasm_bytes), - }; - return module; - } - - /// Initializes a new `Module` by first converting the given wat format - /// into wasm bytecode. - pub fn initFromWat(engine: *Engine, wat: []const u8) !Module { - var wat_bytes = ByteVec.initWithCapacity(wat.len); - defer wat_bytes.deinit(); - - var i: usize = 0; - var ptr = wat_bytes.data; - while (i < wat.len) : (i += 1) { - ptr.* = wat[i]; - ptr += 1; - } - - var wasm_bytes: ByteVec = undefined; - const err = wasmtime_wat2wasm(&wat_bytes, &wasm_bytes); - defer if (err == null) wasm_bytes.deinit(); - - if (err) |e| { - defer e.deinit(); - var msg = e.getMessage(); - defer msg.deinit(); - - log.err("unexpected error occurred: '{s}'", .{msg.toSlice()}); - return Error.ModuleInit; - } - - var module = Module{ - .inner = try Module.initInner(engine, &wasm_bytes), - }; - return module; - } - - fn initInner(engine: *Engine, wasm_bytes: *ByteVec) !*wasm.Module { - var inner: ?*wasm.Module = undefined; - const err = wasmtime_module_new(engine, wasm_bytes, &inner); - - if (err) |e| { - defer e.deinit(); - var msg = e.getMessage(); - defer msg.deinit(); - - log.err("unexpected error occurred: '{s}'", .{msg.toSlice()}); - return Error.ModuleInit; - } - - return inner.?; - } - - pub fn deinit(self: Module) void { - self.inner.deinit(); - } - - extern "c" fn wasmtime_module_new(*Engine, *ByteVec, *?*wasm.Module) ?*WasmError; -}; - -pub const Func = struct { - inner: *wasm.Func, - - pub const CallError = wasm.Func.CallError; - - pub fn init(store: *Store, callback: anytype) !Func { - return Func{ - .inner = try wasm.Func.init(store, callback), - }; - } - - /// Tries to call the wasm function - /// expects `args` to be tuple of arguments - /// TODO this is a hard-copy of wasm.Func.call implementation. Refactor. - pub fn call(self: Func, comptime ResultType: type, args: anytype) CallError!ResultType { - if (!comptime trait.isTuple(@TypeOf(args))) - @compileError("Expected 'args' to be a tuple, but found type '" ++ @typeName(@TypeOf(args)) ++ "'"); - - const args_len = args.len; - comptime var wasm_args: [args_len]Value = undefined; - inline for (wasm_args) |*arg, i| { - arg.* = switch (@TypeOf(args[i])) { - i32, u32 => .{ .kind = .i32, .of = .{ .i32 = @intCast(i32, args[i]) } }, - i64, u64 => .{ .kind = .i64, .of = .{ .i64 = @intCast(i64, args[i]) } }, - f32 => .{ .kind = .f32, .of = .{ .f32 = args[i] } }, - f64 => .{ .kind = .f64, .of = .{ .f64 = args[i] } }, - *Func => .{ .kind = .funcref, .of = .{ .ref = args[i] } }, - *Extern => .{ .kind = .anyref, .of = .{ .ref = args[i] } }, - else => |ty| @compileError("Unsupported argument type '" ++ @typeName(ty) + "'"), - }; - } - - // TODO multiple return values - const result_len: usize = if (ResultType == void) 0 else 1; - if (result_len != wasm_func_result_arity(self.inner)) return CallError.InvalidResultCount; - if (args_len != wasm_func_param_arity(self.inner)) return CallError.InvalidParamCount; - - const final_args = ValVec{ - .size = args_len, - .data = if (args_len == 0) undefined else @ptrCast([*]Value, &wasm_args), - }; - - var trap: ?*Trap = null; - var result_list = ValVec.initWithCapacity(result_len); - defer result_list.deinit(); - const err = wasmtime_func_call(self.inner, &final_args, &result_list, &trap); - - if (err) |e| { - defer e.deinit(); - var msg = e.getMessage(); - defer msg.deinit(); - - log.err("Unable to call function: '{s}'", .{msg.toSlice()}); - return CallError.InnerError; - } - - if (trap) |t| { - t.deinit(); - // TODO handle trap message - log.err("code unexpectedly trapped", .{}); - return CallError.Trap; - } - - if (ResultType == void) return; - - // TODO: Handle multiple returns - const result_ty = result_list.data[0]; - if (!wasm.Func.matchesKind(ResultType, result_ty.kind)) return CallError.InvalidResultType; - - return switch (ResultType) { - i32, u32 => @intCast(ResultType, result_ty.of.i32), - i64, u64 => @intCast(ResultType, result_ty.of.i64), - f32 => result_ty.of.f32, - f64 => result_ty.of.f64, - *Func => @ptrCast(?*Func, result_ty.of.ref).?, - *Extern => @ptrCast(?*c.Extern, result_ty.of.ref).?, - else => |ty| @compileError("Unsupported result type '" ++ @typeName(ty) ++ "'"), - }; - } - - pub fn deinit(self: Func) void { - self.inner.deinit(); - } - - extern "c" fn wasmtime_func_call(*wasm.Func, *const ValVec, *ValVec, *?*Trap) ?*WasmError; - extern "c" fn wasm_func_result_arity(*const wasm.Func) usize; - extern "c" fn wasm_func_param_arity(*const wasm.Func) usize; -}; - -pub const Instance = struct { - inner: *wasm.Instance, - - /// Initializes a new `Instance` using the given `store` and `mode`. - /// The given slice defined in `import` must match what was initialized - /// using the same `Store` as given. - pub fn init(store: *Store, module: Module, import: []const Func) !Instance { - var trap: ?*Trap = null; - var inner: ?*wasm.Instance = null; - - var imports = ExternVec.initWithCapacity(import.len); - defer imports.deinit(); - - var ptr = imports.data; - for (import) |func| { - ptr.* = func.inner.asExtern(); - ptr += 1; - } - - const err = wasmtime_instance_new(store, module.inner, &imports, &inner, &trap); - - if (err) |e| { - defer e.deinit(); - var msg = e.getMessage(); - defer msg.deinit(); - - log.err("unexpected error occurred: '{s}'", .{msg.toSlice()}); - return Error.InstanceInit; - } - - if (trap) |t| { - defer t.deinit(); - // TODO handle trap message - log.err("code unexpectedly trapped", .{}); - return Error.InstanceInit; - } - - return Instance{ - .inner = inner.?, - }; - } - - pub fn getExportFunc(self: Instance, module: Module, name: []const u8) ?Func { - var inner = self.inner.getExportFunc(module.inner, name) orelse return null; - return Func{ .inner = inner }; - } - - pub fn getExportMem(self: Instance, module: Module, name: []const u8) ?*Memory { - return self.inner.getExportMem(module.inner, name); - } - - pub fn deinit(self: Instance) void { - self.inner.deinit(); - } - - extern "c" fn wasmtime_instance_new(*Store, *const wasm.Module, *const ExternVec, *?*wasm.Instance, *?*Trap) ?*WasmError; -}; - -pub const InterruptHandle = opaque { - /// Creates a new interrupt handle. - /// Must be freed by calling `deinit()` - pub fn init(store: *Store) !*InterruptHandle { - return wasmtime_interrupt_handle_new(store) orelse error.InterruptsNotEnabled; - } - /// Invokes an interrupt in the current wasm module - pub fn interrupt(self: *InterruptHandle) void { - wasmtime_interrupt_handle_interrupt(self); - } - - pub fn deinit(self: *InterruptHandle) void { - wasmtime_interrupt_handle_delete(self); - } - - extern "c" fn wasmtime_interrupt_handle_interrupt(*InterruptHandle) void; - extern "c" fn wasmtime_interrupt_handle_delete(*InterruptHandle) void; - extern "c" fn wasmtime_interrupt_handle_new(*Store) ?*InterruptHandle; -}; - -pub const Linker = opaque { - pub fn init(store: *Store) !*Linker { - return wasmtime_linker_new(store) orelse error.LinkerInit; - } - - pub fn deinit(self: *Linker) void { - wasmtime_linker_delete(self); - } - - /// Defines a `WasiInstance` for the current `Linker` - pub fn defineWasi(self: *Linker, wasi: *const WasiInstance) ?*WasmError { - return wasmtime_linker_define_wasi(self, wasi); - } - - /// Defines an `Instance` for the current `Linker` object using the given `name` - pub fn defineInstance(self: *Linker, name: *const NameVec, instance: *const wasm.Instance) ?*WasmError { - return wasmtime_linker_define_instance(self, name, instance); - } - - /// Instantiates the `Linker` for the given `Module` and creates a new `Instance` for it - /// Returns a `WasmError` when failed to instantiate - pub fn instantiate( - self: *const Linker, - module: Module, - instance: *?*wasm.Instance, - trap: *?*Trap, - ) ?*WasmError { - return wasmtime_linker_instantiate(self, module.inner, instance, trap); - } - - extern "c" fn wasmtime_linker_new(*Store) ?*Linker; - extern "c" fn wasmtime_linker_delete(*Linker) void; - extern "c" fn wasmtime_linker_define_wasi(*Linker, *const WasiInstance) ?*WasmError; - extern "c" fn wasmtime_linker_define_instance(*Linker, *const NameVec, *const wasm.Instance) ?*WasmError; - extern "c" fn wasmtime_linker_instantiate(*const Linker, *const wasm.Module, *?*wasm.Instance, *?*Trap) ?*WasmError; -}; +pub const Convert = @import("utils.zig").Convert; +pub const Config = @import("config.zig").Config; +pub const Engine = @import("engine.zig").Engine; +pub const Store = @import("store.zig").Store; +pub const Module = @import("module.zig").Module; +// pub const Func = @import("func.zig").Func; +// pub const Instance = @import("instance.zig").Instance; test "" { testing.refAllDecls(@This()); diff --git a/src/module.zig b/src/module.zig new file mode 100644 index 0000000..bc3de95 --- /dev/null +++ b/src/module.zig @@ -0,0 +1,73 @@ +const std = @import("std"); + +pub const Config = @import("./config.zig").Config; +pub const Error = @import("./error.zig").Error; +pub const Engine = @import("engine.zig").Engine; +pub const Store = @import("./store.zig").Store; +pub const Convert = @import("./utils.zig").Convert; +pub const WasmError = @import("./utils.zig").WasmError; + +pub const wasm = @import("wasm"); + +const log = std.log.scoped(.wasmtime_zig); + +pub const ByteVec = wasm.ByteVec; + +pub const Module = struct { + inner: *wasm.Module, + + /// Initializes a new `Module` by first converting the given wat format + /// into wasm bytecode. + pub fn initFromWat(engine: *Engine, wat: []const u8) !Module { + var wasm_bytes = try Convert.wat2wasm(wat); + // defer wasm_bytes.deinit(); + + var module = Module{ + .inner = try Module.initInner(engine, &wasm_bytes), + }; + + return module; + } + + fn initInner(engine: *Engine, wasm_bytes: *ByteVec) !*wasm.Module { + var inner: ?*wasm.Module = undefined; + + // /** + // * \brief Compiles a WebAssembly binary into a #wasmtime_module_t + // * + // * This function will compile a WebAssembly binary into an owned #wasm_module_t. + // * This performs the same as #wasm_module_new except that it returns a + // * #wasmtime_error_t type to get richer error information. + // * + // * On success the returned #wasmtime_error_t is `NULL` and the `ret` pointer is + // * filled in with a #wasm_module_t. On failure the #wasmtime_error_t is + // * non-`NULL` and the `ret` pointer is unmodified. + // * + // * This function does not take ownership of any of its arguments, but the + // * returned error and module are owned by the caller. + // */ + const err = wasmtime_module_new( // WASM_API_EXTERN wasmtime_error_t *wasmtime_module_new( + engine.inner, // wasm_engine_t *engine, + wasm_bytes.data, // const uint8_t *wasm, + wasm_bytes.size, // size_t wasm_len, + &inner.? // wasmtime_module_t **ret + ); + + if (err) |e| { + defer e.deinit(); + var msg = e.getMessage(); + defer msg.deinit(); + + log.err("unexpected error occurred: '{s}'", .{msg.toSlice()}); + return Error.ModuleInit; + } + + return inner.?; + } + + pub fn deinit(self: Module) void { + self.inner.deinit(); + } + + extern "c" fn wasmtime_module_new(*wasm.Engine, [*]const u8, usize, **wasm.Module) ?*WasmError; +}; diff --git a/src/slab.zig b/src/slab.zig new file mode 100644 index 0000000..7953416 --- /dev/null +++ b/src/slab.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const testing = std.testing; + +pub const Slab = struct { + list: ArrayList(u64), + next: u64, + + pub fn init(allocator: Allocator) Slab { + const s = Slab{ + .list = ArrayList(u64).init(allocator), + .next = 0, + }; + + return s; + } + + pub fn allocate(self: *Slab) !u64 { + if (self.next == self.list.items.len) { + try self.list.append(self.next + 1); + } + const ret = self.next; + self.next = self.list.items[ret]; + return ret; + } + + pub fn deallocate(self: *Slab, slot: u64) void { + self.list.items[slot] = self.next; + self.next = slot; + } +}; + +test "wasmtime.Slab" { + var slab = Slab.init(std.heap.c_allocator); + + var al = try slab.allocate(); + if (al != 0) { + @panic("bad alloc"); + } + al = try slab.allocate(); + if (al != 1) { + @panic("bad alloc"); + } + slab.deallocate(0); + al = try slab.allocate(); + if (al != 0) { + @panic("bad alloc"); + } + al = try slab.allocate(); + if (al != 2) { + @panic("bad alloc"); + } +} diff --git a/src/store.zig b/src/store.zig new file mode 100644 index 0000000..059b08e --- /dev/null +++ b/src/store.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +pub const Error = @import("./error.zig").Error; +pub const Engine = @import("./engine.zig").Engine; + +pub const wasm = @import("wasm"); + +pub const Context = opaque {}; + +// Store is a general group of wasm instances, and many objects +// must all be created with and reference the same `Store` +pub const Store = struct { + inner: *wasm.Store, + + engine: *Engine, + + // init creates a new `Store` from the configuration provided in `engine` + pub fn init(engine: *Engine) !Store { + return Store{ + .inner = try wasm.Store.init(engine.inner), + .engine = engine, + }; + } + + pub fn deinit(self: *Store) void { + self.inner.deinit(); + } + + pub fn context(self: *Store) *Context { + return wasmtime_store_context(self.inner) orelse Error.StoreContext; + } + + pub fn setEpochDeadline(self: *Store, deadline: u64) void { + wasmtime_context_set_epoch_deadline(self.context(), deadline); + } + + extern "c" fn wasmtime_store_context(*Store) *Context; + extern "c" fn wasmtime_context_set_epoch_deadline(*Context, u64) void; + + // not imlemented + // extern "c" fn wasmtime_context_fuel_consumed(*Context, u64) c_int // ?? + // extern "c" fn wasmtime_context_add_fuel(*Context, u64) void + // extern "c" fn wasmtime_context_consume_fuel(*Context, u64, u64) u64 +}; diff --git a/src/utils.zig b/src/utils.zig new file mode 100644 index 0000000..6360313 --- /dev/null +++ b/src/utils.zig @@ -0,0 +1,70 @@ +const std = @import("std"); +const testing = std.testing; +const log = std.log.scoped(.wasmtime_zig); + +pub const er = @import("./error.zig"); +pub const Error = er.Error; + +// pub const wasm = @import(".gyro/wasm-zig-zigwasm-github.com-3a96556d/pkg/src/main.zig"); +pub const wasm = @import("wasm"); + +pub const ByteVec = wasm.ByteVec; + +pub const WasmError = opaque { + /// Gets the error message + pub fn getMessage(self: *WasmError) ByteVec { + var bytes: ByteVec = ByteVec.initWithCapacity(0); + wasmtime_error_message(self, &bytes); + + return bytes; + } + + pub fn deinit(self: *WasmError) void { + wasmtime_error_delete(self); + } + + extern "c" fn wasmtime_error_message(*const WasmError, *ByteVec) void; + extern "c" fn wasmtime_error_delete(*WasmError) void; +}; + +pub const Convert = struct { + // Wat2Wasm converts the text format of WebAssembly to the binary format. + // + // Takes the text format in-memory as input, and returns either the binary + // encoding of the text format or an error if parsing fails. + pub fn wat2wasm(wat: []const u8) !ByteVec { + var retVec: ByteVec = undefined; + + const err = wasmtime_wat2wasm(wat.ptr, wat.len, &retVec); + // defer if (err == null) retVec.deinit(); + + if (err) |e| { + defer e.deinit(); + var msg = e.getMessage(); + defer msg.deinit(); + + log.err("unexpected error occurred: '{s}'", .{msg.toSlice()}); + + return Error.ModuleWat2Wasm; + } + + return retVec; + } + + extern "c" fn wasmtime_wat2wasm(wat: [*]const u8, wat_len: usize, retVec: *ByteVec) ?*WasmError; +}; + +test "wat2wasm" { + const wasm_data1 = try Convert.wat2wasm("(module)"); + + // Return value should be of type ByteVec + try testing.expectEqual(@TypeOf(wasm_data1), @TypeOf(ByteVec.initWithCapacity(0))); + // Return value should be len == 8 + try testing.expectEqual(wasm_data1.toSlice().len, 8); + // Converting wat "asd__" to wasm should fail + var v = Convert.wat2wasm("asd__") catch |err| { + try testing.expect(@TypeOf(err) != @TypeOf(ByteVec.initWithCapacity(0))); + return; + }; + _ = v; +}