mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 08:01:45 +00:00
libghostty: attacking wasm binary size (#13830)
This should release our ReleaseFast bundle from 1.1MB to ~800KB. The major win is disabling logging in ReleaseFast wasm builds (~200KB). The next is removing aggressive inlining in paths that don't make sense for performance. Verified with benchmarks on native to not affect anything really. The third was really dumb: `var buf: [4096]u32 = @splat(c)` in LLVM releasefast for wasm was lowering to 4096 separate `i32.store`... like... 30KB of code. Replacing this with a for loop reduced by 30KB and made REP (a rare sequence) 11x faster lol.
This commit is contained in:
@@ -391,16 +391,15 @@ pub const std_options: std.Options = opts: {
|
||||
var options: std.Options = .{};
|
||||
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
// Wasm builds we specifically want to optimize for space with small
|
||||
// releases so we bump up to warn. Everything else acts pretty normal.
|
||||
options.log_level = switch (builtin.mode) {
|
||||
.Debug => .debug,
|
||||
.ReleaseSmall => .warn,
|
||||
else => .info,
|
||||
};
|
||||
|
||||
// Wasm doesn't have access to stdio so we have a custom log function.
|
||||
options.logFn = @import("os/wasm/log.zig").log;
|
||||
// In non-debug modes, we want to ship effectively no logging
|
||||
// warn and lower add ~200KB at the time of this comment.
|
||||
if (builtin.mode == .Debug) {
|
||||
options.log_level = .debug;
|
||||
options.logFn = @import("os/wasm/log.zig").log;
|
||||
} else {
|
||||
options.log_level = .err;
|
||||
options.logFn = @import("os/wasm/log.zig").noop;
|
||||
}
|
||||
} else if (terminal.options.c_abi) {
|
||||
// For C ABI builds, use a custom log function that dispatches to an
|
||||
// embedder-provided callback (or silently discards when none is set).
|
||||
|
||||
@@ -4,6 +4,22 @@ const wasm = @import("../wasm.zig");
|
||||
// Use the correct implementation
|
||||
pub const log = Freestanding.log;
|
||||
|
||||
/// A log function that discards everything. Since the format string and
|
||||
/// scope are comptime parameters, using this compiles out all logging
|
||||
/// machinery: call sites, format strings, and the std.fmt code they
|
||||
/// reference (~220KB in a ReleaseFast wasm build).
|
||||
pub fn noop(
|
||||
comptime level: std.log.Level,
|
||||
comptime scope: @TypeOf(.EnumLiteral),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
_ = level;
|
||||
_ = scope;
|
||||
_ = format;
|
||||
_ = args;
|
||||
}
|
||||
|
||||
/// Freestanding implementation calls an extern "log" function.
|
||||
pub const Freestanding = struct {
|
||||
// The function std.log will call.
|
||||
|
||||
@@ -498,7 +498,14 @@ pub fn printRepeat(self: *Terminal, count_req: usize) !void {
|
||||
// identical to calling print per codepoint: ineligible characters
|
||||
// or terminal states (insert mode, grapheme clustering, hyperlinks,
|
||||
// etc.) fall back to the per-codepoint print() path internally.
|
||||
var buf: [4096]u32 = @splat(c);
|
||||
//
|
||||
// The buffer is filled with a runtime-bounded loop rather than
|
||||
// `= @splat(c)`: a comptime-known 4096-element splat gets fully
|
||||
// unrolled into ~33KB of consecutive stores (LLVM won't re-roll
|
||||
// or vectorize it, see quirks_memset.zig), and it would fill the
|
||||
// whole buffer even for the typical small repeat counts.
|
||||
var buf: [4096]u32 = undefined;
|
||||
for (buf[0..@min(remaining, buf.len)]) |*cp| cp.* = c;
|
||||
while (remaining > 0) {
|
||||
const n = @min(remaining, buf.len);
|
||||
try self.printSlice(buf[0..n]);
|
||||
|
||||
@@ -1268,32 +1268,17 @@ pub const PageFormatter = struct {
|
||||
if (style_id == invalid_style_id) break :fast;
|
||||
}
|
||||
|
||||
// Specialized on point tracking so that the common
|
||||
// non-tracking case has zero per-cell overhead.
|
||||
const consumed = if (self.point_map == null)
|
||||
try self.writeCellRun(
|
||||
emit,
|
||||
false,
|
||||
writer,
|
||||
cells_subset[cell_i..],
|
||||
x,
|
||||
y,
|
||||
style_id,
|
||||
current_hyperlink_id,
|
||||
&blank_cells,
|
||||
)
|
||||
else
|
||||
try self.writeCellRun(
|
||||
emit,
|
||||
true,
|
||||
writer,
|
||||
cells_subset[cell_i..],
|
||||
x,
|
||||
y,
|
||||
style_id,
|
||||
current_hyperlink_id,
|
||||
&blank_cells,
|
||||
);
|
||||
const consumed = try self.writeCellRun(
|
||||
emit,
|
||||
self.point_map != null,
|
||||
writer,
|
||||
cells_subset[cell_i..],
|
||||
x,
|
||||
y,
|
||||
style_id,
|
||||
current_hyperlink_id,
|
||||
&blank_cells,
|
||||
);
|
||||
|
||||
// Zero cells consumed means the first cell isn't
|
||||
// eligible for the fast path; handle it below.
|
||||
@@ -1579,10 +1564,15 @@ pub const PageFormatter = struct {
|
||||
/// Blank cell accounting matches the slow path: accumulated blanks
|
||||
/// are only materialized once a non-blank cell is found, and any
|
||||
/// remainder is written back to `blank_cells`.
|
||||
fn writeCellRun(
|
||||
// Deliberately not inlined: this is instantiated per emit format and
|
||||
// inlining every copy into formatWithStateEmit's row loop bloats the
|
||||
// binary. track_points is a runtime bool for the same reason: a
|
||||
// comptime bool doubles the instantiation count for one predictable
|
||||
// branch per emitted cell.
|
||||
noinline fn writeCellRun(
|
||||
self: *const PageFormatter,
|
||||
comptime emit: Format,
|
||||
comptime track_points: bool,
|
||||
track_points: bool,
|
||||
writer: *std.Io.Writer,
|
||||
cells: []const Cell,
|
||||
run_x: size.CellCountInt,
|
||||
@@ -1666,7 +1656,7 @@ pub const PageFormatter = struct {
|
||||
|
||||
// This cell produces output: materialize accumulated blanks.
|
||||
if (pending > 0) {
|
||||
if (comptime track_points) try self.appendBlankPoints(
|
||||
if (track_points) try self.appendBlankPoints(
|
||||
&self.point_map.?,
|
||||
pending,
|
||||
x,
|
||||
@@ -1718,7 +1708,7 @@ pub const PageFormatter = struct {
|
||||
}
|
||||
|
||||
// All of the cell's bytes map to the cell's coordinate.
|
||||
if (comptime track_points) {
|
||||
if (track_points) {
|
||||
const map = &self.point_map.?;
|
||||
map.map.appendNTimes(
|
||||
map.alloc,
|
||||
|
||||
Reference in New Issue
Block a user