From 9b8146a7df6270613ab48ba3df53e97a558d1ba4 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 27 Sep 2026 12:21:41 -0300 Subject: Keep the arena buffers out of Debug binaries The five arena buffers are zeroed globals instead of undefined fields, so a Debug build keeps them in .bss rather than writing 160 MB of 0xAA filler into every binary (Debug pardes 839 MB -> 246 MB). Co-Authored-By: Claude Opus 5.5 --- src/memory.zig | 94 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/memory.zig b/src/memory.zig index a2c5547c..b8a64a1a 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -48,48 +48,54 @@ pub const Allocators = struct { /// lock-free interface. The core's allocator is used from two threads: a 9P /// connection task answers a request while the editor's own thread is out /// in a syscall that allocates (`readFileLimit`, `grep`; see `pardes.turn`). -fn SharedStackFallback(comptime size: usize) type { - return struct { - buffer: [size]u8 = undefined, - fallback_allocator: Allocator = undefined, - fixed: std.heap.FixedBufferAllocator = undefined, - - fn get(self: *@This()) Allocator { - self.fixed = .init(self.buffer[0..]); - return .{ .ptr = self, .vtable = &.{ .alloc = alloc, .resize = resize, .remap = remap, .free = free } }; - } - - fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 { - const self: *@This() = @ptrCast(@alignCast(ctx)); - return self.fixed.threadSafeAllocator().rawAlloc(len, alignment, ra) orelse - self.fallback_allocator.rawAlloc(len, alignment, ra); - } - - fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) bool { - const self: *@This() = @ptrCast(@alignCast(ctx)); - if (self.fixed.ownsPtr(buf.ptr)) return self.fixed.threadSafeAllocator().rawResize(buf, alignment, new_len, ra); - return self.fallback_allocator.rawResize(buf, alignment, new_len, ra); - } - - fn remap(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) ?[*]u8 { - const self: *@This() = @ptrCast(@alignCast(ctx)); - if (self.fixed.ownsPtr(buf.ptr)) return self.fixed.threadSafeAllocator().rawRemap(buf, alignment, new_len, ra); - return self.fallback_allocator.rawRemap(buf, alignment, new_len, ra); - } - - fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ra: usize) void { - const self: *@This() = @ptrCast(@alignCast(ctx)); - if (self.fixed.ownsPtr(buf.ptr)) return self.fixed.threadSafeAllocator().rawFree(buf, alignment, ra); - return self.fallback_allocator.rawFree(buf, alignment, ra); - } - }; -} +/// The buffer is a separate zeroed global, not a field: an `undefined` one is +/// written into a Debug binary as 0xAA filler, 160 MB of file for the five +/// arenas, where a zeroed one is .bss and costs nothing. +const SharedStackFallback = struct { + buffer: []u8, + fallback_allocator: Allocator = undefined, + fixed: std.heap.FixedBufferAllocator = undefined, + + fn get(self: *@This()) Allocator { + self.fixed = .init(self.buffer); + return .{ .ptr = self, .vtable = &.{ .alloc = alloc, .resize = resize, .remap = remap, .free = free } }; + } + + fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 { + const self: *@This() = @ptrCast(@alignCast(ctx)); + return self.fixed.threadSafeAllocator().rawAlloc(len, alignment, ra) orelse + self.fallback_allocator.rawAlloc(len, alignment, ra); + } + + fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) bool { + const self: *@This() = @ptrCast(@alignCast(ctx)); + if (self.fixed.ownsPtr(buf.ptr)) return self.fixed.threadSafeAllocator().rawResize(buf, alignment, new_len, ra); + return self.fallback_allocator.rawResize(buf, alignment, new_len, ra); + } + + fn remap(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) ?[*]u8 { + const self: *@This() = @ptrCast(@alignCast(ctx)); + if (self.fixed.ownsPtr(buf.ptr)) return self.fixed.threadSafeAllocator().rawRemap(buf, alignment, new_len, ra); + return self.fallback_allocator.rawRemap(buf, alignment, new_len, ra); + } + + fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ra: usize) void { + const self: *@This() = @ptrCast(@alignCast(ctx)); + if (self.fixed.ownsPtr(buf.ptr)) return self.fixed.threadSafeAllocator().rawFree(buf, alignment, ra); + return self.fallback_allocator.rawFree(buf, alignment, ra); + } +}; -var pardes_fallback: SharedStackFallback(limits.arena.pardes) = .{}; -var frame_fallback: std.heap.StackFallbackAllocator(limits.arena.frame) = undefined; -var tree_sitter_fallback: std.heap.StackFallbackAllocator(limits.arena.tree_sitter) = undefined; -var image_fallback: std.heap.StackFallbackAllocator(limits.arena.image) = undefined; -var pdf_fallback: std.heap.StackFallbackAllocator(limits.arena.pdf) = undefined; +var pardes_buffer: [limits.arena.pardes]u8 = @splat(0); +var frame_buffer: [limits.arena.frame]u8 = @splat(0); +var tree_sitter_buffer: [limits.arena.tree_sitter]u8 = @splat(0); +var image_buffer: [limits.arena.image]u8 = @splat(0); +var pdf_buffer: [limits.arena.pdf]u8 = @splat(0); +var pardes_fallback: SharedStackFallback = .{ .buffer = &pardes_buffer }; +var frame_fallback: SharedStackFallback = .{ .buffer = &frame_buffer }; +var tree_sitter_fallback: SharedStackFallback = .{ .buffer = &tree_sitter_buffer }; +var image_fallback: SharedStackFallback = .{ .buffer = &image_buffer }; +var pdf_fallback: SharedStackFallback = .{ .buffer = &pdf_buffer }; const Debug = std.heap.DebugAllocator(.{}); var pardes_debug: Debug = .init; @@ -109,13 +115,9 @@ pub fn init(fallback: Allocator) Allocators { const backing = if (debug_enabled) std.heap.page_allocator else fallback; pardes_fallback.fallback_allocator = backing; frame_fallback.fallback_allocator = backing; - frame_fallback.get_called = if (std.debug.runtime_safety) false else {}; tree_sitter_fallback.fallback_allocator = backing; - tree_sitter_fallback.get_called = if (std.debug.runtime_safety) false else {}; image_fallback.fallback_allocator = backing; - image_fallback.get_called = if (std.debug.runtime_safety) false else {}; pdf_fallback.fallback_allocator = backing; - pdf_fallback.get_called = if (std.debug.runtime_safety) false else {}; const raw: Allocators = .{ .pardes = pardes_fallback.get(), @@ -159,7 +161,7 @@ test "fixed allocators are separate, spill, and restart" { const core = try allocs.pardes.alloc(u8, 32); const frame = try allocs.frame.alloc(u8, 32); try std.testing.expect(pardes_fallback.fixed.ownsPtr(core.ptr)); - try std.testing.expect(frame_fallback.fixed_buffer_allocator.ownsPtr(frame.ptr)); + try std.testing.expect(frame_fallback.fixed.ownsPtr(frame.ptr)); try std.testing.expect(core.ptr != frame.ptr); const spill = try allocs.pardes.alloc(u8, limits.arena.pardes + 1); -- cgit v1.3