From 39c8dd6ad7e97a974c7a194ae212c1677e0c7029 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 24 Sep 2026 13:54:10 -0300 Subject: Allocate from libc's malloc in every release shell, and check macOS's Debug build too The tty, gui and detached shells allocate from std.start's gpa, which is a DebugAllocator in Debug and, because we link libc, libc's malloc otherwise. The macOS shell hardcoded std.heap.smp_allocator in every mode, so its Debug build checked nothing it allocated outside memory.zig's subsystems. Which allocator a release build should use was measured rather than assumed, in ReleaseFast, with an experimental gui that chose at startup between glibc's malloc and smp_allocator, with and without memory.zig's stack-fallback buffers, plus an A/A pair; ten interleaved rounds, compared round by round. The gui frame-cost harness (idle, scroll and typing in src/pardes.zig, terminal spew, wheel-scrolling docs/design.pdf, first paint) saw every candidate within the A/A pair's noise. Twelve rounds of the allocation-heavy paths themselves, highlighting all of src/pardes.zig (305k tree-sitter allocations) and pardes-pdf-bench's MuPDF renders, split them: smp_allocator was 2 to 6% slower on MuPDF's page-sized rasters (slower in 9 to 11 of 12 rounds), no better on tree-sitter, and the stack-fallback buffers changed nothing either way. So glibc's malloc it is, with the buffers kept. macos.zig now takes a DebugAllocator in Debug, deinitialized in pardes_deinit with leaks logged the way std.start treats the others', and libc's malloc otherwise. main.zig says why init.gpa is kept. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/macos.zig | 14 +++++++++++++- src/main.zig | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/macos.zig b/src/macos.zig index 3ff8bdfa..b19c5996 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -692,8 +692,15 @@ export fn pardes_init(runtime: ?*const Runtime, cols_arg: u16, rows_arg: u16) c_ return 0; } +/// The shell's gpa in a Debug build, as std.start's is for the others. +var debug_gpa: std.heap.DebugAllocator(.{}) = .init; + fn initCore(runtime: ?*const Runtime, cols_arg: u16, rows_arg: u16) !void { - const gpa = std.heap.smp_allocator; + // The allocator every other shell gets from std.start: a DebugAllocator + // in Debug, libc's malloc otherwise. libc's measured at least as fast as + // std.heap.smp_allocator everywhere and faster on MuPDF's page-sized + // rasters, so this shell no longer differs. + const gpa = if (builtin.mode == .Debug) debug_gpa.allocator() else std.heap.c_allocator; const allocs = pardes.memory.init(gpa); errdefer pardes.memory.deinit(); @@ -824,6 +831,11 @@ export fn pardes_deinit() void { st.threaded.deinit(); st.gpa.destroy(st.threaded); pardes.memory.deinit(); + // Leaks are logged and do not fail, as std.start treats the others'. + if (builtin.mode == .Debug) { + _ = debug_gpa.deinit(); + debug_gpa = .init; + } state = null; } diff --git a/src/main.zig b/src/main.zig index 050cee60..86c302fe 100644 --- a/src/main.zig +++ b/src/main.zig @@ -138,6 +138,11 @@ fn webMain() !void { try @import("gui/gui.zig").run(.{}, .{}, null); } +// init.gpa, what the tty, gui and detached shells allocate from, is +// std.start's: a DebugAllocator in Debug and, since we link libc, libc's +// malloc otherwise. Kept on purpose: glibc's malloc was measured at least as +// fast as std.heap.smp_allocator on every scenario and faster on MuPDF's +// page-sized rasters. macos.zig makes the same choice for itself. fn nativeMain(init: std.process.Init) !void { var stderr_probe: [64]u8 = undefined; _ = std.debug.lockStderr(&stderr_probe); -- cgit v1.3