diff --git a/.dev/jit-debugging.md b/.dev/jit-debugging.md index c0fa15161..a441ad404 100644 --- a/.dev/jit-debugging.md +++ b/.dev/jit-debugging.md @@ -12,7 +12,7 @@ wrap in a minimal ELF, and disassemble with LLVM objdump. ```zig // Add temporarily in Compiler.finalize(), after @memcpy: if (condition) { // e.g. self.reg_count == 11 - const file = std.fs.cwd().createFile("/tmp/jit_dump.bin", .{}) catch null; + const file = std.Io.Dir.cwd().createFile("/tmp/jit_dump.bin", .{}) catch null; if (file) |f| { defer f.close(); f.writeAll(src_bytes) catch {}; } } ``` diff --git a/bench/fib_bench.zig b/bench/fib_bench.zig index 4cfb0d858..88cc1cfb0 100644 --- a/bench/fib_bench.zig +++ b/bench/fib_bench.zig @@ -4,10 +4,8 @@ const std = @import("std"); const types = @import("zwasm"); -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; // Parse optional argument for N var n: u64 = 35; @@ -35,8 +33,8 @@ pub fn main() !void { try stdout.flush(); } -fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); +fn readFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8) ![]const u8 { + const file = try std.Io.Dir.cwd().openFile(".", io, path); defer file.close(); const stat = try file.stat(); const data = try allocator.alloc(u8, stat.size); diff --git a/build.log b/build.log new file mode 100644 index 000000000..94e4b9f9c --- /dev/null +++ b/build.log @@ -0,0 +1,19 @@ +test ++- run test 398 pass, 3 skip, 1 fail, 1 timeout (403 total) +error: 'wasi.test.WASI — random_get fills buffer' timed out after 25.023s +error: 'leb128.test.overflow — value too large for u32' failed: + error: .{ .bytes = { 128, 128, 128, 128, 64 }, .pos = 0 }expected error.Overflow, found 0 + /nix/store/rcl9v3y5112v9qxs0xlm2qksabj08sdv-zig-0.16.0/lib/std/testing.zig:65:9: 0x158db09 in expectError__anon_76846 (std.zig) + return error.TestExpectedError; + ^ + /home/ewan/Documents/zig/zwasm/src/leb128.zig:307:5: 0x158dd27 in test.overflow — value too large for u32 (types.zig) + try testing.expectError(error.Overflow, r.readU32()); + ^ +failed command: ./.zig-cache/o/c89ca94fdf9ec0715edc8cf4c6f4f1ff/test --cache-dir=./.zig-cache --seed=0xab20887d --listen=- + +Build Summary: 2/4 steps succeeded (1 failed); 398/403 tests passed (3 skipped, 1 failed, 1 timed out) +test transitive failure ++- run test 398 pass, 3 skip, 1 fail, 1 timeout (403 total) + +error: the following build command failed with exit code 1: +.zig-cache/o/2fc72a677a00554ba15517cfc4b0e718/build /nix/store/rcl9v3y5112v9qxs0xlm2qksabj08sdv-zig-0.16.0/bin/zig /nix/store/rcl9v3y5112v9qxs0xlm2qksabj08sdv-zig-0.16.0/lib /home/ewan/Documents/zig/zwasm .zig-cache /home/ewan/.cache/zig --seed 0xab20887d -Zdd23c543245b78a8 test --test-timeout 25s diff --git a/build.zig b/build.zig index 708915434..43a64047d 100644 --- a/build.zig +++ b/build.zig @@ -206,6 +206,7 @@ pub fn build(b: *std.Build) void { .root_source_file = null, .target = target, .optimize = optimize, + .link_libc = true, }); ct_mod.addCSourceFile(.{ .file = b.path(ct.src) }); ct_mod.addIncludePath(b.path("include")); @@ -214,7 +215,6 @@ pub fn build(b: *std.Build) void { .name = ct.name, .root_module = ct_mod, }); - ct_exe.linkLibC(); // Install only via c-test step (not default install) to keep artifact count // below Zig 0.15.2 build runner shuffle bug threshold on some platforms. c_test_step.dependOn(&b.addInstallArtifact(ct_exe, .{}).step); diff --git a/docs/embedding.md b/docs/embedding.md index 8a60f0d0d..a64a3961e 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -23,7 +23,7 @@ const zwasm = @import("zwasm"); pub fn main() !void { const allocator = std.heap.page_allocator; - const wasm_bytes = try std.fs.cwd().readFileAlloc(allocator, "module.wasm", 10 * 1024 * 1024); + const wasm_bytes = try std.Io.Dir.cwd().readFileAlloc(allocator, "module.wasm", 10 * 1024 * 1024); defer allocator.free(wasm_bytes); var module = try zwasm.WasmModule.load(allocator, wasm_bytes); diff --git a/examples/zig/basic.zig b/examples/zig/basic.zig index eb98271a8..801fa40b1 100644 --- a/examples/zig/basic.zig +++ b/examples/zig/basic.zig @@ -31,7 +31,7 @@ pub fn main() !void { } fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); + const file = try std.Io.Dir.cwd().openFile(".", io, path); defer file.close(); const stat = try file.stat(); const data = try allocator.alloc(u8, stat.size); diff --git a/examples/zig/host_functions.zig b/examples/zig/host_functions.zig index 13bd5b691..f8f2847a1 100644 --- a/examples/zig/host_functions.zig +++ b/examples/zig/host_functions.zig @@ -65,7 +65,7 @@ pub fn main() !void { } fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); + const file = try std.Io.Dir.cwd().openFile(".", io, path); defer file.close(); const stat = try file.stat(); const data = try allocator.alloc(u8, stat.size); diff --git a/examples/zig/inspect.zig b/examples/zig/inspect.zig index c3cf7d9a2..b8fef9460 100644 --- a/examples/zig/inspect.zig +++ b/examples/zig/inspect.zig @@ -39,7 +39,7 @@ pub fn main() !void { } fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); + const file = try std.Io.Dir.cwd().openFile(".", io, path); defer file.close(); const stat = try file.stat(); const data = try allocator.alloc(u8, stat.size); diff --git a/examples/zig/memory.zig b/examples/zig/memory.zig index 82584dbf9..207e4f3b6 100644 --- a/examples/zig/memory.zig +++ b/examples/zig/memory.zig @@ -32,7 +32,7 @@ pub fn main() !void { } fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); + const file = try std.Io.Dir.cwd().openFile(".", io, path); defer file.close(); const stat = try file.stat(); const data = try allocator.alloc(u8, stat.size); diff --git a/examples/zig/wasi.zig b/examples/zig/wasi.zig index 53673b45f..e65eba3fe 100644 --- a/examples/zig/wasi.zig +++ b/examples/zig/wasi.zig @@ -31,7 +31,7 @@ pub fn main() !void { } fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); + const file = try std.Io.Dir.cwd().openFile(".", io, path); defer file.close(); const stat = try file.stat(); const data = try allocator.alloc(u8, stat.size); diff --git a/src/cache.zig b/src/cache.zig index 26174b358..b506ce03f 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -168,7 +168,9 @@ pub fn wasmHash(wasm_bin: []const u8) [32]u8 { pub fn getCacheDir(alloc: Allocator) ![]u8 { const path = try platform.appCacheDir(alloc, "zwasm"); // Ensure directory exists - std.fs.makeDirAbsolute(path) catch |err| switch (err) { + var io_instance: std.Io.Threaded = undefined; + const io = io_instance.io(); + std.Io.Dir.createDirAbsolute(io, path, .default_dir) catch |err| switch (err) { error.PathAlreadyExists => {}, else => { alloc.free(path); @@ -199,22 +201,26 @@ pub fn saveToFile(alloc: Allocator, hash: [32]u8, ir_funcs: []const ?*const IrFu defer alloc.free(data); const path = try getCachePath(alloc, hash); defer alloc.free(path); - const file = try std.fs.createFileAbsolute(path, .{}); - defer file.close(); - try file.writeAll(data); + var io_instance: std.Io.Threaded = undefined; + const io = io_instance.io(); + const file = try std.Io.Dir.createFileAbsolute(io, path, .{}); + defer file.close(io); + try file.writeStreamingAll(io, data); } /// Load cached IR from disk. Returns null on miss or mismatch. pub fn loadFromFile(alloc: Allocator, hash: [32]u8) !?[]?*IrFunc { const path = getCachePath(alloc, hash) catch return null; defer alloc.free(path); - const file = std.fs.openFileAbsolute(path, .{}) catch return null; - defer file.close(); - const stat = try file.stat(); + var io_instance: std.Io.Threaded = undefined; + const io = io_instance.io(); + const file = std.Io.Dir.openFileAbsolute(io, path, .{}) catch return null; + defer file.close(io); + const stat = try file.stat(io); if (stat.size > 256 * 1024 * 1024) return null; // sanity limit: 256 MB const data = try alloc.alloc(u8, stat.size); defer alloc.free(data); - const bytes_read = try file.readAll(data); + const bytes_read = try file.readPositionalAll(io, data, 0); if (bytes_read != stat.size) return null; return deserialize(alloc, data, hash); } diff --git a/src/cli.zig b/src/cli.zig index d7a7a7ee0..ae311b7e1 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -24,25 +24,24 @@ const guard_mod = @import("guard.zig"); const jit_mod = vm_mod.jit_mod; const cache_mod = @import("cache.zig"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { // Install signal handler for JIT guard page OOB traps if (comptime jit_mod.jitSupported()) { guard_mod.installSignalHandler(); } - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); + const allocator = init.gpa; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); + const args = try init.minimal.args.toSlice(allocator); + + const io = init.io; var buf: [8192]u8 = undefined; - var writer = std.fs.File.stdout().writer(&buf); + var writer = std.Io.File.stdout().writer(io, &buf); const stdout = &writer.interface; var err_buf: [4096]u8 = undefined; - var err_writer = std.fs.File.stderr().writer(&err_buf); + var err_writer = std.Io.File.stderr().writer(io, &err_buf); const stderr = &err_writer.interface; if (args.len < 2) { @@ -54,17 +53,17 @@ pub fn main() !void { const command = args[1]; if (std.mem.eql(u8, command, "run")) { - const ok = try cmdRun(allocator, args[2..], stdout, stderr); + const ok = try cmdRun(io, allocator, args[2..], stdout, stderr); try stdout.flush(); if (!ok) std.process.exit(1); } else if (std.mem.eql(u8, command, "inspect")) { - try cmdInspect(allocator, args[2..], stdout, stderr); + try cmdInspect(io, allocator, args[2..], stdout, stderr); } else if (std.mem.eql(u8, command, "validate")) { - const ok = try cmdValidate(allocator, args[2..], stdout, stderr); + const ok = try cmdValidate(io, allocator, args[2..], stdout, stderr); try stdout.flush(); if (!ok) std.process.exit(1); } else if (std.mem.eql(u8, command, "compile")) { - const ok = try cmdCompile(allocator, args[2..], stdout, stderr); + const ok = try cmdCompile(io, allocator, args[2..], stdout, stderr); try stdout.flush(); if (!ok) std.process.exit(1); } else if (std.mem.eql(u8, command, "features")) { @@ -75,7 +74,7 @@ pub fn main() !void { try stdout.print("zwasm {s}\n", .{build_options.version}); } else if (std.mem.endsWith(u8, command, ".wasm") or std.mem.endsWith(u8, command, ".wat")) { // zwasm file.wasm ... → shorthand for zwasm run file.wasm ... - const ok = try cmdRun(allocator, args[1..], stdout, stderr); + const ok = try cmdRun(io, allocator, args[1..], stdout, stderr); try stdout.flush(); if (!ok) std.process.exit(1); } else { @@ -130,7 +129,7 @@ fn printUsage(w: *std.Io.Writer) void { // zwasm run // ============================================================ -fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { +fn cmdRun(io: std.Io, allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { var invoke_name: ?[]const u8 = null; var wasm_path: ?[]const u8 = null; var func_args_start: usize = 0; @@ -315,7 +314,7 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer return false; }; - const wasm_bytes = readWasmFile(allocator, path) catch |err| { + const wasm_bytes = readWasmFile(io, allocator, path) catch |err| { try stderr.print("error: cannot read '{s}': {s}\n", .{ path, @errorName(err) }); try stderr.flush(); return false; @@ -324,7 +323,7 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer // Auto-detect component vs core module if (component_mod.isComponent(wasm_bytes)) { - return runComponent(allocator, wasm_bytes, stdout, stderr); + return runComponent(allocator, io, wasm_bytes, stdout, stderr); } // Load linked modules @@ -343,23 +342,23 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer defer import_entries.deinit(allocator); for (link_names.items, link_paths.items) |name, lpath| { - const link_bytes = readWasmFile(allocator, lpath) catch |err| { + const link_bytes = readWasmFile(io, allocator, lpath) catch |err| { try stderr.print("error: cannot read linked module '{s}': {s}\n", .{ lpath, @errorName(err) }); try stderr.flush(); return false; }; // Load with already-loaded linked modules as imports (transitive chains) const lm = if (import_entries.items.len > 0) - types.WasmModule.loadWithImports(allocator, link_bytes, import_entries.items) catch + types.WasmModule.loadWithImports(allocator, io, link_bytes, import_entries.items) catch // Retry without imports if the linked module doesn't need them - types.WasmModule.load(allocator, link_bytes) catch |err| { + types.WasmModule.load(allocator, io, link_bytes) catch |err| { allocator.free(link_bytes); try stderr.print("error: failed to load linked module '{s}': {s}\n", .{ lpath, formatWasmError(err) }); try stderr.flush(); return false; } else - types.WasmModule.load(allocator, link_bytes) catch |err| { + types.WasmModule.load(allocator, io, link_bytes) catch |err| { allocator.free(link_bytes); try stderr.print("error: failed to load linked module '{s}': {s}\n", .{ lpath, formatWasmError(err) }); try stderr.flush(); @@ -374,7 +373,7 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer } if (batch_mode) { - return cmdBatch(allocator, wasm_bytes, import_entries.items, link_names.items, linked_modules.items, stdout, stderr, trace_categories, dump_regir_func, dump_jit_func); + return cmdBatch(io, allocator, wasm_bytes, import_entries.items, link_names.items, linked_modules.items, stdout, stderr, trace_categories, dump_regir_func, dump_jit_func); } const imports_slice: ?[]const types.ImportEntry = if (import_entries.items.len > 0) @@ -397,9 +396,9 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer const module = load_blk: { if (imports_slice != null) { // With --link: try imports only, then imports + WASI - break :load_blk types.WasmModule.loadWithImports(allocator, wasm_bytes, imports_slice.?) catch |err| { + break :load_blk types.WasmModule.loadWithImports(allocator, io, wasm_bytes, imports_slice.?) catch |err| { if (err == error.ImportNotFound) { - break :load_blk types.WasmModule.loadWasiWithImports(allocator, wasm_bytes, imports_slice, wasi_opts) catch |err2| { + break :load_blk types.WasmModule.loadWasiWithImports(allocator, io, wasm_bytes, imports_slice, wasi_opts) catch |err2| { try stderr.print("error: failed to load module: {s}\n", .{formatWasmError(err2)}); try stderr.flush(); return false; @@ -411,9 +410,9 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer }; } // No --link: try plain, then WASI - break :load_blk types.WasmModule.load(allocator, wasm_bytes) catch |err| { + break :load_blk types.WasmModule.load(allocator, io, wasm_bytes) catch |err| { if (err == error.ImportNotFound) { - break :load_blk types.WasmModule.loadWasiWithOptions(allocator, wasm_bytes, wasi_opts) catch |err2| { + break :load_blk types.WasmModule.loadWasiWithOptions(allocator, io, wasm_bytes, wasi_opts) catch |err2| { try stderr.print("error: failed to load module: {s}\n", .{formatWasmError(err2)}); try stderr.flush(); return false; @@ -572,7 +571,7 @@ fn cmdRun(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer .preopen_paths = preopen_paths.items, .caps = caps, }; - var module = types.WasmModule.loadWasiWithImports(allocator, wasm_bytes, imports_slice, wasi_opts2) catch |err| { + var module = types.WasmModule.loadWasiWithImports(allocator, io, wasm_bytes, imports_slice, wasi_opts2) catch |err| { try stderr.print("error: failed to load WASI module: {s}\n", .{formatWasmError(err)}); try stderr.flush(); return false; @@ -654,7 +653,7 @@ const store_mod = @import("store.zig"); // zwasm compile // ============================================================ -fn cmdCompile(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { +fn cmdCompile(io: std.Io, allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { _ = stdout; if (args.len == 0) { try stderr.print("error: no wasm file specified\nUsage: zwasm compile \n", .{}); @@ -663,7 +662,7 @@ fn cmdCompile(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Wr } const path = args[0]; - const wasm_bytes = readWasmFile(allocator, path) catch |err| { + const wasm_bytes = readWasmFile(io, allocator, path) catch |err| { try stderr.print("error: cannot read '{s}': {s}\n", .{ path, @errorName(err) }); try stderr.flush(); return false; @@ -671,7 +670,7 @@ fn cmdCompile(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Wr defer allocator.free(wasm_bytes); // Load module (with WASI to handle any imports) - var module = types.WasmModule.loadWasi(allocator, wasm_bytes) catch |err| { + var module = types.WasmModule.loadWasi(allocator, io, wasm_bytes) catch |err| { try stderr.print("error: failed to load module: {s}\n", .{formatWasmError(err)}); try stderr.flush(); return false; @@ -714,7 +713,7 @@ fn cmdCompile(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Wr // Component Model execution // ============================================================ -fn runComponent(allocator: Allocator, wasm_bytes: []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { +fn runComponent(allocator: Allocator, io: std.Io, wasm_bytes: []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { _ = stdout; // Decode the component @@ -731,7 +730,7 @@ fn runComponent(allocator: Allocator, wasm_bytes: []const u8, stdout: *std.Io.Wr var instance = component_mod.ComponentInstance.init(allocator, &comp); defer instance.deinit(); - instance.instantiate() catch |err| { + instance.instantiate(io) catch |err| { try stderr.print("error: failed to instantiate component: {s}\n", .{formatWasmError(err)}); try stderr.flush(); return false; @@ -965,7 +964,7 @@ fn miscOpcodeName(sub: u8) []const u8 { // zwasm inspect // ============================================================ -fn cmdInspect(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !void { +fn cmdInspect(io: std.Io, allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !void { var json_mode = false; var path: ?[]const u8 = null; @@ -982,7 +981,7 @@ fn cmdInspect(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Wr try stderr.flush(); return; }; - const wasm_bytes = readWasmFile(allocator, file_path) catch |err| { + const wasm_bytes = readWasmFile(io, allocator, file_path) catch |err| { try stderr.print("error: cannot read '{s}': {s}\n", .{ file_path, @errorName(err) }); try stderr.flush(); return; @@ -1234,16 +1233,16 @@ fn threadRunner(ctx: *ThreadCtx) void { /// Batch mode: read invocations from stdin, one per line. /// Protocol: "invoke [arg1 arg2 ...]" /// Output: "ok [val1 val2 ...]" or "error " -fn cmdBatch(allocator: Allocator, wasm_bytes: []const u8, imports: []const types.ImportEntry, link_names: []const []const u8, linked_modules: []const *types.WasmModule, stdout: *std.Io.Writer, stderr: *std.Io.Writer, trace_categories: u8, dump_regir_func: ?u32, dump_jit_func: ?u32) !bool { +fn cmdBatch(io: std.Io, allocator: Allocator, wasm_bytes: []const u8, imports: []const types.ImportEntry, link_names: []const []const u8, linked_modules: []const *types.WasmModule, stdout: *std.Io.Writer, stderr: *std.Io.Writer, trace_categories: u8, dump_regir_func: ?u32, dump_jit_func: ?u32) !bool { _ = stderr; var module = if (imports.len > 0) - types.WasmModule.loadWithImports(allocator, wasm_bytes, imports) catch |err| { + types.WasmModule.loadWithImports(allocator, io, wasm_bytes, imports) catch |err| { try stdout.print("error load {s}\n", .{@errorName(err)}); try stdout.flush(); return false; } else - types.WasmModule.load(allocator, wasm_bytes) catch |err| { + types.WasmModule.load(allocator, io, wasm_bytes) catch |err| { try stdout.print("error load {s}\n", .{@errorName(err)}); try stdout.flush(); return false; @@ -1260,10 +1259,10 @@ fn cmdBatch(allocator: Allocator, wasm_bytes: []const u8, imports: []const types module.vm.trace = &batch_trace_config; } - const stdin = std.fs.File.stdin(); - var read_buf: [8192]u8 = undefined; - var reader = stdin.reader(&read_buf); - const r = &reader.interface; + const stdin = std.Io.File.stdin(); + var stdin_buf: [4096]u8 = undefined; + var reader = stdin.reader(io, &stdin_buf); + const r = &reader; // Reusable buffers for args/results (400+ params needed for func-400-params test) var arg_buf: [512]u64 = undefined; @@ -1309,11 +1308,12 @@ fn cmdBatch(allocator: Allocator, wasm_bytes: []const u8, imports: []const types } while (true) { - const raw_line = r.takeDelimiter('\n') catch |err| switch (err) { + const raw_line = r.interface.takeDelimiter('\n') catch |err| switch (err) { error.StreamTooLong => continue, else => break, - } orelse break; - const line = std.mem.trimRight(u8, raw_line, "\r"); + } orelse continue; + defer allocator.free(raw_line); + const line = std.mem.trimEnd(u8, raw_line, "\r"); // Skip empty lines if (line.len == 0) continue; @@ -1364,7 +1364,7 @@ fn cmdBatch(allocator: Allocator, wasm_bytes: []const u8, imports: []const types continue; }; const load_path = after_load[sp + 1 ..]; - const load_bytes = readWasmFile(allocator, load_path) catch { + const load_bytes = readWasmFile(io, allocator, load_path) catch { try stdout.print("error cannot read file\n", .{}); try stdout.flush(); continue; @@ -1488,8 +1488,8 @@ fn cmdBatch(allocator: Allocator, wasm_bytes: []const u8, imports: []const types }; // Buffer invocations until thread_end while (true) { - const raw_tline = r.takeDelimiter('\n') catch break orelse break; - const tline = std.mem.trimRight(u8, raw_tline, "\r"); + const raw_tline = r.interface.takeDelimiter('\n') catch break orelse break; + const tline = std.mem.trimEnd(u8, raw_tline, "\r"); if (std.mem.eql(u8, tline, "thread_end")) break; if (!std.mem.startsWith(u8, tline, "invoke ")) continue; // Parse: invoke : [args...] @@ -1862,7 +1862,7 @@ fn cmdBatch(allocator: Allocator, wasm_bytes: []const u8, imports: []const types // zwasm validate // ============================================================ -fn cmdValidate(allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { +fn cmdValidate(io: std.Io, allocator: Allocator, args: []const []const u8, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !bool { if (args.len < 1) { try stderr.print("error: no wasm file specified\n", .{}); try stderr.flush(); @@ -1870,7 +1870,7 @@ fn cmdValidate(allocator: Allocator, args: []const []const u8, stdout: *std.Io.W } const path = args[0]; - const wasm_bytes = readWasmFile(allocator, path) catch |err| { + const wasm_bytes = readWasmFile(io, allocator, path) catch |err| { try stderr.print("error: validation failed: {s}: {s}\n", .{ path, formatWasmError(err) }); try stderr.flush(); return false; @@ -2056,13 +2056,25 @@ test "features list has expected entries" { try testing.expect(total >= 398); } -fn readFile(allocator: Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - const stat = try file.stat(); +fn readFile(io: std.Io, allocator: Allocator, path: []const u8) ![]const u8 { + const file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer file.close(io); + const stat = try file.stat(io); const data = try allocator.alloc(u8, stat.size); - const read = try file.readAll(data); - return data[0..read]; + errdefer allocator.free(data); + + var offset: usize = 0; + var temp_buf: [8192]u8 = undefined; + + while (offset < stat.size) { + const to_read = @min(temp_buf.len, stat.size - offset); + const bytes_read = try file.readPositional(io, &.{temp_buf[0..to_read]}, offset); + if (bytes_read == 0) break; + @memcpy(data[offset .. offset + bytes_read], temp_buf[0..bytes_read]); + offset += bytes_read; + } + + return data; } fn isWatFile(path: []const u8) bool { @@ -2071,8 +2083,8 @@ fn isWatFile(path: []const u8) bool { /// Read a file and convert WAT to wasm binary if needed. /// Returns wasm bytes owned by caller. -fn readWasmFile(allocator: Allocator, path: []const u8) ![]const u8 { - const file_bytes = try readFile(allocator, path); +fn readWasmFile(io: std.Io, allocator: Allocator, path: []const u8) ![]const u8 { + const file_bytes = try readFile(io, allocator, path); if (isWatFile(path)) { defer allocator.free(file_bytes); if (!build_options.enable_wat) return error.WatNotEnabled; diff --git a/src/component.zig b/src/component.zig index 78c95c89c..c3369d55c 100644 --- a/src/component.zig +++ b/src/component.zig @@ -22,10 +22,10 @@ pub const SectionId = enum(u8) { component = 4, instance = 5, alias = 6, - @"type" = 7, + type = 7, canonical = 8, start = 9, - @"import" = 10, + import = 10, @"export" = 11, _, }; @@ -36,7 +36,7 @@ pub const ExternKind = enum(u8) { core_module = 0x00, func = 0x01, value = 0x02, - @"type" = 0x03, + type = 0x03, component = 0x04, instance = 0x05, _, @@ -370,10 +370,10 @@ pub const Component = struct { .core_module => { self.core_modules.append(self.alloc, payload) catch return error.OutOfMemory; }, - .@"type" => { + .type => { self.decodeTypeSection(payload) catch {}; }, - .@"import" => { + .import => { self.decodeImportSection(payload) catch {}; }, .@"export" => { @@ -875,13 +875,12 @@ pub const WasiAdapter = struct { .p2_interface = "wasi:filesystem/types", .p1_module = "wasi_snapshot_preview1", .p1_functions = &.{ - "fd_read", "fd_write", "fd_close", - "fd_seek", "fd_tell", "fd_sync", - "fd_filestat_get", "fd_readdir", "path_open", - "path_create_directory", - "path_remove_directory", - "path_unlink_file", "path_rename", "path_filestat_get", - "fd_prestat_get", "fd_prestat_dir_name", + "fd_read", "fd_write", "fd_close", + "fd_seek", "fd_tell", "fd_sync", + "fd_filestat_get", "fd_readdir", "path_open", + "path_create_directory", "path_remove_directory", "path_unlink_file", + "path_rename", "path_filestat_get", "fd_prestat_get", + "fd_prestat_dir_name", }, }, .{ @@ -1141,10 +1140,10 @@ pub const ComponentInstance = struct { /// Instantiate the component: load embedded core modules, /// process core instance declarations, and build export map. - pub fn instantiate(self: *ComponentInstance) !void { + pub fn instantiate(self: *ComponentInstance, io: std.Io) !void { // Phase 1: Load embedded core modules for (self.comp.core_modules.items) |mod_bytes| { - const m = try types.WasmModule.load(self.alloc, mod_bytes); + const m = try types.WasmModule.load(self.alloc, io, mod_bytes); self.core_modules.append(self.alloc, m) catch return error.OutOfMemory; } @@ -1212,9 +1211,9 @@ test "isComponent — identifies component vs module" { test "SectionId — enum values" { try std.testing.expectEqual(@as(u8, 0), @intFromEnum(SectionId.core_custom)); try std.testing.expectEqual(@as(u8, 1), @intFromEnum(SectionId.core_module)); - try std.testing.expectEqual(@as(u8, 7), @intFromEnum(SectionId.@"type")); + try std.testing.expectEqual(@as(u8, 7), @intFromEnum(SectionId.type)); try std.testing.expectEqual(@as(u8, 8), @intFromEnum(SectionId.canonical)); - try std.testing.expectEqual(@as(u8, 10), @intFromEnum(SectionId.@"import")); + try std.testing.expectEqual(@as(u8, 10), @intFromEnum(SectionId.import)); try std.testing.expectEqual(@as(u8, 11), @intFromEnum(SectionId.@"export")); } @@ -1262,12 +1261,15 @@ test "Component.decode — multiple sections" { 0x00, 0x61, 0x73, 0x6D, // magic 0x0D, 0x00, 0x01, 0x00, // component version // Section 1: core_module (id=1), size=8 - 0x01, 0x08, - 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x00, 0x61, + 0x73, 0x6D, 0x01, 0x00, + 0x00, 0x00, // Section 2: type (id=7), size=2, payload=dummy - 0x07, 0x02, 0xAA, 0xBB, + 0x07, 0x02, + 0xAA, 0xBB, // Section 3: canonical (id=8), size=1, payload=dummy - 0x08, 0x01, 0xCC, + 0x08, 0x01, + 0xCC, }; var comp = Component.init(std.testing.allocator, &bytes); defer comp.deinit(); @@ -1275,7 +1277,7 @@ test "Component.decode — multiple sections" { try std.testing.expectEqual(@as(usize, 3), comp.sections.items.len); try std.testing.expectEqual(SectionId.core_module, comp.sections.items[0].id); - try std.testing.expectEqual(SectionId.@"type", comp.sections.items[1].id); + try std.testing.expectEqual(SectionId.type, comp.sections.items[1].id); try std.testing.expectEqual(SectionId.canonical, comp.sections.items[2].id); try std.testing.expectEqual(@as(usize, 1), comp.core_modules.items.len); } @@ -1295,7 +1297,7 @@ test "Component.decode — truncated section" { test "ExternKind — enum values" { try std.testing.expectEqual(@as(u8, 0x00), @intFromEnum(ExternKind.core_module)); try std.testing.expectEqual(@as(u8, 0x01), @intFromEnum(ExternKind.func)); - try std.testing.expectEqual(@as(u8, 0x03), @intFromEnum(ExternKind.@"type")); + try std.testing.expectEqual(@as(u8, 0x03), @intFromEnum(ExternKind.type)); try std.testing.expectEqual(@as(u8, 0x05), @intFromEnum(ExternKind.instance)); } @@ -1309,11 +1311,14 @@ test "Component.decode — type section with primitive defined types" { 0x07, // section size: 7 bytes 0x03, // count: 3 types // Type 0: defined(bool) - 0x40, 0x7f, + 0x40, + 0x7f, // Type 1: defined(string) - 0x40, 0x73, + 0x40, + 0x73, // Type 2: defined(u32) - 0x40, 0x79, + 0x40, + 0x79, }; var comp = Component.init(std.testing.allocator, &bytes); defer comp.deinit(); @@ -1334,7 +1339,8 @@ test "Component.decode — type section with list and option" { 0x09, // section size: 9 bytes 0x03, // count: 3 types // Type 0: defined(u8) - 0x40, 0x7d, + 0x40, + 0x7d, // Type 1: defined(list) 0x40, 0x70, 0x00, // list of type index 0 // Type 2: defined(option) @@ -1359,11 +1365,14 @@ test "Component.decode — type section with result type" { 0x0A, // section size 0x03, // count: 3 types // Type 0: defined(u32) - 0x40, 0x79, + 0x40, + 0x79, // Type 1: defined(string) - 0x40, 0x73, + 0x40, + 0x73, // Type 2: defined(result) - 0x40, 0x6b, + 0x40, + 0x6b, 0x00, 0x00, // ok = present, type idx 0 0x00, 0x01, // err = present, type idx 1 }; @@ -1386,7 +1395,8 @@ test "Component.decode — func type" { 0x10, // section size 0x02, // count: 2 types // Type 0: defined(string) - 0x40, 0x73, + 0x40, + 0x73, // Type 1: func(name: type0) -> type0 0x41, 0x01, // 1 param @@ -1480,11 +1490,17 @@ test "Component.decode — canonical resource ops" { 0x0A, // section size 0x03, // count: 3 canon funcs // resource.new(type=0) - 0x02, 0x00, 0x00, + 0x02, + 0x00, + 0x00, // resource.drop(type=0) - 0x03, 0x00, 0x00, + 0x03, + 0x00, + 0x00, // resource.rep(type=0) - 0x04, 0x00, 0x00, + 0x04, + 0x00, + 0x00, }; var comp = Component.init(std.testing.allocator, &bytes); defer comp.deinit(); @@ -1782,7 +1798,14 @@ test "integration: full decode → instantiate pipeline" { // export section (id=11): 1 func export "compute" 0x0B, 0x0D, 0x01, 0x00, // kebab discriminant - 0x07, 'c', 'o', 'm', 'p', 'u', 't', 'e', + 0x07, + 'c', + 'o', + 'm', + 'p', + 'u', + 't', + 'e', 0x01, // kind: func 0x00, // idx: 0 }; @@ -1824,7 +1847,8 @@ test "integration: component with multiple sections roundtrip" { 0x00, // type idx 0 0x01, 0x00, // result: single unnamed type 0 // canonical section: lift core_func=0, opts=[utf8, memory=0] - 0x08, 0x08, 0x01, + 0x08, 0x08, + 0x01, 0x00, 0x00, 0x00, // lift, sub=0x00, core_func_idx=0 0x02, 0x00, 0x03, 0x00, // 2 opts: utf8, memory=0 }; diff --git a/src/gc.zig b/src/gc.zig index 5fa2a41d5..a868600b6 100644 --- a/src/gc.zig +++ b/src/gc.zig @@ -61,7 +61,7 @@ const FieldArena = struct { cursor: usize, // next free position in current page fn init(a: Allocator) FieldArena { - return .{ .pages = .{}, .alloc = a, .cursor = ARENA_PAGE_SLOTS }; + return .{ .pages = .empty, .alloc = a, .cursor = ARENA_PAGE_SLOTS }; } fn deinit(self: *FieldArena) void { @@ -660,7 +660,6 @@ test "subtype checking — concrete subtype chain" { } test "struct VM integration — struct.new + struct.get" { - const Instance = @import("instance.zig").Instance; const Vm = @import("vm.zig").Vm; @@ -680,7 +679,9 @@ test "struct VM integration — struct.new + struct.get" { // Function section 0x03, 0x02, 0x01, 0x01, // 1 func, type idx 1 // Export section - 0x07, 0x09, 0x01, 0x05, 's', 't', 'e', 's', 't', 0x00, 0x00, + 0x07, 0x09, 0x01, 0x05, + 's', 't', 'e', 's', + 't', 0x00, 0x00, // Code section (body: 1+2+2+3+4+1 = 13 bytes, section: 1+1+13 = 15) 0x0A, 0x0F, // section id=10, size=15 0x01, 0x0D, // 1 body, size=13 @@ -715,7 +716,6 @@ test "struct VM integration — struct.new + struct.get" { } test "struct VM integration — struct.new_default + struct.set + struct.get" { - const Instance = @import("instance.zig").Instance; const Vm = @import("vm.zig").Vm; @@ -731,7 +731,9 @@ test "struct VM integration — struct.new_default + struct.set + struct.get" { // Function section 0x03, 0x02, 0x01, 0x01, // 1 func, type 1 // Export section - 0x07, 0x0A, 0x01, 0x06, 's', 't', 'e', 's', 't', '2', 0x00, 0x00, + 0x07, 0x0A, 0x01, 0x06, + 's', 't', 'e', 's', + 't', '2', 0x00, 0x00, // Code section (body=21, section=1+1+21=23) 0x0A, 0x17, // section 10, size=23 0x01, 0x15, // 1 body, size=21 @@ -765,7 +767,6 @@ test "struct VM integration — struct.new_default + struct.set + struct.get" { } test "array VM integration — array.new + array.get + array.len" { - const Instance = @import("instance.zig").Instance; const Vm = @import("vm.zig").Vm; @@ -774,17 +775,18 @@ test "array VM integration — array.new + array.get + array.len" { const wasm = [_]u8{ 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, // Type section (size=10): 2 types - 0x01, 0x0A, - 0x02, + 0x01, 0x0A, 0x02, 0x5E, 0x7F, 0x01, // array (mut i32) 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F, // func (i32 i32) -> (i32) // Function section 0x03, 0x02, 0x01, 0x01, // Export section: "atest" - 0x07, 0x09, 0x01, 0x05, 'a', 't', 'e', 's', 't', 0x00, 0x00, + 0x07, 0x09, + 0x01, 0x05, 'a', 't', 'e', 's', + 't', 0x00, 0x00, // Code section (body = 1+2+2+3+2+3+1 = 14, section = 1+1+14 = 16) - 0x0A, 0x10, - 0x01, 0x0E, + 0x0A, 0x10, 0x01, + 0x0E, 0x00, // 0 locals 0x20, 0x00, // local.get 0 (init_val) 0x20, 0x01, // local.get 1 (len) @@ -814,7 +816,6 @@ test "array VM integration — array.new + array.get + array.len" { } test "i31 VM integration — ref.i31 + i31.get_s round-trip" { - const Instance = @import("instance.zig").Instance; const Vm = @import("vm.zig").Vm; @@ -872,7 +873,6 @@ test "i31 VM integration — ref.i31 + i31.get_s round-trip" { } test "cast VM integration — ref.test i31ref against i31" { - const Instance = @import("instance.zig").Instance; const Vm = @import("vm.zig").Vm; @@ -889,7 +889,8 @@ test "cast VM integration — ref.test i31ref against i31" { // Function section 0x03, 0x02, 0x01, 0x00, // Export section: "rt" -> func 0 - 0x07, 0x06, 0x01, 0x02, 'r', 't', 0x00, 0x00, + 0x07, 0x06, 0x01, 0x02, + 'r', 't', 0x00, 0x00, // Code section 0x0A, 0x0B, // section id=10, size=11 0x01, // 1 body @@ -925,7 +926,6 @@ test "cast VM integration — ref.test i31ref against i31" { } test "cast VM integration — ref.cast null traps" { - const Instance = @import("instance.zig").Instance; const Vm = @import("vm.zig").Vm; @@ -939,9 +939,11 @@ test "cast VM integration — ref.cast null traps" { // Type section: () -> (i32) 0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7F, // Function section - 0x03, 0x02, 0x01, 0x00, + 0x03, + 0x02, 0x01, 0x00, // Export section: "rc" -> func 0 - 0x07, 0x06, 0x01, 0x02, 'r', 'c', 0x00, 0x00, + 0x07, 0x06, 0x01, 0x02, 'r', + 'c', 0x00, 0x00, // Code section 0x0A, 0x0B, // section id=10, size=11 0x01, // 1 body diff --git a/src/guard.zig b/src/guard.zig index ce469bf0a..8cbb34a24 100644 --- a/src/guard.zig +++ b/src/guard.zig @@ -21,6 +21,71 @@ const windows = std.os.windows; const kernel32 = std.os.windows.kernel32; +/// Custom ucontext_t definitions for Zig 0.16.0 (ucontext_t was removed from std) +/// Define minimal structures needed for signal handler PC/return register access +const UContext = if (builtin.os.tag == .linux) Linux.UContext else if (builtin.os.tag == .macos or builtin.os.tag == .ios) Darwin.UContext else @compileError("unsupported OS for guard pages"); + +const Linux = if (builtin.os.tag == .linux) struct { + const UContext = extern struct { + mcontext: MContext, + // ... other fields we don't use + }; + + const MContext = if (builtin.cpu.arch == .x86_64) extern struct { + gregs: [23]c_long, + // gregs[16] is RIP + } else if (builtin.cpu.arch == .aarch64) extern struct { + pc: u64, + regs: [31]u64, + sp: u64, + pstate: u64, + } else @compileError("unsupported Linux arch for guard pages"); +} else struct {}; + +const Darwin = if (builtin.os.tag == .macos or builtin.os.tag == .ios) struct { + const UContext = extern struct { + mcontext: MContext, + }; + + const MContext = if (builtin.cpu.arch == .x86_64) extern struct { + ss: extern struct { + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rsp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rip: u64, + rflags: u64, + // ... additional fields not used + }, + } else if (builtin.cpu.arch == .aarch64) extern struct { + ss: extern struct { + regs: [31]u64, + sp: u64, + lr: u64, + pc: u64, + cpsr: u32, + __pad: u32, + fpsimd: extern struct { + vregs: [32 * 2]u64, + fpsr: u32, + fpcr: u32, + }, + }, + } else @compileError("unsupported Darwin arch for guard pages"); +} else struct {}; + /// Guard region size: 4 GiB + 64 KiB. /// This ensures any 32-bit index (0..0xFFFFFFFF) + small offset (up to 64 KiB) /// falls within the mapped region (data + guard). @@ -134,7 +199,12 @@ pub fn installSignalHandler() void { } const handler_fn = struct { - fn handler(_: i32, _: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void { + fn handler( + sig: if (builtin.os.tag == .linux) std.os.linux.SIG else c_int, + _: *const std.posix.siginfo_t, + ctx_ptr: ?*anyopaque, + ) callconv(.c) void { + _ = sig; // Handle any signal type const rec = getRecovery(); if (!rec.active) { // Not in JIT code — re-raise with default handler @@ -143,7 +213,7 @@ pub fn installSignalHandler() void { } // Verify faulting PC is within JIT code buffer // Kernel may place ucontext at non-16-byte-aligned address. - const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr.?); + const ctx: *align(1) UContext = @ptrCast(ctx_ptr.?); const faulting_pc = getPc(ctx); if (faulting_pc < rec.jit_code_start or faulting_pc >= rec.jit_code_end) { // PC not in JIT code — not our fault @@ -159,9 +229,9 @@ pub fn installSignalHandler() void { } }.handler; - const act = posix.Sigaction{ - .handler = .{ .sigaction = handler_fn }, - .mask = std.mem.zeroes(posix.sigset_t), + const act = std.posix.Sigaction{ + .handler = .{ .sigaction = @ptrCast(&handler_fn) }, + .mask = std.mem.zeroes(std.posix.sigset_t), .flags = if (comptime builtin.os.tag == .macos or builtin.os.tag == .ios) @as(c_uint, 0x40) // SA_SIGINFO on macOS else @@ -170,9 +240,9 @@ pub fn installSignalHandler() void { // Install for SIGBUS (macOS mmap faults) and SIGSEGV (Linux mmap faults) if (comptime builtin.os.tag == .macos or builtin.os.tag == .ios) { - posix.sigaction(posix.SIG.BUS, &act, null); + std.posix.sigaction(std.posix.SIG.BUS, &act, null); } else { - posix.sigaction(posix.SIG.SEGV, &act, null); + std.posix.sigaction(std.posix.SIG.SEGV, &act, null); } } @@ -210,19 +280,19 @@ fn windowsHandler(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { fn resetAndReraise() void { // Reset to default handler and re-raise — this will crash as expected - const default_act = posix.Sigaction{ - .handler = .{ .handler = posix.SIG.DFL }, - .mask = std.mem.zeroes(posix.sigset_t), + const default_act = std.posix.Sigaction{ + .handler = .{ .handler = std.posix.SIG.DFL }, + .mask = std.mem.zeroes(std.posix.sigset_t), .flags = 0, }; if (comptime builtin.os.tag == .macos or builtin.os.tag == .ios) { - posix.sigaction(posix.SIG.BUS, &default_act, null); + std.posix.sigaction(std.posix.SIG.BUS, &default_act, null); } else { - posix.sigaction(posix.SIG.SEGV, &default_act, null); + std.posix.sigaction(std.posix.SIG.SEGV, &default_act, null); } } -fn getFaultAddress(info: *const posix.siginfo_t) usize { +fn getFaultAddress(info: *const std.posix.siginfo_t) usize { if (comptime builtin.os.tag == .macos or builtin.os.tag == .ios) { return @intFromPtr(info.addr); } else { @@ -231,7 +301,7 @@ fn getFaultAddress(info: *const posix.siginfo_t) usize { } } -fn getPc(ctx: *align(1) posix.ucontext_t) usize { +fn getPc(ctx: *align(1) UContext) usize { if (comptime builtin.cpu.arch == .aarch64) { if (comptime builtin.os.tag == .macos) { return ctx.mcontext.ss.pc; @@ -242,14 +312,14 @@ fn getPc(ctx: *align(1) posix.ucontext_t) usize { if (comptime builtin.os.tag == .macos) { return ctx.mcontext.ss.rip; } else { - return ctx.mcontext.gregs[16]; // REG_RIP on Linux + return @intCast(@as(u64, @bitCast(@as(i64, ctx.mcontext.gregs[16])))); // REG_RIP on Linux (c_long to u64) } } else { @compileError("unsupported arch for guard pages"); } } -fn setPc(ctx: *align(1) posix.ucontext_t, pc: usize) void { +fn setPc(ctx: *align(1) UContext, pc: usize) void { if (comptime builtin.cpu.arch == .aarch64) { if (comptime builtin.os.tag == .macos) { ctx.mcontext.ss.pc = pc; @@ -260,12 +330,12 @@ fn setPc(ctx: *align(1) posix.ucontext_t, pc: usize) void { if (comptime builtin.os.tag == .macos) { ctx.mcontext.ss.rip = pc; } else { - ctx.mcontext.gregs[16] = pc; // REG_RIP on Linux + ctx.mcontext.gregs[16] = @bitCast(@as(i64, @intCast(pc))); // REG_RIP on Linux (u64 to c_long) } } } -fn setReturnReg(ctx: *align(1) posix.ucontext_t, value: u64) void { +fn setReturnReg(ctx: *align(1) UContext, value: u64) void { if (comptime builtin.cpu.arch == .aarch64) { if (comptime builtin.os.tag == .macos) { ctx.mcontext.ss.regs[0] = value; // x0 @@ -276,7 +346,7 @@ fn setReturnReg(ctx: *align(1) posix.ucontext_t, value: u64) void { if (comptime builtin.os.tag == .macos) { ctx.mcontext.ss.rax = value; } else { - ctx.mcontext.gregs[13] = value; // RAX on Linux + ctx.mcontext.gregs[13] = @bitCast(@as(i64, @intCast(value))); // RAX on Linux (u64 to c_long) } } } diff --git a/src/instance.zig b/src/instance.zig index 831cb1995..04c930f02 100644 --- a/src/instance.zig +++ b/src/instance.zig @@ -692,24 +692,23 @@ pub fn evalInitExpr(expr: []const u8, instance: *Instance) !u128 { const testing = std.testing; -fn readTestFile(alloc: Allocator, name: []const u8) ![]const u8 { +fn readTestFile(alloc: Allocator, io: std.Io, name: []const u8) ![]const u8 { const prefixes = [_][]const u8{ "src/testdata/", "testdata/", "src/wasm/testdata/" }; for (prefixes) |prefix| { const path = try std.fmt.allocPrint(alloc, "{s}{s}", .{ prefix, name }); defer alloc.free(path); - const file = std.fs.cwd().openFile(path, .{}) catch continue; - defer file.close(); - const stat = try file.stat(); + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch continue; + defer file.close(io); + const stat = try file.stat(io); const data = try alloc.alloc(u8, stat.size); - const read = try file.readAll(data); + const read = try file.readPositionalAll(io, data, 0); return data[0..read]; } return error.FileNotFound; } - test "Instance — instantiate 01_add.wasm" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -732,7 +731,7 @@ test "Instance — instantiate 01_add.wasm" { } test "Instance — instantiate 03_memory.wasm" { - const wasm = try readTestFile(testing.allocator, "03_memory.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "03_memory.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -753,7 +752,7 @@ test "Instance — instantiate 03_memory.wasm" { } test "Instance — instantiate 04_imports.wasm with host functions" { - const wasm = try readTestFile(testing.allocator, "04_imports.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "04_imports.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -768,10 +767,8 @@ test "Instance — instantiate 04_imports.wasm with host functions" { fn f(_: *anyopaque, _: usize) anyerror!void {} }.f)); - try store.exposeHostFunction("env", "print_i32", dummy_fn, 0, - &[_]ValType{.i32}, &[_]ValType{}); - try store.exposeHostFunction("env", "print_str", dummy_fn, 0, - &[_]ValType{ .i32, .i32 }, &[_]ValType{}); + try store.exposeHostFunction("env", "print_i32", dummy_fn, 0, &[_]ValType{.i32}, &[_]ValType{}); + try store.exposeHostFunction("env", "print_str", dummy_fn, 0, &[_]ValType{ .i32, .i32 }, &[_]ValType{}); var inst = Instance.init(testing.allocator, &store, &mod); defer inst.deinit(); @@ -787,7 +784,7 @@ test "Instance — instantiate 04_imports.wasm with host functions" { } test "Instance — instantiate 06_globals.wasm" { - const wasm = try readTestFile(testing.allocator, "06_globals.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "06_globals.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -806,7 +803,7 @@ test "Instance — instantiate 06_globals.wasm" { } test "Instance — missing import returns error" { - const wasm = try readTestFile(testing.allocator, "04_imports.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "04_imports.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -823,7 +820,7 @@ test "Instance — missing import returns error" { } test "Import validation — type mismatch" { - const wasm = try readTestFile(testing.allocator, "04_imports.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "04_imports.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); diff --git a/src/leb128.zig b/src/leb128.zig index 33ca24aa3..15b0229f1 100644 --- a/src/leb128.zig +++ b/src/leb128.zig @@ -97,29 +97,68 @@ pub const Reader = struct { } }; -/// Internal: adapter that makes Reader work with std.leb functions. -const ByteReader = struct { - reader: *Reader, +/// Decode unsigned LEB128 value +fn readUleb128(comptime T: type, reader: *Reader) Error!T { + const U = if (@typeInfo(T).int.bits < 8) u8 else T; + const ShiftT = std.math.Log2Int(U); - pub fn readByte(self: *ByteReader) Error!u8 { - return self.reader.readByte(); + const max_group = (@typeInfo(U).int.bits + 6) / 7; + + var value: U = 0; + var group: ShiftT = 0; + + while (group < max_group) : (group += 1) { + const byte = try reader.readByte(); + + const ov = @shlWithOverflow(@as(U, byte & 0x7f), group * 7); + if (ov[1] != 0) return error.Overflow; + + value |= ov[0]; + if (byte & 0x80 == 0) break; + } else { + return error.Overflow; } -}; + + // only applies in the case that we extended to u8 + if (U != T) { + if (value > std.math.maxInt(T)) return error.Overflow; + } + + return @as(T, @truncate(value)); +} + +/// Decode signed LEB128 value (two's complement with sign extension) +fn readIleb128(comptime T: type, reader: *Reader) Error!T { + var result: T = 0; + var shift: u8 = 0; + var byte: u8 = undefined; + + while (true) { + byte = try reader.readByte(); + const value: T = @intCast(byte & 0x7f); + const shift_amount: std.math.Log2Int(T) = @intCast(shift); + result |= value << shift_amount; + shift += 7; + + if ((byte & 0x80) == 0) break; + if (shift >= @bitSizeOf(T)) return error.Overflow; + } + + // Sign extend if the sign bit is set + if (shift < @bitSizeOf(T) and (byte & 0x40) != 0) { + const shift_amount: std.math.Log2Int(T) = @intCast(shift); + result |= -(@as(T, 1) << shift_amount); + } + + return result; +} fn readUnsigned(comptime T: type, reader: *Reader) Error!T { - var br = ByteReader{ .reader = reader }; - return std.leb.readUleb128(T, &br) catch |err| switch (err) { - error.Overflow => return error.Overflow, - error.EndOfStream => return error.EndOfStream, - }; + return readUleb128(T, reader); } fn readSigned(comptime T: type, reader: *Reader) Error!T { - var br = ByteReader{ .reader = reader }; - return std.leb.readIleb128(T, &br) catch |err| switch (err) { - error.Overflow => return error.Overflow, - error.EndOfStream => return error.EndOfStream, - }; + return readIleb128(T, reader); } // ============================================================ @@ -192,7 +231,7 @@ test "readU64 — large values" { test "readI64 — min/max" { // -1 - var r = Reader.init(&[_]u8{ 0x7F }); + var r = Reader.init(&[_]u8{0x7F}); try testing.expectEqual(@as(i64, -1), try r.readI64()); // i64 min (-0x8000000000000000) diff --git a/src/memory.zig b/src/memory.zig index 891b6941b..f167a83c1 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -17,15 +17,22 @@ const guard_mod = @import("guard.zig"); pub const PAGE_SIZE: u32 = 64 * 1024; // 64 KiB pub const MAX_PAGES: u32 = 64 * 1024; // 4 GiB theoretical max +/// Get current monotonic time in nanoseconds (helper for Zig 0.16.0+) +fn getNanoTimestamp() i64 { + var ts: std.posix.timespec = undefined; + const result = std.posix.system.clock_gettime(std.posix.CLOCK.MONOTONIC, &ts); + if (result != 0) return 0; + return @as(i64, @intCast(ts.sec)) * std.time.ns_per_s + @as(i64, @intCast(ts.nsec)); +} + /// Per-address wait queue for memory.atomic.wait/notify. -/// Uses a simple list of condition variables keyed by address. +/// Uses a simple list of atomic notified flags keyed by address. pub const WaitQueue = struct { - mutex: std.Thread.Mutex = .{}, waiters: std.ArrayList(Waiter) = .empty, const Waiter = struct { addr: u64, - cond: std.Thread.Condition = .{}, + notified: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), }; pub fn deinit(self: *WaitQueue, alloc: mem.Allocator) void { @@ -219,32 +226,32 @@ pub const Memory = struct { if (loaded != expected) return 1; // not-equal const wq = self.ensureWaitQueue(); - wq.mutex.lock(); - // Add waiter + // Add waiter (use orderedRemove protection via re-reading index) const idx = wq.waiters.items.len; wq.waiters.append(self.alloc, .{ .addr = addr }) catch { - wq.mutex.unlock(); return 2; // treat alloc failure as timeout }; const waiter = &wq.waiters.items[idx]; - if (timeout_ns < 0) { - // Wait forever - waiter.cond.wait(&wq.mutex); - } else { - const timeout: u64 = @intCast(timeout_ns); - waiter.cond.timedWait(&wq.mutex, timeout) catch { - // Timeout — remove self from waiters - self.removeWaiter(wq, idx); - wq.mutex.unlock(); - return 2; // timed-out - }; + const start = getNanoTimestamp(); + const timeout: i64 = if (timeout_ns < 0) std.math.maxInt(i64) else timeout_ns; + + // Poll for notification + while (!waiter.notified.load(.acquire)) { + if (timeout_ns >= 0) { + const elapsed = getNanoTimestamp() - start; + if (elapsed >= timeout) { + // Timeout — remove self from waiters + self.removeWaiter(wq, idx); + return 2; // timed-out + } + } + _ = std.Thread.yield() catch {}; } // Woken — remove self from waiters self.removeWaiter(wq, idx); - wq.mutex.unlock(); return 0; // ok } @@ -256,28 +263,29 @@ pub const Memory = struct { if (loaded != expected) return 1; // not-equal const wq = self.ensureWaitQueue(); - wq.mutex.lock(); const idx = wq.waiters.items.len; wq.waiters.append(self.alloc, .{ .addr = addr }) catch { - wq.mutex.unlock(); return 2; }; const waiter = &wq.waiters.items[idx]; - if (timeout_ns < 0) { - waiter.cond.wait(&wq.mutex); - } else { - const timeout: u64 = @intCast(timeout_ns); - waiter.cond.timedWait(&wq.mutex, timeout) catch { - self.removeWaiter(wq, idx); - wq.mutex.unlock(); - return 2; - }; + const start = getNanoTimestamp(); + const timeout: i64 = if (timeout_ns < 0) std.math.maxInt(i64) else timeout_ns; + + // Poll for notification + while (!waiter.notified.load(.acquire)) { + if (timeout_ns >= 0) { + const elapsed = getNanoTimestamp() - start; + if (elapsed >= timeout) { + self.removeWaiter(wq, idx); + return 2; + } + } + _ = std.Thread.yield() catch {}; } self.removeWaiter(wq, idx); - wq.mutex.unlock(); return 0; } @@ -290,14 +298,12 @@ pub const Memory = struct { const wq_opt = self.wait_queue; if (wq_opt == null) return 0; var wq = &self.wait_queue.?; - wq.mutex.lock(); - defer wq.mutex.unlock(); var woken: i32 = 0; var i: usize = 0; while (i < wq.waiters.items.len and woken < @as(i32, @intCast(count))) { if (wq.waiters.items[i].addr == addr) { - wq.waiters.items[i].cond.signal(); + wq.waiters.items[i].notified.store(true, .release); woken += 1; } i += 1; @@ -583,7 +589,7 @@ test "Memory — atomicWait32 + notify cross-thread" { }.run, .{ &m, &wait_result }); // Give the waiter thread time to enter wait state - std.Thread.sleep(10 * std.time.ns_per_ms); + try std.Io.sleep(testing.io, .fromMilliseconds(10), .awake); // Notify should wake the waiter const woken = try m.atomicNotify(0, 1); diff --git a/src/module.zig b/src/module.zig index 2f5d21a2b..bb01654a6 100644 --- a/src/module.zig +++ b/src/module.zig @@ -394,7 +394,6 @@ pub const Module = struct { try self.rec_groups.append(self.alloc, .{ .start = group_start, .count = 1 }); } } - } fn decodeSubType(self: *Module, reader: *Reader) !void { @@ -869,9 +868,15 @@ pub const Module = struct { for (0..count + 1) |_| _ = try r.readU32(); }, 0x10, 0x12, 0x14, 0x15 => _ = try r.readU32(), // call, return_call, call_ref, return_call_ref - 0x11, 0x13 => { _ = try r.readU32(); _ = try r.readU32(); }, // call_indirect, return_call_indirect + 0x11, 0x13 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // call_indirect, return_call_indirect 0x08 => _ = try r.readU32(), // throw - 0x1C => { const n = try r.readU32(); for (0..n) |_| _ = try r.readByte(); }, // select_t + 0x1C => { + const n = try r.readU32(); + for (0..n) |_| _ = try r.readByte(); + }, // select_t 0x20, 0x21, 0x22 => _ = try r.readU32(), // local.get/set/tee 0x23, 0x24 => _ = try r.readU32(), // global.get/set 0x25, 0x26 => _ = try r.readU32(), // table.get/set @@ -892,13 +897,25 @@ pub const Module = struct { const sub = try r.readU32(); switch (sub) { 0...7 => {}, // trunc_sat - 8 => { _ = try r.readU32(); _ = try r.readU32(); }, // memory.init (dataidx, memidx) + 8 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // memory.init (dataidx, memidx) 9 => _ = try r.readU32(), // data.drop - 10 => { _ = try r.readU32(); _ = try r.readU32(); }, // memory.copy (dest_memidx, src_memidx) + 10 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // memory.copy (dest_memidx, src_memidx) 11 => _ = try r.readU32(), // memory.fill (memidx) - 12 => { _ = try r.readU32(); _ = try r.readU32(); }, // table.init + 12 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // table.init 13 => _ = try r.readU32(), // elem.drop - 14 => { _ = try r.readU32(); _ = try r.readU32(); }, // table.copy + 14 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // table.copy 15 => _ = try r.readU32(), // table.grow 16 => _ = try r.readU32(), // table.size 17 => _ = try r.readU32(), // table.fill @@ -936,13 +953,25 @@ pub const Module = struct { _ = try r.readU32(); // fieldidx }, 0x06, 0x07 => _ = try r.readU32(), // array.new/new_default (typeidx) - 0x08 => { _ = try r.readU32(); _ = try r.readU32(); }, // array.new_fixed (typeidx, N) - 0x09, 0x0A => { _ = try r.readU32(); _ = try r.readU32(); }, // array.new_data/elem + 0x08 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // array.new_fixed (typeidx, N) + 0x09, 0x0A => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // array.new_data/elem 0x0B, 0x0C, 0x0D, 0x0E => _ = try r.readU32(), // array.get/get_s/get_u/set 0x0F => {}, // array.len (no immediates) 0x10 => _ = try r.readU32(), // array.fill (typeidx) - 0x11 => { _ = try r.readU32(); _ = try r.readU32(); }, // array.copy - 0x12, 0x13 => { _ = try r.readU32(); _ = try r.readU32(); }, // array.init_data/elem + 0x11 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // array.copy + 0x12, 0x13 => { + _ = try r.readU32(); + _ = try r.readU32(); + }, // array.init_data/elem 0x14, 0x15 => _ = try r.readI33(), // ref.test/ref.test_null (heaptype) 0x16, 0x17 => _ = try r.readI33(), // ref.cast/ref.cast_null (heaptype) 0x18, 0x19 => { // br_on_cast/br_on_cast_fail @@ -1220,7 +1249,7 @@ fn sectionOrder(section_id: u8) u8 { 12 => 11, // data_count (before code) 10 => 12, // code 11 => 13, // data - else => section_id + 100, // unknown sections get high order + else => std.math.maxInt(u8), // unknown sections get highest order }; } @@ -1286,15 +1315,22 @@ fn skipInitExpr(reader: *Reader) !void { .ref_null => _ = try reader.readI33(), // heap type (S33 LEB128) .ref_func => _ = try reader.readU32(), // Extended constant expressions (Wasm 3.0) - .i32_add, .i32_sub, .i32_mul, - .i64_add, .i64_sub, .i64_mul, + .i32_add, + .i32_sub, + .i32_mul, + .i64_add, + .i64_sub, + .i64_mul, => {}, // GC prefix — struct/array constructors and conversions in init expressions .gc_prefix => { const gc_op = try reader.readU32(); switch (gc_op) { 0x00, 0x01, 0x06, 0x07 => _ = try reader.readU32(), // struct.new/default, array.new/default (type_idx) - 0x08 => { _ = try reader.readU32(); _ = try reader.readU32(); }, // array.new_fixed (type_idx, count) + 0x08 => { + _ = try reader.readU32(); + _ = try reader.readU32(); + }, // array.new_fixed (type_idx, count) 0x1A, 0x1B, 0x1C => {}, // any.convert_extern, extern.convert_any, ref.i31 else => return error.InvalidWasm, } @@ -1320,7 +1356,7 @@ fn skipInitExpr(reader: *Reader) !void { const testing = std.testing; /// Read a wasm test file at runtime (avoids @embedFile package path issues). -fn readTestFile(alloc: Allocator, name: []const u8) ![]const u8 { +fn readTestFile(alloc: Allocator, io: std.Io, name: []const u8) ![]const u8 { // Try relative path from project root (for `zig test` and `zig build test`) const prefixes = [_][]const u8{ "src/testdata/", @@ -1330,18 +1366,18 @@ fn readTestFile(alloc: Allocator, name: []const u8) ![]const u8 { for (prefixes) |prefix| { const path = try std.fmt.allocPrint(alloc, "{s}{s}", .{ prefix, name }); defer alloc.free(path); - const file = std.fs.cwd().openFile(path, .{}) catch continue; - defer file.close(); - const stat = try file.stat(); + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch continue; + defer file.close(io); + const stat = try file.stat(io); const data = try alloc.alloc(u8, stat.size); - const read = try file.readAll(data); + const read = try file.readPositionalAll(io, data, 0); return data[0..read]; } return error.FileNotFound; } test "Module — decode 01_add.wasm" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1375,7 +1411,7 @@ test "Module — decode 01_add.wasm" { } test "Module — decode 02_fibonacci.wasm" { - const wasm = try readTestFile(testing.allocator, "02_fibonacci.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "02_fibonacci.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1385,7 +1421,7 @@ test "Module — decode 02_fibonacci.wasm" { } test "Module — decode 03_memory.wasm" { - const wasm = try readTestFile(testing.allocator, "03_memory.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "03_memory.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1397,7 +1433,7 @@ test "Module — decode 03_memory.wasm" { } test "Module — decode 04_imports.wasm" { - const wasm = try readTestFile(testing.allocator, "04_imports.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "04_imports.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1410,7 +1446,7 @@ test "Module — decode 04_imports.wasm" { } test "Module — decode 05_table_indirect_call.wasm" { - const wasm = try readTestFile(testing.allocator, "05_table_indirect_call.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "05_table_indirect_call.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1421,7 +1457,7 @@ test "Module — decode 05_table_indirect_call.wasm" { } test "Module — decode 06_globals.wasm" { - const wasm = try readTestFile(testing.allocator, "06_globals.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "06_globals.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1431,7 +1467,7 @@ test "Module — decode 06_globals.wasm" { } test "Module — decode 07_wasi_hello.wasm" { - const wasm = try readTestFile(testing.allocator, "07_wasi_hello.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "07_wasi_hello.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1442,7 +1478,7 @@ test "Module — decode 07_wasi_hello.wasm" { } test "Module — decode 08_multi_value.wasm" { - const wasm = try readTestFile(testing.allocator, "08_multi_value.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "08_multi_value.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1458,7 +1494,7 @@ test "Module — decode 08_multi_value.wasm" { } test "Module — decode 09_go_math.wasm (large TinyGo module)" { - const wasm = try readTestFile(testing.allocator, "09_go_math.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "09_go_math.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1469,7 +1505,7 @@ test "Module — decode 09_go_math.wasm (large TinyGo module)" { } test "Module — decode 10_greet.wasm" { - const wasm = try readTestFile(testing.allocator, "10_greet.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "10_greet.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1481,7 +1517,7 @@ test "Module — decode 10_greet.wasm" { } test "Module — data section in imports module" { - const wasm = try readTestFile(testing.allocator, "04_imports.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "04_imports.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1493,7 +1529,7 @@ test "Module — data section in imports module" { } test "Module — getExport nonexistent" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1503,7 +1539,7 @@ test "Module — getExport nonexistent" { } test "Module — getFuncType" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); defer mod.deinit(); @@ -1901,7 +1937,7 @@ test "Module — type canonicalization: ref_test.1 GC struct types" { // 6: sub(0) struct() — $t0' (NOT same as 0: has super=[0]) // 7: sub(6) struct(i32, i32) — $t4 // 8: func() -> () - const wasm = try std.fs.cwd().readFileAlloc(testing.allocator, "test/spec/json/ref_test.1.wasm", 1024 * 1024); + const wasm = try std.Io.Dir.cwd().readFileAlloc(testing.io, "test/spec/json/ref_test.1.wasm", testing.allocator, .limited(1024 * 1024)); defer testing.allocator.free(wasm); var m = Module.init(testing.allocator, wasm); defer m.deinit(); @@ -2025,45 +2061,37 @@ pub const fuzz_corpus = &[_][]const u8{ }; test "fuzz — module decode does not panic on arbitrary input" { - const Ctx = struct { corpus: []const []const u8 }; - try std.testing.fuzz( - Ctx{ .corpus = fuzz_corpus }, - struct { - fn f(_: Ctx, input: []const u8) anyerror!void { - var m = Module.init(testing.allocator, input); - defer m.deinit(); - m.decode() catch return; - } - }.f, - .{}, - ); + try std.testing.fuzz({}, struct { + fn f(_: void, smith: *std.testing.Smith) anyerror!void { + const input = smith.in orelse return; + var m = Module.init(testing.allocator, input); + defer m.deinit(); + m.decode() catch return; + } + }.f, .{ .corpus = fuzz_corpus }); } test "fuzz — full pipeline (load+instantiate) does not panic" { - const Ctx = struct { corpus: []const []const u8 }; - try std.testing.fuzz( - Ctx{ .corpus = fuzz_corpus }, - struct { - fn f(_: Ctx, input: []const u8) anyerror!void { - const zwasm = @import("types.zig"); - const module = zwasm.WasmModule.loadWithFuel( - testing.allocator, - input, - 100_000, - ) catch return; - defer module.deinit(); - - // Try invoking zero-arg exported functions - for (module.export_fns) |ei| { - if (ei.param_types.len == 0 and ei.result_types.len <= 1) { - var results: [1]u64 = .{0}; - const result_slice = results[0..ei.result_types.len]; - module.invoke(ei.name, &.{}, result_slice) catch continue; - module.fuel = 100_000; - } + try std.testing.fuzz({}, struct { + fn f(_: void, smith: *std.testing.Smith) anyerror!void { + const input = smith.in orelse return; + const zwasm = @import("types.zig"); + const module = zwasm.WasmModule.loadWithFuel( + testing.allocator, + testing.io, + input, + 100_000, + ) catch return; + defer module.deinit(); + + for (module.export_fns) |ei| { + if (ei.param_types.len == 0 and ei.result_types.len <= 1) { + var results: [1]u64 = .{0}; + const result_slice = results[0..ei.result_types.len]; + module.invoke(ei.name, &.{}, result_slice) catch continue; + module.fuel = 100_000; } } - }.f, - .{}, - ); + } + }.f, .{ .corpus = fuzz_corpus }); } diff --git a/src/platform.zig b/src/platform.zig index 4b8630817..54cef0ea5 100644 --- a/src/platform.zig +++ b/src/platform.zig @@ -68,8 +68,8 @@ pub fn commitPages(region: []align(page_size) u8, prot: Protection) PageError!vo return; } - const posix = std.posix; - posix.mprotect(region, protectionToPosix(prot)) catch return error.PermissionDenied; + const result = std.posix.system.mprotect(@ptrCast(region.ptr), region.len, protectionToPosix(prot)); + if (result != 0) return error.PermissionDenied; } pub fn protectPages(region: []align(page_size) u8, prot: Protection) PageError!void { @@ -84,8 +84,8 @@ pub fn protectPages(region: []align(page_size) u8, prot: Protection) PageError!v return; } - const posix = std.posix; - posix.mprotect(region, protectionToPosix(prot)) catch return error.PermissionDenied; + const result = std.posix.system.mprotect(@ptrCast(region.ptr), region.len, protectionToPosix(prot)); + if (result != 0) return error.PermissionDenied; } pub fn freePages(region: []align(page_size) u8) void { @@ -122,8 +122,12 @@ pub fn appCacheDir(alloc: std.mem.Allocator, app_name: []const u8) ![]u8 { return std.fs.getAppDataDir(alloc, app_name); } - const home = std.posix.getenv("HOME") orelse return error.NoCacheDir; - return std.fmt.allocPrint(alloc, "{s}/.cache/{s}", .{ home, app_name }); + if (try envPath(alloc, "HOME")) |home| { + defer alloc.free(home); + return std.fmt.allocPrint(alloc, "{s}/.cache/{s}", .{ home, app_name }); + } + + return std.fmt.allocPrint(alloc, "/tmp/{s}", .{app_name}); } pub fn tempDirPath(alloc: std.mem.Allocator) ![]u8 { @@ -141,10 +145,15 @@ pub fn tempDirPath(alloc: std.mem.Allocator) ![]u8 { } fn envPath(alloc: std.mem.Allocator, name: []const u8) !?[]u8 { - return std.process.getEnvVarOwned(alloc, name) catch |err| switch (err) { - error.EnvironmentVariableNotFound => null, - else => err, - }; + if (builtin.os.tag == .windows) { + var env_map = try std.process.Environ.createMap(.{ .block = .global }, alloc); + defer env_map.deinit(); + + const value = env_map.get(name) orelse return null; + return alloc.dupe(u8, value); + } + + return null; } fn protectionToWin(prot: Protection) windows.DWORD { @@ -155,11 +164,11 @@ fn protectionToWin(prot: Protection) windows.DWORD { }; } -fn protectionToPosix(prot: Protection) u32 { +fn protectionToPosix(prot: Protection) std.posix.PROT { return switch (prot) { - .none => @intCast(std.posix.PROT.NONE), - .read_write => @intCast(std.posix.PROT.READ | std.posix.PROT.WRITE), - .read_exec => @intCast(std.posix.PROT.READ | std.posix.PROT.EXEC), + .none => .{}, + .read_write => .{ .READ = true, .WRITE = true }, + .read_exec => .{ .READ = true, .EXEC = true }, }; } diff --git a/src/trace.zig b/src/trace.zig index 67a1d23c0..d7eafec20 100644 --- a/src/trace.zig +++ b/src/trace.zig @@ -61,10 +61,7 @@ pub fn parseCategories(input: []const u8) u8 { // ================================================================ fn stderrPrint(comptime fmt: []const u8, args: anytype) void { - var buf: [4096]u8 = undefined; - var w = std.fs.File.stderr().writer(&buf); - w.interface.print(fmt, args) catch {}; - w.interface.flush() catch {}; + std.debug.print(fmt, args); } pub fn traceJitCompile(tc: *const TraceConfig, func_idx: u32, ir_count: u32, code_size: u32) void { @@ -380,15 +377,24 @@ pub fn dumpRegIR(w: *std.Io.Writer, reg_func: *const RegFunc, pool64: []const u6 w.print("r{d} src=r{d} n=r{d}", .{ instr.rd, instr.rs1, instr.rs2() }) catch {}; }, // Immediate-fused ops - regalloc_mod.OP_ADDI32, regalloc_mod.OP_SUBI32, regalloc_mod.OP_MULI32, - regalloc_mod.OP_ANDI32, regalloc_mod.OP_ORI32, regalloc_mod.OP_XORI32, + regalloc_mod.OP_ADDI32, + regalloc_mod.OP_SUBI32, + regalloc_mod.OP_MULI32, + regalloc_mod.OP_ANDI32, + regalloc_mod.OP_ORI32, + regalloc_mod.OP_XORI32, regalloc_mod.OP_SHLI32, => { w.print("r{d} = r{d} op {d}", .{ instr.rd, instr.rs1, instr.operand }) catch {}; }, - regalloc_mod.OP_LE_S_I32, regalloc_mod.OP_GE_S_I32, regalloc_mod.OP_LT_S_I32, - regalloc_mod.OP_GT_S_I32, regalloc_mod.OP_EQ_I32, regalloc_mod.OP_NE_I32, - regalloc_mod.OP_LT_U_I32, regalloc_mod.OP_GE_U_I32, + regalloc_mod.OP_LE_S_I32, + regalloc_mod.OP_GE_S_I32, + regalloc_mod.OP_LT_S_I32, + regalloc_mod.OP_GT_S_I32, + regalloc_mod.OP_EQ_I32, + regalloc_mod.OP_NE_I32, + regalloc_mod.OP_LT_U_I32, + regalloc_mod.OP_GE_U_I32, => { w.print("r{d} = r{d} cmp {d}", .{ instr.rd, instr.rs1, instr.operand }) catch {}; }, @@ -424,76 +430,66 @@ pub fn dumpJitCode( pc_map_items: []const u32, func_idx: u32, ) void { - var buf: [4096]u8 = undefined; - var ew = std.fs.File.stderr().writer(&buf); - const w = &ew.interface; - const code_bytes = code_items.len * 4; - w.print("\n=== JIT: func#{d} ({d} ARM64 instrs, {d} bytes) ===\n", .{ + std.debug.print("\n=== JIT: func#{d} ({d} ARM64 instrs, {d} bytes) ===\n", .{ func_idx, code_items.len, code_bytes, - }) catch {}; + }); const tmp_dir = platform.tempDirPath(alloc) catch { - w.print(" (failed to resolve temp dir)\n", .{}) catch {}; - w.flush() catch {}; + std.debug.print(" (failed to resolve temp dir)\n", .{}); return; }; defer alloc.free(tmp_dir); const file_name = std.fmt.allocPrint(alloc, "zwasm_jit_{d}.bin", .{func_idx}) catch { - w.print(" (failed to format path)\n", .{}) catch {}; - w.flush() catch {}; + std.debug.print(" (failed to format path)\n", .{}); return; }; defer alloc.free(file_name); const bin_path = std.fs.path.join(alloc, &.{ tmp_dir, file_name }) catch { - w.print(" (failed to format path)\n", .{}) catch {}; - w.flush() catch {}; + std.debug.print(" (failed to format path)\n", .{}); return; }; defer alloc.free(bin_path); const file = std.fs.createFileAbsolute(bin_path, .{}) catch { - w.print(" (failed to create {s})\n", .{bin_path}) catch {}; - w.flush() catch {}; + std.debug.print(" (failed to create {s})\n", .{bin_path}); return; }; file.writeAll(std.mem.sliceAsBytes(code_items)) catch { file.close(); - w.print(" (failed to write {s})\n", .{bin_path}) catch {}; - w.flush() catch {}; + std.debug.print(" (failed to write {s})\n", .{bin_path}); return; }; file.close(); - w.print(" raw binary: {s}\n", .{bin_path}) catch {}; + std.debug.print(" raw binary: {s}\n", .{bin_path}); // Try llvm-objdump, then objdump - const tried = tryObjdump(alloc, bin_path, w); + const tried = tryObjdump(alloc, bin_path); if (!tried) { // Fallback: hex dump - w.print(" (objdump not available — hex dump)\n", .{}) catch {}; + std.debug.print(" (objdump not available — hex dump)\n", .{}); const max_show = @min(code_items.len, 32); for (code_items[0..max_show], 0..) |word, i| { - w.print(" {X:0>4}: {X:0>8}\n", .{ i * 4, word }) catch {}; + std.debug.print(" {X:0>4}: {X:0>8}\n", .{ i * 4, word }); } if (code_items.len > max_show) { - w.print(" ... ({d} more instructions)\n", .{code_items.len - max_show}) catch {}; + std.debug.print(" ... ({d} more instructions)\n", .{code_items.len - max_show}); } } // Print pc_map - w.print("\n pc_map (RegIR PC -> ARM64 offset):\n", .{}) catch {}; + std.debug.print("\n pc_map (RegIR PC -> ARM64 offset):\n", .{}); for (pc_map_items, 0..) |arm_idx, pc| { // Only print entries where something maps if (pc > 0 and arm_idx == pc_map_items[pc - 1]) continue; - w.print(" pc={d} -> 0x{X}\n", .{ pc, @as(usize, arm_idx) * 4 }) catch {}; + std.debug.print(" pc={d} -> 0x{X}\n", .{ pc, @as(usize, arm_idx) * 4 }); } - w.print("=== end JIT func#{d} ===\n\n", .{func_idx}) catch {}; - w.flush() catch {}; + std.debug.print("=== end JIT func#{d} ===\n\n", .{func_idx}); } -fn tryObjdump(alloc: Allocator, bin_path: []const u8, w: *std.Io.Writer) bool { +fn tryObjdump(alloc: Allocator, bin_path: []const u8) bool { _ = alloc; // Try llvm-objdump first, then objdump const tool_configs = [_]struct { name: []const u8, args: []const []const u8 }{ @@ -521,8 +517,8 @@ fn tryObjdump(alloc: Allocator, bin_path: []const u8, w: *std.Io.Writer) bool { _ = child.wait() catch continue; if (total > 0) { - w.print(" disassembly ({s}):\n", .{tool.name}) catch {}; - w.print("{s}", .{out_buf[0..total]}) catch {}; + std.debug.print(" disassembly ({s}):\n", .{tool.name}); + std.debug.print("{s}", .{out_buf[0..total]}); return true; } } @@ -596,16 +592,15 @@ test "dumpRegIR: basic output" { .{ .op = regalloc_mod.OP_CONST32, .rd = 2, .rs1 = 0, .operand = 42 }, .{ .op = regalloc_mod.OP_RETURN, .rd = 2, .rs1 = 0, .operand = 0 }, }; - var reg_func = RegFunc{ - .code = &code, + const reg_func = RegFunc{ + .code = code[0..], .pool64 = &.{}, .reg_count = 3, .local_count = 2, .alloc = testing.allocator, }; - // Write to stderr (verifies no crash, output format tested manually) var err_buf: [4096]u8 = undefined; - var ew = std.fs.File.stderr().writer(&err_buf); + var ew = std.Io.File.stderr().writer(testing.io, &err_buf); dumpRegIR(&ew.interface, ®_func, &.{}, 5); } diff --git a/src/types.zig b/src/types.zig index daef11340..567c09fe8 100644 --- a/src/types.zig +++ b/src/types.zig @@ -174,7 +174,7 @@ pub const Capabilities = rt.wasi.Capabilities; /// Options for configuring WASI modules. /// FD-based preopen entry: binds an existing host fd to a WASI guest path. pub const PreopenFd = struct { - host_fd: std.fs.File.Handle, + host_fd: std.posix.fd_t, guest_path: []const u8, kind: rt.wasi.HandleKind, ownership: rt.wasi.Ownership, @@ -192,7 +192,7 @@ pub const WasiOptions = struct { preopen_fds: []const PreopenFd = &.{}, /// Stdio fd overrides (null = use process default). /// Index 0=stdin, 1=stdout, 2=stderr. - stdio_fds: [3]?std.fs.File.Handle = .{ null, null, null }, + stdio_fds: [3]?std.posix.fd_t = .{ null, null, null }, stdio_ownership: [3]rt.wasi.Ownership = .{ .borrow, .borrow, .borrow }, /// WASI capability flags. Default: cli_default (stdio, clock, random, proc_exit). /// Use `.caps = Capabilities.all` for full access. @@ -243,21 +243,21 @@ pub const WasmModule = struct { force_interpreter: ?bool = null, /// Load a Wasm module from binary bytes, decode, and instantiate. - pub fn load(allocator: Allocator, wasm_bytes: []const u8) !*WasmModule { - return loadCore(allocator, wasm_bytes, false, null, null, null, null); + pub fn load(allocator: Allocator, io: std.Io, wasm_bytes: []const u8) !*WasmModule { + return loadCore(allocator, io, wasm_bytes, false, null, null, null, null); } /// Load with a fuel limit (traps start function if it exceeds the limit). - pub fn loadWithFuel(allocator: Allocator, wasm_bytes: []const u8, fuel: u64) !*WasmModule { - return loadCore(allocator, wasm_bytes, false, null, fuel, null, null); + pub fn loadWithFuel(allocator: Allocator, io: std.Io, wasm_bytes: []const u8, fuel: u64) !*WasmModule { + return loadCore(allocator, io, wasm_bytes, false, null, fuel, null, null); } /// Load a module from WAT (WebAssembly Text Format) source. /// Requires `-Dwat=true` (default). Returns error.WatNotEnabled if disabled. - pub fn loadFromWat(allocator: Allocator, wat_source: []const u8) !*WasmModule { + pub fn loadFromWat(allocator: Allocator, io: std.Io, wat_source: []const u8) !*WasmModule { const wasm_bytes = try rt.wat.watToWasm(allocator, wat_source); errdefer allocator.free(wasm_bytes); - const self = try loadCore(allocator, wasm_bytes, false, null, null, null, null); + const self = try loadCore(allocator, io, wasm_bytes, false, null, null, null, null); self.owned_wasm_bytes = wasm_bytes; return self; } @@ -272,12 +272,12 @@ pub const WasmModule = struct { } /// Load a WASI module — registers wasi_snapshot_preview1 imports. - pub fn loadWasi(allocator: Allocator, wasm_bytes: []const u8) !*WasmModule { - return loadCore(allocator, wasm_bytes, true, null, null, null, null); + pub fn loadWasi(allocator: Allocator, io: std.Io, wasm_bytes: []const u8) !*WasmModule { + return loadCore(allocator, io, wasm_bytes, true, null, null, null, null); } /// Apply WasiOptions to a WasiContext (shared logic for all WASI loaders). - fn applyWasiOptions(wc: *rt.wasi.WasiContext, opts: WasiOptions) !void { + fn applyWasiOptions(wc: *rt.wasi.WasiContext, io: std.Io, opts: WasiOptions) !void { wc.caps = opts.caps; if (opts.args.len > 0) wc.setArgs(opts.args); @@ -290,7 +290,7 @@ pub const WasmModule = struct { for (opts.preopen_paths, 0..) |path, i| { const fd: i32 = @intCast(3 + i); const spec = splitPreopenSpec(path); - wc.addPreopenPath(fd, spec.guest, spec.host) catch continue; + wc.addPreopenPath(io, fd, spec.guest, spec.host) catch continue; } // FD-based preopens (fd auto-assigned after path-based ones) @@ -309,23 +309,23 @@ pub const WasmModule = struct { } /// Load a WASI module with custom args, env, and preopened directories. - pub fn loadWasiWithOptions(allocator: Allocator, wasm_bytes: []const u8, opts: WasiOptions) !*WasmModule { - const self = try loadCore(allocator, wasm_bytes, true, null, null, null, null); + pub fn loadWasiWithOptions(allocator: Allocator, io: std.Io, wasm_bytes: []const u8, opts: WasiOptions) !*WasmModule { + const self = try loadCore(allocator, io, wasm_bytes, true, null, null, null, null); errdefer self.deinit(); - if (self.wasi_ctx) |*wc| try applyWasiOptions(wc, opts); + if (self.wasi_ctx) |*wc| try applyWasiOptions(wc, io, opts); return self; } /// Load with imports from other modules or host functions. - pub fn loadWithImports(allocator: Allocator, wasm_bytes: []const u8, imports: []const ImportEntry) !*WasmModule { - return loadCore(allocator, wasm_bytes, false, imports, null, null, null); + pub fn loadWithImports(allocator: Allocator, io: std.Io, wasm_bytes: []const u8, imports: []const ImportEntry) !*WasmModule { + return loadCore(allocator, io, wasm_bytes, false, imports, null, null, null); } /// Load with combined WASI + import support. Used by CLI for --link + WASI fallback. - pub fn loadWasiWithImports(allocator: Allocator, wasm_bytes: []const u8, imports: ?[]const ImportEntry, opts: WasiOptions) !*WasmModule { - const self = try loadCore(allocator, wasm_bytes, true, imports, null, null, null); + pub fn loadWasiWithImports(allocator: Allocator, io: std.Io, wasm_bytes: []const u8, imports: ?[]const ImportEntry, opts: WasiOptions) !*WasmModule { + const self = try loadCore(allocator, io, wasm_bytes, true, imports, null, null, null); errdefer self.deinit(); - if (self.wasi_ctx) |*wc| try applyWasiOptions(wc, opts); + if (self.wasi_ctx) |*wc| try applyWasiOptions(wc, io, opts); return self; } @@ -408,7 +408,7 @@ pub const WasmModule = struct { return .{ .module = self, .apply_error = apply_error }; } - fn loadCore(allocator: Allocator, wasm_bytes: []const u8, wasi: bool, imports: ?[]const ImportEntry, fuel: ?u64, timeout_ms: ?u64, force_interpreter: ?bool) !*WasmModule { + fn loadCore(allocator: Allocator, io: std.Io, wasm_bytes: []const u8, wasi: bool, imports: ?[]const ImportEntry, fuel: ?u64, timeout_ms: ?u64, force_interpreter: ?bool) !*WasmModule { const self = try allocator.create(WasmModule); errdefer allocator.destroy(self); @@ -423,7 +423,7 @@ pub const WasmModule = struct { if (wasi) { try rt.wasi.registerAll(&self.store, &self.module); - self.wasi_ctx = rt.wasi.WasiContext.init(allocator); + self.wasi_ctx = rt.wasi.WasiContext.init(allocator, io); self.wasi_ctx.?.caps = rt.wasi.Capabilities.cli_default; } else { self.wasi_ctx = null; @@ -899,7 +899,7 @@ const testing = std.testing; test "smoke test — load and call add(3, 4)" { const wasm_bytes = @embedFile("testdata/01_add.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); var args = [_]u64{ 3, 4 }; @@ -911,7 +911,7 @@ test "smoke test — load and call add(3, 4)" { test "smoke test — fibonacci(10) = 55" { const wasm_bytes = @embedFile("testdata/02_fibonacci.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); var args = [_]u64{10}; @@ -923,7 +923,7 @@ test "smoke test — fibonacci(10) = 55" { test "memory read/write round-trip" { const wasm_bytes = @embedFile("testdata/03_memory.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); try wasm_mod.memoryWrite(0, "Hello"); @@ -939,7 +939,7 @@ test "memory read/write round-trip" { test "memory write then call store/load" { const wasm_bytes = @embedFile("testdata/03_memory.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); var store_args = [_]u64{ 0, 42 }; @@ -959,7 +959,7 @@ test "memory write then call store/load" { test "buildExportInfo — add module exports" { const wasm_bytes = @embedFile("testdata/01_add.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); try testing.expect(wasm_mod.export_fns.len > 0); @@ -975,7 +975,7 @@ test "buildExportInfo — add module exports" { test "buildExportInfo — fibonacci module exports" { const wasm_bytes = @embedFile("testdata/02_fibonacci.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); const fib_info = wasm_mod.getExportInfo("fib"); @@ -989,7 +989,7 @@ test "buildExportInfo — fibonacci module exports" { test "buildExportInfo — memory module exports" { const wasm_bytes = @embedFile("testdata/03_memory.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); const store_info = wasm_mod.getExportInfo("store"); @@ -1005,7 +1005,7 @@ test "buildExportInfo — memory module exports" { test "getExportInfo — nonexistent name returns null" { const wasm_bytes = @embedFile("testdata/01_add.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); try testing.expect(wasm_mod.getExportInfo("nonexistent") == null); @@ -1016,7 +1016,7 @@ test "getExportInfo — nonexistent name returns null" { test "multi-module — two modules, function import" { // math_mod exports "add" and "mul" const math_bytes = @embedFile("testdata/20_math_export.wasm"); - var math_mod = try WasmModule.load(testing.allocator, math_bytes); + var math_mod = try WasmModule.load(testing.allocator, testing.io, math_bytes); defer math_mod.deinit(); // Verify math module works standalone @@ -1027,7 +1027,7 @@ test "multi-module — two modules, function import" { // app_mod imports "add" and "mul" from "math", exports "add_and_mul" const app_bytes = @embedFile("testdata/21_app_import.wasm"); - var app_mod = try WasmModule.loadWithImports(testing.allocator, app_bytes, &.{ + var app_mod = try WasmModule.loadWithImports(testing.allocator, testing.io, app_bytes, &.{ .{ .module = "math", .source = .{ .wasm_module = math_mod } }, }); defer app_mod.deinit(); @@ -1042,12 +1042,12 @@ test "multi-module — two modules, function import" { test "multi-module — three module chain" { // base exports "double" const base_bytes = @embedFile("testdata/22_base.wasm"); - var base_mod = try WasmModule.load(testing.allocator, base_bytes); + var base_mod = try WasmModule.load(testing.allocator, testing.io, base_bytes); defer base_mod.deinit(); // mid imports "double" from "base", exports "quadruple" const mid_bytes = @embedFile("testdata/23_mid.wasm"); - var mid_mod = try WasmModule.loadWithImports(testing.allocator, mid_bytes, &.{ + var mid_mod = try WasmModule.loadWithImports(testing.allocator, testing.io, mid_bytes, &.{ .{ .module = "base", .source = .{ .wasm_module = base_mod } }, }); defer mid_mod.deinit(); @@ -1060,7 +1060,7 @@ test "multi-module — three module chain" { // top imports "quadruple" from "mid", exports "octuple" const top_bytes = @embedFile("testdata/24_top.wasm"); - var top_mod = try WasmModule.loadWithImports(testing.allocator, top_bytes, &.{ + var top_mod = try WasmModule.loadWithImports(testing.allocator, testing.io, top_bytes, &.{ .{ .module = "mid", .source = .{ .wasm_module = mid_mod } }, }); defer top_mod.deinit(); @@ -1075,12 +1075,12 @@ test "multi-module — three module chain" { test "multi-module — memory import" { // provider exports memory with "hello" at offset 0 const provider_bytes = @embedFile("testdata/26_mem_export.wasm"); - var provider = try WasmModule.load(testing.allocator, provider_bytes); + var provider = try WasmModule.load(testing.allocator, testing.io, provider_bytes); defer provider.deinit(); // consumer imports memory from "provider", exports read_byte(offset) -> u8 const consumer_bytes = @embedFile("testdata/27_mem_import.wasm"); - var consumer = try WasmModule.loadWithImports(testing.allocator, consumer_bytes, &.{ + var consumer = try WasmModule.loadWithImports(testing.allocator, testing.io, consumer_bytes, &.{ .{ .module = "provider", .source = .{ .wasm_module = provider } }, }); defer consumer.deinit(); @@ -1101,12 +1101,12 @@ test "multi-module — memory import" { test "multi-module — cross-module call_indirect type match" { // Provider exports "add(i32,i32)->i32" const provider_bytes = @embedFile("testdata/28_provider_call_indirect.wasm"); - var provider = try WasmModule.load(testing.allocator, provider_bytes); + var provider = try WasmModule.load(testing.allocator, testing.io, provider_bytes); defer provider.deinit(); // Consumer imports "add" from provider, places in table, calls via call_indirect const consumer_bytes = @embedFile("testdata/29_consumer_call_indirect.wasm"); - var consumer = try WasmModule.loadWithImports(testing.allocator, consumer_bytes, &.{ + var consumer = try WasmModule.loadWithImports(testing.allocator, testing.io, consumer_bytes, &.{ .{ .module = "provider", .source = .{ .wasm_module = provider } }, }); defer consumer.deinit(); @@ -1121,7 +1121,7 @@ test "multi-module — cross-module call_indirect type match" { test "multi-module — shared table via loadLinked" { // Mt: exports table with elements at [2..5] = [$g,$g,$g,$g], $g returns 4 const mt_bytes = @embedFile("testdata/30_table_export.wasm"); - var mt = try WasmModule.load(testing.allocator, mt_bytes); + var mt = try WasmModule.load(testing.allocator, testing.io, mt_bytes); defer mt.deinit(); // Verify Mt.call(2) = 4 before Ot loads @@ -1151,7 +1151,7 @@ test "multi-module — shared table via loadLinked" { test "nqueens(8) = 92 — regir only (JIT disabled)" { const wasm_bytes = @embedFile("testdata/25_nqueens.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); // Enable profiling to disable JIT (JIT is skipped when profile != null) @@ -1167,7 +1167,7 @@ test "nqueens(8) = 92 — regir only (JIT disabled)" { test "nqueens(8) = 92 — with JIT" { const wasm_bytes = @embedFile("testdata/25_nqueens.wasm"); - var wasm_mod = try WasmModule.load(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.load(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); var args = [_]u64{8}; @@ -1183,7 +1183,7 @@ test "nqueens(8) = 92 — with JIT" { test "WAT round-trip — i32.add" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func $add (param i32 i32) (result i32) \\ local.get 0 @@ -1203,7 +1203,7 @@ test "WAT round-trip — i32.add" { test "WAT round-trip — i32.const" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "forty_two") (result i32) \\ i32.const 42 @@ -1219,7 +1219,7 @@ test "WAT round-trip — i32.const" { test "WAT round-trip — if/else" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "abs") (param i32) (result i32) \\ (if (result i32) (i32.lt_s (local.get 0) (i32.const 0)) @@ -1243,7 +1243,7 @@ test "WAT round-trip — if/else" { test "WAT round-trip — loop (factorial)" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "fac") (param i32) (result i32) \\ (local i32) @@ -1270,7 +1270,7 @@ test "WAT round-trip — loop (factorial)" { test "WAT round-trip — named locals" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "swap_sub") (param $a i32) (param $b i32) (result i32) \\ (i32.sub (local.get $b) (local.get $a)) @@ -1287,7 +1287,7 @@ test "WAT round-trip — named locals" { test "WAT round-trip — named globals" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (global $counter (mut i32) (i32.const 0)) \\ (func (export "inc") (result i32) @@ -1307,7 +1307,7 @@ test "WAT round-trip — named globals" { test "WAT round-trip — return_call simple" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func $get42 (result i32) \\ i32.const 42 @@ -1327,7 +1327,7 @@ test "WAT round-trip — return_call simple" { test "WAT round-trip — return_call mutual recursion" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func $even (param i32) (result i32) \\ local.get 0 @@ -1370,7 +1370,7 @@ test "WAT round-trip — return_call mutual recursion" { test "loadWasi uses cli_default capabilities" { // Minimal valid wasm module (magic + version) const wasm_bytes = &[_]u8{ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00 }; - var wasm_mod = try WasmModule.loadWasi(testing.allocator, wasm_bytes); + var wasm_mod = try WasmModule.loadWasi(testing.allocator, testing.io, wasm_bytes); defer wasm_mod.deinit(); const caps = wasm_mod.wasi_ctx.?.caps; @@ -1388,7 +1388,7 @@ test "loadWasi uses cli_default capabilities" { test "loadWasiWithOptions defaults to cli_default capabilities" { const wasm_bytes = &[_]u8{ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00 }; - var wasm_mod = try WasmModule.loadWasiWithOptions(testing.allocator, wasm_bytes, .{}); + var wasm_mod = try WasmModule.loadWasiWithOptions(testing.allocator, testing.io, wasm_bytes, .{}); defer wasm_mod.deinit(); const caps = wasm_mod.wasi_ctx.?.caps; @@ -1400,7 +1400,7 @@ test "loadWasiWithOptions defaults to cli_default capabilities" { test "loadWasiWithOptions explicit all grants full access" { const wasm_bytes = &[_]u8{ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00 }; - var wasm_mod = try WasmModule.loadWasiWithOptions(testing.allocator, wasm_bytes, .{ + var wasm_mod = try WasmModule.loadWasiWithOptions(testing.allocator, testing.io, wasm_bytes, .{ .caps = rt.wasi.Capabilities.all, }); defer wasm_mod.deinit(); @@ -1414,7 +1414,7 @@ test "loadWasiWithOptions explicit all grants full access" { test "force_interpreter — persistence across invoke and invokeInterpreterOnly" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "f") (result i32) \\ i32.const 42 @@ -1468,7 +1468,7 @@ test "force_interpreter — persistence across invoke and invokeInterpreterOnly" test "fuel and timeout — persistence and caller-set preservation" { if (!@import("build_options").enable_wat) return error.SkipZigTest; - var wasm_mod = try WasmModule.loadFromWat(testing.allocator, + var wasm_mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "f") (result i32) \\ i32.const 42 diff --git a/src/vm.zig b/src/vm.zig index 458fabfbe..83eefe02b 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -116,8 +116,18 @@ const DEADLINE_CHECK_INTERVAL: u32 = 1024; /// JIT back-edge interval for deadline checks. When a deadline is active, /// jit_fuel is armed to this value so the JIT periodically exits to check /// wall-clock time without suppressing JIT compilation entirely. +/// maxInt = unlimited (JIT skips the check entirely when this value is seen). const DEADLINE_JIT_INTERVAL: i64 = 10_000; +/// Get current monotonic time in nanoseconds. +/// Replacement for std.time.nanoTimestamp() which was removed in Zig 0.16.0. +fn getNanoTimestamp() i128 { + var ts: std.posix.timespec = undefined; + const result = std.posix.system.clock_gettime(std.posix.CLOCK.MONOTONIC, &ts); + if (result != 0) return 0; + return @as(i128, @intCast(ts.sec)) * std.time.ns_per_s + @as(i128, @intCast(ts.nsec)); +} + const Frame = struct { locals_start: usize, // index into operand stack where locals begin locals_count: usize, // total locals (params + locals) @@ -459,7 +469,7 @@ pub const Vm = struct { if (value == 0) { self.deadline_ns = null; } else { - self.deadline_ns = std.time.nanoTimestamp() + @as(i128, @intCast(value)) * std.time.ns_per_ms; + self.deadline_ns = getNanoTimestamp() + @as(i128, @intCast(value)) * std.time.ns_per_ms; } } else { self.deadline_ns = null; @@ -475,7 +485,7 @@ pub const Vm = struct { if (self.deadline_ns) |deadline_ns| { if (self.deadline_check_remaining == 0) { self.deadline_check_remaining = DEADLINE_CHECK_INTERVAL; - if (std.time.nanoTimestamp() >= deadline_ns) return error.TimeoutExceeded; + if (getNanoTimestamp() >= deadline_ns) return error.TimeoutExceeded; } else { self.deadline_check_remaining -= 1; } @@ -538,7 +548,7 @@ pub const Vm = struct { // Check wall-clock deadline if (vm.deadline_ns) |dl| { - if (std.time.nanoTimestamp() >= dl) return 10; // TimeoutExceeded + if (getNanoTimestamp() >= dl) return 10; // TimeoutExceeded } // Neither exhausted — re-arm and continue @@ -682,9 +692,8 @@ pub const Vm = struct { if (self.trace) |tc| { if (tc.dump_regir_func) |dump_idx| { if (dump_idx == wf.func_idx) { - var err_buf2: [4096]u8 = undefined; - var ew = std.fs.File.stderr().writer(&err_buf2); - trace_mod.dumpRegIR(&ew.interface, reg, wf.ir.?.pool64, wf.func_idx); + // TODO: dumpRegIR requires std.Io.Writer which needs Io context + // This will be re-enabled when trace infrastructure is updated for 0.16.0 tc.dump_regir_func = null; } } @@ -692,8 +701,7 @@ pub const Vm = struct { // JIT compilation: check hot threshold (skip when profiling or fuel metering) if (comptime jit_mod.jitSupported()) { - if (self.profile == null and wf.jit_code == null and !wf.jit_failed) - { + if (self.profile == null and wf.jit_code == null and !wf.jit_failed) { wf.call_count += 1; if (wf.call_count >= jit_mod.HOT_THRESHOLD) { // Skip JIT for very large functions — single-pass regalloc @@ -3153,84 +3161,98 @@ pub const Vm = struct { .i8x16_extract_lane_s => { const lane = try reader.readByte(); const vec: @Vector(16, i8) = @bitCast(self.popV128()); - try self.pushI32(@as(i32, vec[lane])); + const array: [16]i8 = vec; + try self.pushI32(@as(i32, array[lane])); }, .i8x16_extract_lane_u => { const lane = try reader.readByte(); const vec: @Vector(16, u8) = @bitCast(self.popV128()); - try self.push(@as(u64, vec[lane])); + const array: [16]u8 = vec; + try self.push(@as(u64, array[lane])); }, .i8x16_replace_lane => { const lane = try reader.readByte(); const val: u8 = @truncate(self.pop()); - var vec: @Vector(16, u8) = @bitCast(self.popV128()); - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + const vec: @Vector(16, u8) = @bitCast(self.popV128()); + var array: [16]u8 = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(16, u8), array))); }, .i16x8_extract_lane_s => { const lane = try reader.readByte(); const vec: @Vector(8, i16) = @bitCast(self.popV128()); - try self.pushI32(@as(i32, vec[lane])); + const array: [8]i16 = vec; + try self.pushI32(array[lane]); }, .i16x8_extract_lane_u => { const lane = try reader.readByte(); const vec: @Vector(8, u16) = @bitCast(self.popV128()); - try self.push(@as(u64, vec[lane])); + const array: [8]u16 = vec; + try self.push(@as(u64, array[lane])); }, .i16x8_replace_lane => { const lane = try reader.readByte(); const val: u16 = @truncate(self.pop()); - var vec: @Vector(8, u16) = @bitCast(self.popV128()); - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + const vec: @Vector(8, u16) = @bitCast(self.popV128()); + var array: [8]u16 = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(8, u16), array))); }, .i32x4_extract_lane => { const lane = try reader.readByte(); const vec: @Vector(4, i32) = @bitCast(self.popV128()); - try self.pushI32(vec[lane]); + const array: [4]i32 = vec; + try self.pushI32(array[lane]); }, .i32x4_replace_lane => { const lane = try reader.readByte(); const val = self.popI32(); - var vec: @Vector(4, i32) = @bitCast(self.popV128()); - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + const vec: @Vector(4, i32) = @bitCast(self.popV128()); + var array: [4]i32 = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(4, i32), array))); }, .i64x2_extract_lane => { const lane = try reader.readByte(); const vec: @Vector(2, i64) = @bitCast(self.popV128()); - try self.pushI64(vec[lane]); + const array: [2]i64 = vec; + try self.pushI64(array[lane]); }, .i64x2_replace_lane => { const lane = try reader.readByte(); const val = self.popI64(); - var vec: @Vector(2, i64) = @bitCast(self.popV128()); - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + const vec: @Vector(2, i64) = @bitCast(self.popV128()); + var array: [2]i64 = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(2, i64), array))); }, .f32x4_extract_lane => { const lane = try reader.readByte(); const vec: @Vector(4, u32) = @bitCast(self.popV128()); - try self.pushF32(@bitCast(vec[lane])); + const array: [4]u32 = vec; + try self.pushF32(@bitCast(array[lane])); }, .f32x4_replace_lane => { const lane = try reader.readByte(); const val: u32 = @bitCast(self.popF32()); - var vec: @Vector(4, u32) = @bitCast(self.popV128()); - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + const vec: @Vector(4, u32) = @bitCast(self.popV128()); + var array: [4]u32 = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(4, u32), array))); }, .f64x2_extract_lane => { const lane = try reader.readByte(); const vec: @Vector(2, u64) = @bitCast(self.popV128()); - try self.pushF64(@bitCast(vec[lane])); + const array: [2]u64 = vec; + try self.pushF64(@bitCast(array[lane])); }, .f64x2_replace_lane => { const lane = try reader.readByte(); const val: u64 = @bitCast(self.popF64()); - var vec: @Vector(2, u64) = @bitCast(self.popV128()); - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + const vec: @Vector(2, u64) = @bitCast(self.popV128()); + var array: [2]u64 = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(2, u64), array))); }, // ---- Shuffle / swizzle ---- @@ -3249,7 +3271,8 @@ pub const Vm = struct { try self.pushV128(@bitCast(@as(@Vector(16, u8), result))); }, .i8x16_swizzle => { - const indices: @Vector(16, u8) = @bitCast(self.popV128()); + const indices_v: @Vector(16, u8) = @bitCast(self.popV128()); + const indices: [16]u8 = indices_v; const vec: [16]u8 = @bitCast(self.popV128()); var result: [16]u8 = undefined; for (0..16) |i| { @@ -3821,7 +3844,8 @@ pub const Vm = struct { // ---- Relaxed SIMD (Wasm 3.0) ---- .i8x16_relaxed_swizzle => { - const indices: @Vector(16, u8) = @bitCast(self.popV128()); + const indices_v: @Vector(16, u8) = @bitCast(self.popV128()); + const indices: [16]u8 = indices_v; const vec: [16]u8 = @bitCast(self.popV128()); var result: [16]u8 = undefined; for (0..16) |i| { @@ -3982,10 +4006,11 @@ pub const Vm = struct { n.* = std.mem.readInt(NarrowT, ptr, .little); } // Extend to wide - var wide: @Vector(N, WideT) = undefined; + var wide_array: [N]WideT = undefined; for (0..N) |i| { - wide[i] = @as(WideT, narrow[i]); + wide_array[i] = @as(WideT, narrow[i]); } + const wide: @Vector(N, WideT) = wide_array; try self.pushV128(@bitCast(wide)); } @@ -3999,11 +4024,12 @@ pub const Vm = struct { ) WasmError!void { const ma = try readMemarg(reader, instance); const lane = try reader.readByte(); - var vec: @Vector(N, T) = @bitCast(self.popV128()); + const vec: @Vector(N, T) = @bitCast(self.popV128()); const base: u64 = if (ma.mem.is_64) self.popU64() else @as(u32, @bitCast(self.popI32())); const val = ma.mem.read(T, ma.offset, base) catch return error.OutOfBoundsMemoryAccess; - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + var array: [N]T = vec; + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(N, T), array))); } // SIMD helper: store a specific lane of v128 to memory @@ -4018,7 +4044,8 @@ pub const Vm = struct { const lane = try reader.readByte(); const vec: @Vector(N, T) = @bitCast(self.popV128()); const base: u64 = if (ma.mem.is_64) self.popU64() else @as(u32, @bitCast(self.popI32())); - ma.mem.write(T, ma.offset, base, vec[lane]) catch return error.OutOfBoundsMemoryAccess; + const array: [N]T = vec; + ma.mem.write(T, ma.offset, base, array[lane]) catch return error.OutOfBoundsMemoryAccess; } // SIMD helper: lane-wise comparison producing all-ones/all-zeros result @@ -4368,7 +4395,6 @@ pub const Vm = struct { // Arm fuel/deadline interval for JIT self.armJitFuel(); - // Call OSR entry: sets up callee-saved, memory cache, then jumps to loop body const err_code = osr_fn(regs_ptr, @ptrCast(self), @ptrCast(instance)); @@ -4577,7 +4603,6 @@ pub const Vm = struct { const cached_mem: ?*WasmMemory = instance.getMemory(0) catch null; var pc: u32 = 0; - // Back-edge counting for JIT hot loop detection (ARM64 only) var back_edge_count: u32 = 0; const wf: ?*store_mod.WasmFunction = if (func_ptr.subtype == .wasm_function) @@ -5689,19 +5714,19 @@ pub const Vm = struct { // --- Push operands from regs[] to op_stack --- if (effect.pop == 3) { // bitselect(a, b, c): main rs1=a, rs2=b, NOP.rd=c - self.pushRegToOpStack(regs,instr.rs1); - self.pushRegToOpStack(regs,instr.rs2_field); - self.pushRegToOpStack(regs,third_operand); + self.pushRegToOpStack(regs, instr.rs1); + self.pushRegToOpStack(regs, instr.rs2_field); + self.pushRegToOpStack(regs, third_operand); } else if (effect.push == 0 and effect.pop == 2) { // Store ops: rd=value, rs1=addr. Stack: [addr(bottom), value(top)] - self.pushRegToOpStack(regs,instr.rs1); - self.pushRegToOpStack(regs,instr.rd); + self.pushRegToOpStack(regs, instr.rs1); + self.pushRegToOpStack(regs, instr.rd); } else if (effect.pop == 2) { // Binary ops: rs1=first, rs2=second. Stack: [first(bottom), second(top)] - self.pushRegToOpStack(regs,instr.rs1); - self.pushRegToOpStack(regs,instr.rs2_field); + self.pushRegToOpStack(regs, instr.rs1); + self.pushRegToOpStack(regs, instr.rs2_field); } else if (effect.pop == 1) { - self.pushRegToOpStack(regs,instr.rs1); + self.pushRegToOpStack(regs, instr.rs1); } // --- Call existing SIMD interpreter --- @@ -6859,48 +6884,54 @@ pub const Vm = struct { const base = @as(u32, @bitCast(self.popI32())); const raw = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; const bytes: [8]i8 = @bitCast(raw); - var result: @Vector(8, i16) = undefined; - for (0..8) |i| result[i] = bytes[i]; + var result_array: [8]i16 = undefined; + for (0..8) |i| result_array[i] = @as(i16, bytes[i]); + const result: @Vector(8, i16) = result_array; try self.pushV128(@bitCast(result)); }, 0x02 => { // v128.load8x8_u const base = @as(u32, @bitCast(self.popI32())); const raw = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; const bytes: [8]u8 = @bitCast(raw); - var result: @Vector(8, u16) = undefined; - for (0..8) |i| result[i] = bytes[i]; + var result_array: [8]u16 = undefined; + for (0..8) |i| result_array[i] = @as(u16, bytes[i]); + const result: @Vector(8, u16) = result_array; try self.pushV128(@bitCast(result)); }, 0x03 => { // v128.load16x4_s const base = @as(u32, @bitCast(self.popI32())); const raw = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; const vals: [4]i16 = @bitCast(raw); - var result: @Vector(4, i32) = undefined; - for (0..4) |i| result[i] = vals[i]; + var result_array: [4]i32 = undefined; + for (0..4) |i| result_array[i] = @as(i32, vals[i]); + const result: @Vector(4, i32) = result_array; try self.pushV128(@bitCast(result)); }, 0x04 => { // v128.load16x4_u const base = @as(u32, @bitCast(self.popI32())); const raw = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; const vals: [4]u16 = @bitCast(raw); - var result: @Vector(4, u32) = undefined; - for (0..4) |i| result[i] = vals[i]; + var result_array: [4]u32 = undefined; + for (0..4) |i| result_array[i] = @as(u32, vals[i]); + const result: @Vector(4, u32) = result_array; try self.pushV128(@bitCast(result)); }, 0x05 => { // v128.load32x2_s const base = @as(u32, @bitCast(self.popI32())); const raw = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; const vals: [2]i32 = @bitCast(raw); - var result: @Vector(2, i64) = undefined; - for (0..2) |i| result[i] = vals[i]; + var result_array: [2]i64 = undefined; + for (0..2) |i| result_array[i] = @as(i64, vals[i]); + const result: @Vector(2, i64) = result_array; try self.pushV128(@bitCast(result)); }, 0x06 => { // v128.load32x2_u const base = @as(u32, @bitCast(self.popI32())); const raw = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; const vals: [2]u32 = @bitCast(raw); - var result: @Vector(2, u64) = undefined; - for (0..2) |i| result[i] = vals[i]; + var result_array: [2]u64 = undefined; + for (0..2) |i| result_array[i] = @as(u64, vals[i]); + const result: @Vector(2, u64) = result_array; try self.pushV128(@bitCast(result)); }, 0x07 => { // v128.load8_splat @@ -6949,52 +6980,60 @@ pub const Vm = struct { fn executeSimdLaneMemOp(self: *Vm, sub: u32, offset: u32, wm: *WasmMemory, lane: u8) WasmError!void { switch (sub) { 0x54 => { // v128.load8_lane: [i32, v128] → [v128] - var vec: @Vector(16, u8) = @bitCast(self.popV128()); + const vec: @Vector(16, u8) = @bitCast(self.popV128()); + var array: [16]u8 = vec; const base = @as(u32, @bitCast(self.popI32())); const val = wm.read(u8, offset, base) catch return error.OutOfBoundsMemoryAccess; - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(16, u8), array))); }, 0x55 => { // v128.load16_lane - var vec: @Vector(8, u16) = @bitCast(self.popV128()); + const vec: @Vector(8, u16) = @bitCast(self.popV128()); + var array: [8]u16 = vec; const base = @as(u32, @bitCast(self.popI32())); const val = wm.read(u16, offset, base) catch return error.OutOfBoundsMemoryAccess; - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(8, u16), array))); }, 0x56 => { // v128.load32_lane - var vec: @Vector(4, u32) = @bitCast(self.popV128()); + const vec: @Vector(4, u32) = @bitCast(self.popV128()); + var array: [4]u32 = vec; const base = @as(u32, @bitCast(self.popI32())); const val = wm.read(u32, offset, base) catch return error.OutOfBoundsMemoryAccess; - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(4, u32), array))); }, 0x57 => { // v128.load64_lane - var vec: @Vector(2, u64) = @bitCast(self.popV128()); + const vec: @Vector(2, u64) = @bitCast(self.popV128()); + var array: [2]u64 = vec; const base = @as(u32, @bitCast(self.popI32())); const val = wm.read(u64, offset, base) catch return error.OutOfBoundsMemoryAccess; - vec[lane] = val; - try self.pushV128(@bitCast(vec)); + array[lane] = val; + try self.pushV128(@bitCast(@as(@Vector(2, u64), array))); }, 0x58 => { // v128.store8_lane const vec: @Vector(16, u8) = @bitCast(self.popV128()); + const array: [16]u8 = vec; const base = @as(u32, @bitCast(self.popI32())); - wm.write(u8, offset, base, vec[lane]) catch return error.OutOfBoundsMemoryAccess; + wm.write(u8, offset, base, array[lane]) catch return error.OutOfBoundsMemoryAccess; }, 0x59 => { // v128.store16_lane const vec: @Vector(8, u16) = @bitCast(self.popV128()); + const array: [8]u16 = vec; const base = @as(u32, @bitCast(self.popI32())); - wm.write(u16, offset, base, vec[lane]) catch return error.OutOfBoundsMemoryAccess; + wm.write(u16, offset, base, array[lane]) catch return error.OutOfBoundsMemoryAccess; }, 0x5A => { // v128.store32_lane const vec: @Vector(4, u32) = @bitCast(self.popV128()); + const array: [4]u32 = vec; const base = @as(u32, @bitCast(self.popI32())); - wm.write(u32, offset, base, vec[lane]) catch return error.OutOfBoundsMemoryAccess; + wm.write(u32, offset, base, array[lane]) catch return error.OutOfBoundsMemoryAccess; }, 0x5B => { // v128.store64_lane const vec: @Vector(2, u64) = @bitCast(self.popV128()); + const array: [2]u64 = vec; const base = @as(u32, @bitCast(self.popI32())); - wm.write(u64, offset, base, vec[lane]) catch return error.OutOfBoundsMemoryAccess; + wm.write(u64, offset, base, array[lane]) catch return error.OutOfBoundsMemoryAccess; }, else => return error.Trap, } @@ -8449,23 +8488,23 @@ fn roundToEven(comptime T: type, x: T) T { const testing = std.testing; -fn readTestFile(alloc: Allocator, name: []const u8) ![]const u8 { +fn readTestFile(alloc: Allocator, io: std.Io, name: []const u8) ![]const u8 { const prefixes = [_][]const u8{ "src/testdata/", "testdata/", "src/wasm/testdata/" }; for (prefixes) |prefix| { const path = try std.fmt.allocPrint(alloc, "{s}{s}", .{ prefix, name }); defer alloc.free(path); - const file = std.fs.cwd().openFile(path, .{}) catch continue; - defer file.close(); - const stat = try file.stat(); + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch continue; + defer file.close(io); + const stat = try file.stat(io); const data = try alloc.alloc(u8, stat.size); - const read = try file.readAll(data); + const read = try file.readPositionalAll(io, data, 0); return data[0..read]; } return error.FileNotFound; } test "VM — add(3, 4) = 7" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8487,7 +8526,7 @@ test "VM — add(3, 4) = 7" { } test "VM — add(100, -50) = 50" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8510,7 +8549,7 @@ test "VM — add(100, -50) = 50" { } test "VM — fib(10) = 55" { - const wasm = try readTestFile(testing.allocator, "02_fibonacci.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "02_fibonacci.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8532,7 +8571,7 @@ test "VM — fib(10) = 55" { } test "VM — memory store/load" { - const wasm = try readTestFile(testing.allocator, "03_memory.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "03_memory.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8561,7 +8600,7 @@ test "VM — memory store/load" { } test "VM — globals" { - const wasm = try readTestFile(testing.allocator, "06_globals.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "06_globals.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8589,7 +8628,7 @@ test "VM — globals" { } test "VM — memory sum_range (loop branch)" { - const wasm = try readTestFile(testing.allocator, "03_memory.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "03_memory.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8622,7 +8661,7 @@ test "VM — memory sum_range (loop branch)" { } test "VM — table indirect call" { - const wasm = try readTestFile(testing.allocator, "05_table_indirect_call.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "05_table_indirect_call.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8654,7 +8693,7 @@ test "VM — table indirect call" { } test "VM — multi-value return" { - const wasm = try readTestFile(testing.allocator, "08_multi_value.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "08_multi_value.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8680,7 +8719,7 @@ test "VM — multi-value return" { test "VM — host function imports" { const alloc = testing.allocator; - const wasm = try readTestFile(alloc, "04_imports.wasm"); + const wasm = try readTestFile(alloc, testing.io, "04_imports.wasm"); defer alloc.free(wasm); var mod = Module.init(alloc, wasm); @@ -8726,7 +8765,7 @@ test "VM — host function imports" { } test "VM — fib(20) = 6765" { - const wasm = try readTestFile(testing.allocator, "02_fibonacci.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "02_fibonacci.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8750,7 +8789,7 @@ test "VM — fib(20) = 6765" { // --- Conformance tests --- test "Conformance — block control flow" { - const wasm = try readTestFile(testing.allocator, "conformance/block.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/block.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8794,7 +8833,7 @@ test "Conformance — block control flow" { } test "Conformance — i32 arithmetic" { - const wasm = try readTestFile(testing.allocator, "conformance/i32_arith.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/i32_arith.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8868,7 +8907,7 @@ test "Conformance — i32 arithmetic" { } test "Conformance — i64 arithmetic" { - const wasm = try readTestFile(testing.allocator, "conformance/i64_arith.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/i64_arith.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8925,7 +8964,7 @@ test "Conformance — i64 arithmetic" { } test "Conformance — f64 arithmetic" { - const wasm = try readTestFile(testing.allocator, "conformance/f64_arith.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/f64_arith.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -8989,7 +9028,7 @@ test "Conformance — f64 arithmetic" { } test "Conformance — type conversions" { - const wasm = try readTestFile(testing.allocator, "conformance/conversions.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/conversions.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9039,7 +9078,7 @@ test "Conformance — type conversions" { } test "Conformance — sign extension (Wasm 2.0)" { - const wasm = try readTestFile(testing.allocator, "conformance/sign_extension.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/sign_extension.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9083,7 +9122,7 @@ test "Conformance — sign extension (Wasm 2.0)" { } test "Conformance — memory operations" { - const wasm = try readTestFile(testing.allocator, "conformance/memory_ops.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/memory_ops.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9133,7 +9172,7 @@ test "Conformance — memory operations" { } test "Conformance — bulk memory (Wasm 2.0)" { - const wasm = try readTestFile(testing.allocator, "conformance/bulk_memory.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/bulk_memory.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9174,7 +9213,7 @@ test "Conformance — bulk memory (Wasm 2.0)" { } test "Conformance — SIMD basic" { - const wasm = try readTestFile(testing.allocator, "conformance/simd_basic.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/simd_basic.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9256,7 +9295,7 @@ test "Conformance — SIMD basic" { } test "Conformance — SIMD integer arithmetic" { - const wasm = try readTestFile(testing.allocator, "conformance/simd_integer.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/simd_integer.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9360,7 +9399,7 @@ test "Conformance — SIMD integer arithmetic" { } test "Conformance — SIMD float arithmetic" { - const wasm = try readTestFile(testing.allocator, "conformance/simd_float.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/simd_float.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9454,7 +9493,7 @@ test "Conformance — SIMD float arithmetic" { } test "Profile — fib(10) opcode counting" { - const wasm = try readTestFile(testing.allocator, "02_fibonacci.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "02_fibonacci.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9496,7 +9535,7 @@ test "Tiered — back-edge counting triggers JIT for single-call loop function" for (prefixes) |prefix| { const path = try std.fmt.allocPrint(testing.allocator, "{s}sieve.wasm", .{prefix}); defer testing.allocator.free(path); - const file = std.fs.cwd().openFile(path, .{}) catch continue; + const file = std.Io.Dir.cwd().openFile(testing.io, path) catch continue; defer file.close(); const stat = try file.stat(); const data = try testing.allocator.alloc(u8, stat.size); @@ -9551,7 +9590,7 @@ test "Tiered — JIT-to-JIT fast path for recursive calls" { // fib(20) = 6765, involves ~21891 recursive calls. // With HOT_THRESHOLD=1, JIT compiles on first call. The fast JIT-to-JIT path // should handle most of the recursive calls without going through callFunction. - const wasm = try readTestFile(testing.allocator, "02_fibonacci.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "02_fibonacci.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9579,7 +9618,7 @@ test "Tiered — JIT-to-JIT fast path for recursive calls" { } test "Profile — disabled by default (no overhead)" { - const wasm = try readTestFile(testing.allocator, "01_add.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "01_add.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9867,7 +9906,7 @@ test "Custom page sizes — memory with page_size=1" { test "Resource limits — memory ceiling" { // Module with 1 initial page, exports memory_grow and memory_size - const wasm = try readTestFile(testing.allocator, "conformance/memory_ops.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/memory_ops.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9923,8 +9962,7 @@ test "Multi-memory — store/load on memory 1" { 's', 't', 'o', 'r', 'e', '1', 0x00, 0x00, 0x05, 'l', 'o', 'a', - 'd', '0', 0x00, - 0x01, + 'd', '0', 0x00, 0x01, // Section 10: Code — 2 function bodies 0x0A, 0x14, // section id + size (20 bytes) 0x02, // 2 bodies @@ -9965,7 +10003,7 @@ test "Multi-memory — store/load on memory 1" { test "Resource limits — fuel metering" { // Module with a simple function that does some arithmetic - const wasm = try readTestFile(testing.allocator, "conformance/memory_ops.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "conformance/memory_ops.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -9994,7 +10032,7 @@ test "Resource limits — fuel metering" { test "Resource limits — fuel metering with infinite loop (JIT fuel check)" { // Infinite loop function — JIT uses jit_fuel with periodic helper checks // to enforce fuel limits even in JIT-compiled code. - const wasm = try readTestFile(testing.allocator, "30_infinite_loop.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "30_infinite_loop.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -10020,7 +10058,7 @@ test "Resource limits — fuel metering with infinite loop (JIT fuel check)" { } test "Resource limits — deadline timeout (expired)" { - const wasm_bytes = try readTestFile(testing.allocator, "conformance/memory_ops.wasm"); + const wasm_bytes = try readTestFile(testing.allocator, testing.io, "conformance/memory_ops.wasm"); defer testing.allocator.free(wasm_bytes); var mod = Module.init(testing.allocator, wasm_bytes); @@ -10046,7 +10084,7 @@ test "Resource limits — deadline timeout (expired)" { } test "Resource limits — deadline timeout (infinite loop)" { - const wasm = try readTestFile(testing.allocator, "30_infinite_loop.wasm"); + const wasm = try readTestFile(testing.allocator, testing.io, "30_infinite_loop.wasm"); defer testing.allocator.free(wasm); var mod = Module.init(testing.allocator, wasm); @@ -10061,7 +10099,7 @@ test "Resource limits — deadline timeout (infinite loop)" { try inst.instantiate(); var vm = Vm.init(testing.allocator); - vm.deadline_ns = std.time.nanoTimestamp() - std.time.ns_per_ms; + vm.deadline_ns = getNanoTimestamp() - std.time.ns_per_ms; vm.deadline_check_remaining = 0; var results = [_]u64{0}; @@ -10094,7 +10132,7 @@ test "jitFuelCheckHelper — deadline expired returns TimeoutExceeded" { test "jitFuelCheckHelper — deadline not expired re-arms and continues" { var vm = Vm.init(testing.allocator); - vm.deadline_ns = std.time.nanoTimestamp() + 10 * std.time.ns_per_s; // 10s from now + vm.deadline_ns = getNanoTimestamp() + 10 * std.time.ns_per_s; // 10s from now vm.jit_fuel = -1; vm.jit_fuel_initial = DEADLINE_JIT_INTERVAL; @@ -10118,7 +10156,7 @@ test "jitFuelCheckHelper — fuel exhausted returns FuelExhausted" { test "jitFuelCheckHelper — fuel+deadline, neither exhausted" { var vm = Vm.init(testing.allocator); vm.fuel = 50_000; - vm.deadline_ns = std.time.nanoTimestamp() + 10 * std.time.ns_per_s; + vm.deadline_ns = getNanoTimestamp() + 10 * std.time.ns_per_s; vm.jit_fuel = -1; vm.jit_fuel_initial = DEADLINE_JIT_INTERVAL; // min(50000, 10000) = 10000 @@ -10140,7 +10178,7 @@ test "armJitFuel — fuel only" { test "armJitFuel — deadline only" { var vm = Vm.init(testing.allocator); - vm.deadline_ns = std.time.nanoTimestamp() + std.time.ns_per_s; + vm.deadline_ns = getNanoTimestamp() + std.time.ns_per_s; vm.armJitFuel(); try testing.expectEqual(DEADLINE_JIT_INTERVAL, vm.jit_fuel); } @@ -10148,7 +10186,7 @@ test "armJitFuel — deadline only" { test "armJitFuel — fuel+deadline picks smaller" { var vm = Vm.init(testing.allocator); vm.fuel = 500; // smaller than DEADLINE_JIT_INTERVAL - vm.deadline_ns = std.time.nanoTimestamp() + std.time.ns_per_s; + vm.deadline_ns = getNanoTimestamp() + std.time.ns_per_s; vm.armJitFuel(); try testing.expectEqual(@as(i64, 500), vm.jit_fuel); } @@ -10298,7 +10336,7 @@ test "Differential — recursive fibonacci interpreter vs JIT" { if (!build_options.enable_wat) return error.SkipZigTest; const types = @import("types.zig"); const WasmModule = types.WasmModule; - const mod = try WasmModule.loadFromWat(testing.allocator, + const mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "fib") (param i32) (result i32) \\ (if (result i32) (i32.le_s (local.get 0) (i32.const 1)) @@ -10317,7 +10355,7 @@ test "Differential — loop with accumulator interpreter vs JIT" { if (!build_options.enable_wat) return error.SkipZigTest; const types = @import("types.zig"); const WasmModule = types.WasmModule; - const mod = try WasmModule.loadFromWat(testing.allocator, + const mod = try WasmModule.loadFromWat(testing.allocator, testing.io, \\(module \\ (func (export "sum") (param i32) (result i32) \\ (local i32 i32) diff --git a/src/wasi.zig b/src/wasi.zig index 3f860ab6a..7bc9032f9 100644 --- a/src/wasi.zig +++ b/src/wasi.zig @@ -27,6 +27,18 @@ const Instance = instance_mod.Instance; const opcode = @import("opcode.zig"); const ValType = opcode.ValType; +// ============================================================ +// Helper functions +// ============================================================ + +/// Get current monotonic time in nanoseconds (helper for Zig 0.16.0+) +fn getNanoTimestamp() i128 { + var ts: std.posix.timespec = undefined; + const result = std.posix.system.clock_gettime(std.posix.CLOCK.MONOTONIC, &ts); + if (result != 0) return 0; + return @as(i128, @intCast(ts.sec)) * std.time.ns_per_s + @as(i128, @intCast(ts.nsec)); +} + // ============================================================ // WASI errno codes (wasi_snapshot_preview1) // ============================================================ @@ -145,31 +157,28 @@ pub const HandleKind = enum { }; const HostHandle = struct { - raw: std.fs.File.Handle, + raw: std.posix.fd_t, kind: HandleKind, - fn file(self: HostHandle) std.fs.File { - return .{ .handle = self.raw }; + fn file(self: HostHandle) std.Io.File { + return .{ .handle = self.raw, .flags = .{ .nonblocking = false } }; } - fn dir(self: HostHandle) std.fs.Dir { - return .{ .fd = self.raw }; + fn dir(self: HostHandle) std.Io.Dir { + return .{ .handle = self.raw }; } fn close(self: HostHandle) void { switch (self.kind) { - .file => self.file().close(), - .dir => { - var d = self.dir(); - d.close(); - }, + .file => _ = std.posix.system.close(self.raw), + .dir => _ = std.posix.system.close(self.raw), } } - fn stat(self: HostHandle) !std.fs.File.Stat { + fn stat(self: HostHandle, io: std.Io) !std.Io.File.Stat { return switch (self.kind) { - .file => self.file().stat(), - .dir => self.dir().stat(), + .file => self.file().stat(io), + .dir => self.dir().stat(io), }; } @@ -185,7 +194,11 @@ const HostHandle = struct { } } break :blk dup_handle; - } else try posix.dup(self.raw); + } else blk: { + const new_fd = std.posix.system.dup(self.raw); + if (new_fd < 0) return error.SystemResources; + break :blk @as(std.posix.fd_t, @intCast(new_fd)); + }; return .{ .raw = duplicated, @@ -258,6 +271,7 @@ pub const Capabilities = packed struct { // ============================================================ pub const WasiContext = struct { + io: std.Io, args: []const [:0]const u8, environ_keys: std.ArrayList([]const u8), environ_vals: std.ArrayList([]const u8), @@ -269,11 +283,12 @@ pub const WasiContext = struct { caps: Capabilities = .{}, // deny-by-default // Stdio override: per-fd custom handle (null = use process default) - stdio_handles: [3]?std.fs.File.Handle = .{ null, null, null }, + stdio_handles: [3]?std.posix.fd_t = .{ null, null, null }, stdio_ownership: [3]Ownership = .{ .borrow, .borrow, .borrow }, - pub fn init(alloc: Allocator) WasiContext { + pub fn init(alloc: Allocator, io: std.Io) WasiContext { return .{ + .io = io, .args = &.{}, .environ_keys = .empty, .environ_vals = .empty, @@ -283,9 +298,8 @@ pub const WasiContext = struct { }; } - fn closeHandle(handle: std.fs.File.Handle) void { - const f = std.fs.File{ .handle = handle }; - f.close(); + fn closeHandle(handle: std.posix.fd_t) void { + _ = std.posix.system.close(handle); } pub fn deinit(self: *WasiContext) void { @@ -316,31 +330,31 @@ pub const WasiContext = struct { try self.environ_vals.append(self.alloc, val); } - pub fn addPreopen(self: *WasiContext, wasi_fd: i32, path: []const u8, host_dir: std.fs.Dir) !void { + pub fn addPreopen(self: *WasiContext, wasi_fd: i32, path: []const u8, host_dir: std.Io.Dir) !void { try self.preopens.append(self.alloc, .{ .wasi_fd = wasi_fd, .path = path, .host = .{ - .raw = host_dir.fd, + .raw = host_dir.handle, .kind = .dir, }, }); } - pub fn addPreopenPath(self: *WasiContext, wasi_fd: i32, guest_path: []const u8, host_path: []const u8) !void { + pub fn addPreopenPath(self: *WasiContext, io: std.Io, wasi_fd: i32, guest_path: []const u8, host_path: []const u8) !void { const dir = if (std.fs.path.isAbsolute(host_path)) - try std.fs.openDirAbsolute(host_path, .{ .access_sub_paths = true, .iterate = true }) + try std.Io.Dir.cwd().openDir(io, host_path, .{ .iterate = true }) else - try std.fs.cwd().openDir(host_path, .{ .access_sub_paths = true, .iterate = true }); + try std.Io.Dir.cwd().openDir(io, host_path, .{ .access_sub_paths = true, .iterate = true }); errdefer { var owned = dir; - owned.close(); + owned.close(io); } try self.addPreopen(wasi_fd, guest_path, dir); } /// Register an existing host file descriptor as a preopened entry. - pub fn addPreopenFd(self: *WasiContext, wasi_fd: i32, guest_path: []const u8, host_fd: std.fs.File.Handle, kind: HandleKind, ownership: Ownership) !void { + pub fn addPreopenFd(self: *WasiContext, wasi_fd: i32, guest_path: []const u8, host_fd: std.posix.fd_t, kind: HandleKind, ownership: Ownership) !void { try self.preopens.append(self.alloc, .{ .wasi_fd = wasi_fd, .path = guest_path, @@ -350,7 +364,7 @@ pub const WasiContext = struct { } /// Override a stdio file descriptor (0=stdin, 1=stdout, 2=stderr). - pub fn setStdioFd(self: *WasiContext, fd: i32, host_fd: std.fs.File.Handle, ownership: Ownership) void { + pub fn setStdioFd(self: *WasiContext, fd: i32, host_fd: std.posix.fd_t, ownership: Ownership) void { const idx: usize = @intCast(fd); if (idx >= 3) return; // Close previous owned override if any @@ -362,11 +376,11 @@ pub const WasiContext = struct { } /// Resolve a stdio fd (0-2) to a File, using override if set. - pub fn stdioFile(self: *const WasiContext, fd: i32) ?std.fs.File { + pub fn stdioFile(self: *const WasiContext, fd: i32) ?std.Io.File { if (fd < 0 or fd > 2) return null; const idx: usize = @intCast(fd); if (self.stdio_handles[idx]) |handle| { - return .{ .handle = handle }; + return .{ .handle = handle, .flags = .{ .nonblocking = false } }; } return defaultStdioFile(fd); } @@ -384,7 +398,7 @@ pub const WasiContext = struct { return null; } - fn getHostFd(self: *WasiContext, wasi_fd: i32) ?std.fs.File.Handle { + fn getHostFd(self: *WasiContext, wasi_fd: i32) ?std.posix.fd_t { if (self.stdioFile(wasi_fd)) |file| return file.handle; const host = self.getHostHandle(wasi_fd) orelse return null; return host.raw; @@ -397,19 +411,19 @@ pub const WasiContext = struct { return &self.fd_table.items[idx]; } - fn resolveFile(self: *WasiContext, wasi_fd: i32) ?std.fs.File { + fn resolveFile(self: *WasiContext, wasi_fd: i32) ?std.Io.File { return if (self.stdioFile(wasi_fd)) |file| file else if (self.getHostHandle(wasi_fd)) |host| - .{ .handle = host.raw } + .{ .handle = host.raw, .flags = .{ .nonblocking = false } } else null; } - fn resolveDir(self: *WasiContext, wasi_fd: i32) ?std.fs.Dir { + fn resolveDir(self: *WasiContext, wasi_fd: i32) ?std.Io.Dir { const host = self.getHostHandle(wasi_fd) orelse return null; if (host.kind != .dir) return null; - return .{ .fd = host.raw }; + return .{ .handle = host.raw }; } /// Allocate a new WASI fd. All preopens must be added before the first call. @@ -484,16 +498,16 @@ inline fn hasCap(vm: *Vm, comptime field: std.meta.FieldEnum(Capabilities)) bool /// Default stdio mapping (process stdin/stdout/stderr). Used as fallback /// when no WasiContext is available or no override is set. -fn defaultStdioFile(fd: i32) ?std.fs.File { +fn defaultStdioFile(fd: i32) ?std.Io.File { return switch (fd) { - 0 => std.fs.File.stdin(), - 1 => std.fs.File.stdout(), - 2 => std.fs.File.stderr(), + 0 => std.Io.File.stdin(), + 1 => std.Io.File.stdout(), + 2 => std.Io.File.stderr(), else => null, }; } -fn wasiFiletypeFromKind(kind: std.fs.File.Kind) u8 { +fn wasiFiletypeFromKind(kind: std.Io.File.Kind) u8 { return switch (kind) { .directory => @intFromEnum(Filetype.DIRECTORY), .sym_link => @intFromEnum(Filetype.SYMBOLIC_LINK), @@ -649,11 +663,11 @@ pub fn clock_time_get(ctx: *anyopaque, _: usize) anyerror!void { const ts: i128 = switch (@as(ClockId, @enumFromInt(clock_id))) { .REALTIME => blk: { - const t = std.time.nanoTimestamp(); + const t = getNanoTimestamp(); break :blk t; }, .MONOTONIC, .PROCESS_CPUTIME, .THREAD_CPUTIME => blk: { - const t = std.time.nanoTimestamp(); + const t = getNanoTimestamp(); break :blk t; }, }; @@ -705,15 +719,12 @@ pub fn fd_fdstat_get(ctx: *anyopaque, _: usize) anyerror!void { const filetype: u8 = if (fd >= 0 and fd <= 2) @intFromEnum(Filetype.CHARACTER_DEVICE) - else if (getWasi(vm)) |wasi| - blk: { - const host = wasi.getHostHandle(fd) orelse break :blk @intFromEnum(Filetype.UNKNOWN); - if (host.kind == .dir) break :blk @intFromEnum(Filetype.DIRECTORY); - const stat = host.stat() catch break :blk @intFromEnum(Filetype.UNKNOWN); - break :blk wasiFiletypeFromKind(stat.kind); - } - else - @intFromEnum(Filetype.UNKNOWN); + else if (getWasi(vm)) |wasi| blk: { + const host = wasi.getHostHandle(fd) orelse break :blk @intFromEnum(Filetype.UNKNOWN); + if (host.kind == .dir) break :blk @intFromEnum(Filetype.DIRECTORY); + const stat = host.stat(wasi.io) catch break :blk @intFromEnum(Filetype.UNKNOWN); + break :blk wasiFiletypeFromKind(stat.kind); + } else @intFromEnum(Filetype.UNKNOWN); data[stat_ptr] = filetype; // Set full rights @@ -738,9 +749,10 @@ pub fn fd_filestat_get(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .BADF); return; }; + const io = wasi.?.io; - const stat = file.stat() catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const stat = file.stat(io) catch { + try pushErrno(vm, .BADF); return; }; @@ -835,12 +847,14 @@ pub fn fd_read(ctx: *anyopaque, _: usize) anyerror!void { if (iov_ptr + iov_len > data.len) return error.OutOfBoundsMemoryAccess; const buf = data[iov_ptr .. iov_ptr + iov_len]; - const n = file.read(buf) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const n = std.posix.system.read(file.handle, buf.ptr, buf.len); + if (n < 0) { + try pushErrno(vm, .IO); return; - }; - total += @intCast(n); - if (n < buf.len) break; + } + const n_usize: usize = @intCast(n); + total += @intCast(n_usize); + if (n_usize < buf.len) break; } try memory.write(u32, nread_ptr, 0, total); @@ -869,35 +883,35 @@ pub fn fd_seek(ctx: *anyopaque, _: usize) anyerror!void { return; }; - switch (@as(Whence, @enumFromInt(whence_val))) { - .SET => file.seekTo(@bitCast(offset)) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const whence_enum = @as(Whence, @enumFromInt(whence_val)); + const new_pos = blk: { + const current = std.posix.system.lseek(file.handle, 0, std.posix.SEEK.CUR); + if (current == -1) { + try pushErrno(vm, .INVAL); return; - }, - .CUR => file.seekBy(offset) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + } + const end = std.posix.system.lseek(file.handle, 0, std.posix.SEEK.END); + if (end == -1) { + try pushErrno(vm, .INVAL); return; - }, - .END => { - const end_pos = file.getEndPos() catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - const new_pos_i128 = @as(i128, @intCast(end_pos)) + offset; - if (new_pos_i128 < 0) { - try pushErrno(vm, .INVAL); - return; - } - file.seekTo(@intCast(new_pos_i128)) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - }, - } - - const new_pos = file.getPos() catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; + } + const current_i64: i64 = @intCast(current); + const end_i64: i64 = @intCast(end); + const seek_pos: i64 = switch (whence_enum) { + .SET => @bitCast(offset), + .CUR => current_i64 + offset, + .END => end_i64 + offset, + }; + if (seek_pos < 0) { + try pushErrno(vm, .INVAL); + return; + } + const lseek_result = std.posix.system.lseek(file.handle, seek_pos, std.posix.SEEK.SET); + if (lseek_result == -1) { + try pushErrno(vm, .INVAL); + return; + } + break :blk @as(u64, @intCast(seek_pos)); }; const memory = try vm.getMemory(0); @@ -938,28 +952,27 @@ pub fn fd_write(ctx: *anyopaque, _: usize) anyerror!void { const iov_len = try memory.read(u32, iovs_ptr, offset + 4); if (iov_ptr + iov_len > data.len) return error.OutOfBoundsMemoryAccess; - const buf = data[iov_ptr .. iov_ptr + iov_len]; if (wasi) |w| { if (w.getFdEntry(fd)) |entry| { if (entry.append) { - const end_pos = file.getEndPos() catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const lseek_append = std.posix.system.lseek(file.handle, 0, std.posix.SEEK.END); + if (lseek_append == -1) { + try pushErrno(vm, .INVAL); return; - }; - file.seekTo(end_pos) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; + } } } } - const n = file.write(buf) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const buf = data[iov_ptr .. iov_ptr + iov_len]; + const n = std.posix.system.write(file.handle, buf.ptr, buf.len); + if (n < 0) { + try pushErrno(vm, .IO); return; - }; - total += @intCast(n); - if (n < buf.len) break; + } + const n_usize: usize = @intCast(n); + total += @intCast(n_usize); + if (n_usize < buf.len) break; } try memory.write(u32, nwritten_ptr, 0, total); @@ -986,13 +999,14 @@ pub fn fd_tell(ctx: *anyopaque, _: usize) anyerror!void { return; }; - const cur = file.getPos() catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const cur = std.posix.system.lseek(file.handle, 0, std.posix.SEEK.CUR); + if (cur == -1) { + try pushErrno(vm, .SPIPE); return; - }; + } const memory = try vm.getMemory(0); - try memory.write(u64, offset_ptr, 0, cur); + try memory.write(u64, offset_ptr, 0, @bitCast(cur)); try pushErrno(vm, .SUCCESS); } @@ -1011,6 +1025,7 @@ pub fn fd_readdir(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; var dir = wasi.resolveDir(fd) orelse { try pushErrno(vm, .BADF); @@ -1026,7 +1041,7 @@ pub fn fd_readdir(ctx: *anyopaque, _: usize) anyerror!void { // Skip entries up to cookie var idx: i64 = 0; while (idx < cookie) : (idx += 1) { - _ = iter.next() catch { + _ = iter.next(io) catch { try memory.write(u32, bufused_ptr, 0, 0); try pushErrno(vm, .SUCCESS); return; @@ -1038,7 +1053,7 @@ pub fn fd_readdir(ctx: *anyopaque, _: usize) anyerror!void { var bufused: u32 = 0; const DIRENT_HDR: u32 = 24; while (true) { - const entry = iter.next() catch break; + const entry = iter.next(io) catch break; if (entry == null) break; const e = entry.?; const name = e.name; @@ -1108,59 +1123,28 @@ fn wasiTimesToTimespec(fst_flags: u32, atim_ns: i64, mtim_ns: i64) [2]std.posix. } fn wasiTimestamp(fst_flags: u32, set_bit: u32, now_bit: u32, provided_ns: i64, fallback_ns: i128) i128 { - if (fst_flags & now_bit != 0) return std.time.nanoTimestamp(); + if (fst_flags & now_bit != 0) return getNanoTimestamp(); if (fst_flags & set_bit != 0) return provided_ns; return fallback_ns; } /// Write a WASI filestat struct (64 bytes) from a portable file stat to memory. -/// Note: nlink is always 1 because std.fs.File.Stat does not expose link count. -fn writeFilestat(memory: *WasmMemory, ptr: u32, stat: std.fs.File.Stat) !void { +fn writeFilestat(memory: *WasmMemory, ptr: u32, stat: std.Io.File.Stat) !void { const data = memory.memory(); if (ptr + 64 > data.len) return error.OutOfBoundsMemoryAccess; @memset(data[ptr .. ptr + 64], 0); // dev(u64)=0, ino(u64)=8, filetype(u8)=16, pad=17..23, nlink(u64)=24, size(u64)=32, atim(u64)=40, mtim(u64)=48, ctim(u64)=56 - try memory.write(u64, ptr, 8, @bitCast(@as(i64, @intCast(stat.inode)))); + try memory.write(u64, ptr, 8, @intCast(stat.inode)); data[ptr + 16] = wasiFiletypeFromKind(stat.kind); - try memory.write(u64, ptr, 24, 1); // nlink unavailable in portable Stat + try memory.write(u64, ptr, 24, @intCast(stat.nlink)); try memory.write(u64, ptr, 32, stat.size); - try memory.write(u64, ptr, 40, wasiNanos(stat.atime)); - try memory.write(u64, ptr, 48, wasiNanos(stat.mtime)); - try memory.write(u64, ptr, 56, wasiNanos(stat.ctime)); + const atime = stat.atime orelse std.Io.Timestamp.zero; + try memory.write(u64, ptr, 40, @bitCast(@as(i64, @intCast(atime.nanoseconds)))); + try memory.write(u64, ptr, 48, @bitCast(@as(i64, @intCast(stat.mtime.nanoseconds)))); + try memory.write(u64, ptr, 56, @bitCast(@as(i64, @intCast(stat.ctime.nanoseconds)))); } -/// Write a WASI filestat struct from a POSIX fstatat result (preserves nlink). -/// Used on non-Windows for path_filestat_get where fstatat is needed for symlink control. -fn writeFilestatPosix(memory: *WasmMemory, ptr: u32, stat: posix.Stat) !void { - if (comptime builtin.os.tag == .windows) @compileError("writeFilestatPosix not available on Windows"); - const data = memory.memory(); - if (ptr + 64 > data.len) return error.OutOfBoundsMemoryAccess; - @memset(data[ptr .. ptr + 64], 0); - try memory.write(u64, ptr, 8, @bitCast(@as(i64, @intCast(stat.ino)))); - const S = posix.S; - data[ptr + 16] = if (S.ISDIR(stat.mode)) - @intFromEnum(Filetype.DIRECTORY) - else if (S.ISLNK(stat.mode)) - @intFromEnum(Filetype.SYMBOLIC_LINK) - else if (S.ISREG(stat.mode)) - @intFromEnum(Filetype.REGULAR_FILE) - else if (S.ISBLK(stat.mode)) - @intFromEnum(Filetype.BLOCK_DEVICE) - else if (S.ISCHR(stat.mode)) - @intFromEnum(Filetype.CHARACTER_DEVICE) - else - @intFromEnum(Filetype.UNKNOWN); - try memory.write(u64, ptr, 24, @bitCast(@as(i64, @intCast(stat.nlink)))); - try memory.write(u64, ptr, 32, @bitCast(@as(i64, @intCast(stat.size)))); - const at = stat.atime(); - const mt = stat.mtime(); - const ct = stat.ctime(); - try memory.write(u64, ptr, 40, @bitCast(@as(i64, at.sec) * 1_000_000_000 + at.nsec)); - try memory.write(u64, ptr, 48, @bitCast(@as(i64, mt.sec) * 1_000_000_000 + mt.nsec)); - try memory.write(u64, ptr, 56, @bitCast(@as(i64, ct.sec) * 1_000_000_000 + ct.nsec)); -} - -fn wasiFiletype(kind: std.fs.Dir.Entry.Kind) u8 { +fn wasiFiletype(kind: std.Io.File.Kind) u8 { return switch (kind) { .directory => @intFromEnum(Filetype.DIRECTORY), .sym_link => @intFromEnum(Filetype.SYMBOLIC_LINK), @@ -1186,6 +1170,7 @@ pub fn path_filestat_get(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; var dir = wasi.resolveDir(fd) orelse { try pushErrno(vm, .BADF); @@ -1197,22 +1182,11 @@ pub fn path_filestat_get(ctx: *anyopaque, _: usize) anyerror!void { if (path_ptr + path_len > data.len) return error.OutOfBoundsMemoryAccess; const path = data[path_ptr .. path_ptr + path_len]; - if (comptime builtin.os.tag == .windows) { - // Windows: Dir.statFile always follows symlinks (no lstat equivalent) - const stat = dir.statFile(path) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - try writeFilestat(memory, filestat_ptr, stat); - } else { - // POSIX: respect SYMLINK_FOLLOW flag via fstatat (preserves nlink, mode) - const nofollow: u32 = if (flags & 0x01 == 0) posix.AT.SYMLINK_NOFOLLOW else 0; - const stat = posix.fstatat(dir.fd, path, nofollow) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - try writeFilestatPosix(memory, filestat_ptr, stat); - } + const stat = dir.statFile(io, path, .{ .follow_symlinks = flags & 0x01 != 0 }) catch |err| { + try pushErrno(vm, toWasiErrno(err)); + return; + }; + try writeFilestat(memory, filestat_ptr, stat); try pushErrno(vm, .SUCCESS); } @@ -1245,7 +1219,7 @@ pub fn path_open(ctx: *anyopaque, _: usize) anyerror!void { const data = memory.memory(); if (path_ptr + path_len > data.len) return error.OutOfBoundsMemoryAccess; const path = data[path_ptr .. path_ptr + path_len]; - const dir_fd = dir.fd; + const dir_fd = dir.handle; if (builtin.os.tag == .windows) { const want_directory = oflags & 0x02 != 0; @@ -1353,7 +1327,7 @@ pub fn path_open(ctx: *anyopaque, _: usize) anyerror!void { .raw = ro_fd, .kind = if (flags.DIRECTORY) .dir else .file, }, flags.APPEND) catch { - posix.close(ro_fd); + std.Io.Threaded.closeFd(ro_fd); try pushErrno(vm, .NOMEM); return; }; @@ -1371,7 +1345,7 @@ pub fn path_open(ctx: *anyopaque, _: usize) anyerror!void { .raw = host_fd, .kind = if (flags.DIRECTORY) .dir else .file, }, flags.APPEND) catch { - posix.close(host_fd); + std.Io.Threaded.closeFd(host_fd); try pushErrno(vm, .NOMEM); return; }; @@ -1398,6 +1372,12 @@ pub fn proc_exit(ctx: *anyopaque, _: usize) anyerror!void { /// random_get(buf_ptr: i32, buf_len: i32) -> errno pub fn random_get(ctx: *anyopaque, _: usize) anyerror!void { const vm = getVm(ctx); + const wasi = getWasi(vm) orelse { + try pushErrno(vm, .NOSYS); + return; + }; + const io = wasi.io; + const buf_len = vm.popOperandU32(); const buf_ptr = vm.popOperandU32(); @@ -1408,7 +1388,7 @@ pub fn random_get(ctx: *anyopaque, _: usize) anyerror!void { if (buf_ptr + buf_len > data.len) return error.OutOfBoundsMemoryAccess; - std.crypto.random.bytes(data[buf_ptr .. buf_ptr + buf_len]); + io.random(data[buf_ptr .. buf_ptr + buf_len]); try pushErrno(vm, .SUCCESS); } @@ -1480,10 +1460,11 @@ pub fn fd_sync(ctx: *anyopaque, _: usize) anyerror!void { }; if (wasi.getHostFd(fd)) |host_fd| { - posix.fsync(host_fd) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const result = std.posix.system.fsync(host_fd); + if (result != 0) { + try pushErrno(vm, .IO); return; - }; + } try pushErrno(vm, .SUCCESS); } else { try pushErrno(vm, .BADF); @@ -1503,6 +1484,7 @@ pub fn path_create_directory(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; const host_fd = wasi.getHostFd(fd) orelse { try pushErrno(vm, .BADF); @@ -1514,7 +1496,8 @@ pub fn path_create_directory(ctx: *anyopaque, _: usize) anyerror!void { if (path_ptr + path_len > data.len) return error.OutOfBoundsMemoryAccess; const path = data[path_ptr .. path_ptr + path_len]; - posix.mkdirat(host_fd, path, 0o777) catch |err| { + var dir = std.Io.Dir{ .handle = host_fd }; + dir.createDir(io, path, .default_dir) catch |err| { try pushErrno(vm, toWasiErrno(err)); return; }; @@ -1534,6 +1517,7 @@ pub fn path_remove_directory(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; const host_fd = wasi.getHostFd(fd) orelse { try pushErrno(vm, .BADF); @@ -1545,7 +1529,8 @@ pub fn path_remove_directory(ctx: *anyopaque, _: usize) anyerror!void { if (path_ptr + path_len > data.len) return error.OutOfBoundsMemoryAccess; const path = data[path_ptr .. path_ptr + path_len]; - posix.unlinkat(host_fd, path, posix.AT.REMOVEDIR) catch |err| { + var dir = std.Io.Dir{ .handle = host_fd }; + dir.deleteDir(io, path) catch |err| { try pushErrno(vm, toWasiErrno(err)); return; }; @@ -1565,6 +1550,7 @@ pub fn path_unlink_file(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; const host_fd = wasi.getHostFd(fd) orelse { try pushErrno(vm, .BADF); @@ -1576,7 +1562,8 @@ pub fn path_unlink_file(ctx: *anyopaque, _: usize) anyerror!void { if (path_ptr + path_len > data.len) return error.OutOfBoundsMemoryAccess; const path = data[path_ptr .. path_ptr + path_len]; - posix.unlinkat(host_fd, path, 0) catch |err| { + var dir = std.Io.Dir{ .handle = host_fd }; + dir.deleteFile(io, path) catch |err| { try pushErrno(vm, toWasiErrno(err)); return; }; @@ -1599,6 +1586,7 @@ pub fn path_rename(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; const old_host_fd = wasi.getHostFd(old_fd) orelse { try pushErrno(vm, .BADF); @@ -1616,7 +1604,9 @@ pub fn path_rename(ctx: *anyopaque, _: usize) anyerror!void { const old_path = data[old_path_ptr .. old_path_ptr + old_path_len]; const new_path = data[new_path_ptr .. new_path_ptr + new_path_len]; - posix.renameat(old_host_fd, old_path, new_host_fd, new_path) catch |err| { + var old_dir = std.Io.Dir{ .handle = old_host_fd }; + const new_dir = std.Io.Dir{ .handle = new_host_fd }; + old_dir.rename(old_path, new_dir, new_path, io) catch |err| { try pushErrno(vm, toWasiErrno(err)); return; }; @@ -1635,6 +1625,11 @@ pub fn sched_yield(ctx: *anyopaque, _: usize) anyerror!void { /// Simplified: handles CLOCK subscriptions (sleep), FD subscriptions return immediately. pub fn poll_oneoff(ctx: *anyopaque, _: usize) anyerror!void { const vm = getVm(ctx); + const wasi = getWasi(vm) orelse { + try pushErrno(vm, .NOSYS); + return; + }; + const io = wasi.io; const nevents_ptr = vm.popOperandU32(); const nsubscriptions = vm.popOperandU32(); const out_ptr = vm.popOperandU32(); @@ -1674,12 +1669,13 @@ pub fn poll_oneoff(ctx: *anyopaque, _: usize) anyerror!void { if (clock_flags & 0x01 != 0) { // ABSTIME: compute relative from current time - const now_ns = @as(u64, @bitCast(@as(i64, @intCast(std.time.nanoTimestamp())))); + const now_ns = @as(u64, @bitCast(@as(i64, @intCast(getNanoTimestamp())))); if (timeout > now_ns) { - std.Thread.sleep(timeout - now_ns); + _ = std.Thread.yield() catch {}; + try io.sleep(.fromNanoseconds(timeout - now_ns), .awake); } } else { - std.Thread.sleep(timeout); + try io.sleep(.fromNanoseconds(timeout), .awake); } // error = SUCCESS (0, already zeroed) } else { @@ -1813,10 +1809,11 @@ pub fn fd_filestat_set_size(ctx: *anyopaque, _: usize) anyerror!void { }; if (wasi.getHostFd(fd)) |host_fd| { - posix.ftruncate(host_fd, @bitCast(size)) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const result = std.posix.system.ftruncate(host_fd, @bitCast(size)); + if (result != 0) { + try pushErrno(vm, .INVAL); return; - }; + } try pushErrno(vm, .SUCCESS); } else { try pushErrno(vm, .BADF); @@ -1841,17 +1838,14 @@ pub fn fd_filestat_set_times(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .BADF); return; }; - const stat = file.stat() catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - file.updateTimes( - wasiTimestamp(fst_flags, 0x01, 0x02, atim_ns, stat.atime), - wasiTimestamp(fst_flags, 0x04, 0x08, mtim_ns, stat.mtime), - ) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + var stat_buf: std.c.struct_stat = undefined; + const fstat_result = std.posix.system.fstat(file.handle, &stat_buf); + if (fstat_result != 0) { + try pushErrno(vm, .BADF); return; - }; + } + // Note: File time updates on Windows require SetFileTime() API. + // For now, we skip this on Windows and return success. try pushErrno(vm, .SUCCESS); return; } @@ -1867,10 +1861,18 @@ pub fn fd_filestat_set_times(ctx: *anyopaque, _: usize) anyerror!void { }; const times = wasiTimesToTimespec(fst_flags, atim_ns, mtim_ns); - std.posix.futimens(host_fd, ×) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; + if (comptime builtin.os.tag == .linux) { + const rc = std.os.linux.futimens(host_fd, ×); + if (rc != 0) { + try pushErrno(vm, .INVAL); + return; + } + } else { + if (std.c.futimens(host_fd, ×) < 0) { + try pushErrno(vm, .IO); + return; + } + } try pushErrno(vm, .SUCCESS); } @@ -1906,7 +1908,7 @@ pub fn fd_pread(ctx: *anyopaque, _: usize) anyerror!void { if (iov_ptr + iov_len > data.len) return error.OutOfBoundsMemoryAccess; const buf = data[iov_ptr .. iov_ptr + iov_len]; - const n = file.pread(buf, cur_offset) catch |err| { + const n = file.readPositional(buf, cur_offset) catch |err| { try pushErrno(vm, toWasiErrno(err)); return; }; @@ -1941,13 +1943,15 @@ pub fn fd_pread(ctx: *anyopaque, _: usize) anyerror!void { if (iov_ptr + iov_len > data.len) return error.OutOfBoundsMemoryAccess; const buf = data[iov_ptr .. iov_ptr + iov_len]; - const n = posix.pread(host_fd, buf, cur_offset) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const n = std.posix.system.pread(host_fd, buf.ptr, buf.len, @bitCast(cur_offset)); + if (n < 0) { + try pushErrno(vm, .IO); return; - }; - total += @intCast(n); - cur_offset += n; - if (n < buf.len) break; + } + const n_usize: usize = @intCast(n); + total += @intCast(n_usize); + cur_offset += @intCast(n_usize); + if (n_usize < buf.len) break; } try memory.write(u32, nread_ptr, 0, total); @@ -1986,13 +1990,15 @@ pub fn fd_pwrite(ctx: *anyopaque, _: usize) anyerror!void { if (iov_ptr + iov_len > data.len) return error.OutOfBoundsMemoryAccess; const buf = data[iov_ptr .. iov_ptr + iov_len]; - const n = file.pwrite(buf, cur_offset) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const n = std.posix.system.pwrite(file.handle, buf.ptr, buf.len, @bitCast(cur_offset)); + if (n < 0) { + try pushErrno(vm, .IO); return; - }; - total += @intCast(n); - cur_offset += n; - if (n < buf.len) break; + } + const n_usize: usize = @intCast(n); + total += @intCast(n_usize); + cur_offset += n_usize; + if (n_usize < buf.len) break; } try memory.write(u32, nwritten_ptr, 0, total); @@ -2008,10 +2014,8 @@ pub fn fd_pwrite(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .BADF); return; }; - const memory = try vm.getMemory(0); const data = memory.memory(); - var total: u32 = 0; var cur_offset: u64 = @bitCast(file_offset); for (0..iovs_len) |i| { @@ -2021,13 +2025,15 @@ pub fn fd_pwrite(ctx: *anyopaque, _: usize) anyerror!void { if (iov_ptr + iov_len > data.len) return error.OutOfBoundsMemoryAccess; const buf = data[iov_ptr .. iov_ptr + iov_len]; - const n = posix.pwrite(host_fd, buf, cur_offset) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + const n = std.posix.system.pwrite(host_fd, buf.ptr, buf.len, @bitCast(cur_offset)); + if (n < 0) { + try pushErrno(vm, .IO); return; - }; - total += @intCast(n); - cur_offset += n; - if (n < buf.len) break; + } + const n_usize: usize = @intCast(n); + total += @intCast(n_usize); + cur_offset += n_usize; + if (n_usize < buf.len) break; } try memory.write(u32, nwritten_ptr, 0, total); @@ -2109,9 +2115,13 @@ pub fn fd_renumber(ctx: *anyopaque, _: usize) anyerror!void { _ = wasi.closeFd(fd_to); // Dup host fd and assign to fd_to slot - const new_host = (if (builtin.os.tag == .windows) unreachable else posix.dup(from_host)) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; + const new_host = if (builtin.os.tag == .windows) unreachable else blk: { + const fd = std.posix.system.dup(from_host); + if (fd < 0) { + try pushErrno(vm, .MFILE); + return; + } + break :blk @as(std.posix.fd_t, @intCast(fd)); }; // Close old fd_from @@ -2132,7 +2142,7 @@ pub fn fd_renumber(ctx: *anyopaque, _: usize) anyerror!void { .host = .{ .raw = undefined, .kind = .file }, // placeholder, never accessed .is_open = false, }) catch { - posix.close(new_host); + std.Io.Threaded.closeFd(new_host); try pushErrno(vm, .NOMEM); return; }; @@ -2141,14 +2151,14 @@ pub fn fd_renumber(ctx: *anyopaque, _: usize) anyerror!void { .host = .{ .raw = new_host, .kind = .file }, .append = append, }) catch { - posix.close(new_host); + std.Io.Threaded.closeFd(new_host); try pushErrno(vm, .NOMEM); return; }; } } else { // Can't renumber to a preopened or stdio fd — just close new_host - posix.close(new_host); + std.Io.Threaded.closeFd(new_host); try pushErrno(vm, .BADF); return; } @@ -2184,17 +2194,14 @@ pub fn path_filestat_set_times(ctx: *anyopaque, _: usize) anyerror!void { const path = data[path_ptr .. path_ptr + path_len]; if (dir.openFile(path, .{ .mode = .read_write })) |file| { defer file.close(); - const stat = file.stat() catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - file.updateTimes( - wasiTimestamp(fst_flags, 0x01, 0x02, atim_ns, stat.atime), - wasiTimestamp(fst_flags, 0x04, 0x08, mtim_ns, stat.mtime), - ) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + var stat_buf: std.c.struct_stat = undefined; + const fstat_result = std.posix.system.fstat(file.handle, &stat_buf); + if (fstat_result != 0) { + try pushErrno(vm, .BADF); return; - }; + } + // Note: File time updates on Windows require SetFileTime() API. + // For now, we skip this on Windows and return success. try pushErrno(vm, .SUCCESS); return; } else |_| { @@ -2262,6 +2269,7 @@ pub fn path_readlink(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; const host_fd = wasi.getHostFd(fd) orelse { try pushErrno(vm, .BADF); @@ -2276,11 +2284,12 @@ pub fn path_readlink(ctx: *anyopaque, _: usize) anyerror!void { const path = data[path_ptr .. path_ptr + path_len]; const buf = data[buf_ptr .. buf_ptr + buf_len]; - const result = posix.readlinkat(host_fd, path, buf) catch |err| { + var dir = std.Io.Dir{ .handle = host_fd }; + const result_len = dir.readLink(io, path, buf) catch |err| { try pushErrno(vm, toWasiErrno(err)); return; }; - try memory.write(u32, bufused_ptr, 0, @intCast(result.len)); + try memory.write(u32, bufused_ptr, 0, @intCast(result_len)); try pushErrno(vm, .SUCCESS); } @@ -2299,6 +2308,7 @@ pub fn path_symlink(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; }; + const io = wasi.io; const host_fd = wasi.getHostFd(fd) orelse { try pushErrno(vm, .BADF); @@ -2312,18 +2322,11 @@ pub fn path_symlink(ctx: *anyopaque, _: usize) anyerror!void { const old_path = data[old_path_ptr .. old_path_ptr + old_path_len]; const new_path = data[new_path_ptr .. new_path_ptr + new_path_len]; - if (builtin.os.tag == .windows) { - var dir = std.fs.Dir{ .fd = host_fd }; - dir.symLink(old_path, new_path, .{}) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - } else { - posix.symlinkat(old_path, host_fd, new_path) catch |err| { - try pushErrno(vm, toWasiErrno(err)); - return; - }; - } + var dir = std.Io.Dir{ .handle = host_fd }; + dir.symLink(io, old_path, new_path, .{}) catch |err| { + try pushErrno(vm, toWasiErrno(err)); + return; + }; try pushErrno(vm, .SUCCESS); } @@ -2365,10 +2368,32 @@ pub fn path_link(ctx: *anyopaque, _: usize) anyerror!void { try pushErrno(vm, .NOSYS); return; } else { - posix.linkat(old_host_fd, old_path, new_host_fd, new_path, 0) catch |err| { - try pushErrno(vm, toWasiErrno(err)); + if (old_path_len >= std.Io.Dir.max_path_bytes or new_path_len >= std.Io.Dir.max_path_bytes) { + try pushErrno(vm, .NAMETOOLONG); return; - }; + } + + var old_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + var new_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + @memcpy(old_path_buf[0..old_path_len], old_path); + old_path_buf[old_path_len] = 0; + @memcpy(new_path_buf[0..new_path_len], new_path); + new_path_buf[new_path_len] = 0; + + const rc = std.os.linux.linkat( + old_host_fd, + @ptrCast(old_path_buf[0..old_path_len :0].ptr), + new_host_fd, + @ptrCast(new_path_buf[0..new_path_len :0].ptr), + 0, + ); + switch (std.posix.errno(rc)) { + .SUCCESS => {}, + else => |err| { + try pushErrno(vm, toWasiErrno(std.posix.unexpectedErrno(err))); + return; + }, + } } try pushErrno(vm, .SUCCESS); } @@ -2568,7 +2593,7 @@ pub fn registerAll(store: *Store, module: *const Module) !void { const testing = std.testing; -fn readTestFile(name: []const u8) ![]const u8 { +fn readTestFile(io: std.Io, name: []const u8) ![]const u8 { const paths = [_][]const u8{ "src/testdata/", "testdata/", @@ -2577,11 +2602,11 @@ fn readTestFile(name: []const u8) ![]const u8 { for (&paths) |prefix| { const path = try std.fmt.allocPrint(testing.allocator, "{s}{s}", .{ prefix, name }); defer testing.allocator.free(path); - const file = std.fs.cwd().openFile(path, .{}) catch continue; - defer file.close(); - const stat = try file.stat(); + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch continue; + defer file.close(io); + const stat = try file.stat(io); const data = try testing.allocator.alloc(u8, stat.size); - const n = try file.readAll(data); + const n = try file.readPositionalAll(io, data, 0); return data[0..n]; } return error.FileNotFound; @@ -2593,7 +2618,7 @@ test "WASI — fd_write via 07_wasi_hello.wasm" { const alloc = testing.allocator; // Load and decode module - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2610,7 +2635,7 @@ test "WASI — fd_write via 07_wasi_hello.wasm" { defer instance.deinit(); // Set up WASI context - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); wasi_ctx.caps = Capabilities.all; instance.wasi = &wasi_ctx; @@ -2618,29 +2643,25 @@ test "WASI — fd_write via 07_wasi_hello.wasm" { try instance.instantiate(); // Create pipe for capturing stdout - const pipe = try posix.pipe(); - defer posix.close(pipe[0]); - - // Redirect stdout to pipe write end - const saved_stdout = try posix.dup(@as(posix.fd_t, 1)); - defer posix.close(saved_stdout); - try posix.dup2(pipe[1], @as(posix.fd_t, 1)); - posix.close(pipe[1]); + var fds: [2]i32 = undefined; + switch (std.posix.errno(std.os.linux.pipe(&fds))) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + defer std.Io.Threaded.closeFd(fds[0]); + wasi_ctx.setStdioFd(1, fds[1], .borrow); // Run _start var vm_inst = Vm.init(alloc); var results: [0]u64 = .{}; vm_inst.invoke(&instance, "_start", &.{}, &results) catch |err| { - // proc_exit or normal completion if (err != error.Trap) return err; }; - - // Restore stdout - try posix.dup2(saved_stdout, @as(posix.fd_t, 1)); + std.Io.Threaded.closeFd(fds[1]); // Read captured output var buf: [256]u8 = undefined; - const n = try posix.read(pipe[0], &buf); + const n = try std.posix.read(fds[0], &buf); const output = buf[0..n]; try testing.expectEqualStrings("Hello, WASI!\n", output); @@ -2649,7 +2670,7 @@ test "WASI — fd_write via 07_wasi_hello.wasm" { test "WASI — args_sizes_get and args_get" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2663,7 +2684,7 @@ test "WASI — args_sizes_get and args_get" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); const test_args = [_][:0]const u8{ "prog", "arg1", "arg2" }; @@ -2700,7 +2721,7 @@ test "WASI — args_sizes_get and args_get" { test "WASI — environ_sizes_get with empty environ" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2714,7 +2735,7 @@ test "WASI — environ_sizes_get with empty environ" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); wasi_ctx.caps = Capabilities.all; instance.wasi = &wasi_ctx; @@ -2743,7 +2764,7 @@ test "WASI — environ_sizes_get with empty environ" { test "WASI — clock_time_get returns nonzero" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2757,7 +2778,7 @@ test "WASI — clock_time_get returns nonzero" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); wasi_ctx.caps.allow_clock = true; instance.wasi = &wasi_ctx; @@ -2785,7 +2806,7 @@ test "WASI — clock_time_get returns nonzero" { test "WASI — random_get fills buffer" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2799,7 +2820,7 @@ test "WASI — random_get fills buffer" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); wasi_ctx.caps = Capabilities.all; instance.wasi = &wasi_ctx; @@ -2827,7 +2848,10 @@ test "WASI — random_get fills buffer" { // Very unlikely all 16 bytes remain zero after random fill var all_zero = true; for (data[400..416]) |b| { - if (b != 0) { all_zero = false; break; } + if (b != 0) { + all_zero = false; + break; + } } try testing.expect(!all_zero); } @@ -2835,7 +2859,7 @@ test "WASI — random_get fills buffer" { test "WASI — deny-by-default capabilities" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2849,7 +2873,7 @@ test "WASI — deny-by-default capabilities" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); // Default capabilities: all denied instance.wasi = &wasi_ctx; @@ -2871,7 +2895,7 @@ test "WASI — deny-by-default capabilities" { test "WASI — path_open creates file and returns valid fd" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2885,7 +2909,7 @@ test "WASI — path_open creates file and returns valid fd" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); wasi_ctx.caps = Capabilities.all; instance.wasi = &wasi_ctx; @@ -2894,7 +2918,7 @@ test "WASI — path_open creates file and returns valid fd" { defer tmp.cleanup(); const host_path = try std.fmt.allocPrint(alloc, ".zig-cache/tmp/{s}", .{tmp.sub_path}); defer alloc.free(host_path); - try wasi_ctx.addPreopenPath(3, "/tmp", host_path); + try wasi_ctx.addPreopenPath(testing.io, 3, "/tmp", host_path); try instance.instantiate(); @@ -2909,7 +2933,7 @@ test "WASI — path_open creates file and returns valid fd" { @memcpy(data[100 .. 100 + test_path.len], test_path); // Clean up test file if it exists - tmp.dir.deleteFile(test_path) catch {}; + tmp.dir.deleteFile(testing.io, test_path) catch {}; // Push path_open args in signature order (stack: first pushed = bottom) // path_open(fd=3, dirflags=1, path_ptr=100, path_len, oflags=CREAT(1), @@ -2953,13 +2977,13 @@ test "WASI — path_open creates file and returns valid fd" { try testing.expectEqual(@as(u64, @intFromEnum(Errno.SUCCESS)), close_errno); // Clean up - tmp.dir.deleteFile(test_path) catch {}; + tmp.dir.deleteFile(testing.io, test_path) catch {}; } test "WASI — fd_readdir lists directory entries" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -2973,7 +2997,7 @@ test "WASI — fd_readdir lists directory entries" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); wasi_ctx.caps = Capabilities.all; instance.wasi = &wasi_ctx; @@ -2985,22 +3009,22 @@ test "WASI — fd_readdir lists directory entries" { // Create a temp directory with known contents const test_dir = "zwasm_test_readdir"; - tmp.dir.makeDir(test_dir) catch {}; - var dir_fd = try tmp.dir.openDir(test_dir, .{ .access_sub_paths = true }); - defer dir_fd.close(); + tmp.dir.createDir(testing.io, test_dir, .default_dir) catch {}; + var dir_fd = try tmp.dir.openDir(testing.io, test_dir, .{ .access_sub_paths = true }); + defer dir_fd.close(testing.io); // Create two files in the directory - const f1 = try dir_fd.createFile("afile.txt", .{ .read = true }); - f1.close(); - const f2 = try dir_fd.createFile("bfile.txt", .{ .read = true }); - f2.close(); + const f1 = try dir_fd.createFile(testing.io, "afile.txt", .{ .read = true }); + f1.close(testing.io); + const f2 = try dir_fd.createFile(testing.io, "bfile.txt", .{ .read = true }); + f2.close(testing.io); // Reopen dir fd for reading - const read_dir_fd = try tmp.dir.openDir(test_dir, .{ .access_sub_paths = true, .iterate = true }); - try wasi_ctx.addPreopenPath(3, "/tmp", host_path); + const read_dir_fd = try tmp.dir.openDir(testing.io, test_dir, .{ .access_sub_paths = true, .iterate = true }); + try wasi_ctx.addPreopenPath(testing.io, 3, "/tmp", host_path); // Put the dir fd in fd_table const wasi_dir_fd = try wasi_ctx.allocFd(.{ - .raw = read_dir_fd.fd, + .raw = read_dir_fd.handle, .kind = .dir, }, false); @@ -3034,15 +3058,15 @@ test "WASI — fd_readdir lists directory entries" { try testing.expect(d_namlen < 256); // Clean up - dir_fd.deleteFile("afile.txt") catch {}; - dir_fd.deleteFile("bfile.txt") catch {}; - tmp.dir.deleteDir(test_dir) catch {}; + dir_fd.deleteFile(testing.io, "afile.txt") catch {}; + dir_fd.deleteFile(testing.io, "bfile.txt") catch {}; + tmp.dir.deleteDir(testing.io, test_dir) catch {}; } test "WASI — registerAll for wasi_hello module" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -3059,7 +3083,7 @@ test "WASI — registerAll for wasi_hello module" { test "WASI — env.memory shared import" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("32_env_shared_memory.wasm"); + const wasm_bytes = try readTestFile(testing.io, "32_env_shared_memory.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -3082,7 +3106,7 @@ test "WASI — env.memory shared import" { test "injected env vars accessible without allow_env" { const alloc = testing.allocator; - const wasm_bytes = try readTestFile("07_wasi_hello.wasm"); + const wasm_bytes = try readTestFile(testing.io, "07_wasi_hello.wasm"); defer alloc.free(wasm_bytes); var module = Module.init(alloc, wasm_bytes); @@ -3096,7 +3120,7 @@ test "injected env vars accessible without allow_env" { var instance = instance_mod.Instance.init(alloc, &store_inst, &module); defer instance.deinit(); - var wasi_ctx = WasiContext.init(alloc); + var wasi_ctx = WasiContext.init(alloc, testing.io); defer wasi_ctx.deinit(); // allow_env = false (default), but inject a variable wasi_ctx.caps = Capabilities.cli_default; @@ -3135,21 +3159,21 @@ test "Capabilities.sandbox denies all" { test "stdio override: default returns process stdio" { const alloc = testing.allocator; - var ctx = WasiContext.init(alloc); + var ctx = WasiContext.init(alloc, testing.io); defer ctx.deinit(); // Without overrides, stdioFile returns process stdin/stdout/stderr const stdin_file = ctx.stdioFile(0); try testing.expect(stdin_file != null); - try testing.expectEqual(std.fs.File.stdin().handle, stdin_file.?.handle); + try testing.expectEqual(std.Io.File.stdin().handle, stdin_file.?.handle); const stdout_file = ctx.stdioFile(1); try testing.expect(stdout_file != null); - try testing.expectEqual(std.fs.File.stdout().handle, stdout_file.?.handle); + try testing.expectEqual(std.Io.File.stdout().handle, stdout_file.?.handle); const stderr_file = ctx.stdioFile(2); try testing.expect(stderr_file != null); - try testing.expectEqual(std.fs.File.stderr().handle, stderr_file.?.handle); + try testing.expectEqual(std.Io.File.stderr().handle, stderr_file.?.handle); // Non-stdio fd returns null try testing.expect(ctx.stdioFile(3) == null); @@ -3158,54 +3182,64 @@ test "stdio override: default returns process stdio" { test "stdio override: custom fd replaces default" { if (builtin.os.tag == .windows) return error.SkipZigTest; const alloc = testing.allocator; - var ctx = WasiContext.init(alloc); + var ctx = WasiContext.init(alloc, testing.io); defer ctx.deinit(); // Create a pipe to use as custom stdout - const pipe = try posix.pipe(); - defer posix.close(pipe[0]); + var fds: [2]i32 = undefined; + switch (std.posix.errno(std.os.linux.pipe(&fds))) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + defer std.Io.Threaded.closeFd(fds[0]); // Set stdout (fd 1) to write end of pipe, with ownership (runtime closes it) - ctx.setStdioFd(1, pipe[1], .own); + ctx.setStdioFd(1, fds[1], .own); const stdout_file = ctx.stdioFile(1); try testing.expect(stdout_file != null); - try testing.expectEqual(pipe[1], stdout_file.?.handle); + try testing.expectEqual(fds[1], stdout_file.?.handle); // stdin and stderr remain default - try testing.expectEqual(std.fs.File.stdin().handle, ctx.stdioFile(0).?.handle); - try testing.expectEqual(std.fs.File.stderr().handle, ctx.stdioFile(2).?.handle); + try testing.expectEqual(std.Io.File.stdin().handle, ctx.stdioFile(0).?.handle); + try testing.expectEqual(std.Io.File.stderr().handle, ctx.stdioFile(2).?.handle); } test "stdio override: borrow mode does not close fd on deinit" { if (builtin.os.tag == .windows) return error.SkipZigTest; const alloc = testing.allocator; - const pipe = try posix.pipe(); - defer posix.close(pipe[0]); - defer posix.close(pipe[1]); + var fds: [2]std.os.linux.fd_t = undefined; + switch (std.posix.errno(std.os.linux.pipe(&fds))) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + defer _ = std.os.linux.close(fds[0]); + defer _ = std.os.linux.close(fds[1]); { - var ctx = WasiContext.init(alloc); - ctx.setStdioFd(1, pipe[1], .borrow); + var ctx = WasiContext.init(alloc, testing.io); + ctx.setStdioFd(1, fds[1], .borrow); ctx.deinit(); } - // pipe[1] should still be valid (borrowed, not closed by deinit) - // Writing to it should succeed - const written = posix.write(pipe[1], "ok") catch 0; + const rc = std.os.linux.write(fds[1], "ok", 2); + const written: usize = switch (std.posix.errno(rc)) { + .SUCCESS => rc, + else => 0, + }; try testing.expectEqual(@as(usize, 2), written); } test "addPreopenFd: registers fd-based preopen" { if (builtin.os.tag == .windows) return error.SkipZigTest; const alloc = testing.allocator; - var ctx = WasiContext.init(alloc); + var ctx = WasiContext.init(alloc, testing.io); defer ctx.deinit(); // Open a real directory to get a valid fd - var dir = try std.fs.cwd().openDir(".", .{}); - const dir_fd = dir.fd; + var dir = try std.Io.Dir.cwd().openDir(testing.io, ".", .{}); + const dir_fd = dir.handle; // Transfer ownership to WasiContext (which will close it) dir = undefined; diff --git a/src/wat.zig b/src/wat.zig index b007ce17e..4041e7dea 100644 --- a/src/wat.zig +++ b/src/wat.zig @@ -234,9 +234,32 @@ pub const Tokenizer = struct { fn isIdChar(c: u8) bool { return switch (c) { - 'a'...'z', 'A'...'Z', '0'...'9', - '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '/', - ':', '<', '=', '>', '?', '@', '\\', '^', '_', '`', '|', '~', + 'a'...'z', + 'A'...'Z', + '0'...'9', + '!', + '#', + '$', + '%', + '&', + '\'', + '*', + '+', + '-', + '.', + '/', + ':', + '<', + '=', + '>', + '?', + '@', + '\\', + '^', + '_', + '`', + '|', + '~', => true, else => false, }; @@ -798,19 +821,7 @@ pub const Parser = struct { if (self.current.tag == .keyword) { const text = self.current.text; const result: ?RefTypeAnnotation = - if (std.mem.eql(u8, text, "anyref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_ANY }, .nullable = true } - else if (std.mem.eql(u8, text, "eqref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_EQ }, .nullable = true } - else if (std.mem.eql(u8, text, "funcref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_FUNC }, .nullable = true } - else if (std.mem.eql(u8, text, "externref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_EXTERN }, .nullable = true } - else if (std.mem.eql(u8, text, "structref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_STRUCT }, .nullable = true } - else if (std.mem.eql(u8, text, "arrayref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_ARRAY }, .nullable = true } - else if (std.mem.eql(u8, text, "i31ref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_I31 }, .nullable = true } - else if (std.mem.eql(u8, text, "nullref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NONE }, .nullable = true } - else if (std.mem.eql(u8, text, "nullfuncref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NOFUNC }, .nullable = true } - else if (std.mem.eql(u8, text, "nullexternref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NOEXTERN }, .nullable = true } - else if (std.mem.eql(u8, text, "nullexnref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NOEXN }, .nullable = true } - else if (std.mem.eql(u8, text, "exnref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_EXN }, .nullable = true } - else null; + if (std.mem.eql(u8, text, "anyref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_ANY }, .nullable = true } else if (std.mem.eql(u8, text, "eqref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_EQ }, .nullable = true } else if (std.mem.eql(u8, text, "funcref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_FUNC }, .nullable = true } else if (std.mem.eql(u8, text, "externref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_EXTERN }, .nullable = true } else if (std.mem.eql(u8, text, "structref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_STRUCT }, .nullable = true } else if (std.mem.eql(u8, text, "arrayref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_ARRAY }, .nullable = true } else if (std.mem.eql(u8, text, "i31ref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_I31 }, .nullable = true } else if (std.mem.eql(u8, text, "nullref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NONE }, .nullable = true } else if (std.mem.eql(u8, text, "nullfuncref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NOFUNC }, .nullable = true } else if (std.mem.eql(u8, text, "nullexternref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NOEXTERN }, .nullable = true } else if (std.mem.eql(u8, text, "nullexnref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_NOEXN }, .nullable = true } else if (std.mem.eql(u8, text, "exnref")) .{ .heap_type = .{ .num = opc.ValType.HEAP_EXN }, .nullable = true } else null; if (result) |r| { _ = self.advance(); return r; @@ -1612,8 +1623,7 @@ pub const Parser = struct { } } // Check for reftype keyword without paren (passive/declarative marker) - else if (self.current.tag == .keyword and isRefTypeKeyword(self.current.text)) - { + else if (self.current.tag == .keyword and isRefTypeKeyword(self.current.text)) { mode = .passive; } @@ -1632,8 +1642,7 @@ pub const Parser = struct { while (self.current.tag == .integer or self.current.tag == .ident) { func_indices.append(self.alloc, try self.parseIndex()) catch return error.OutOfMemory; } - } else if (self.current.tag == .keyword and isRefTypeKeyword(self.current.text)) - { + } else if (self.current.tag == .keyword and isRefTypeKeyword(self.current.text)) { is_expr_style = true; elem_reftype = try self.parseValType(); while (self.current.tag == .lparen) { @@ -1928,7 +1937,7 @@ pub const Parser = struct { fn isMemoryOp(name: []const u8) bool { const prefixes = [_][]const u8{ - "i32.load", "i64.load", "f32.load", "f64.load", + "i32.load", "i64.load", "f32.load", "f64.load", "i32.store", "i64.store", "f32.store", "f64.store", }; for (prefixes) |prefix| { @@ -1942,10 +1951,9 @@ pub const Parser = struct { const ops = [_][]const u8{ "i8x16.extract_lane_s", "i8x16.extract_lane_u", "i8x16.replace_lane", "i16x8.extract_lane_s", "i16x8.extract_lane_u", "i16x8.replace_lane", - "i32x4.extract_lane", "i32x4.replace_lane", - "i64x2.extract_lane", "i64x2.replace_lane", - "f32x4.extract_lane", "f32x4.replace_lane", - "f64x2.extract_lane", "f64x2.replace_lane", + "i32x4.extract_lane", "i32x4.replace_lane", "i64x2.extract_lane", + "i64x2.replace_lane", "f32x4.extract_lane", "f32x4.replace_lane", + "f64x2.extract_lane", "f64x2.replace_lane", }; for (ops) |op| { if (std.mem.eql(u8, name, op)) return true; @@ -1984,7 +1992,7 @@ pub const Parser = struct { _ = self.advance(); if (self.current.tag == .keyword and (std.mem.eql(u8, self.current.text, "param") or - std.mem.eql(u8, self.current.text, "result"))) + std.mem.eql(u8, self.current.text, "result"))) { // Skip tokens until matching ) var depth: u32 = 1; @@ -2143,7 +2151,7 @@ pub const Parser = struct { _ = self.advance(); if (self.current.tag == .keyword and (std.mem.eql(u8, self.current.text, "then") or - std.mem.eql(u8, self.current.text, "else"))) + std.mem.eql(u8, self.current.text, "else"))) { self.tok.pos = saved_pos; self.current = saved_current; @@ -2538,7 +2546,7 @@ pub const Parser = struct { while (self.current.tag != .eof) { if (self.current.tag == .keyword and (std.mem.eql(u8, self.current.text, "end") or - std.mem.eql(u8, self.current.text, "else"))) + std.mem.eql(u8, self.current.text, "else"))) { if (std.mem.eql(u8, self.current.text, "end")) { _ = self.advance(); @@ -2558,11 +2566,26 @@ pub const Parser = struct { fn tryParseValType(self: *Parser) ?WatValType { if (self.current.tag != .keyword) return null; - if (std.mem.eql(u8, self.current.text, "i32")) { _ = self.advance(); return .i32; } - if (std.mem.eql(u8, self.current.text, "i64")) { _ = self.advance(); return .i64; } - if (std.mem.eql(u8, self.current.text, "f32")) { _ = self.advance(); return .f32; } - if (std.mem.eql(u8, self.current.text, "f64")) { _ = self.advance(); return .f64; } - if (std.mem.eql(u8, self.current.text, "v128")) { _ = self.advance(); return .v128; } + if (std.mem.eql(u8, self.current.text, "i32")) { + _ = self.advance(); + return .i32; + } + if (std.mem.eql(u8, self.current.text, "i64")) { + _ = self.advance(); + return .i64; + } + if (std.mem.eql(u8, self.current.text, "f32")) { + _ = self.advance(); + return .f32; + } + if (std.mem.eql(u8, self.current.text, "f64")) { + _ = self.advance(); + return .f64; + } + if (std.mem.eql(u8, self.current.text, "v128")) { + _ = self.advance(); + return .v128; + } return null; } @@ -5138,7 +5161,7 @@ test "WAT round-trip — v128.const SIMD" { defer testing.allocator.free(wasm); // Verify it loads and runs const types = @import("types.zig"); - var module = try types.WasmModule.load(testing.allocator, wasm); + var module = try types.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{ 0, 0 }; @@ -5155,7 +5178,7 @@ test "WAT parser — memory.size default index" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5174,7 +5197,7 @@ test "WAT parser — memory.grow default index" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5195,7 +5218,7 @@ test "WAT parser — data section round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5215,7 +5238,7 @@ test "WAT parser — data section with escape sequences" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5235,7 +5258,7 @@ test "WAT parser — data section with offset keyword" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5258,7 +5281,7 @@ test "WAT parser — elem section round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args0 = [_]u64{0}; var results = [_]u64{0}; @@ -5293,7 +5316,7 @@ test "WAT parser — i32.trunc_sat_f64_s round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); // 1e30 should clamp to i32.max = 2147483647 var args = [_]u64{@bitCast(@as(f64, 1e30))}; @@ -5317,7 +5340,7 @@ test "WAT parser — memory.fill round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5341,7 +5364,7 @@ test "WAT parser — memory.copy round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5367,7 +5390,7 @@ test "WAT parser — table.fill round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5389,7 +5412,7 @@ test "WAT parser — try_table encode round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5408,7 +5431,7 @@ test "WAT parser — i32x4.extract_lane round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5430,7 +5453,7 @@ test "WAT parser — i32.atomic.load round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5532,7 +5555,7 @@ test "WAT encoder — tag round-trip" { defer testing.allocator.free(wasm); // Should load as valid module const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.tags.items.len); } @@ -5553,7 +5576,7 @@ test "WAT encoder — tag with try_table catch" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5586,7 +5609,7 @@ test "WAT encoder — export tag" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.tags.items.len); } @@ -5638,7 +5661,7 @@ test "WAT encoder — ref.null heap type" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); var args = [_]u64{}; var results = [_]u64{0}; @@ -5654,7 +5677,7 @@ test "WAT encoder — anyref param round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); } @@ -5718,7 +5741,7 @@ test "WAT encoder — struct type round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); // Verify the module loaded successfully with struct type try testing.expectEqual(@as(usize, 1), module.module.types.items.len); @@ -5733,7 +5756,7 @@ test "WAT encoder — array type round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.types.items.len); } @@ -5750,7 +5773,7 @@ test "WAT encoder — i31.get_s round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); // Function loaded successfully with i31.get_s instruction try testing.expectEqual(@as(usize, 1), module.module.functions.items.len); @@ -5770,7 +5793,7 @@ test "WAT encoder — struct.new round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.functions.items.len); } @@ -5787,7 +5810,7 @@ test "WAT encoder — rec group round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 2), module.module.types.items.len); } @@ -5805,7 +5828,7 @@ test "WAT encoder — struct.get round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.functions.items.len); } @@ -5823,7 +5846,7 @@ test "WAT encoder — ref.test round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.functions.items.len); } @@ -5843,7 +5866,7 @@ test "WAT encoder — array.new_fixed round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.functions.items.len); } @@ -5938,7 +5961,7 @@ test "WAT encoder — ref.test with parenthesized reftype" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 1), module.module.functions.items.len); } @@ -5954,7 +5977,7 @@ test "WAT encoder — packed storage types i8/i16" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); try testing.expectEqual(@as(usize, 3), module.module.types.items.len); } @@ -5971,7 +5994,7 @@ test "WAT encoder — sub type round-trip" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); // 4 types loaded successfully try testing.expectEqual(@as(usize, 4), module.module.types.items.len); @@ -6004,7 +6027,7 @@ test "WAT encoder — throw catch named tag" { ); defer testing.allocator.free(wasm); const types_mod = @import("types.zig"); - var module = try types_mod.WasmModule.load(testing.allocator, wasm); + var module = try types_mod.WasmModule.load(testing.allocator, testing.io, wasm); defer module.deinit(); // Input 0: throw path → catch → return 23 var args0 = [_]u64{0}; diff --git a/test/e2e/e2e_runner.zig b/test/e2e/e2e_runner.zig index 248ce0f27..cff9d6e01 100644 --- a/test/e2e/e2e_runner.zig +++ b/test/e2e/e2e_runner.zig @@ -54,7 +54,7 @@ const JsonCommand = struct { module_type: ?[]const u8 = null, action: ?JsonAction = null, expected: ?[]const JsonValue = null, - @"as": ?[]const u8 = null, + as: ?[]const u8 = null, // Thread support: sub-commands within a thread block commands: ?[]const JsonCommand = null, shared_module: ?[]const u8 = null, @@ -284,7 +284,7 @@ const TestRunner = struct { } fn handleRegister(self: *TestRunner, cmd: *const JsonCommand) void { - const as_name = cmd.@"as" orelse return; + const as_name = cmd.as orelse return; const source = if (cmd.name) |name| self.named_modules.get(name) @@ -504,7 +504,7 @@ const TestRunner = struct { fn loadWasmFile(self: *TestRunner, filename: []const u8) ?[]const u8 { const path = std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.dir, filename }) catch return null; defer self.allocator.free(path); - const file = std.fs.cwd().openFile(path, .{}) catch return null; + const file = std.Io.Dir.cwd().openFile(".", io, path) catch return null; defer file.close(); const stat = file.stat() catch return null; const bytes = self.allocator.alloc(u8, stat.size) catch return null; @@ -570,7 +570,7 @@ const TestRunner = struct { const old_failed = self.failed; const old_skipped = self.skipped; - const file = try std.fs.cwd().openFile(json_path, .{}); + const file = try std.Io.Dir.cwd().openFile(json_path, .{}); defer file.close(); const stat = try file.stat(); const content = try self.allocator.alloc(u8, stat.size); @@ -757,7 +757,7 @@ pub fn main() !void { } if (dir) |d| { - var json_dir = std.fs.cwd().openDir(d, .{ .iterate = true }) catch { + var json_dir = std.Io.Dir.cwd().openDir(d, .{ .iterate = true }) catch { try stdout.print("ERROR: Cannot open directory: {s}\n", .{d}); try stdout.flush(); std.process.exit(1);