aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/mem.zig
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:28:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:29:28 +0300
commit4a3302d19a2baa3a33e910e0088c24e780905b90 (patch)
treedcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/terminal/mem.zig
parent550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff)
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/mem.zig')
-rw-r--r--vendor/libghostty-vt/src/terminal/mem.zig222
1 files changed, 222 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/terminal/mem.zig b/vendor/libghostty-vt/src/terminal/mem.zig
new file mode 100644
index 00000000..7fa31480
--- /dev/null
+++ b/vendor/libghostty-vt/src/terminal/mem.zig
@@ -0,0 +1,222 @@
+//! Virtual-memory operations shared by terminal page owners.
+//!
+//! Terminal pages use page-aligned, page-multiple mappings. This module can
+//! discard the physical pages behind one of those mappings without releasing
+//! its virtual address range, then prepare the same range for reuse. It does
+//! not allocate memory or decide which terminal pages should be discarded.
+const std = @import("std");
+const builtin = @import("builtin");
+const assert = @import("../quirks.zig").inlineAssert;
+
+const log = std.log.scoped(.terminal_mem);
+
+/// What guarantee decommit must provide when the OS cannot discard a mapping.
+pub const DecommitMode = enum {
+ /// The dirty prefix must read as zero after this call, even when physical
+ /// reclamation is unavailable. Bytes after the prefix must already be zero.
+ zero,
+
+ /// Physical-memory reclamation is required. Do not touch the mapping when
+ /// reclamation is unsupported or fails; report the failure to the caller.
+ strict,
+};
+
+/// Return whether this target can reclaim physical memory for `mode` while
+/// retaining the mapping's virtual address range.
+///
+/// Test builds support both modes because `decommit` simulates reclamation by
+/// clearing the supplied range. Runtime reclamation is intentionally limited
+/// to 64-bit Linux and Darwin. Other targets must leave strict callers' memory
+/// resident; zero mode still provides its documented memset fallback through
+/// `decommit` even when this function returns false.
+pub inline fn canReclaim(comptime mode: DecommitMode) bool {
+ // Both modes use the same retained-mapping primitives. Keeping the switch
+ // exhaustive makes additions to DecommitMode choose target support
+ // explicitly rather than inheriting it accidentally.
+ return switch (mode) {
+ .zero, .strict => supported: {
+ // Tests never call into the OS because their allocator ranges can
+ // share mappings with unrelated allocations. `decommit` simulates
+ // successful reclamation by zeroing the requested range instead,
+ // so both modes are always available to tests on every target.
+ if (builtin.is_test) break :supported true;
+
+ // Compression currently retains complete page mappings for its
+ // lifetime. Limit the initial runtime support to 64-bit address
+ // spaces where that virtual-memory cost is negligible and where
+ // the retained-mapping behavior has been validated.
+ if (builtin.target.ptrBitWidth() != 64) break :supported false;
+
+ // Linux provides MADV_DONTNEED, which immediately discards pages
+ // from a private anonymous mapping and faults them back as zeroes.
+ // Zig reaches this through the raw syscall path without libc.
+ if (builtin.target.os.tag == .linux) break :supported true;
+
+ // Darwin provides the paired MADV_FREE_REUSABLE/FREE_REUSE
+ // operations used below. Darwin requires libc independently of
+ // this feature, so using its madvise entry point adds no new
+ // dependency to libghostty-vt.
+ if (builtin.target.os.tag.isDarwin()) break :supported true;
+
+ // Other targets have no retained-mapping reclamation contract in
+ // this module. Zero mode can still clear through its memset
+ // fallback, but strict callers must leave their mapping resident.
+ break :supported false;
+ },
+ };
+}
+
+/// Discard physical pages while retaining a mapping's virtual address range.
+///
+/// The complete mapping must be page-aligned and a multiple of the minimum
+/// system page size. `dirty_len` identifies the prefix whose contents may be
+/// nonzero. Strict mode requires the complete mapping to be dirty because a
+/// successful discard invalidates all of its contents.
+///
+/// The return value reports whether the OS accepted the reclamation request.
+/// Test builds return true after simulating reclamation by zeroing dirty bytes.
+/// In zero mode, the requested bytes are guaranteed to be zero regardless of
+/// the return value.
+pub fn decommit(
+ comptime mode: DecommitMode,
+ memory: []align(std.heap.page_size_min) u8,
+ dirty_len: usize,
+) bool {
+ assert(memory.len > 0);
+ assert(@intFromPtr(memory.ptr) % std.heap.page_size_min == 0);
+ assert(memory.len % std.heap.page_size_min == 0);
+ assert(dirty_len <= memory.len);
+ if (comptime mode == .strict) assert(dirty_len == memory.len);
+
+ // Testing allocator ranges may share an OS mapping with unrelated memory,
+ // so madvise is not safe. Zeroing models the only content guarantee callers
+ // have after a successful discard.
+ if (comptime builtin.is_test) {
+ @memset(memory[0..dirty_len], 0);
+ return true;
+ }
+
+ // DONTNEED immediately reclaims private anonymous pages on Linux and
+ // faults them back as zero-filled pages. We deliberately avoid MADV_FREE:
+ // it does not reduce RSS until memory pressure and does not guarantee that
+ // the next read is zero.
+ if (comptime builtin.os.tag == .linux) {
+ if (std.posix.madvise(
+ memory.ptr,
+ memory.len,
+ std.posix.MADV.DONTNEED,
+ )) |_| return true else |err| {
+ log.warn("madvise(DONTNEED) failed err={}", .{err});
+ if (comptime mode == .strict) return false;
+ // Zero mode falls through to the memset below.
+ }
+ }
+
+ // FREE_REUSABLE removes the range from the Darwin process footprint while
+ // retaining its mapping. Zero mode clears its dirty prefix first because
+ // the kernel may preserve the contents. Strict mode avoids that write
+ // because its caller will replace the entire mapping after recommit.
+ if (comptime builtin.os.tag.isDarwin()) {
+ if (comptime mode == .zero) @memset(memory[0..dirty_len], 0);
+
+ if (std.posix.madvise(
+ memory.ptr,
+ memory.len,
+ std.posix.MADV.FREE_REUSABLE,
+ )) |_| return true else |err| {
+ switch (mode) {
+ .strict => {
+ log.warn("madvise(FREE_REUSABLE) failed err={}", .{err});
+ return false;
+ },
+
+ .zero => {
+ // Plain FREE can still reclaim the already-zero mapping
+ // under pressure and does not require a reuse pairing.
+ std.posix.madvise(
+ memory.ptr,
+ memory.len,
+ std.posix.MADV.FREE,
+ ) catch {};
+ return false;
+ },
+ }
+ }
+ }
+
+ if (comptime mode == .zero) @memset(memory[0..dirty_len], 0);
+ return false;
+}
+
+/// Prepare a mapping previously passed to decommit for reuse.
+///
+/// Linux and test builds need no explicit operation. Darwin pairs
+/// FREE_REUSABLE with FREE_REUSE so pages touched by the caller are accounted
+/// to the process again. Failure does not invalidate the retained mapping, so
+/// reuse can continue after logging the accounting failure.
+pub fn recommit(memory: []align(std.heap.page_size_min) u8) void {
+ assert(memory.len > 0);
+ assert(@intFromPtr(memory.ptr) % std.heap.page_size_min == 0);
+ assert(memory.len % std.heap.page_size_min == 0);
+
+ if (comptime builtin.is_test) return;
+ if (comptime builtin.os.tag.isDarwin()) {
+ std.posix.madvise(
+ memory.ptr,
+ memory.len,
+ std.posix.MADV.FREE_REUSE,
+ ) catch |err| {
+ log.warn("madvise(FREE_REUSE) failed err={}", .{err});
+ };
+ }
+}
+
+test "decommit with zero fallback clears the dirty prefix" {
+ const testing = std.testing;
+ const memory_len = 2 * std.heap.page_size_min;
+ const memory = try testing.allocator.alignedAlloc(
+ u8,
+ .fromByteUnits(std.heap.page_size_min),
+ memory_len,
+ );
+ defer testing.allocator.free(memory);
+
+ @memset(memory, 0xAA);
+ _ = decommit(.zero, memory, memory.len);
+ try testing.expect(std.mem.allEqual(u8, memory, 0));
+
+ // The tail is already zero by contract, so a partially dirty mapping only
+ // needs its dirty prefix cleared.
+ @memset(memory[0..1024], 0xAA);
+ _ = decommit(.zero, memory, 1024);
+ try testing.expect(std.mem.allEqual(u8, memory, 0));
+}
+
+test "strict decommit retains the mapping for recommit" {
+ const testing = std.testing;
+ const memory_len = 2 * std.heap.page_size_min;
+ const memory = try testing.allocator.alignedAlloc(
+ u8,
+ .fromByteUnits(std.heap.page_size_min),
+ memory_len,
+ );
+ defer testing.allocator.free(memory);
+ @memset(memory, 0xAA);
+
+ const original_ptr = memory.ptr;
+ const original_len = memory.len;
+ try testing.expect(decommit(.strict, memory, memory.len));
+ try testing.expectEqual(original_ptr, memory.ptr);
+ try testing.expectEqual(original_len, memory.len);
+ try testing.expect(std.mem.allEqual(u8, memory, 0));
+
+ recommit(memory);
+ @memset(memory, 0xBB);
+ try testing.expect(std.mem.allEqual(u8, memory, 0xBB));
+}
+
+test "test builds can reclaim retained mappings" {
+ const testing = std.testing;
+ try testing.expect(canReclaim(.zero));
+ try testing.expect(canReclaim(.strict));
+}