From 16d59aa63d6dfb195753a98ce009855eff49932b Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 31 Jul 2025 10:37:04 +1000 Subject: [PATCH 1/6] update stable array dep to zig-0.14.0 branch --- build.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig.zon b/build.zig.zon index 831f577..6a4090d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,7 +4,7 @@ .fingerprint = 0x5a2a0eadb1367749, .dependencies = .{ .@"zig-stable-array" = .{ - .url = "https://github.com/rdunnington/zig-stable-array/archive/9cdccbb42f56305eec9c8090f7123c99bc0e6793.tar.gz", + .url = "git+https://github.com/rdunnington/zig-stable-array.git?branch=zig-0.14.0#9cdccbb42f56305eec9c8090f7123c99bc0e6793", .hash = "stable_array-0.1.0-3ihgvfZaAADfEtMl2cUuB-Owkfa_F6fWHaaJltcXM2T6", }, }, From 694b53b748dba8078751a8836f1b3cead792dcca Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 31 Jul 2025 10:54:53 +1000 Subject: [PATCH 2/6] use patched zig-stable-array dependency --- build.zig | 2 +- build.zig.zon | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 1f7f769..4b179f1 100644 --- a/build.zig +++ b/build.zig @@ -53,7 +53,7 @@ pub fn build(b: *Build) void { options.addOption(bool, "enable_debug_trap", enable_debug_trap); options.addOption(StackVmKind, "vm_kind", vm_kind); - const stable_array = b.dependency("zig-stable-array", .{ + const stable_array = b.dependency("stable_array", .{ .target = target, .optimize = optimize, }); diff --git a/build.zig.zon b/build.zig.zon index 6a4090d..f2120c5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,9 +3,9 @@ .version = "0.0.1", .fingerprint = 0x5a2a0eadb1367749, .dependencies = .{ - .@"zig-stable-array" = .{ - .url = "git+https://github.com/rdunnington/zig-stable-array.git?branch=zig-0.14.0#9cdccbb42f56305eec9c8090f7123c99bc0e6793", - .hash = "stable_array-0.1.0-3ihgvfZaAADfEtMl2cUuB-Owkfa_F6fWHaaJltcXM2T6", + .stable_array = .{ + .url = "git+https://github.com/lukewilliamboswell/zig-stable-array.git#edd1aa9d540144e45622c664f8d1232cee3b5580", + .hash = "stable_array-0.1.0-3ihgvUVbAADlJAqXJOXZ2xEIHUY5-32i8d6fk7hraKju", }, }, .minimum_zig_version = "0.13.0", From 8bcb0f5c80a2fe677f9e3e2243f247d79b429183 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Tue, 14 Apr 2026 14:54:34 +1000 Subject: [PATCH 3/6] Ignore the new zig-pkg/ directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 37f9432..063f210 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .zig-cache zig-out +zig-pkg test/wasm/wasm-generated *.wasm *.wasm.o From 69adbd45745263deb9827e6bde3d4aca1e87abdc Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Tue, 14 Apr 2026 17:51:10 +1000 Subject: [PATCH 4/6] Upgrade to Zig 0.16.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate the entire codebase to Zig 0.16.0-dev.3153, adapting to the major standard library reorganization. All tests pass (unit, wasm, mem64, cffi, wasi). Key changes: - New std.Io system: std.fs.* → std.Io.*, fixedBufferStream → Reader.fixed/Writer.fixed - Reader API: readByte→takeByte, readInt→takeInt, std.leb→reader.takeLeb128 - Main signature: main() → main(process_init: std.process.Init) - Allocator: GeneralPurposeAllocator → DebugAllocator / process_init.gpa - Build system: addCSourceFile/linkLibC/addIncludePath moved to root_module - WASI: posix_compat wrappers for removed std.posix.* functions using std.c.* - Vector ops: convert to array before runtime indexing (0.16 requirement) - Fix pre-existing use-after-pop bug in ModuleValidator.popControl - LEB128: enforce wasm spec byte count limits with new takeLeb128 API Co-Authored-By: Claude Opus 4.6 (1M context) --- build.zig | 10 +- build.zig.zon | 4 +- run/main.zig | 77 ++++++----- src/cffi.zig | 2 +- src/common.zig | 73 ++++------ src/definition.zig | 205 +++++++++++++-------------- src/stack_ops.zig | 89 +++++++----- src/tests.zig | 6 +- src/vm_stack.zig | 7 +- src/wasi.zig | 259 ++++++++++++++++++++++++++++------- test/mem64/main.zig | 7 +- test/wasi/bytebox_adapter.py | 3 +- test/wasm/main.zig | 58 ++++---- 13 files changed, 484 insertions(+), 316 deletions(-) diff --git a/build.zig b/build.zig index c6b2889..5ac8088 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,4 @@ const std = @import("std"); -const CrossTarget = std.zig.CrossTarget; - const Build = std.Build; const Module = Build.Module; const Import = Module.Import; @@ -185,15 +183,15 @@ pub fn build(b: *Build) void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .use_llvm = use_llvm, }); - cffi_test.addCSourceFile(.{ + cffi_test.root_module.addCSourceFile(.{ .file = b.path("test/cffi/main.c"), }); - cffi_test.addIncludePath(b.path("src/bytebox.h")); - cffi_test.linkLibC(); - cffi_test.linkLibrary(lib_bytebox); + cffi_test.root_module.addIncludePath(b.path("src/bytebox.h")); + cffi_test.root_module.linkLibrary(lib_bytebox); const ffi_guest: WasmBuild = buildWasmExe(b, "test/cffi/module.zig", .wasm32); diff --git a/build.zig.zon b/build.zig.zon index f2120c5..8a7c27e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,11 +4,11 @@ .fingerprint = 0x5a2a0eadb1367749, .dependencies = .{ .stable_array = .{ - .url = "git+https://github.com/lukewilliamboswell/zig-stable-array.git#edd1aa9d540144e45622c664f8d1232cee3b5580", + .url = "git+https://github.com/lukewilliamboswell/zig-stable-array.git#98e6224ff7db9853cbd284ac199affd220617007", .hash = "stable_array-0.1.0-3ihgvUVbAADlJAqXJOXZ2xEIHUY5-32i8d6fk7hraKju", }, }, - .minimum_zig_version = "0.13.0", + .minimum_zig_version = "0.16.0", .paths = .{ "src", "test/mem64", diff --git a/run/main.zig b/run/main.zig index a59239c..a545b55 100644 --- a/run/main.zig +++ b/run/main.zig @@ -184,12 +184,17 @@ fn printHelp(args: []const []const u8) void { log.info(usage_string, .{args[0]}); } -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - var allocator: std.mem.Allocator = gpa.allocator(); - - const args: []const [:0]u8 = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); +pub fn main(process_init: std.process.Init) !void { + const allocator: std.mem.Allocator = process_init.gpa; + + var args_list = std.ArrayList([:0]const u8).empty; + defer args_list.deinit(allocator); + var args_iter = std.process.Args.Iterator.init(process_init.minimal.args); + defer args_iter.deinit(); + while (args_iter.next()) |arg| { + try args_list.append(allocator, arg); + } + const args: []const [:0]const u8 = args_list.items; var env_buffer = std.array_list.Managed([]const u8).init(allocator); defer env_buffer.deinit(); @@ -227,8 +232,7 @@ pub fn main() !void { std.debug.assert(opts.filename != null); - var cwd = std.fs.cwd(); - const wasm_data: []u8 = cwd.readFileAlloc(allocator, opts.filename.?, 1024 * 1024 * 128) catch |e| { + const wasm_data: []u8 = std.Io.Dir.cwd().readFileAlloc(process_init.io, opts.filename.?, allocator, .limited(1024 * 1024 * 128)) catch |e| { std.log.err("Failed to read file '{s}' into memory: {}", .{ opts.filename.?, e }); return RunErrors.IoError; }; @@ -247,10 +251,10 @@ pub fn main() !void { }; if (opts.print_dump) { - var strbuf = std.array_list.Managed(u8).init(allocator); - try strbuf.ensureTotalCapacity(1024 * 16); - try module_def.dump(strbuf.writer()); - log.info("{s}", .{strbuf.items}); + var aw = try std.Io.Writer.Allocating.initCapacity(allocator, 1024 * 16); + defer aw.deinit(); + try module_def.dump(&aw.writer); + log.info("{s}", .{aw.writer.buffered()}); return; } @@ -301,14 +305,14 @@ pub fn main() !void { const num_params: usize = invoke_args.len; if (func_export.params.len != num_params) { - var strbuf = std.array_list.Managed(u8).init(allocator); - defer strbuf.deinit(); - try writeSignature(&strbuf, &func_export); + var aw = std.Io.Writer.Allocating.init(allocator); + defer aw.deinit(); + try writeSignature(&aw.writer, &func_export); std.log.err("Specified {} params but expected {}. The signature of '{s}' is:\n{s}", .{ num_params, func_export.params.len, invoke_funcname, - strbuf.items, + aw.writer.buffered(), }); return RunErrors.FunctionParamMismatch; } @@ -375,51 +379,50 @@ pub fn main() !void { }; { - var strbuf = std.array_list.Managed(u8).init(allocator); - defer strbuf.deinit(); - const writer = strbuf.writer(); + var aw = std.Io.Writer.Allocating.init(allocator); + defer aw.deinit(); + const writer = &aw.writer; if (returns.items.len > 0) { const return_types = func_export.returns; - try std.fmt.format(writer, "return:\n", .{}); + try writer.print("return:\n", .{}); for (returns.items, 0..) |_, i| { switch (return_types[i]) { - .I32 => try std.fmt.format(writer, " {} (i32)\n", .{returns.items[i].I32}), - .I64 => try std.fmt.format(writer, " {} (i64)\n", .{returns.items[i].I64}), - .F32 => try std.fmt.format(writer, " {} (f32)\n", .{returns.items[i].F32}), - .F64 => try std.fmt.format(writer, " {} (f64)\n", .{returns.items[i].F64}), + .I32 => try writer.print(" {} (i32)\n", .{returns.items[i].I32}), + .I64 => try writer.print(" {} (i64)\n", .{returns.items[i].I64}), + .F32 => try writer.print(" {} (f32)\n", .{returns.items[i].F32}), + .F64 => try writer.print(" {} (f64)\n", .{returns.items[i].F64}), .V128 => unreachable, // TODO support - .FuncRef => try std.fmt.format(writer, " (funcref)\n", .{}), - .ExternRef => try std.fmt.format(writer, " (externref)\n", .{}), + .FuncRef => try writer.print(" (funcref)\n", .{}), + .ExternRef => try writer.print(" (externref)\n", .{}), } } - try std.fmt.format(writer, "\n", .{}); + try writer.print("\n", .{}); } - if (strbuf.items.len > 0) { - log.info("{s}\n", .{strbuf.items}); + if (aw.writer.end > 0) { + log.info("{s}\n", .{aw.writer.buffered()}); } } } -fn writeSignature(strbuf: *std.array_list.Managed(u8), info: *const bytebox.FunctionExport) !void { - const writer = strbuf.writer(); +fn writeSignature(writer: *std.Io.Writer, info: *const bytebox.FunctionExport) !void { if (info.params.len == 0) { - try std.fmt.format(writer, " params: none\n", .{}); + try writer.print(" params: none\n", .{}); } else { - try std.fmt.format(writer, " params:\n", .{}); + try writer.print(" params:\n", .{}); for (info.params) |valtype| { const name: []const u8 = valtypeToString(valtype); - try std.fmt.format(writer, " {s}\n", .{name}); + try writer.print(" {s}\n", .{name}); } } if (info.returns.len == 0) { - try std.fmt.format(writer, " returns: none\n", .{}); + try writer.print(" returns: none\n", .{}); } else { - try std.fmt.format(writer, " returns:\n", .{}); + try writer.print(" returns:\n", .{}); for (info.returns) |valtype| { const name: []const u8 = valtypeToString(valtype); - try std.fmt.format(writer, " {s}\n", .{name}); + try writer.print(" {s}\n", .{name}); } } } diff --git a/src/cffi.zig b/src/cffi.zig index 562ce08..fb891d8 100644 --- a/src/cffi.zig +++ b/src/cffi.zig @@ -101,7 +101,7 @@ const CGlobalExport = extern struct { const INVALID_FUNC_INDEX = std.math.maxInt(u32); -var cffi_gpa = std.heap.GeneralPurposeAllocator(.{}){}; +var cffi_gpa = std.heap.DebugAllocator(.{}){}; // const CAllocator = struct { // const AllocError = std.mem.Allocator.Error; diff --git a/src/common.zig b/src/common.zig index 7f46d37..7137035 100644 --- a/src/common.zig +++ b/src/common.zig @@ -26,23 +26,8 @@ pub const Logger = struct { }; } - fn defaultLog(level: LogLevel, text: [:0]const u8) void { - var fd = switch (level) { - .Info => std.fs.File.stdout(), - .Error => std.fs.File.stderr(), - }; - - var buffer: [1024]u8 = undefined; - var writer = fd.writer(&buffer); - const w: *std.io.Writer = &writer.interface; - - nosuspend w.writeAll(text) catch |e| { - std.debug.print("Failed logging due to error: {}\n", .{e}); - }; - - nosuspend w.flush() catch |e| { - std.debug.print("Failed flushing log due to error: {}\n", .{e}); - }; + fn defaultLog(_: LogLevel, text: [:0]const u8) void { + std.debug.print("{s}", .{text}); } pub fn info(self: Logger, comptime format: []const u8, args: anytype) void { @@ -79,7 +64,15 @@ pub const ScratchAllocator = struct { } pub fn allocator(self: *ScratchAllocator) std.mem.Allocator { - return std.mem.Allocator.init(self, alloc, resize, free); + return .{ + .ptr = self, + .vtable = &.{ + .alloc = @ptrCast(&alloc), + .resize = @ptrCast(&resize), + .remap = std.mem.Allocator.noRemap, + .free = @ptrCast(&free), + }, + }; } pub fn reset(self: *ScratchAllocator) void { @@ -89,50 +82,34 @@ pub const ScratchAllocator = struct { fn alloc( self: *ScratchAllocator, len: usize, - ptr_align: u29, - len_align: u29, - ret_addr: usize, - ) std.mem.Allocator.Error![]u8 { - _ = ret_addr; - _ = len_align; - + alignment: std.mem.Alignment, + _: usize, + ) ?[*]u8 { const alloc_size = len; - const offset_begin = std.mem.alignForward(self.buffer.items.len, ptr_align); + const offset_begin = std.mem.alignForward(usize, self.buffer.items.len, alignment.toByteUnits()); const offset_end = offset_begin + alloc_size; self.buffer.resize(offset_end) catch { - return std.mem.Allocator.Error.OutOfMemory; + return null; }; - return self.buffer.items[offset_begin..offset_end]; + return self.buffer.items[offset_begin..offset_end].ptr; } fn resize( - self: *ScratchAllocator, + _: *ScratchAllocator, old_mem: []u8, - old_align: u29, + _: std.mem.Alignment, new_size: usize, - len_align: u29, - ret_addr: usize, - ) ?usize { - _ = self; - _ = old_align; - _ = ret_addr; - - if (new_size > old_mem.len) { - return null; - } - const aligned_size: usize = if (len_align == 0) new_size else std.mem.alignForward(new_size, len_align); - return aligned_size; + _: usize, + ) bool { + return new_size <= old_mem.len; } fn free( self: *ScratchAllocator, - old_mem: []u8, - old_align: u29, - ret_addr: usize, + _: []u8, + _: std.mem.Alignment, + _: usize, ) void { _ = self; - _ = old_mem; - _ = old_align; - _ = ret_addr; } }; diff --git a/src/definition.zig b/src/definition.zig index b271c0b..78c4ad3 100644 --- a/src/definition.zig +++ b/src/definition.zig @@ -94,46 +94,52 @@ const Section = enum(u8) { Custom, FunctionType, Import, Function, Table, Memory const k_function_type_sentinel_byte: u8 = 0x60; const k_block_type_void_sentinel_byte: u8 = 0x40; +fn intToEnum(comptime E: type, value: @typeInfo(E).@"enum".tag_type) ?E { + const fields = @typeInfo(E).@"enum".fields; + inline for (fields) |field| { + if (field.value == value) return @enumFromInt(value); + } + return null; +} + fn eosError(e: anyerror) MalformedError { - if (e == error.EndOfStream) { - return MalformedError.MalformedUnexpectedEnd; - } else if (e == error.EndOfBuffer) { + if (e == error.EndOfStream or e == error.EndOfBuffer or e == error.ReadFailed) { return MalformedError.MalformedUnexpectedEnd; } else { unreachable; } } -fn readByte(reader: anytype) MalformedError!u8 { - return reader.readByte() catch |e| return eosError(e); +const Reader = std.Io.Reader; + +fn readByte(reader: *Reader) MalformedError!u8 { + return reader.takeByte() catch |e| return eosError(e); } -fn readBytes(reader: anytype, bytes: []u8) MalformedError!usize { - return reader.read(bytes) catch |e| return eosError(e); +fn readBytes(reader: *Reader, bytes: []u8) MalformedError!usize { + return reader.readSliceShort(bytes) catch |e| return eosError(e); } -fn decodeLEB128(comptime T: type, reader: anytype) MalformedError!T { - if (@typeInfo(T).int.signedness == .signed) { - return std.leb.readIleb128(T, reader) catch |e| { - if (e == error.Overflow) { - return error.MalformedLEB128; - } else { - return eosError(e); - } - }; - } else { - return std.leb.readUleb128(T, reader) catch |e| { - if (e == error.Overflow) { - return error.MalformedLEB128; - } else { - return eosError(e); - } - }; +fn decodeLEB128(comptime T: type, reader: *Reader) MalformedError!T { + const info = @typeInfo(T).int; + const max_bytes = (info.bits + 6) / 7; // ceil(bits/7) + const pos_before = reader.seek; + const result = reader.takeLeb128(T) catch |e| { + if (e == error.Overflow) { + return error.MalformedLEB128; + } else { + return eosError(e); + } + }; + const bytes_consumed = reader.seek - pos_before; + if (bytes_consumed > max_bytes) { + return error.MalformedLEB128; } + return result; } -fn decodeWasmOpcode(reader: anytype) MalformedError!WasmOpcode { - const byte = try readByte(&reader); +fn decodeWasmOpcode(reader: *Reader) MalformedError!WasmOpcode { + const byte = try readByte(reader); var wasm_op: WasmOpcode = undefined; if (byte == 0xFC or byte == 0xFD) { const type_opcode = try decodeLEB128(u32, reader); @@ -145,28 +151,28 @@ fn decodeWasmOpcode(reader: anytype) MalformedError!WasmOpcode { extended = extended << 8; extended |= byte2; - wasm_op = std.meta.intToEnum(WasmOpcode, extended) catch { + wasm_op = intToEnum(WasmOpcode, extended) orelse { return error.MalformedIllegalOpcode; }; } else { - wasm_op = std.meta.intToEnum(WasmOpcode, byte) catch { + wasm_op = intToEnum(WasmOpcode, byte) orelse { return error.MalformedIllegalOpcode; }; } return wasm_op; } -fn decodeFloat(comptime T: type, reader: anytype) MalformedError!T { +fn decodeFloat(comptime T: type, reader: *Reader) MalformedError!T { return switch (T) { - f32 => @as(f32, @bitCast(reader.readInt(u32, .little) catch |e| return eosError(e))), - f64 => @as(f64, @bitCast(reader.readInt(u64, .little) catch |e| return eosError(e))), + f32 => @as(f32, @bitCast(reader.takeInt(u32, .little) catch |e| return eosError(e))), + f64 => @as(f64, @bitCast(reader.takeInt(u64, .little) catch |e| return eosError(e))), else => unreachable, }; } -fn decodeVec(reader: anytype) MalformedError!v128 { +fn decodeVec(reader: *Reader) MalformedError!v128 { var bytes: [16]u8 = undefined; - _ = reader.read(&bytes) catch |e| eosError(e); + _ = reader.readSliceShort(&bytes) catch |e| return eosError(e); return std.mem.bytesToValue(v128, &bytes); } @@ -194,11 +200,11 @@ pub const ValType = enum(c_int) { }; } - fn decode(reader: anytype) MalformedError!ValType { - return try bytecodeToValtype(try readByte(&reader)); + fn decode(reader: *Reader) MalformedError!ValType { + return try bytecodeToValtype(try readByte(reader)); } - fn decodeReftype(reader: anytype) MalformedError!ValType { + fn decodeReftype(reader: *Reader) MalformedError!ValType { const valtype = try decode(reader); if (isRefType(valtype) == false) { return error.MalformedReferenceType; @@ -374,8 +380,8 @@ pub const Limits = struct { pub const k_max_bytes_i64 = (1024 * 1024 * 1024 * 128); pub const k_max_pages_i64 = k_max_bytes_i64 / MemoryDefinition.k_page_size; - fn decode(reader: anytype) !Limits { - const limit_type: u8 = try readByte(&reader); + fn decode(reader: *Reader) !Limits { + const limit_type: u8 = try readByte(reader); if (limit_type > 7) { return error.MalformedLimits; @@ -480,7 +486,7 @@ pub const ConstantExpression = union(ConstantExpressionType) { Immutable, }; - fn decode(reader: anytype, module_def: *const ModuleDefinition, comptime expected_global_mut: ExpectedGlobalMut, expected_valtype: ValType) !ConstantExpression { + fn decode(reader: *Reader, module_def: *const ModuleDefinition, comptime expected_global_mut: ExpectedGlobalMut, expected_valtype: ValType) !ConstantExpression { const opcode = try decodeWasmOpcode(reader); const expr = switch (opcode) { @@ -534,7 +540,7 @@ pub const ConstantExpression = union(ConstantExpressionType) { } } - const end = @as(WasmOpcode, @enumFromInt(try readByte(&reader))); + const end = @as(WasmOpcode, @enumFromInt(try readByte(reader))); if (end != .End) { return error.ValidationBadConstantExpression; } @@ -654,9 +660,9 @@ pub const GlobalMut = enum(u8) { Immutable = 0, Mutable = 1, - fn decode(reader: anytype) !GlobalMut { - const byte = try readByte(&reader); - const value = std.meta.intToEnum(GlobalMut, byte) catch { + fn decode(reader: *Reader) !GlobalMut { + const byte = try readByte(reader); + const value = intToEnum(GlobalMut, byte) orelse { return error.MalformedMutability; }; return value; @@ -712,7 +718,7 @@ pub const DataDefinition = struct { offset: ?ConstantExpression, mode: DataMode, - fn decode(reader: anytype, module_def: *const ModuleDefinition, allocator: std.mem.Allocator) DecodeError!DataDefinition { + fn decode(reader: *Reader, module_def: *const ModuleDefinition, allocator: std.mem.Allocator) DecodeError!DataDefinition { const data_type: u32 = try decodeLEB128(u32, reader); if (data_type > 2) { return error.MalformedDataType; @@ -792,7 +798,7 @@ const MemArg = struct { alignment: u32, offset: u64, - fn decode(reader: anytype, comptime bitwidth: u32) !MemArg { + fn decode(reader: *Reader, comptime bitwidth: u32) !MemArg { std.debug.assert(bitwidth % 8 == 0); const memarg = MemArg{ .alignment = try decodeLEB128(u32, reader), @@ -892,10 +898,10 @@ pub const Instruction = struct { } } - fn decode(reader: anytype, module: *ModuleDefinition, func: *FunctionDefinition) !DecodedInstruction { + fn decode(reader: *Reader, module: *ModuleDefinition, func: *FunctionDefinition) !DecodedInstruction { const Helpers = struct { fn decodeBlockType( - _reader: anytype, + _reader: *Reader, _module: *ModuleDefinition, out_immediates: *InstructionImmediates, out_validation_immediates: *ValidationImmediates, @@ -903,14 +909,14 @@ pub const Instruction = struct { var block_type: BlockType = undefined; var block_value: BlockTypeValue = undefined; - const blocktype_raw = try readByte(&_reader); + const blocktype_raw = try readByte(_reader); const valtype_or_err = ValType.bytecodeToValtype(blocktype_raw); if (std.meta.isError(valtype_or_err)) { if (blocktype_raw == k_block_type_void_sentinel_byte) { block_type = .Void; block_value = BlockTypeValue{ .TypeIndex = 0 }; } else { - _reader.context.pos -= 1; // move the stream backwards 1 byte to reconstruct the integer + _reader.seek -= 1; // move the stream backwards 1 byte to reconstruct the integer const index_33bit = try decodeLEB128(i33, _reader); if (index_33bit < 0) { return error.MalformedBytecode; @@ -945,7 +951,7 @@ pub const Instruction = struct { }; } - fn decodeTablePair(_reader: anytype) !InstructionImmediates { + fn decodeTablePair(_reader: *Reader) !InstructionImmediates { const elem_index = try decodeLEB128(u32, _reader); const table_index = try decodeLEB128(u32, _reader); @@ -957,9 +963,9 @@ pub const Instruction = struct { }; } - fn decodeMemoryOffsetAndLane(_reader: anytype, comptime bitwidth: u32, _module: *ModuleDefinition) DecodeError!InstructionImmediates { + fn decodeMemoryOffsetAndLane(_reader: *Reader, comptime bitwidth: u32, _module: *ModuleDefinition) DecodeError!InstructionImmediates { const memarg = try MemArg.decode(_reader, bitwidth); - const laneidx = try readByte(&_reader); + const laneidx = try readByte(_reader); const immediates = MemoryOffsetAndLaneImmediates{ .offset = memarg.offset, .laneidx = laneidx, @@ -1214,13 +1220,13 @@ pub const Instruction = struct { immediate = InstructionImmediates{ .MemoryOffset = memarg.offset }; }, .Memory_Size => { - const reserved = try readByte(&reader); + const reserved = try readByte(reader); if (reserved != 0x00) { return error.MalformedMissingZeroByte; } }, .Memory_Grow => { - const reserved = try readByte(&reader); + const reserved = try readByte(reader); if (reserved != 0x00) { return error.MalformedMissingZeroByte; } @@ -1234,7 +1240,7 @@ pub const Instruction = struct { immediate = InstructionImmediates{ .Index = try decodeLEB128(u32, reader) }; // dataidx - const reserved = try readByte(&reader); + const reserved = try readByte(reader); if (reserved != 0x00) { return error.MalformedMissingZeroByte; } @@ -1254,17 +1260,17 @@ pub const Instruction = struct { immediate = InstructionImmediates{ .Index = try decodeLEB128(u32, reader) }; // dataidx }, .Memory_Copy => { - var reserved = try readByte(&reader); + var reserved = try readByte(reader); if (reserved != 0x00) { return error.MalformedMissingZeroByte; } - reserved = try readByte(&reader); + reserved = try readByte(reader); if (reserved != 0x00) { return error.MalformedMissingZeroByte; } }, .Memory_Fill => { - const reserved = try readByte(&reader); + const reserved = try readByte(reader); if (reserved != 0x00) { return error.MalformedMissingZeroByte; } @@ -1334,7 +1340,7 @@ pub const Instruction = struct { .F64x2_Extract_Lane, .F64x2_Replace_Lane, => { - immediate = InstructionImmediates{ .Index = try readByte(&reader) }; // laneidx + immediate = InstructionImmediates{ .Index = try readByte(reader) }; // laneidx }, .V128_Store => { const memarg = try MemArg.decode(reader, 128); @@ -1348,7 +1354,7 @@ pub const Instruction = struct { .I8x16_Shuffle => { var lane_indices: [16]u8 = undefined; for (&lane_indices) |*v| { - const laneidx: u8 = try readByte(&reader); + const laneidx: u8 = try readByte(reader); v.* = laneidx; } @@ -1430,42 +1436,40 @@ pub const NameCustomSection = struct { fn decodeInternal(self: *NameCustomSection, module_definition: *const ModuleDefinition, bytes: []const u8) !void { const DecodeHelpers = struct { - fn readName(stream: anytype) ![]const u8 { - const reader = stream.reader(); + fn readName(reader: *Reader) ![]const u8 { const name_length = try decodeLEB128(u32, reader); - const name: []const u8 = stream.buffer[stream.pos .. stream.pos + name_length]; - try stream.seekBy(name_length); + const name: []const u8 = reader.buffer[reader.seek .. reader.seek + name_length]; + try reader.discardAll(name_length); return name; } }; - var fixed_buffer_stream = std.io.fixedBufferStream(bytes); - var reader = fixed_buffer_stream.reader(); + var reader: Reader = .fixed(bytes); - while (try fixed_buffer_stream.getPos() != try fixed_buffer_stream.getEndPos()) { + while (reader.seek < reader.end) { const section_code = try readByte(&reader); - const section_size = try decodeLEB128(u32, reader); + const section_size = try decodeLEB128(u32, &reader); switch (section_code) { 0 => { - self.module_name = try DecodeHelpers.readName(&fixed_buffer_stream); + self.module_name = try DecodeHelpers.readName(&reader); }, 1 => { - const num_func_names = try decodeLEB128(u32, reader); + const num_func_names = try decodeLEB128(u32, &reader); try self.function_names.ensureTotalCapacity(num_func_names); var index: u32 = 0; while (index < num_func_names) : (index += 1) { - const func_index = try decodeLEB128(u32, reader); - const func_name: []const u8 = try DecodeHelpers.readName(&fixed_buffer_stream); + const func_index = try decodeLEB128(u32, &reader); + const func_name: []const u8 = try DecodeHelpers.readName(&reader); try self.function_names.putNoClobber(func_index, func_name); } }, 2 => { // TODO locals - try fixed_buffer_stream.seekBy(section_size); + try reader.discardAll(section_size); }, else => { - try fixed_buffer_stream.seekBy(section_size); + try reader.discardAll(section_size); }, } } @@ -2725,9 +2729,10 @@ const ModuleValidator = struct { return error.ValidationTypeStackHeightMismatch; } + const result = frame.*; _ = self.control_stack.pop(); - return frame.*; + return result; } fn freeControlTypes(self: *ModuleValidator, frame: *const ControlFrame) !void { @@ -2842,7 +2847,7 @@ pub const ModuleDefinition = struct { std.debug.assert(self.is_decoded == false); const DecodeHelpers = struct { - fn readRefValue(valtype: ValType, reader: anytype) MalformedError!Val { + fn readRefValue(valtype: ValType, reader: *Reader) MalformedError!Val { switch (valtype) { .FuncRef => { const func_index = try decodeLEB128(u32, reader); @@ -2857,12 +2862,12 @@ pub const ModuleDefinition = struct { } // TODO move these names into a string pool - fn readName(reader: anytype, _allocator: std.mem.Allocator) DecodeError![]const u8 { + fn readName(reader: *Reader, _allocator: std.mem.Allocator) DecodeError![]const u8 { const name_length = try decodeLEB128(u32, reader); const name: []u8 = try _allocator.alloc(u8, name_length); errdefer _allocator.free(name); - const read_length = try reader.read(name); + const read_length = reader.readSliceShort(name) catch return error.MalformedUnexpectedEnd; if (read_length != name_length) { return error.MalformedUnexpectedEnd; } @@ -2879,16 +2884,16 @@ pub const ModuleDefinition = struct { var validator = ModuleValidator.init(allocator, self.log); defer validator.deinit(); - var stream = std.io.fixedBufferStream(wasm); - var reader = stream.reader(); + var _main_reader: Reader = .fixed(wasm); + const reader = &_main_reader; // wasm header { - const magic = reader.readInt(u32, .big) catch |e| return eosError(e); + const magic = reader.takeInt(u32, .big) catch |e| return eosError(e); if (magic != 0x0061736D) { return error.MalformedMagicSignature; } - const version = reader.readInt(u32, .little) catch |e| return eosError(e); + const version = reader.takeInt(u32, .little) catch |e| return eosError(e); if (version != 1) { return error.MalformedUnsupportedWasmVersion; } @@ -2896,12 +2901,12 @@ pub const ModuleDefinition = struct { var num_functions_parsed: u32 = 0; - while (stream.pos < stream.buffer.len) { - const section_id: Section = std.meta.intToEnum(Section, try readByte(&reader)) catch { + while (reader.seek < reader.end) { + const section_id: Section = intToEnum(Section, try readByte(reader)) orelse { return error.MalformedSectionId; }; const section_size_bytes: usize = try decodeLEB128(u32, reader); - const section_start_pos = stream.pos; + const section_start_pos = reader.seek; switch (section_id) { .Custom => { @@ -2917,10 +2922,10 @@ pub const ModuleDefinition = struct { .data = std.array_list.Managed(u8).init(allocator), }; - const name_length: usize = stream.pos - section_start_pos; + const name_length: usize = reader.seek - section_start_pos; const data_length: usize = section_size_bytes - name_length; try section.data.resize(data_length); - const data_length_read = try reader.read(section.data.items); + const data_length_read = reader.readSliceShort(section.data.items) catch return error.MalformedUnexpectedEnd; if (data_length != data_length_read) { return error.MalformedUnexpectedEnd; } @@ -2938,7 +2943,7 @@ pub const ModuleDefinition = struct { var types_index: u32 = 0; while (types_index < num_types) : (types_index += 1) { - const sentinel = try readByte(&reader); + const sentinel = try readByte(reader); if (sentinel != k_function_type_sentinel_byte) { return error.MalformedTypeSentinel; } @@ -2984,7 +2989,7 @@ pub const ModuleDefinition = struct { .import_name = import_name, }; - const desc = try readByte(&reader); + const desc = try readByte(reader); switch (desc) { 0x00 => { const type_index = try decodeLEB128(u32, reader); @@ -3200,7 +3205,7 @@ pub const ModuleDefinition = struct { } } - const exportType = @as(ExportType, @enumFromInt(try readByte(&reader))); + const exportType = @as(ExportType, @enumFromInt(try readByte(reader))); const item_index = try decodeLEB128(u32, reader); const def = ExportDefinition{ .name = name, .index = item_index }; @@ -3245,12 +3250,12 @@ pub const ModuleDefinition = struct { }, .Element => { const ElementHelpers = struct { - fn readOffsetExpr(_reader: anytype, _module: *const ModuleDefinition) !ConstantExpression { + fn readOffsetExpr(_reader: *Reader, _module: *const ModuleDefinition) !ConstantExpression { const expr = try ConstantExpression.decode(_reader, _module, .Immutable, .I32); return expr; } - fn readElemsVal(elems: *std.array_list.Managed(Val), valtype: ValType, _reader: anytype, _module: *const ModuleDefinition) !void { + fn readElemsVal(elems: *std.array_list.Managed(Val), valtype: ValType, _reader: *Reader, _module: *const ModuleDefinition) !void { const num_elems = try decodeLEB128(u32, _reader); try elems.ensureTotalCapacity(num_elems); @@ -3264,7 +3269,7 @@ pub const ModuleDefinition = struct { } } - fn readElemsExpr(elems: *std.array_list.Managed(ConstantExpression), _reader: anytype, _module: *const ModuleDefinition, expected_reftype: ValType) !void { + fn readElemsExpr(elems: *std.array_list.Managed(ConstantExpression), _reader: *Reader, _module: *const ModuleDefinition, expected_reftype: ValType) !void { const num_elems = try decodeLEB128(u32, _reader); try elems.ensureTotalCapacity(num_elems); @@ -3275,8 +3280,8 @@ pub const ModuleDefinition = struct { } } - fn readNullElemkind(_reader: anytype) !void { - const null_elemkind = try readByte(&_reader); + fn readNullElemkind(_reader: *Reader) !void { + const null_elemkind = try readByte(_reader); if (null_elemkind != 0x00) { return error.MalformedBytecode; } @@ -3373,7 +3378,7 @@ pub const ModuleDefinition = struct { return error.MalformedFunctionCodeSectionMismatch; } - const wasm_code_address_begin: usize = stream.pos; + const wasm_code_address_begin: usize = reader.seek; const TypeCount = struct { valtype: ValType, @@ -3385,7 +3390,7 @@ pub const ModuleDefinition = struct { var code_index: u32 = 0; while (code_index < num_codes) { const code_size = try decodeLEB128(u32, reader); - const code_begin_pos = stream.pos; + const code_begin_pos = reader.seek; var func_def: *FunctionDefinition = &self.functions.items[code_index + self.imports.functions.items.len]; @@ -3429,7 +3434,7 @@ pub const ModuleDefinition = struct { while (parsing_code) { const instruction_index = @as(u32, @intCast(instructions.items.len)); - const wasm_instruction_address = stream.pos - wasm_code_address_begin; + const wasm_instruction_address = reader.seek - wasm_code_address_begin; const decoded_instruction: DecodedInstruction = try Instruction.decode(reader, self, func_def); const validation_immediates: ValidationImmediates = decoded_instruction.validation_immediates; @@ -3504,7 +3509,7 @@ pub const ModuleDefinition = struct { func_def.instructions_end = @intCast(instructions.items.len); - const code_actual_size = stream.pos - code_begin_pos; + const code_actual_size = reader.seek - code_begin_pos; if (code_actual_size != code_size) { return error.MalformedSectionSizeMismatch; } @@ -3531,7 +3536,7 @@ pub const ModuleDefinition = struct { }, } - const consumed_bytes = stream.pos - section_start_pos; + const consumed_bytes = reader.seek - section_start_pos; if (section_size_bytes != consumed_bytes) { return error.MalformedSectionSizeMismatch; } diff --git a/src/stack_ops.zig b/src/stack_ops.zig index e5cdef8..b0be32c 100644 --- a/src/stack_ops.zig +++ b/src/stack_ops.zig @@ -2135,17 +2135,19 @@ pub inline fn i8x16Shuffle(pc: u32, code: [*]const Instruction, stack: *Stack) v const immediate_index = code[pc].immediate.Index; const indices: u8x16 = stack.topFrame().module_instance.module_def.code.vec_shuffle_16_immediates.items[immediate_index]; + const v1_arr: [16]i8 = v1; + const v2_arr: [16]i8 = v2; + const indices_arr: [16]u8 = indices; var concat: [32]i8 = undefined; for (concat[0..16], 0..) |_, i| { - concat[i] = v1[i]; - concat[i + 16] = v2[i]; + concat[i] = v1_arr[i]; + concat[i + 16] = v2_arr[i]; } - const concat_v: @Vector(32, i8) = concat; var arr: [16]i8 = undefined; for (&arr, 0..) |*v, i| { - const laneidx = indices[i]; - v.* = concat_v[laneidx]; + const laneidx = indices_arr[i]; + v.* = concat[laneidx]; } const shuffled: i8x16 = arr; @@ -2153,15 +2155,17 @@ pub inline fn i8x16Shuffle(pc: u32, code: [*]const Instruction, stack: *Stack) v } pub inline fn i8x16Swizzle(stack: *Stack) void { - const indices: i8x16 = @as(i8x16, @bitCast(stack.popV128())); - const vec: i8x16 = @as(i8x16, @bitCast(stack.popV128())); - var swizzled: i8x16 = undefined; + const indices_vec: i8x16 = @as(i8x16, @bitCast(stack.popV128())); + const vec_v: i8x16 = @as(i8x16, @bitCast(stack.popV128())); + const indices: [16]i8 = indices_vec; + const vec: [16]i8 = vec_v; + var swizzled: [16]i8 = undefined; var i: usize = 0; while (i < 16) : (i += 1) { const value = if (indices[i] >= 0 and indices[i] < 16) vec[@as(usize, @intCast(indices[i]))] else @as(i8, 0); swizzled[i] = value; } - stack.pushV128(@as(v128, @bitCast(swizzled))); + stack.pushV128(@as(v128, @bitCast(@as(i8x16, swizzled)))); } pub inline fn v128Not(stack: *Stack) void { @@ -2406,8 +2410,8 @@ pub inline fn i16x8Neg(stack: *Stack) void { } pub inline fn i16x8Q15mulrSatS(stack: *Stack) void { - const v2 = @as(i16x8, @bitCast(stack.popV128())); - const v1 = @as(i16x8, @bitCast(stack.popV128())); + const v2: [8]i16 = @as(i16x8, @bitCast(stack.popV128())); + const v1: [8]i16 = @as(i16x8, @bitCast(stack.popV128())); const power: i32 = comptime std.math.powi(i32, 2, 14) catch unreachable; var arr: [8]i16 = undefined; @@ -2664,10 +2668,11 @@ pub inline fn i32x4DotI16x8S(stack: *Stack) void { const v1: i32x8 = @as(i16x8, @bitCast(stack.popV128())); const v2: i32x8 = @as(i16x8, @bitCast(stack.popV128())); const product = v1 * v2; + const product_arr: [8]i32 = product; var arr: [4]i32 = undefined; for (&arr, 0..) |*v, i| { - const p1: i32 = product[i * 2]; - const p2: i32 = product[(i * 2) + 1]; + const p1: i32 = product_arr[i * 2]; + const p2: i32 = product_arr[(i * 2) + 1]; v.* = p1 +% p2; } const dot: i32x4 = arr; @@ -3143,9 +3148,11 @@ const VectorBinaryOp = enum(u8) { }; fn vectorOr(comptime len: usize, v1: @Vector(len, bool), v2: @Vector(len, bool)) @Vector(len, bool) { + const a1: [len]bool = v1; + const a2: [len]bool = v2; var arr: [len]bool = undefined; for (&arr, 0..) |*v, i| { - v.* = v1[i] or v2[i]; + v.* = a1[i] or a2[i]; } return arr; } @@ -3219,7 +3226,7 @@ fn vectorBinOp(comptime T: type, comptime op: VectorBinaryOp, stack: *Stack) voi fn vectorAbs(comptime T: type, stack: *Stack) void { const type_info = @typeInfo(T).vector; const child_type = type_info.child; - const vec = @as(T, @bitCast(stack.popV128())); + const vec: [type_info.len]child_type = @as(T, @bitCast(stack.popV128())); var arr: [type_info.len]child_type = undefined; for (&arr, 0..) |*v, i| { v.* = @as(child_type, @bitCast(@abs(vec[i]))); @@ -3233,8 +3240,8 @@ fn vectorAvgrU(comptime T: type, stack: *Stack) void { const child_type = type_info.child; const type_big_width = std.meta.Int(.unsigned, @bitSizeOf(child_type) * 2); - const v1 = @as(T, @bitCast(stack.popV128())); - const v2 = @as(T, @bitCast(stack.popV128())); + const v1: [type_info.len]child_type = @as(T, @bitCast(stack.popV128())); + const v2: [type_info.len]child_type = @as(T, @bitCast(stack.popV128())); var arr: [type_info.len]child_type = undefined; for (&arr, 0..) |*v, i| { const vv1: type_big_width = v1[i]; @@ -3357,11 +3364,11 @@ fn vectorBitmask(comptime T: type, vec: v128) i32 { fn vectorLoadLane(comptime T: type, instruction: Instruction, stack: *Stack) TrapError!void { const vec_type_info = @typeInfo(T).vector; - var vec = @as(T, @bitCast(stack.popV128())); + var arr: [vec_type_info.len]vec_type_info.child = @as(T, @bitCast(stack.popV128())); const immediate = stack.topFrame().module_instance.module_def.code.memory_offset_and_lane_immediates.items[instruction.immediate.Index]; const scalar = try loadFromMem(vec_type_info.child, stack, immediate.offset); - vec[immediate.laneidx] = scalar; - stack.pushV128(@as(v128, @bitCast(vec))); + arr[immediate.laneidx] = scalar; + stack.pushV128(@as(v128, @bitCast(@as(T, arr)))); } fn vectorLoadExtend(comptime mem_type: type, comptime extend_type: type, comptime len: usize, mem_offset: u64, stack: *Stack) TrapError!void { @@ -3384,17 +3391,22 @@ fn vectorLoadLaneZero(comptime T: type, instruction: Instruction, stack: *Stack) fn vectorStoreLane(comptime T: type, instruction: Instruction, stack: *Stack) TrapError!void { const vec = @as(T, @bitCast(stack.popV128())); + const child_type = @typeInfo(T).vector.child; + const vec_len = @typeInfo(T).vector.len; const immediate = stack.topFrame().module_instance.module_def.code.memory_offset_and_lane_immediates.items[instruction.immediate.Index]; - const scalar = vec[immediate.laneidx]; + const arr: [vec_len]child_type = vec; + const scalar = arr[immediate.laneidx]; try storeInMem(scalar, stack, immediate.offset); stack.pushV128(@as(v128, @bitCast(vec))); } fn vectorExtractLane(comptime T: type, lane: u32, stack: *Stack) void { const vec = @as(T, @bitCast(stack.popV128())); - const lane_value = vec[lane]; - const child_type = @typeInfo(T).vector.child; + const vec_len = @typeInfo(T).vector.len; + const arr: [vec_len]child_type = vec; + const lane_value = arr[lane]; + switch (child_type) { i8, u8, i16, u16, i32 => stack.pushI32(lane_value), i64 => stack.pushI64(lane_value), @@ -3406,6 +3418,7 @@ fn vectorExtractLane(comptime T: type, lane: u32, stack: *Stack) void { fn vectorReplaceLane(comptime T: type, lane: u32, stack: *Stack) void { const child_type = @typeInfo(T).vector.child; + const vec_len = @typeInfo(T).vector.len; const lane_value = switch (child_type) { i8, i16, i32 => @as(child_type, @truncate(stack.popI32())), i64 => stack.popI64(), @@ -3413,9 +3426,10 @@ fn vectorReplaceLane(comptime T: type, lane: u32, stack: *Stack) void { f64 => stack.popF64(), else => unreachable, }; - var vec = @as(T, @bitCast(stack.popV128())); - vec[lane] = lane_value; - stack.pushV128(@as(v128, @bitCast(vec))); + const vec = @as(T, @bitCast(stack.popV128())); + var arr: [vec_len]child_type = vec; + arr[lane] = lane_value; + stack.pushV128(@as(v128, @bitCast(@as(T, arr)))); } const VectorSide = enum { @@ -3432,10 +3446,12 @@ fn vectorAddPairwise(comptime in_type: type, comptime out_type: type, stack: *St const out_info = @typeInfo(out_type).vector; const vec = @as(in_type, @bitCast(stack.popV128())); + const in_info = @typeInfo(in_type).vector; + const vec_arr: [in_info.len]in_info.child = vec; var arr: [out_info.len]out_info.child = undefined; for (&arr, 0..) |*v, i| { - const v1: out_info.child = vec[i * 2]; - const v2: out_info.child = vec[(i * 2) + 1]; + const v1: out_info.child = vec_arr[i * 2]; + const v2: out_info.child = vec_arr[(i * 2) + 1]; v.* = v1 + v2; } const sum: out_type = arr; @@ -3447,12 +3463,15 @@ fn vectorMulPairwise(comptime in_type: type, comptime out_type: type, side: Vect const vec2 = @as(in_type, @bitCast(stack.popV128())); const vec1 = @as(in_type, @bitCast(stack.popV128())); + const in_info = @typeInfo(in_type).vector; + const vec1_arr: [in_info.len]in_info.child = vec1; + const vec2_arr: [in_info.len]in_info.child = vec2; var arr: [info_out.len]info_out.child = undefined; for (&arr, 0..) |*v, i| { const index = if (side == .Low) i else i + info_out.len; - const v1: info_out.child = vec1[index]; - const v2: info_out.child = vec2[index]; + const v1: info_out.child = vec1_arr[index]; + const v2: info_out.child = vec2_arr[index]; v.* = v1 * v2; } const product = arr; @@ -3465,9 +3484,10 @@ fn vectorExtend(comptime in_type: type, comptime out_type: type, comptime side: const side_offset = if (side == .Low) 0 else in_info.len / 2; const vec = @as(in_type, @bitCast(stack.popV128())); + const vec_arr: [in_info.len]in_info.child = vec; var arr: [out_info.len]out_info.child = undefined; for (&arr, 0..) |*v, i| { - v.* = vec[i + side_offset]; + v.* = vec_arr[i + side_offset]; } const extended: out_type = arr; stack.pushV128(@as(v128, @bitCast(extended))); @@ -3489,10 +3509,10 @@ fn vectorConvert(comptime in_type: type, comptime out_type: type, comptime side: const out_info = @typeInfo(out_type).vector; const side_offset = if (side == .Low) 0 else in_info.len / 2; - const vec_in = @as(in_type, @bitCast(stack.popV128())); + const vec_in_arr: [in_info.len]in_info.child = @as(in_type, @bitCast(stack.popV128())); var arr: [out_info.len]out_info.child = undefined; for (arr, 0..) |_, i| { - const v: in_info.child = if (i < in_info.len) vec_in[i + side_offset] else 0; + const v: in_info.child = if (i < in_info.len) vec_in_arr[i + side_offset] else 0; switch (@typeInfo(out_info.child)) { .int => arr[i] = blk: { if (convert == .SafeCast) { @@ -3516,9 +3536,10 @@ fn vectorNarrowingSaturate(comptime in_type: type, comptime out_type: type, vec: std.debug.assert(out_info.len == in_info.len); + const vec_arr: [in_info.len]in_info.child = vec; var arr: [out_info.len]T = undefined; for (&arr, 0..) |*v, i| { - v.* = @as(T, @intCast(std.math.clamp(vec[i], std.math.minInt(T), std.math.maxInt(T)))); + v.* = @as(T, @intCast(std.math.clamp(vec_arr[i], std.math.minInt(T), std.math.maxInt(T)))); } return arr; } diff --git a/src/tests.zig b/src/tests.zig index b90c826..f1c7a9c 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -13,8 +13,7 @@ test "StackVM.Integration" { var allocator = std.testing.allocator; - var cwd = std.fs.cwd(); - const wasm_data: []u8 = try cwd.readFileAlloc(allocator, wasm_filepath, 1024 * 1024 * 128); + const wasm_data: []u8 = try std.Io.Dir.cwd().readFileAlloc(std.Options.debug_io, wasm_filepath, allocator, .limited(1024 * 1024 * 128)); defer allocator.free(wasm_data); const module_def_opts = core.ModuleDefinitionOpts{ @@ -37,8 +36,7 @@ test "StackVM.Metering" { var allocator = std.testing.allocator; - var cwd = std.fs.cwd(); - const wasm_data: []u8 = try cwd.readFileAlloc(allocator, wasm_filepath, 1024 * 1024 * 128); + const wasm_data: []u8 = try std.Io.Dir.cwd().readFileAlloc(std.Options.debug_io, wasm_filepath, allocator, .limited(1024 * 1024 * 128)); defer allocator.free(wasm_data); const module_def_opts = core.ModuleDefinitionOpts{ diff --git a/src/vm_stack.zig b/src/vm_stack.zig index 5c8f343..46f0fc3 100644 --- a/src/vm_stack.zig +++ b/src/vm_stack.zig @@ -3684,7 +3684,6 @@ pub const StackVM = struct { var buffer = std.array_list.Managed(u8).init(allocator); try buffer.ensureTotalCapacity(512); - var writer = buffer.writer(); for (self.stack.frames[0..self.stack.num_frames], 0..) |_, i| { const reverse_index = (self.stack.num_frames - 1) - i; @@ -3692,7 +3691,7 @@ pub const StackVM = struct { var indent_level: usize = 0; while (indent_level < indent) : (indent_level += 1) { - try writer.print("\t", .{}); + try buffer.appendSlice("\t"); } const name_section: *const NameCustomSection = &frame.func.module.module_def.name_section; @@ -3701,7 +3700,9 @@ pub const StackVM = struct { const func_name_index: usize = frame.func.def_index; const function_name = name_section.findFunctionName(func_name_index); - try writer.print("{}: {s}!{s}\n", .{ reverse_index, module_name, function_name }); + var fmt_buf: [256]u8 = undefined; + const formatted = std.fmt.bufPrint(&fmt_buf, "{}: {s}!{s}\n", .{ reverse_index, module_name, function_name }) catch "(format error)\n"; + try buffer.appendSlice(formatted); } return buffer; diff --git a/src/wasi.zig b/src/wasi.zig index 9217bec..b33f403 100644 --- a/src/wasi.zig +++ b/src/wasi.zig @@ -4,6 +4,162 @@ const core = @import("core.zig"); const StringPool = @import("stringpool.zig"); +/// Compatibility wrappers for POSIX functions removed from std.posix in Zig 0.16. +const posix_compat = struct { + const posix = std.posix; + const c = std.c; + const PosixError = error{ AccessDenied, DeviceBusy, DiskQuota, FileBusy, FileNotFound, FileTooBig, InputOutput, IsDir, LinkQuotaExceeded, NameTooLong, NoDevice, NoSpaceLeft, NotDir, PathAlreadyExists, ProcessFdQuotaExceeded, ReadOnlyFileSystem, SymLinkLoop, SystemFdQuotaExceeded, SystemResources, Unexpected, WouldBlock }; + + fn mapErrno() PosixError { + return switch (@as(c.E, @enumFromInt(c._errno().*))) { + .ACCES, .PERM => error.AccessDenied, + .BUSY => error.DeviceBusy, + .DQUOT => error.DiskQuota, + .TXTBSY => error.FileBusy, + .NOENT => error.FileNotFound, + .FBIG => error.FileTooBig, + .IO => error.InputOutput, + .ISDIR => error.IsDir, + .MLINK => error.LinkQuotaExceeded, + .NAMETOOLONG => error.NameTooLong, + .NODEV => error.NoDevice, + .NOSPC => error.NoSpaceLeft, + .NOTDIR => error.NotDir, + .EXIST => error.PathAlreadyExists, + .MFILE => error.ProcessFdQuotaExceeded, + .ROFS => error.ReadOnlyFileSystem, + .LOOP => error.SymLinkLoop, + .NFILE => error.SystemFdQuotaExceeded, + .NOMEM => error.SystemResources, + .AGAIN => error.WouldBlock, + else => error.Unexpected, + }; + } + + fn close(fd: posix.fd_t) void { + _ = c.close(fd); + } + + fn fcntl(fd: posix.fd_t, cmd: c_int, arg: usize) PosixError!usize { + const rc = c.fcntl(fd, cmd, @as(c_int, @intCast(arg))); + if (rc == -1) return mapErrno(); + return @intCast(rc); + } + + fn fstat(fd: posix.fd_t) PosixError!posix.Stat { + var stat: posix.Stat = undefined; + const rc = c.fstat(fd, &stat); + if (rc != 0) return mapErrno(); + return stat; + } + + fn futimens(fd: posix.fd_t, times: *const [2]posix.timespec) PosixError!void { + const rc = c.futimens(fd, times); + if (rc != 0) return mapErrno(); + } + + fn lseek_SET(fd: posix.fd_t, offset: u64) PosixError!void { + const rc = c.lseek(fd, @intCast(offset), c.SEEK.SET); + if (rc == -1) return mapErrno(); + } + + fn lseek_CUR_get(fd: posix.fd_t) PosixError!u64 { + const rc = c.lseek(fd, 0, c.SEEK.CUR); + if (rc == -1) return mapErrno(); + return @intCast(rc); + } + + fn lseek_CUR(fd: posix.fd_t, offset: i64) PosixError!void { + const rc = c.lseek(fd, @intCast(offset), c.SEEK.CUR); + if (rc == -1) return mapErrno(); + } + + fn lseek_END(fd: posix.fd_t, offset: i64) PosixError!void { + const rc = c.lseek(fd, @intCast(offset), c.SEEK.END); + if (rc == -1) return mapErrno(); + } + + fn readv(fd: posix.fd_t, iov: []posix.iovec) PosixError!usize { + const rc = c.readv(fd, @ptrCast(iov.ptr), @intCast(iov.len)); + if (rc == -1) return mapErrno(); + return @intCast(rc); + } + + fn preadv(fd: posix.fd_t, iov: []posix.iovec, offset: u64) PosixError!usize { + const rc = c.preadv(fd, @ptrCast(iov.ptr), @intCast(iov.len), @intCast(offset)); + if (rc == -1) return mapErrno(); + return @intCast(rc); + } + + fn writev(fd: posix.fd_t, iov: []const posix.iovec_const) PosixError!usize { + const rc = c.writev(fd, @ptrCast(iov.ptr), @intCast(iov.len)); + if (rc == -1) return mapErrno(); + return @intCast(rc); + } + + fn pwritev(fd: posix.fd_t, iov: []const posix.iovec_const, offset: u64) PosixError!usize { + const rc = c.pwritev(fd, @ptrCast(iov.ptr), @intCast(iov.len), @intCast(offset)); + if (rc == -1) return mapErrno(); + return @intCast(rc); + } + + fn ftruncate(fd: posix.fd_t, length: u64) PosixError!void { + const rc = c.ftruncate(fd, @intCast(length)); + if (rc != 0) return mapErrno(); + } + + fn mkdirat(dirfd: posix.fd_t, path: anytype, mode: posix.mode_t) PosixError!void { + const p = toPosixPath(path); + const rc = c.mkdirat(dirfd, &p, mode); + if (rc != 0) return mapErrno(); + } + + fn unlinkat(dirfd: posix.fd_t, path: anytype, flags: u32) PosixError!void { + const p = toPosixPath(path); + const rc = c.unlinkat(dirfd, &p, @intCast(flags)); + if (rc != 0) return mapErrno(); + } + + fn symlinkat(target: anytype, dirfd: posix.fd_t, linkpath: anytype) PosixError!void { + const t = toPosixPath(target); + const l = toPosixPath(linkpath); + const rc = c.symlinkat(&t, dirfd, &l); + if (rc != 0) return mapErrno(); + } + + fn toPosixPath(path: anytype) [posix.PATH_MAX - 1:0]u8 { + var result: [posix.PATH_MAX - 1:0]u8 = undefined; + @memcpy(result[0..path.len], path); + result[path.len] = 0; + return result; + } + + fn clock_getres(clk_id: posix.clockid_t) PosixError!posix.timespec { + var ts: posix.timespec = undefined; + const rc = c.clock_getres(clk_id, &ts); + if (rc != 0) return mapErrno(); + return ts; + } + + fn clock_gettime(clk_id: posix.clockid_t) PosixError!posix.timespec { + var ts: posix.timespec = undefined; + const rc = c.clock_gettime(clk_id, &ts); + if (rc != 0) return mapErrno(); + return ts; + } + + fn open(path: anytype, flags: std.posix.O, mode: posix.mode_t) PosixError!posix.fd_t { + const p = toPosixPath(path); + const rc = c.open(&p, flags, mode); + if (rc == -1) return mapErrno(); + return rc; + } + + fn random_bytes(buf: []u8) void { + c.arc4random_buf(buf.ptr, buf.len); + } +}; + const Val = core.Val; const ValType = core.ValType; const ModuleInstance = core.ModuleInstance; @@ -47,9 +203,10 @@ const WasiContext = struct { }; { - var cwd_buffer: [std.fs.max_path_bytes]u8 = undefined; - const cwd: []const u8 = try std.process.getCwd(&cwd_buffer); - context.cwd = try context.strings.put(cwd); + var cwd_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + const cwd_ptr = std.c.getcwd(&cwd_buffer, cwd_buffer.len) orelse return error.Unexpected; + const cwd_len = std.mem.indexOfScalar(u8, cwd_ptr[0..cwd_buffer.len], 0) orelse cwd_buffer.len; + context.cwd = try context.strings.put(cwd_ptr[0..cwd_len]); } if (opts.argv) |argv| { @@ -80,9 +237,9 @@ const WasiContext = struct { const empty_dir_entries = std.array_list.Managed(WasiDirEntry).init(allocator); try context.fd_table.ensureTotalCapacity(3 + context.dirs.len); - context.fd_table.appendAssumeCapacity(FdInfo{ .fd = std.fs.File.stdin().handle, .path_absolute = path_stdin, .rights = .{}, .is_preopen = true, .dir_entries = empty_dir_entries }); - context.fd_table.appendAssumeCapacity(FdInfo{ .fd = std.fs.File.stdout().handle, .path_absolute = path_stdout, .rights = .{}, .is_preopen = true, .dir_entries = empty_dir_entries }); - context.fd_table.appendAssumeCapacity(FdInfo{ .fd = std.fs.File.stderr().handle, .path_absolute = path_stderr, .rights = .{}, .is_preopen = true, .dir_entries = empty_dir_entries }); + context.fd_table.appendAssumeCapacity(FdInfo{ .fd = std.Io.File.stdin().handle, .path_absolute = path_stdin, .rights = .{}, .is_preopen = true, .dir_entries = empty_dir_entries }); + context.fd_table.appendAssumeCapacity(FdInfo{ .fd = std.Io.File.stdout().handle, .path_absolute = path_stdout, .rights = .{}, .is_preopen = true, .dir_entries = empty_dir_entries }); + context.fd_table.appendAssumeCapacity(FdInfo{ .fd = std.Io.File.stderr().handle, .path_absolute = path_stderr, .rights = .{}, .is_preopen = true, .dir_entries = empty_dir_entries }); try context.fd_wasi_table.put(0, 0); try context.fd_wasi_table.put(1, 1); try context.fd_wasi_table.put(2, 2); @@ -314,7 +471,7 @@ const WasiContext = struct { fd_info.open_handles -= 1; if (fd_info.open_handles == 0) { - std.posix.close(fd_info.fd); + posix_compat.close(fd_info.fd); self.fd_table_freelist.appendAssumeCapacity(fd_table_index); // capacity was allocated when the associated fd_table slot was allocated } } else { @@ -335,7 +492,7 @@ const WasiContext = struct { _ = self.fd_path_lookup.remove(path_absolute); var fd_info: *FdInfo = &self.fd_table.items[fd_table_index]; - std.posix.close(fd_info.fd); + posix_compat.close(fd_info.fd); fd_info.open_handles = 0; self.fd_table_freelist.appendAssumeCapacity(fd_table_index); // capacity was allocated when the associated fd_table slot was allocated } @@ -977,8 +1134,8 @@ const Helpers = struct { .fs_rights_inheriting = WASI_RIGHTS_ALL, }; - if (std.posix.fcntl(fd, std.posix.F.GETFL, 0)) |fd_flags| { - if (std.posix.fstat(fd)) |fd_stat| { + if (posix_compat.fcntl(fd, std.posix.F.GETFL, 0)) |fd_flags| { + if (posix_compat.fstat(fd)) |fd_stat| { const flags: std.posix.O = @bitCast(@as(u32, @intCast(fd_flags))); // filetype @@ -1115,7 +1272,7 @@ const Helpers = struct { const flags = fdflagsToFlagsPosix(fdflags); const flags_int = @as(u32, @bitCast(flags)); - if (std.posix.fcntl(fd_info.fd, std.posix.F.SETFL, flags_int)) |_| {} else |err| { + if (posix_compat.fcntl(fd_info.fd, std.posix.F.SETFL, flags_int)) |_| {} else |err| { errno.* = Errno.translateError(err); } @@ -1206,7 +1363,7 @@ const Helpers = struct { times[1].nsec = UTIME_NOW; } - std.posix.futimens(fd, ×) catch |err| { + posix_compat.futimens(fd, ×) catch |err| { errno.* = Errno.translateError(err); }; } @@ -1246,7 +1403,7 @@ const Helpers = struct { var stat_wasi: std.os.wasi.filestat_t = undefined; - if (std.posix.fstat(fd)) |stat| { + if (posix_compat.fstat(fd)) |stat| { stat_wasi.dev = if (builtin.os.tag == .macos) @as(u32, @bitCast(stat.dev)) else stat.dev; stat_wasi.ino = stat.ino; stat_wasi.filetype = posixModeToWasiFiletype(stat.mode); @@ -1347,7 +1504,7 @@ const Helpers = struct { errno.* = Errno.LOOP; } if (rc == .SUCCESS) { - std.posix.close(fd); + posix_compat.close(fd); } return null; } @@ -1419,7 +1576,7 @@ const Helpers = struct { const S = std.posix.S; const mode: std.posix.mode_t = S.IRUSR | S.IWUSR | S.IRGRP | S.IWGRP | S.IROTH; - if (std.posix.open(path, flags, mode)) |fd| { + if (posix_compat.open(path, flags, mode)) |fd| { return fd; } else |err| { errno.* = Errno.translateError(err); @@ -1448,10 +1605,9 @@ const Helpers = struct { var file_index = start_cookie; - var fbs = std.io.fixedBufferStream(out_buffer); - var writer = fbs.writer(); + var writer: std.Io.Writer = .fixed(out_buffer); - while (fbs.pos < fbs.buffer.len and errno.* == .SUCCESS) { + while (writer.end < writer.buffer.len and errno.* == .SUCCESS) { if (file_index < fd_info.dir_entries.items.len) { for (fd_info.dir_entries.items[@intCast(file_index)..]) |entry| { const cookie = file_index + 1; @@ -1459,14 +1615,14 @@ const Helpers = struct { writer.writeInt(u64, entry.inode, .little) catch break; writer.writeInt(u32, signedCast(u32, entry.filename.len, errno), .little) catch break; writer.writeInt(u32, @intFromEnum(entry.filetype), .little) catch break; - _ = writer.write(entry.filename) catch break; + writer.writeAll(entry.filename) catch break; file_index += 1; } } // load more entries for the next loop iteration - if (fbs.pos < fbs.buffer.len and errno.* == .SUCCESS) { + if (writer.end < writer.buffer.len and errno.* == .SUCCESS) { if (osFunc(fd_info, restart_scan, errno) == false) { // no more files or error break; @@ -1475,7 +1631,7 @@ const Helpers = struct { restart_scan = false; } - const bytes_written = signedCast(u32, fbs.pos, errno); + const bytes_written = signedCast(u32, writer.end, errno); return bytes_written; } @@ -1551,7 +1707,7 @@ const Helpers = struct { fn enumerateDirEntriesDarwin(fd_info: *WasiContext.FdInfo, restart_scan: bool, errno: *Errno) bool { if (restart_scan) { - std.posix.lseek_SET(fd_info.fd, 0) catch |err| { + posix_compat.lseek_SET(fd_info.fd, 0) catch |err| { errno.* = Errno.translateError(err); return false; }; @@ -1621,7 +1777,7 @@ const Helpers = struct { fn enumerateDirEntriesLinux(fd_info: *WasiContext.FdInfo, restart_scan: bool, errno: *Errno) bool { if (restart_scan) { - std.posix.lseek_SET(fd_info.fd, 0) catch |err| { + posix_compat.lseek_SET(fd_info.fd, 0) catch |err| { errno.* = Errno.translateError(err); return false; }; @@ -1688,16 +1844,15 @@ const Helpers = struct { const iov = stack_iov[0..iovec_array_count]; const iovec_array_bytes_length = @sizeOf(u32) * 2 * iovec_array_count; if (getMemorySlice(module, iovec_array_begin, iovec_array_bytes_length, errno)) |iovec_mem| { - var stream = std.io.fixedBufferStream(iovec_mem); - var reader = stream.reader(); + var reader: std.Io.Reader = .fixed(iovec_mem); for (iov) |*iovec| { - const iov_base: u32 = reader.readInt(u32, .little) catch { + const iov_base: u32 = reader.takeInt(u32, .little) catch { errno.* = Errno.INVAL; return null; }; - const iov_len: u32 = reader.readInt(u32, .little) catch { + const iov_len: u32 = reader.takeInt(u32, .little) catch { errno.* = Errno.INVAL; return null; }; @@ -1780,10 +1935,14 @@ fn wasi_clock_res_get(_: ?*anyopaque, module: *ModuleInstance, params: [*]const } } } else { - const clock_getres = if (builtin.os.tag == .linux) Linux.clock_getres else std.posix.clock_getres; - - var ts: std.posix.timespec = undefined; - if (clock_getres(system_clockid, &ts)) { + if (builtin.os.tag == .linux) { + var ts: std.posix.timespec = undefined; + if (Linux.clock_getres(system_clockid, &ts)) { + freqency_ns = @as(u64, @intCast(ts.nsec)); + } else |_| { + errno = Errno.INVAL; + } + } else if (posix_compat.clock_getres(system_clockid)) |ts| { freqency_ns = @as(u64, @intCast(ts.nsec)); } else |_| { errno = Errno.INVAL; @@ -1852,7 +2011,7 @@ fn wasi_clock_time_get(_: ?*anyopaque, module: *ModuleInstance, params: [*]const }, } } else { - const maybe_ts: ?std.posix.timespec = std.posix.clock_gettime(system_clockid) catch null; + const maybe_ts: ?std.posix.timespec = posix_compat.clock_gettime(system_clockid) catch null; if (maybe_ts) |ts| { timestamp_ns = Helpers.posixTimespecToWasi(ts); } else { @@ -1933,7 +2092,7 @@ fn fd_wasi_prestat_get(userdata: ?*anyopaque, module: *ModuleInstance, params: [ if (context.fdDirPath(fd_dir_wasi, &errno)) |path_source| { const name_len: u32 = @as(u32, @intCast(path_source.len)); - Helpers.writeIntToMemory(u32, std.os.wasi.PREOPENTYPE_DIR, prestat_mem_offset + 0, module, &errno); + Helpers.writeIntToMemory(u32, @intFromEnum(std.os.wasi.preopentype_t.DIR), prestat_mem_offset + 0, module, &errno); Helpers.writeIntToMemory(u32, name_len, prestat_mem_offset + @sizeOf(u32), module, &errno); } } @@ -1982,7 +2141,7 @@ fn fd_wasi_read(userdata: ?*anyopaque, module: *ModuleInstance, params: [*]const if (context.fdLookup(fd_wasi, &errno)) |fd_info| { var stack_iov = [_]std.posix.iovec{undefined} ** 1024; if (Helpers.initIovecs(std.posix.iovec, &stack_iov, &errno, module, iovec_array_begin, iovec_array_count)) |iov| { - if (std.posix.readv(fd_info.fd, iov)) |read_bytes| { + if (posix_compat.readv(fd_info.fd, iov)) |read_bytes| { if (read_bytes <= std.math.maxInt(u32)) { Helpers.writeIntToMemory(u32, @as(u32, @intCast(read_bytes)), bytes_read_out_offset, module, &errno); } else { @@ -2046,7 +2205,7 @@ fn fd_wasi_pread(userdata: ?*anyopaque, module: *ModuleInstance, params: [*]cons if (context.fdLookup(fd_wasi, &errno)) |fd_info| { var stack_iov = [_]std.posix.iovec{undefined} ** 1024; if (Helpers.initIovecs(std.posix.iovec, &stack_iov, &errno, module, iovec_array_begin, iovec_array_count)) |iov| { - if (std.posix.preadv(fd_info.fd, iov, read_offset)) |read_bytes| { + if (posix_compat.preadv(fd_info.fd, iov, read_offset)) |read_bytes| { if (read_bytes <= std.math.maxInt(u32)) { Helpers.writeIntToMemory(u32, @as(u32, @intCast(read_bytes)), bytes_read_out_offset, module, &errno); } else { @@ -2157,7 +2316,7 @@ fn fd_wasi_allocate(userdata: ?*anyopaque, _: *ModuleInstance, params: [*]const // so we need to emulate that behavior here const length_total = @as(u64, @intCast(@as(i128, offset) + length_relative)); if (stat.size < length_total) { - std.posix.ftruncate(fd_info.fd, length_total) catch |err| { + posix_compat.ftruncate(fd_info.fd, length_total) catch |err| { errno = Errno.translateError(err); }; } @@ -2219,7 +2378,7 @@ fn fd_wasi_filestat_set_size(userdata: ?*anyopaque, _: *ModuleInstance, params: if (Helpers.isStdioHandle(fd_wasi)) { errno = Errno.BADF; } else if (context.fdLookup(fd_wasi, &errno)) |fd_info| { - std.posix.ftruncate(fd_info.fd, size) catch |err| { + posix_compat.ftruncate(fd_info.fd, size) catch |err| { errno = Errno.translateError(err); }; } @@ -2278,24 +2437,24 @@ fn fd_wasi_seek(userdata: ?*anyopaque, module: *ModuleInstance, params: [*]const .Set => { if (offset >= 0) { const offset_unsigned = @as(u64, @intCast(offset)); - std.posix.lseek_SET(fd_os, offset_unsigned) catch |err| { + posix_compat.lseek_SET(fd_os, offset_unsigned) catch |err| { errno = Errno.translateError(err); }; } }, .Cur => { - std.posix.lseek_CUR(fd_os, offset) catch |err| { + posix_compat.lseek_CUR(fd_os, offset) catch |err| { errno = Errno.translateError(err); }; }, .End => { - std.posix.lseek_END(fd_os, offset) catch |err| { + posix_compat.lseek_END(fd_os, offset) catch |err| { errno = Errno.translateError(err); }; }, } - if (std.posix.lseek_CUR_get(fd_os)) |filepos| { + if (posix_compat.lseek_CUR_get(fd_os)) |filepos| { Helpers.writeIntToMemory(u64, filepos, filepos_out_offset, module, &errno); } else |err| { errno = Errno.translateError(err); @@ -2322,7 +2481,7 @@ fn fd_wasi_tell(userdata: ?*anyopaque, module: *ModuleInstance, params: [*]const if (errno == .SUCCESS) { if (context.fdLookup(fd_wasi, &errno)) |fd_info| { - if (std.posix.lseek_CUR_get(fd_info.fd)) |filepos| { + if (posix_compat.lseek_CUR_get(fd_info.fd)) |filepos| { Helpers.writeIntToMemory(u64, filepos, filepos_out_offset, module, &errno); } else |err| { errno = Errno.translateError(err); @@ -2346,7 +2505,7 @@ fn fd_wasi_write(userdata: ?*anyopaque, module: *ModuleInstance, params: [*]cons if (context.fdLookup(fd_wasi, &errno)) |fd_info| { var stack_iov = [_]std.posix.iovec_const{undefined} ** 1024; if (Helpers.initIovecs(std.posix.iovec_const, &stack_iov, &errno, module, iovec_array_begin, iovec_array_count)) |iov| { - if (std.posix.writev(fd_info.fd, iov)) |written_bytes| { + if (posix_compat.writev(fd_info.fd, iov)) |written_bytes| { Helpers.writeIntToMemory(u32, @as(u32, @intCast(written_bytes)), bytes_written_out_offset, module, &errno); } else |err| { errno = Errno.translateError(err); @@ -2372,7 +2531,7 @@ fn fd_wasi_pwrite(userdata: ?*anyopaque, module: *ModuleInstance, params: [*]con if (context.fdLookup(fd_wasi, &errno)) |fd_info| { var stack_iov = [_]std.posix.iovec_const{undefined} ** 1024; if (Helpers.initIovecs(std.posix.iovec_const, &stack_iov, &errno, module, iovec_array_begin, iovec_array_count)) |iov| { - if (std.posix.pwritev(fd_info.fd, iov, write_offset)) |written_bytes| { + if (posix_compat.pwritev(fd_info.fd, iov, write_offset)) |written_bytes| { Helpers.writeIntToMemory(u32, @as(u32, @intCast(written_bytes)), bytes_written_out_offset, module, &errno); } else |err| { errno = Errno.translateError(err); @@ -2397,7 +2556,7 @@ fn wasi_path_create_directory(userdata: ?*anyopaque, module: *ModuleInstance, pa if (Helpers.getMemorySlice(module, path_mem_offset, path_mem_length, &errno)) |path| { if (context.hasPathAccess(fd_info, path, &errno)) { const mode: std.posix.mode_t = if (builtin.os.tag == .windows) undefined else std.posix.S.IRWXU | std.posix.S.IRWXG | std.posix.S.IROTH; - std.posix.mkdirat(fd_info.fd, path, mode) catch |err| { + posix_compat.mkdirat(fd_info.fd, path, mode) catch |err| { errno = Errno.translateError(err); }; } @@ -2447,7 +2606,7 @@ fn wasi_path_filestat_get(userdata: ?*anyopaque, module: *ModuleInstance, params const mode: std.posix.mode_t = 644; if (std.posix.openat(fd_info.fd, path, flags, mode)) |fd_opened| { - defer std.posix.close(fd_opened); + defer posix_compat.close(fd_opened); const stat: std.os.wasi.filestat_t = Helpers.filestatGetPosix(fd_opened, &errno); if (errno == .SUCCESS) { @@ -2518,7 +2677,7 @@ fn wasi_path_remove_directory(userdata: ?*anyopaque, module: *ModuleInstance, pa if (context.hasPathAccess(fd_info, path, &errno)) { var static_path_buffer: [std.fs.max_path_bytes * 2]u8 = undefined; if (Helpers.resolvePath(fd_info, path, &static_path_buffer, &errno)) |resolved_path| { - std.posix.unlinkat(FD_OS_INVALID, resolved_path, std.posix.AT.REMOVEDIR) catch |err| { + posix_compat.unlinkat(FD_OS_INVALID, resolved_path, std.posix.AT.REMOVEDIR) catch |err| { errno = Errno.translateError(err); }; @@ -2574,7 +2733,7 @@ fn wasi_path_symlink(userdata: ?*anyopaque, module: *ModuleInstance, params: [*] } } } else { - std.posix.symlinkat(link_contents, fd_info.fd, link_path) catch |err| { + posix_compat.symlinkat(link_contents, fd_info.fd, link_path) catch |err| { errno = Errno.translateError(err); }; } @@ -2603,7 +2762,7 @@ fn wasi_path_unlink_file(userdata: ?*anyopaque, module: *ModuleInstance, params: if (context.hasPathAccess(fd_info, path, &errno)) { var static_path_buffer: [std.fs.max_path_bytes * 2]u8 = undefined; if (Helpers.resolvePath(fd_info, path, &static_path_buffer, &errno)) |resolved_path| { - std.posix.unlinkat(FD_OS_INVALID, resolved_path, 0) catch |err| { + posix_compat.unlinkat(FD_OS_INVALID, resolved_path, 0) catch |err| { errno = Errno.translateError(err); }; @@ -2628,7 +2787,7 @@ fn wasi_random_get(_: ?*anyopaque, module: *ModuleInstance, params: [*]const Val if (errno == .SUCCESS) { if (array_length > 0) { if (Helpers.getMemorySlice(module, array_begin_offset, array_length, &errno)) |mem| { - std.crypto.random.bytes(mem); + posix_compat.random_bytes(mem); } } } diff --git a/test/mem64/main.zig b/test/mem64/main.zig index eac4e6f..a3593d5 100644 --- a/test/mem64/main.zig +++ b/test/mem64/main.zig @@ -2,13 +2,12 @@ const std = @import("std"); const bytebox = @import("bytebox"); const Val = bytebox.Val; -pub fn main() !void { +pub fn main(process_init: std.process.Init) !void { std.debug.print("\nRunning mem64 test...\n", .{}); - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - var allocator: std.mem.Allocator = gpa.allocator(); + const allocator: std.mem.Allocator = process_init.gpa; - const wasm_data: []u8 = try std.fs.cwd().readFileAlloc(allocator, "zig-out/bin/memtest.wasm", 1024 * 512); + const wasm_data: []u8 = try std.Io.Dir.cwd().readFileAlloc(process_init.io, "zig-out/bin/memtest.wasm", allocator, .limited(1024 * 512)); defer allocator.free(wasm_data); const module_def = try bytebox.createModuleDefinition(allocator, .{}); diff --git a/test/wasi/bytebox_adapter.py b/test/wasi/bytebox_adapter.py index 70ab91f..22acd03 100644 --- a/test/wasi/bytebox_adapter.py +++ b/test/wasi/bytebox_adapter.py @@ -21,7 +21,8 @@ args = parser.parse_args() if args.version: - subprocess.run([BYTEBOX] + ["--version"]) + result = subprocess.run([BYTEBOX] + ["--version"], capture_output=True, text=True) + print(result.stdout.strip() if result.stdout.strip() else result.stderr.strip()) sys.exit(0) TEST_FILE = args.test_file diff --git a/test/wasm/main.zig b/test/wasm/main.zig index 83a975b..df37cd9 100644 --- a/test/wasm/main.zig +++ b/test/wasm/main.zig @@ -444,7 +444,7 @@ fn isSameError(err: anyerror, err_string: []const u8) bool { }; } -fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator) !std.array_list.Managed(Command) { +fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator, io: std.Io) !std.array_list.Managed(Command) { const Helpers = struct { fn parseAction(json_action: *std.json.Value, fallback_module: []const u8, _allocator: std.mem.Allocator) !Action { const json_type = json_action.object.getPtr("type").?; @@ -494,7 +494,7 @@ fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator) !std.array }; // print("json_path: {s}\n", .{json_path}); - const json_data = try std.fs.cwd().readFileAlloc(allocator, json_path, 1024 * 1024 * 8); + const json_data = try std.Io.Dir.cwd().readFileAlloc(io, json_path, allocator, .limited(1024 * 1024 * 8)); var parsed = try std.json.parseFromSlice(std.json.Value, allocator, json_data, .{}); var fallback_module: []const u8 = ""; @@ -748,10 +748,10 @@ fn makeSpectestImports(allocator: std.mem.Allocator) !bytebox.ModuleImportPackag return imports; } -fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOpts) !bool { +fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOpts, io: std.Io) !bool { var did_fail_any_test: bool = false; - var commands: std.array_list.Managed(Command) = try parseCommands(suite_path, allocator); + var commands: std.array_list.Managed(Command) = try parseCommands(suite_path, allocator, io); defer { for (commands.items) |*command| { command.deinit(allocator); @@ -855,8 +855,7 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp if (module.inst == null) { const module_path = try std.fs.path.join(allocator, &[_][]const u8{ suite_dir, module_filename }); - var cwd = std.fs.cwd(); - const module_data = try cwd.readFileAlloc(allocator, module_path, 1024 * 1024 * 8); + const module_data = try std.Io.Dir.cwd().readFileAlloc(io, module_path, allocator, .limited(1024 * 1024 * 8)); var decode_expected_error: ?[]const u8 = null; switch (command.*) { @@ -1121,12 +1120,14 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp }, f32, f64 => { const len = @typeInfo(VectorType).vector.len; + const expected_arr: [len]child_type = expected_typed; + const actual_arr: [len]child_type = actual_typed; var vec_i: u32 = 0; while (vec_i < len) : (vec_i += 1) { - if (std.math.isNan(expected_typed[vec_i])) { - is_equal = is_equal and std.math.isNan(actual_typed[vec_i]); + if (std.math.isNan(expected_arr[vec_i])) { + is_equal = is_equal and std.math.isNan(actual_arr[vec_i]); } else { - is_equal = is_equal and expected_typed[vec_i] == actual_typed[vec_i]; + is_equal = is_equal and expected_arr[vec_i] == actual_arr[vec_i]; } } }, @@ -1260,8 +1261,8 @@ pub fn parseVmType(backend_str: []const u8) VmType { } } -fn pathExists(path: []const u8) bool { - std.fs.cwd().access(path, .{ .mode = .read_only }) catch |e| { +fn pathExists(path: []const u8, io: std.Io) bool { + std.Io.Dir.cwd().access(io, path, .{}) catch |e| { return switch (e) { error.PermissionDenied, error.FileBusy, @@ -1276,7 +1277,6 @@ fn pathExists(path: []const u8) bool { error.SystemResources, error.BadPathName, error.SymLinkLoop, - error.InvalidUtf8, => false, else => false, }; @@ -1294,12 +1294,17 @@ fn getNextArg(args: []const []const u8, index: *usize, print_help: *bool) ?[]con return null; } -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - var allocator: std.mem.Allocator = gpa.allocator(); +pub fn main(process_init: std.process.Init) !void { + const allocator: std.mem.Allocator = process_init.gpa; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); + var args_list = std.ArrayList([:0]const u8).empty; + defer args_list.deinit(allocator); + var args_iter = std.process.Args.Iterator.init(process_init.minimal.args); + defer args_iter.deinit(); + while (args_iter.next()) |arg| { + try args_list.append(allocator, arg); + } + const args: []const [:0]const u8 = args_list.items; var opts = TestOpts{}; @@ -1576,7 +1581,7 @@ pub fn main() !void { const suite_wast_path = blk: { const is_64bit_arch = @sizeOf(usize) >= @sizeOf(u64); - if (is_64bit_arch and pathExists(suite_wast_mem64_path)) { + if (is_64bit_arch and pathExists(suite_wast_mem64_path, process_init.io)) { if (opts.log_suite) { print("Using memory64 for suite {s}\n", .{suite}); } @@ -1597,7 +1602,7 @@ pub fn main() !void { if (opts.force_wasm_regen_only) { needs_regen = true; } else { - needs_regen = pathExists(suite_path) == false; + needs_regen = pathExists(suite_path, process_init.io) == false; } if (needs_regen) { @@ -1613,23 +1618,24 @@ pub fn main() !void { const suite_wasm_folder: []const u8 = try std.fs.path.join(allocator, &[_][]const u8{ "test", "wasm", "wasm-generated", suite }); defer allocator.free(suite_wasm_folder); - std.fs.cwd().makeDir("test/wasm/wasm-generated") catch |e| { + std.Io.Dir.cwd().createDir(process_init.io, "test/wasm/wasm-generated", .default_dir) catch |e| { if (e != error.PathAlreadyExists) { return e; } }; - std.fs.cwd().makeDir(suite_wasm_folder) catch |e| { + std.Io.Dir.cwd().createDir(process_init.io, suite_wasm_folder, .default_dir) catch |e| { if (e != error.PathAlreadyExists) { return e; } }; - var process = std.process.Child.init(&[_][]const u8{ "wasm-tools", "json-from-wast", "--pretty", "-o", suite_json_filename, suite_wast_path_relative }, allocator); - - process.cwd = suite_wasm_folder; + var child = try std.process.spawn(process_init.io, .{ + .argv = &[_][]const u8{ "wasm-tools", "json-from-wast", "--pretty", "-o", suite_json_filename, suite_wast_path_relative }, + .cwd = .{ .path = suite_wasm_folder }, + }); - _ = try process.spawnAndWait(); + _ = try child.wait(process_init.io); } if (opts.force_wasm_regen_only == false) { @@ -1637,7 +1643,7 @@ pub fn main() !void { print("Running test suite: {s}\n", .{suite}); } - const success: bool = try run(allocator, suite_path, &opts); + const success: bool = try run(allocator, suite_path, &opts, process_init.io); did_all_succeed = did_all_succeed and success; if (success and opts.log_suite and !g_verbose_logging) { From 5db05b4243beff04464e2a6e6aebc582c10d8c17 Mon Sep 17 00:00:00 2001 From: "Luke Boswell (Linux-Desktop)" Date: Mon, 20 Apr 2026 12:32:15 +1000 Subject: [PATCH 5/6] ci: bump setup-zig version to 0.16.0 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51eca00..e6d7926 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.1 + version: 0.16.0 - name: Setup Python uses: actions/setup-python@v4 From d464d50776d0fe432329219acaed5d614b11cdb9 Mon Sep 17 00:00:00 2001 From: "Luke Boswell (Linux-Desktop)" Date: Mon, 20 Apr 2026 12:46:34 +1000 Subject: [PATCH 6/6] Fix Zig 0.16.0 compatibility and memory leaks - Update stable_array dependency to 0.16.0-compatible version - Rewrite posix_compat in wasi.zig to use raw Linux syscalls (std.os.linux) since std.c removed many wrappers on Linux in Zig 0.16.0 - Replace std.posix.ClockGetTimeError with local error type (removed in 0.16.0) - Use statx syscall for fstat on Linux (fstat removed from std.c on Linux) - Fix memory leaks in test harness (test/wasm/main.zig): free json_data, parsed, module_path, module_data; fix leaked dupes on reassignment; deinit skipped commands; properly cleanup spectest imports - Fix memory leak in Store.deinit: free element instance refs - Fix memory leaks in definition.zig: add errdefer guards for data/bytes arrays allocated during decode that could leak on error paths Co-Authored-By: Claude Opus 4.6 (1M context) --- build.zig.zon | 4 +- src/definition.zig | 19 ++- src/instance.zig | 4 + src/wasi.zig | 311 ++++++++++++++++++++++++++++++++++----------- test/wasm/main.zig | 25 +++- 5 files changed, 275 insertions(+), 88 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 8a7c27e..dfa38d9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,8 +4,8 @@ .fingerprint = 0x5a2a0eadb1367749, .dependencies = .{ .stable_array = .{ - .url = "git+https://github.com/lukewilliamboswell/zig-stable-array.git#98e6224ff7db9853cbd284ac199affd220617007", - .hash = "stable_array-0.1.0-3ihgvUVbAADlJAqXJOXZ2xEIHUY5-32i8d6fk7hraKju", + .url = "git+https://github.com/lukewilliamboswell/zig-stable-array.git#727b74bc223ddcedcf9201ec0952e57963649efd", + .hash = "stable_array-0.1.0-3ihgvTFbAAAduZIruCYsxVwc2nWci42weBHshok2iRhM", }, }, .minimum_zig_version = "0.16.0", diff --git a/src/definition.zig b/src/definition.zig index 78c4ad3..a87b5e7 100644 --- a/src/definition.zig +++ b/src/definition.zig @@ -750,6 +750,7 @@ pub const DataDefinition = struct { const num_bytes = try decodeLEB128(u32, reader); var bytes = std.array_list.Managed(u8).init(allocator); try bytes.resize(num_bytes); + errdefer bytes.deinit(); const num_read = try readBytes(reader, bytes.items); if (num_read != num_bytes) { return error.MalformedUnexpectedEnd; @@ -2924,13 +2925,16 @@ pub const ModuleDefinition = struct { const name_length: usize = reader.seek - section_start_pos; const data_length: usize = section_size_bytes - name_length; - try section.data.resize(data_length); - const data_length_read = reader.readSliceShort(section.data.items) catch return error.MalformedUnexpectedEnd; - if (data_length != data_length_read) { - return error.MalformedUnexpectedEnd; - } - try self.custom_sections.append(section); + { + errdefer section.data.deinit(); + try section.data.resize(data_length); + const data_length_read = reader.readSliceShort(section.data.items) catch return error.MalformedUnexpectedEnd; + if (data_length != data_length_read) { + return error.MalformedUnexpectedEnd; + } + try self.custom_sections.append(section); + } if (std.mem.eql(u8, section.name, "name")) { try self.name_section.decode(self, section.data.items); @@ -3526,7 +3530,8 @@ pub const ModuleDefinition = struct { var data_index: u32 = 0; while (data_index < num_datas) : (data_index += 1) { - const data = try DataDefinition.decode(reader, self, allocator); + var data = try DataDefinition.decode(reader, self, allocator); + errdefer data.bytes.deinit(); try self.datas.append(data); } }, diff --git a/src/instance.zig b/src/instance.zig index f59a0f5..efbd784 100644 --- a/src/instance.zig +++ b/src/instance.zig @@ -638,6 +638,10 @@ pub const Store = struct { self.memories.deinit(); self.globals.deinit(); + + for (self.elements.items) |*item| { + item.refs.deinit(); + } self.elements.deinit(); for (self.imports.functions.items) |*item| { diff --git a/src/wasi.zig b/src/wasi.zig index b33f403..c0e5d3e 100644 --- a/src/wasi.zig +++ b/src/wasi.zig @@ -4,14 +4,38 @@ const core = @import("core.zig"); const StringPool = @import("stringpool.zig"); -/// Compatibility wrappers for POSIX functions removed from std.posix in Zig 0.16. +/// Compatibility wrappers for POSIX functions. +/// On Linux, uses raw syscalls (std.os.linux) since std.c removed many wrappers in Zig 0.16. +/// On other platforms (macOS, etc.), uses std.c wrappers. const posix_compat = struct { const posix = std.posix; const c = std.c; + const linux = std.os.linux; + const is_linux = builtin.os.tag == .linux; const PosixError = error{ AccessDenied, DeviceBusy, DiskQuota, FileBusy, FileNotFound, FileTooBig, InputOutput, IsDir, LinkQuotaExceeded, NameTooLong, NoDevice, NoSpaceLeft, NotDir, PathAlreadyExists, ProcessFdQuotaExceeded, ReadOnlyFileSystem, SymLinkLoop, SystemFdQuotaExceeded, SystemResources, Unexpected, WouldBlock }; - fn mapErrno() PosixError { - return switch (@as(c.E, @enumFromInt(c._errno().*))) { + /// A platform-independent stat result containing the fields needed by WASI. + const StatResult = struct { + dev: u64, + ino: u64, + mode: std.posix.mode_t, + nlink: u64, + size: u64, + atim: posix.timespec, + mtim: posix.timespec, + ctim: posix.timespec, + }; + + fn mapLinuxErrno(rc: usize) PosixError { + return mapE(posix.errno(rc)); + } + + fn mapCErrno() PosixError { + return mapE(@enumFromInt(c._errno().*)); + } + + fn mapE(e: c.E) PosixError { + return switch (e) { .ACCES, .PERM => error.AccessDenied, .BUSY => error.DeviceBusy, .DQUOT => error.DiskQuota, @@ -37,94 +61,209 @@ const posix_compat = struct { } fn close(fd: posix.fd_t) void { - _ = c.close(fd); + if (is_linux) { + _ = linux.close(fd); + } else { + _ = c.close(fd); + } } fn fcntl(fd: posix.fd_t, cmd: c_int, arg: usize) PosixError!usize { - const rc = c.fcntl(fd, cmd, @as(c_int, @intCast(arg))); - if (rc == -1) return mapErrno(); - return @intCast(rc); - } - - fn fstat(fd: posix.fd_t) PosixError!posix.Stat { - var stat: posix.Stat = undefined; - const rc = c.fstat(fd, &stat); - if (rc != 0) return mapErrno(); - return stat; + if (is_linux) { + const rc = linux.fcntl(fd, cmd, arg); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return rc; + } else { + const rc = c.fcntl(fd, cmd, @as(c_int, @intCast(arg))); + if (rc == -1) return mapCErrno(); + return @intCast(rc); + } + } + + fn fstat(fd: posix.fd_t) PosixError!StatResult { + if (is_linux) { + var stx: linux.Statx = undefined; + const mask: linux.STATX = .{ + .TYPE = true, + .MODE = true, + .NLINK = true, + .INO = true, + .SIZE = true, + .ATIME = true, + .MTIME = true, + .CTIME = true, + }; + const rc = linux.statx(fd, "\x00", linux.AT.EMPTY_PATH, mask, &stx); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return StatResult{ + .dev = @as(u64, stx.dev_major) << 32 | stx.dev_minor, + .ino = stx.ino, + .mode = stx.mode, + .nlink = stx.nlink, + .size = stx.size, + .atim = .{ .sec = stx.atime.sec, .nsec = stx.atime.nsec }, + .mtim = .{ .sec = stx.mtime.sec, .nsec = stx.mtime.nsec }, + .ctim = .{ .sec = stx.ctime.sec, .nsec = stx.ctime.nsec }, + }; + } else { + var stat: c.Stat = undefined; + const rc = c.fstat(fd, &stat); + if (rc != 0) return mapCErrno(); + return StatResult{ + .dev = if (builtin.os.tag.isDarwin()) @as(u32, @bitCast(stat.dev)) else stat.dev, + .ino = stat.ino, + .mode = stat.mode, + .nlink = stat.nlink, + .size = if (std.math.cast(u64, stat.size)) |s| s else 0, + .atim = stat.atime(), + .mtim = stat.mtime(), + .ctim = stat.ctime(), + }; + } } fn futimens(fd: posix.fd_t, times: *const [2]posix.timespec) PosixError!void { - const rc = c.futimens(fd, times); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.futimens(fd, times); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.futimens(fd, times); + if (rc != 0) return mapCErrno(); + } } fn lseek_SET(fd: posix.fd_t, offset: u64) PosixError!void { - const rc = c.lseek(fd, @intCast(offset), c.SEEK.SET); - if (rc == -1) return mapErrno(); + if (is_linux) { + const rc = linux.lseek(fd, @intCast(offset), linux.SEEK.SET); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.lseek(fd, @intCast(offset), c.SEEK.SET); + if (rc == -1) return mapCErrno(); + } } fn lseek_CUR_get(fd: posix.fd_t) PosixError!u64 { - const rc = c.lseek(fd, 0, c.SEEK.CUR); - if (rc == -1) return mapErrno(); - return @intCast(rc); + if (is_linux) { + const rc = linux.lseek(fd, 0, linux.SEEK.CUR); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return rc; + } else { + const rc = c.lseek(fd, 0, c.SEEK.CUR); + if (rc == -1) return mapCErrno(); + return @intCast(rc); + } } fn lseek_CUR(fd: posix.fd_t, offset: i64) PosixError!void { - const rc = c.lseek(fd, @intCast(offset), c.SEEK.CUR); - if (rc == -1) return mapErrno(); + if (is_linux) { + const rc = linux.lseek(fd, offset, linux.SEEK.CUR); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.lseek(fd, @intCast(offset), c.SEEK.CUR); + if (rc == -1) return mapCErrno(); + } } fn lseek_END(fd: posix.fd_t, offset: i64) PosixError!void { - const rc = c.lseek(fd, @intCast(offset), c.SEEK.END); - if (rc == -1) return mapErrno(); + if (is_linux) { + const rc = linux.lseek(fd, offset, linux.SEEK.END); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.lseek(fd, @intCast(offset), c.SEEK.END); + if (rc == -1) return mapCErrno(); + } } fn readv(fd: posix.fd_t, iov: []posix.iovec) PosixError!usize { - const rc = c.readv(fd, @ptrCast(iov.ptr), @intCast(iov.len)); - if (rc == -1) return mapErrno(); - return @intCast(rc); + if (is_linux) { + const rc = linux.readv(fd, @ptrCast(iov.ptr), iov.len); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return rc; + } else { + const rc = c.readv(fd, @ptrCast(iov.ptr), @intCast(iov.len)); + if (rc == -1) return mapCErrno(); + return @intCast(rc); + } } fn preadv(fd: posix.fd_t, iov: []posix.iovec, offset: u64) PosixError!usize { - const rc = c.preadv(fd, @ptrCast(iov.ptr), @intCast(iov.len), @intCast(offset)); - if (rc == -1) return mapErrno(); - return @intCast(rc); + if (is_linux) { + const rc = linux.preadv(fd, @ptrCast(iov.ptr), iov.len, @intCast(offset)); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return rc; + } else { + const rc = c.preadv(fd, @ptrCast(iov.ptr), @intCast(iov.len), @intCast(offset)); + if (rc == -1) return mapCErrno(); + return @intCast(rc); + } } fn writev(fd: posix.fd_t, iov: []const posix.iovec_const) PosixError!usize { - const rc = c.writev(fd, @ptrCast(iov.ptr), @intCast(iov.len)); - if (rc == -1) return mapErrno(); - return @intCast(rc); + if (is_linux) { + const rc = linux.writev(fd, @ptrCast(iov.ptr), iov.len); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return rc; + } else { + const rc = c.writev(fd, @ptrCast(iov.ptr), @intCast(iov.len)); + if (rc == -1) return mapCErrno(); + return @intCast(rc); + } } fn pwritev(fd: posix.fd_t, iov: []const posix.iovec_const, offset: u64) PosixError!usize { - const rc = c.pwritev(fd, @ptrCast(iov.ptr), @intCast(iov.len), @intCast(offset)); - if (rc == -1) return mapErrno(); - return @intCast(rc); + if (is_linux) { + const rc = linux.pwritev(fd, @ptrCast(iov.ptr), iov.len, @intCast(offset)); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + return rc; + } else { + const rc = c.pwritev(fd, @ptrCast(iov.ptr), @intCast(iov.len), @intCast(offset)); + if (rc == -1) return mapCErrno(); + return @intCast(rc); + } } fn ftruncate(fd: posix.fd_t, length: u64) PosixError!void { - const rc = c.ftruncate(fd, @intCast(length)); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.ftruncate(fd, @intCast(length)); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.ftruncate(fd, @intCast(length)); + if (rc != 0) return mapCErrno(); + } } fn mkdirat(dirfd: posix.fd_t, path: anytype, mode: posix.mode_t) PosixError!void { const p = toPosixPath(path); - const rc = c.mkdirat(dirfd, &p, mode); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.mkdirat(dirfd, &p, mode); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.mkdirat(dirfd, &p, mode); + if (rc != 0) return mapCErrno(); + } } fn unlinkat(dirfd: posix.fd_t, path: anytype, flags: u32) PosixError!void { const p = toPosixPath(path); - const rc = c.unlinkat(dirfd, &p, @intCast(flags)); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.unlinkat(dirfd, &p, flags); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.unlinkat(dirfd, &p, @intCast(flags)); + if (rc != 0) return mapCErrno(); + } } fn symlinkat(target: anytype, dirfd: posix.fd_t, linkpath: anytype) PosixError!void { const t = toPosixPath(target); const l = toPosixPath(linkpath); - const rc = c.symlinkat(&t, dirfd, &l); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.symlinkat(&t, dirfd, &l); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.symlinkat(&t, dirfd, &l); + if (rc != 0) return mapCErrno(); + } } fn toPosixPath(path: anytype) [posix.PATH_MAX - 1:0]u8 { @@ -136,27 +275,62 @@ const posix_compat = struct { fn clock_getres(clk_id: posix.clockid_t) PosixError!posix.timespec { var ts: posix.timespec = undefined; - const rc = c.clock_getres(clk_id, &ts); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.clock_getres(clk_id, &ts); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.clock_getres(clk_id, &ts); + if (rc != 0) return mapCErrno(); + } return ts; } fn clock_gettime(clk_id: posix.clockid_t) PosixError!posix.timespec { var ts: posix.timespec = undefined; - const rc = c.clock_gettime(clk_id, &ts); - if (rc != 0) return mapErrno(); + if (is_linux) { + const rc = linux.clock_gettime(clk_id, &ts); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + } else { + const rc = c.clock_gettime(clk_id, &ts); + if (rc != 0) return mapCErrno(); + } return ts; } fn open(path: anytype, flags: std.posix.O, mode: posix.mode_t) PosixError!posix.fd_t { const p = toPosixPath(path); - const rc = c.open(&p, flags, mode); - if (rc == -1) return mapErrno(); - return rc; + if (is_linux) { + const rc = linux.open(&p, flags, mode); + const err = posix.errno(rc); + if (err != .SUCCESS) return mapLinuxErrno(rc); + return @intCast(rc); + } else { + const rc = c.open(&p, flags, mode); + if (rc == -1) return mapCErrno(); + return rc; + } } fn random_bytes(buf: []u8) void { - c.arc4random_buf(buf.ptr, buf.len); + if (is_linux) { + // Use getrandom syscall on Linux (arc4random_buf may not be available) + _ = linux.getrandom(buf.ptr, buf.len, 0); + } else { + c.arc4random_buf(buf.ptr, buf.len); + } + } + + fn getcwd(buffer: []u8) PosixError![]const u8 { + if (is_linux) { + const rc = linux.getcwd(buffer.ptr, buffer.len); + if (posix.errno(rc) != .SUCCESS) return mapLinuxErrno(rc); + const len = std.mem.indexOfScalar(u8, buffer, 0) orelse buffer.len; + return buffer[0..len]; + } else { + const cwd_ptr = c.getcwd(buffer.ptr, buffer.len) orelse return mapCErrno(); + const len = std.mem.indexOfScalar(u8, cwd_ptr[0..buffer.len], 0) orelse buffer.len; + return cwd_ptr[0..len]; + } } }; @@ -204,9 +378,8 @@ const WasiContext = struct { { var cwd_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; - const cwd_ptr = std.c.getcwd(&cwd_buffer, cwd_buffer.len) orelse return error.Unexpected; - const cwd_len = std.mem.indexOfScalar(u8, cwd_ptr[0..cwd_buffer.len], 0) orelse cwd_buffer.len; - context.cwd = try context.strings.put(cwd_ptr[0..cwd_len]); + const cwd = posix_compat.getcwd(&cwd_buffer) catch return error.Unexpected; + context.cwd = try context.strings.put(cwd); } if (opts.argv) |argv| { @@ -792,13 +965,13 @@ const WindowsApi = struct { const Linux = struct { const clockid_t = std.posix.clockid_t; const timespec = std.posix.timespec; - // copy of std.os.linux function, but with a bugfix for the system.clock_getres call. Delete and replace - // with the fixed version in a future update - pub fn clock_getres(clock_id: clockid_t, res: *timespec) std.posix.ClockGetTimeError!void { - switch (std.posix.errno(std.posix.system.clock_getres(@intCast(@intFromEnum(clock_id)), res))) { + const ClockError = error{UnsupportedClock} || std.posix.UnexpectedError; + pub fn clock_getres(clock_id: clockid_t, res: *timespec) ClockError!void { + const rc = std.os.linux.clock_getres(clock_id, res); + switch (std.posix.errno(rc)) { .SUCCESS => return, .FAULT => unreachable, - .INVAL => return std.posix.ClockGetTimeError.UnsupportedClock, + .INVAL => return error.UnsupportedClock, else => |err| return std.posix.unexpectedErrno(err), } } @@ -1404,20 +1577,14 @@ const Helpers = struct { var stat_wasi: std.os.wasi.filestat_t = undefined; if (posix_compat.fstat(fd)) |stat| { - stat_wasi.dev = if (builtin.os.tag == .macos) @as(u32, @bitCast(stat.dev)) else stat.dev; + stat_wasi.dev = stat.dev; stat_wasi.ino = stat.ino; stat_wasi.filetype = posixModeToWasiFiletype(stat.mode); stat_wasi.nlink = stat.nlink; - stat_wasi.size = if (std.math.cast(u64, stat.size)) |s| s else 0; - if (builtin.os.tag == .macos) { - stat_wasi.atim = posixTimespecToWasi(stat.atimespec); - stat_wasi.mtim = posixTimespecToWasi(stat.mtimespec); - stat_wasi.ctim = posixTimespecToWasi(stat.ctimespec); - } else { - stat_wasi.atim = posixTimespecToWasi(stat.atim); - stat_wasi.mtim = posixTimespecToWasi(stat.mtim); - stat_wasi.ctim = posixTimespecToWasi(stat.ctim); - } + stat_wasi.size = stat.size; + stat_wasi.atim = posixTimespecToWasi(stat.atim); + stat_wasi.mtim = posixTimespecToWasi(stat.mtim); + stat_wasi.ctim = posixTimespecToWasi(stat.ctim); } else |err| { errno.* = Errno.translateError(err); } diff --git a/test/wasm/main.zig b/test/wasm/main.zig index df37cd9..d3a2ec9 100644 --- a/test/wasm/main.zig +++ b/test/wasm/main.zig @@ -471,6 +471,7 @@ fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator, io: std.Io var module: []const u8 = try _allocator.dupe(u8, fallback_module); const json_module_or_null = json_action.object.getPtr("module"); if (json_module_or_null) |json_module| { + _allocator.free(module); module = try _allocator.dupe(u8, json_module.string); } @@ -495,9 +496,11 @@ fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator, io: std.Io // print("json_path: {s}\n", .{json_path}); const json_data = try std.Io.Dir.cwd().readFileAlloc(io, json_path, allocator, .limited(1024 * 1024 * 8)); + defer allocator.free(json_data); var parsed = try std.json.parseFromSlice(std.json.Value, allocator, json_data, .{}); + defer parsed.deinit(); - var fallback_module: []const u8 = ""; + var fallback_module: []const u8 = try allocator.dupe(u8, ""); defer allocator.free(fallback_module); var commands = std.array_list.Managed(Command).init(allocator); @@ -509,10 +512,12 @@ fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator, io: std.Io if (strcmp("module", json_command_type.string)) { const json_filename = json_command.object.getPtr("filename").?; const filename: []const u8 = try allocator.dupe(u8, json_filename.string); + allocator.free(fallback_module); fallback_module = filename; var name = try allocator.dupe(u8, filename); if (json_command.object.getPtr("name")) |json_module_name| { + allocator.free(name); name = try allocator.dupe(u8, json_module_name.string); } @@ -578,22 +583,26 @@ fn parseCommands(json_path: []const u8, allocator: std.mem.Allocator, io: std.Io }; try commands.append(command); } else if (strcmp("assert_malformed", json_command_type.string)) { - const command = Command{ + var command = Command{ .AssertMalformed = CommandAssertMalformed{ .err = try Helpers.parseBadModuleError(&json_command, allocator), }, }; if (std.mem.endsWith(u8, command.AssertMalformed.err.module, ".wasm")) { try commands.append(command); + } else { + command.deinit(allocator); } } else if (strcmp("assert_invalid", json_command_type.string)) { - const command = Command{ + var command = Command{ .AssertInvalid = CommandAssertInvalid{ .err = try Helpers.parseBadModuleError(&json_command, allocator), }, }; if (std.mem.endsWith(u8, command.AssertInvalid.err.module, ".wasm")) { try commands.append(command); + } else { + command.deinit(allocator); } } else if (strcmp("assert_unlinkable", json_command_type.string)) { const command = Command{ @@ -785,22 +794,22 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp // NOTE this shares the same copies of the import arrays, since the modules must share instances var imports = std.array_list.Managed(bytebox.ModuleImportPackage).init(allocator); defer { - const spectest_imports = imports.items[0]; + // Destroy host-created objects before deinit frees the names/arrays + var spectest_imports = &imports.items[0]; for (spectest_imports.tables.items) |*item| { - allocator.free(item.name); item.data.Host.deinit(); allocator.destroy(item.data.Host); } for (spectest_imports.memories.items) |*item| { - allocator.free(item.name); item.data.Host.deinit(); allocator.destroy(item.data.Host); } for (spectest_imports.globals.items) |*item| { - allocator.free(item.name); allocator.destroy(item.data.Host.def); allocator.destroy(item.data.Host); } + // deinit frees names, type lists, and backing arrays + spectest_imports.deinit(); for (imports.items[1..]) |*item| { item.deinit(); @@ -854,8 +863,10 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp if (module.inst == null) { const module_path = try std.fs.path.join(allocator, &[_][]const u8{ suite_dir, module_filename }); + defer allocator.free(module_path); const module_data = try std.Io.Dir.cwd().readFileAlloc(io, module_path, allocator, .limited(1024 * 1024 * 8)); + defer allocator.free(module_data); var decode_expected_error: ?[]const u8 = null; switch (command.*) {