authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-04 15:45:09+01:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-06 10:09:05+01:00
log7b9865b046993dc282435d89c8da93992e84888b
tree03573aa04d9fc87f2ca49d97e0ad0a98c105c847
parent46c72ed970850af6ba0933b4bcd38b5764a3528e

std.heap.FixedBufferAllocator: complete thread-safe implementation

`FixedBufferAllocator.threadSafeAllocator()` already provided a thread-safe `alloc` implementation, but all other functions were nops. This commit implements the remaining `Allocator` functions and tightens up the memory orderings in `alloc` a bit, `monotonic` is good enough here.

1 files changed, 72 insertions(+), 9 deletions(-)

lib/std/heap/FixedBufferAllocator.zig+72-9
...@@ -36,9 +36,9 @@ pub fn threadSafeAllocator(self: *FixedBufferAllocator) Allocator {...@@ -36,9 +36,9 @@ pub fn threadSafeAllocator(self: *FixedBufferAllocator) Allocator {
36 .ptr = self,36 .ptr = self,
37 .vtable = &.{37 .vtable = &.{
38 .alloc = threadSafeAlloc,38 .alloc = threadSafeAlloc,
39 .resize = Allocator.noResize,39 .resize = threadSafeResize,
40 .remap = Allocator.noRemap,40 .remap = threadSafeRemap,
41 .free = Allocator.noFree,41 .free = threadSafeFree,
42 },42 },
43 };43 };
44}44}
...@@ -127,21 +127,84 @@ pub fn free(...@@ -127,21 +127,84 @@ pub fn free(
127 }127 }
128}128}
129129
130fn threadSafeAlloc(ctx: *anyopaque, n: usize, alignment: mem.Alignment, ra: usize) ?[*]u8 {130fn threadSafeAlloc(ctx: *anyopaque, n: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {
131 const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx));131 const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx));
132 _ = ra;132 _ = ret_addr;
133 const ptr_align = alignment.toByteUnits();133 const ptr_align = alignment.toByteUnits();
134 var end_index = @atomicLoad(usize, &self.end_index, .seq_cst);134 var cur_end_index = @atomicLoad(usize, &self.end_index, .monotonic);
135 while (true) {135 while (true) {
136 const adjust_off = mem.alignPointerOffset(self.buffer.ptr + end_index, ptr_align) orelse return null;136 const adjust_off = mem.alignPointerOffset(self.buffer.ptr + cur_end_index, ptr_align) orelse return null;
137 const adjusted_index = end_index + adjust_off;137 const adjusted_index = cur_end_index + adjust_off;
138 const new_end_index = adjusted_index + n;138 const new_end_index = adjusted_index + n;
139 if (new_end_index > self.buffer.len) return null;139 if (new_end_index > self.buffer.len) return null;
140 end_index = @cmpxchgWeak(usize, &self.end_index, end_index, new_end_index, .seq_cst, .seq_cst) orelse140 cur_end_index = @cmpxchgWeak(usize, &self.end_index, cur_end_index, new_end_index, .monotonic, .monotonic) orelse
141 return self.buffer[adjusted_index..new_end_index].ptr;141 return self.buffer[adjusted_index..new_end_index].ptr;
142 }142 }
143}143}
144144
145fn threadSafeResize(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) bool {
146 const fba: *FixedBufferAllocator = @ptrCast(@alignCast(ctx));
147 _ = alignment;
148 _ = ret_addr;
149
150 const cur_end_index = @atomicLoad(usize, &fba.end_index, .monotonic);
151 if (fba.buffer.ptr + cur_end_index != memory.ptr + memory.len) {
152 // It's not the most recent allocation, so it cannot be expanded,
153 // but it's fine if they want to make it smaller.
154 return new_len <= memory.len;
155 }
156
157 const new_end_index: usize = new_end_index: {
158 if (memory.len >= new_len) {
159 break :new_end_index cur_end_index - (memory.len - new_len);
160 }
161 if (fba.buffer.len - cur_end_index >= new_len - memory.len) {
162 break :new_end_index cur_end_index + (new_len - memory.len);
163 }
164 return false;
165 };
166 assert(fba.buffer.ptr + new_end_index == memory.ptr + new_len);
167
168 return null == @cmpxchgStrong(
169 usize,
170 &fba.end_index,
171 cur_end_index,
172 new_end_index,
173 .monotonic,
174 .monotonic,
175 );
176}
177
178fn threadSafeRemap(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
179 return if (threadSafeResize(ctx, memory, alignment, new_len, ret_addr)) memory.ptr else null;
180}
181
182fn threadSafeFree(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, ret_addr: usize) void {
183 const fba: *FixedBufferAllocator = @ptrCast(@alignCast(ctx));
184 _ = alignment;
185 _ = ret_addr;
186
187 assert(memory.len > 0);
188
189 const cur_end_index = @atomicLoad(usize, &fba.end_index, .monotonic);
190 if (fba.buffer.ptr + cur_end_index != memory.ptr + memory.len) {
191 // Not the most recent allocation; we cannot free it.
192 return;
193 }
194
195 const new_end_index = cur_end_index - memory.len;
196 assert(fba.buffer.ptr + new_end_index == memory.ptr);
197
198 _ = @cmpxchgStrong(
199 usize,
200 &fba.end_index,
201 cur_end_index,
202 new_end_index,
203 .monotonic,
204 .monotonic,
205 );
206}
207
145pub fn reset(self: *FixedBufferAllocator) void {208pub fn reset(self: *FixedBufferAllocator) void {
146 self.end_index = 0;209 self.end_index = 0;
147}210}