Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Linker & Host Functions

The linker connects WASM imports to host-provided functions.

Linker

Creating a Linker

var linker = wasmz.Linker.empty;
defer linker.deinit(allocator);

Defining Functions

try linker.define(
    allocator,
    "module_name",  // Import module name
    "func_name",    // Import function name
    wasmz.HostFunc.init(
        null,                    // Context (optional)
        host_function,           // Function pointer
        &[_]wasmz.ValType{ .I32 }, // Parameter types
        &[_]wasmz.ValType{ .I32 }, // Result types
    ),
);

Methods

MethodDescription
define(alloc, module, name, func)Register a host function
get(module, name)Look up a function
deinit(alloc)Free linker resources

HostFunc

Creating Host Functions

fn host_print(
    ctx: ?*anyopaque,
    hc: *wasmz.HostContext,
    params: []const wasmz.RawVal,
    results: []wasmz.RawVal,
) wasmz.HostError!void {
    const value = params[0].readAs(i32);
    std.debug.print("Value: {d}\n", .{value});
    // No return value - results is empty
}

With Context

const MyContext = struct {
    counter: u32,
};

fn host_increment(
    ctx: ?*anyopaque,
    hc: *wasmz.HostContext,
    params: []const wasmz.RawVal,
    results: []wasmz.RawVal,
) wasmz.HostError!void {
    const my_ctx: *MyContext = @ptrCast(@alignCast(ctx.?));
    my_ctx.counter += params[0].readAs(u32);
    results[0] = wasmz.RawVal.from(@as(i32, @intCast(my_ctx.counter)));
}

var my_ctx = MyContext{ .counter = 0 };
try linker.define(allocator, "env", "increment", wasmz.HostFunc.init(
    &my_ctx,
    host_increment,
    &[_]wasmz.ValType{.I32},
    &[_]wasmz.ValType{.I32},
));

HostContext

The HostContext provides access to runtime state:

fn host_memory_access(
    ctx: ?*anyopaque,
    hc: *wasmz.HostContext,
    params: []const wasmz.RawVal,
    results: []wasmz.RawVal,
) wasmz.HostError!void {
    // Access instance memory
    const offset = @as(usize, @intCast(params[0].readAs(u32)));
    const memory = hc.instance.memory;
    const byte = memory.readByte(offset);
    results[0] = wasmz.RawVal.from(@as(i32, byte));
}

Properties

PropertyDescription
instanceAccess to the instance
storeAccess to the store

ValType

Value types for function signatures:

const ValType = enum {
    I32,
    I64,
    F32,
    F64,
    FuncRef,
    ExternRef,
};

WASI Integration

Add WASI functions to the linker:

const wasi = @import("wasi").preview1;

var wasi_host = wasi.Host.init(allocator);
defer wasi_host.deinit();

// Set command-line arguments
try wasi_host.setArgs(&[_][]const u8{ "program.wasm", "--verbose" });

// Add to linker
try wasi_host.addToLinker(&linker, allocator);

Error Handling from Host

Return errors from host functions:

fn host_may_fail(
    ctx: ?*anyopaque,
    hc: *wasmz.HostContext,
    params: []const wasmz.RawVal,
    results: []wasmz.RawVal,
) wasmz.HostError!void {
    const value = params[0].readAs(i32);
    if (value < 0) {
        return wasmz.HostError.Trap; // Will cause a trap
    }
    results[0] = wasmz.RawVal.from(value * 2);
}

Complete Example

const std = @import("std");
const wasmz = @import("wasmz");

fn host_add(
    _: ?*anyopaque,
    _: *wasmz.HostContext,
    params: []const wasmz.RawVal,
    results: []wasmz.RawVal,
) wasmz.HostError!void {
    const a = params[0].readAs(i32);
    const b = params[1].readAs(i32);
    results[0] = wasmz.RawVal.from(a + b);
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Setup
    var engine = try wasmz.Engine.init(allocator, .{});
    defer engine.deinit();

    var store = try wasmz.Store.init(allocator, engine);
    defer store.deinit();

    // Create linker with host function
    var linker = wasmz.Linker.empty;
    defer linker.deinit(allocator);

    try linker.define(allocator, "env", "host_add", wasmz.HostFunc.init(
        null,
        host_add,
        &[_]wasmz.ValType{ .I32, .I32 },
        &[_]wasmz.ValType{.I32},
    ));

    // Load and run WASM that imports "env::host_add"
    const bytes = try std.fs.cwd().readFileAlloc(allocator, "module.wasm", 1024 * 1024);
    defer allocator.free(bytes);

    var module = try wasmz.Module.compile(engine, bytes);
    defer module.deinit();

    var instance = try wasmz.Instance.init(&store, module, linker);
    defer instance.deinit();

    _ = try instance.runStartFunction();
}