diff --git a/src/terminal/new/PageList.zig b/src/terminal/new/PageList.zig index a08d20485..f2c6d3f7d 100644 --- a/src/terminal/new/PageList.zig +++ b/src/terminal/new/PageList.zig @@ -92,7 +92,7 @@ pub fn init( // no errdefer because the pool deinit will clean up the page page.* = .{ - .data = try Page.init(alloc, .{ + .data = try Page.init(.{ .cols = cols, .rows = @max(rows, page_min_rows), .styles = page_default_styles, @@ -131,7 +131,7 @@ pub fn init( pub fn deinit(self: *PageList) void { // Deallocate all the pages. We don't need to deallocate the list or // nodes because they all reside in the pool. - while (self.pages.popFirst()) |node| node.data.deinit(self.alloc); + while (self.pages.popFirst()) |node| node.data.deinit(); self.pool.deinit(); } @@ -237,10 +237,10 @@ pub fn grow(self: *PageList) !*List.Node { /// Create a new page node. This does not add it to the list. fn createPage(self: *PageList) !*List.Node { var page = try self.pool.create(); - errdefer page.data.deinit(self.alloc); + errdefer page.data.deinit(); page.* = .{ - .data = try Page.init(self.alloc, .{ + .data = try Page.init(.{ .cols = self.cols, .rows = @max(self.rows, page_min_rows), .styles = page_default_styles, diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig index 7b42d7240..af3d4d6dc 100644 --- a/src/terminal/new/page.zig +++ b/src/terminal/new/page.zig @@ -112,11 +112,27 @@ pub const Page = struct { /// Initialize a new page, allocating the required backing memory. /// The size of the initialized page defaults to the full capacity. - pub fn init(alloc: Allocator, cap: Capacity) !Page { + /// + /// The backing memory is always allocated using mmap directly. + /// You cannot use custom allocators with this structure because + /// it is critical to performance that we use mmap. + pub fn init(cap: Capacity) !Page { const l = layout(cap); - const backing = try alloc.alignedAlloc(u8, std.mem.page_size, l.total_size); - errdefer alloc.free(backing); - @memset(backing, 0); + + // We use mmap directly to avoid Zig allocator overhead + // (small but meaningful for this path) and because a private + // anonymous mmap is guaranteed on Linux and macOS to be zeroed, + // which is a critical property for us. + assert(l.total_size % std.mem.page_size == 0); + const backing = try std.os.mmap( + null, + l.total_size, + std.os.PROT.READ | std.os.PROT.WRITE, + .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, + -1, + 0, + ); + errdefer std.os.munmap(backing); const buf = OffsetBuf.init(backing); const rows = buf.member(Row, l.rows_start); @@ -154,8 +170,8 @@ pub const Page = struct { }; } - pub fn deinit(self: *Page, alloc: Allocator) void { - alloc.free(self.memory); + pub fn deinit(self: *Page) void { + std.os.munmap(self.memory); self.* = undefined; } @@ -228,7 +244,7 @@ pub const Page = struct { const grapheme_map_start = alignForward(usize, grapheme_alloc_end, GraphemeMap.base_align); const grapheme_map_end = grapheme_map_start + grapheme_map_layout.total_size; - const total_size = grapheme_map_end; + const total_size = alignForward(usize, grapheme_map_end, std.mem.page_size); return .{ .total_size = total_size, @@ -317,25 +333,22 @@ pub const Cell = packed struct(u64) { // } test "Page init" { - const testing = std.testing; - const alloc = testing.allocator; - var page = try Page.init(alloc, .{ + var page = try Page.init(.{ .cols = 120, .rows = 80, .styles = 32, }); - defer page.deinit(alloc); + defer page.deinit(); } test "Page read and write cells" { const testing = std.testing; - const alloc = testing.allocator; - var page = try Page.init(alloc, .{ + var page = try Page.init(.{ .cols = 10, .rows = 10, .styles = 8, }); - defer page.deinit(alloc); + defer page.deinit(); for (0..page.capacity.rows) |y| { const rac = page.getRowAndCell(1, y);