authorgravatar for jmcaine@caines.usjmcaine <jmcaine@caines.us> 2026-04-10 09:06:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-18 14:51:25-07:00
log73ecc6333fcc57dce51a5ca93e0740fe6a3346cf
treeb8072f0f6bec777bf3772ec9bc671316b4000f00
parent0346aef2da921a424e0763ed345adc207b4e684b

std: implement heap.StackFirstAllocator

second attempt

2 files changed, 124 insertions(+), 0 deletions(-)

lib/std/heap.zig+1
...@@ -12,6 +12,7 @@ const Alignment = std.mem.Alignment;...@@ -12,6 +12,7 @@ const Alignment = std.mem.Alignment;
12pub const ArenaAllocator = @import("heap/ArenaAllocator.zig");12pub const ArenaAllocator = @import("heap/ArenaAllocator.zig");
13pub const SmpAllocator = @import("heap/SmpAllocator.zig");13pub const SmpAllocator = @import("heap/SmpAllocator.zig");
14pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");14pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
15pub const StackFirstAllocator = @import("heap/StackFirstAllocator.zig");
15pub const PageAllocator = @import("heap/PageAllocator.zig");16pub const PageAllocator = @import("heap/PageAllocator.zig");
16pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");17pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");
17pub const BrkAllocator = @import("heap/BrkAllocator.zig");18pub const BrkAllocator = @import("heap/BrkAllocator.zig");
lib/std/heap/StackFirstAllocator.zig created+123
...@@ -0,0 +1,123 @@
1//! A "composite" allocator that attempts to allocate first on the stack, using
2//! the provided FixedBufferAllocator; upon failure, the provided secondary
3//! allocator is used. reset() is NOT provided, even though available for the
4//! (primary) FixedBufferAllocator, because it may not be available for the
5//! provided secondary allocator (so callers must call reset() on underlying
6//! allocators, themselves, when desirable).
7
8const std = @import("../std.zig");
9const mem = std.mem;
10const Allocator = mem.Allocator;
11const FixedBufferAllocator = std.heap.FixedBufferAllocator;
12const assert = std.debug.assert;
13
14const StackFirstAllocator = @This();
15
16primary: FixedBufferAllocator,
17secondary: Allocator,
18
19pub fn init(buffer: []u8, secondary_allocator: Allocator) StackFirstAllocator {
20 return .{
21 .primary = .init(buffer),
22 .secondary = secondary_allocator,
23 };
24}
25
26pub fn allocator(self: *StackFirstAllocator) Allocator {
27 return .{
28 .ptr = self,
29 .vtable = &.{
30 .alloc = alloc,
31 .resize = resize,
32 .remap = remap,
33 .free = free,
34 },
35 };
36}
37
38pub fn alloc(ctx: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {
39 const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
40 return FixedBufferAllocator.alloc(&self.primary, len, alignment, ret_addr) orelse
41 self.secondary.rawAlloc(len, alignment, ret_addr);
42}
43
44pub fn resize(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) bool {
45 const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
46 return if (self.primary.ownsPtr(memory.ptr))
47 FixedBufferAllocator.resize(&self.primary, memory, alignment, new_len, ret_addr)
48 else
49 self.secondary.rawResize(memory, alignment, new_len, ret_addr);
50}
51
52pub fn remap(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
53 const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
54 return if (self.primary.ownsPtr(memory.ptr))
55 FixedBufferAllocator.remap(&self.primary, memory, alignment, new_len, ret_addr)
56 else
57 self.secondary.rawRemap(memory, alignment, new_len, ret_addr);
58}
59
60pub fn free(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, ret_addr: usize) void {
61 const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
62 return if (self.primary.ownsPtr(memory.ptr))
63 FixedBufferAllocator.free(&self.primary, memory, alignment, ret_addr)
64 else
65 self.secondary.rawFree(memory, alignment, ret_addr);
66}
67
68test StackFirstAllocator {
69 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
70 defer arena.deinit();
71 var buffer: [10]u8 = undefined;
72 var sfa = StackFirstAllocator.init(&buffer, arena.allocator());
73
74 const expect = std.testing.expect;
75 const expectEqualStrings = std.testing.expectEqualStrings;
76
77 const al = sfa.allocator();
78 const txt = "0123456789";
79
80 const dest = try al.alloc(u8, txt.len);
81 @memcpy(dest, txt);
82 try expectEqualStrings(txt, dest);
83 try expect(sfa.primary.ownsPtr(dest.ptr));
84
85 const txt2 = "abcde";
86 const dest2 = try al.alloc(u8, txt2.len);
87 @memcpy(dest2, txt2);
88 try expectEqualStrings(txt2, dest2);
89 try expect(!sfa.primary.ownsPtr(dest2.ptr));
90
91 sfa.primary.reset();
92
93 const txt3 = "0123";
94 const dest3 = try al.alloc(u8, txt3.len);
95 @memcpy(dest3, txt3);
96 try expectEqualStrings(txt3, dest3);
97 try expect(sfa.primary.ownsPtr(dest3.ptr));
98
99 sfa.primary.reset();
100 //arena.reset(); // unnecessary, but allowed (note `defer arena.deinit()` above)
101
102 // stock tests:
103 {
104 var buf: [16]u8 = undefined;
105 var a = StackFirstAllocator.init(&buf, std.testing.allocator);
106 try std.heap.testAllocator(a.allocator());
107 }
108 {
109 var buf: [16]u8 = undefined;
110 var a = StackFirstAllocator.init(&buf, std.testing.allocator);
111 try std.heap.testAllocatorAligned(a.allocator());
112 }
113 {
114 var buf: [16]u8 = undefined;
115 var a = StackFirstAllocator.init(&buf, std.testing.allocator);
116 try std.heap.testAllocatorLargeAlignment(a.allocator());
117 }
118 {
119 var buf: [16]u8 = undefined;
120 var a = StackFirstAllocator.init(&buf, std.testing.allocator);
121 try std.heap.testAllocatorAlignedShrink(a.allocator());
122 }
123}