Skip to content

Commit 5ca5710

Browse files
committed
Make BumpAllocator work at comptime.
1 parent c2166ee commit 5ca5710

File tree

2 files changed

+58
-36
lines changed

2 files changed

+58
-36
lines changed

lib/std/heap/BumpAllocator.zig

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ const std = @import("../std.zig");
22
const Alignment = std.mem.Alignment;
33
const Allocator = std.mem.Allocator;
44

5-
base: usize,
6-
limit: usize,
5+
unused: usize,
6+
buffer_end: [*]u8,
77

88
pub fn init(buffer: []u8) @This() {
9-
const base: usize = @intFromPtr(buffer.ptr);
10-
const limit: usize = base + buffer.len;
11-
return .{ .base = base, .limit = limit };
9+
return .{
10+
.unused = buffer.len,
11+
.buffer_end = buffer.ptr + buffer.len,
12+
};
1213
}
1314

1415
pub fn allocator(self: *@This()) Allocator {
@@ -25,12 +26,12 @@ pub fn allocator(self: *@This()) Allocator {
2526

2627
/// Save the current state of the allocator
2728
pub fn savestate(self: *@This()) usize {
28-
return self.base;
29+
return self.unused;
2930
}
3031

3132
/// Restore a previously saved allocator state
3233
pub fn restore(self: *@This(), state: usize) void {
33-
self.base = state;
34+
self.unused = state;
3435
}
3536

3637
pub fn alloc(
@@ -39,15 +40,19 @@ pub fn alloc(
3940
alignment: Alignment,
4041
_: usize,
4142
) ?[*]u8 {
42-
const self: *@This() = @alignCast(@ptrCast(ctx));
43+
const self: *@This() = @ptrCast(@alignCast(ctx));
44+
45+
const buffer_base = self.buffer_end - self.unused;
46+
const align_bytes = alignment.toByteUnits();
47+
const ptr_adjust = std.mem.alignPointerOffset(buffer_base, align_bytes);
48+
const align_overhead = ptr_adjust orelse return null;
4349

4450
// Only allocate if we have enough space
45-
const aligned = alignment.forward(self.base);
46-
const end_addr = @addWithOverflow(aligned, length);
47-
if ((end_addr[1] == 1) | (end_addr[0] > self.limit)) return null;
51+
const allocated_length = length + align_overhead;
52+
if (allocated_length > self.unused) return null;
4853

49-
self.base = end_addr[0];
50-
return @ptrFromInt(aligned);
54+
self.unused = self.unused - allocated_length;
55+
return buffer_base + align_overhead;
5156
}
5257

5358
pub fn resize(
@@ -57,21 +62,19 @@ pub fn resize(
5762
new_length: usize,
5863
_: usize,
5964
) bool {
60-
const self: *@This() = @alignCast(@ptrCast(ctx));
61-
62-
const alloc_base = @intFromPtr(memory.ptr);
63-
const next_alloc = alloc_base + memory.len;
65+
const self: *@This() = @ptrCast(@alignCast(ctx));
6466

6567
// Prior allocations can be shrunk, but not grown
68+
const next_alloc = memory.ptr + memory.len;
69+
const buffer_base = self.buffer_end - self.unused;
6670
const shrinking = memory.len >= new_length;
67-
if (next_alloc != self.base) return shrinking;
71+
if (next_alloc != buffer_base) return shrinking;
6872

6973
// Grow allocations only if we have enough space
70-
const end_addr = @addWithOverflow(alloc_base, new_length);
71-
const overflow = (end_addr[1] == 1) | (end_addr[0] > self.limit);
74+
const overflow = new_length > self.unused + memory.len;
7275
if (!shrinking and overflow) return false;
7376

74-
self.base = end_addr[0];
77+
self.unused = (self.unused + memory.len) - new_length;
7578
return true;
7679
}
7780

@@ -95,14 +98,14 @@ pub fn free(
9598
_: Alignment,
9699
_: usize,
97100
) void {
98-
const self: *@This() = @alignCast(@ptrCast(ctx));
101+
const self: *@This() = @ptrCast(@alignCast(ctx));
99102

100103
// Only free the immediate last allocation
101-
const alloc_base = @intFromPtr(memory.ptr);
102-
const next_alloc = alloc_base + memory.len;
103-
if (next_alloc != self.base) return;
104+
const next_alloc = memory.ptr + memory.len;
105+
const buffer_base = self.buffer_end - self.unused;
106+
if (next_alloc != buffer_base) return;
104107

105-
self.base = self.base - memory.len;
108+
self.unused = self.unused + memory.len;
106109
}
107110

108111
test "BumpAllocator" {
@@ -159,6 +162,20 @@ test "avoid integer overflow for obscene allocations" {
159162
try std.testing.expectError(error.OutOfMemory, problem);
160163
}
161164

165+
test "works at comptime" {
166+
comptime {
167+
var buffer: [256]u8 = undefined;
168+
var bump_allocator: @This() = .init(&buffer);
169+
const gpa = bump_allocator.allocator();
170+
171+
var list: std.ArrayList(u8) = .empty;
172+
defer list.deinit(gpa);
173+
for ("Hello, World!\n") |byte| {
174+
try list.append(gpa, byte);
175+
}
176+
}
177+
}
178+
162179
/// Deprecated; to be removed after 0.16.0 is tagged.
163180
/// Provides a lock free thread safe `Allocator` interface to the underlying `FixedBufferAllocator`
164181
/// Using this at the same time as the interface returned by `allocator` is not thread safe.
@@ -181,21 +198,26 @@ fn threadSafeAlloc(
181198
alignment: Alignment,
182199
_: usize,
183200
) ?[*]u8 {
184-
const self: *@This() = @alignCast(@ptrCast(ctx));
201+
const self: *@This() = @ptrCast(@alignCast(ctx));
202+
const align_bytes = alignment.toByteUnits();
203+
204+
var old_unused = @atomicLoad(usize, &self.unused, .seq_cst);
185205

186-
var old_base = @atomicLoad(usize, &self.base, .seq_cst);
187206
while (true) {
188-
// Only allocate if we have enough space
189-
const aligned = alignment.forward(old_base);
190-
const end_addr = @addWithOverflow(aligned, length);
191-
if ((end_addr[1] == 1) | (end_addr[0] > self.limit)) return null;
207+
const buffer_base = self.buffer_end - old_unused;
208+
const align_overhead = std.mem.alignPointerOffset(buffer_base, align_bytes) orelse return null;
209+
210+
const allocated_length = length + align_overhead;
211+
if (allocated_length > old_unused) return null;
212+
213+
const new_unused = old_unused - allocated_length;
192214

193-
if (@cmpxchgWeak(usize, &self.base, old_base, @intCast(end_addr[0]), .seq_cst, .seq_cst)) |prev| {
194-
old_base = prev;
215+
if (@cmpxchgWeak(usize, &self.unused, old_unused, new_unused, .seq_cst, .seq_cst)) |prev| {
216+
old_unused = prev;
195217
continue;
196218
}
197219

198-
return @ptrFromInt(aligned);
220+
return buffer_base + align_overhead;
199221
}
200222
}
201223

lib/std/mem.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ test orderZ {
684684
/// Returns true if a slice owns the memory for an element pointer
685685
pub fn sliceOwnsPtr(comptime T: type, slice: []const T, elem: *T) bool {
686686
const above_base = @intFromPtr(elem) >= @intFromPtr(slice.ptr);
687-
const below_limit = @intFromPtr(elem) < @intFromPtr(&slice[slice.len]);
687+
const below_limit = @intFromPtr(elem) < @intFromPtr(slice.ptr) + slice.len * @sizeOf(T);
688688
return above_base and below_limit;
689689
}
690690

0 commit comments

Comments
 (0)