From 3e8d94bb1c20f92fcafee9bac2c5203ea725f2da Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 17 Nov 2025 21:34:33 -0700 Subject: [PATCH] perf: misc inlines and branch hints Inlined trivial functions, added cold branch hints to error paths, added likely branch hints to common paths --- src/terminal/Parser.zig | 2 ++ src/terminal/Terminal.zig | 16 ++++++++++++++-- src/terminal/color.zig | 29 ++++++++++++++++++++++++----- src/terminal/osc.zig | 9 +++++++++ src/terminal/page.zig | 14 +++++++------- src/terminal/ref_counted_set.zig | 2 ++ 6 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index 612c93ee0..2a2e72a1d 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -312,6 +312,7 @@ pub fn next(self: *Parser, c: u8) [3]?Action { pub inline fn collect(self: *Parser, c: u8) void { if (self.intermediates_idx >= MAX_INTERMEDIATE) { + @branchHint(.cold); log.warn("invalid intermediates count", .{}); return; } @@ -386,6 +387,7 @@ inline fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { // We only allow colon or mixed separators for the 'm' command. if (c != 'm' and self.params_sep.count() > 0) { + @branchHint(.cold); log.warn( "CSI colon or mixed separators only allowed for 'm' command, got: {f}", .{result}, diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 8fa0e655d..fb5b67127 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -293,7 +293,10 @@ pub fn print(self: *Terminal, c: u21) !void { // log.debug("print={x} y={} x={}", .{ c, self.screens.active.cursor.y, self.screens.active.cursor.x }); // If we're not on the main display, do nothing for now - if (self.status_display != .main) return; + if (self.status_display != .main) { + @branchHint(.cold); + return; + } // After doing any printing, wrapping, scrolling, etc. we want to ensure // that our screen remains in a consistent state. @@ -313,6 +316,7 @@ pub fn print(self: *Terminal, c: u21) !void { self.modes.get(.grapheme_cluster) and self.screens.active.cursor.x > 0) grapheme: { + @branchHint(.unlikely); // We need the previous cell to determine if we're at a grapheme // break or not. If we are NOT, then we are still combining the // same grapheme. Otherwise, we can stay in this cell. @@ -478,6 +482,7 @@ pub fn print(self: *Terminal, c: u21) !void { // Attach zero-width characters to our cell as grapheme data. if (width == 0) { + @branchHint(.unlikely); // If we have grapheme clustering enabled, we don't blindly attach // any zero width character to our cells and we instead just ignore // it. @@ -535,6 +540,7 @@ pub fn print(self: *Terminal, c: u21) !void { switch (width) { // Single cell is very easy: just write in the cell 1 => { + @branchHint(.likely); self.screens.active.cursorMarkDirty(); @call(.always_inline, printCell, .{ self, c, .narrow }); }, @@ -602,10 +608,14 @@ fn printCell( self.screens.active.charset.single_shift = null; break :blk key_once; } else self.screens.active.charset.gl; + const set = self.screens.active.charset.charsets.get(key); // UTF-8 or ASCII is used as-is - if (set == .utf8 or set == .ascii) break :c unmapped_c; + if (set == .utf8 or set == .ascii) { + @branchHint(.likely); + break :c unmapped_c; + } // If we're outside of ASCII range this is an invalid value in // this table so we just return space. @@ -718,6 +728,7 @@ fn printCell( // row so that the renderer can lookup rows with these much faster. if (comptime build_options.kitty_graphics) { if (c == kitty.graphics.unicode.placeholder) { + @branchHint(.unlikely); self.screens.active.cursor.page_row.kitty_virtual_placeholder = true; } } @@ -727,6 +738,7 @@ fn printCell( // overwriting the same hyperlink. if (self.screens.active.cursor.hyperlink_id > 0) { self.screens.active.cursorSetHyperlink() catch |err| { + @branchHint(.unlikely); log.warn("error reallocating for more hyperlink space, ignoring hyperlink err={}", .{err}); assert(!cell.hyperlink); }; diff --git a/src/terminal/color.zig b/src/terminal/color.zig index ce7e9ce5d..07c3e72f5 100644 --- a/src/terminal/color.zig +++ b/src/terminal/color.zig @@ -356,8 +356,12 @@ pub const RGB = packed struct(u24) { /// /// The value should be between 0.0 and 1.0, inclusive. fn fromIntensity(value: []const u8) !u8 { - const i = std.fmt.parseFloat(f64, value) catch return error.InvalidFormat; + const i = std.fmt.parseFloat(f64, value) catch { + @branchHint(.cold); + return error.InvalidFormat; + }; if (i < 0.0 or i > 1.0) { + @branchHint(.cold); return error.InvalidFormat; } @@ -370,10 +374,15 @@ pub const RGB = packed struct(u24) { /// value scaled in 4, 8, 12, or 16 bits, respectively. fn fromHex(value: []const u8) !u8 { if (value.len == 0 or value.len > 4) { + @branchHint(.cold); return error.InvalidFormat; } - const color = std.fmt.parseUnsigned(u16, value, 16) catch return error.InvalidFormat; + const color = std.fmt.parseUnsigned(u16, value, 16) catch { + @branchHint(.cold); + return error.InvalidFormat; + }; + const divisor: usize = switch (value.len) { 1 => std.math.maxInt(u4), 2 => std.math.maxInt(u8), @@ -407,6 +416,7 @@ pub const RGB = packed struct(u24) { /// per color channel. pub fn parse(value: []const u8) !RGB { if (value.len == 0) { + @branchHint(.cold); return error.InvalidFormat; } @@ -433,7 +443,10 @@ pub const RGB = packed struct(u24) { .b = try RGB.fromHex(value[9..13]), }, - else => return error.InvalidFormat, + else => { + @branchHint(.cold); + return error.InvalidFormat; + }, } } @@ -443,6 +456,7 @@ pub const RGB = packed struct(u24) { if (x11_color.map.get(std.mem.trim(u8, value, " "))) |rgb| return rgb; if (value.len < "rgb:a/a/a".len or !std.mem.eql(u8, value[0..3], "rgb")) { + @branchHint(.cold); return error.InvalidFormat; } @@ -454,6 +468,7 @@ pub const RGB = packed struct(u24) { } else false; if (value[i] != ':') { + @branchHint(.cold); return error.InvalidFormat; } @@ -462,8 +477,10 @@ pub const RGB = packed struct(u24) { const r = r: { const slice = if (std.mem.indexOfScalarPos(u8, value, i, '/')) |end| value[i..end] - else + else { + @branchHint(.cold); return error.InvalidFormat; + }; i += slice.len + 1; @@ -476,8 +493,10 @@ pub const RGB = packed struct(u24) { const g = g: { const slice = if (std.mem.indexOfScalarPos(u8, value, i, '/')) |end| value[i..end] - else + else { + @branchHint(.cold); return error.InvalidFormat; + }; i += slice.len + 1; diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index ca212bae0..f62b7a6cd 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -524,6 +524,7 @@ pub const Parser = struct { // We always keep space for 1 byte at the end to null-terminate // values. if (self.buf_idx >= self.buf.len - 1) { + @branchHint(.cold); if (self.state != .invalid) { log.warn( "OSC sequence too long (> {d}), ignoring. state={}", @@ -1048,6 +1049,7 @@ pub const Parser = struct { ';' => { const ext = self.buf[self.buf_start .. self.buf_idx - 1]; if (!std.mem.eql(u8, ext, "notify")) { + @branchHint(.cold); log.warn("unknown rxvt extension: {s}", .{ext}); self.state = .invalid; return; @@ -1601,11 +1603,13 @@ pub const Parser = struct { fn endKittyColorProtocolOption(self: *Parser, kind: enum { key_only, key_and_value }, final: bool) void { if (self.temp_state.key.len == 0) { + @branchHint(.cold); log.warn("zero length key in kitty color protocol", .{}); return; } const key = kitty_color.Kind.parse(self.temp_state.key) orelse { + @branchHint(.cold); log.warn("unknown key in kitty color protocol: {s}", .{self.temp_state.key}); return; }; @@ -1620,6 +1624,7 @@ pub const Parser = struct { .kitty_color_protocol => |*v| { // Cap our allocation amount for our list. if (v.list.items.len >= @as(usize, kitty_color.Kind.max) * 2) { + @branchHint(.cold); self.state = .invalid; log.warn("exceeded limit for number of keys in kitty color protocol, ignoring", .{}); return; @@ -1631,11 +1636,13 @@ pub const Parser = struct { if (kind == .key_only or value.len == 0) { v.list.append(alloc, .{ .reset = key }) catch |err| { + @branchHint(.cold); log.warn("unable to append kitty color protocol option: {}", .{err}); return; }; } else if (mem.eql(u8, "?", value)) { v.list.append(alloc, .{ .query = key }) catch |err| { + @branchHint(.cold); log.warn("unable to append kitty color protocol option: {}", .{err}); return; }; @@ -1651,6 +1658,7 @@ pub const Parser = struct { }, }, }) catch |err| { + @branchHint(.cold); log.warn("unable to append kitty color protocol option: {}", .{err}); return; }; @@ -1681,6 +1689,7 @@ pub const Parser = struct { const alloc = self.alloc.?; const list = self.buf_dynamic.?; list.append(alloc, 0) catch { + @branchHint(.cold); log.warn("allocation failed on allocable string termination", .{}); self.temp_state.str.* = ""; return; diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 4b80aae45..6ed1db51a 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1896,7 +1896,7 @@ pub const Cell = packed struct(u64) { return cell; } - pub fn isZero(self: Cell) bool { + pub inline fn isZero(self: Cell) bool { return @as(u64, @bitCast(self)) == 0; } @@ -1906,7 +1906,7 @@ pub const Cell = packed struct(u64) { /// - Cell text is blank /// - Cell is styled but only with a background color and no text /// - Cell has a unicode placeholder for Kitty graphics protocol - pub fn hasText(self: Cell) bool { + pub inline fn hasText(self: Cell) bool { return switch (self.content_tag) { .codepoint, .codepoint_grapheme, @@ -1918,7 +1918,7 @@ pub const Cell = packed struct(u64) { }; } - pub fn codepoint(self: Cell) u21 { + pub inline fn codepoint(self: Cell) u21 { return switch (self.content_tag) { .codepoint, .codepoint_grapheme, @@ -1931,14 +1931,14 @@ pub const Cell = packed struct(u64) { } /// The width in grid cells that this cell takes up. - pub fn gridWidth(self: Cell) u2 { + pub inline fn gridWidth(self: Cell) u2 { return switch (self.wide) { .narrow, .spacer_head, .spacer_tail => 1, .wide => 2, }; } - pub fn hasStyling(self: Cell) bool { + pub inline fn hasStyling(self: Cell) bool { return self.style_id != stylepkg.default_id; } @@ -1957,12 +1957,12 @@ pub const Cell = packed struct(u64) { }; } - pub fn hasGrapheme(self: Cell) bool { + pub inline fn hasGrapheme(self: Cell) bool { return self.content_tag == .codepoint_grapheme; } /// Returns true if the set of cells has text in it. - pub fn hasTextAny(cells: []const Cell) bool { + pub inline fn hasTextAny(cells: []const Cell) bool { for (cells) |cell| { if (cell.hasText()) return true; } diff --git a/src/terminal/ref_counted_set.zig b/src/terminal/ref_counted_set.zig index 70007f00d..25512bdaf 100644 --- a/src/terminal/ref_counted_set.zig +++ b/src/terminal/ref_counted_set.zig @@ -256,6 +256,7 @@ pub fn RefCountedSet( // we may end up with a PSL of `len` which would exceed the bounds. // In such a case, we claim to be out of memory. if (self.psl_stats[self.psl_stats.len - 1] > 0) { + @branchHint(.cold); return AddError.OutOfMemory; } @@ -308,6 +309,7 @@ pub fn RefCountedSet( if (items[id].meta.ref == 0) { // See comment in `addContext` for details. if (self.psl_stats[self.psl_stats.len - 1] > 0) { + @branchHint(.cold); return AddError.OutOfMemory; }