also looking at this
fix(memory): honor pool alignment and round block strides safely
#910
1 file
+36
-5
| @@ -9,7 +9,7 @@ pub const Pool = struct { | ||
| 9 | 9 | block_size: usize, |
| 10 | 10 | block_count: usize, |
| 11 | 11 | free_list: ?*FreeNode, |
| 12 | buffer: []u8, | |
| 12 | buffer: []align(@alignOf(FreeNode)) u8, | |
| 13 | 13 | stats: MemStats, |
| 14 | 14 | parent_allocator: std.mem.Allocator, |
| 15 | 15 | |
| @@ -18,9 +18,12 @@ pub const Pool = struct { | ||
| 18 | 18 | }; |
| 19 | 19 | |
| 20 | 20 | 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); | |
| 22 | 25 | 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; | |
| 24 | 27 | |
| 25 | 28 | var pool = Pool{ |
| 26 | 29 | .block_size = actual_block_size, |
| @@ -60,10 +63,9 @@ pub const Pool = struct { | ||
| 60 | 63 | |
| 61 | 64 | fn alloc(ctx: *anyopaque, len: usize, ptr_align: std.mem.Alignment, ret_addr: usize) ?[*]u8 { |
| 62 | 65 | const self: *Pool = @ptrCast(@alignCast(ctx)); |
| 63 | _ = ptr_align; | |
| 64 | 66 | _ = ret_addr; |
| 65 | 67 | |
| 66 | if (len > self.block_size) { | |
| 68 | if (len > self.block_size or ptr_align.toByteUnits() > @alignOf(FreeNode)) { | |
| 67 | 69 | return null; // Block too large |
| 68 | 70 | } |
| 69 | 71 | |
| @@ -117,3 +119,32 @@ test "pool allocator" { | ||
| 117 | 119 | const block3 = try allocator.alloc(u8, 16); |
| 118 | 120 | try testing.expectEqual(@as(usize, 16), block3.len); |
| 119 | 121 | } |
| 122 | ||
| 123 | test "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 | ||
| 138 | test "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 | ||
| 144 | test "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 | } | |