ReviewOS

also looking at this

home-lang/home

fix(memory): honor pool alignment and round block strides safely

#910
Merged chris wants to merge fix/memory-pool-alignment into main
1 file +36 -5

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/memory/src/pool.zigmodified+36-5
Changes to packages/memory/src/pool.zig
@@ -9,7 +9,7 @@ pub const Pool = struct {
99 block_size: usize,
1010 block_count: usize,
1111 free_list: ?*FreeNode,
12 buffer: []u8,
12 buffer: []align(@alignOf(FreeNode)) u8,
1313 stats: MemStats,
1414 parent_allocator: std.mem.Allocator,
1515
@@ -18,9 +18,12 @@ pub const Pool = struct {
1818 };
1919
2020 pub fn init(parent: std.mem.Allocator, block_size: usize, block_count: usize) AllocatorError!Pool {
21 const actual_block_size = @max(block_size, @sizeOf(FreeNode));
21 const alignment = @alignOf(FreeNode);
22 const min_block_size = @max(block_size, @sizeOf(FreeNode));
23 const padded_size = std.math.add(usize, min_block_size, alignment - 1) catch return AllocatorError.OutOfMemory;
24 const actual_block_size = padded_size & ~@as(usize, alignment - 1);
2225 const buffer_size = std.math.mul(usize, actual_block_size, block_count) catch return AllocatorError.OutOfMemory;
23 const buffer = parent.alloc(u8, buffer_size) catch return AllocatorError.OutOfMemory;
26 const buffer = parent.alignedAlloc(u8, .fromByteUnits(alignment), buffer_size) catch return AllocatorError.OutOfMemory;
2427
2528 var pool = Pool{
2629 .block_size = actual_block_size,
@@ -60,10 +63,9 @@ pub const Pool = struct {
6063
6164 fn alloc(ctx: *anyopaque, len: usize, ptr_align: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
6265 const self: *Pool = @ptrCast(@alignCast(ctx));
63 _ = ptr_align;
6466 _ = ret_addr;
6567
66 if (len > self.block_size) {
68 if (len > self.block_size or ptr_align.toByteUnits() > @alignOf(FreeNode)) {
6769 return null; // Block too large
6870 }
6971
@@ -117,3 +119,32 @@ test "pool allocator" {
117119 const block3 = try allocator.alloc(u8, 16);
118120 try testing.expectEqual(@as(usize, 16), block3.len);
119121}
122
123test "pool rounds odd blocks to free-list node alignment" {
124 var pool = try Pool.init(std.testing.allocator, 9, 3);
125 defer pool.deinit();
126 const a = pool.allocator();
127 const one = try a.alloc(u8, 9);
128 const two = try a.alloc(u8, 9);
129 const three = try a.alloc(u8, 9);
130 try std.testing.expectEqual(@as(usize, 0), @intFromPtr(one.ptr) % @alignOf(usize));
131 try std.testing.expectEqual(@as(usize, 0), @intFromPtr(two.ptr) % @alignOf(usize));
132 try std.testing.expectEqual(@as(usize, 0), @intFromPtr(three.ptr) % @alignOf(usize));
133 a.free(two);
134 const reused = try a.alloc(u8, 9);
135 try std.testing.expectEqual(two.ptr, reused.ptr);
136}
137
138test "pool refuses alignment beyond its backing allocation" {
139 var pool = try Pool.init(std.testing.allocator, 64, 2);
140 defer pool.deinit();
141 try std.testing.expectError(error.OutOfMemory, pool.allocator().alignedAlloc(u8, .@"64", 1));
142}
143
144test "pool checks stride rounding and total size overflow" {
145 try std.testing.expectError(error.OutOfMemory, Pool.init(std.testing.allocator, std.math.maxInt(usize), 1));
146 try std.testing.expectError(error.OutOfMemory, Pool.init(std.testing.allocator, 16, std.math.maxInt(usize)));
147 var empty = try Pool.init(std.testing.allocator, 9, 0);
148 defer empty.deinit();
149 try std.testing.expectError(error.OutOfMemory, empty.allocator().alloc(u8, 1));
150}