From dda065dbb73ddd326102cb78001f6be93c3f006c Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 17:18:54 -0500 Subject: [PATCH 1/8] fix: Update API calls for Zig 0.15.2 compatibility - ArrayHashMap.put() now requires allocator as first parameter - ArrayList.append() now requires allocator as first parameter These changes are required for Zig 0.15.2 which changed the API for managed data structures to take the allocator explicitly. --- src/instance.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/instance.zig b/src/instance.zig index d252a5a3..eb2bb729 100644 --- a/src/instance.zig +++ b/src/instance.zig @@ -455,7 +455,7 @@ pub const Instance = struct { } pub fn addWasiPreopen(self: *Instance, wasi_fd: wasi.fd_t, name: []const u8, host_fd: posix.fd_t) !void { - return self.wasi_preopens.put(wasi_fd, .{ + return self.wasi_preopens.put(self.alloc, wasi_fd, .{ .wasi_fd = wasi_fd, .name = name, .host_fd = host_fd, @@ -475,7 +475,7 @@ pub const Instance = struct { const args = try std.process.argsAlloc(alloc); for (args) |arg| { - try self.wasi_args.append(arg); + try self.wasi_args.append(alloc, arg); } return args; From 05d6a6297b2c442dad3d6255c44b13bacee1279a Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 17:21:11 -0500 Subject: [PATCH 2/8] feat(wasi): add fd_tell and fd_readdir implementations - fd_tell: Get current file offset using cross-platform posix.lseek_CUR_get - fd_readdir: Read directory entries with WASI dirent format Platform support for fd_readdir: - Linux: Implemented using getdents64 syscall (no libc required) - macOS/iOS/tvOS/watchOS/visionOS: Implemented using getdirentries via libc - FreeBSD/OpenBSD/NetBSD/DragonFly: Implemented using getdents via libc - Windows: Compile-time error (TODO: implement with NtQueryDirectoryFile) - Other platforms: Compile-time error These are required for running CPython 3.13 WASM builds. --- src/wasi/wasi.zig | 246 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index 56b58661..e68d9b83 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -1,9 +1,11 @@ const std = @import("std"); +const builtin = @import("builtin"); const mem = std.mem; const fs = std.fs; const posix = std.posix; const math = std.math; const wasi = std.os.wasi; +const native_os = builtin.os.tag; const VirtualMachine = @import("../instance/vm.zig").VirtualMachine; const WasmError = @import("../instance/vm.zig").WasmError; @@ -447,3 +449,247 @@ fn toWasiTimestamp(ns: i128) u64 { } const _ = std.testing.refAllDecls(); + +/// fd_tell - Get the current offset of a file descriptor +/// Returns the current position within the file +pub fn fd_tell(vm: *VirtualMachine) WasmError!void { + const offset_ptr = vm.popOperand(u32); + const fd = vm.popOperand(i32); + + const host_fd = vm.getHostFd(fd); + const current_offset = posix.lseek_CUR_get(host_fd) catch |err| { + try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); + return; + }; + + const memory = try vm.inst.getMemory(0); + try memory.write(u64, offset_ptr, 0, current_offset); + + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); +} + +/// fd_readdir - Read directory entries from a directory file descriptor +/// Reads directory entries into the provided buffer using the cookie for pagination. +/// The cookie value 0 starts from the beginning; subsequent calls use the d_next +/// value from the last entry to continue reading. +/// +/// WASI dirent structure (24 bytes header + variable name): +/// d_next: u64 (offset 0) - Cookie for next entry +/// d_ino: u64 (offset 8) - Inode number +/// d_namlen: u32 (offset 16) - Length of the name +/// d_type: u8 (offset 20) - File type +/// padding: 3 bytes (offset 21-23) +/// name: [d_namlen]u8 (offset 24) - Name (not null-terminated) +/// fd_readdir - Read directory entries from a directory file descriptor +/// Reads directory entries into the provided buffer using the cookie for pagination. +/// The cookie value 0 starts from the beginning; subsequent calls use the d_next +/// value from the last entry to continue reading. +/// +/// WASI dirent structure (24 bytes header + variable name): +/// d_next: u64 (offset 0) - Cookie for next entry +/// d_ino: u64 (offset 8) - Inode number +/// d_namlen: u32 (offset 16) - Length of the name +/// d_type: u8 (offset 20) - File type +/// padding: 3 bytes (offset 21-23) +/// name: [d_namlen]u8 (offset 24) - Name (not null-terminated) +/// +/// Platform support: +/// - Linux: getdents64 syscall (no libc required) +/// - macOS/iOS/BSD: getdirentries/getdents via std.c (requires libc) +/// - Windows: Not yet implemented +pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { + const bufused_ptr = vm.popOperand(u32); + const cookie = vm.popOperand(u64); + const buf_len = vm.popOperand(u32); + const buf_ptr = vm.popOperand(u32); + const fd = vm.popOperand(i32); + + const memory = try vm.inst.getMemory(0); + const mem_data = memory.memory(); + const host_fd = vm.getHostFd(fd); + + switch (native_os) { + .linux => { + // Linux implementation using getdents64 syscall (no libc needed) + _ = std.os.linux.lseek(host_fd, 0, std.os.linux.SEEK.SET); + + var entry_idx: u64 = 0; + var bytes_used: u32 = 0; + + while (true) { + var kernel_buf: [8192]u8 = undefined; + const nread = std.os.linux.getdents64(host_fd, &kernel_buf, kernel_buf.len); + if (nread == 0) break; + if (@as(isize, @bitCast(nread)) < 0) break; + + var kernel_offset: usize = 0; + while (kernel_offset < nread) { + const linux_entry: *align(1) std.os.linux.dirent64 = @ptrCast(&kernel_buf[kernel_offset]); + const d_ino = linux_entry.ino; + const d_reclen = linux_entry.reclen; + const d_type = linux_entry.type; + const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); + + kernel_offset += d_reclen; + + // Skip . and .. + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; + + // Skip entries before the cookie position + if (entry_idx < cookie) { + entry_idx += 1; + continue; + } + + // WASI dirent header is 24 bytes + const wasi_dirent_header_size: u32 = 24; + const wasi_entry_size: u32 = wasi_dirent_header_size + @as(u32, @intCast(name.len)); + + if (bytes_used + wasi_entry_size > buf_len) { + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + return; + } + + const wasm_offset = buf_ptr + bytes_used; + try memory.write(u64, wasm_offset, 0, entry_idx + 1); // d_next + try memory.write(u64, wasm_offset + 8, 0, d_ino); // d_ino + try memory.write(u32, wasm_offset + 16, 0, @as(u32, @intCast(name.len))); // d_namlen + mem_data[wasm_offset + 20] = d_type; // d_type + @memcpy(mem_data[wasm_offset + wasi_dirent_header_size ..][0..name.len], name); + + bytes_used += wasi_entry_size; + entry_idx += 1; + } + } + + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + }, + + .macos, .ios, .tvos, .watchos, .visionos => { + // Darwin implementation using getdirentries via libc + posix.lseek_SET(host_fd, 0) catch { + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); + return; + }; + + var entry_idx: u64 = 0; + var bytes_used: u32 = 0; + var seek: i64 = 0; + + while (true) { + var kernel_buf: [8192]u8 align(@alignOf(std.c.dirent)) = undefined; + const nread = std.c.getdirentries(host_fd, &kernel_buf, kernel_buf.len, &seek); + if (nread <= 0) break; + + var kernel_offset: usize = 0; + while (kernel_offset < @as(usize, @intCast(nread))) { + const entry: *align(1) std.c.dirent = @ptrCast(&kernel_buf[kernel_offset]); + const d_ino: u64 = entry.ino; + const d_reclen: u16 = entry.reclen; + const name = @as([*]const u8, @ptrCast(&entry.name))[0..entry.namlen]; + const d_type: u8 = entry.type; + + kernel_offset += d_reclen; + + // Skip . and .. + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; + + if (entry_idx < cookie) { + entry_idx += 1; + continue; + } + + const wasi_dirent_header_size: u32 = 24; + const wasi_entry_size: u32 = wasi_dirent_header_size + @as(u32, @intCast(name.len)); + + if (bytes_used + wasi_entry_size > buf_len) { + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + return; + } + + const wasm_offset = buf_ptr + bytes_used; + try memory.write(u64, wasm_offset, 0, entry_idx + 1); + try memory.write(u64, wasm_offset + 8, 0, d_ino); + try memory.write(u32, wasm_offset + 16, 0, @as(u32, @intCast(name.len))); + mem_data[wasm_offset + 20] = d_type; + @memcpy(mem_data[wasm_offset + wasi_dirent_header_size ..][0..name.len], name); + + bytes_used += wasi_entry_size; + entry_idx += 1; + } + } + + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + }, + + .freebsd, .openbsd, .netbsd, .dragonfly => { + // BSD implementation using getdents via libc + posix.lseek_SET(host_fd, 0) catch { + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); + return; + }; + + var entry_idx: u64 = 0; + var bytes_used: u32 = 0; + + while (true) { + var kernel_buf: [8192]u8 align(@alignOf(std.c.dirent)) = undefined; + const nread = std.c.getdents(host_fd, &kernel_buf, kernel_buf.len); + if (nread <= 0) break; + + var kernel_offset: usize = 0; + while (kernel_offset < @as(usize, @intCast(nread))) { + const entry: *align(1) std.c.dirent = @ptrCast(&kernel_buf[kernel_offset]); + const d_ino: u64 = entry.fileno; + const d_reclen: u16 = entry.reclen; + const name = @as([*]const u8, @ptrCast(&entry.name))[0..entry.namlen]; + const d_type: u8 = entry.type; + + kernel_offset += d_reclen; + + // Skip . and .. + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; + + if (entry_idx < cookie) { + entry_idx += 1; + continue; + } + + const wasi_dirent_header_size: u32 = 24; + const wasi_entry_size: u32 = wasi_dirent_header_size + @as(u32, @intCast(name.len)); + + if (bytes_used + wasi_entry_size > buf_len) { + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + return; + } + + const wasm_offset = buf_ptr + bytes_used; + try memory.write(u64, wasm_offset, 0, entry_idx + 1); + try memory.write(u64, wasm_offset + 8, 0, d_ino); + try memory.write(u32, wasm_offset + 16, 0, @as(u32, @intCast(name.len))); + mem_data[wasm_offset + 20] = d_type; + @memcpy(mem_data[wasm_offset + wasi_dirent_header_size ..][0..name.len], name); + + bytes_used += wasi_entry_size; + entry_idx += 1; + } + } + + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + }, + + .windows => { + @compileError("fd_readdir not yet implemented for Windows - requires NtQueryDirectoryFile or FindFirstFile/FindNextFile"); + }, + + else => { + @compileError("fd_readdir not implemented for this platform"); + }, + } +} From ae950e69464d24cc3689867c1be43d440e32f6f7 Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 18:03:12 -0500 Subject: [PATCH 3/8] Refactor fd_readdir to use std.os.wasi types and add Windows support Changes: - Use @sizeOf(wasi.dirent_t) instead of hardcoded 24 for WASI dirent header size - Add toWasiFiletype() helper to convert native DT_* values to wasi.filetype_t enum - Add writeWasiDirent() helper for spec-compliant WASI dirent structure writes - Implement Windows fd_readdir using NtQueryDirectoryFile API: - Uses FILE_BOTH_DIR_INFORMATION for directory enumeration - Converts UTF-16 filenames to UTF-8 - Maps FILE_ATTRIBUTE_* to wasi.filetype_t - Uses FileIndex as pseudo-inode (Windows has no true inodes) - Update doc comments to reference std.os.wasi type definitions Platform support: - Linux: getdents64 syscall (no libc) - macOS/iOS/tvOS/watchOS/visionOS: getdirentries via std.c - FreeBSD/OpenBSD/NetBSD/DragonFly: getdents via std.c - Windows: NtQueryDirectoryFile via std.os.windows - Other platforms: @compileError --- src/wasi/wasi.zig | 234 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 176 insertions(+), 58 deletions(-) diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index e68d9b83..e34295b0 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -468,35 +468,64 @@ pub fn fd_tell(vm: *VirtualMachine) WasmError!void { try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); } +/// Convert native directory entry type to WASI filetype +fn toWasiFiletype(native_type: u8) wasi.filetype_t { + return switch (native_type) { + std.posix.DT.BLK => .BLOCK_DEVICE, + std.posix.DT.CHR => .CHARACTER_DEVICE, + std.posix.DT.DIR => .DIRECTORY, + std.posix.DT.LNK => .SYMBOLIC_LINK, + std.posix.DT.REG => .REGULAR_FILE, + std.posix.DT.SOCK => .SOCKET_STREAM, + std.posix.DT.FIFO => .UNKNOWN, // WASI has no FIFO type + else => .UNKNOWN, + }; +} + +/// Write a WASI dirent entry to memory +/// Uses std.os.wasi.dirent_t for spec-compliant structure layout +fn writeWasiDirent( + mem_data: []u8, + memory: anytype, + offset: u32, + next_cookie: u64, + inode: u64, + name: []const u8, + filetype: wasi.filetype_t, +) !void { + // WASI dirent_t structure (from std.os.wasi): + // next: dircookie_t (u64) - offset 0 + // ino: inode_t (u64) - offset 8 + // namlen: dirnamlen_t (u32) - offset 16 + // type: filetype_t (u8) - offset 20 + // [3 bytes padding to align to 24 bytes] + // name follows immediately after (not null-terminated) + try memory.write(u64, offset, 0, next_cookie); + try memory.write(u64, offset + 8, 0, inode); + try memory.write(u32, offset + 16, 0, @as(u32, @intCast(name.len))); + mem_data[offset + 20] = @intFromEnum(filetype); + // Padding bytes 21-23 are implicitly handled + const name_offset = offset + @sizeOf(wasi.dirent_t); + @memcpy(mem_data[name_offset..][0..name.len], name); +} + /// fd_readdir - Read directory entries from a directory file descriptor /// Reads directory entries into the provided buffer using the cookie for pagination. /// The cookie value 0 starts from the beginning; subsequent calls use the d_next /// value from the last entry to continue reading. /// -/// WASI dirent structure (24 bytes header + variable name): -/// d_next: u64 (offset 0) - Cookie for next entry -/// d_ino: u64 (offset 8) - Inode number -/// d_namlen: u32 (offset 16) - Length of the name -/// d_type: u8 (offset 20) - File type -/// padding: 3 bytes (offset 21-23) -/// name: [d_namlen]u8 (offset 24) - Name (not null-terminated) -/// fd_readdir - Read directory entries from a directory file descriptor -/// Reads directory entries into the provided buffer using the cookie for pagination. -/// The cookie value 0 starts from the beginning; subsequent calls use the d_next -/// value from the last entry to continue reading. -/// -/// WASI dirent structure (24 bytes header + variable name): -/// d_next: u64 (offset 0) - Cookie for next entry -/// d_ino: u64 (offset 8) - Inode number -/// d_namlen: u32 (offset 16) - Length of the name -/// d_type: u8 (offset 20) - File type -/// padding: 3 bytes (offset 21-23) -/// name: [d_namlen]u8 (offset 24) - Name (not null-terminated) +/// Uses std.os.wasi types for spec-compliant WASI dirent structure: +/// next: dircookie_t (u64) - Cookie for next entry +/// ino: inode_t (u64) - Inode number +/// namlen: dirnamlen_t (u32) - Length of the name +/// type: filetype_t (u8) - File type +/// [3 bytes padding] +/// name: [namlen]u8 - Name (not null-terminated) /// /// Platform support: /// - Linux: getdents64 syscall (no libc required) /// - macOS/iOS/BSD: getdirentries/getdents via std.c (requires libc) -/// - Windows: Not yet implemented +/// - Windows: NtQueryDirectoryFile via std.os.windows pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { const bufused_ptr = vm.popOperand(u32); const cookie = vm.popOperand(u64); @@ -508,6 +537,9 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { const mem_data = memory.memory(); const host_fd = vm.getHostFd(fd); + // Size of WASI dirent header (from std.os.wasi.dirent_t) + const dirent_header_size: u32 = @sizeOf(wasi.dirent_t); + switch (native_os) { .linux => { // Linux implementation using getdents64 syscall (no libc needed) @@ -525,12 +557,9 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { var kernel_offset: usize = 0; while (kernel_offset < nread) { const linux_entry: *align(1) std.os.linux.dirent64 = @ptrCast(&kernel_buf[kernel_offset]); - const d_ino = linux_entry.ino; - const d_reclen = linux_entry.reclen; - const d_type = linux_entry.type; const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); - kernel_offset += d_reclen; + kernel_offset += linux_entry.reclen; // Skip . and .. if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; @@ -541,9 +570,7 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { continue; } - // WASI dirent header is 24 bytes - const wasi_dirent_header_size: u32 = 24; - const wasi_entry_size: u32 = wasi_dirent_header_size + @as(u32, @intCast(name.len)); + const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); if (bytes_used + wasi_entry_size > buf_len) { try memory.write(u32, bufused_ptr, 0, bytes_used); @@ -551,12 +578,15 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { return; } - const wasm_offset = buf_ptr + bytes_used; - try memory.write(u64, wasm_offset, 0, entry_idx + 1); // d_next - try memory.write(u64, wasm_offset + 8, 0, d_ino); // d_ino - try memory.write(u32, wasm_offset + 16, 0, @as(u32, @intCast(name.len))); // d_namlen - mem_data[wasm_offset + 20] = d_type; // d_type - @memcpy(mem_data[wasm_offset + wasi_dirent_header_size ..][0..name.len], name); + try writeWasiDirent( + mem_data, + memory, + buf_ptr + bytes_used, + entry_idx + 1, + linux_entry.ino, + name, + toWasiFiletype(linux_entry.type), + ); bytes_used += wasi_entry_size; entry_idx += 1; @@ -586,12 +616,9 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { var kernel_offset: usize = 0; while (kernel_offset < @as(usize, @intCast(nread))) { const entry: *align(1) std.c.dirent = @ptrCast(&kernel_buf[kernel_offset]); - const d_ino: u64 = entry.ino; - const d_reclen: u16 = entry.reclen; const name = @as([*]const u8, @ptrCast(&entry.name))[0..entry.namlen]; - const d_type: u8 = entry.type; - kernel_offset += d_reclen; + kernel_offset += entry.reclen; // Skip . and .. if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; @@ -601,8 +628,7 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { continue; } - const wasi_dirent_header_size: u32 = 24; - const wasi_entry_size: u32 = wasi_dirent_header_size + @as(u32, @intCast(name.len)); + const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); if (bytes_used + wasi_entry_size > buf_len) { try memory.write(u32, bufused_ptr, 0, bytes_used); @@ -610,12 +636,15 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { return; } - const wasm_offset = buf_ptr + bytes_used; - try memory.write(u64, wasm_offset, 0, entry_idx + 1); - try memory.write(u64, wasm_offset + 8, 0, d_ino); - try memory.write(u32, wasm_offset + 16, 0, @as(u32, @intCast(name.len))); - mem_data[wasm_offset + 20] = d_type; - @memcpy(mem_data[wasm_offset + wasi_dirent_header_size ..][0..name.len], name); + try writeWasiDirent( + mem_data, + memory, + buf_ptr + bytes_used, + entry_idx + 1, + entry.ino, + name, + toWasiFiletype(entry.type), + ); bytes_used += wasi_entry_size; entry_idx += 1; @@ -644,12 +673,9 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { var kernel_offset: usize = 0; while (kernel_offset < @as(usize, @intCast(nread))) { const entry: *align(1) std.c.dirent = @ptrCast(&kernel_buf[kernel_offset]); - const d_ino: u64 = entry.fileno; - const d_reclen: u16 = entry.reclen; const name = @as([*]const u8, @ptrCast(&entry.name))[0..entry.namlen]; - const d_type: u8 = entry.type; - kernel_offset += d_reclen; + kernel_offset += entry.reclen; // Skip . and .. if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; @@ -659,8 +685,7 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { continue; } - const wasi_dirent_header_size: u32 = 24; - const wasi_entry_size: u32 = wasi_dirent_header_size + @as(u32, @intCast(name.len)); + const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); if (bytes_used + wasi_entry_size > buf_len) { try memory.write(u32, bufused_ptr, 0, bytes_used); @@ -668,12 +693,15 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { return; } - const wasm_offset = buf_ptr + bytes_used; - try memory.write(u64, wasm_offset, 0, entry_idx + 1); - try memory.write(u64, wasm_offset + 8, 0, d_ino); - try memory.write(u32, wasm_offset + 16, 0, @as(u32, @intCast(name.len))); - mem_data[wasm_offset + 20] = d_type; - @memcpy(mem_data[wasm_offset + wasi_dirent_header_size ..][0..name.len], name); + try writeWasiDirent( + mem_data, + memory, + buf_ptr + bytes_used, + entry_idx + 1, + entry.fileno, + name, + toWasiFiletype(entry.type), + ); bytes_used += wasi_entry_size; entry_idx += 1; @@ -685,7 +713,97 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { }, .windows => { - @compileError("fd_readdir not yet implemented for Windows - requires NtQueryDirectoryFile or FindFirstFile/FindNextFile"); + // Windows implementation using NtQueryDirectoryFile + const w = std.os.windows; + + var entry_idx: u64 = 0; + var bytes_used: u32 = 0; + var first_iter = true; + + while (true) { + var io: w.IO_STATUS_BLOCK = undefined; + var dir_buf: [4096]u8 align(@alignOf(w.FILE_BOTH_DIR_INFORMATION)) = undefined; + + const rc = w.ntdll.NtQueryDirectoryFile( + @ptrFromInt(@as(usize, @bitCast(@as(isize, host_fd)))), + null, + null, + null, + &io, + &dir_buf, + dir_buf.len, + .FileBothDirectoryInformation, + w.FALSE, // ReturnSingleEntry + null, // FileName filter + if (first_iter) w.TRUE else w.FALSE, // RestartScan + ); + first_iter = false; + + if (rc != .SUCCESS) break; + if (io.Information == 0) break; + + var buf_offset: usize = 0; + while (buf_offset < io.Information) { + const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&dir_buf[buf_offset])); + + // Get name as UTF-8 + const name_wtf16 = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; + var name_buf: [256]u8 = undefined; + const name_len = std.unicode.wtf16LeToWtf8(&name_buf, name_wtf16); + const name = name_buf[0..name_len]; + + // Move to next entry + if (dir_info.NextEntryOffset != 0) { + buf_offset += dir_info.NextEntryOffset; + } else { + buf_offset = io.Information; // Force exit + } + + // Skip . and .. + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; + + // Skip entries before cookie + if (entry_idx < cookie) { + entry_idx += 1; + continue; + } + + // Determine file type + const filetype: wasi.filetype_t = blk: { + const attrs = dir_info.FileAttributes; + if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .DIRECTORY; + if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .SYMBOLIC_LINK; + break :blk .REGULAR_FILE; + }; + + const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); + + if (bytes_used + wasi_entry_size > buf_len) { + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); + return; + } + + // Windows doesn't have inodes, use FileIndex as a pseudo-inode + const pseudo_inode: u64 = @as(u64, dir_info.FileIndex); + + try writeWasiDirent( + mem_data, + memory, + buf_ptr + bytes_used, + entry_idx + 1, + pseudo_inode, + name, + filetype, + ); + + bytes_used += wasi_entry_size; + entry_idx += 1; + } + } + + try memory.write(u32, bufused_ptr, 0, bytes_used); + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); }, else => { From b26a7b95b5668eac94edc5892acfe7ac103ac6fa Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 18:10:13 -0500 Subject: [PATCH 4/8] Add missing error mappings in toWasiError Add error mappings for: - Unseekable -> ESPIPE (illegal seek on pipes/sockets) - NotOpenForReading -> EBADF (bad file descriptor) - InvalidArgument -> EINVAL - PermissionDenied -> EPERM Note: The Windows fd_readdir implementation from the previous commit is untested - zware itself lacks Windows support in other areas (fd_t type handling, stat.inode types, posix.O flags, etc.) --- src/wasi/wasi.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index e34295b0..9c39e662 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -416,10 +416,14 @@ fn toWasiError(err: anyerror) wasi.errno_t { error.NoSpaceLeft => .NOSPC, error.BrokenPipe => .PIPE, error.NotOpenForWriting => .BADF, + error.NotOpenForReading => .BADF, error.SystemResources => .NOMEM, error.FileNotFound => .NOENT, error.PathAlreadyExists => .EXIST, error.IsDir => .ISDIR, + error.Unseekable => .SPIPE, // ESPIPE: Illegal seek (e.g., on pipes/sockets) + error.InvalidArgument => .INVAL, + error.PermissionDenied => .PERM, else => std.debug.panic("WASI: Unhandled zig stdlib error: {s}", .{@errorName(err)}), }; } From 4b4ca758adff88ec809cbb3482cd1fc4f2744267 Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 18:27:24 -0500 Subject: [PATCH 5/8] fix(fd_readdir): include . and .. entries for WASI compliance WASI spec does not mandate filtering these entries, and major runtimes like wasmtime and wasmer include them. Removing the filter ensures compatibility with applications that may depend on seeing these entries. --- src/wasi/wasi.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index 9c39e662..c1fa41e8 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -565,8 +565,6 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { kernel_offset += linux_entry.reclen; - // Skip . and .. - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; // Skip entries before the cookie position if (entry_idx < cookie) { @@ -624,8 +622,6 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { kernel_offset += entry.reclen; - // Skip . and .. - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; if (entry_idx < cookie) { entry_idx += 1; @@ -681,8 +677,6 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { kernel_offset += entry.reclen; - // Skip . and .. - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; if (entry_idx < cookie) { entry_idx += 1; @@ -763,8 +757,6 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { buf_offset = io.Information; // Force exit } - // Skip . and .. - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue; // Skip entries before cookie if (entry_idx < cookie) { From eb61726ecd5f4a8b8d484c0808966e1ed268f5cf Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 18:38:47 -0500 Subject: [PATCH 6/8] refactor(wasi): clean up fd_tell/fd_readdir, remove Windows impl - Remove verbose doc comments (code is self-documenting) - Remove Windows implementation (to be added in separate branch) - Simplify variable names and reduce code duplication - Keep Linux, macOS, and BSD implementations --- src/wasi/wasi.zig | 198 ++++------------------------------------------ 1 file changed, 16 insertions(+), 182 deletions(-) diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index c1fa41e8..c86e65ab 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -454,8 +454,6 @@ fn toWasiTimestamp(ns: i128) u64 { const _ = std.testing.refAllDecls(); -/// fd_tell - Get the current offset of a file descriptor -/// Returns the current position within the file pub fn fd_tell(vm: *VirtualMachine) WasmError!void { const offset_ptr = vm.popOperand(u32); const fd = vm.popOperand(i32); @@ -468,11 +466,9 @@ pub fn fd_tell(vm: *VirtualMachine) WasmError!void { const memory = try vm.inst.getMemory(0); try memory.write(u64, offset_ptr, 0, current_offset); - try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); } -/// Convert native directory entry type to WASI filetype fn toWasiFiletype(native_type: u8) wasi.filetype_t { return switch (native_type) { std.posix.DT.BLK => .BLOCK_DEVICE, @@ -481,13 +477,10 @@ fn toWasiFiletype(native_type: u8) wasi.filetype_t { std.posix.DT.LNK => .SYMBOLIC_LINK, std.posix.DT.REG => .REGULAR_FILE, std.posix.DT.SOCK => .SOCKET_STREAM, - std.posix.DT.FIFO => .UNKNOWN, // WASI has no FIFO type else => .UNKNOWN, }; } -/// Write a WASI dirent entry to memory -/// Uses std.os.wasi.dirent_t for spec-compliant structure layout fn writeWasiDirent( mem_data: []u8, memory: anytype, @@ -497,39 +490,14 @@ fn writeWasiDirent( name: []const u8, filetype: wasi.filetype_t, ) !void { - // WASI dirent_t structure (from std.os.wasi): - // next: dircookie_t (u64) - offset 0 - // ino: inode_t (u64) - offset 8 - // namlen: dirnamlen_t (u32) - offset 16 - // type: filetype_t (u8) - offset 20 - // [3 bytes padding to align to 24 bytes] - // name follows immediately after (not null-terminated) try memory.write(u64, offset, 0, next_cookie); try memory.write(u64, offset + 8, 0, inode); try memory.write(u32, offset + 16, 0, @as(u32, @intCast(name.len))); mem_data[offset + 20] = @intFromEnum(filetype); - // Padding bytes 21-23 are implicitly handled const name_offset = offset + @sizeOf(wasi.dirent_t); @memcpy(mem_data[name_offset..][0..name.len], name); } -/// fd_readdir - Read directory entries from a directory file descriptor -/// Reads directory entries into the provided buffer using the cookie for pagination. -/// The cookie value 0 starts from the beginning; subsequent calls use the d_next -/// value from the last entry to continue reading. -/// -/// Uses std.os.wasi types for spec-compliant WASI dirent structure: -/// next: dircookie_t (u64) - Cookie for next entry -/// ino: inode_t (u64) - Inode number -/// namlen: dirnamlen_t (u32) - Length of the name -/// type: filetype_t (u8) - File type -/// [3 bytes padding] -/// name: [namlen]u8 - Name (not null-terminated) -/// -/// Platform support: -/// - Linux: getdents64 syscall (no libc required) -/// - macOS/iOS/BSD: getdirentries/getdents via std.c (requires libc) -/// - Windows: NtQueryDirectoryFile via std.os.windows pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { const bufused_ptr = vm.popOperand(u32); const cookie = vm.popOperand(u64); @@ -540,13 +508,10 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { const memory = try vm.inst.getMemory(0); const mem_data = memory.memory(); const host_fd = vm.getHostFd(fd); - - // Size of WASI dirent header (from std.os.wasi.dirent_t) - const dirent_header_size: u32 = @sizeOf(wasi.dirent_t); + const dirent_size: u32 = @sizeOf(wasi.dirent_t); switch (native_os) { .linux => { - // Linux implementation using getdents64 syscall (no libc needed) _ = std.os.linux.lseek(host_fd, 0, std.os.linux.SEEK.SET); var entry_idx: u64 = 0; @@ -560,37 +525,24 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { var kernel_offset: usize = 0; while (kernel_offset < nread) { - const linux_entry: *align(1) std.os.linux.dirent64 = @ptrCast(&kernel_buf[kernel_offset]); - const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); - - kernel_offset += linux_entry.reclen; - + const entry: *align(1) std.os.linux.dirent64 = @ptrCast(&kernel_buf[kernel_offset]); + const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); + kernel_offset += entry.reclen; - // Skip entries before the cookie position if (entry_idx < cookie) { entry_idx += 1; continue; } - const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); - - if (bytes_used + wasi_entry_size > buf_len) { + const entry_size: u32 = dirent_size + @as(u32, @intCast(name.len)); + if (bytes_used + entry_size > buf_len) { try memory.write(u32, bufused_ptr, 0, bytes_used); try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); return; } - try writeWasiDirent( - mem_data, - memory, - buf_ptr + bytes_used, - entry_idx + 1, - linux_entry.ino, - name, - toWasiFiletype(linux_entry.type), - ); - - bytes_used += wasi_entry_size; + try writeWasiDirent(mem_data, memory, buf_ptr + bytes_used, entry_idx + 1, entry.ino, name, toWasiFiletype(entry.type)); + bytes_used += entry_size; entry_idx += 1; } } @@ -600,7 +552,6 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { }, .macos, .ios, .tvos, .watchos, .visionos => { - // Darwin implementation using getdirentries via libc posix.lseek_SET(host_fd, 0) catch { try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); return; @@ -619,34 +570,22 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { while (kernel_offset < @as(usize, @intCast(nread))) { const entry: *align(1) std.c.dirent = @ptrCast(&kernel_buf[kernel_offset]); const name = @as([*]const u8, @ptrCast(&entry.name))[0..entry.namlen]; - kernel_offset += entry.reclen; - if (entry_idx < cookie) { entry_idx += 1; continue; } - const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); - - if (bytes_used + wasi_entry_size > buf_len) { + const entry_size: u32 = dirent_size + @as(u32, @intCast(name.len)); + if (bytes_used + entry_size > buf_len) { try memory.write(u32, bufused_ptr, 0, bytes_used); try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); return; } - try writeWasiDirent( - mem_data, - memory, - buf_ptr + bytes_used, - entry_idx + 1, - entry.ino, - name, - toWasiFiletype(entry.type), - ); - - bytes_used += wasi_entry_size; + try writeWasiDirent(mem_data, memory, buf_ptr + bytes_used, entry_idx + 1, entry.ino, name, toWasiFiletype(entry.type)); + bytes_used += entry_size; entry_idx += 1; } } @@ -656,7 +595,6 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { }, .freebsd, .openbsd, .netbsd, .dragonfly => { - // BSD implementation using getdents via libc posix.lseek_SET(host_fd, 0) catch { try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); return; @@ -674,126 +612,22 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { while (kernel_offset < @as(usize, @intCast(nread))) { const entry: *align(1) std.c.dirent = @ptrCast(&kernel_buf[kernel_offset]); const name = @as([*]const u8, @ptrCast(&entry.name))[0..entry.namlen]; - kernel_offset += entry.reclen; - - if (entry_idx < cookie) { - entry_idx += 1; - continue; - } - - const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); - - if (bytes_used + wasi_entry_size > buf_len) { - try memory.write(u32, bufused_ptr, 0, bytes_used); - try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); - return; - } - - try writeWasiDirent( - mem_data, - memory, - buf_ptr + bytes_used, - entry_idx + 1, - entry.fileno, - name, - toWasiFiletype(entry.type), - ); - - bytes_used += wasi_entry_size; - entry_idx += 1; - } - } - - try memory.write(u32, bufused_ptr, 0, bytes_used); - try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); - }, - - .windows => { - // Windows implementation using NtQueryDirectoryFile - const w = std.os.windows; - - var entry_idx: u64 = 0; - var bytes_used: u32 = 0; - var first_iter = true; - - while (true) { - var io: w.IO_STATUS_BLOCK = undefined; - var dir_buf: [4096]u8 align(@alignOf(w.FILE_BOTH_DIR_INFORMATION)) = undefined; - - const rc = w.ntdll.NtQueryDirectoryFile( - @ptrFromInt(@as(usize, @bitCast(@as(isize, host_fd)))), - null, - null, - null, - &io, - &dir_buf, - dir_buf.len, - .FileBothDirectoryInformation, - w.FALSE, // ReturnSingleEntry - null, // FileName filter - if (first_iter) w.TRUE else w.FALSE, // RestartScan - ); - first_iter = false; - - if (rc != .SUCCESS) break; - if (io.Information == 0) break; - - var buf_offset: usize = 0; - while (buf_offset < io.Information) { - const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&dir_buf[buf_offset])); - - // Get name as UTF-8 - const name_wtf16 = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; - var name_buf: [256]u8 = undefined; - const name_len = std.unicode.wtf16LeToWtf8(&name_buf, name_wtf16); - const name = name_buf[0..name_len]; - - // Move to next entry - if (dir_info.NextEntryOffset != 0) { - buf_offset += dir_info.NextEntryOffset; - } else { - buf_offset = io.Information; // Force exit - } - - - // Skip entries before cookie if (entry_idx < cookie) { entry_idx += 1; continue; } - // Determine file type - const filetype: wasi.filetype_t = blk: { - const attrs = dir_info.FileAttributes; - if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .DIRECTORY; - if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .SYMBOLIC_LINK; - break :blk .REGULAR_FILE; - }; - - const wasi_entry_size: u32 = dirent_header_size + @as(u32, @intCast(name.len)); - - if (bytes_used + wasi_entry_size > buf_len) { + const entry_size: u32 = dirent_size + @as(u32, @intCast(name.len)); + if (bytes_used + entry_size > buf_len) { try memory.write(u32, bufused_ptr, 0, bytes_used); try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); return; } - // Windows doesn't have inodes, use FileIndex as a pseudo-inode - const pseudo_inode: u64 = @as(u64, dir_info.FileIndex); - - try writeWasiDirent( - mem_data, - memory, - buf_ptr + bytes_used, - entry_idx + 1, - pseudo_inode, - name, - filetype, - ); - - bytes_used += wasi_entry_size; + try writeWasiDirent(mem_data, memory, buf_ptr + bytes_used, entry_idx + 1, entry.fileno, name, toWasiFiletype(entry.type)); + bytes_used += entry_size; entry_idx += 1; } } From e9f0c3827b93c2d7dce7515b6bf01c077b2474e5 Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 18:59:59 -0500 Subject: [PATCH 7/8] fix(fd_readdir): proper error handling for lseek calls --- src/wasi/wasi.zig | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index c86e65ab..c794a319 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -512,7 +512,10 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { switch (native_os) { .linux => { - _ = std.os.linux.lseek(host_fd, 0, std.os.linux.SEEK.SET); + posix.lseek_SET(host_fd, 0) catch |err| { + try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); + return; + }; var entry_idx: u64 = 0; var bytes_used: u32 = 0; @@ -552,8 +555,8 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { }, .macos, .ios, .tvos, .watchos, .visionos => { - posix.lseek_SET(host_fd, 0) catch { - try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); + posix.lseek_SET(host_fd, 0) catch |err| { + try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); return; }; @@ -595,8 +598,8 @@ pub fn fd_readdir(vm: *VirtualMachine) WasmError!void { }, .freebsd, .openbsd, .netbsd, .dragonfly => { - posix.lseek_SET(host_fd, 0) catch { - try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.BADF)); + posix.lseek_SET(host_fd, 0) catch |err| { + try vm.pushOperand(u64, @intFromEnum(toWasiError(err))); return; }; From f2079b357239fddfc13af2185483ad72a4ebb95e Mon Sep 17 00:00:00 2001 From: Noah Baertsch Date: Mon, 29 Dec 2025 19:25:34 -0500 Subject: [PATCH 8/8] env vars for cpython --- src/instance.zig | 2 +- src/wasi/wasi.zig | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/instance.zig b/src/instance.zig index eb2bb729..d2692813 100644 --- a/src/instance.zig +++ b/src/instance.zig @@ -74,7 +74,7 @@ pub const Instance = struct { .wasi_preopens = .empty, .wasi_args = .empty, - .wasi_env = .empty, + .wasi_env = .{}, }; } diff --git a/src/wasi/wasi.zig b/src/wasi/wasi.zig index c794a319..de74ce05 100644 --- a/src/wasi/wasi.zig +++ b/src/wasi/wasi.zig @@ -55,6 +55,57 @@ pub fn args_sizes_get(vm: *VirtualMachine) WasmError!void { try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); } +pub fn environ_get(vm: *VirtualMachine) WasmError!void { + const environ_buf_ptr = vm.popOperand(u32); + const environ_ptr = vm.popOperand(u32); + + const memory = try vm.inst.getMemory(0); + const data = memory.memory(); + + var environ_buf_i: usize = 0; + var i: usize = 0; + var iter = vm.wasi_env.iterator(); + while (iter.next()) |entry| { + const env_i_ptr = environ_buf_ptr + environ_buf_i; + const key = entry.key_ptr.*; + const value = entry.value_ptr.*; + + // Write "KEY=value\0" to WASI memory + @memcpy(data[env_i_ptr..][0..key.len], key); + data[env_i_ptr + key.len] = '='; + @memcpy(data[env_i_ptr + key.len + 1 ..][0..value.len], value); + data[env_i_ptr + key.len + 1 + value.len] = 0; + + const env_len = key.len + 1 + value.len + 1; // key + '=' + value + '\0' + try memory.write(u32, environ_ptr, 4 * @as(u32, @intCast(i)), @as(u32, @intCast(env_i_ptr))); + + environ_buf_i += env_len; + i += 1; + } + + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); +} + +pub fn environ_sizes_get(vm: *VirtualMachine) WasmError!void { + const environ_buf_size_ptr = vm.popOperand(u32); + const environc_ptr = vm.popOperand(u32); + + const memory = try vm.inst.getMemory(0); + + const environc = vm.wasi_env.count(); + try memory.write(u32, environc_ptr, 0, @as(u32, @intCast(environc))); + + var buf_size: usize = 0; + var iter = vm.wasi_env.iterator(); + while (iter.next()) |entry| { + // key + '=' + value + '\0' + buf_size += entry.key_ptr.*.len + 1 + entry.value_ptr.*.len + 1; + } + try memory.write(u32, environ_buf_size_ptr, 0, @as(u32, @intCast(buf_size))); + + try vm.pushOperand(u64, @intFromEnum(wasi.errno_t.SUCCESS)); +} + pub fn clock_time_get(vm: *VirtualMachine) WasmError!void { const timestamp_ptr = vm.popOperand(u32); const precision = vm.popOperand(i64); // FIXME: we should probably be using this