mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 13:30:29 +00:00
terminal: more strict sizing for page capacities, max cap can fit 64-bit
This commit is contained in:
@@ -1318,7 +1318,7 @@ const ReflowCursor = struct {
|
||||
// Grow our capacity until we can
|
||||
// definitely fit the extra bytes.
|
||||
const required = cps.len * @sizeOf(u21);
|
||||
var new_grapheme_capacity: usize = cap.grapheme_bytes;
|
||||
var new_grapheme_capacity: size.GraphemeBytesInt = cap.grapheme_bytes;
|
||||
while (new_grapheme_capacity - cap.grapheme_bytes < required) {
|
||||
new_grapheme_capacity *= 2;
|
||||
}
|
||||
@@ -1362,7 +1362,7 @@ const ReflowCursor = struct {
|
||||
} else |_| {
|
||||
// Grow our capacity until we can
|
||||
// definitely fit the extra bytes.
|
||||
var new_string_capacity: usize = cap.string_bytes;
|
||||
var new_string_capacity: size.StringBytesInt = cap.string_bytes;
|
||||
while (new_string_capacity - cap.string_bytes < additional_required_string_capacity) {
|
||||
new_string_capacity *= 2;
|
||||
}
|
||||
@@ -2647,16 +2647,16 @@ pub const AdjustCapacity = struct {
|
||||
/// Adjust the number of styles in the page. This may be
|
||||
/// rounded up if necessary to fit alignment requirements,
|
||||
/// but it will never be rounded down.
|
||||
styles: ?usize = null,
|
||||
styles: ?size.StyleCountInt = null,
|
||||
|
||||
/// Adjust the number of available grapheme bytes in the page.
|
||||
grapheme_bytes: ?usize = null,
|
||||
grapheme_bytes: ?size.GraphemeBytesInt = null,
|
||||
|
||||
/// Adjust the number of available hyperlink bytes in the page.
|
||||
hyperlink_bytes: ?usize = null,
|
||||
hyperlink_bytes: ?size.HyperlinkCountInt = null,
|
||||
|
||||
/// Adjust the number of available string bytes in the page.
|
||||
string_bytes: ?usize = null,
|
||||
string_bytes: ?size.StringBytesInt = null,
|
||||
};
|
||||
|
||||
pub const AdjustCapacityError = Allocator.Error || Page.CloneFromError;
|
||||
@@ -2692,23 +2692,19 @@ pub fn adjustCapacity(
|
||||
// All ceilPowerOfTwo is unreachable because we're always same or less
|
||||
// bit width so maxInt is always possible.
|
||||
if (adjustment.styles) |v| {
|
||||
comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
|
||||
const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
|
||||
const aligned = std.math.ceilPowerOfTwo(size.StyleCountInt, v) catch unreachable;
|
||||
cap.styles = @max(cap.styles, aligned);
|
||||
}
|
||||
if (adjustment.grapheme_bytes) |v| {
|
||||
comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
|
||||
const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
|
||||
const aligned = std.math.ceilPowerOfTwo(size.GraphemeBytesInt, v) catch unreachable;
|
||||
cap.grapheme_bytes = @max(cap.grapheme_bytes, aligned);
|
||||
}
|
||||
if (adjustment.hyperlink_bytes) |v| {
|
||||
comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
|
||||
const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
|
||||
const aligned = std.math.ceilPowerOfTwo(size.HyperlinkCountInt, v) catch unreachable;
|
||||
cap.hyperlink_bytes = @max(cap.hyperlink_bytes, aligned);
|
||||
}
|
||||
if (adjustment.string_bytes) |v| {
|
||||
comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
|
||||
const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
|
||||
const aligned = std.math.ceilPowerOfTwo(size.StringBytesInt, v) catch unreachable;
|
||||
cap.string_bytes = @max(cap.string_bytes, aligned);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ const autoHash = std.hash.autoHash;
|
||||
const autoHashStrat = std.hash.autoHashStrat;
|
||||
|
||||
/// The unique identifier for a hyperlink. This is at most the number of cells
|
||||
/// that can fit in a single terminal page.
|
||||
pub const Id = size.CellCountInt;
|
||||
/// that can fit in a single terminal page, since each cell can only contain
|
||||
/// at most one hyperlink.
|
||||
pub const Id = size.HyperlinkCountInt;
|
||||
|
||||
// The mapping of cell to hyperlink. We use an offset hash map to save space
|
||||
// since its very unlikely a cell is a hyperlink, so its a waste to store
|
||||
|
||||
@@ -1569,7 +1569,10 @@ 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 = @divFloor(cap.grapheme_bytes, grapheme_chunk);
|
||||
const grapheme_count = std.math.ceilPowerOfTwo(
|
||||
usize,
|
||||
@divFloor(cap.grapheme_bytes, grapheme_chunk),
|
||||
) catch unreachable;
|
||||
const grapheme_map_layout = GraphemeMap.layout(@intCast(grapheme_count));
|
||||
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;
|
||||
@@ -1639,25 +1642,33 @@ pub const Size = struct {
|
||||
};
|
||||
|
||||
/// Capacity of this page.
|
||||
///
|
||||
/// This capacity can be maxed out (every field max) and still fit
|
||||
/// within a 64-bit memory space. If you need more than this, you will
|
||||
/// need to split data across separate pages.
|
||||
///
|
||||
/// For 32-bit systems, it is possible to overflow the addressable
|
||||
/// space and this is something we still need to address in the future
|
||||
/// likely by limiting the maximum capacity on 32-bit systems further.
|
||||
pub const Capacity = struct {
|
||||
/// Number of columns and rows we can know about.
|
||||
cols: size.CellCountInt,
|
||||
rows: size.CellCountInt,
|
||||
|
||||
/// Number of unique styles that can be used on this page.
|
||||
styles: usize = 16,
|
||||
styles: size.StyleCountInt = 16,
|
||||
|
||||
/// Number of bytes to allocate for hyperlink data. Note that the
|
||||
/// amount of data used for hyperlinks in total is more than this because
|
||||
/// hyperlinks use string data as well as a small amount of lookup metadata.
|
||||
/// This number is a rough approximation.
|
||||
hyperlink_bytes: usize = hyperlink_bytes_default,
|
||||
hyperlink_bytes: size.HyperlinkCountInt = hyperlink_bytes_default,
|
||||
|
||||
/// Number of bytes to allocate for grapheme data.
|
||||
grapheme_bytes: usize = grapheme_bytes_default,
|
||||
grapheme_bytes: size.GraphemeBytesInt = grapheme_bytes_default,
|
||||
|
||||
/// Number of bytes to allocate for strings.
|
||||
string_bytes: usize = string_bytes_default,
|
||||
string_bytes: size.StringBytesInt = string_bytes_default,
|
||||
|
||||
pub const Adjustment = struct {
|
||||
cols: ?size.CellCountInt = null,
|
||||
@@ -2025,6 +2036,19 @@ pub const Cell = packed struct(u64) {
|
||||
// //const pages = total_size / std.heap.page_size_min;
|
||||
// }
|
||||
|
||||
test "Page.layout can take a maxed capacity" {
|
||||
// Our intention is for a maxed-out capacity to always fit
|
||||
// within a page layout without trigering runtime safety on any
|
||||
// overflow. This simplifies some of our handling downstream of the
|
||||
// call (relevant to: https://github.com/ghostty-org/ghostty/issues/10258)
|
||||
var cap: Capacity = undefined;
|
||||
inline for (@typeInfo(Capacity).@"struct".fields) |field| {
|
||||
@field(cap, field.name) = std.math.maxInt(field.type);
|
||||
}
|
||||
|
||||
_ = Page.layout(cap);
|
||||
}
|
||||
|
||||
test "Cell is zero by default" {
|
||||
const cell = Cell.init(0);
|
||||
const cell_int: u64 = @bitCast(cell);
|
||||
|
||||
@@ -11,9 +11,32 @@ pub const max_page_size = std.math.maxInt(u32);
|
||||
/// derived from the maximum terminal page size.
|
||||
pub const OffsetInt = std.math.IntFittingRange(0, max_page_size - 1);
|
||||
|
||||
/// The int type that can contain the maximum number of cells in a page.
|
||||
pub const CellCountInt = u16; // TODO: derive
|
||||
/// Int types for maximum values of things. A lot of these sizes are
|
||||
/// based on "X is enough for any reasonable use case" principles.
|
||||
// The goal is that a user can have the maxInt amount of all of these
|
||||
// present at one time and be able to address them in a single Page.zig.
|
||||
|
||||
// Total number of cells that are possible in each dimension (row/col).
|
||||
// Based on 2^16 being enough for any reasonable terminal size and allowing
|
||||
// IDs to remain 16-bit.
|
||||
pub const CellCountInt = u16;
|
||||
|
||||
// Total number of styles and hyperlinks that are possible in a page.
|
||||
// We match CellCountInt here because each cell in a single row can have at
|
||||
// most one style, making it simple to split a page by splitting rows.
|
||||
//
|
||||
// Note due to the way RefCountedSet works, we are short one value, but
|
||||
// this is a theoretical limit we accept. A page with a single row max
|
||||
// columns wide would be one short of having every cell have a unique style.
|
||||
pub const StyleCountInt = CellCountInt;
|
||||
pub const HyperlinkCountInt = CellCountInt;
|
||||
|
||||
// Total number of bytes that can be taken up by grapheme data and string
|
||||
// data. Both of these technically unlimited with malicious input, but
|
||||
// we choose a reasonable limit of 2^32 (4GB) per.
|
||||
pub const GraphemeBytesInt = u32;
|
||||
pub const StringBytesInt = u32;
|
||||
|
||||
/// The offset from the base address of the page to the start of some data.
|
||||
/// This is typed for ease of use.
|
||||
///
|
||||
|
||||
@@ -11,7 +11,7 @@ const RefCountedSet = @import("ref_counted_set.zig").RefCountedSet;
|
||||
|
||||
/// The unique identifier for a style. This is at most the number of cells
|
||||
/// that can fit into a terminal page.
|
||||
pub const Id = size.CellCountInt;
|
||||
pub const Id = size.StyleCountInt;
|
||||
|
||||
/// The Id to use for default styling.
|
||||
pub const default_id: Id = 0;
|
||||
|
||||
Reference in New Issue
Block a user