aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig
diff options
context:
space:
mode:
authorsl0p <sl0p@sl0p.foo>2026-08-13 22:00:03 +0200
committersl0p <sl0p@sl0p.foo>2026-08-13 22:00:03 +0200
commit2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8 (patch)
tree7cd6274303ba1da1f8bd57371aec336dd687fb58 /vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig
downloadslosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.tar.gz
slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.tar.xz
slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.zip
sl0ptty: M0 — libghostty-vt vendored, one pane, compositor, diff renderer
Design contract in DESIGN.md. libghostty-vt vendored fresh from ghostty main@bedb6f2 (zig 0.16), built to a 14MB static lib. M0: pty spawn (no libutil), a lib-vt terminal per pane, a screen cell buffer, a diff emitter that repaints only what changed, raw mode, SIGWINCH, and a ~120Hz frame cap that coalesces pty reads. Also the seed of the headless harness: --headless dumps the composited screen without a tty, so composition is testable in milliseconds. tests/live_m0.py covers what headless cannot: raw mode, the diff emitter, resize propagation, and the quit path. 6/6 green.
Diffstat (limited to 'vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig')
-rw-r--r--vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig155
1 files changed, 155 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig b/vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig
new file mode 100644
index 0000000..166c3d4
--- /dev/null
+++ b/vendor/libghostty-vt/src/benchmark/HyperlinkMap.zig
@@ -0,0 +1,155 @@
+//! Benchmark hyperlink cell-map lookups and remove/insert churn.
+//!
+//! Hyperlink cells are stored in a fixed-capacity, open-addressed hash map.
+//! The `churn` mode models terminal output that repeatedly replaces cells in
+//! a page whose hyperlink map is already close to full. This is particularly
+//! useful for catching probe-length cliffs at high load factors.
+const HyperlinkMap = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const terminal = @import("../terminal/main.zig");
+const hyperlink = @import("../terminal/hyperlink.zig");
+const Benchmark = @import("Benchmark.zig");
+
+const log = std.log.scoped(.@"hyperlink-map-bench");
+
+opts: Options,
+page: terminal.Page,
+link_id: hyperlink.Id,
+entry_count: usize,
+
+pub const Options = struct {
+ /// Requested hyperlink working-set size. Must be a power of two and at
+ /// least 16. The map may reserve additional probe headroom.
+ entries: u16 = 4096,
+
+ /// Percentage of the map populated before the timed operation.
+ /// Values above 100 are treated as 100.
+ @"load-percent": u8 = 100,
+
+ /// Number of complete passes over the populated cells per step.
+ loops: u16 = 1,
+
+ /// Operation to perform in the timed region.
+ mode: Mode = .churn,
+};
+
+pub const Mode = enum {
+ /// Look up every populated hyperlink cell.
+ lookup,
+
+ /// Remove and reinsert every populated hyperlink cell.
+ churn,
+};
+
+pub fn create(alloc: Allocator, opts: Options) !*HyperlinkMap {
+ if (opts.entries < 16 or !std.math.isPowerOfTwo(opts.entries)) {
+ log.err("entries must be a power of two greater than or equal to 16", .{});
+ return error.InvalidEntries;
+ }
+
+ const ptr = try alloc.create(HyperlinkMap);
+ errdefer alloc.destroy(ptr);
+
+ // The page requests one map slot per `hyperlink_cell_multiplier` set
+ // entries. Keep this relationship explicit so `entries` is the working
+ // set size under test regardless of the map's reserved probe headroom.
+ const set_entries = opts.entries / 16;
+ var page = try terminal.Page.init(.{
+ .cols = opts.entries,
+ .rows = 1,
+ .hyperlink_bytes = @intCast(
+ @as(usize, set_entries) * @sizeOf(hyperlink.Set.Item),
+ ),
+ });
+ errdefer page.deinit();
+
+ if (page.hyperlinkCapacity() < opts.entries) {
+ log.err("insufficient map capacity expected_at_least={} actual={}", .{
+ opts.entries,
+ page.hyperlinkCapacity(),
+ });
+ return error.UnexpectedCapacity;
+ }
+
+ const link_id = try page.insertHyperlink(.{
+ .id = .{ .implicit = 1 },
+ .uri = "https://example.com/benchmark",
+ });
+
+ const load = @min(opts.@"load-percent", 100);
+ const entry_count = @max(
+ 1,
+ @divFloor(@as(usize, opts.entries) * load, 100),
+ );
+ for (0..entry_count) |x| {
+ const rac = page.getRowAndCell(x, 0);
+ page.hyperlink_set.use(page.memory, link_id);
+ try page.setHyperlink(rac.row, rac.cell, link_id);
+ }
+
+ ptr.* = .{
+ .opts = opts,
+ .page = page,
+ .link_id = link_id,
+ .entry_count = entry_count,
+ };
+ return ptr;
+}
+
+pub fn destroy(self: *HyperlinkMap, alloc: Allocator) void {
+ self.page.deinit();
+ alloc.destroy(self);
+}
+
+pub fn benchmark(self: *HyperlinkMap) Benchmark {
+ return .init(self, .{
+ .stepFn = switch (self.opts.mode) {
+ .lookup => stepLookup,
+ .churn => stepChurn,
+ },
+ });
+}
+
+fn stepLookup(ptr: *anyopaque) Benchmark.Error!void {
+ const self: *HyperlinkMap = @ptrCast(@alignCast(ptr));
+
+ for (0..self.opts.loops) |_| {
+ for (0..self.entry_count) |x| {
+ const cell = self.page.getRowAndCell(x, 0).cell;
+ const id = self.page.lookupHyperlink(cell) orelse
+ return error.BenchmarkFailed;
+ std.mem.doNotOptimizeAway(id);
+ }
+ }
+}
+
+fn stepChurn(ptr: *anyopaque) Benchmark.Error!void {
+ const self: *HyperlinkMap = @ptrCast(@alignCast(ptr));
+
+ for (0..self.opts.loops) |_| {
+ for (0..self.entry_count) |x| {
+ const rac = self.page.getRowAndCell(x, 0);
+ self.page.clearHyperlink(rac.cell);
+ self.page.hyperlink_set.use(self.page.memory, self.link_id);
+ self.page.setHyperlink(rac.row, rac.cell, self.link_id) catch
+ return error.BenchmarkFailed;
+ }
+ }
+}
+
+test HyperlinkMap {
+ const alloc = std.testing.allocator;
+
+ inline for (.{ Mode.lookup, Mode.churn }) |mode| {
+ const impl = try HyperlinkMap.create(alloc, .{
+ .entries = 64,
+ .mode = mode,
+ });
+ defer impl.destroy(alloc);
+
+ const bench = impl.benchmark();
+ _ = try bench.run(.once);
+ }
+}