mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 04:39:01 +00:00
terminal: align page bitmap capacities
Bitmap allocators reserve complete 64-chunk words, but Page recorded the smaller requested byte counts. Doubling a small capacity could therefore recreate the same layout and make an immediate allocation retry fail again. Size the grapheme map directly from the rounded allocator raw slot capacity and record the actual grapheme and string byte capacities. Page layout is now idempotent and each capacity increase advances to larger storage.
This commit is contained in:
@@ -9979,7 +9979,10 @@ test "PageList increaseCapacity to increase graphemes" {
|
||||
var s = try init(alloc, 2, 2, 0);
|
||||
defer s.deinit();
|
||||
|
||||
const original_cap = s.pages.first.?.capacity().grapheme_bytes;
|
||||
const original_page = s.pages.first.?.page();
|
||||
const original_cap = original_page.capacity.grapheme_bytes;
|
||||
const original_alloc_cap = original_page.grapheme_alloc.capacityBytes();
|
||||
try testing.expectEqual(original_alloc_cap, @as(usize, original_cap));
|
||||
|
||||
{
|
||||
try testing.expect(s.pages.first == s.pages.last);
|
||||
@@ -10003,6 +10006,11 @@ test "PageList increaseCapacity to increase graphemes" {
|
||||
const page = s.pages.first.?.page();
|
||||
|
||||
try testing.expectEqual(original_cap * 2, page.capacity.grapheme_bytes);
|
||||
try testing.expect(page.grapheme_alloc.capacityBytes() > original_alloc_cap);
|
||||
try testing.expectEqual(
|
||||
page.grapheme_alloc.capacityBytes(),
|
||||
@as(usize, page.capacity.grapheme_bytes),
|
||||
);
|
||||
|
||||
for (0..s.rows) |y| {
|
||||
for (0..s.cols) |x| {
|
||||
@@ -10067,7 +10075,10 @@ test "PageList increaseCapacity to increase string_bytes" {
|
||||
var s = try init(alloc, 2, 2, 0);
|
||||
defer s.deinit();
|
||||
|
||||
const original_cap = s.pages.first.?.capacity().string_bytes;
|
||||
const original_page = s.pages.first.?.page();
|
||||
const original_cap = original_page.capacity.string_bytes;
|
||||
const original_alloc_cap = original_page.string_alloc.capacityBytes();
|
||||
try testing.expectEqual(original_alloc_cap, @as(usize, original_cap));
|
||||
|
||||
{
|
||||
try testing.expect(s.pages.first == s.pages.last);
|
||||
@@ -10091,6 +10102,11 @@ test "PageList increaseCapacity to increase string_bytes" {
|
||||
const page = s.pages.first.?.page();
|
||||
|
||||
try testing.expectEqual(original_cap * 2, page.capacity.string_bytes);
|
||||
try testing.expect(page.string_alloc.capacityBytes() > original_alloc_cap);
|
||||
try testing.expectEqual(
|
||||
page.string_alloc.capacityBytes(),
|
||||
@as(usize, page.capacity.string_bytes),
|
||||
);
|
||||
|
||||
for (0..s.rows) |y| {
|
||||
for (0..s.cols) |x| {
|
||||
|
||||
@@ -124,6 +124,13 @@ pub fn OffsetHashMap(
|
||||
return Unmanaged.layoutForSize(cap);
|
||||
}
|
||||
|
||||
/// Returns the backing layout for an exact raw slot capacity. The
|
||||
/// capacity must be zero or a power of two. Unlike layout(), this
|
||||
/// does not scale the request by the configured load factor.
|
||||
pub fn layoutForCapacity(cap: Unmanaged.Size) Layout {
|
||||
return Unmanaged.layoutForCapacity(cap);
|
||||
}
|
||||
|
||||
/// Initialize a new HashMap with the given capacity and backing
|
||||
/// memory. The backing memory must be aligned to base_align.
|
||||
pub fn init(buf: OffsetBuf, l: Layout) Self {
|
||||
|
||||
@@ -1735,6 +1735,7 @@ pub const Page = struct {
|
||||
hyperlink_map_layout: hyperlink.Map.Layout,
|
||||
hyperlink_set_start: usize,
|
||||
hyperlink_set_layout: hyperlink.Set.Layout,
|
||||
/// Actual usable capacity after allocator layout rounding.
|
||||
capacity: Capacity,
|
||||
};
|
||||
|
||||
@@ -1762,14 +1763,18 @@ pub const Page = struct {
|
||||
const grapheme_alloc_start = alignForward(usize, styles_end, GraphemeAlloc.base_align.toByteUnits());
|
||||
const grapheme_alloc_end = grapheme_alloc_start + grapheme_alloc_layout.total_size;
|
||||
|
||||
const grapheme_count: usize = count: {
|
||||
if (cap.grapheme_bytes == 0) break :count 0;
|
||||
// Use divCeil to match GraphemeAlloc.layout() which uses alignForward,
|
||||
// ensuring grapheme_map has capacity when grapheme_alloc has chunks.
|
||||
const base = std.math.divCeil(usize, cap.grapheme_bytes, grapheme_chunk) catch unreachable;
|
||||
break :count std.math.ceilPowerOfTwo(usize, base) catch unreachable;
|
||||
};
|
||||
const grapheme_map_layout = GraphemeMap.layout(@intCast(grapheme_count));
|
||||
// Size the map for every chunk the allocator can address. Using the
|
||||
// allocator layout makes Page.layout idempotent after capacity
|
||||
// rounding and avoids applying a separate rounding policy here.
|
||||
const grapheme_count = grapheme_alloc_layout.bitmap_count *
|
||||
GraphemeAlloc.bitmap_bit_size;
|
||||
const grapheme_map_capacity = if (grapheme_count == 0)
|
||||
0
|
||||
else
|
||||
std.math.ceilPowerOfTwo(usize, grapheme_count) catch unreachable;
|
||||
const grapheme_map_layout = GraphemeMap.layoutForCapacity(
|
||||
@intCast(grapheme_map_capacity),
|
||||
);
|
||||
const grapheme_map_start = alignForward(usize, grapheme_alloc_end, GraphemeMap.base_align.toByteUnits());
|
||||
const grapheme_map_end = grapheme_map_start + grapheme_map_layout.total_size;
|
||||
|
||||
@@ -1777,6 +1782,22 @@ pub const Page = struct {
|
||||
const string_start = alignForward(usize, grapheme_map_end, StringAlloc.base_align.toByteUnits());
|
||||
const string_end = string_start + string_layout.total_size;
|
||||
|
||||
// Bitmap allocators address complete 64-chunk words. Preserve their
|
||||
// actual usable byte capacities so doubling a Page capacity always
|
||||
// advances to a larger allocator layout rather than repeating the
|
||||
// same rounded layout.
|
||||
var actual_capacity = cap;
|
||||
actual_capacity.grapheme_bytes = std.math.cast(
|
||||
size.GraphemeBytesInt,
|
||||
grapheme_alloc_layout.bitmap_count *
|
||||
GraphemeAlloc.bitmap_bit_size * grapheme_chunk,
|
||||
) orelse std.math.maxInt(size.GraphemeBytesInt);
|
||||
actual_capacity.string_bytes = std.math.cast(
|
||||
size.StringBytesInt,
|
||||
string_layout.bitmap_count *
|
||||
StringAlloc.bitmap_bit_size * string_chunk,
|
||||
) orelse std.math.maxInt(size.StringBytesInt);
|
||||
|
||||
const hyperlink_count = @divFloor(cap.hyperlink_bytes, @sizeOf(hyperlink.Set.Item));
|
||||
const hyperlink_set_layout: hyperlink.Set.Layout = .init(@intCast(hyperlink_count));
|
||||
const hyperlink_set_start = alignForward(usize, string_end, hyperlink.Set.base_align.toByteUnits());
|
||||
@@ -1814,7 +1835,7 @@ pub const Page = struct {
|
||||
.hyperlink_map_layout = hyperlink_map_layout,
|
||||
.hyperlink_set_start = hyperlink_set_start,
|
||||
.hyperlink_set_layout = hyperlink_set_layout,
|
||||
.capacity = cap,
|
||||
.capacity = actual_capacity,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -2675,6 +2696,56 @@ test "Page init" {
|
||||
defer page.deinit();
|
||||
}
|
||||
|
||||
test "Page capacity reflects bitmap allocator rounding" {
|
||||
var page = try Page.init(.{
|
||||
.cols = 1,
|
||||
.rows = 1,
|
||||
.grapheme_bytes = 1,
|
||||
.string_bytes = 1,
|
||||
});
|
||||
defer page.deinit();
|
||||
|
||||
try testing.expectEqual(
|
||||
page.grapheme_alloc.capacityBytes(),
|
||||
@as(usize, page.capacity.grapheme_bytes),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
page.string_alloc.capacityBytes(),
|
||||
@as(usize, page.capacity.string_bytes),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
page.memory.len,
|
||||
Page.layout(page.capacity).total_size,
|
||||
);
|
||||
}
|
||||
|
||||
test "Page grapheme map covers rounded allocator" {
|
||||
for ([_]usize{ 0, 1, 65, 129, 192, 512 }) |requested_chunks| {
|
||||
const cap: Capacity = .{
|
||||
.cols = 1,
|
||||
.rows = 1,
|
||||
.grapheme_bytes = @intCast(
|
||||
requested_chunks * grapheme_chunk,
|
||||
),
|
||||
};
|
||||
const layout = Page.layout(cap);
|
||||
const alloc_chunks = layout.grapheme_alloc_layout.bitmap_count *
|
||||
GraphemeAlloc.bitmap_bit_size;
|
||||
try testing.expect(
|
||||
layout.grapheme_map_layout.capacity >= alloc_chunks,
|
||||
);
|
||||
const normalized = Page.layout(layout.capacity);
|
||||
try testing.expectEqual(
|
||||
layout.grapheme_alloc_layout.total_size,
|
||||
normalized.grapheme_alloc_layout.total_size,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
layout.grapheme_map_layout.capacity,
|
||||
normalized.grapheme_map_layout.capacity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test "Page read and write cells" {
|
||||
var page = try Page.init(.{
|
||||
.cols = 10,
|
||||
|
||||
Reference in New Issue
Block a user