mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 08:01:45 +00:00
libghostty: replace Io.Threaded with custom Io impl (TinyIo)
Add a new Io implementation `TinyIo` that only supports the operations we need and doesn't support concurrency. This shrinks the binary size of libghostty by anywhere from ~100KB (macOS) to ~200KB (Linux) and runtime memory requirements by over 256KB (the thread-local storage `std.Io.Threaded` creates plus the 18KB threaded structure is gone). `TinyIo` is POSIX-only: Windows keeps std.Io.Threaded, and on freestanding targets (wasm) TinyIo degrades to std.Io.failing behavior just like before. It is also exported from the Zig module as `ghostty.TinyIo` so Zig embedders can opt into the same size win when constructing terminals.
This commit is contained in:
1521
src/lib/TinyIo.zig
Normal file
1521
src/lib/TinyIo.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ const types = @import("types.zig");
|
||||
const unionpkg = @import("union.zig");
|
||||
|
||||
pub const allocator = @import("allocator.zig");
|
||||
pub const TinyIo = @import("TinyIo.zig");
|
||||
pub const Buffer = types.Buffer;
|
||||
pub const Enum = enumpkg.Enum;
|
||||
pub const checkGhosttyHEnum = enumpkg.checkGhosttyHEnum;
|
||||
|
||||
@@ -38,6 +38,17 @@ const terminal = @import("terminal/main.zig");
|
||||
/// Additional functionality will be added here over time as needed.
|
||||
pub const sys = terminal.sys;
|
||||
|
||||
/// A tiny, blocking `std.Io` implementation optimized for binary size.
|
||||
///
|
||||
/// Constructing a `Terminal` requires a `std.Io` for features that touch
|
||||
/// the filesystem (e.g. Kitty graphics file transmission). Embedders that
|
||||
/// don't have their own `Io` can use `TinyIo` (e.g.
|
||||
/// `(TinyIo.init).io()`) instead of `std.Io.Threaded` to avoid linking
|
||||
/// Threaded's full vtable (networking, process spawning, async
|
||||
/// machinery, etc.), which is worth roughly 110KB of binary size. See
|
||||
/// the TinyIo docs for the exact tradeoffs.
|
||||
pub const TinyIo = @import("lib/TinyIo.zig");
|
||||
|
||||
pub const apc = terminal.apc;
|
||||
pub const dcs = terminal.dcs;
|
||||
pub const osc = terminal.osc;
|
||||
|
||||
@@ -719,33 +719,6 @@ test "decoder option and empty source" {
|
||||
try testing.expectEqual(null, terminal);
|
||||
}
|
||||
|
||||
test "snapshot decoder defers terminal I/O allocation until READY" {
|
||||
var failing = testing.FailingAllocator.init(testing.allocator, .{
|
||||
// The decoder wrapper is the only allocation performed by new_buf.
|
||||
// Fail the following allocation, which creates terminal-owned I/O.
|
||||
.fail_index = 1,
|
||||
});
|
||||
const failing_zig = failing.allocator();
|
||||
const failing_c: CAllocator = .fromZig(&failing_zig);
|
||||
|
||||
var decoder: Decoder = null;
|
||||
try testing.expectEqual(Result.success, decoder_new_buf(
|
||||
&failing_c,
|
||||
&decoder,
|
||||
null,
|
||||
0,
|
||||
));
|
||||
defer decoder_free(decoder);
|
||||
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.out_of_memory, decoder_ready(
|
||||
decoder,
|
||||
&terminal,
|
||||
));
|
||||
try testing.expectEqual(null, terminal);
|
||||
try testing.expectEqual(@as(usize, 0), decoder.?.source.offset());
|
||||
}
|
||||
|
||||
test "snapshot C API full round trip restores continuation" {
|
||||
var source: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
|
||||
@@ -51,17 +51,25 @@ pub const Io = struct {
|
||||
impl: Impl,
|
||||
|
||||
/// Platform-specific storage backing the public `std.Io` value.
|
||||
const Impl = if (builtin.os.tag != .freestanding)
|
||||
///
|
||||
/// Where supported (POSIX) we use TinyIo, which is stateless and
|
||||
/// supports exactly the operations the terminal needs at a fraction
|
||||
/// of the code size (see lib/TinyIo.zig). On Windows we use
|
||||
/// `std.Io.Threaded` since TinyIo doesn't implement the NT
|
||||
/// operations. On the remaining targets (e.g. freestanding wasm)
|
||||
/// TinyIo degrades to `std.Io.failing`, which is correct: they have
|
||||
/// no filesystem.
|
||||
const Impl = if (builtin.os.tag == .windows)
|
||||
*std.Io.Threaded
|
||||
else
|
||||
void;
|
||||
lib.TinyIo;
|
||||
|
||||
/// Allocation failures possible while constructing an I/O owner.
|
||||
pub const Error = error{OutOfMemory};
|
||||
|
||||
/// Allocate the native I/O implementation when the platform requires it.
|
||||
pub fn init(alloc: std.mem.Allocator) Error!Io {
|
||||
if (comptime builtin.os.tag == .freestanding) return .{ .impl = {} };
|
||||
if (comptime Impl == lib.TinyIo) return .{ .impl = .init };
|
||||
|
||||
const ptr = alloc.create(std.Io.Threaded) catch
|
||||
return error.OutOfMemory;
|
||||
@@ -71,15 +79,15 @@ pub const Io = struct {
|
||||
|
||||
/// Return the value passed to native terminal construction and decoding.
|
||||
pub fn io(self: Io) std.Io {
|
||||
if (comptime builtin.os.tag == .freestanding) {
|
||||
return std.Io.failing;
|
||||
}
|
||||
return self.impl.io();
|
||||
}
|
||||
|
||||
/// Release an I/O implementation that has not already been transferred.
|
||||
pub fn deinit(self: Io, alloc: std.mem.Allocator) void {
|
||||
if (comptime builtin.os.tag != .freestanding) {
|
||||
// Note: this must not name `std.Io.Threaded` in the condition
|
||||
// because resolving that type trips its container-level comptime
|
||||
// checks on targets it doesn't support (e.g. wasm32-freestanding).
|
||||
if (comptime Impl != lib.TinyIo) {
|
||||
self.impl.deinit();
|
||||
alloc.destroy(self.impl);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub const calling_conv: std.builtin.CallingConvention = .c;
|
||||
|
||||
/// Forwarded decls from lib that are used.
|
||||
pub const alloc = lib.allocator;
|
||||
pub const TinyIo = lib.TinyIo;
|
||||
pub const Buffer = lib.Buffer;
|
||||
pub const Enum = lib.Enum;
|
||||
pub const TaggedUnion = lib.TaggedUnion;
|
||||
|
||||
@@ -80,6 +80,9 @@ rin = "rin"
|
||||
ower = "ower"
|
||||
# OpenType table names
|
||||
loca = "loca"
|
||||
# TinyIo
|
||||
tio = "tio"
|
||||
WRONLY = "WRONLY"
|
||||
|
||||
[type.po]
|
||||
extend-glob = ["*.po"]
|
||||
|
||||
Reference in New Issue
Block a user