Skip to content

Releases: ooyeku/wick

Release list

wick v0.1.0

Choose a tag to compare

@ooyeku ooyeku released this 25 Jul 18:23

wick is a small, dependency-free WebAssembly interpreter for embedding plugin systems in Zig applications.

It runs wasm 1.0 core modules by direct interpretation — no JIT, no codegen, no libc. An std.mem.Allocator is the only thing it asks of its host. It's built for one job: letting an application execute third-party code where the host controls every doorway (imports are the only way out of the sandbox) and every budget (memory ceiling, call depth, instruction fuel).

Highlights

One-call setup, typed calls in both directions.

var plugin = try wick.Plugin.init(allocator, wasm_bytes, .{
    .imports = &imports,
    .user_data = &host,
    .limits = .{ .fuel = 10_000_000, .max_memory_pages = 64 },
});
defer plugin.deinit();

const add = try plugin.func("add", fn (i32, i32) i32);
const sum = try add.call(.{ 2, 3 });

wick.bind generates host-function argument unpacking from native Zig signatures (including []const u8 parameters resolved through guest memory with bounds checks); wick.guest.func does the mirror image for guest exports, checking signatures at resolve time so a missing or mismatched export fails before any guest code runs.

Contained by construction. Per-instance Limits bound instruction fuel, call depth, value-stack size, and linear memory. A runaway plugin returns error.OutOfFuel instead of hanging the host, and the instance stays usable afterward. The decoder bounds every section count against the bytes backing it, so a forged header can't drive a giant allocation.

Fast for an interpreter. Function bodies are translated once at decode time into pre-decoded IR — immediates unpacked, branch targets resolved to direct indices, stack heights validated — then run through a threaded-dispatch loop over preallocated stacks. Nothing inside the interpreter loop allocates. Roughly 800–1550 MIPS and 126M host calls/second on the bundled benchmarks. See ROADMAP.md for the optimization phases, their measurements, and one phase that was measured and rejected.

Getting started

zig fetch --save git+https://github.com/ooyeku/wick#v0.1.0

Requires Zig 0.16.0 or newer. zig build example compiles a real Zig plugin to wasm32-freestanding and runs a host that drives it — typed calls, host callbacks, guest-side allocation, direct memory reads, and fuel containment of a runaway export.

Scope

Implemented: full control flow, i32/i64/f32/f64 arithmetic and conversions (including trapping and saturating float→int truncations), linear memory with bounded growth, globals, funcref tables with runtime-type-checked call_indirect, active data and element segments, the start function, and the bulk-memory ops LLVM toolchains emit.

Not implemented for now: SIMD, threads, multi-memory, reference types beyond funcref tables, and WASI. Modules using them fail with a typed error rather than running incorrectly.

Verified against the official WebAssembly spec test suite (zig build spec), plus unit tests and decode/execute fuzz targets.