diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/lib/TinyIo.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/lib/TinyIo.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/lib/TinyIo.zig | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/lib/TinyIo.zig b/vendor/libghostty-vt/src/lib/TinyIo.zig new file mode 100644 index 00000000..118af197 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/TinyIo.zig @@ -0,0 +1,269 @@ +//! TinyIo: a tiny, blocking `std.Io` implementation optimized for +//! binary size. +//! +//! Compared to `std.Io.Threaded`, the binary cost is roughly ~100KB to +//! ~200KB (macOS vs Linux) smaller, and the runtime cost is ~300KB (256KB +//! of TLS plus the ~20KB threaded structure) smaller. On Windows the +//! DLL is ~370KB smaller (ReleaseFast) because Threaded's Windows logic +//! has Winsock, AFD and process creation. +//! +//! Utilizing the built-in Zig `std.Io.Threaded` is easy but due to its +//! vtable architecture, the linker can't prune uncalled functions, meaning +//! you have to pay for all the code for every op. Plus, at runtime, Threaded +//! requires 256KB of thread-local storage, plus the structure itself is +//! very large. +//! +//! TinyIo implements exactly the operations we need as plain blocking +//! syscalls. It doesn't support cancelation. It doesn't support concurrency +//! operations, but all our APIs are direct syscalls so it supports the +//! concurrency of calls that those syscalls support (which is usually +//! safe on POSIX systems). +//! +//! The platform arms live in `tinyio/`: `posix.zig` is direct syscalls +//! cribbed from Zig's std.posix, and `windows.zig` is direct ntdll and +//! kernel32 calls modeled on the Windows arms of `std.Io.Threaded`, so +//! the library never links Threaded's vtable there either. Shared tests +//! are in `tinyio/test.zig`. + +const TinyIo = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const Io = std.Io; + +/// True if this platform has a real TinyIo implementation. On +/// unsupported platforms `io()` still works but every operation fails +/// like `std.Io.failing`. +pub const supported: bool = switch (builtin.os.tag) { + .wasi, .freestanding, .other, .uefi => false, + else => true, +}; + +/// Initialize a TinyIo. TinyIo is stateless (zero-sized), so this exists +/// purely to mirror the `std.Io.Threaded` initialization and usage shape, +/// making the two easy to swap. +pub const init: TinyIo = .{}; + +/// Returns the `std.Io` interface for this implementation. +pub fn io(self: TinyIo) Io { + _ = self; + return .{ + .userdata = null, + .vtable = &vtable, + }; +} + +const vtable: Io.VTable = if (!supported) std.Io.failing.vtable.* else .{ + .crashHandler = Io.noCrashHandler, + + // `noAsync` runs the task synchronously and returns null, which means + // `await`/`cancel` can never be called. `concurrent` reports that + // concurrency is unavailable, which callers must handle anyway. + .async = Io.noAsync, + .concurrent = Io.failingConcurrent, + .await = Io.unreachableAwait, + .cancel = Io.unreachableCancel, + + .groupAsync = Io.noGroupAsync, + .groupConcurrent = Io.failingGroupConcurrent, + .groupAwait = Io.unreachableGroupAwait, + .groupCancel = Io.unreachableGroupCancel, + + // Cancelation is never requested (there is no async), but these are + // benign no-ops instead of `unreachable` since generic std code may + // toggle cancel protection around operations. + .recancel = recancel, + .swapCancelProtection = swapCancelProtection, + .checkCancel = checkCancel, + + // Real futexes: `std.Io.Mutex` (used by kitty graphics storage) parks + // on these when contended. + .futexWait = futexWait, + .futexWaitUncancelable = futexWaitUncancelable, + .futexWake = futexWake, + + // `operate` carries the streaming file reads used by `File.Reader`. + .operate = operate, + .batchAwaitAsync = Io.unreachableBatchAwaitAsync, + .batchAwaitConcurrent = Io.unreachableBatchAwaitConcurrent, + .batchCancel = Io.unreachableBatchCancel, + + .dirCreateDir = Io.failingDirCreateDir, + .dirCreateDirPath = Io.failingDirCreateDirPath, + .dirCreateDirPathOpen = Io.failingDirCreateDirPathOpen, + .dirOpenDir = Io.failingDirOpenDir, + .dirStat = Io.failingDirStat, + .dirStatFile = Io.failingDirStatFile, + .dirAccess = Io.failingDirAccess, + .dirCreateFile = Io.failingDirCreateFile, + .dirCreateFileAtomic = Io.failingDirCreateFileAtomic, + .dirOpenFile = impl.dirOpenFile, + .dirClose = impl.dirClose, + .dirRead = Io.noDirRead, + .dirRealPath = Io.failingDirRealPath, + .dirRealPathFile = impl.dirRealPathFile, + .dirDeleteFile = impl.dirDeleteFile, + .dirDeleteDir = Io.failingDirDeleteDir, + .dirRename = Io.failingDirRename, + .dirRenamePreserve = Io.failingDirRenamePreserve, + .dirSymLink = Io.failingDirSymLink, + .dirReadLink = Io.failingDirReadLink, + .dirSetOwner = Io.failingDirSetOwner, + .dirSetFileOwner = Io.failingDirSetFileOwner, + .dirSetPermissions = Io.failingDirSetPermissions, + .dirSetFilePermissions = Io.failingDirSetFilePermissions, + .dirSetTimestamps = Io.noDirSetTimestamps, + .dirHardLink = Io.failingDirHardLink, + + .fileStat = impl.fileStat, + .fileLength = impl.fileLength, + .fileClose = impl.fileClose, + .fileWritePositional = Io.failingFileWritePositional, + .fileWriteFileStreaming = Io.noFileWriteFileStreaming, + .fileWriteFilePositional = Io.noFileWriteFilePositional, + .fileReadPositional = impl.fileReadPositional, + .fileSeekBy = impl.fileSeekBy, + .fileSeekTo = impl.fileSeekTo, + .fileSync = Io.failingFileSync, + .fileIsTty = Io.unreachableFileIsTty, + .fileEnableAnsiEscapeCodes = Io.unreachableFileEnableAnsiEscapeCodes, + .fileSupportsAnsiEscapeCodes = Io.unreachableFileSupportsAnsiEscapeCodes, + .fileSetLength = Io.failingFileSetLength, + .fileSetOwner = Io.failingFileSetOwner, + .fileSetPermissions = Io.failingFileSetPermissions, + .fileSetTimestamps = Io.noFileSetTimestamps, + .fileLock = Io.failingFileLock, + .fileTryLock = Io.failingFileTryLock, + .fileUnlock = Io.unreachableFileUnlock, + .fileDowngradeLock = Io.failingFileDowngradeLock, + .fileRealPath = impl.fileRealPath, + .fileHardLink = Io.failingFileHardLink, + + .fileMemoryMapCreate = Io.failingFileMemoryMapCreate, + .fileMemoryMapDestroy = Io.unreachableFileMemoryMapDestroy, + .fileMemoryMapSetLength = Io.unreachableFileMemoryMapSetLength, + .fileMemoryMapRead = Io.unreachableFileMemoryMapRead, + .fileMemoryMapWrite = Io.unreachableFileMemoryMapWrite, + + .processExecutableOpen = Io.failingProcessExecutableOpen, + .processExecutablePath = Io.failingProcessExecutablePath, + .lockStderr = Io.unreachableLockStderr, + .tryLockStderr = Io.noTryLockStderr, + .unlockStderr = Io.unreachableUnlockStderr, + .processCurrentPath = Io.failingProcessCurrentPath, + .processSetCurrentDir = Io.failingProcessSetCurrentDir, + .processSetCurrentPath = Io.failingProcessSetCurrentPath, + .processReplace = Io.failingProcessReplace, + .processReplacePath = Io.failingProcessReplacePath, + .processSpawn = Io.failingProcessSpawn, + .processSpawnPath = Io.failingProcessSpawnPath, + .childWait = Io.unreachableChildWait, + .childKill = Io.unreachableChildKill, + + .progressParentFile = Io.failingProgressParentFile, + + .random = Io.noRandom, + .randomSecure = randomSecure, + + .now = Io.noNow, + .clockResolution = Io.failingClockResolution, + .sleep = Io.noSleep, + + .netListenIp = Io.failingNetListenIp, + .netAccept = Io.failingNetAccept, + .netBindIp = Io.failingNetBindIp, + .netConnectIp = Io.failingNetConnectIp, + .netListenUnix = Io.failingNetListenUnix, + .netConnectUnix = Io.failingNetConnectUnix, + .netSocketCreatePair = Io.failingNetSocketCreatePair, + .netSend = Io.failingNetSend, + .netRead = Io.failingNetRead, + .netWrite = Io.failingNetWrite, + .netWriteFile = Io.failingNetWriteFile, + .netClose = Io.unreachableNetClose, + .netShutdown = Io.failingNetShutdown, + .netInterfaceNameResolve = Io.failingNetInterfaceNameResolve, + .netInterfaceName = Io.unreachableNetInterfaceName, + .netLookup = Io.failingNetLookup, +}; + +/// The platform arm behind the vtable. Each arm exports the same set of +/// operations; only the selected one is ever analyzed, and `supported` +/// gates the vtable so unsupported targets never resolve either. +const impl = switch (builtin.os.tag) { + .windows => @import("tinyio/windows.zig"), + else => @import("tinyio/posix.zig"), +}; + +fn recancel(_: ?*anyopaque) void {} + +fn swapCancelProtection( + _: ?*anyopaque, + new: Io.CancelProtection, +) Io.CancelProtection { + // Stateless: there is no async, so cancelation can never be requested + // and the protection state is meaningless. Callers save this return + // value only to restore it via another call to this function. + _ = new; + return .blocked; +} + +fn checkCancel(_: ?*anyopaque) Io.Cancelable!void {} + +fn randomSecure(_: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { + if (buffer.len == 0) return; + return impl.randomSecure(buffer); +} + +fn operate( + userdata: ?*anyopaque, + operation: Io.Operation, +) Io.Cancelable!Io.Operation.Result { + switch (operation) { + // Streaming file reads are how `File.Reader` consumes files that + // don't support positional reads (and the fallback path in + // general). + .file_read_streaming => |o| return .{ + .file_read_streaming = impl.fileReadStreaming(o.file, o.data), + }, + + // Everything else (streaming writes, ioctls, socket receives) is + // unused by the terminal; fail like `Io.failing` does. + else => return Io.failingOperate(userdata, operation), + } +} + +/// Convert an `Io.Timeout` to relative nanoseconds without consulting a +/// clock. Deadlines can't be resolved without `now` support; treat them +/// as a short poll, which is valid because futex waits are allowed to +/// wake spuriously (callers must re-check their condition and retry). +fn timeoutToNs(timeout: Io.Timeout) ?u64 { + return switch (timeout) { + .none => null, + .duration => |d| @intCast(@max(0, d.raw.toNanoseconds())), + .deadline => 10 * std.time.ns_per_ms, + }; +} + +fn futexWait( + userdata: ?*anyopaque, + ptr: *const u32, + expected: u32, + timeout: Io.Timeout, +) Io.Cancelable!void { + _ = userdata; + impl.futexWaitInner(ptr, expected, timeoutToNs(timeout)); +} + +fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { + _ = userdata; + impl.futexWaitInner(ptr, expected, null); +} + +fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { + return impl.futexWake(userdata, ptr, max_waiters); +} + +test { + _ = @import("tinyio/test.zig"); +} |
