From ac4dc12056c137378da49152b806edc01ba730d6 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 4 Aug 2026 09:00:14 -0400 Subject: [PATCH] terminal: size dynamic tabstops by bits Dynamic tabstop storage treated the number of columns above the inline capacity as a byte count even though each byte stores eight stops. This was wasteful, although in practice this is in the order of just bytes. Round the extra column count up to whole storage units and grow the existing slice with realloc, preserve existing stops. --- src/terminal/Tabstops.zig | 42 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/terminal/Tabstops.zig b/src/terminal/Tabstops.zig index 8955447f2..7e74bc961 100644 --- a/src/terminal/Tabstops.zig +++ b/src/terminal/Tabstops.zig @@ -14,7 +14,6 @@ const tripwire = @import("../tripwire.zig"); const Allocator = std.mem.Allocator; const testing = std.testing; const assert = @import("../quirks.zig").inlineAssert; -const fastmem = @import("../fastmem.zig"); /// Unit is the type we use per tabstop unit (see file docs). const Unit = u8; @@ -138,24 +137,21 @@ pub fn resize( return; } - // What we need in the dynamic size - const size = cols - prealloc_columns; - if (size <= self.dynamic_stops.len) { + // Number of units needed beyond the preallocated columns. + const dynamic_count = std.math.divCeil( + usize, + cols - prealloc_columns, + unit_bits, + ) catch unreachable; + if (dynamic_count <= self.dynamic_stops.len) { self.cols = cols; return; } - // Note: we can probably try to realloc here but I'm not sure it matters. try tw.check(.dynamic_alloc); - const new = try alloc.alloc(Unit, size); - errdefer comptime unreachable; - @memset(new, 0); - if (self.dynamic_stops.len > 0) { - fastmem.copy(Unit, new, self.dynamic_stops); - alloc.free(self.dynamic_stops); - } - - self.dynamic_stops = new; + const old_len = self.dynamic_stops.len; + self.dynamic_stops = try alloc.realloc(self.dynamic_stops, dynamic_count); + @memset(self.dynamic_stops[old_len..], 0); self.cols = cols; } @@ -208,21 +204,27 @@ test "Tabstops: dynamic allocations" { var t: Tabstops = .{}; defer t.deinit(testing.allocator); - // Grow the capacity by 2. + // Grow by less than one unit to verify the allocation rounds up. const cap = t.capacity(); - try t.resize(testing.allocator, cap * 2); + try t.resize(testing.allocator, cap + 5); + try testing.expectEqual(cap + unit_bits, t.capacity()); // Set something that was out of range of the first - t.set(cap + 5); - try testing.expect(t.get(cap + 5)); - try testing.expect(!t.get(cap + 4)); + t.set(cap + 4); + try testing.expect(t.get(cap + 4)); + try testing.expect(!t.get(cap + 3)); + + // Growing again preserves existing stops and clears the new unit. + try t.resize(testing.allocator, cap + unit_bits + 1); + try testing.expect(t.get(cap + 4)); + try testing.expect(!t.get(cap + unit_bits)); // Prealloc still works try testing.expect(!t.get(5)); } test "Tabstops: resize to existing capacity does not allocate" { - var backing: [prealloc_columns]Unit = undefined; + var backing: [prealloc_count]Unit = undefined; var fixed = std.heap.FixedBufferAllocator.init(&backing); var t: Tabstops = .{};