Skip to content

Commit

Permalink
add zware-run to run wasm binaries from the command-line
Browse files Browse the repository at this point in the history
An initial version of a command-line program that can take a WASM binary
on the command line and a function name and execute it.

It will find missing imports and populate them with "stubs". The stub will
log the call and then populate the results with "zeroed" values. An
enhancement could be to take multiple files on the command line and link
them together before execution.

A few examples of binaries I could use with this was:

   - binaries from the test suite
   - WASM4 binaries
   - the Zig wasm executable

Of the ones I tested this seems like a viable strategy to quickly run
the binaries from the test suite.  The WASM4/Zig binaries would usually
just call a function or two and then hit an assert because of the "zeroed"
results that are produced by the stubs.
  • Loading branch information
marler8997 committed Oct 20, 2024
1 parent 9cd6f56 commit b0efcd0
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 27 deletions.
18 changes: 18 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ pub fn build(b: *Build) !void {
const test_step = b.step("test", "Run all the tests");
test_step.dependOn(unittest_step);
test_step.dependOn(testsuite_step);

{
const exe = b.addExecutable(.{
.name = "zware-run",
.root_source_file = b.path("tools/zware-run/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zware", zware_module);
const install = b.addInstallArtifact(exe, .{});
b.getInstallStep().dependOn(&install.step);
const run = b.addRunArtifact(exe);
run.step.dependOn(&install.step);
if (b.args) |args| {
run.addArgs(args);
}
b.step("run", "Run the cmdline runner zware-run").dependOn(&run.step);
}
}

fn addWast2Json(b: *Build) *Build.Step.Compile {
Expand Down
44 changes: 44 additions & 0 deletions src/error.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const std = @import("std");

const module = @import("module.zig");

/// A function can take a reference to this to pass extra error information to the caller.
/// A function that does this guarantees the reference will be populated if it returns error.SeeContext.
/// Error implements a format function.
/// The same error instance can be re-used for multiple calls.
///
/// Example usage:
/// ----
/// var zware_error: Error = undefined;
/// foo(&zware_error) catch |err| switch (err) {
/// error.SeeContext => std.log.err("foo failed: {}", .{zware_error}),
/// else => |err| return err,
/// };
/// ---
pub const Error = union(enum) {
missing_import: module.Import,
any: anyerror,

/// Called by a function that wants to both populate this error instance and let the caller
/// know it's been populated by returning error.SeeContext.
pub fn set(self: *Error, e: Error) error{SeeContext} {
self.* = e;
return error.SeeContext;
}
pub fn format(
self: Error,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
switch (self) {
.missing_import => |import| try writer.print(
"missing {s} import '{s}' from module '{s}'",
.{ @tagName(import.desc_tag), import.name, import.module },
),
.any => |e| try writer.print("{s}", .{@errorName(e)}),
}
}
};
24 changes: 19 additions & 5 deletions src/instance.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const math = std.math;
const posix = std.posix;
const wasi = std.os.wasi;
const ArrayList = std.ArrayList;
const Error = @import("error.zig").Error;
const Module = @import("module.zig").Module;
const Store = @import("store.zig").ArrayListStore;
const Function = @import("store/function.zig").Function;
Expand Down Expand Up @@ -128,9 +129,20 @@ pub const Instance = struct {
}

pub fn instantiate(self: *Instance) !void {
var context: Error = undefined;
self.instantiateWithError(&context) catch |err| switch (err) {
error.SeeContext => switch (context) {
.missing_import => return error.ImportNotFound,
.any => |e| return e,
},
else => |e| return e,
};
}

pub fn instantiateWithError(self: *Instance, err: *Error) !void {
if (self.module.decoded == false) return error.ModuleNotDecoded;

try self.instantiateImports();
try self.instantiateImports(err);
try self.instantiateFunctions();
try self.instantiateGlobals();
try self.instantiateMemories();
Expand All @@ -143,9 +155,11 @@ pub const Instance = struct {
}
}

fn instantiateImports(self: *Instance) !void {
fn instantiateImports(self: *Instance, err: *Error) error{ OutOfMemory, SeeContext }!void {
for (self.module.imports.list.items) |import| {
const import_handle = try self.store.import(import.module, import.name, import.desc_tag);
const import_handle = self.store.import(import.module, import.name, import.desc_tag) catch |e| switch (e) {
error.ImportNotFound => return err.set(.{ .missing_import = import }),
};
switch (import.desc_tag) {
.Func => try self.funcaddrs.append(import_handle),
.Mem => try self.memaddrs.append(import_handle),
Expand Down Expand Up @@ -361,7 +375,7 @@ pub const Instance = struct {
},
.host_function => |host_func| {
var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], self);
try host_func.func(&vm);
try host_func.func(&vm, host_func.context);
},
}
}
Expand Down Expand Up @@ -404,7 +418,7 @@ pub const Instance = struct {
},
.host_function => |host_func| {
var vm = VirtualMachine.init(op_stack[0..], frame_stack[0..], label_stack[0..], self);
try host_func.func(&vm);
try host_func.func(&vm, host_func.context);
},
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/instance/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ pub const VirtualMachine = struct {
next_ip = f.start;
},
.host_function => |hf| {
try hf.func(self);
try hf.func(self, hf.context);
next_ip = ip + 1;
},
}
Expand Down Expand Up @@ -350,7 +350,7 @@ pub const VirtualMachine = struct {
next_ip = func.start;
},
.host_function => |host_func| {
try host_func.func(self);
try host_func.func(self, host_func.context);

next_ip = ip + 1;
},
Expand Down
2 changes: 2 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub const Error = @import("error.zig").Error;
pub const Module = @import("module.zig").Module;
pub const FuncType = @import("module.zig").FuncType;
pub const Instance = @import("instance.zig").Instance;
pub const VirtualMachine = @import("instance/vm.zig").VirtualMachine;
pub const WasmError = @import("instance/vm.zig").WasmError;
Expand Down
4 changes: 2 additions & 2 deletions src/module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ pub const Module = struct {
return count;
}

pub fn getExport(self: *Module, tag: Tag, name: []const u8) !usize {
pub fn getExport(self: *const Module, tag: Tag, name: []const u8) !usize {
for (self.exports.list.items) |exported| {
if (tag == exported.tag and mem.eql(u8, name, exported.name)) return exported.index;
}
Expand Down Expand Up @@ -841,7 +841,7 @@ fn Section(comptime T: type) type {
return self.list.items;
}

pub fn lookup(self: *Self, idx: anytype) !T {
pub fn lookup(self: *const Self, idx: anytype) !T {
const index = switch (@TypeOf(idx)) {
u32 => idx,
usize => math.cast(u32, idx) orelse return error.IndexTooLarge,
Expand Down
13 changes: 12 additions & 1 deletion src/store.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const Mutability = @import("module.zig").Mutability;
const RefType = @import("valtype.zig").RefType;
const ValType = @import("valtype.zig").ValType;
const Instance = @import("instance.zig").Instance;
const VirtualMachine = @import("instance/vm.zig").VirtualMachine;
const WasmError = @import("instance/vm.zig").WasmError;

// - Stores provide the runtime memory shared between modules
// - For different applications you may want to use a store that
Expand Down Expand Up @@ -188,13 +190,22 @@ pub const ArrayListStore = struct {

// Helper functions for exposing values

pub fn exposeHostFunction(self: *ArrayListStore, module: []const u8, function_name: []const u8, host_function_pointer: anytype, params: []const ValType, results: []const ValType) !void {
pub fn exposeHostFunction(
self: *ArrayListStore,
module: []const u8,
function_name: []const u8,
host_function_pointer: *const fn (*VirtualMachine, usize) WasmError!void,
host_function_context: usize,
params: []const ValType,
results: []const ValType,
) !void {
const funcaddr = try self.addFunction(Function{
.params = params,
.results = results,
.subtype = .{
.host_function = .{
.func = host_function_pointer,
.context = host_function_context,
},
},
});
Expand Down
3 changes: 2 additions & 1 deletion src/store/function.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ pub const Function = struct {
instance: *Instance,
},
host_function: struct {
func: *const fn (*VirtualMachine) WasmError!void,
func: *const fn (*VirtualMachine, usize) WasmError!void,
context: usize,
},
},

Expand Down
30 changes: 14 additions & 16 deletions test/testrunner/src/testrunner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,37 @@ const WasmError = zware.WasmError;

var gpa = GeneralPurposeAllocator(.{}){};

fn print(_: *VirtualMachine) WasmError!void {
fn print(_: *VirtualMachine, _: usize) WasmError!void {
std.debug.print("print\n", .{});
}

fn print_i32(vm: *VirtualMachine) WasmError!void {
fn print_i32(vm: *VirtualMachine, _: usize) WasmError!void {
const value = vm.popOperand(i32);
std.debug.print("print_i32: {}\n", .{value});
}

fn print_i64(vm: *VirtualMachine) WasmError!void {
fn print_i64(vm: *VirtualMachine, _: usize) WasmError!void {
const value = vm.popOperand(i64);
std.debug.print("print_i64: {}\n", .{value});
}

fn print_f32(vm: *VirtualMachine) WasmError!void {
fn print_f32(vm: *VirtualMachine, _: usize) WasmError!void {
const value = vm.popOperand(f32);
std.debug.print("print_f32: {}\n", .{value});
}

fn print_f64(vm: *VirtualMachine) WasmError!void {
fn print_f64(vm: *VirtualMachine, _: usize) WasmError!void {
const value = vm.popOperand(f64);
std.debug.print("print_f64: {}\n", .{value});
}

fn print_i32_f32(vm: *VirtualMachine) WasmError!void {
fn print_i32_f32(vm: *VirtualMachine, _: usize) WasmError!void {
const value_f32 = vm.popOperand(f32);
const value_i32 = vm.popOperand(i32);
std.debug.print("print_i32_f32: {}, {}\n", .{ value_i32, value_f32 });
}

fn print_f64_f64(vm: *VirtualMachine) WasmError!void {
fn print_f64_f64(vm: *VirtualMachine, _: usize) WasmError!void {
const value_f64_2 = vm.popOperand(f64);
const value_f64_1 = vm.popOperand(f64);
std.debug.print("print_f64_f64: {}, {}\n", .{ value_f64_1, value_f64_2 });
Expand Down Expand Up @@ -113,13 +113,13 @@ pub fn main() anyerror!void {
try store.exposeGlobal(spectest, "global_f64", 666, .F64, .Immutable);

// Expose host functions
try store.exposeHostFunction(spectest, "print", print, &[_]ValType{}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_i32", print_i32, &[_]ValType{.I32}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_i64", print_i64, &[_]ValType{.I64}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_f32", print_f32, &[_]ValType{.F32}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_f64", print_f64, &[_]ValType{.F64}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_i32_f32", print_i32_f32, &[_]ValType{.I32, .F32}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_f64_f64", print_f64_f64, &[_]ValType{.F64, .F64}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print", print, 0, &[_]ValType{}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_i32", print_i32, 0, &[_]ValType{.I32}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_i64", print_i64, 0, &[_]ValType{.I64}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_f32", print_f32, 0, &[_]ValType{.F32}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_f64", print_f64, 0, &[_]ValType{.F64}, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_i32_f32", print_i32_f32, 0, &[_]ValType{ .I32, .F32 }, &[_]ValType{});
try store.exposeHostFunction(spectest, "print_f64_f64", print_f64_f64, 0, &[_]ValType{ .F64, .F64 }, &[_]ValType{});

var current_instance: *Instance = undefined;
var registered_names = StringHashMap(*Instance).init(alloc);
Expand Down Expand Up @@ -893,7 +893,6 @@ const CommandRegister = struct {
as: []const u8,
};


const Action = union(enum) {
invoke: ActionInvoke,
get: ActionGet,
Expand Down Expand Up @@ -929,7 +928,6 @@ const ActionGet = struct {
module: ?[]const u8 = null,
};


const Value = struct {
type: []const u8,
value: []const u8,
Expand Down
Loading

0 comments on commit b0efcd0

Please sign in to comment.