From f2a6e9425ac70272f169ff04217105ef21ded18e Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 00:17:07 +0200 Subject: [PATCH 01/24] regz: Move VirtualFilesystem into /modules --- modules/virtual-io/README.md | 4 +++ modules/virtual-io/build.zig | 9 ++++++ modules/virtual-io/build.zig.zon | 11 +++++++ .../virtual-io/src/root.zig | 2 +- tools/regz/build.zig | 30 +++++++------------ tools/regz/build.zig.zon | 1 + tools/regz/src/gen.zig | 2 +- 7 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 modules/virtual-io/README.md create mode 100644 modules/virtual-io/build.zig create mode 100644 modules/virtual-io/build.zig.zon rename tools/regz/src/VirtualFilesystem.zig => modules/virtual-io/src/root.zig (99%) diff --git a/modules/virtual-io/README.md b/modules/virtual-io/README.md new file mode 100644 index 000000000..50d8a4009 --- /dev/null +++ b/modules/virtual-io/README.md @@ -0,0 +1,4 @@ +# Virtual Io interface for Zig +A Zig Io implemention that keeps the directory structure and file contents in ram. +- All Io operations that would touch the disk go into ram, backed by a hash map +- Everything else fails diff --git a/modules/virtual-io/build.zig b/modules/virtual-io/build.zig new file mode 100644 index 000000000..14a49efe2 --- /dev/null +++ b/modules/virtual-io/build.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + _ = b.addModule("virtual-io", .{ + .root_source_file = b.path("src/root.zig"), + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + }); +} diff --git a/modules/virtual-io/build.zig.zon b/modules/virtual-io/build.zig.zon new file mode 100644 index 000000000..c760f07bf --- /dev/null +++ b/modules/virtual-io/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .virtual_io, + .fingerprint = 0x9ed0871d23642986, + .version = "0.0.1", + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + "README.md", + }, +} diff --git a/tools/regz/src/VirtualFilesystem.zig b/modules/virtual-io/src/root.zig similarity index 99% rename from tools/regz/src/VirtualFilesystem.zig rename to modules/virtual-io/src/root.zig index 4b8cffa2b..454db9c8b 100644 --- a/tools/regz/src/VirtualFilesystem.zig +++ b/modules/virtual-io/src/root.zig @@ -6,7 +6,7 @@ files: Map(ID, File) = .empty, hierarchy: Map(ID, ID) = .empty, next_id: u16 = 2, -const VirtualFilesystem = @This(); +pub const VirtualFilesystem = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; diff --git a/tools/regz/build.zig b/tools/regz/build.zig index ab9f02247..14c30c77a 100644 --- a/tools/regz/build.zig +++ b/tools/regz/build.zig @@ -12,12 +12,15 @@ pub fn build(b: *Build) !void { .optimize = .ReleaseSafe, }); - const zqlite_dep = b.dependency("zqlite", .{ + const virtual_io = b.dependency("virtual_io", .{ .target = target, .optimize = optimize, - }); + }).module("virtual-io"); - const zqlite = zqlite_dep.module("zqlite"); + const zqlite = b.dependency("zqlite", .{ + .target = target, + .optimize = optimize, + }).module("zqlite"); const xml_module = b.createModule(.{ .root_source_file = b.path("src/xml.zig"), @@ -33,14 +36,9 @@ pub fn build(b: *Build) !void { .target = target, .optimize = optimize, .imports = &.{ - .{ - .name = "zqlite", - .module = zqlite, - }, - .{ - .name = "xml", - .module = xml_module, - }, + .{ .name = "virtual-io", .module = virtual_io }, + .{ .name = "xml", .module = xml_module }, + .{ .name = "zqlite", .module = zqlite }, }, }); regz_module.linkLibrary(libxml2_dep.artifact("xml")); @@ -52,14 +50,8 @@ pub fn build(b: *Build) !void { .target = target, .optimize = optimize, .imports = &.{ - .{ - .name = "regz", - .module = regz_module, - }, - .{ - .name = "xml", - .module = xml_module, - }, + .{ .name = "regz", .module = regz_module }, + .{ .name = "xml", .module = xml_module }, }, }), .use_llvm = true, diff --git a/tools/regz/build.zig.zon b/tools/regz/build.zig.zon index 8e1033808..81cdc6f64 100644 --- a/tools/regz/build.zig.zon +++ b/tools/regz/build.zig.zon @@ -13,6 +13,7 @@ .url = "git+https://github.com/allyourcodebase/libxml2.git#e2c881ccb72dc96b03bb80f1f4c4b386969b76f7", .hash = "libxml2-2.15.1-2-qHdjhjJXAAAF6fWNrfH_GFaXDHRNjlYQoeKF_RBnWtVu", }, + .virtual_io = .{ .path = "../../modules/virtual-io" }, .zqlite = .{ .url = "git+https://github.com/karlseguin/zqlite.zig#95054418207705ed9188b4d7467fec5136100f13", .hash = "zqlite-0.0.1-RWLaY9UynADU9w6axCjGV9--FpxxKO_qu1qmorhTj2D9", diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 37b25a663..97d35d4a1 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -13,7 +13,7 @@ const DevicePeripheral = Database.DevicePeripheral; const EnumID = Database.EnumID; const StructID = Database.StructID; const NestedStructField = Database.NestedStructField; -const VirtualFilesystem = @import("VirtualFilesystem.zig"); +const VirtualFilesystem = @import("virtual-io").VirtualFilesystem; const Properties = @import("properties.zig").Properties; const arm = @import("arch/arm.zig"); From 91a34c1e3e92fcf97b30f85cb5f057a92d01a4dc Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 00:30:06 +0200 Subject: [PATCH 02/24] virtual-io: rename VirtualFilesystem to VirtualIo --- modules/virtual-io/src/root.zig | 76 ++++++++++++++++----------------- tools/regz/src/gen.zig | 2 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 454db9c8b..508719fc0 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -1,4 +1,19 @@ -//! Filesystem to store generated files +//! In-memory filesystem implementation +const std = @import("std"); +const builtin = @import("builtin"); + +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const log = std.log.scoped(.vio); +const Map = std.AutoArrayHashMapUnmanaged; + +pub const Kind = enum(u1) { + file, + directory, +}; + +pub const VirtualIo = @This(); + gpa: Allocator, directories: Map(ID, Dir) = .empty, files: Map(ID, File) = .empty, @@ -6,21 +21,11 @@ files: Map(ID, File) = .empty, hierarchy: Map(ID, ID) = .empty, next_id: u16 = 2, -pub const VirtualFilesystem = @This(); - -const std = @import("std"); -const Allocator = std.mem.Allocator; -const Map = std.AutoArrayHashMapUnmanaged; -const assert = std.debug.assert; -const log = std.log.scoped(.vfs); - -const builtin = @import("builtin"); - fn id_from_handle(handle: std.posix.fd_t) ID { - return switch (builtin.os.tag) { - .windows => @enumFromInt(@intFromPtr(handle)), - else => @enumFromInt(handle), - }; + return @enumFromInt(switch (builtin.os.tag) { + .windows => @intFromPtr(handle), + else => handle, + }); } fn handle_from_id(id: ID) std.posix.fd_t { @@ -30,11 +35,6 @@ fn handle_from_id(id: ID) std.posix.fd_t { }; } -pub const Kind = enum { - file, - directory, -}; - pub const Dir = struct { name: []const u8, @@ -43,7 +43,7 @@ pub const Dir = struct { } fn create_file(userdata: ?*anyopaque, dir: std.Io.Dir, sub_path: []const u8, _: std.Io.Dir.CreateFileOptions) std.Io.File.OpenError!std.Io.File { - const vfs: *VirtualFilesystem = @ptrCast(@alignCast(userdata.?)); + const vfs: *VirtualIo = @ptrCast(@alignCast(userdata.?)); const dir_id = id_from_handle(dir.handle); if (std.mem.findScalar(u8, sub_path, '/') != null) { @@ -69,7 +69,7 @@ pub const Dir = struct { _: std.Io.Dir.Permissions, _: std.Io.Dir.OpenOptions, ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { - const vfs: *VirtualFilesystem = @ptrCast(@alignCast(userdata.?)); + const vfs: *VirtualIo = @ptrCast(@alignCast(userdata.?)); const parent = id_from_handle(parent_dir.handle); const id = vfs.create_dir(parent, sub_path) catch return error.NoSpaceLeft; @@ -94,7 +94,7 @@ pub const File = struct { }; fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { - const vfs: *VirtualFilesystem = @ptrCast(@alignCast(userdata.?)); + const vfs: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return switch (op) { .file_write_streaming => |write_op| blk: { const file = vfs.files.getPtr(id_from_handle(write_op.file.handle)).?; @@ -121,13 +121,13 @@ pub const ID = enum(u16) { _, }; -pub fn init(gpa: Allocator) VirtualFilesystem { - return VirtualFilesystem{ +pub fn init(gpa: Allocator) VirtualIo { + return VirtualIo{ .gpa = gpa, }; } -pub fn deinit(fs: *VirtualFilesystem) void { +pub fn deinit(fs: *VirtualIo) void { for (fs.directories.values()) |*directory| directory.deinit(fs.gpa); for (fs.files.values()) |*file| file.deinit(fs.gpa); @@ -136,12 +136,12 @@ pub fn deinit(fs: *VirtualFilesystem) void { fs.hierarchy.deinit(fs.gpa); } -pub fn root_dir(fs: *VirtualFilesystem) std.Io.Dir { +pub fn root_dir(fs: *VirtualIo) std.Io.Dir { _ = fs; return .{ .handle = handle_from_id(ID.root) }; } -pub fn get_file(fs: *VirtualFilesystem, path: []const u8) !?ID { +pub fn get_file(fs: *VirtualIo, path: []const u8) !?ID { var components: std.ArrayList([]const u8) = .empty; defer components.deinit(fs.gpa); @@ -152,7 +152,7 @@ pub fn get_file(fs: *VirtualFilesystem, path: []const u8) !?ID { return fs.recursive_get_file(0, .root, components.items); } -fn recursive_get_file(fs: *VirtualFilesystem, depth: usize, dir_id: ID, components: []const []const u8) !?ID { +fn recursive_get_file(fs: *VirtualIo, depth: usize, dir_id: ID, components: []const []const u8) !?ID { return switch (components.len) { 0 => null, 1 => blk: { @@ -187,7 +187,7 @@ pub const Entry = struct { kind: Kind, }; -pub fn get_children(fs: *VirtualFilesystem, allocator: Allocator, id: ID) ![]const Entry { +pub fn get_children(fs: *VirtualIo, allocator: Allocator, id: ID) ![]const Entry { var ret: std.ArrayList(Entry) = .empty; for (fs.hierarchy.keys(), fs.hierarchy.values()) |child_id, parent_id| { if (parent_id == id) @@ -199,12 +199,12 @@ pub fn get_children(fs: *VirtualFilesystem, allocator: Allocator, id: ID) ![]con return ret.toOwnedSlice(allocator); } -fn new_id(fs: *VirtualFilesystem) ID { +fn new_id(fs: *VirtualIo) ID { defer fs.next_id += 1; return @enumFromInt(fs.next_id); } -fn create_dir(fs: *VirtualFilesystem, parent: ID, name: []const u8) !ID { +fn create_dir(fs: *VirtualIo, parent: ID, name: []const u8) !ID { const id = fs.new_id(); const name_copy = try fs.gpa.dupe(u8, name); @@ -217,7 +217,7 @@ fn create_dir(fs: *VirtualFilesystem, parent: ID, name: []const u8) !ID { return id; } -fn create_file(fs: *VirtualFilesystem, parent: ID, name: []const u8) !ID { +fn create_file(fs: *VirtualIo, parent: ID, name: []const u8) !ID { const id = fs.new_id(); const name_copy = try fs.gpa.dupe(u8, name); @@ -231,7 +231,7 @@ fn create_file(fs: *VirtualFilesystem, parent: ID, name: []const u8) !ID { return id; } -pub fn get_kind(fs: *VirtualFilesystem, id: ID) Kind { +pub fn get_kind(fs: *VirtualIo, id: ID) Kind { return if (fs.files.contains(id)) .file else if (fs.directories.contains(id)) @@ -240,7 +240,7 @@ pub fn get_kind(fs: *VirtualFilesystem, id: ID) Kind { unreachable; } -pub fn get_name(fs: *VirtualFilesystem, id: ID) []const u8 { +pub fn get_name(fs: *VirtualIo, id: ID) []const u8 { return if (fs.files.get(id)) |f| f.name else if (fs.directories.get(id)) |d| @@ -249,19 +249,19 @@ pub fn get_name(fs: *VirtualFilesystem, id: ID) []const u8 { unreachable; } -pub fn get_content(fs: *VirtualFilesystem, id: ID) []const u8 { +pub fn get_content(fs: *VirtualIo, id: ID) []const u8 { assert(fs.get_kind(id) == .file); return fs.files.get(id).?.content.writer.buffered(); } -fn get_child(fs: *VirtualFilesystem, parent: ID, component: []const u8) ?ID { +fn get_child(fs: *VirtualIo, parent: ID, component: []const u8) ?ID { return for (fs.hierarchy.keys(), fs.hierarchy.values()) |child_id, entry_parent_id| { if (entry_parent_id == parent and std.mem.eql(u8, component, fs.get_name(child_id))) break child_id; } else null; } -pub fn io(vfs: *VirtualFilesystem) std.Io { +pub fn io(vfs: *VirtualIo) std.Io { return .{ .userdata = vfs, .vtable = &.{ diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 97d35d4a1..94b62313a 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -13,7 +13,7 @@ const DevicePeripheral = Database.DevicePeripheral; const EnumID = Database.EnumID; const StructID = Database.StructID; const NestedStructField = Database.NestedStructField; -const VirtualFilesystem = @import("virtual-io").VirtualFilesystem; +const VirtualFilesystem = @import("virtual-io").VirtualIo; const Properties = @import("properties.zig").Properties; const arm = @import("arch/arm.zig"); From 387b0e6b929a865e6622e873b9136325e73a78f7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 00:42:55 +0200 Subject: [PATCH 03/24] virtual-io: create vtable from std.Io.failing --- modules/virtual-io/src/root.zig | 137 +++----------------------------- 1 file changed, 13 insertions(+), 124 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 508719fc0..6a3bd0d05 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -14,6 +14,18 @@ pub const Kind = enum(u1) { pub const VirtualIo = @This(); +pub const vtable: std.Io.VTable = blk: { + var ret = std.Io.failing.vtable.*; + + ret.operate = operate; + ret.dirCreateDirPathOpen = Dir.create_dir_path_open; + ret.dirClose = Dir.close; + ret.dirCreateFile = Dir.create_file; + ret.fileClose = File.close; + + break :blk ret; +}; + gpa: Allocator, directories: Map(ID, Dir) = .empty, files: Map(ID, File) = .empty, @@ -264,129 +276,6 @@ fn get_child(fs: *VirtualIo, parent: ID, component: []const u8) ?ID { pub fn io(vfs: *VirtualIo) std.Io { return .{ .userdata = vfs, - .vtable = &.{ - .dirCreateFile = Dir.create_file, - .operate = operate, - - // Default/failing/unimplemented handlers - .crashHandler = std.Io.noCrashHandler, - .async = std.Io.noAsync, - .concurrent = std.Io.failingConcurrent, - .await = std.Io.unreachableAwait, - .cancel = std.Io.unreachableCancel, - .groupAsync = std.Io.noGroupAsync, - .groupConcurrent = std.Io.failingGroupConcurrent, - .groupAwait = std.Io.unreachableGroupAwait, - .groupCancel = std.Io.unreachableGroupCancel, - - .recancel = std.Io.unreachableRecancel, - .swapCancelProtection = std.Io.unreachableSwapCancelProtection, - .checkCancel = std.Io.unreachableCheckCancel, - - .futexWait = std.Io.noFutexWait, - .futexWaitUncancelable = std.Io.noFutexWaitUncancelable, - .futexWake = std.Io.noFutexWake, - - .batchAwaitAsync = std.Io.unreachableBatchAwaitAsync, - .batchAwaitConcurrent = std.Io.unreachableBatchAwaitConcurrent, - .batchCancel = std.Io.unreachableBatchCancel, - - .dirCreateDir = std.Io.failingDirCreateDir, - .dirCreateDirPath = std.Io.failingDirCreateDirPath, - .dirCreateDirPathOpen = Dir.create_dir_path_open, - .dirOpenDir = std.Io.failingDirOpenDir, - .dirStat = std.Io.failingDirStat, - .dirStatFile = std.Io.failingDirStatFile, - .dirAccess = std.Io.failingDirAccess, - .dirCreateFileAtomic = std.Io.failingDirCreateFileAtomic, - .dirOpenFile = std.Io.failingDirOpenFile, - .dirClose = Dir.close, - .dirRead = std.Io.noDirRead, - .dirRealPath = std.Io.failingDirRealPath, - .dirRealPathFile = std.Io.failingDirRealPathFile, - .dirDeleteFile = std.Io.failingDirDeleteFile, - .dirDeleteDir = std.Io.failingDirDeleteDir, - .dirRename = std.Io.failingDirRename, - .dirRenamePreserve = std.Io.failingDirRenamePreserve, - .dirSymLink = std.Io.failingDirSymLink, - .dirReadLink = std.Io.failingDirReadLink, - .dirSetOwner = std.Io.failingDirSetOwner, - .dirSetFileOwner = std.Io.failingDirSetFileOwner, - .dirSetPermissions = std.Io.failingDirSetPermissions, - .dirSetFilePermissions = std.Io.failingDirSetFilePermissions, - .dirSetTimestamps = std.Io.noDirSetTimestamps, - .dirHardLink = std.Io.failingDirHardLink, - - .fileStat = std.Io.failingFileStat, - .fileLength = std.Io.failingFileLength, - .fileClose = File.close, - .fileWritePositional = std.Io.failingFileWritePositional, - .fileWriteFileStreaming = std.Io.noFileWriteFileStreaming, - .fileWriteFilePositional = std.Io.noFileWriteFilePositional, - .fileReadPositional = std.Io.failingFileReadPositional, - .fileSeekBy = std.Io.failingFileSeekBy, - .fileSeekTo = std.Io.failingFileSeekTo, - .fileSync = std.Io.failingFileSync, - .fileIsTty = std.Io.unreachableFileIsTty, - .fileEnableAnsiEscapeCodes = std.Io.unreachableFileEnableAnsiEscapeCodes, - .fileSupportsAnsiEscapeCodes = std.Io.unreachableFileSupportsAnsiEscapeCodes, - .fileSetLength = std.Io.failingFileSetLength, - .fileSetOwner = std.Io.failingFileSetOwner, - .fileSetPermissions = std.Io.failingFileSetPermissions, - .fileSetTimestamps = std.Io.noFileSetTimestamps, - .fileLock = std.Io.failingFileLock, - .fileTryLock = std.Io.failingFileTryLock, - .fileUnlock = std.Io.unreachableFileUnlock, - .fileDowngradeLock = std.Io.failingFileDowngradeLock, - .fileRealPath = std.Io.failingFileRealPath, - .fileHardLink = std.Io.failingFileHardLink, - - .fileMemoryMapCreate = std.Io.failingFileMemoryMapCreate, - .fileMemoryMapDestroy = std.Io.unreachableFileMemoryMapDestroy, - .fileMemoryMapSetLength = std.Io.unreachableFileMemoryMapSetLength, - .fileMemoryMapRead = std.Io.unreachableFileMemoryMapRead, - .fileMemoryMapWrite = std.Io.unreachableFileMemoryMapWrite, - - .processExecutableOpen = std.Io.failingProcessExecutableOpen, - .processExecutablePath = std.Io.failingProcessExecutablePath, - .lockStderr = std.Io.unreachableLockStderr, - .tryLockStderr = std.Io.noTryLockStderr, - .unlockStderr = std.Io.unreachableUnlockStderr, - .processCurrentPath = std.Io.failingProcessCurrentPath, - .processSetCurrentDir = std.Io.failingProcessSetCurrentDir, - .processSetCurrentPath = std.Io.failingProcessSetCurrentPath, - .processReplace = std.Io.failingProcessReplace, - .processReplacePath = std.Io.failingProcessReplacePath, - .processSpawn = std.Io.failingProcessSpawn, - .processSpawnPath = std.Io.failingProcessSpawnPath, - .childWait = std.Io.unreachableChildWait, - .childKill = std.Io.unreachableChildKill, - - .progressParentFile = std.Io.failingProgressParentFile, - - .now = std.Io.noNow, - .clockResolution = std.Io.failingClockResolution, - .sleep = std.Io.noSleep, - - .random = std.Io.noRandom, - .randomSecure = std.Io.failingRandomSecure, - - .netListenIp = std.Io.failingNetListenIp, - .netAccept = std.Io.failingNetAccept, - .netBindIp = std.Io.failingNetBindIp, - .netConnectIp = std.Io.failingNetConnectIp, - .netListenUnix = std.Io.failingNetListenUnix, - .netConnectUnix = std.Io.failingNetConnectUnix, - .netSocketCreatePair = std.Io.failingNetSocketCreatePair, - .netSend = std.Io.failingNetSend, - - .netWrite = std.Io.failingNetWrite, - .netWriteFile = std.Io.failingNetWriteFile, - .netClose = std.Io.unreachableNetClose, - .netShutdown = std.Io.failingNetShutdown, - .netInterfaceNameResolve = std.Io.failingNetInterfaceNameResolve, - .netInterfaceName = std.Io.unreachableNetInterfaceName, - .netLookup = std.Io.failingNetLookup, - }, + .vtable = &vtable, }; } From 08a5af2b78f2e63486c518a04faaaac2daba11b1 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 01:55:02 +0200 Subject: [PATCH 04/24] virtual-io: rename vfs and fs to vio --- modules/virtual-io/src/root.zig | 112 ++++++++++++++++---------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 6a3bd0d05..be128355c 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -55,7 +55,7 @@ pub const Dir = struct { } fn create_file(userdata: ?*anyopaque, dir: std.Io.Dir, sub_path: []const u8, _: std.Io.Dir.CreateFileOptions) std.Io.File.OpenError!std.Io.File { - const vfs: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); const dir_id = id_from_handle(dir.handle); if (std.mem.findScalar(u8, sub_path, '/') != null) { @@ -63,7 +63,7 @@ pub const Dir = struct { return error.BadPathName; } - const id = vfs.create_file(dir_id, sub_path) catch |err| switch (err) { + const id = vio.create_file(dir_id, sub_path) catch |err| switch (err) { error.OutOfMemory => return error.NoSpaceLeft, }; return .{ @@ -81,9 +81,9 @@ pub const Dir = struct { _: std.Io.Dir.Permissions, _: std.Io.Dir.OpenOptions, ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { - const vfs: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); const parent = id_from_handle(parent_dir.handle); - const id = vfs.create_dir(parent, sub_path) catch return error.NoSpaceLeft; + const id = vio.create_dir(parent, sub_path) catch return error.NoSpaceLeft; return .{ .handle = handle_from_id(id), @@ -106,10 +106,10 @@ pub const File = struct { }; fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { - const vfs: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return switch (op) { .file_write_streaming => |write_op| blk: { - const file = vfs.files.getPtr(id_from_handle(write_op.file.handle)).?; + const file = vio.files.getPtr(id_from_handle(write_op.file.handle)).?; const header = write_op.header; const data = write_op.data; const splat = write_op.splat; @@ -139,56 +139,56 @@ pub fn init(gpa: Allocator) VirtualIo { }; } -pub fn deinit(fs: *VirtualIo) void { - for (fs.directories.values()) |*directory| directory.deinit(fs.gpa); - for (fs.files.values()) |*file| file.deinit(fs.gpa); +pub fn deinit(vio: *VirtualIo) void { + for (vio.directories.values()) |*directory| directory.deinit(vio.gpa); + for (vio.files.values()) |*file| file.deinit(vio.gpa); - fs.directories.deinit(fs.gpa); - fs.files.deinit(fs.gpa); - fs.hierarchy.deinit(fs.gpa); + vio.directories.deinit(vio.gpa); + vio.files.deinit(vio.gpa); + vio.hierarchy.deinit(vio.gpa); } -pub fn root_dir(fs: *VirtualIo) std.Io.Dir { - _ = fs; +pub fn root_dir(vio: *VirtualIo) std.Io.Dir { + _ = vio; return .{ .handle = handle_from_id(ID.root) }; } -pub fn get_file(fs: *VirtualIo, path: []const u8) !?ID { +pub fn get_file(vio: *VirtualIo, path: []const u8) !?ID { var components: std.ArrayList([]const u8) = .empty; - defer components.deinit(fs.gpa); + defer components.deinit(vio.gpa); var it = std.mem.tokenizeScalar(u8, path, '/'); while (it.next()) |component| - try components.append(fs.gpa, component); + try components.append(vio.gpa, component); - return fs.recursive_get_file(0, .root, components.items); + return vio.recursive_get_file(0, .root, components.items); } -fn recursive_get_file(fs: *VirtualIo, depth: usize, dir_id: ID, components: []const []const u8) !?ID { +fn recursive_get_file(vio: *VirtualIo, depth: usize, dir_id: ID, components: []const []const u8) !?ID { return switch (components.len) { 0 => null, 1 => blk: { - const children = try fs.get_children(fs.gpa, dir_id); - defer fs.gpa.free(children); + const children = try vio.get_children(vio.gpa, dir_id); + defer vio.gpa.free(children); break :blk for (children) |child| { if (child.kind != .file) continue; - const name = fs.get_name(child.id); + const name = vio.get_name(child.id); if (std.mem.eql(u8, name, components[0])) break child.id; } else null; }, else => blk: { - const children = try fs.get_children(fs.gpa, dir_id); - defer fs.gpa.free(children); + const children = try vio.get_children(vio.gpa, dir_id); + defer vio.gpa.free(children); break :blk for (children) |child| { if (child.kind != .directory) continue; - break try fs.recursive_get_file(depth + 1, child.id, components[1..]) orelse continue; + break try vio.recursive_get_file(depth + 1, child.id, components[1..]) orelse continue; } else null; }, }; @@ -199,83 +199,83 @@ pub const Entry = struct { kind: Kind, }; -pub fn get_children(fs: *VirtualIo, allocator: Allocator, id: ID) ![]const Entry { +pub fn get_children(vio: *VirtualIo, allocator: Allocator, id: ID) ![]const Entry { var ret: std.ArrayList(Entry) = .empty; - for (fs.hierarchy.keys(), fs.hierarchy.values()) |child_id, parent_id| { + for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, parent_id| { if (parent_id == id) try ret.append(allocator, .{ .id = child_id, - .kind = fs.get_kind(child_id), + .kind = vio.get_kind(child_id), }); } return ret.toOwnedSlice(allocator); } -fn new_id(fs: *VirtualIo) ID { - defer fs.next_id += 1; - return @enumFromInt(fs.next_id); +fn new_id(vio: *VirtualIo) ID { + defer vio.next_id += 1; + return @enumFromInt(vio.next_id); } -fn create_dir(fs: *VirtualIo, parent: ID, name: []const u8) !ID { - const id = fs.new_id(); +fn create_dir(vio: *VirtualIo, parent: ID, name: []const u8) !ID { + const id = vio.new_id(); - const name_copy = try fs.gpa.dupe(u8, name); - try fs.directories.put(fs.gpa, id, .{ + const name_copy = try vio.gpa.dupe(u8, name); + try vio.directories.put(vio.gpa, id, .{ .name = name_copy, }); - try fs.hierarchy.put(fs.gpa, id, parent); + try vio.hierarchy.put(vio.gpa, id, parent); return id; } -fn create_file(fs: *VirtualIo, parent: ID, name: []const u8) !ID { - const id = fs.new_id(); +fn create_file(vio: *VirtualIo, parent: ID, name: []const u8) !ID { + const id = vio.new_id(); - const name_copy = try fs.gpa.dupe(u8, name); - try fs.files.put(fs.gpa, id, .{ + const name_copy = try vio.gpa.dupe(u8, name); + try vio.files.put(vio.gpa, id, .{ .name = name_copy, - .content = .init(fs.gpa), + .content = .init(vio.gpa), }); - try fs.hierarchy.put(fs.gpa, id, parent); + try vio.hierarchy.put(vio.gpa, id, parent); return id; } -pub fn get_kind(fs: *VirtualIo, id: ID) Kind { - return if (fs.files.contains(id)) +pub fn get_kind(vio: *VirtualIo, id: ID) Kind { + return if (vio.files.contains(id)) .file - else if (fs.directories.contains(id)) + else if (vio.directories.contains(id)) .directory else unreachable; } -pub fn get_name(fs: *VirtualIo, id: ID) []const u8 { - return if (fs.files.get(id)) |f| +pub fn get_name(vio: *VirtualIo, id: ID) []const u8 { + return if (vio.files.get(id)) |f| f.name - else if (fs.directories.get(id)) |d| + else if (vio.directories.get(id)) |d| d.name else unreachable; } -pub fn get_content(fs: *VirtualIo, id: ID) []const u8 { - assert(fs.get_kind(id) == .file); - return fs.files.get(id).?.content.writer.buffered(); +pub fn get_content(vio: *VirtualIo, id: ID) []const u8 { + assert(vio.get_kind(id) == .file); + return vio.files.get(id).?.content.writer.buffered(); } -fn get_child(fs: *VirtualIo, parent: ID, component: []const u8) ?ID { - return for (fs.hierarchy.keys(), fs.hierarchy.values()) |child_id, entry_parent_id| { - if (entry_parent_id == parent and std.mem.eql(u8, component, fs.get_name(child_id))) +fn get_child(vio: *VirtualIo, parent: ID, component: []const u8) ?ID { + return for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, entry_parent_id| { + if (entry_parent_id == parent and std.mem.eql(u8, component, vio.get_name(child_id))) break child_id; } else null; } -pub fn io(vfs: *VirtualIo) std.Io { +pub fn io(vio: *VirtualIo) std.Io { return .{ - .userdata = vfs, + .userdata = vio, .vtable = &vtable, }; } From 9d4400cc4e3cbfda03c05b3ea1a80e3ab093fc7c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 01:55:37 +0200 Subject: [PATCH 05/24] virtual-io: make root_dir a decl instead of a function --- modules/virtual-io/src/root.zig | 23 ++++++------- tools/regz/src/gen.zig | 58 ++++++++++++++++----------------- 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index be128355c..7fdf0b74a 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -26,12 +26,17 @@ pub const vtable: std.Io.VTable = blk: { break :blk ret; }; +pub const root_dir: std.Io.Dir = .{ .handle = handle_from_id(ID.root) }; + gpa: Allocator, directories: Map(ID, Dir) = .empty, files: Map(ID, File) = .empty, // child -> parent hierarchy: Map(ID, ID) = .empty, -next_id: u16 = 2, +last_id: u16 = switch (builtin.os.tag) { + .windows => @intFromPtr(root_dir.handle), + else => root_dir.handle, +}, fn id_from_handle(handle: std.posix.fd_t) ID { return @enumFromInt(switch (builtin.os.tag) { @@ -148,11 +153,6 @@ pub fn deinit(vio: *VirtualIo) void { vio.hierarchy.deinit(vio.gpa); } -pub fn root_dir(vio: *VirtualIo) std.Io.Dir { - _ = vio; - return .{ .handle = handle_from_id(ID.root) }; -} - pub fn get_file(vio: *VirtualIo, path: []const u8) !?ID { var components: std.ArrayList([]const u8) = .empty; defer components.deinit(vio.gpa); @@ -211,13 +211,9 @@ pub fn get_children(vio: *VirtualIo, allocator: Allocator, id: ID) ![]const Entr return ret.toOwnedSlice(allocator); } -fn new_id(vio: *VirtualIo) ID { - defer vio.next_id += 1; - return @enumFromInt(vio.next_id); -} - fn create_dir(vio: *VirtualIo, parent: ID, name: []const u8) !ID { - const id = vio.new_id(); + vio.last_id += 1; + const id: ID = @enumFromInt(vio.last_id); const name_copy = try vio.gpa.dupe(u8, name); try vio.directories.put(vio.gpa, id, .{ @@ -230,7 +226,8 @@ fn create_dir(vio: *VirtualIo, parent: ID, name: []const u8) !ID { } fn create_file(vio: *VirtualIo, parent: ID, name: []const u8) !ID { - const id = vio.new_id(); + vio.last_id += 1; + const id: ID = @enumFromInt(vio.last_id); const name_copy = try vio.gpa.dupe(u8, name); try vio.files.put(vio.gpa, id, .{ diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 94b62313a..89ff8b2b0 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1783,7 +1783,7 @@ test "gen.peripheral instantiation" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "TEST_DEVICE.zig", @@ -1906,7 +1906,7 @@ test "gen.peripherals with a shared type" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "TEST_DEVICE.zig", @@ -2025,7 +2025,7 @@ test "gen.peripheral with modes" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2131,7 +2131,7 @@ test "gen.peripheral with enum" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2200,7 +2200,7 @@ test "gen.peripheral with enum, enum is exhausted of values" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2275,7 +2275,7 @@ test "gen.field with named enum" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2356,7 +2356,7 @@ test "gen.field with named enum and named default" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2437,7 +2437,7 @@ test "gen.field with named enum and unnamed default" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2515,7 +2515,7 @@ test "gen.field with anonymous enum" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2593,7 +2593,7 @@ test "gen.field with anonymous enum and default" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2678,7 +2678,7 @@ test "gen.namespaced register groups" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -2791,7 +2791,7 @@ test "gen.peripheral with reserved register" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -2894,7 +2894,7 @@ test "gen.peripheral with count" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -2998,7 +2998,7 @@ test "gen.peripheral with count, padding required" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3103,7 +3103,7 @@ test "gen.register with count" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3220,7 +3220,7 @@ test "gen.register with count and fields" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3328,7 +3328,7 @@ test "gen.field with count, width of one, offset, and padding" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3401,7 +3401,7 @@ test "gen.field with count, multi-bit width, offset, and padding" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3465,7 +3465,7 @@ test "gen.interrupts.avr" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3561,7 +3561,7 @@ test "gen.peripheral type with register and field" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3651,7 +3651,7 @@ test "gen.name collisions in enum name cause them to be anonymous" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3732,7 +3732,7 @@ test "gen.pick one enum field in value collisions" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3808,7 +3808,7 @@ test "gen.pick one enum field in value collisions" { // var vfs: VirtualFilesystem = .init(std.testing.allocator); // defer vfs.deinit(); // -// try db.to_zig(vfs.io(), vfs.root_dir(), .{}); +// try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); // try expect_output(&.{ // .{ // .path = "types.zig", @@ -3885,7 +3885,7 @@ test "gen.pick one enum field in value collisions" { // var vfs: VirtualFilesystem = .init(std.testing.allocator); // defer vfs.deinit(); // -// try db.to_zig(vfs.io(), vfs.root_dir(), .{}); +// try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); // try expect_output(&.{ // .{ // .path = "types.zig", @@ -3963,7 +3963,7 @@ test "gen.nested struct field in a peripheral" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4048,7 +4048,7 @@ test "gen.nested struct field in a peripheral that has a named type" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4133,7 +4133,7 @@ test "gen.nested struct field in a peripheral with offset" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4225,7 +4225,7 @@ test "gen.nested struct field in nested struct field" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4327,7 +4327,7 @@ test "gen.nested struct field next to register" { var vfs: VirtualFilesystem = .init(std.testing.allocator); defer vfs.deinit(); - try db.to_zig(vfs.io(), vfs.root_dir(), .{}); + try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", From 5a0e71b4be9ffe9533aae5e9d3244debd0f9bcea Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 01:56:12 +0200 Subject: [PATCH 06/24] virtual-io: use raw file descriptors instead of ID --- modules/virtual-io/src/root.zig | 123 ++++++++++++++------------------ 1 file changed, 53 insertions(+), 70 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 7fdf0b74a..38f15da21 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -26,29 +26,24 @@ pub const vtable: std.Io.VTable = blk: { break :blk ret; }; -pub const root_dir: std.Io.Dir = .{ .handle = handle_from_id(ID.root) }; +// Don't use file descriptor 0, because on windows, fd_t is actually a *anyopaque, +// and setting that null under the hood is ILLEGAL, and I don't want to go to jail. +pub const root_dir: std.Io.Dir = .{ .handle = handle_from_int(1) }; gpa: Allocator, -directories: Map(ID, Dir) = .empty, -files: Map(ID, File) = .empty, +directories: Map(std.Io.Dir, Dir) = .empty, +files: Map(std.posix.fd_t, File) = .empty, // child -> parent -hierarchy: Map(ID, ID) = .empty, +hierarchy: Map(std.posix.fd_t, std.Io.Dir) = .empty, last_id: u16 = switch (builtin.os.tag) { .windows => @intFromPtr(root_dir.handle), else => root_dir.handle, }, -fn id_from_handle(handle: std.posix.fd_t) ID { - return @enumFromInt(switch (builtin.os.tag) { - .windows => @intFromPtr(handle), - else => handle, - }); -} - -fn handle_from_id(id: ID) std.posix.fd_t { +fn handle_from_int(int: u16) std.posix.fd_t { return switch (builtin.os.tag) { - .windows => @ptrFromInt(@intFromEnum(id)), - else => @intFromEnum(id), + .windows => @ptrFromInt(int), + else => int, }; } @@ -59,40 +54,36 @@ pub const Dir = struct { gpa.free(d.name); } - fn create_file(userdata: ?*anyopaque, dir: std.Io.Dir, sub_path: []const u8, _: std.Io.Dir.CreateFileOptions) std.Io.File.OpenError!std.Io.File { + fn create_file( + userdata: ?*anyopaque, + dir: std.Io.Dir, + sub_path: []const u8, + _: std.Io.Dir.CreateFileOptions, + ) std.Io.File.OpenError!std.Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const dir_id = id_from_handle(dir.handle); if (std.mem.findScalar(u8, sub_path, '/') != null) { log.err("path includes '/': '{s}'", .{sub_path}); return error.BadPathName; } - const id = vio.create_file(dir_id, sub_path) catch |err| switch (err) { - error.OutOfMemory => return error.NoSpaceLeft, - }; return .{ - .handle = handle_from_id(id), - .flags = .{ - .nonblocking = false, + .handle = vio.create_file(dir, sub_path) catch |err| switch (err) { + error.OutOfMemory => return error.NoSpaceLeft, }, + .flags = .{ .nonblocking = false }, }; } fn create_dir_path_open( userdata: ?*anyopaque, - parent_dir: std.Io.Dir, + dir: std.Io.Dir, sub_path: []const u8, _: std.Io.Dir.Permissions, _: std.Io.Dir.OpenOptions, ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = id_from_handle(parent_dir.handle); - const id = vio.create_dir(parent, sub_path) catch return error.NoSpaceLeft; - - return .{ - .handle = handle_from_id(id), - }; + return vio.create_dir(dir, sub_path) catch return error.NoSpaceLeft; } fn close(_: ?*anyopaque, _: []const std.Io.Dir) void {} @@ -112,32 +103,24 @@ pub const File = struct { fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return switch (op) { - .file_write_streaming => |write_op| blk: { - const file = vio.files.getPtr(id_from_handle(write_op.file.handle)).?; - const header = write_op.header; - const data = write_op.data; - const splat = write_op.splat; - break :blk .{ - .file_write_streaming = file.content.writer.writeSplatHeader(header, data, splat) catch error.NoSpaceLeft, + switch (op) { + .file_write_streaming => |write_op| { + const file = vio.files.getPtr(write_op.file.handle).?; + return .{ + .file_write_streaming = file.content.writer.writeSplatHeader( + write_op.header, + write_op.data, + write_op.splat, + ) catch error.NoSpaceLeft, }; }, .file_read_streaming => unreachable, .device_io_control => unreachable, .net_receive => unreachable, .net_read => unreachable, - }; + } } -pub const ID = enum(u16) { - // Don't use this one, because on windows, fd_t is actually a *anyopaque, - // and setting that null under the hood is ILLEGAL, and I don't want to go - // to jail. - invalid = 0, - root = 1, - _, -}; - pub fn init(gpa: Allocator) VirtualIo { return VirtualIo{ .gpa = gpa, @@ -153,7 +136,7 @@ pub fn deinit(vio: *VirtualIo) void { vio.hierarchy.deinit(vio.gpa); } -pub fn get_file(vio: *VirtualIo, path: []const u8) !?ID { +pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.posix.fd_t { var components: std.ArrayList([]const u8) = .empty; defer components.deinit(vio.gpa); @@ -161,14 +144,14 @@ pub fn get_file(vio: *VirtualIo, path: []const u8) !?ID { while (it.next()) |component| try components.append(vio.gpa, component); - return vio.recursive_get_file(0, .root, components.items); + return vio.recursive_get_file(0, root_dir, components.items); } -fn recursive_get_file(vio: *VirtualIo, depth: usize, dir_id: ID, components: []const []const u8) !?ID { +fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: std.Io.Dir, components: []const []const u8) !?std.posix.fd_t { return switch (components.len) { 0 => null, 1 => blk: { - const children = try vio.get_children(vio.gpa, dir_id); + const children = try vio.get_children(vio.gpa, dir); defer vio.gpa.free(children); break :blk for (children) |child| { @@ -181,28 +164,28 @@ fn recursive_get_file(vio: *VirtualIo, depth: usize, dir_id: ID, components: []c } else null; }, else => blk: { - const children = try vio.get_children(vio.gpa, dir_id); + const children = try vio.get_children(vio.gpa, dir); defer vio.gpa.free(children); break :blk for (children) |child| { if (child.kind != .directory) continue; - break try vio.recursive_get_file(depth + 1, child.id, components[1..]) orelse continue; + break try vio.recursive_get_file(depth + 1, .{ .handle = child.id }, components[1..]) orelse continue; } else null; }, }; } pub const Entry = struct { - id: ID, + id: std.posix.fd_t, kind: Kind, }; -pub fn get_children(vio: *VirtualIo, allocator: Allocator, id: ID) ![]const Entry { +pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]const Entry { var ret: std.ArrayList(Entry) = .empty; - for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, parent_id| { - if (parent_id == id) + for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, parent| { + if (parent.handle == dir.handle) try ret.append(allocator, .{ .id = child_id, .kind = vio.get_kind(child_id), @@ -211,23 +194,23 @@ pub fn get_children(vio: *VirtualIo, allocator: Allocator, id: ID) ![]const Entr return ret.toOwnedSlice(allocator); } -fn create_dir(vio: *VirtualIo, parent: ID, name: []const u8) !ID { +fn create_dir(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.Dir { vio.last_id += 1; - const id: ID = @enumFromInt(vio.last_id); + const dir: std.Io.Dir = .{ .handle = handle_from_int(vio.last_id) }; const name_copy = try vio.gpa.dupe(u8, name); - try vio.directories.put(vio.gpa, id, .{ + try vio.directories.put(vio.gpa, dir, .{ .name = name_copy, }); - try vio.hierarchy.put(vio.gpa, id, parent); + try vio.hierarchy.put(vio.gpa, dir.handle, parent); - return id; + return dir; } -fn create_file(vio: *VirtualIo, parent: ID, name: []const u8) !ID { +fn create_file(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.posix.fd_t { vio.last_id += 1; - const id: ID = @enumFromInt(vio.last_id); + const id = handle_from_int(vio.last_id); const name_copy = try vio.gpa.dupe(u8, name); try vio.files.put(vio.gpa, id, .{ @@ -240,30 +223,30 @@ fn create_file(vio: *VirtualIo, parent: ID, name: []const u8) !ID { return id; } -pub fn get_kind(vio: *VirtualIo, id: ID) Kind { +pub fn get_kind(vio: *VirtualIo, id: std.posix.fd_t) Kind { return if (vio.files.contains(id)) .file - else if (vio.directories.contains(id)) + else if (vio.directories.contains(.{ .handle = id })) .directory else unreachable; } -pub fn get_name(vio: *VirtualIo, id: ID) []const u8 { +pub fn get_name(vio: *VirtualIo, id: std.posix.fd_t) []const u8 { return if (vio.files.get(id)) |f| f.name - else if (vio.directories.get(id)) |d| + else if (vio.directories.get(.{ .handle = id })) |d| d.name else unreachable; } -pub fn get_content(vio: *VirtualIo, id: ID) []const u8 { +pub fn get_content(vio: *VirtualIo, id: std.posix.fd_t) []const u8 { assert(vio.get_kind(id) == .file); return vio.files.get(id).?.content.writer.buffered(); } -fn get_child(vio: *VirtualIo, parent: ID, component: []const u8) ?ID { +fn get_child(vio: *VirtualIo, parent: std.Io.Dir, component: []const u8) ?std.posix.fd_t { return for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, entry_parent_id| { if (entry_parent_id == parent and std.mem.eql(u8, component, vio.get_name(child_id))) break child_id; From 3fd877837517f578139c86a7fe5be2178f482bcb Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 02:23:45 +0200 Subject: [PATCH 07/24] virtual-io: redo error handling --- modules/virtual-io/src/root.zig | 35 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 38f15da21..e01969602 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -68,9 +68,7 @@ pub const Dir = struct { } return .{ - .handle = vio.create_file(dir, sub_path) catch |err| switch (err) { - error.OutOfMemory => return error.NoSpaceLeft, - }, + .handle = try vio.create_file(dir, sub_path), .flags = .{ .nonblocking = false }, }; } @@ -83,7 +81,7 @@ pub const Dir = struct { _: std.Io.Dir.OpenOptions, ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return vio.create_dir(dir, sub_path) catch return error.NoSpaceLeft; + return vio.create_dir(dir, sub_path); } fn close(_: ?*anyopaque, _: []const std.Io.Dir) void {} @@ -142,7 +140,8 @@ pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.posix.fd_t { var it = std.mem.tokenizeScalar(u8, path, '/'); while (it.next()) |component| - try components.append(vio.gpa, component); + components.append(vio.gpa, component) catch + return error.NoSpaceLeft; return vio.recursive_get_file(0, root_dir, components.items); } @@ -186,10 +185,10 @@ pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]c var ret: std.ArrayList(Entry) = .empty; for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, parent| { if (parent.handle == dir.handle) - try ret.append(allocator, .{ + ret.append(allocator, .{ .id = child_id, .kind = vio.get_kind(child_id), - }); + }) catch return error.NoSpaceLeft; } return ret.toOwnedSlice(allocator); } @@ -198,12 +197,12 @@ fn create_dir(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.Dir vio.last_id += 1; const dir: std.Io.Dir = .{ .handle = handle_from_int(vio.last_id) }; - const name_copy = try vio.gpa.dupe(u8, name); - try vio.directories.put(vio.gpa, dir, .{ - .name = name_copy, - }); - - try vio.hierarchy.put(vio.gpa, dir.handle, parent); + const name_copy = vio.gpa.dupe(u8, name) catch + return error.NoSpaceLeft; + vio.directories.put(vio.gpa, dir, .{ .name = name_copy }) catch + return error.NoSpaceLeft; + vio.hierarchy.put(vio.gpa, dir.handle, parent) catch + return error.NoSpaceLeft; return dir; } @@ -212,13 +211,15 @@ fn create_file(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.posix vio.last_id += 1; const id = handle_from_int(vio.last_id); - const name_copy = try vio.gpa.dupe(u8, name); - try vio.files.put(vio.gpa, id, .{ + const name_copy = vio.gpa.dupe(u8, name) catch + return error.NoSpaceLeft; + vio.files.put(vio.gpa, id, .{ .name = name_copy, .content = .init(vio.gpa), - }); + }) catch return error.NoSpaceLeft; - try vio.hierarchy.put(vio.gpa, id, parent); + vio.hierarchy.put(vio.gpa, id, parent) catch + return error.NoSpaceLeft; return id; } From 00434be6b3dfed3dd66bfd9333acaecd898e9e45 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 02:33:32 +0200 Subject: [PATCH 08/24] virtual-io: improve file/dir creation --- modules/virtual-io/src/root.zig | 37 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index e01969602..5688a922e 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -67,10 +67,7 @@ pub const Dir = struct { return error.BadPathName; } - return .{ - .handle = try vio.create_file(dir, sub_path), - .flags = .{ .nonblocking = false }, - }; + return vio.create_file(dir, sub_path); } fn create_dir_path_open( @@ -193,35 +190,43 @@ pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]c return ret.toOwnedSlice(allocator); } -fn create_dir(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.Dir { +fn create_node(vio: *VirtualIo, parent: std.Io.Dir) !std.posix.fd_t { vio.last_id += 1; - const dir: std.Io.Dir = .{ .handle = handle_from_int(vio.last_id) }; + const handle = handle_from_int(vio.last_id); + + vio.hierarchy.put(vio.gpa, handle, parent) catch + return error.NoSpaceLeft; + + return handle; +} + +fn create_dir(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.Dir { + const dir: std.Io.Dir = .{ + .handle = try vio.create_node(parent), + }; const name_copy = vio.gpa.dupe(u8, name) catch return error.NoSpaceLeft; vio.directories.put(vio.gpa, dir, .{ .name = name_copy }) catch return error.NoSpaceLeft; - vio.hierarchy.put(vio.gpa, dir.handle, parent) catch - return error.NoSpaceLeft; return dir; } -fn create_file(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.posix.fd_t { - vio.last_id += 1; - const id = handle_from_int(vio.last_id); +fn create_file(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.File { + const file: std.Io.File = .{ + .handle = try vio.create_node(parent), + .flags = .{ .nonblocking = false }, + }; const name_copy = vio.gpa.dupe(u8, name) catch return error.NoSpaceLeft; - vio.files.put(vio.gpa, id, .{ + vio.files.put(vio.gpa, file.handle, .{ .name = name_copy, .content = .init(vio.gpa), }) catch return error.NoSpaceLeft; - vio.hierarchy.put(vio.gpa, id, parent) catch - return error.NoSpaceLeft; - - return id; + return file; } pub fn get_kind(vio: *VirtualIo, id: std.posix.fd_t) Kind { From 5b3f4081ef0ae59992ba2be9156900ec5585e5ed Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 02:51:41 +0200 Subject: [PATCH 09/24] virtual-io: move VTable functions to one place --- modules/virtual-io/src/root.zig | 85 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 5688a922e..7b97e8d58 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -17,11 +17,43 @@ pub const VirtualIo = @This(); pub const vtable: std.Io.VTable = blk: { var ret = std.Io.failing.vtable.*; - ret.operate = operate; - ret.dirCreateDirPathOpen = Dir.create_dir_path_open; - ret.dirClose = Dir.close; - ret.dirCreateFile = Dir.create_file; - ret.fileClose = File.close; + const VTable = struct { + fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + return vio.operate(op); + } + + fn create_dir_path_open( + userdata: ?*anyopaque, + dir: std.Io.Dir, + sub_path: []const u8, + _: std.Io.Dir.Permissions, + _: std.Io.Dir.OpenOptions, + ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + return vio.create_dir(dir, sub_path); + } + + fn dir_close(_: ?*anyopaque, _: []const std.Io.Dir) void {} + + fn dir_create_file( + userdata: ?*anyopaque, + dir: std.Io.Dir, + sub_path: []const u8, + _: std.Io.Dir.CreateFileOptions, + ) std.Io.File.OpenError!std.Io.File { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + return vio.create_file(dir, sub_path); + } + + fn file_close(_: ?*anyopaque, _: []const std.Io.File) void {} + }; + + ret.operate = VTable.operate; + ret.dirCreateDirPathOpen = VTable.create_dir_path_open; + ret.dirClose = VTable.dir_close; + ret.dirCreateFile = VTable.dir_create_file; + ret.fileClose = VTable.file_close; break :blk ret; }; @@ -53,35 +85,6 @@ pub const Dir = struct { pub fn deinit(d: *Dir, gpa: Allocator) void { gpa.free(d.name); } - - fn create_file( - userdata: ?*anyopaque, - dir: std.Io.Dir, - sub_path: []const u8, - _: std.Io.Dir.CreateFileOptions, - ) std.Io.File.OpenError!std.Io.File { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - - if (std.mem.findScalar(u8, sub_path, '/') != null) { - log.err("path includes '/': '{s}'", .{sub_path}); - return error.BadPathName; - } - - return vio.create_file(dir, sub_path); - } - - fn create_dir_path_open( - userdata: ?*anyopaque, - dir: std.Io.Dir, - sub_path: []const u8, - _: std.Io.Dir.Permissions, - _: std.Io.Dir.OpenOptions, - ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return vio.create_dir(dir, sub_path); - } - - fn close(_: ?*anyopaque, _: []const std.Io.Dir) void {} }; pub const File = struct { @@ -92,12 +95,9 @@ pub const File = struct { gpa.free(f.name); f.content.deinit(); } - - pub fn close(_: ?*anyopaque, _: []const std.Io.File) void {} }; -fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); +fn operate(vio: *VirtualIo, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { switch (op) { .file_write_streaming => |write_op| { const file = vio.files.getPtr(write_op.file.handle).?; @@ -213,13 +213,18 @@ fn create_dir(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.Dir return dir; } -fn create_file(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.File { +fn create_file(vio: *VirtualIo, parent: std.Io.Dir, sub_path: []const u8) !std.Io.File { + if (std.mem.findScalar(u8, sub_path, '/') != null) { + log.err("path includes '/': '{s}'", .{sub_path}); + return error.BadPathName; + } + const file: std.Io.File = .{ .handle = try vio.create_node(parent), .flags = .{ .nonblocking = false }, }; - const name_copy = vio.gpa.dupe(u8, name) catch + const name_copy = vio.gpa.dupe(u8, sub_path) catch return error.NoSpaceLeft; vio.files.put(vio.gpa, file.handle, .{ .name = name_copy, From 65bb7e4e46d2a5db5bdd8756c19e960a8863ada8 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 04:07:28 +0200 Subject: [PATCH 10/24] virtual-io: unify files and directories into nodes --- modules/virtual-io/src/root.zig | 240 +++++++++++++++----------------- tools/regz/src/gen.zig | 2 +- 2 files changed, 117 insertions(+), 125 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 7b97e8d58..88c3c155f 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -7,9 +7,27 @@ const assert = std.debug.assert; const log = std.log.scoped(.vio); const Map = std.AutoArrayHashMapUnmanaged; -pub const Kind = enum(u1) { - file, - directory, +pub const Node = struct { + pub const Kind = union(enum) { + file: std.Io.Writer.Allocating, + dir, + }; + + pub const CreateInfo = union(std.meta.Tag(Kind)) { + file: []const u8, + dir, + }; + + name: []const u8, + kind: Kind, + + pub fn deinit(node: *Node, gpa: Allocator) void { + gpa.free(node.name); + switch (node.kind) { + .file => |*w| w.deinit(), + .dir => {}, + } + } }; pub const VirtualIo = @This(); @@ -31,7 +49,9 @@ pub const vtable: std.Io.VTable = blk: { _: std.Io.Dir.OpenOptions, ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return vio.create_dir(dir, sub_path); + return .{ + .handle = try vio.create_node(dir, sub_path, .dir), + }; } fn dir_close(_: ?*anyopaque, _: []const std.Io.Dir) void {} @@ -43,7 +63,10 @@ pub const vtable: std.Io.VTable = blk: { _: std.Io.Dir.CreateFileOptions, ) std.Io.File.OpenError!std.Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return vio.create_file(dir, sub_path); + return .{ + .handle = try vio.create_node(dir, sub_path, .{ .file = "" }), + .flags = .{ .nonblocking = false }, + }; } fn file_close(_: ?*anyopaque, _: []const std.Io.File) void {} @@ -63,8 +86,7 @@ pub const vtable: std.Io.VTable = blk: { pub const root_dir: std.Io.Dir = .{ .handle = handle_from_int(1) }; gpa: Allocator, -directories: Map(std.Io.Dir, Dir) = .empty, -files: Map(std.posix.fd_t, File) = .empty, +nodes: Map(std.posix.fd_t, Node) = .empty, // child -> parent hierarchy: Map(std.posix.fd_t, std.Io.Dir) = .empty, last_id: u16 = switch (builtin.os.tag) { @@ -79,30 +101,16 @@ fn handle_from_int(int: u16) std.posix.fd_t { }; } -pub const Dir = struct { - name: []const u8, - - pub fn deinit(d: *Dir, gpa: Allocator) void { - gpa.free(d.name); - } -}; - -pub const File = struct { - name: []const u8, - content: std.Io.Writer.Allocating, - - pub fn deinit(f: *File, gpa: Allocator) void { - gpa.free(f.name); - f.content.deinit(); - } -}; - -fn operate(vio: *VirtualIo, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { +fn operate(vio: VirtualIo, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { switch (op) { .file_write_streaming => |write_op| { - const file = vio.files.getPtr(write_op.file.handle).?; + const node = vio.nodes.getPtr(write_op.file.handle).?; + const w = switch (node.kind) { + .file => |*allocating| &allocating.writer, + .dir => unreachable, + }; return .{ - .file_write_streaming = file.content.writer.writeSplatHeader( + .file_write_streaming = w.writeSplatHeader( write_op.header, write_op.data, write_op.splat, @@ -123,15 +131,13 @@ pub fn init(gpa: Allocator) VirtualIo { } pub fn deinit(vio: *VirtualIo) void { - for (vio.directories.values()) |*directory| directory.deinit(vio.gpa); - for (vio.files.values()) |*file| file.deinit(vio.gpa); - - vio.directories.deinit(vio.gpa); - vio.files.deinit(vio.gpa); + for (vio.nodes.values()) |*node| + node.deinit(vio.gpa); + vio.nodes.deinit(vio.gpa); vio.hierarchy.deinit(vio.gpa); } -pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.posix.fd_t { +pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.Io.File { var components: std.ArrayList([]const u8) = .empty; defer components.deinit(vio.gpa); @@ -143,125 +149,111 @@ pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.posix.fd_t { return vio.recursive_get_file(0, root_dir, components.items); } -fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: std.Io.Dir, components: []const []const u8) !?std.posix.fd_t { - return switch (components.len) { - 0 => null, - 1 => blk: { - const children = try vio.get_children(vio.gpa, dir); - defer vio.gpa.free(children); - - break :blk for (children) |child| { - if (child.kind != .file) - continue; - - const name = vio.get_name(child.id); - if (std.mem.eql(u8, name, components[0])) - break child.id; - } else null; - }, - else => blk: { - const children = try vio.get_children(vio.gpa, dir); - defer vio.gpa.free(children); +fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: std.Io.Dir, components: []const []const u8) !?std.Io.File { + const children = try vio.get_children(vio.gpa, dir); + defer vio.gpa.free(children); - break :blk for (children) |child| { - if (child.kind != .directory) - continue; + switch (components.len) { + 0 => {}, + 1 => for (children) |id| { + const child = vio.nodes.getPtr(id).?; - break try vio.recursive_get_file(depth + 1, .{ .handle = child.id }, components[1..]) orelse continue; - } else null; + if (child.kind != .file) + continue; + + if (std.mem.eql(u8, child.name, components[0])) + return .{ + .handle = id, + .flags = .{ .nonblocking = false }, + }; }, - }; -} + else => for (children) |id| { + const child = vio.nodes.getPtr(id).?; -pub const Entry = struct { - id: std.posix.fd_t, - kind: Kind, -}; + if (child.kind != .dir) + continue; -pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]const Entry { - var ret: std.ArrayList(Entry) = .empty; - for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, parent| { - if (parent.handle == dir.handle) - ret.append(allocator, .{ - .id = child_id, - .kind = vio.get_kind(child_id), - }) catch return error.NoSpaceLeft; + return try vio.recursive_get_file( + depth + 1, + .{ .handle = id }, + components[1..], + ) orelse continue; + }, } - return ret.toOwnedSlice(allocator); -} - -fn create_node(vio: *VirtualIo, parent: std.Io.Dir) !std.posix.fd_t { - vio.last_id += 1; - const handle = handle_from_int(vio.last_id); - - vio.hierarchy.put(vio.gpa, handle, parent) catch - return error.NoSpaceLeft; - - return handle; + return null; } -fn create_dir(vio: *VirtualIo, parent: std.Io.Dir, name: []const u8) !std.Io.Dir { - const dir: std.Io.Dir = .{ - .handle = try vio.create_node(parent), - }; - - const name_copy = vio.gpa.dupe(u8, name) catch - return error.NoSpaceLeft; - vio.directories.put(vio.gpa, dir, .{ .name = name_copy }) catch +pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]std.posix.fd_t { + var ret: std.ArrayList(std.posix.fd_t) = .empty; + var it = vio.hierarchy.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.handle == dir.handle) + ret.append(allocator, entry.key_ptr.*) catch + return error.NoSpaceLeft; + } + return ret.toOwnedSlice(allocator) catch return error.NoSpaceLeft; - - return dir; } -fn create_file(vio: *VirtualIo, parent: std.Io.Dir, sub_path: []const u8) !std.Io.File { +fn create_node( + vio: *VirtualIo, + dir: std.Io.Dir, + sub_path: []const u8, + info: Node.CreateInfo, +) error{ NoSpaceLeft, BadPathName }!std.posix.fd_t { if (std.mem.findScalar(u8, sub_path, '/') != null) { log.err("path includes '/': '{s}'", .{sub_path}); return error.BadPathName; } - const file: std.Io.File = .{ - .handle = try vio.create_node(parent), - .flags = .{ .nonblocking = false }, + const node: Node = .{ + .name = vio.gpa.dupe(u8, sub_path) catch + return error.NoSpaceLeft, + .kind = switch (info) { + .file => |content| blk: { + var w: std.Io.Writer.Allocating = .init(vio.gpa); + w.writer.writeAll(content) catch + return error.NoSpaceLeft; + break :blk .{ .file = w }; + }, + .dir => .dir, + }, }; - const name_copy = vio.gpa.dupe(u8, sub_path) catch - return error.NoSpaceLeft; - vio.files.put(vio.gpa, file.handle, .{ - .name = name_copy, - .content = .init(vio.gpa), - }) catch return error.NoSpaceLeft; + vio.last_id += 1; + const handle = handle_from_int(vio.last_id); - return file; -} + vio.nodes.put(vio.gpa, handle, node) catch + return error.NoSpaceLeft; + vio.hierarchy.put(vio.gpa, handle, dir) catch + return error.NoSpaceLeft; -pub fn get_kind(vio: *VirtualIo, id: std.posix.fd_t) Kind { - return if (vio.files.contains(id)) - .file - else if (vio.directories.contains(.{ .handle = id })) - .directory - else - unreachable; + return handle; } pub fn get_name(vio: *VirtualIo, id: std.posix.fd_t) []const u8 { - return if (vio.files.get(id)) |f| - f.name - else if (vio.directories.get(.{ .handle = id })) |d| - d.name + return if (vio.nodes.getPtr(id)) |node| + node.name else unreachable; } -pub fn get_content(vio: *VirtualIo, id: std.posix.fd_t) []const u8 { - assert(vio.get_kind(id) == .file); - return vio.files.get(id).?.content.writer.buffered(); +pub fn get_content(vio: *VirtualIo, file: std.Io.File) []const u8 { + const node = vio.nodes.getPtr(file.handle).?; + return switch (node.kind) { + .file => |*w| w.written(), + .dir => unreachable, + }; } -fn get_child(vio: *VirtualIo, parent: std.Io.Dir, component: []const u8) ?std.posix.fd_t { - return for (vio.hierarchy.keys(), vio.hierarchy.values()) |child_id, entry_parent_id| { - if (entry_parent_id == parent and std.mem.eql(u8, component, vio.get_name(child_id))) - break child_id; - } else null; +pub fn file_count(vio: *VirtualIo) usize { + var ret: usize = 0; + var it = vio.nodes.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.kind == .file) + ret += 1; + } + return ret; } pub fn io(vio: *VirtualIo) std.Io { diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 89ff8b2b0..3558d157e 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1736,7 +1736,7 @@ const ExpectedOutput = struct { }; fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { - try std.testing.expectEqual(expected_outputs.len, vfs.files.count()); + try std.testing.expectEqual(expected_outputs.len, vfs.file_count()); for (expected_outputs) |eo| { const file_id = try vfs.get_file(eo.path) orelse unreachable; try std.testing.expectEqualStrings(eo.content, vfs.get_content(file_id)); From 0406dc4d475ac168e8a499028466850da8632627 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 04:27:16 +0200 Subject: [PATCH 11/24] virtual-io: style improvements --- modules/virtual-io/src/root.zig | 154 +++++++++++++++----------------- tools/regz/src/gen.zig | 4 +- 2 files changed, 72 insertions(+), 86 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 88c3c155f..21679edeb 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -4,10 +4,14 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const Dir = std.Io.Dir; +const File = std.Io.File; const log = std.log.scoped(.vio); const Map = std.AutoArrayHashMapUnmanaged; pub const Node = struct { + pub const ID = std.posix.fd_t; + pub const Kind = union(enum) { file: std.Io.Writer.Allocating, dir, @@ -21,7 +25,7 @@ pub const Node = struct { name: []const u8, kind: Kind, - pub fn deinit(node: *Node, gpa: Allocator) void { + pub fn destroy(node: *Node, gpa: Allocator) void { gpa.free(node.name); switch (node.kind) { .file => |*w| w.deinit(), @@ -32,36 +36,55 @@ pub const Node = struct { pub const VirtualIo = @This(); -pub const vtable: std.Io.VTable = blk: { +pub const vtable: *const std.Io.VTable = blk: { var ret = std.Io.failing.vtable.*; const VTable = struct { fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return vio.operate(op); + switch (op) { + .file_write_streaming => |write_op| { + const node = vio.nodes.getPtr(write_op.file.handle).?; + const w = switch (node.kind) { + .file => |*allocating| &allocating.writer, + .dir => unreachable, + }; + return .{ + .file_write_streaming = w.writeSplatHeader( + write_op.header, + write_op.data, + write_op.splat, + ) catch error.NoSpaceLeft, + }; + }, + .file_read_streaming => unreachable, + .device_io_control => unreachable, + .net_receive => unreachable, + .net_read => unreachable, + } } fn create_dir_path_open( userdata: ?*anyopaque, - dir: std.Io.Dir, + dir: Dir, sub_path: []const u8, - _: std.Io.Dir.Permissions, - _: std.Io.Dir.OpenOptions, - ) std.Io.Dir.CreateDirPathOpenError!std.Io.Dir { + _: Dir.Permissions, + _: Dir.OpenOptions, + ) Dir.CreateDirPathOpenError!Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return .{ .handle = try vio.create_node(dir, sub_path, .dir), }; } - fn dir_close(_: ?*anyopaque, _: []const std.Io.Dir) void {} + fn dir_close(_: ?*anyopaque, _: []const Dir) void {} fn dir_create_file( userdata: ?*anyopaque, - dir: std.Io.Dir, + dir: Dir, sub_path: []const u8, - _: std.Io.Dir.CreateFileOptions, - ) std.Io.File.OpenError!std.Io.File { + _: Dir.CreateFileOptions, + ) File.OpenError!File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return .{ .handle = try vio.create_node(dir, sub_path, .{ .file = "" }), @@ -69,7 +92,7 @@ pub const vtable: std.Io.VTable = blk: { }; } - fn file_close(_: ?*anyopaque, _: []const std.Io.File) void {} + fn file_close(_: ?*anyopaque, _: []const File) void {} }; ret.operate = VTable.operate; @@ -78,50 +101,25 @@ pub const vtable: std.Io.VTable = blk: { ret.dirCreateFile = VTable.dir_create_file; ret.fileClose = VTable.file_close; - break :blk ret; + const ret_const = ret; + break :blk &ret_const; }; // Don't use file descriptor 0, because on windows, fd_t is actually a *anyopaque, // and setting that null under the hood is ILLEGAL, and I don't want to go to jail. -pub const root_dir: std.Io.Dir = .{ .handle = handle_from_int(1) }; +pub const root_dir: Dir = .{ .handle = handle_from_int(1) }; gpa: Allocator, -nodes: Map(std.posix.fd_t, Node) = .empty, +nodes: Map(Node.ID, Node) = .empty, // child -> parent -hierarchy: Map(std.posix.fd_t, std.Io.Dir) = .empty, +hierarchy: Map(Node.ID, Dir) = .empty, last_id: u16 = switch (builtin.os.tag) { .windows => @intFromPtr(root_dir.handle), else => root_dir.handle, }, -fn handle_from_int(int: u16) std.posix.fd_t { - return switch (builtin.os.tag) { - .windows => @ptrFromInt(int), - else => int, - }; -} - -fn operate(vio: VirtualIo, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { - switch (op) { - .file_write_streaming => |write_op| { - const node = vio.nodes.getPtr(write_op.file.handle).?; - const w = switch (node.kind) { - .file => |*allocating| &allocating.writer, - .dir => unreachable, - }; - return .{ - .file_write_streaming = w.writeSplatHeader( - write_op.header, - write_op.data, - write_op.splat, - ) catch error.NoSpaceLeft, - }; - }, - .file_read_streaming => unreachable, - .device_io_control => unreachable, - .net_receive => unreachable, - .net_read => unreachable, - } +pub fn io(vio: *VirtualIo) std.Io { + return .{ .userdata = vio, .vtable = vtable }; } pub fn init(gpa: Allocator) VirtualIo { @@ -132,12 +130,30 @@ pub fn init(gpa: Allocator) VirtualIo { pub fn deinit(vio: *VirtualIo) void { for (vio.nodes.values()) |*node| - node.deinit(vio.gpa); + node.destroy(vio.gpa); vio.nodes.deinit(vio.gpa); vio.hierarchy.deinit(vio.gpa); } -pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.Io.File { +pub fn total_file_count(vio: *VirtualIo) usize { + var ret: usize = 0; + var it = vio.nodes.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.kind == .file) + ret += 1; + } + return ret; +} + +pub fn file_content(vio: *VirtualIo, file: File) []const u8 { + const node = vio.nodes.getPtr(file.handle).?; + return switch (node.kind) { + .file => |*w| w.written(), + .dir => unreachable, + }; +} + +pub fn get_file(vio: *VirtualIo, path: []const u8) !?File { var components: std.ArrayList([]const u8) = .empty; defer components.deinit(vio.gpa); @@ -149,7 +165,7 @@ pub fn get_file(vio: *VirtualIo, path: []const u8) !?std.Io.File { return vio.recursive_get_file(0, root_dir, components.items); } -fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: std.Io.Dir, components: []const []const u8) !?std.Io.File { +fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: Dir, components: []const []const u8) !?File { const children = try vio.get_children(vio.gpa, dir); defer vio.gpa.free(children); @@ -183,8 +199,8 @@ fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: std.Io.Dir, components return null; } -pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]std.posix.fd_t { - var ret: std.ArrayList(std.posix.fd_t) = .empty; +fn get_children(vio: *VirtualIo, allocator: Allocator, dir: Dir) ![]Node.ID { + var ret: std.ArrayList(Node.ID) = .empty; var it = vio.hierarchy.iterator(); while (it.next()) |entry| { if (entry.value_ptr.handle == dir.handle) @@ -195,12 +211,7 @@ pub fn get_children(vio: *VirtualIo, allocator: Allocator, dir: std.Io.Dir) ![]s return error.NoSpaceLeft; } -fn create_node( - vio: *VirtualIo, - dir: std.Io.Dir, - sub_path: []const u8, - info: Node.CreateInfo, -) error{ NoSpaceLeft, BadPathName }!std.posix.fd_t { +fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.CreateInfo) !Node.ID { if (std.mem.findScalar(u8, sub_path, '/') != null) { log.err("path includes '/': '{s}'", .{sub_path}); return error.BadPathName; @@ -231,34 +242,9 @@ fn create_node( return handle; } -pub fn get_name(vio: *VirtualIo, id: std.posix.fd_t) []const u8 { - return if (vio.nodes.getPtr(id)) |node| - node.name - else - unreachable; -} - -pub fn get_content(vio: *VirtualIo, file: std.Io.File) []const u8 { - const node = vio.nodes.getPtr(file.handle).?; - return switch (node.kind) { - .file => |*w| w.written(), - .dir => unreachable, - }; -} - -pub fn file_count(vio: *VirtualIo) usize { - var ret: usize = 0; - var it = vio.nodes.iterator(); - while (it.next()) |entry| { - if (entry.value_ptr.kind == .file) - ret += 1; - } - return ret; -} - -pub fn io(vio: *VirtualIo) std.Io { - return .{ - .userdata = vio, - .vtable = &vtable, +fn handle_from_int(int: u16) Node.ID { + return switch (builtin.os.tag) { + .windows => @ptrFromInt(int), + else => int, }; } diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 3558d157e..05e2df829 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1736,10 +1736,10 @@ const ExpectedOutput = struct { }; fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { - try std.testing.expectEqual(expected_outputs.len, vfs.file_count()); + try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { const file_id = try vfs.get_file(eo.path) orelse unreachable; - try std.testing.expectEqualStrings(eo.content, vfs.get_content(file_id)); + try std.testing.expectEqualStrings(eo.content, vfs.file_content(file_id)); } } From eaf681a10dbd1a02c980cbd3bca4d0336e251ca8 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 04:36:41 +0200 Subject: [PATCH 12/24] virtual-io: reduce allocations --- modules/virtual-io/src/root.zig | 65 +++++++++------------------------ tools/regz/src/gen.zig | 2 +- 2 files changed, 19 insertions(+), 48 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 21679edeb..01003cf85 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -153,64 +153,35 @@ pub fn file_content(vio: *VirtualIo, file: File) []const u8 { }; } -pub fn get_file(vio: *VirtualIo, path: []const u8) !?File { - var components: std.ArrayList([]const u8) = .empty; - defer components.deinit(vio.gpa); - - var it = std.mem.tokenizeScalar(u8, path, '/'); - while (it.next()) |component| - components.append(vio.gpa, component) catch - return error.NoSpaceLeft; - - return vio.recursive_get_file(0, root_dir, components.items); -} - -fn recursive_get_file(vio: *VirtualIo, depth: usize, dir: Dir, components: []const []const u8) !?File { - const children = try vio.get_children(vio.gpa, dir); - defer vio.gpa.free(children); +pub fn get_file(vio: *VirtualIo, dir: Dir, path: []const u8) !?File { + var it = vio.hierarchy.iterator(); + const idx_opt = std.mem.findScalar(u8, path, '/'); + while (it.next()) |entry| { + if (entry.value_ptr.handle != dir.handle) + continue; + const id = entry.key_ptr.*; + const child = vio.nodes.getPtr(id).?; - switch (components.len) { - 0 => {}, - 1 => for (children) |id| { - const child = vio.nodes.getPtr(id).?; + if (idx_opt) |idx| { + if (child.kind != .dir) continue; - if (child.kind != .file) - continue; + return try vio.get_file( + .{ .handle = id }, + path[idx + 1 ..], + ) orelse continue; + } else { + if (child.kind != .file) continue; - if (std.mem.eql(u8, child.name, components[0])) + if (std.mem.eql(u8, child.name, path)) return .{ .handle = id, .flags = .{ .nonblocking = false }, }; - }, - else => for (children) |id| { - const child = vio.nodes.getPtr(id).?; - - if (child.kind != .dir) - continue; - - return try vio.recursive_get_file( - depth + 1, - .{ .handle = id }, - components[1..], - ) orelse continue; - }, + } } return null; } -fn get_children(vio: *VirtualIo, allocator: Allocator, dir: Dir) ![]Node.ID { - var ret: std.ArrayList(Node.ID) = .empty; - var it = vio.hierarchy.iterator(); - while (it.next()) |entry| { - if (entry.value_ptr.handle == dir.handle) - ret.append(allocator, entry.key_ptr.*) catch - return error.NoSpaceLeft; - } - return ret.toOwnedSlice(allocator) catch - return error.NoSpaceLeft; -} - fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.CreateInfo) !Node.ID { if (std.mem.findScalar(u8, sub_path, '/') != null) { log.err("path includes '/': '{s}'", .{sub_path}); diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 05e2df829..fe5e7f439 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1738,7 +1738,7 @@ const ExpectedOutput = struct { fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { - const file_id = try vfs.get_file(eo.path) orelse unreachable; + const file_id = try vfs.get_file(VirtualFilesystem.root_dir, eo.path) orelse unreachable; try std.testing.expectEqualStrings(eo.content, vfs.file_content(file_id)); } } From 763a581afe6c72d87543d1ac9d855cdb82a1de52 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 05:22:57 +0200 Subject: [PATCH 13/24] virtual-io: improve error handling --- modules/virtual-io/src/root.zig | 61 ++++++++++++++++++++------------- tools/regz/src/gen.zig | 4 +-- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 01003cf85..d94c567d5 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -42,26 +42,15 @@ pub const vtable: *const std.Io.VTable = blk: { const VTable = struct { fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - switch (op) { - .file_write_streaming => |write_op| { - const node = vio.nodes.getPtr(write_op.file.handle).?; - const w = switch (node.kind) { - .file => |*allocating| &allocating.writer, - .dir => unreachable, - }; - return .{ - .file_write_streaming = w.writeSplatHeader( - write_op.header, - write_op.data, - write_op.splat, - ) catch error.NoSpaceLeft, - }; + return switch (op) { + .file_write_streaming => |write_op| .{ + .file_write_streaming = vio.file_write_streaming(write_op), }, - .file_read_streaming => unreachable, + .file_read_streaming => .{ .file_read_streaming = error.InputOutput }, .device_io_control => unreachable, - .net_receive => unreachable, - .net_read => unreachable, - } + .net_receive => .{ .net_receive = .{ error.NetworkDown, 0 } }, + .net_read => .{ .net_read = error.NetworkDown }, + }; } fn create_dir_path_open( @@ -92,6 +81,16 @@ pub const vtable: *const std.Io.VTable = blk: { }; } + fn dir_open_file( + userdata: ?*anyopaque, + dir: Dir, + sub_path: []const u8, + _: Dir.OpenFileOptions, + ) File.OpenError!File { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + return try vio.dir_open_file(dir, sub_path) orelse error.FileNotFound; + } + fn file_close(_: ?*anyopaque, _: []const File) void {} }; @@ -99,6 +98,7 @@ pub const vtable: *const std.Io.VTable = blk: { ret.dirCreateDirPathOpen = VTable.create_dir_path_open; ret.dirClose = VTable.dir_close; ret.dirCreateFile = VTable.dir_create_file; + ret.dirOpenFile = VTable.dir_open_file; ret.fileClose = VTable.file_close; const ret_const = ret; @@ -145,15 +145,16 @@ pub fn total_file_count(vio: *VirtualIo) usize { return ret; } -pub fn file_content(vio: *VirtualIo, file: File) []const u8 { - const node = vio.nodes.getPtr(file.handle).?; +pub fn file_content(vio: *VirtualIo, file: File) ![]const u8 { + const node = vio.nodes.getPtr(file.handle) orelse + return error.Unexpected; return switch (node.kind) { .file => |*w| w.written(), - .dir => unreachable, + .dir => error.Unexpected, }; } -pub fn get_file(vio: *VirtualIo, dir: Dir, path: []const u8) !?File { +fn dir_open_file(vio: *VirtualIo, dir: Dir, path: []const u8) !?File { var it = vio.hierarchy.iterator(); const idx_opt = std.mem.findScalar(u8, path, '/'); while (it.next()) |entry| { @@ -165,7 +166,7 @@ pub fn get_file(vio: *VirtualIo, dir: Dir, path: []const u8) !?File { if (idx_opt) |idx| { if (child.kind != .dir) continue; - return try vio.get_file( + return try vio.dir_open_file( .{ .handle = id }, path[idx + 1 ..], ) orelse continue; @@ -213,6 +214,20 @@ fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.Creat return handle; } +fn file_write_streaming(vio: *VirtualIo, op: std.Io.Operation.FileWriteStreaming) std.Io.Operation.FileWriteStreaming.Result { + const node = vio.nodes.getPtr(op.file.handle) orelse + return error.Unexpected; + const w = switch (node.kind) { + .file => |*allocating| &allocating.writer, + .dir => return error.Unexpected, + }; + return w.writeSplatHeader( + op.header, + op.data, + op.splat, + ) catch error.NoSpaceLeft; +} + fn handle_from_int(int: u16) Node.ID { return switch (builtin.os.tag) { .windows => @ptrFromInt(int), diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index fe5e7f439..59ff1e8a1 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1738,8 +1738,8 @@ const ExpectedOutput = struct { fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { - const file_id = try vfs.get_file(VirtualFilesystem.root_dir, eo.path) orelse unreachable; - try std.testing.expectEqualStrings(eo.content, vfs.file_content(file_id)); + const file = try std.Io.Dir.openFile(VirtualFilesystem.root_dir, vfs.io(), eo.path, .{}); + try std.testing.expectEqualStrings(eo.content, try vfs.file_content(file)); } } From c0933fe566081f718b5dcbdbc9ee54962bfb2ad5 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 12:32:43 +0200 Subject: [PATCH 14/24] virtual-io: create root dir during initialization --- modules/virtual-io/src/root.zig | 26 +++++++++------ tools/regz/src/gen.zig | 58 ++++++++++++++++----------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index d94c567d5..73eac1597 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -7,7 +7,6 @@ const assert = std.debug.assert; const Dir = std.Io.Dir; const File = std.Io.File; const log = std.log.scoped(.vio); -const Map = std.AutoArrayHashMapUnmanaged; pub const Node = struct { pub const ID = std.posix.fd_t; @@ -22,6 +21,8 @@ pub const Node = struct { dir, }; + const Map = std.AutoArrayHashMapUnmanaged(ID, Node); + name: []const u8, kind: Kind, @@ -107,24 +108,29 @@ pub const vtable: *const std.Io.VTable = blk: { // Don't use file descriptor 0, because on windows, fd_t is actually a *anyopaque, // and setting that null under the hood is ILLEGAL, and I don't want to go to jail. -pub const root_dir: Dir = .{ .handle = handle_from_int(1) }; +const root_dir_handle = 1; +pub const root_dir: Dir = .{ + .handle = handle_from_int(root_dir_handle), +}; gpa: Allocator, -nodes: Map(Node.ID, Node) = .empty, +nodes: Node.Map, // child -> parent -hierarchy: Map(Node.ID, Dir) = .empty, -last_id: u16 = switch (builtin.os.tag) { - .windows => @intFromPtr(root_dir.handle), - else => root_dir.handle, -}, +hierarchy: std.AutoArrayHashMapUnmanaged(Node.ID, Dir), +last_id: u16, pub fn io(vio: *VirtualIo) std.Io { return .{ .userdata = vio, .vtable = vtable }; } -pub fn init(gpa: Allocator) VirtualIo { - return VirtualIo{ +pub fn init(gpa: Allocator) !VirtualIo { + var nodes: Node.Map = .empty; + try nodes.put(gpa, root_dir.handle, .{ .name = "", .kind = .dir }); + return .{ .gpa = gpa, + .nodes = nodes, + .hierarchy = .empty, + .last_id = root_dir_handle, }; } diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 59ff1e8a1..7f1bd5be9 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1780,7 +1780,7 @@ test "gen.peripheral instantiation" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -1903,7 +1903,7 @@ test "gen.peripherals with a shared type" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2022,7 +2022,7 @@ test "gen.peripheral with modes" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2128,7 +2128,7 @@ test "gen.peripheral with enum" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2197,7 +2197,7 @@ test "gen.peripheral with enum, enum is exhausted of values" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2272,7 +2272,7 @@ test "gen.field with named enum" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2353,7 +2353,7 @@ test "gen.field with named enum and named default" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2434,7 +2434,7 @@ test "gen.field with named enum and unnamed default" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2512,7 +2512,7 @@ test "gen.field with anonymous enum" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2590,7 +2590,7 @@ test "gen.field with anonymous enum and default" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2675,7 +2675,7 @@ test "gen.namespaced register groups" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2788,7 +2788,7 @@ test "gen.peripheral with reserved register" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2891,7 +2891,7 @@ test "gen.peripheral with count" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -2995,7 +2995,7 @@ test "gen.peripheral with count, padding required" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3100,7 +3100,7 @@ test "gen.register with count" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3217,7 +3217,7 @@ test "gen.register with count and fields" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3325,7 +3325,7 @@ test "gen.field with count, width of one, offset, and padding" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3398,7 +3398,7 @@ test "gen.field with count, multi-bit width, offset, and padding" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3462,7 +3462,7 @@ test "gen.interrupts.avr" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3558,7 +3558,7 @@ test "gen.peripheral type with register and field" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3648,7 +3648,7 @@ test "gen.name collisions in enum name cause them to be anonymous" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3729,7 +3729,7 @@ test "gen.pick one enum field in value collisions" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3805,7 +3805,7 @@ test "gen.pick one enum field in value collisions" { // var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); // defer buffer.deinit(); // -// var vfs: VirtualFilesystem = .init(std.testing.allocator); +// var vfs: VirtualFilesystem = try .init(std.testing.allocator); // defer vfs.deinit(); // // try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3882,7 +3882,7 @@ test "gen.pick one enum field in value collisions" { // var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); // defer buffer.deinit(); // -// var vfs: VirtualFilesystem = .init(std.testing.allocator); +// var vfs: VirtualFilesystem = try .init(std.testing.allocator); // defer vfs.deinit(); // // try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -3960,7 +3960,7 @@ test "gen.nested struct field in a peripheral" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -4045,7 +4045,7 @@ test "gen.nested struct field in a peripheral that has a named type" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -4130,7 +4130,7 @@ test "gen.nested struct field in a peripheral with offset" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -4222,7 +4222,7 @@ test "gen.nested struct field in nested struct field" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); @@ -4324,7 +4324,7 @@ test "gen.nested struct field next to register" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = .init(std.testing.allocator); + var vfs: VirtualFilesystem = try .init(std.testing.allocator); defer vfs.deinit(); try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); From 405afb68cd033f00ee8e9db6a0cec9ba9badc8fb Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 13:02:50 +0200 Subject: [PATCH 15/24] virtual-io: add dirOpenDir to VTable --- modules/virtual-io/src/root.zig | 46 ++++++++++++++------------------- tools/regz/src/gen.zig | 4 +-- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 73eac1597..95e4d6616 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -89,7 +89,14 @@ pub const vtable: *const std.Io.VTable = blk: { _: Dir.OpenFileOptions, ) File.OpenError!File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return try vio.dir_open_file(dir, sub_path) orelse error.FileNotFound; + const node = try vio.get_node(dir, sub_path); + return switch (node.value_ptr.kind) { + .file => .{ + .handle = node.key_ptr.*, + .flags = .{ .nonblocking = false }, + }, + .dir => error.IsDir, + }; } fn file_close(_: ?*anyopaque, _: []const File) void {} @@ -141,7 +148,7 @@ pub fn deinit(vio: *VirtualIo) void { vio.hierarchy.deinit(vio.gpa); } -pub fn total_file_count(vio: *VirtualIo) usize { +pub fn total_file_count(vio: *const VirtualIo) usize { var ret: usize = 0; var it = vio.nodes.iterator(); while (it.next()) |entry| { @@ -151,42 +158,29 @@ pub fn total_file_count(vio: *VirtualIo) usize { return ret; } -pub fn file_content(vio: *VirtualIo, file: File) ![]const u8 { - const node = vio.nodes.getPtr(file.handle) orelse - return error.Unexpected; - return switch (node.kind) { - .file => |*w| w.written(), - .dir => error.Unexpected, - }; -} - -fn dir_open_file(vio: *VirtualIo, dir: Dir, path: []const u8) !?File { +pub fn get_node(vio: *const VirtualIo, dir: Dir, path: []const u8) !Node.Map.Entry { var it = vio.hierarchy.iterator(); const idx_opt = std.mem.findScalar(u8, path, '/'); + const path_part = path[0 .. idx_opt orelse path.len]; while (it.next()) |entry| { if (entry.value_ptr.handle != dir.handle) continue; const id = entry.key_ptr.*; - const child = vio.nodes.getPtr(id).?; + const child = vio.nodes.getEntry(id).?; + + if (!std.mem.eql(u8, path_part, child.value_ptr.name)) + continue; if (idx_opt) |idx| { - if (child.kind != .dir) continue; + if (child.value_ptr.kind != .dir) continue; - return try vio.dir_open_file( + return try vio.get_node( .{ .handle = id }, path[idx + 1 ..], - ) orelse continue; - } else { - if (child.kind != .file) continue; - - if (std.mem.eql(u8, child.name, path)) - return .{ - .handle = id, - .flags = .{ .nonblocking = false }, - }; - } + ); + } else return child; } - return null; + return error.FileNotFound; } fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.CreateInfo) !Node.ID { diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 7f1bd5be9..66153d6f3 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1738,8 +1738,8 @@ const ExpectedOutput = struct { fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { - const file = try std.Io.Dir.openFile(VirtualFilesystem.root_dir, vfs.io(), eo.path, .{}); - try std.testing.expectEqualStrings(eo.content, try vfs.file_content(file)); + const node = (try vfs.get_node(VirtualFilesystem.root_dir, eo.path)).value_ptr; + try std.testing.expectEqualStrings(eo.content, node.kind.file.written()); } } From badc08b07b09eed751a4d01e60c830c9f9d1ac3a Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 14:03:24 +0200 Subject: [PATCH 16/24] virtual-io: store directory contents instead of relationships --- modules/virtual-io/src/root.zig | 106 +++++++++++++++----------------- tools/regz/src/gen.zig | 5 +- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 95e4d6616..0cda45e39 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -8,29 +8,29 @@ const Dir = std.Io.Dir; const File = std.Io.File; const log = std.log.scoped(.vio); -pub const Node = struct { +pub const Node = union(enum) { pub const ID = std.posix.fd_t; - pub const Kind = union(enum) { - file: std.Io.Writer.Allocating, - dir, - }; - - pub const CreateInfo = union(std.meta.Tag(Kind)) { + pub const CreateInfo = union(std.meta.Tag(Node)) { file: []const u8, dir, }; const Map = std.AutoArrayHashMapUnmanaged(ID, Node); + const Directory = std.StringHashMapUnmanaged(ID); - name: []const u8, - kind: Kind, + file: std.Io.Writer.Allocating, + dir: Directory, pub fn destroy(node: *Node, gpa: Allocator) void { - gpa.free(node.name); - switch (node.kind) { + switch (node.*) { .file => |*w| w.deinit(), - .dir => {}, + .dir => |*entries| { + var it = entries.keyIterator(); + while (it.next()) |name| + gpa.free(name.*); + entries.deinit(gpa); + }, } } }; @@ -89,10 +89,12 @@ pub const vtable: *const std.Io.VTable = blk: { _: Dir.OpenFileOptions, ) File.OpenError!File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const node = try vio.get_node(dir, sub_path); - return switch (node.value_ptr.kind) { + const node_id = try vio.get_node(dir, sub_path); + const node = vio.nodes.get(node_id) orelse + return error.FileNotFound; + return switch (node) { .file => .{ - .handle = node.key_ptr.*, + .handle = node_id, .flags = .{ .nonblocking = false }, }, .dir => error.IsDir, @@ -122,8 +124,6 @@ pub const root_dir: Dir = .{ gpa: Allocator, nodes: Node.Map, -// child -> parent -hierarchy: std.AutoArrayHashMapUnmanaged(Node.ID, Dir), last_id: u16, pub fn io(vio: *VirtualIo) std.Io { @@ -132,11 +132,10 @@ pub fn io(vio: *VirtualIo) std.Io { pub fn init(gpa: Allocator) !VirtualIo { var nodes: Node.Map = .empty; - try nodes.put(gpa, root_dir.handle, .{ .name = "", .kind = .dir }); + try nodes.put(gpa, root_dir.handle, .{ .dir = .empty }); return .{ .gpa = gpa, .nodes = nodes, - .hierarchy = .empty, .last_id = root_dir_handle, }; } @@ -145,42 +144,33 @@ pub fn deinit(vio: *VirtualIo) void { for (vio.nodes.values()) |*node| node.destroy(vio.gpa); vio.nodes.deinit(vio.gpa); - vio.hierarchy.deinit(vio.gpa); } pub fn total_file_count(vio: *const VirtualIo) usize { var ret: usize = 0; var it = vio.nodes.iterator(); while (it.next()) |entry| { - if (entry.value_ptr.kind == .file) + if (entry.value_ptr.* == .file) ret += 1; } return ret; } -pub fn get_node(vio: *const VirtualIo, dir: Dir, path: []const u8) !Node.Map.Entry { - var it = vio.hierarchy.iterator(); +pub fn get_node(vio: *const VirtualIo, dir: Dir, path: []const u8) !Node.ID { const idx_opt = std.mem.findScalar(u8, path, '/'); const path_part = path[0 .. idx_opt orelse path.len]; - while (it.next()) |entry| { - if (entry.value_ptr.handle != dir.handle) - continue; - const id = entry.key_ptr.*; - const child = vio.nodes.getEntry(id).?; - - if (!std.mem.eql(u8, path_part, child.value_ptr.name)) - continue; - - if (idx_opt) |idx| { - if (child.value_ptr.kind != .dir) continue; - - return try vio.get_node( - .{ .handle = id }, - path[idx + 1 ..], - ); - } else return child; - } - return error.FileNotFound; + + const dir_node = vio.nodes.getEntry(dir.handle) orelse + return error.Unexpected; + + const id = switch (dir_node.value_ptr.*) { + .dir => |d| d.get(path_part) orelse return error.FileNotFound, + else => return error.FileNotFound, + }; + + if (idx_opt) |idx| { + return try vio.get_node(.{ .handle = id }, path[idx + 1 ..]); + } else return id; } fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.CreateInfo) !Node.ID { @@ -189,18 +179,14 @@ fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.Creat return error.BadPathName; } - const node: Node = .{ - .name = vio.gpa.dupe(u8, sub_path) catch - return error.NoSpaceLeft, - .kind = switch (info) { - .file => |content| blk: { - var w: std.Io.Writer.Allocating = .init(vio.gpa); - w.writer.writeAll(content) catch - return error.NoSpaceLeft; - break :blk .{ .file = w }; - }, - .dir => .dir, + const node: Node = switch (info) { + .file => |content| blk: { + var w: std.Io.Writer.Allocating = .init(vio.gpa); + w.writer.writeAll(content) catch + return error.NoSpaceLeft; + break :blk .{ .file = w }; }, + .dir => .{ .dir = .empty }, }; vio.last_id += 1; @@ -208,16 +194,26 @@ fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.Creat vio.nodes.put(vio.gpa, handle, node) catch return error.NoSpaceLeft; - vio.hierarchy.put(vio.gpa, handle, dir) catch + + const dir_node = vio.nodes.getPtr(dir.handle) orelse + return error.Unexpected; + + const name = vio.gpa.dupe(u8, sub_path) catch return error.NoSpaceLeft; + switch (dir_node.*) { + .dir => |*d| d.put(vio.gpa, name, handle) catch + return error.NoSpaceLeft, + else => return error.Unexpected, + } + return handle; } fn file_write_streaming(vio: *VirtualIo, op: std.Io.Operation.FileWriteStreaming) std.Io.Operation.FileWriteStreaming.Result { const node = vio.nodes.getPtr(op.file.handle) orelse return error.Unexpected; - const w = switch (node.kind) { + const w = switch (node.*) { .file => |*allocating| &allocating.writer, .dir => return error.Unexpected, }; diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 66153d6f3..bb811e28f 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1738,8 +1738,9 @@ const ExpectedOutput = struct { fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { - const node = (try vfs.get_node(VirtualFilesystem.root_dir, eo.path)).value_ptr; - try std.testing.expectEqualStrings(eo.content, node.kind.file.written()); + const node_id = try vfs.get_node(VirtualFilesystem.root_dir, eo.path); + const node = vfs.nodes.getPtr(node_id) orelse unreachable; + try std.testing.expectEqualStrings(eo.content, node.file.written()); } } From 26f0b9e43d146c8c887fb084d0fa4196aa0adde4 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 14:30:49 +0200 Subject: [PATCH 17/24] virtual-io: store files as arrayList instead of Writer.Allocating --- modules/virtual-io/src/root.zig | 37 +++++++++++++-------------------- tools/regz/src/gen.zig | 2 +- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 0cda45e39..0785e76ba 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -10,21 +10,16 @@ const log = std.log.scoped(.vio); pub const Node = union(enum) { pub const ID = std.posix.fd_t; - - pub const CreateInfo = union(std.meta.Tag(Node)) { - file: []const u8, - dir, - }; + pub const Kind = std.meta.Tag(Node); const Map = std.AutoArrayHashMapUnmanaged(ID, Node); - const Directory = std.StringHashMapUnmanaged(ID); - file: std.Io.Writer.Allocating, - dir: Directory, + file: std.ArrayList(u8), + dir: std.StringHashMapUnmanaged(ID), pub fn destroy(node: *Node, gpa: Allocator) void { switch (node.*) { - .file => |*w| w.deinit(), + .file => |*w| w.deinit(gpa), .dir => |*entries| { var it = entries.keyIterator(); while (it.next()) |name| @@ -38,8 +33,6 @@ pub const Node = union(enum) { pub const VirtualIo = @This(); pub const vtable: *const std.Io.VTable = blk: { - var ret = std.Io.failing.vtable.*; - const VTable = struct { fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); @@ -77,7 +70,7 @@ pub const vtable: *const std.Io.VTable = blk: { ) File.OpenError!File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return .{ - .handle = try vio.create_node(dir, sub_path, .{ .file = "" }), + .handle = try vio.create_node(dir, sub_path, .file), .flags = .{ .nonblocking = false }, }; } @@ -104,6 +97,7 @@ pub const vtable: *const std.Io.VTable = blk: { fn file_close(_: ?*anyopaque, _: []const File) void {} }; + var ret = std.Io.failing.vtable.*; ret.operate = VTable.operate; ret.dirCreateDirPathOpen = VTable.create_dir_path_open; ret.dirClose = VTable.dir_close; @@ -173,19 +167,14 @@ pub fn get_node(vio: *const VirtualIo, dir: Dir, path: []const u8) !Node.ID { } else return id; } -fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.CreateInfo) !Node.ID { +fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, kind: Node.Kind) !Node.ID { if (std.mem.findScalar(u8, sub_path, '/') != null) { log.err("path includes '/': '{s}'", .{sub_path}); return error.BadPathName; } - const node: Node = switch (info) { - .file => |content| blk: { - var w: std.Io.Writer.Allocating = .init(vio.gpa); - w.writer.writeAll(content) catch - return error.NoSpaceLeft; - break :blk .{ .file = w }; - }, + const node: Node = switch (kind) { + .file => .{ .file = .empty }, .dir => .{ .dir = .empty }, }; @@ -213,11 +202,13 @@ fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, info: Node.Creat fn file_write_streaming(vio: *VirtualIo, op: std.Io.Operation.FileWriteStreaming) std.Io.Operation.FileWriteStreaming.Result { const node = vio.nodes.getPtr(op.file.handle) orelse return error.Unexpected; - const w = switch (node.*) { - .file => |*allocating| &allocating.writer, + const file = switch (node.*) { + .file => |*data| data, .dir => return error.Unexpected, }; - return w.writeSplatHeader( + var allocating: std.Io.Writer.Allocating = .fromArrayList(vio.gpa, file); + defer file.* = allocating.toArrayList(); + return allocating.writer.writeSplatHeader( op.header, op.data, op.splat, diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index bb811e28f..2482a47ad 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1740,7 +1740,7 @@ fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesyst for (expected_outputs) |eo| { const node_id = try vfs.get_node(VirtualFilesystem.root_dir, eo.path); const node = vfs.nodes.getPtr(node_id) orelse unreachable; - try std.testing.expectEqualStrings(eo.content, node.file.written()); + try std.testing.expectEqualStrings(eo.content, node.file.items); } } From 5260788b90c568e7a53cf0c765e97718eef67307 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 18:07:04 +0200 Subject: [PATCH 18/24] virtual-io: Use enums as IDs again --- modules/virtual-io/src/root.zig | 91 ++++++++++++++++++++------------- tools/regz/src/gen.zig | 2 +- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 0785e76ba..15d950d9f 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -9,13 +9,46 @@ const File = std.Io.File; const log = std.log.scoped(.vio); pub const Node = union(enum) { - pub const ID = std.posix.fd_t; + pub const Dir = std.StringHashMapUnmanaged(ID); + pub const File = std.ArrayList(u8); pub const Kind = std.meta.Tag(Node); + pub const Map = std.AutoArrayHashMapUnmanaged(ID, Node); + + pub const ID = enum(u16) { + const Backing = @Int(.unsigned, @bitSizeOf(ID)); + + // Reserve first 256 file handles for os-specific values, such as sitdin/out/err + // on posix, cwd on wasm and the reserved NULL value on windows. + root = 0x100, + _, + + pub fn from_handle(handle: std.posix.fd_t) !ID { + return if (std.math.cast(Backing, switch (builtin.os.tag) { + .windows => @intFromPtr(handle), + else => handle, + })) |int| + @enumFromInt(int) + else + error.Unexpected; + } - const Map = std.AutoArrayHashMapUnmanaged(ID, Node); + pub fn to_handle(id: ID) std.posix.fd_t { + return switch (builtin.os.tag) { + .windows => @ptrFromInt(@intFromEnum(id)), + else => @intFromEnum(id), + }; + } + + fn next(prev: ID) !ID { + return if (std.math.add(Backing, @intFromEnum(prev), 1)) |int| + @enumFromInt(int) + else |_| + error.Unexpected; + } + }; - file: std.ArrayList(u8), - dir: std.StringHashMapUnmanaged(ID), + file: Node.File, + dir: Node.Dir, pub fn destroy(node: *Node, gpa: Allocator) void { switch (node.*) { @@ -56,7 +89,7 @@ pub const vtable: *const std.Io.VTable = blk: { ) Dir.CreateDirPathOpenError!Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return .{ - .handle = try vio.create_node(dir, sub_path, .dir), + .handle = (try vio.create_node(try .from_handle(dir.handle), sub_path, .dir)).to_handle(), }; } @@ -70,7 +103,7 @@ pub const vtable: *const std.Io.VTable = blk: { ) File.OpenError!File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return .{ - .handle = try vio.create_node(dir, sub_path, .file), + .handle = (try vio.create_node(try .from_handle(dir.handle), sub_path, .file)).to_handle(), .flags = .{ .nonblocking = false }, }; } @@ -82,12 +115,12 @@ pub const vtable: *const std.Io.VTable = blk: { _: Dir.OpenFileOptions, ) File.OpenError!File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const node_id = try vio.get_node(dir, sub_path); + const node_id = try vio.get_node(try .from_handle(dir.handle), sub_path); const node = vio.nodes.get(node_id) orelse return error.FileNotFound; return switch (node) { .file => .{ - .handle = node_id, + .handle = node_id.to_handle(), .flags = .{ .nonblocking = false }, }, .dir => error.IsDir, @@ -109,16 +142,11 @@ pub const vtable: *const std.Io.VTable = blk: { break :blk &ret_const; }; -// Don't use file descriptor 0, because on windows, fd_t is actually a *anyopaque, -// and setting that null under the hood is ILLEGAL, and I don't want to go to jail. -const root_dir_handle = 1; -pub const root_dir: Dir = .{ - .handle = handle_from_int(root_dir_handle), -}; +pub const root_dir: Dir = .{ .handle = Node.ID.root.to_handle() }; gpa: Allocator, nodes: Node.Map, -last_id: u16, +last_id: Node.ID, pub fn io(vio: *VirtualIo) std.Io { return .{ .userdata = vio, .vtable = vtable }; @@ -126,11 +154,11 @@ pub fn io(vio: *VirtualIo) std.Io { pub fn init(gpa: Allocator) !VirtualIo { var nodes: Node.Map = .empty; - try nodes.put(gpa, root_dir.handle, .{ .dir = .empty }); + try nodes.put(gpa, .root, .{ .dir = .empty }); return .{ .gpa = gpa, .nodes = nodes, - .last_id = root_dir_handle, + .last_id = .root, }; } @@ -150,11 +178,11 @@ pub fn total_file_count(vio: *const VirtualIo) usize { return ret; } -pub fn get_node(vio: *const VirtualIo, dir: Dir, path: []const u8) !Node.ID { +pub fn get_node(vio: *const VirtualIo, dir: Node.ID, path: []const u8) !Node.ID { const idx_opt = std.mem.findScalar(u8, path, '/'); const path_part = path[0 .. idx_opt orelse path.len]; - const dir_node = vio.nodes.getEntry(dir.handle) orelse + const dir_node = vio.nodes.getEntry(dir) orelse return error.Unexpected; const id = switch (dir_node.value_ptr.*) { @@ -163,11 +191,11 @@ pub fn get_node(vio: *const VirtualIo, dir: Dir, path: []const u8) !Node.ID { }; if (idx_opt) |idx| { - return try vio.get_node(.{ .handle = id }, path[idx + 1 ..]); + return try vio.get_node(id, path[idx + 1 ..]); } else return id; } -fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, kind: Node.Kind) !Node.ID { +fn create_node(vio: *VirtualIo, dir: Node.ID, sub_path: []const u8, kind: Node.Kind) !Node.ID { if (std.mem.findScalar(u8, sub_path, '/') != null) { log.err("path includes '/': '{s}'", .{sub_path}); return error.BadPathName; @@ -178,29 +206,29 @@ fn create_node(vio: *VirtualIo, dir: Dir, sub_path: []const u8, kind: Node.Kind) .dir => .{ .dir = .empty }, }; - vio.last_id += 1; - const handle = handle_from_int(vio.last_id); + const id: Node.ID = try vio.last_id.next(); + vio.last_id = id; - vio.nodes.put(vio.gpa, handle, node) catch + vio.nodes.put(vio.gpa, id, node) catch return error.NoSpaceLeft; - const dir_node = vio.nodes.getPtr(dir.handle) orelse + const dir_node = vio.nodes.getPtr(dir) orelse return error.Unexpected; const name = vio.gpa.dupe(u8, sub_path) catch return error.NoSpaceLeft; switch (dir_node.*) { - .dir => |*d| d.put(vio.gpa, name, handle) catch + .dir => |*d| d.put(vio.gpa, name, id) catch return error.NoSpaceLeft, else => return error.Unexpected, } - return handle; + return id; } fn file_write_streaming(vio: *VirtualIo, op: std.Io.Operation.FileWriteStreaming) std.Io.Operation.FileWriteStreaming.Result { - const node = vio.nodes.getPtr(op.file.handle) orelse + const node = vio.nodes.getPtr(try .from_handle(op.file.handle)) orelse return error.Unexpected; const file = switch (node.*) { .file => |*data| data, @@ -214,10 +242,3 @@ fn file_write_streaming(vio: *VirtualIo, op: std.Io.Operation.FileWriteStreaming op.splat, ) catch error.NoSpaceLeft; } - -fn handle_from_int(int: u16) Node.ID { - return switch (builtin.os.tag) { - .windows => @ptrFromInt(int), - else => int, - }; -} diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 2482a47ad..b32518a4c 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1738,7 +1738,7 @@ const ExpectedOutput = struct { fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { - const node_id = try vfs.get_node(VirtualFilesystem.root_dir, eo.path); + const node_id = try vfs.get_node(.root, eo.path); const node = vfs.nodes.getPtr(node_id) orelse unreachable; try std.testing.expectEqualStrings(eo.content, node.file.items); } From 9aaffeb8b2a26ad8a812f54c3fc6b330fc850c48 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 21:54:04 +0200 Subject: [PATCH 19/24] virtual-io: Type safety around directory --- modules/virtual-io/src/root.zig | 191 +++++++++++++++++--------------- tools/regz/src/gen.zig | 5 +- 2 files changed, 103 insertions(+), 93 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 15d950d9f..9c465012e 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -4,15 +4,73 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const Dir = std.Io.Dir; -const File = std.Io.File; +const Io = std.Io; const log = std.log.scoped(.vio); +pub const Dir = struct { + inner: std.StringHashMapUnmanaged(Node.ID) = .empty, + + pub fn from_std(vio: *const VirtualIo, dir: Io.Dir) !*Dir { + return from_id(vio, try .from_handle(dir.handle)); + } + + pub fn from_id(vio: *const VirtualIo, id: Node.ID) !*Dir { + const node = vio.nodes.getPtr(id) orelse + return error.Unexpected; + return switch (node.*) { + .dir => |*ret| ret, + else => error.NotDir, + }; + } + + pub fn sub_path(parent: *Dir, vio: *const VirtualIo, path: []const u8) !Node.ID { + var dir = parent; + var path_tail = path; + while (std.mem.findScalar(u8, path_tail, '/')) |idx| { + dir = try .from_id(vio, dir.inner.get(path_tail[0..idx]) orelse + return error.FileNotFound); + path_tail = path_tail[idx + 1 ..]; + } + return dir.inner.get(path_tail) orelse error.FileNotFound; + } + + pub fn create_node(dir: *Dir, vio: *VirtualIo, name: []const u8, comptime kind: Node.Kind) !Node.ID { + if (std.mem.findScalar(u8, name, '/') != null) { + log.err("name includes '/': '{s}'", .{name}); + return error.BadPathName; + } + + const id: Node.ID = try vio.last_id.next(); + vio.last_id = id; + + const name_owned = vio.gpa.dupe(u8, name) catch return error.NoSpaceLeft; + errdefer vio.gpa.free(name_owned); + + if (dir.inner.getOrPut(vio.gpa, name_owned)) |result| { + if (result.found_existing) + return error.PathAlreadyExists; + + result.value_ptr.* = id; + } else |_| return error.NoSpaceLeft; + + if (vio.nodes.getOrPut(vio.gpa, id)) |result| { + if (result.found_existing) + return error.PathAlreadyExists; + + result.value_ptr.* = switch (kind) { + .file => .{ .file = .empty }, + .dir => .{ .dir = .{} }, + }; + } else |_| return error.NoSpaceLeft; + + return id; + } +}; + pub const Node = union(enum) { - pub const Dir = std.StringHashMapUnmanaged(ID); - pub const File = std.ArrayList(u8); pub const Kind = std.meta.Tag(Node); pub const Map = std.AutoArrayHashMapUnmanaged(ID, Node); + pub const File = std.ArrayList(u8); pub const ID = enum(u16) { const Backing = @Int(.unsigned, @bitSizeOf(ID)); @@ -47,17 +105,17 @@ pub const Node = union(enum) { } }; - file: Node.File, - dir: Node.Dir, + file: File, + dir: Dir, pub fn destroy(node: *Node, gpa: Allocator) void { switch (node.*) { - .file => |*w| w.deinit(gpa), - .dir => |*entries| { - var it = entries.keyIterator(); + .file => |*file| file.deinit(gpa), + .dir => |*dir| { + var it = dir.inner.keyIterator(); while (it.next()) |name| gpa.free(name.*); - entries.deinit(gpa); + dir.inner.deinit(gpa); }, } } @@ -65,9 +123,9 @@ pub const Node = union(enum) { pub const VirtualIo = @This(); -pub const vtable: *const std.Io.VTable = blk: { +pub const vtable: *const Io.VTable = blk: { const VTable = struct { - fn operate(userdata: ?*anyopaque, op: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result { + fn operate(userdata: ?*anyopaque, op: Io.Operation) Io.Cancelable!Io.Operation.Result { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return switch (op) { .file_write_streaming => |write_op| .{ @@ -82,55 +140,53 @@ pub const vtable: *const std.Io.VTable = blk: { fn create_dir_path_open( userdata: ?*anyopaque, - dir: Dir, + dir: Io.Dir, sub_path: []const u8, - _: Dir.Permissions, - _: Dir.OpenOptions, - ) Dir.CreateDirPathOpenError!Dir { + _: Io.Dir.Permissions, + _: Io.Dir.OpenOptions, + ) Io.Dir.CreateDirPathOpenError!Io.Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return .{ - .handle = (try vio.create_node(try .from_handle(dir.handle), sub_path, .dir)).to_handle(), - }; + const parent = try Dir.from_std(vio, dir); + const ret = try parent.create_node(vio, sub_path, .dir); + return .{ .handle = ret.to_handle() }; } - fn dir_close(_: ?*anyopaque, _: []const Dir) void {} + fn dir_close(_: ?*anyopaque, _: []const Io.Dir) void {} fn dir_create_file( userdata: ?*anyopaque, - dir: Dir, + dir: Io.Dir, sub_path: []const u8, - _: Dir.CreateFileOptions, - ) File.OpenError!File { + _: Io.Dir.CreateFileOptions, + ) Io.File.OpenError!Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const parent = try Dir.from_std(vio, dir); + const ret = try parent.create_node(vio, sub_path, .file); return .{ - .handle = (try vio.create_node(try .from_handle(dir.handle), sub_path, .file)).to_handle(), + .handle = ret.to_handle(), .flags = .{ .nonblocking = false }, }; } fn dir_open_file( userdata: ?*anyopaque, - dir: Dir, + dir: Io.Dir, sub_path: []const u8, - _: Dir.OpenFileOptions, - ) File.OpenError!File { + _: Io.Dir.OpenFileOptions, + ) Io.File.OpenError!Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const node_id = try vio.get_node(try .from_handle(dir.handle), sub_path); - const node = vio.nodes.get(node_id) orelse - return error.FileNotFound; - return switch (node) { - .file => .{ - .handle = node_id.to_handle(), - .flags = .{ .nonblocking = false }, - }, - .dir => error.IsDir, + const parent = try Dir.from_std(vio, dir); + const node = try parent.sub_path(vio, sub_path); + return .{ + .handle = node.to_handle(), + .flags = .{ .nonblocking = false }, }; } - fn file_close(_: ?*anyopaque, _: []const File) void {} + fn file_close(_: ?*anyopaque, _: []const Io.File) void {} }; - var ret = std.Io.failing.vtable.*; + var ret = Io.failing.vtable.*; ret.operate = VTable.operate; ret.dirCreateDirPathOpen = VTable.create_dir_path_open; ret.dirClose = VTable.dir_close; @@ -142,19 +198,19 @@ pub const vtable: *const std.Io.VTable = blk: { break :blk &ret_const; }; -pub const root_dir: Dir = .{ .handle = Node.ID.root.to_handle() }; +pub const root_dir: Io.Dir = .{ .handle = Node.ID.root.to_handle() }; gpa: Allocator, nodes: Node.Map, last_id: Node.ID, -pub fn io(vio: *VirtualIo) std.Io { +pub fn io(vio: *VirtualIo) Io { return .{ .userdata = vio, .vtable = vtable }; } pub fn init(gpa: Allocator) !VirtualIo { var nodes: Node.Map = .empty; - try nodes.put(gpa, .root, .{ .dir = .empty }); + try nodes.put(gpa, .root, .{ .dir = .{} }); return .{ .gpa = gpa, .nodes = nodes, @@ -178,63 +234,18 @@ pub fn total_file_count(vio: *const VirtualIo) usize { return ret; } -pub fn get_node(vio: *const VirtualIo, dir: Node.ID, path: []const u8) !Node.ID { - const idx_opt = std.mem.findScalar(u8, path, '/'); - const path_part = path[0 .. idx_opt orelse path.len]; - - const dir_node = vio.nodes.getEntry(dir) orelse - return error.Unexpected; - - const id = switch (dir_node.value_ptr.*) { - .dir => |d| d.get(path_part) orelse return error.FileNotFound, - else => return error.FileNotFound, - }; - - if (idx_opt) |idx| { - return try vio.get_node(id, path[idx + 1 ..]); - } else return id; -} - -fn create_node(vio: *VirtualIo, dir: Node.ID, sub_path: []const u8, kind: Node.Kind) !Node.ID { - if (std.mem.findScalar(u8, sub_path, '/') != null) { - log.err("path includes '/': '{s}'", .{sub_path}); - return error.BadPathName; - } - - const node: Node = switch (kind) { - .file => .{ .file = .empty }, - .dir => .{ .dir = .empty }, - }; - - const id: Node.ID = try vio.last_id.next(); - vio.last_id = id; - - vio.nodes.put(vio.gpa, id, node) catch - return error.NoSpaceLeft; - - const dir_node = vio.nodes.getPtr(dir) orelse - return error.Unexpected; - - const name = vio.gpa.dupe(u8, sub_path) catch - return error.NoSpaceLeft; - - switch (dir_node.*) { - .dir => |*d| d.put(vio.gpa, name, id) catch - return error.NoSpaceLeft, - else => return error.Unexpected, - } - - return id; +pub fn file_contents(vio: *const VirtualIo, file: Io.File) !*Node.File { + return &vio.nodes.getPtr(try .from_handle(file.handle)).?.file; } -fn file_write_streaming(vio: *VirtualIo, op: std.Io.Operation.FileWriteStreaming) std.Io.Operation.FileWriteStreaming.Result { +fn file_write_streaming(vio: *VirtualIo, op: Io.Operation.FileWriteStreaming) Io.Operation.FileWriteStreaming.Result { const node = vio.nodes.getPtr(try .from_handle(op.file.handle)) orelse return error.Unexpected; const file = switch (node.*) { .file => |*data| data, .dir => return error.Unexpected, }; - var allocating: std.Io.Writer.Allocating = .fromArrayList(vio.gpa, file); + var allocating: Io.Writer.Allocating = .fromArrayList(vio.gpa, file); defer file.* = allocating.toArrayList(); return allocating.writer.writeSplatHeader( op.header, diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index b32518a4c..a360183c9 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -1738,9 +1738,8 @@ const ExpectedOutput = struct { fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); for (expected_outputs) |eo| { - const node_id = try vfs.get_node(.root, eo.path); - const node = vfs.nodes.getPtr(node_id) orelse unreachable; - try std.testing.expectEqualStrings(eo.content, node.file.items); + const file = try VirtualFilesystem.root_dir.openFile(vfs.io(), eo.path, .{}); + try std.testing.expectEqualStrings(eo.content, (try vfs.file_contents(file)).items); } } From 94c72ae4e9b7a17a1b63d4987832801853bc2f08 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 22:26:11 +0200 Subject: [PATCH 20/24] virtual-io: Type safety around file --- modules/virtual-io/src/root.zig | 56 ++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 9c465012e..d2621560e 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -58,7 +58,7 @@ pub const Dir = struct { return error.PathAlreadyExists; result.value_ptr.* = switch (kind) { - .file => .{ .file = .empty }, + .file => .{ .file = .{} }, .dir => .{ .dir = .{} }, }; } else |_| return error.NoSpaceLeft; @@ -67,10 +67,38 @@ pub const Dir = struct { } }; +pub const File = struct { + inner: std.ArrayList(u8) = .empty, + + pub fn from_std(vio: *const VirtualIo, file: Io.File) !*File { + return from_id(vio, try .from_handle(file.handle)); + } + + pub fn from_id(vio: *const VirtualIo, id: Node.ID) !*File { + const node = vio.nodes.getPtr(id) orelse + return error.Unexpected; + return switch (node.*) { + .file => |*ret| ret, + .dir => error.IsDir, + }; + } + + pub fn write_streaming( + vio: *const VirtualIo, + op: Io.Operation.FileWriteStreaming, + ) Io.Operation.FileWriteStreaming.Result { + const file = File.from_std(vio, op.file) catch + return error.Unexpected; + var allocating: Io.Writer.Allocating = .fromArrayList(vio.gpa, &file.inner); + defer file.inner = allocating.toArrayList(); + return allocating.writer.writeSplatHeader(op.header, op.data, op.splat) catch + error.NoSpaceLeft; + } +}; + pub const Node = union(enum) { pub const Kind = std.meta.Tag(Node); pub const Map = std.AutoArrayHashMapUnmanaged(ID, Node); - pub const File = std.ArrayList(u8); pub const ID = enum(u16) { const Backing = @Int(.unsigned, @bitSizeOf(ID)); @@ -110,7 +138,7 @@ pub const Node = union(enum) { pub fn destroy(node: *Node, gpa: Allocator) void { switch (node.*) { - .file => |*file| file.deinit(gpa), + .file => |*file| file.inner.deinit(gpa), .dir => |*dir| { var it = dir.inner.keyIterator(); while (it.next()) |name| @@ -129,7 +157,7 @@ pub const vtable: *const Io.VTable = blk: { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); return switch (op) { .file_write_streaming => |write_op| .{ - .file_write_streaming = vio.file_write_streaming(write_op), + .file_write_streaming = File.write_streaming(vio, write_op), }, .file_read_streaming => .{ .file_read_streaming = error.InputOutput }, .device_io_control => unreachable, @@ -234,22 +262,6 @@ pub fn total_file_count(vio: *const VirtualIo) usize { return ret; } -pub fn file_contents(vio: *const VirtualIo, file: Io.File) !*Node.File { - return &vio.nodes.getPtr(try .from_handle(file.handle)).?.file; -} - -fn file_write_streaming(vio: *VirtualIo, op: Io.Operation.FileWriteStreaming) Io.Operation.FileWriteStreaming.Result { - const node = vio.nodes.getPtr(try .from_handle(op.file.handle)) orelse - return error.Unexpected; - const file = switch (node.*) { - .file => |*data| data, - .dir => return error.Unexpected, - }; - var allocating: Io.Writer.Allocating = .fromArrayList(vio.gpa, file); - defer file.* = allocating.toArrayList(); - return allocating.writer.writeSplatHeader( - op.header, - op.data, - op.splat, - ) catch error.NoSpaceLeft; +pub fn file_contents(vio: *const VirtualIo, file: Io.File) !*std.ArrayList(u8) { + return &(try File.from_std(vio, file)).inner; } From fa330ed325d2adede91c4afd96d16abc94db9d44 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 16 Jul 2026 22:29:09 +0200 Subject: [PATCH 21/24] virtual-io: Redo namespacing --- modules/virtual-io/src/root.zig | 208 ++++++++++++++++---------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index d2621560e..555d96d91 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -149,119 +149,119 @@ pub const Node = union(enum) { } }; -pub const VirtualIo = @This(); - -pub const vtable: *const Io.VTable = blk: { - const VTable = struct { - fn operate(userdata: ?*anyopaque, op: Io.Operation) Io.Cancelable!Io.Operation.Result { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - return switch (op) { - .file_write_streaming => |write_op| .{ - .file_write_streaming = File.write_streaming(vio, write_op), - }, - .file_read_streaming => .{ .file_read_streaming = error.InputOutput }, - .device_io_control => unreachable, - .net_receive => .{ .net_receive = .{ error.NetworkDown, 0 } }, - .net_read => .{ .net_read = error.NetworkDown }, - }; - } +const VTable = struct { + fn operate(userdata: ?*anyopaque, op: Io.Operation) Io.Cancelable!Io.Operation.Result { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + return switch (op) { + .file_write_streaming => |write_op| .{ + .file_write_streaming = File.write_streaming(vio, write_op), + }, + .file_read_streaming => .{ .file_read_streaming = error.InputOutput }, + .device_io_control => unreachable, + .net_receive => .{ .net_receive = .{ error.NetworkDown, 0 } }, + .net_read => .{ .net_read = error.NetworkDown }, + }; + } - fn create_dir_path_open( - userdata: ?*anyopaque, - dir: Io.Dir, - sub_path: []const u8, - _: Io.Dir.Permissions, - _: Io.Dir.OpenOptions, - ) Io.Dir.CreateDirPathOpenError!Io.Dir { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = try Dir.from_std(vio, dir); - const ret = try parent.create_node(vio, sub_path, .dir); - return .{ .handle = ret.to_handle() }; - } + fn create_dir_path_open( + userdata: ?*anyopaque, + dir: Io.Dir, + sub_path: []const u8, + _: Io.Dir.Permissions, + _: Io.Dir.OpenOptions, + ) Io.Dir.CreateDirPathOpenError!Io.Dir { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const parent = try Dir.from_std(vio, dir); + const ret = try parent.create_node(vio, sub_path, .dir); + return .{ .handle = ret.to_handle() }; + } - fn dir_close(_: ?*anyopaque, _: []const Io.Dir) void {} - - fn dir_create_file( - userdata: ?*anyopaque, - dir: Io.Dir, - sub_path: []const u8, - _: Io.Dir.CreateFileOptions, - ) Io.File.OpenError!Io.File { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = try Dir.from_std(vio, dir); - const ret = try parent.create_node(vio, sub_path, .file); - return .{ - .handle = ret.to_handle(), - .flags = .{ .nonblocking = false }, - }; - } + fn dir_close(_: ?*anyopaque, _: []const Io.Dir) void {} + + fn dir_create_file( + userdata: ?*anyopaque, + dir: Io.Dir, + sub_path: []const u8, + _: Io.Dir.CreateFileOptions, + ) Io.File.OpenError!Io.File { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const parent = try Dir.from_std(vio, dir); + const ret = try parent.create_node(vio, sub_path, .file); + return .{ + .handle = ret.to_handle(), + .flags = .{ .nonblocking = false }, + }; + } - fn dir_open_file( - userdata: ?*anyopaque, - dir: Io.Dir, - sub_path: []const u8, - _: Io.Dir.OpenFileOptions, - ) Io.File.OpenError!Io.File { - const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = try Dir.from_std(vio, dir); - const node = try parent.sub_path(vio, sub_path); - return .{ - .handle = node.to_handle(), - .flags = .{ .nonblocking = false }, - }; - } + fn dir_open_file( + userdata: ?*anyopaque, + dir: Io.Dir, + sub_path: []const u8, + _: Io.Dir.OpenFileOptions, + ) Io.File.OpenError!Io.File { + const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); + const parent = try Dir.from_std(vio, dir); + const node = try parent.sub_path(vio, sub_path); + return .{ + .handle = node.to_handle(), + .flags = .{ .nonblocking = false }, + }; + } - fn file_close(_: ?*anyopaque, _: []const Io.File) void {} - }; + fn file_close(_: ?*anyopaque, _: []const Io.File) void {} +}; - var ret = Io.failing.vtable.*; - ret.operate = VTable.operate; - ret.dirCreateDirPathOpen = VTable.create_dir_path_open; - ret.dirClose = VTable.dir_close; - ret.dirCreateFile = VTable.dir_create_file; - ret.dirOpenFile = VTable.dir_open_file; - ret.fileClose = VTable.file_close; +pub const VirtualIo = struct { + pub const root_dir: Io.Dir = .{ .handle = Node.ID.root.to_handle() }; - const ret_const = ret; - break :blk &ret_const; -}; + pub const vtable: *const Io.VTable = blk: { + var ret = Io.failing.vtable.*; + ret.operate = VTable.operate; + ret.dirCreateDirPathOpen = VTable.create_dir_path_open; + ret.dirClose = VTable.dir_close; + ret.dirCreateFile = VTable.dir_create_file; + ret.dirOpenFile = VTable.dir_open_file; + ret.fileClose = VTable.file_close; -pub const root_dir: Io.Dir = .{ .handle = Node.ID.root.to_handle() }; + const ret_const = ret; + break :blk &ret_const; + }; -gpa: Allocator, -nodes: Node.Map, -last_id: Node.ID, + gpa: Allocator, + nodes: Node.Map, + last_id: Node.ID, -pub fn io(vio: *VirtualIo) Io { - return .{ .userdata = vio, .vtable = vtable }; -} + pub fn io(vio: *VirtualIo) Io { + return .{ .userdata = vio, .vtable = vtable }; + } -pub fn init(gpa: Allocator) !VirtualIo { - var nodes: Node.Map = .empty; - try nodes.put(gpa, .root, .{ .dir = .{} }); - return .{ - .gpa = gpa, - .nodes = nodes, - .last_id = .root, - }; -} - -pub fn deinit(vio: *VirtualIo) void { - for (vio.nodes.values()) |*node| - node.destroy(vio.gpa); - vio.nodes.deinit(vio.gpa); -} - -pub fn total_file_count(vio: *const VirtualIo) usize { - var ret: usize = 0; - var it = vio.nodes.iterator(); - while (it.next()) |entry| { - if (entry.value_ptr.* == .file) - ret += 1; + pub fn init(gpa: Allocator) !VirtualIo { + var nodes: Node.Map = .empty; + try nodes.put(gpa, .root, .{ .dir = .{} }); + return .{ + .gpa = gpa, + .nodes = nodes, + .last_id = .root, + }; } - return ret; -} -pub fn file_contents(vio: *const VirtualIo, file: Io.File) !*std.ArrayList(u8) { - return &(try File.from_std(vio, file)).inner; -} + pub fn deinit(vio: *VirtualIo) void { + for (vio.nodes.values()) |*node| + node.destroy(vio.gpa); + vio.nodes.deinit(vio.gpa); + } + + pub fn total_file_count(vio: *const VirtualIo) usize { + var ret: usize = 0; + var it = vio.nodes.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.* == .file) + ret += 1; + } + return ret; + } + + pub fn file_contents(vio: *const VirtualIo, file: Io.File) !*std.ArrayList(u8) { + return &(try File.from_std(vio, file)).inner; + } +}; From fe49b56ba6432b59981f07723230c0d2fe83c397 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 17 Jul 2026 02:14:08 +0200 Subject: [PATCH 22/24] virtual-io: Separate IDs for files and directories --- modules/virtual-io/src/root.zig | 199 +++++++++++++++++--------------- 1 file changed, 108 insertions(+), 91 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 555d96d91..9be0dc152 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -7,40 +7,67 @@ const assert = std.debug.assert; const Io = std.Io; const log = std.log.scoped(.vio); +pub fn ResultID(T: type) type { + return switch (T) { + Dir, File => T.ID, + else => @compileError("Expected File or Dir, got " ++ @typeName(T)), + }; +} + pub const Dir = struct { - inner: std.StringHashMapUnmanaged(Node.ID) = .empty, + pub const kind: Node.Kind = .dir; + pub const empty: Node = .{ .dir = .{ .inner = .empty } }; - pub fn from_std(vio: *const VirtualIo, dir: Io.Dir) !*Dir { - return from_id(vio, try .from_handle(dir.handle)); - } + pub const ID = enum(Node.ID) { + // Reserve first 256 file handles for os-specific values, such as sitdin/out/err + // on posix, cwd on wasm and the reserved NULL value on windows. + root = 0x100, + _, - pub fn from_id(vio: *const VirtualIo, id: Node.ID) !*Dir { - const node = vio.nodes.getPtr(id) orelse - return error.Unexpected; - return switch (node.*) { - .dir => |*ret| ret, - else => error.NotDir, - }; - } + pub fn from_std(dir: Io.Dir) !ID { + return from_handle(ID, dir.handle); + } + + pub fn to_std(id: ID) Io.Dir { + return .{ .handle = to_handle(id) }; + } - pub fn sub_path(parent: *Dir, vio: *const VirtualIo, path: []const u8) !Node.ID { + pub fn get(id: ID, vio: *const VirtualIo) !*Dir { + const node = vio.nodes.getPtr(@intFromEnum(id)) orelse + return error.Unexpected; + return switch (node.*) { + .dir => |*ret| ret, + else => error.NotDir, + }; + } + }; + + inner: std.StringHashMapUnmanaged(Node.ID) = .empty, + + pub fn open(parent: *Dir, vio: *const VirtualIo, sub_path: []const u8) !Node.ID { var dir = parent; - var path_tail = path; + var path_tail = sub_path; while (std.mem.findScalar(u8, path_tail, '/')) |idx| { - dir = try .from_id(vio, dir.inner.get(path_tail[0..idx]) orelse - return error.FileNotFound); + const next = dir.inner.get(path_tail[0..idx]) orelse + return error.FileNotFound; + const node = vio.nodes.getPtr(next) orelse + return error.Unexpected; + switch (node.*) { + .dir => |*d| dir = d, + .file => return error.FileNotFound, + } path_tail = path_tail[idx + 1 ..]; } return dir.inner.get(path_tail) orelse error.FileNotFound; } - pub fn create_node(dir: *Dir, vio: *VirtualIo, name: []const u8, comptime kind: Node.Kind) !Node.ID { + pub fn create(dir: *Dir, vio: *VirtualIo, name: []const u8, T: type) !ResultID(T) { if (std.mem.findScalar(u8, name, '/') != null) { log.err("name includes '/': '{s}'", .{name}); return error.BadPathName; } - const id: Node.ID = try vio.last_id.next(); + const id = vio.last_id + 1; vio.last_id = id; const name_owned = vio.gpa.dupe(u8, name) catch return error.NoSpaceLeft; @@ -57,37 +84,48 @@ pub const Dir = struct { if (result.found_existing) return error.PathAlreadyExists; - result.value_ptr.* = switch (kind) { - .file => .{ .file = .{} }, - .dir => .{ .dir = .{} }, - }; + result.value_ptr.* = T.empty; } else |_| return error.NoSpaceLeft; - return id; + return @enumFromInt(id); } }; pub const File = struct { - inner: std.ArrayList(u8) = .empty, + pub const kind: Node.Kind = .dir; + pub const empty: Node = .{ .file = .{ .inner = .empty } }; - pub fn from_std(vio: *const VirtualIo, file: Io.File) !*File { - return from_id(vio, try .from_handle(file.handle)); - } + pub const ID = enum(Node.ID) { + _, - pub fn from_id(vio: *const VirtualIo, id: Node.ID) !*File { - const node = vio.nodes.getPtr(id) orelse - return error.Unexpected; - return switch (node.*) { - .file => |*ret| ret, - .dir => error.IsDir, - }; - } + pub fn from_std(file: Io.File) !ID { + return from_handle(ID, file.handle); + } + + pub fn to_std(id: ID) Io.File { + return .{ + .handle = to_handle(id), + .flags = .{ .nonblocking = false }, + }; + } + + pub fn get(id: ID, vio: *const VirtualIo) !*File { + const node = vio.nodes.getPtr(@intFromEnum(id)) orelse + return error.Unexpected; + return switch (node.*) { + .file => |*ret| ret, + .dir => error.IsDir, + }; + } + }; + + inner: std.ArrayList(u8) = .empty, pub fn write_streaming( vio: *const VirtualIo, op: Io.Operation.FileWriteStreaming, ) Io.Operation.FileWriteStreaming.Result { - const file = File.from_std(vio, op.file) catch + const file = (try File.ID.from_std(op.file)).get(vio) catch return error.Unexpected; var allocating: Io.Writer.Allocating = .fromArrayList(vio.gpa, &file.inner); defer file.inner = allocating.toArrayList(); @@ -97,42 +135,10 @@ pub const File = struct { }; pub const Node = union(enum) { + pub const ID = u16; pub const Kind = std.meta.Tag(Node); pub const Map = std.AutoArrayHashMapUnmanaged(ID, Node); - pub const ID = enum(u16) { - const Backing = @Int(.unsigned, @bitSizeOf(ID)); - - // Reserve first 256 file handles for os-specific values, such as sitdin/out/err - // on posix, cwd on wasm and the reserved NULL value on windows. - root = 0x100, - _, - - pub fn from_handle(handle: std.posix.fd_t) !ID { - return if (std.math.cast(Backing, switch (builtin.os.tag) { - .windows => @intFromPtr(handle), - else => handle, - })) |int| - @enumFromInt(int) - else - error.Unexpected; - } - - pub fn to_handle(id: ID) std.posix.fd_t { - return switch (builtin.os.tag) { - .windows => @ptrFromInt(@intFromEnum(id)), - else => @intFromEnum(id), - }; - } - - fn next(prev: ID) !ID { - return if (std.math.add(Backing, @intFromEnum(prev), 1)) |int| - @enumFromInt(int) - else |_| - error.Unexpected; - } - }; - file: File, dir: Dir, @@ -171,9 +177,8 @@ const VTable = struct { _: Io.Dir.OpenOptions, ) Io.Dir.CreateDirPathOpenError!Io.Dir { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = try Dir.from_std(vio, dir); - const ret = try parent.create_node(vio, sub_path, .dir); - return .{ .handle = ret.to_handle() }; + const parent = try (try Dir.ID.from_std(dir)).get(vio); + return (try parent.create(vio, sub_path, Dir)).to_std(); } fn dir_close(_: ?*anyopaque, _: []const Io.Dir) void {} @@ -185,12 +190,8 @@ const VTable = struct { _: Io.Dir.CreateFileOptions, ) Io.File.OpenError!Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = try Dir.from_std(vio, dir); - const ret = try parent.create_node(vio, sub_path, .file); - return .{ - .handle = ret.to_handle(), - .flags = .{ .nonblocking = false }, - }; + const parent = try (try Dir.ID.from_std(dir)).get(vio); + return (try parent.create(vio, sub_path, File)).to_std(); } fn dir_open_file( @@ -200,19 +201,16 @@ const VTable = struct { _: Io.Dir.OpenFileOptions, ) Io.File.OpenError!Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); - const parent = try Dir.from_std(vio, dir); - const node = try parent.sub_path(vio, sub_path); - return .{ - .handle = node.to_handle(), - .flags = .{ .nonblocking = false }, - }; + const parent = try (try Dir.ID.from_std(dir)).get(vio); + const node = try parent.open(vio, sub_path); + return @as(File.ID, @enumFromInt(node)).to_std(); } fn file_close(_: ?*anyopaque, _: []const Io.File) void {} }; pub const VirtualIo = struct { - pub const root_dir: Io.Dir = .{ .handle = Node.ID.root.to_handle() }; + pub const root_dir = Dir.ID.root.to_std(); pub const vtable: *const Io.VTable = blk: { var ret = Io.failing.vtable.*; @@ -236,13 +234,13 @@ pub const VirtualIo = struct { } pub fn init(gpa: Allocator) !VirtualIo { - var nodes: Node.Map = .empty; - try nodes.put(gpa, .root, .{ .dir = .{} }); - return .{ + var ret: VirtualIo = .{ .gpa = gpa, - .nodes = nodes, - .last_id = .root, + .nodes = .empty, + .last_id = @intFromEnum(Dir.ID.root), }; + try ret.nodes.put(gpa, ret.last_id, Dir.empty); + return ret; } pub fn deinit(vio: *VirtualIo) void { @@ -262,6 +260,25 @@ pub const VirtualIo = struct { } pub fn file_contents(vio: *const VirtualIo, file: Io.File) !*std.ArrayList(u8) { - return &(try File.from_std(vio, file)).inner; + return &(try (try File.ID.from_std(file)).get(vio)).inner; } }; + +pub fn from_handle(T: type, handle: std.posix.fd_t) !T { + const Backing = @Int(.unsigned, @bitSizeOf(T)); + return if (std.math.cast(Backing, switch (builtin.os.tag) { + .windows => @intFromPtr(handle), + else => handle, + })) |int| + @enumFromInt(int) + else + error.Unexpected; +} + +pub fn to_handle(id: anytype) std.posix.fd_t { + // const ID = @TypeOf(id); + return switch (builtin.os.tag) { + .windows => @ptrFromInt(@intFromEnum(id)), + else => @intFromEnum(id), + }; +} From b75acc6eed9a4e193b1ce0281184970ba93eacb6 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 17 Jul 2026 02:28:17 +0200 Subject: [PATCH 23/24] virtual-io: Additional error checking --- modules/virtual-io/src/root.zig | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index 9be0dc152..abb3a75bc 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -15,8 +15,8 @@ pub fn ResultID(T: type) type { } pub const Dir = struct { - pub const kind: Node.Kind = .dir; pub const empty: Node = .{ .dir = .{ .inner = .empty } }; + pub const kind: Node.Kind = empty; pub const ID = enum(Node.ID) { // Reserve first 256 file handles for os-specific values, such as sitdin/out/err @@ -44,7 +44,7 @@ pub const Dir = struct { inner: std.StringHashMapUnmanaged(Node.ID) = .empty, - pub fn open(parent: *Dir, vio: *const VirtualIo, sub_path: []const u8) !Node.ID { + pub fn open(parent: *Dir, vio: *const VirtualIo, sub_path: []const u8, T: type) !ResultID(T) { var dir = parent; var path_tail = sub_path; while (std.mem.findScalar(u8, path_tail, '/')) |idx| { @@ -58,7 +58,15 @@ pub const Dir = struct { } path_tail = path_tail[idx + 1 ..]; } - return dir.inner.get(path_tail) orelse error.FileNotFound; + const id = dir.inner.get(path_tail) orelse + return error.FileNotFound; + + // Check that node exists and is the right type + const node = vio.nodes.get(id); + if (node == null or node.? != T.kind) + return error.Unexpected; + + return @enumFromInt(id); } pub fn create(dir: *Dir, vio: *VirtualIo, name: []const u8, T: type) !ResultID(T) { @@ -92,8 +100,8 @@ pub const Dir = struct { }; pub const File = struct { - pub const kind: Node.Kind = .dir; pub const empty: Node = .{ .file = .{ .inner = .empty } }; + pub const kind: Node.Kind = empty; pub const ID = enum(Node.ID) { _, @@ -202,8 +210,7 @@ const VTable = struct { ) Io.File.OpenError!Io.File { const vio: *VirtualIo = @ptrCast(@alignCast(userdata.?)); const parent = try (try Dir.ID.from_std(dir)).get(vio); - const node = try parent.open(vio, sub_path); - return @as(File.ID, @enumFromInt(node)).to_std(); + return (try parent.open(vio, sub_path, File)).to_std(); } fn file_close(_: ?*anyopaque, _: []const Io.File) void {} From 9f6357a155b58de27c1362ee4c03951aa2e71585 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 17 Jul 2026 02:44:30 +0200 Subject: [PATCH 24/24] regz: Rename VirtualFilesystem to VirtualIo --- tools/regz/src/gen.zig | 242 ++++++++++++++++++++--------------------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index a360183c9..770f1d3dc 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -13,7 +13,7 @@ const DevicePeripheral = Database.DevicePeripheral; const EnumID = Database.EnumID; const StructID = Database.StructID; const NestedStructField = Database.NestedStructField; -const VirtualFilesystem = @import("virtual-io").VirtualIo; +const VirtualIo = @import("virtual-io").VirtualIo; const Properties = @import("properties.zig").Properties; const arm = @import("arch/arm.zig"); @@ -1735,11 +1735,11 @@ const ExpectedOutput = struct { content: []const u8, }; -fn expect_output(expected_outputs: []const ExpectedOutput, vfs: *VirtualFilesystem) !void { - try std.testing.expectEqual(expected_outputs.len, vfs.total_file_count()); +fn expect_output(expected_outputs: []const ExpectedOutput, vio: *VirtualIo) !void { + try std.testing.expectEqual(expected_outputs.len, vio.total_file_count()); for (expected_outputs) |eo| { - const file = try VirtualFilesystem.root_dir.openFile(vfs.io(), eo.path, .{}); - try std.testing.expectEqualStrings(eo.content, (try vfs.file_contents(file)).items); + const file = try VirtualIo.root_dir.openFile(vio.io(), eo.path, .{}); + try std.testing.expectEqualStrings(eo.content, (try vio.file_contents(file)).items); } } @@ -1780,10 +1780,10 @@ test "gen.peripheral instantiation" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "TEST_DEVICE.zig", @@ -1853,7 +1853,7 @@ test "gen.peripheral instantiation" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripherals with a shared type" { @@ -1903,10 +1903,10 @@ test "gen.peripherals with a shared type" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "TEST_DEVICE.zig", @@ -1977,7 +1977,7 @@ test "gen.peripherals with a shared type" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral with modes" { @@ -2022,10 +2022,10 @@ test "gen.peripheral with modes" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2098,7 +2098,7 @@ test "gen.peripheral with modes" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral with enum" { @@ -2128,10 +2128,10 @@ test "gen.peripheral with enum" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2168,7 +2168,7 @@ test "gen.peripheral with enum" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral with enum, enum is exhausted of values" { @@ -2197,10 +2197,10 @@ test "gen.peripheral with enum, enum is exhausted of values" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2236,7 +2236,7 @@ test "gen.peripheral with enum, enum is exhausted of values" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with named enum" { @@ -2272,10 +2272,10 @@ test "gen.field with named enum" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2315,7 +2315,7 @@ test "gen.field with named enum" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with named enum and named default" { @@ -2353,10 +2353,10 @@ test "gen.field with named enum and named default" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2396,7 +2396,7 @@ test "gen.field with named enum and named default" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with named enum and unnamed default" { @@ -2434,10 +2434,10 @@ test "gen.field with named enum and unnamed default" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2477,7 +2477,7 @@ test "gen.field with named enum and unnamed default" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with anonymous enum" { @@ -2512,10 +2512,10 @@ test "gen.field with anonymous enum" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2553,7 +2553,7 @@ test "gen.field with anonymous enum" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with anonymous enum and default" { @@ -2590,10 +2590,10 @@ test "gen.field with anonymous enum and default" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -2631,7 +2631,7 @@ test "gen.field with anonymous enum and default" { \\ , }, - }, &vfs); + }, &vio); } test "gen.namespaced register groups" { @@ -2675,10 +2675,10 @@ test "gen.namespaced register groups" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -2759,7 +2759,7 @@ test "gen.namespaced register groups" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral with reserved register" { @@ -2788,10 +2788,10 @@ test "gen.peripheral with reserved register" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -2862,7 +2862,7 @@ test "gen.peripheral with reserved register" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral with count" { @@ -2891,10 +2891,10 @@ test "gen.peripheral with count" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -2965,7 +2965,7 @@ test "gen.peripheral with count" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral with count, padding required" { @@ -2995,10 +2995,10 @@ test "gen.peripheral with count, padding required" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3070,7 +3070,7 @@ test "gen.peripheral with count, padding required" { \\ , }, - }, &vfs); + }, &vio); } test "gen.register with count" { @@ -3100,10 +3100,10 @@ test "gen.register with count" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3174,7 +3174,7 @@ test "gen.register with count" { \\ , }, - }, &vfs); + }, &vio); } test "gen.register with count and fields" { @@ -3217,10 +3217,10 @@ test "gen.register with count and fields" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3294,7 +3294,7 @@ test "gen.register with count and fields" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with count, width of one, offset, and padding" { @@ -3325,10 +3325,10 @@ test "gen.field with count, width of one, offset, and padding" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3367,7 +3367,7 @@ test "gen.field with count, width of one, offset, and padding" { \\ , }, - }, &vfs); + }, &vio); } test "gen.field with count, multi-bit width, offset, and padding" { @@ -3398,10 +3398,10 @@ test "gen.field with count, multi-bit width, offset, and padding" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3437,7 +3437,7 @@ test "gen.field with count, multi-bit width, offset, and padding" { \\ , }, - }, &vfs); + }, &vio); } test "gen.interrupts.avr" { @@ -3462,10 +3462,10 @@ test "gen.interrupts.avr" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "ATmega328P.zig", @@ -3527,7 +3527,7 @@ test "gen.interrupts.avr" { \\ , }, - }, &vfs); + }, &vio); } test "gen.peripheral type with register and field" { @@ -3558,10 +3558,10 @@ test "gen.peripheral type with register and field" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3598,7 +3598,7 @@ test "gen.peripheral type with register and field" { \\ , }, - }, &vfs); + }, &vio); } test "gen.name collisions in enum name cause them to be anonymous" { @@ -3648,10 +3648,10 @@ test "gen.name collisions in enum name cause them to be anonymous" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3693,7 +3693,7 @@ test "gen.name collisions in enum name cause them to be anonymous" { \\ , }, - }, &vfs); + }, &vio); } test "gen.pick one enum field in value collisions" { @@ -3729,10 +3729,10 @@ test "gen.pick one enum field in value collisions" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -3769,7 +3769,7 @@ test "gen.pick one enum field in value collisions" { \\ , }, - }, &vfs); + }, &vio); } //test "gen.pick one enum field in name collisions" { @@ -3805,10 +3805,10 @@ test "gen.pick one enum field in value collisions" { // var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); // defer buffer.deinit(); // -// var vfs: VirtualFilesystem = try .init(std.testing.allocator); -// defer vfs.deinit(); +// var vio: VirtualIo = try .init(std.testing.allocator); +// defer vio.deinit(); // -// try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); +// try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); // try expect_output(&.{ // .{ // .path = "types.zig", @@ -3845,7 +3845,7 @@ test "gen.pick one enum field in value collisions" { // \\ // , // }, -// }, &vfs); +// }, &vio); //} //test "gen.register fields with name collision" { @@ -3882,10 +3882,10 @@ test "gen.pick one enum field in value collisions" { // var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); // defer buffer.deinit(); // -// var vfs: VirtualFilesystem = try .init(std.testing.allocator); -// defer vfs.deinit(); +// var vio: VirtualIo = try .init(std.testing.allocator); +// defer vio.deinit(); // -// try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); +// try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); // try expect_output(&.{ // .{ // .path = "types.zig", @@ -3921,7 +3921,7 @@ test "gen.pick one enum field in value collisions" { // \\ // , // }, -// }, &vfs); +// }, &vio); //} test "gen.nested struct field in a peripheral" { @@ -3960,10 +3960,10 @@ test "gen.nested struct field in a peripheral" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4004,7 +4004,7 @@ test "gen.nested struct field in a peripheral" { \\ , }, - }, &vfs); + }, &vio); } test "gen.nested struct field in a peripheral that has a named type" { @@ -4045,10 +4045,10 @@ test "gen.nested struct field in a peripheral that has a named type" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4091,7 +4091,7 @@ test "gen.nested struct field in a peripheral that has a named type" { \\ , }, - }, &vfs); + }, &vio); } test "gen.nested struct field in a peripheral with offset" { @@ -4130,10 +4130,10 @@ test "gen.nested struct field in a peripheral with offset" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4176,7 +4176,7 @@ test "gen.nested struct field in a peripheral with offset" { \\ , }, - }, &vfs); + }, &vio); } test "gen.nested struct field in nested struct field" { @@ -4222,10 +4222,10 @@ test "gen.nested struct field in nested struct field" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4269,7 +4269,7 @@ test "gen.nested struct field in nested struct field" { \\ , }, - }, &vfs); + }, &vio); } test "gen.nested struct field next to register" { @@ -4324,10 +4324,10 @@ test "gen.nested struct field next to register" { var buffer: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buffer.deinit(); - var vfs: VirtualFilesystem = try .init(std.testing.allocator); - defer vfs.deinit(); + var vio: VirtualIo = try .init(std.testing.allocator); + defer vio.deinit(); - try db.to_zig(vfs.io(), VirtualFilesystem.root_dir, .{}); + try db.to_zig(vio.io(), VirtualIo.root_dir, .{}); try expect_output(&.{ .{ .path = "types.zig", @@ -4377,5 +4377,5 @@ test "gen.nested struct field next to register" { \\ , }, - }, &vfs); + }, &vio); }