authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-16 19:28:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-18 14:51:49-07:00
log6d40d374d8d2c0eef0c0c6c557461f35b385d085
tree73edc83caa615b69b133d4a8fb305bc24953dc08
parent8c96487bb95e45a86f35e7cfa6012c265c0fdcca

Merges together the two buffer first allocator implementations


2 files changed, 167 insertions(+), 104 deletions(-)

lib/std/heap.zig+2-104
......@@ -12,7 +12,7 @@ const Alignment = std.mem.Alignment;
1212pub const ArenaAllocator = @import("heap/ArenaAllocator.zig");
1313pub const SmpAllocator = @import("heap/SmpAllocator.zig");
1414pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
15pub const StackFirstAllocator = @import("heap/StackFirstAllocator.zig");
15pub const BufferFirstAllocator = @import("heap/BufferFirstAllocator.zig");
1616pub const PageAllocator = @import("heap/PageAllocator.zig");
1717pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");
1818pub const BrkAllocator = @import("heap/BrkAllocator.zig");
......@@ -368,89 +368,6 @@ pub const brk_allocator: Allocator = .{
368368 .vtable = &BrkAllocator.vtable,
369369};
370370
371/// An allocator that attempts to allocate from the given buffer, falling back to
372/// `fallback_allocator` if this fails.
373pub const StackFallbackAllocator = struct {
374 const Self = @This();
375
376 fallback_allocator: Allocator,
377 fixed_buffer_allocator: FixedBufferAllocator,
378
379 pub fn init(buf: []u8, fallback_allocator: Allocator) Self {
380 return .{
381 .fallback_allocator = fallback_allocator,
382 .fixed_buffer_allocator = .init(buf),
383 };
384 }
385
386 pub fn allocator(self: *Self) Allocator {
387 return .{
388 .ptr = self,
389 .vtable = &.{
390 .alloc = alloc,
391 .resize = resize,
392 .remap = remap,
393 .free = free,
394 },
395 };
396 }
397
398 fn alloc(
399 ctx: *anyopaque,
400 len: usize,
401 alignment: Alignment,
402 ra: usize,
403 ) ?[*]u8 {
404 const self: *Self = @ptrCast(@alignCast(ctx));
405 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
406 return self.fallback_allocator.rawAlloc(len, alignment, ra);
407 }
408
409 fn resize(
410 ctx: *anyopaque,
411 buf: []u8,
412 alignment: Alignment,
413 new_len: usize,
414 ra: usize,
415 ) bool {
416 const self: *Self = @ptrCast(@alignCast(ctx));
417 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
418 return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
419 } else {
420 return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
421 }
422 }
423
424 fn remap(
425 context: *anyopaque,
426 memory: []u8,
427 alignment: Alignment,
428 new_len: usize,
429 return_address: usize,
430 ) ?[*]u8 {
431 const self: *Self = @ptrCast(@alignCast(context));
432 if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
433 return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
434 } else {
435 return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
436 }
437 }
438
439 fn free(
440 ctx: *anyopaque,
441 buf: []u8,
442 alignment: Alignment,
443 ra: usize,
444 ) void {
445 const self: *Self = @ptrCast(@alignCast(ctx));
446 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
447 return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
448 } else {
449 return self.fallback_allocator.rawFree(buf, alignment, ra);
450 }
451 }
452};
453
454371test c_allocator {
455372 if (builtin.link_libc) {
456373 try testAllocator(c_allocator);
......@@ -501,26 +418,6 @@ test ArenaAllocator {
501418 try testAllocatorAlignedShrink(allocator);
502419}
503420
504test StackFallbackAllocator {
505 var buf: [4096]u8 = undefined;
506 {
507 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
508 try testAllocator(stack_allocator.allocator());
509 }
510 {
511 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
512 try testAllocatorAligned(stack_allocator.allocator());
513 }
514 {
515 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
516 try testAllocatorLargeAlignment(stack_allocator.allocator());
517 }
518 {
519 var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator);
520 try testAllocatorAlignedShrink(stack_allocator.allocator());
521 }
522}
523
524421/// This one should not try alignments that exceed what C malloc can handle.
525422pub fn testAllocator(base_allocator: mem.Allocator) !void {
526423 var validationAllocator = mem.validationWrap(base_allocator);
......@@ -989,6 +886,7 @@ test {
989886 _ = ArenaAllocator;
990887 _ = DebugAllocator(.{});
991888 _ = FixedBufferAllocator;
889 _ = BufferFirstAllocator;
992890 if (builtin.single_threaded) {
993891 if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) {
994892 _ = brk_allocator;
lib/std/heap/BufferFirstAllocator.zig created+165
......@@ -0,0 +1,165 @@
1//! An allocator that attempts to allocate from the given buffer, falling back to
2//! `fallback_allocator` if this fails.
3
4const std = @import("../std.zig");
5const heap = std.heap;
6const testing = std.testing;
7
8const Alignment = std.mem.Alignment;
9const Allocator = std.mem.Allocator;
10const FixedBufferAllocator = std.heap.FixedBufferAllocator;
11
12const BufferFirstAllocator = @This();
13
14fallback_allocator: Allocator,
15fixed_buffer_allocator: FixedBufferAllocator,
16
17pub fn init(buffer: []u8, fallback_allocator: Allocator) BufferFirstAllocator {
18 return .{
19 .fallback_allocator = fallback_allocator,
20 .fixed_buffer_allocator = .init(buffer),
21 };
22}
23
24pub fn allocator(self: *BufferFirstAllocator) Allocator {
25 return .{
26 .ptr = self,
27 .vtable = &.{
28 .alloc = alloc,
29 .resize = resize,
30 .remap = remap,
31 .free = free,
32 },
33 };
34}
35
36fn alloc(
37 ctx: *anyopaque,
38 len: usize,
39 alignment: Alignment,
40 ra: usize,
41) ?[*]u8 {
42 const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx));
43 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
44 return self.fallback_allocator.rawAlloc(len, alignment, ra);
45}
46
47fn resize(
48 ctx: *anyopaque,
49 buf: []u8,
50 alignment: Alignment,
51 new_len: usize,
52 ra: usize,
53) bool {
54 const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx));
55 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
56 return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
57 } else {
58 return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
59 }
60}
61
62fn remap(
63 context: *anyopaque,
64 memory: []u8,
65 alignment: Alignment,
66 new_len: usize,
67 return_address: usize,
68) ?[*]u8 {
69 const self: *BufferFirstAllocator = @ptrCast(@alignCast(context));
70 if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
71 return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
72 } else {
73 return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
74 }
75}
76
77fn free(
78 ctx: *anyopaque,
79 buf: []u8,
80 alignment: Alignment,
81 ra: usize,
82) void {
83 const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx));
84 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
85 return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
86 } else {
87 return self.fallback_allocator.rawFree(buf, alignment, ra);
88 }
89}
90
91test "BufferFirstAllocator" {
92 // Buffer first specific tests
93 {
94 var buffer: [10]u8 = undefined;
95 var bfa_state: BufferFirstAllocator = .init(&buffer, std.testing.allocator);
96 const bfa = bfa_state.allocator();
97
98 // We're under the limit, so we should be allocated in the buffer
99 const txt0 = "hellowrld";
100 const buf0 = try bfa.create(@TypeOf(txt0.*));
101 buf0.* = txt0.*;
102 try testing.expect(bfa_state.fixed_buffer_allocator.ownsPtr(buf0.ptr));
103
104 // We're now over the limit, so we should be allocated from the fallback
105 const txt1 = "test!";
106 const buf1 = try bfa.create(@TypeOf(txt1.*));
107 buf1.* = txt1.*;
108 try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf1.ptr));
109
110 // Free the allocation that took up space in the buffer
111 try testing.expectEqualStrings(txt0, buf0);
112 bfa.destroy(buf0);
113
114 // The next allocation would go in the buffer, but it's too big so it doesn't
115 const txt2 = "qwertyqwerty";
116 const buf2 = try bfa.create(@TypeOf(txt2.*));
117 buf2.* = txt2.*;
118 try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf2.ptr));
119
120 // The next allocation is smaller and fits in the buffer
121 const txt3 = "dvorak";
122 const buf3 = try bfa.create(@TypeOf(txt3.*));
123 buf3.* = txt3.*;
124 try testing.expect(bfa_state.fixed_buffer_allocator.ownsPtr(buf3.ptr));
125
126 // The remainder in the buffer is too small for the following allocation so it falls back
127 const txt4 = "moretext";
128 const buf4 = try bfa.create(@TypeOf(txt4.*));
129 buf4.* = txt4.*;
130 try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf4.ptr));
131
132 // Check equality on the remaining buffers and free them
133 try testing.expectEqualStrings(txt1, buf1);
134 bfa.destroy(buf1);
135 try testing.expectEqualStrings(txt2, buf2);
136 bfa.destroy(buf2);
137 try testing.expectEqualStrings(txt3, buf3);
138 bfa.destroy(buf3);
139 try testing.expectEqualStrings(txt4, buf4);
140 bfa.destroy(buf4);
141
142 try testing.expectEqual(0, bfa_state.fixed_buffer_allocator.end_index);
143 }
144
145 // Standard allocator tests
146 {
147 var buf: [4096]u8 = undefined;
148 {
149 var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
150 try heap.testAllocator(bfa.allocator());
151 }
152 {
153 var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
154 try heap.testAllocatorAligned(bfa.allocator());
155 }
156 {
157 var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
158 try heap.testAllocatorLargeAlignment(bfa.allocator());
159 }
160 {
161 var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
162 try heap.testAllocatorAlignedShrink(bfa.allocator());
163 }
164 }
165}