terminal: reduce reflow run scan to masked compares

Finding the length of a bulk-copyable cell run evaluated the
field-wise bulkCopyable predicate plus a style compare per cell,
which compiles to a chain of extracts and branches and had become
the hottest loop in a column reflow.

Once the first cell passes the full predicate, a cell continues the
run iff it matches the first cell in content tag, style id, wide
property, and hyperlink flag, so the continuation test is now a
masked compare of the raw cell bits via the Mask helper, plus a
masked equality test against the Kitty virtual placeholder codepoint
for text runs (placeholders must set a row flag so they take the
slow path). This is slightly stricter than the predicate (a bg-color
cell no longer extends an unstyled text run), which only splits a
copy into multiple runs and remains correct.

1.21x faster on ghostty-bench +terminal-resize --mode=cols (120x80
terminal, 10k-line scrollback, shrink/grow column reflow cycles).
This commit is contained in:
Mitchell Hashimoto
2026-07-31 20:56:58 -07:00
parent 0fb3565c76
commit d4e446c480

View File

@@ -1859,18 +1859,71 @@ const ReflowCursor = struct {
};
}
/// The group length for the masked-compare helpers below. This
/// matches the group length used by other cell scans (e.g. the
/// render state scans).
const bulk_group_len = 8;
/// Masked compare helper covering every cell field that a run
/// must share to be copied by copyRun: given that the first cell
/// of a run passed the full bulkCopyable predicate, equality on
/// these fields implies the same for every subsequent cell, with
/// the same style.
///
/// Note this is slightly stricter than bulkCopyable (e.g. a
/// bg-color cell won't extend an unstyled text run even though it
/// is copyable): that only splits the copy into multiple runs,
/// which is still correct.
const BulkRunMask = pagepkg.Mask(pagepkg.Cell, &.{
"content_tag",
"style_id",
"wide",
"hyperlink",
}, bulk_group_len);
/// Masked compare helper for detecting the Kitty virtual
/// placeholder codepoint in text cells. Placeholders take the
/// slow path (they must set a row flag), so they terminate a run.
const PlaceholderMask = pagepkg.Mask(pagepkg.Cell, &.{
"content.codepoint.data",
}, bulk_group_len);
/// The length of the prefix of cells that can be copied at once
/// with copyRun: bulk-copyable cells sharing a single style.
/// with copyRun: bulk-copyable cells sharing a single style. The
/// scan uses masked compares of the raw cell bits (see
/// BulkRunMask), which is significantly cheaper than the
/// field-wise predicate for this hot loop.
fn bulkRunLength(cells: []const pagepkg.Cell) usize {
if (cells.len == 0) return 0;
const first = cells[0];
if (!bulkCopyable(first)) return 0;
const run_pattern = BulkRunMask.pattern(first);
// Only text cells can contain a placeholder; for bg color
// tags the content bits are a color, so we skip the check for
// those (the tag is part of BulkRunMask, making tags uniform
// per run).
const check_placeholder = build_options.kitty_graphics and
first.content_tag == .codepoint;
const placeholder_pattern = comptime pattern: {
// Never used without kitty graphics (check_placeholder
// is comptime-false), but it must still compile and the
// placeholder codepoint doesn't exist in that build.
if (!build_options.kitty_graphics) break :pattern 0;
break :pattern PlaceholderMask.pattern(.init(
kitty.graphics.unicode.placeholder,
));
};
var len: usize = 1;
while (len < cells.len) : (len += 1) {
const cell = cells[len];
if (!bulkCopyable(cell) or
cell.style_id != first.style_id) break;
if (!BulkRunMask.eqlScalar(cells[len], run_pattern)) break;
if (check_placeholder and PlaceholderMask.eqlScalar(
cells[len],
placeholder_pattern,
)) break;
}
return len;