Skip to content

Commit 1ead3b4

Browse files
committed
new allocator interface
1 parent c6764fd commit 1ead3b4

File tree

7 files changed

+363
-386
lines changed

7 files changed

+363
-386
lines changed

lib/std/heap.zig

+215-298
Large diffs are not rendered by default.

lib/std/heap/arena_allocator.zig

+4-16
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ pub const ArenaAllocator = struct {
2020
pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator {
2121
return .{
2222
.allocator = Allocator{
23-
.reallocFn = realloc,
24-
.shrinkFn = shrink,
23+
.allocFn = alloc,
24+
.freeFn = free,
25+
.resizeFn = Allocator.noResize,
2526
},
2627
.child_allocator = child_allocator,
2728
.state = self,
@@ -81,18 +82,5 @@ pub const ArenaAllocator = struct {
8182
}
8283
}
8384

84-
fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
85-
if (new_size <= old_mem.len and new_align <= new_size) {
86-
// We can't do anything with the memory, so tell the client to keep it.
87-
return error.OutOfMemory;
88-
} else {
89-
const result = try alloc(allocator, new_size, new_align);
90-
@memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len));
91-
return result;
92-
}
93-
}
94-
95-
fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
96-
return old_mem[0..new_size];
97-
}
85+
fn free(allocator: *Allocator, buf: []u8) void { }
9886
};

lib/std/heap/logging_allocator.zig

+19-15
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,19 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
1515
pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self {
1616
return Self{
1717
.allocator = Allocator{
18-
.reallocFn = realloc,
19-
.shrinkFn = shrink,
18+
.allocFn = alloc,
19+
.freeFn = free,
20+
.resizeFn = resize,
2021
},
2122
.parent_allocator = parent_allocator,
2223
.out_stream = out_stream,
2324
};
2425
}
2526

26-
fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
27+
fn alloc(allocator: *std.mem.Allocator, len: usize, alignment: u29) error{OutOfMemory}![]u8 {
2728
const self = @fieldParentPtr(Self, "allocator", allocator);
28-
if (old_mem.len == 0) {
29-
self.out_stream.print("allocation of {} ", .{new_size}) catch {};
30-
} else {
31-
self.out_stream.print("resize from {} to {} ", .{ old_mem.len, new_size }) catch {};
32-
}
33-
const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align);
29+
self.out_stream.print("allocation of {} ", .{len}) catch {};
30+
const result = self.parent_allocator.allocMem(len, alignment);
3431
if (result) |buff| {
3532
self.out_stream.print("success!\n", .{}) catch {};
3633
} else |err| {
@@ -39,13 +36,20 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
3936
return result;
4037
}
4138

42-
fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
39+
fn free(allocator: *std.mem.Allocator, buf: []u8) void {
40+
const self = @fieldParentPtr(Self, "allocator", allocator);
41+
self.parent_allocator.freeMem(buf);
42+
self.out_stream.print("free of {} bytes success!\n", .{buf.len}) catch {};
43+
}
44+
45+
fn resize(allocator: *std.mem.Allocator, buf: []u8, new_len: usize) error{OutOfMemory}!void {
4346
const self = @fieldParentPtr(Self, "allocator", allocator);
44-
const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align);
45-
if (new_size == 0) {
46-
self.out_stream.print("free of {} bytes success!\n", .{old_mem.len}) catch {};
47-
} else {
48-
self.out_stream.print("shrink from {} bytes to {} bytes success!\n", .{ old_mem.len, new_size }) catch {};
47+
self.out_stream.print("resize from {} to {} ", .{ buf.len, new_len }) catch {};
48+
const result = self.parent_allocator.resizeMem(buf, new_len);
49+
if (result) |buff| {
50+
self.out_stream.print("success!\n", .{}) catch {};
51+
} else |err| {
52+
self.out_stream.print("failure!\n", .{}) catch {};
4953
}
5054
return result;
5155
}

lib/std/mem.zig

+84-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,35 @@ pub const page_size = switch (builtin.arch) {
1616
pub const Allocator = struct {
1717
pub const Error = error{OutOfMemory};
1818

19+
/// Allocate memory.
20+
allocFn: fn (self: *Allocator, len: usize, alignment: u29) Error![]u8,
21+
22+
/// Free memory returned by allocFn, this function must succeed.
23+
freeFn: fn (self: *Allocator, buf: []u8) void,
24+
25+
/// Resizes memory in-place returned by allocFn. This function is optional.
26+
resizeFn: fn (self: *Allocator, buf: []u8, new_len: usize) Error!void,
27+
28+
/// call allocFn with the given allocator
29+
pub fn allocMem(self: *Allocator, new_len: usize, alignment: u29) Error![]u8 {
30+
return self.allocFn(self, new_len, alignment);
31+
}
32+
33+
/// call freeFn with the given allocator
34+
pub fn freeMem(self: *Allocator, buf: []u8) void {
35+
self.freeFn(self, buf);
36+
}
37+
38+
/// call allocFn with the given allocator
39+
pub fn resizeMem(self: *Allocator, buf: []u8, new_len: usize) Error!void {
40+
return self.resizeFn(self, buf, new_len);
41+
}
42+
43+
/// Set to resizeFn if in-place resize is not supported.
44+
pub fn noResize(self: *Allocator, buf: []u8, new_len: usize) Error!void {
45+
return Error.OutOfMemory;
46+
}
47+
1948
/// Realloc is used to modify the size or alignment of an existing allocation,
2049
/// as well as to provide the allocator with an opportunity to move an allocation
2150
/// to a better location.
@@ -24,7 +53,7 @@ pub const Allocator = struct {
2453
/// When the size/alignment is less than or equal to the previous allocation,
2554
/// this function returns `error.OutOfMemory` when the allocator decides the client
2655
/// would be better off keeping the extra alignment/size. Clients will call
27-
/// `shrinkFn` when they require the allocator to track a new alignment/size,
56+
/// `shrinkMem` when they require the allocator to track a new alignment/size,
2857
/// and so this function should only return success when the allocator considers
2958
/// the reallocation desirable from the allocator's perspective.
3059
/// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
@@ -37,16 +66,16 @@ pub const Allocator = struct {
3766
/// as `old_mem` was when `reallocFn` is called. The bytes of
3867
/// `return_value[old_mem.len..]` have undefined values.
3968
/// The returned slice must have its pointer aligned at least to `new_alignment` bytes.
40-
reallocFn: fn (
69+
fn reallocMem(
4170
self: *Allocator,
4271
/// Guaranteed to be the same as what was returned from most recent call to
43-
/// `reallocFn` or `shrinkFn`.
72+
/// `reallocFn` or `shrinkMem`.
4473
/// If `old_mem.len == 0` then this is a new allocation and `new_byte_count`
4574
/// is guaranteed to be >= 1.
4675
old_mem: []u8,
4776
/// If `old_mem.len == 0` then this is `undefined`, otherwise:
4877
/// Guaranteed to be the same as what was returned from most recent call to
49-
/// `reallocFn` or `shrinkFn`.
78+
/// `reallocFn` or `shrinkMem`.
5079
/// Guaranteed to be >= 1.
5180
/// Guaranteed to be a power of 2.
5281
old_alignment: u29,
@@ -57,23 +86,63 @@ pub const Allocator = struct {
5786
/// Guaranteed to be a power of 2.
5887
/// Returned slice's pointer must have this alignment.
5988
new_alignment: u29,
60-
) Error![]u8,
89+
) Error![]u8 {
90+
if (old_mem.len == 0)
91+
return self.allocMem(new_byte_count, new_alignment);
92+
if (new_byte_count == 0) {
93+
self.freeMem(old_mem);
94+
return old_mem[0..0];
95+
}
96+
if (isAligned(@ptrToInt(old_mem.ptr), new_alignment)) {
97+
if (self.resizeMem(old_mem, new_byte_count)) {
98+
return old_mem.ptr[0..new_byte_count];
99+
} else |e| switch (e) {
100+
error.OutOfMemory => {},
101+
}
102+
}
103+
if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) {
104+
return error.OutOfMemory;
105+
}
106+
return self.moveMem(old_mem, new_byte_count, new_alignment);
107+
}
61108

62-
/// This function deallocates memory. It must succeed.
63-
shrinkFn: fn (
109+
/// Move the given memory to a new location in the given allocator to accomodate a new
110+
/// size and alignment.
111+
fn moveMem(self: *Allocator, old_mem: []u8, new_len: usize, new_alignment: u29) Error![]u8 {
112+
assert(old_mem.len > 0);
113+
assert(new_len > 0);
114+
const new_mem = try self.allocMem(new_len, new_alignment);
115+
@memcpy(new_mem.ptr, old_mem.ptr, std.math.min(new_len, old_mem.len));
116+
self.freeMem(old_mem);
117+
return new_mem;
118+
}
119+
120+
/// This function attempts to deallocates memory. It must succeed.
121+
fn shrinkMem(
64122
self: *Allocator,
65123
/// Guaranteed to be the same as what was returned from most recent call to
66-
/// `reallocFn` or `shrinkFn`.
124+
/// `reallocFn` or `shrinkMem`.
67125
old_mem: []u8,
68126
/// Guaranteed to be the same as what was returned from most recent call to
69-
/// `reallocFn` or `shrinkFn`.
127+
/// `reallocFn` or `shrinkMem`.
70128
old_alignment: u29,
71129
/// Guaranteed to be less than or equal to `old_mem.len`.
72130
new_byte_count: usize,
73131
/// If `new_byte_count == 0` then this is `undefined`, otherwise:
74132
/// Guaranteed to be less than or equal to `old_alignment`.
75133
new_alignment: u29,
76-
) []u8,
134+
) []u8 {
135+
assert(new_byte_count <= old_mem.len);
136+
assert(new_alignment <= old_alignment);
137+
if (new_byte_count == 0) {
138+
self.freeMem(old_mem);
139+
} else {
140+
self.resizeMem(old_mem, new_byte_count) catch |e| switch (e) {
141+
error.OutOfMemory => {}, // ignore the error, can't fail
142+
};
143+
}
144+
return old_mem[0..new_byte_count];
145+
}
77146

78147
/// Returns a pointer to undefined memory.
79148
/// Call `destroy` with the result to free the memory.
@@ -89,8 +158,7 @@ pub const Allocator = struct {
89158
const T = @TypeOf(ptr).Child;
90159
if (@sizeOf(T) == 0) return;
91160
const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
92-
const shrink_result = self.shrinkFn(self, non_const_ptr[0..@sizeOf(T)], @alignOf(T), 0, 1);
93-
assert(shrink_result.len == 0);
161+
self.freeMem(non_const_ptr[0..@sizeOf(T)]);
94162
}
95163

96164
/// Allocates an array of `n` items of type `T` and sets all the
@@ -161,7 +229,7 @@ pub const Allocator = struct {
161229
}
162230

163231
const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
164-
const byte_slice = try self.reallocFn(self, &[0]u8{}, undefined, byte_count, a);
232+
const byte_slice = try self.allocMem(byte_count, a);
165233
assert(byte_slice.len == byte_count);
166234
@memset(byte_slice.ptr, undefined, byte_slice.len);
167235
if (alignment == null) {
@@ -215,7 +283,7 @@ pub const Allocator = struct {
215283
const old_byte_slice = mem.sliceAsBytes(old_mem);
216284
const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory;
217285
// Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
218-
const byte_slice = try self.reallocFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment);
286+
const byte_slice = try self.reallocMem(old_byte_slice, Slice.alignment, byte_count, new_alignment);
219287
assert(byte_slice.len == byte_count);
220288
if (new_n > old_mem.len) {
221289
@memset(byte_slice.ptr + old_byte_slice.len, undefined, byte_slice.len - old_byte_slice.len);
@@ -262,7 +330,7 @@ pub const Allocator = struct {
262330

263331
const old_byte_slice = mem.sliceAsBytes(old_mem);
264332
@memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count);
265-
const byte_slice = self.shrinkFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment);
333+
const byte_slice = self.shrinkMem(old_byte_slice, Slice.alignment, byte_count, new_alignment);
266334
assert(byte_slice.len == byte_count);
267335
return mem.bytesAsSlice(T, @alignCast(new_alignment, byte_slice));
268336
}
@@ -276,7 +344,7 @@ pub const Allocator = struct {
276344
if (bytes_len == 0) return;
277345
const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
278346
@memset(non_const_ptr, undefined, bytes_len);
279-
const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
347+
const shrink_result = self.shrinkMem(non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
280348
assert(shrink_result.len == 0);
281349
}
282350

lib/std/os/windows/bits.zig

+1
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ pub const FILE_CURRENT = 1;
593593
pub const FILE_END = 2;
594594

595595
pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;
596+
pub const HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010;
596597
pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;
597598
pub const HEAP_NO_SERIALIZE = 0x00000001;
598599

lib/std/testing/failing_allocator.zig

+23-27
Original file line numberDiff line numberDiff line change
@@ -39,43 +39,39 @@ pub const FailingAllocator = struct {
3939
.allocations = 0,
4040
.deallocations = 0,
4141
.allocator = mem.Allocator{
42-
.reallocFn = realloc,
43-
.shrinkFn = shrink,
42+
.allocFn = alloc,
43+
.freeFn = free,
44+
.resizeFn = resize,
4445
},
4546
};
4647
}
4748

48-
fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
49-
const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
49+
fn alloc(allocator: *std.mem.Allocator, len: usize, alignment: u29) error{OutOfMemory}![]u8 {
50+
const self = @fieldParentPtr(@This(), "allocator", allocator);
5051
if (self.index == self.fail_index) {
5152
return error.OutOfMemory;
5253
}
53-
const result = try self.internal_allocator.reallocFn(
54-
self.internal_allocator,
55-
old_mem,
56-
old_align,
57-
new_size,
58-
new_align,
59-
);
60-
if (new_size < old_mem.len) {
61-
self.freed_bytes += old_mem.len - new_size;
62-
if (new_size == 0)
63-
self.deallocations += 1;
64-
} else if (new_size > old_mem.len) {
65-
self.allocated_bytes += new_size - old_mem.len;
66-
if (old_mem.len == 0)
67-
self.allocations += 1;
68-
}
54+
const result = try self.internal_allocator.allocMem(len, alignment);
55+
self.allocated_bytes += len;
56+
self.allocations += 1;
6957
self.index += 1;
7058
return result;
7159
}
7260

73-
fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
74-
const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
75-
const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
76-
self.freed_bytes += old_mem.len - r.len;
77-
if (new_size == 0)
78-
self.deallocations += 1;
79-
return r;
61+
fn free(allocator: *std.mem.Allocator, buf: []u8) void {
62+
const self = @fieldParentPtr(@This(), "allocator", allocator);
63+
self.internal_allocator.freeMem(buf);
64+
self.freed_bytes += buf.len;
65+
self.deallocations += 1;
66+
}
67+
68+
fn resize(allocator: *std.mem.Allocator, buf: []u8, new_len: usize) error{OutOfMemory}!void {
69+
const self = @fieldParentPtr(@This(), "allocator", allocator);
70+
const result = try self.internal_allocator.resizeMem(buf, new_len);
71+
if (new_len < buf.len) {
72+
self.freed_bytes += buf.len - new_len;
73+
} else {
74+
self.allocated_bytes += new_len - buf.len;
75+
}
8076
}
8177
};

lib/std/testing/leak_count_allocator.zig

+17-14
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,34 @@ pub const LeakCountAllocator = struct {
1414
return .{
1515
.count = 0,
1616
.allocator = .{
17-
.reallocFn = realloc,
18-
.shrinkFn = shrink,
17+
.allocFn = alloc,
18+
.freeFn = free,
19+
//.resizeFn = allocator.resizeFn, // this is a compile error?
20+
.resizeFn = resize,
1921
},
2022
.internal_allocator = allocator,
2123
};
2224
}
2325

24-
fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
26+
fn alloc(allocator: *std.mem.Allocator, len: usize, alignment: u29) error{OutOfMemory}![]u8 {
2527
const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
26-
var data = try self.internal_allocator.reallocFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
27-
if (old_mem.len == 0) {
28-
self.count += 1;
29-
}
28+
var data = try self.internal_allocator.allocMem(len, alignment);
29+
self.count += 1;
3030
return data;
3131
}
3232

33-
fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
33+
fn free(allocator: *std.mem.Allocator, buf: []u8) void {
3434
const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
35-
if (new_size == 0) {
36-
if (self.count == 0) {
37-
std.debug.panic("error - too many calls to free, most likely double free", .{});
38-
}
39-
self.count -= 1;
35+
if (self.count == 0) {
36+
std.debug.panic("error - too many calls to free, most likely double free", .{});
4037
}
41-
return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
38+
self.count -= 1;
39+
return self.internal_allocator.freeMem(buf);
40+
}
41+
42+
fn resize(allocator: *std.mem.Allocator, buf: []u8, new_len: usize) error{OutOfMemory}!void {
43+
const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
44+
return self.internal_allocator.resizeMem(buf, new_len);
4245
}
4346

4447
pub fn validate(self: LeakCountAllocator) !void {

0 commit comments

Comments
 (0)