authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-08 20:20:15-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-08 20:20:15-04:00
log069a6f2432be5fd5e9a3eabd13faa43143001bbd
treef68e0ce1a1a7e62665be0db0fc701f79c25189aa
parentab483281d31c852e77fe0afc6a623fedd1574546
parentf98cffc615bb7ea73eee3bd24ae8a957fe8ebb82
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5998 from ziglang/general-purpose-allocator

std: introduce GeneralPurposeAllocator

23 files changed, 1791 insertions(+), 691 deletions(-)

doc/langref.html.in+11-3
...@@ -9357,9 +9357,17 @@ pub fn main() !void {...@@ -9357,9 +9357,17 @@ pub fn main() !void {
9357 is handled correctly? In this case, use {#syntax#}std.testing.FailingAllocator{#endsyntax#}.9357 is handled correctly? In this case, use {#syntax#}std.testing.FailingAllocator{#endsyntax#}.
9358 </li>9358 </li>
9359 <li>9359 <li>
9360 Finally, if none of the above apply, you need a general purpose allocator. Zig does not9360 Are you writing a test? In this case, use {#syntax#}std.testing.allocator{#endsyntax#}.
9361 yet have a general purpose allocator in the standard library,9361 </li>
9362 <a href="https://github.com/andrewrk/zig-general-purpose-allocator/">but one is being actively developed</a>.9362 <li>
9363 Finally, if none of the above apply, you need a general purpose allocator.
9364 Zig's general purpose allocator is available as a function that takes a {#link|comptime#}
9365 {#link|struct#} of configuration options and returns a type.
9366 Generally, you will set up one {#syntax#}std.heap.GeneralPurposeAllocator{#endsyntax#} in
9367 your main function, and then pass it or sub-allocators around to various parts of your
9368 application.
9369 </li>
9370 <li>
9363 You can also consider {#link|Implementing an Allocator#}.9371 You can also consider {#link|Implementing an Allocator#}.
9364 </li>9372 </li>
9365 </ol>9373 </ol>
lib/std/array_list.zig+1
...@@ -263,6 +263,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -263,6 +263,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
263 if (better_capacity >= new_capacity) break;263 if (better_capacity >= new_capacity) break;
264 }264 }
265265
266 // TODO This can be optimized to avoid needlessly copying undefined memory.
266 const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), better_capacity);267 const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), better_capacity);
267 self.items.ptr = new_memory.ptr;268 self.items.ptr = new_memory.ptr;
268 self.capacity = new_memory.len;269 self.capacity = new_memory.len;
lib/std/atomic/queue.zig+1-1
...@@ -22,7 +22,7 @@ pub fn Queue(comptime T: type) type {...@@ -22,7 +22,7 @@ pub fn Queue(comptime T: type) type {
22 return Self{22 return Self{
23 .head = null,23 .head = null,
24 .tail = null,24 .tail = null,
25 .mutex = std.Mutex.init(),25 .mutex = std.Mutex{},
26 };26 };
27 }27 }
2828
lib/std/debug.zig+2-5
...@@ -19,9 +19,6 @@ const windows = std.os.windows;...@@ -19,9 +19,6 @@ const windows = std.os.windows;
1919
20pub const leb = @import("debug/leb128.zig");20pub const leb = @import("debug/leb128.zig");
2121
22pub const global_allocator = @compileError("Please switch to std.testing.allocator.");
23pub const failing_allocator = @compileError("Please switch to std.testing.failing_allocator.");
24
25pub const runtime_safety = switch (builtin.mode) {22pub const runtime_safety = switch (builtin.mode) {
26 .Debug, .ReleaseSafe => true,23 .Debug, .ReleaseSafe => true,
27 .ReleaseFast, .ReleaseSmall => false,24 .ReleaseFast, .ReleaseSmall => false,
...@@ -50,7 +47,7 @@ pub const LineInfo = struct {...@@ -50,7 +47,7 @@ pub const LineInfo = struct {
50 }47 }
51};48};
5249
53var stderr_mutex = std.Mutex.init();50var stderr_mutex = std.Mutex{};
5451
55/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for52/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for
56/// "printf debugging".53/// "printf debugging".
...@@ -235,7 +232,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {...@@ -235,7 +232,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {
235var panicking: u8 = 0;232var panicking: u8 = 0;
236233
237// Locked to avoid interleaving panic messages from multiple threads.234// Locked to avoid interleaving panic messages from multiple threads.
238var panic_mutex = std.Mutex.init();235var panic_mutex = std.Mutex{};
239236
240/// Counts how many times the panic handler is invoked by this thread.237/// Counts how many times the panic handler is invoked by this thread.
241/// This is used to catch and handle panics triggered by the panic handler.238/// This is used to catch and handle panics triggered by the panic handler.
lib/std/heap.zig+103-37
...@@ -12,6 +12,7 @@ const maxInt = std.math.maxInt;...@@ -12,6 +12,7 @@ const maxInt = std.math.maxInt;
12pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator;12pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator;
13pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator;13pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator;
14pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator;14pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator;
15pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig").GeneralPurposeAllocator;
1516
16const Allocator = mem.Allocator;17const Allocator = mem.Allocator;
1718
...@@ -36,7 +37,7 @@ var c_allocator_state = Allocator{...@@ -36,7 +37,7 @@ var c_allocator_state = Allocator{
36 .resizeFn = cResize,37 .resizeFn = cResize,
37};38};
3839
39fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 {40fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Allocator.Error![]u8 {
40 assert(ptr_align <= @alignOf(c_longdouble));41 assert(ptr_align <= @alignOf(c_longdouble));
41 const ptr = @ptrCast([*]u8, c.malloc(len) orelse return error.OutOfMemory);42 const ptr = @ptrCast([*]u8, c.malloc(len) orelse return error.OutOfMemory);
42 if (len_align == 0) {43 if (len_align == 0) {
...@@ -53,7 +54,14 @@ fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocato...@@ -53,7 +54,14 @@ fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocato
53 return ptr[0..mem.alignBackwardAnyAlign(full_len, len_align)];54 return ptr[0..mem.alignBackwardAnyAlign(full_len, len_align)];
54}55}
5556
56fn cResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize {57fn cResize(
58 self: *Allocator,
59 buf: []u8,
60 old_align: u29,
61 new_len: usize,
62 len_align: u29,
63 ret_addr: usize,
64) Allocator.Error!usize {
57 if (new_len == 0) {65 if (new_len == 0) {
58 c.free(buf.ptr);66 c.free(buf.ptr);
59 return 0;67 return 0;
...@@ -88,8 +96,6 @@ var wasm_page_allocator_state = Allocator{...@@ -88,8 +96,6 @@ var wasm_page_allocator_state = Allocator{
88 .resizeFn = WasmPageAllocator.resize,96 .resizeFn = WasmPageAllocator.resize,
89};97};
9098
91pub const direct_allocator = @compileError("deprecated; use std.heap.page_allocator");
92
93/// Verifies that the adjusted length will still map to the full length99/// Verifies that the adjusted length will still map to the full length
94pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize {100pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize {
95 const aligned_len = mem.alignAllocLen(full_len, len, len_align);101 const aligned_len = mem.alignAllocLen(full_len, len, len_align);
...@@ -97,10 +103,13 @@ pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize {...@@ -97,10 +103,13 @@ pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize {
97 return aligned_len;103 return aligned_len;
98}104}
99105
106/// TODO Utilize this on Windows.
107pub var next_mmap_addr_hint: ?[*]align(mem.page_size) u8 = null;
108
100const PageAllocator = struct {109const PageAllocator = struct {
101 fn alloc(allocator: *Allocator, n: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 {110 fn alloc(allocator: *Allocator, n: usize, alignment: u29, len_align: u29, ra: usize) error{OutOfMemory}![]u8 {
102 assert(n > 0);111 assert(n > 0);
103 const alignedLen = mem.alignForward(n, mem.page_size);112 const aligned_len = mem.alignForward(n, mem.page_size);
104113
105 if (builtin.os.tag == .windows) {114 if (builtin.os.tag == .windows) {
106 const w = os.windows;115 const w = os.windows;
...@@ -112,14 +121,14 @@ const PageAllocator = struct {...@@ -112,14 +121,14 @@ const PageAllocator = struct {
112 // see https://devblogs.microsoft.com/oldnewthing/?p=42223121 // see https://devblogs.microsoft.com/oldnewthing/?p=42223
113 const addr = w.VirtualAlloc(122 const addr = w.VirtualAlloc(
114 null,123 null,
115 alignedLen,124 aligned_len,
116 w.MEM_COMMIT | w.MEM_RESERVE,125 w.MEM_COMMIT | w.MEM_RESERVE,
117 w.PAGE_READWRITE,126 w.PAGE_READWRITE,
118 ) catch return error.OutOfMemory;127 ) catch return error.OutOfMemory;
119128
120 // If the allocation is sufficiently aligned, use it.129 // If the allocation is sufficiently aligned, use it.
121 if (@ptrToInt(addr) & (alignment - 1) == 0) {130 if (@ptrToInt(addr) & (alignment - 1) == 0) {
122 return @ptrCast([*]u8, addr)[0..alignPageAllocLen(alignedLen, n, len_align)];131 return @ptrCast([*]u8, addr)[0..alignPageAllocLen(aligned_len, n, len_align)];
123 }132 }
124133
125 // If it wasn't, actually do an explicitely aligned allocation.134 // If it wasn't, actually do an explicitely aligned allocation.
...@@ -146,20 +155,24 @@ const PageAllocator = struct {...@@ -146,20 +155,24 @@ const PageAllocator = struct {
146 // until it succeeds.155 // until it succeeds.
147 const ptr = w.VirtualAlloc(156 const ptr = w.VirtualAlloc(
148 @intToPtr(*c_void, aligned_addr),157 @intToPtr(*c_void, aligned_addr),
149 alignedLen,158 aligned_len,
150 w.MEM_COMMIT | w.MEM_RESERVE,159 w.MEM_COMMIT | w.MEM_RESERVE,
151 w.PAGE_READWRITE,160 w.PAGE_READWRITE,
152 ) catch continue;161 ) catch continue;
153162
154 return @ptrCast([*]u8, ptr)[0..alignPageAllocLen(alignedLen, n, len_align)];163 return @ptrCast([*]u8, ptr)[0..alignPageAllocLen(aligned_len, n, len_align)];
155 }164 }
156 }165 }
157166
158 const maxDropLen = alignment - std.math.min(alignment, mem.page_size);167 const max_drop_len = alignment - std.math.min(alignment, mem.page_size);
159 const allocLen = if (maxDropLen <= alignedLen - n) alignedLen else mem.alignForward(alignedLen + maxDropLen, mem.page_size);168 const alloc_len = if (max_drop_len <= aligned_len - n)
169 aligned_len
170 else
171 mem.alignForward(aligned_len + max_drop_len, mem.page_size);
172 const hint = @atomicLoad(@TypeOf(next_mmap_addr_hint), &next_mmap_addr_hint, .Unordered);
160 const slice = os.mmap(173 const slice = os.mmap(
161 null,174 hint,
162 allocLen,175 alloc_len,
163 os.PROT_READ | os.PROT_WRITE,176 os.PROT_READ | os.PROT_WRITE,
164 os.MAP_PRIVATE | os.MAP_ANONYMOUS,177 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
165 -1,178 -1,
...@@ -168,25 +181,36 @@ const PageAllocator = struct {...@@ -168,25 +181,36 @@ const PageAllocator = struct {
168 assert(mem.isAligned(@ptrToInt(slice.ptr), mem.page_size));181 assert(mem.isAligned(@ptrToInt(slice.ptr), mem.page_size));
169182
170 const aligned_addr = mem.alignForward(@ptrToInt(slice.ptr), alignment);183 const aligned_addr = mem.alignForward(@ptrToInt(slice.ptr), alignment);
184 const result_ptr = @alignCast(mem.page_size, @intToPtr([*]u8, aligned_addr));
171185
172 // Unmap the extra bytes that were only requested in order to guarantee186 // Unmap the extra bytes that were only requested in order to guarantee
173 // that the range of memory we were provided had a proper alignment in187 // that the range of memory we were provided had a proper alignment in
174 // it somewhere. The extra bytes could be at the beginning, or end, or both.188 // it somewhere. The extra bytes could be at the beginning, or end, or both.
175 const dropLen = aligned_addr - @ptrToInt(slice.ptr);189 const drop_len = aligned_addr - @ptrToInt(slice.ptr);
176 if (dropLen != 0) {190 if (drop_len != 0) {
177 os.munmap(slice[0..dropLen]);191 os.munmap(slice[0..drop_len]);
178 }192 }
179193
180 // Unmap extra pages194 // Unmap extra pages
181 const alignedBufferLen = allocLen - dropLen;195 const aligned_buffer_len = alloc_len - drop_len;
182 if (alignedBufferLen > alignedLen) {196 if (aligned_buffer_len > aligned_len) {
183 os.munmap(@alignCast(mem.page_size, @intToPtr([*]u8, aligned_addr))[alignedLen..alignedBufferLen]);197 os.munmap(result_ptr[aligned_len..aligned_buffer_len]);
184 }198 }
185199
186 return @intToPtr([*]u8, aligned_addr)[0..alignPageAllocLen(alignedLen, n, len_align)];200 const new_hint = @alignCast(mem.page_size, result_ptr + aligned_len);
201 _ = @cmpxchgStrong(@TypeOf(next_mmap_addr_hint), &next_mmap_addr_hint, hint, new_hint, .Monotonic, .Monotonic);
202
203 return result_ptr[0..alignPageAllocLen(aligned_len, n, len_align)];
187 }204 }
188205
189 fn resize(allocator: *Allocator, buf_unaligned: []u8, new_size: usize, len_align: u29) Allocator.Error!usize {206 fn resize(
207 allocator: *Allocator,
208 buf_unaligned: []u8,
209 buf_align: u29,
210 new_size: usize,
211 len_align: u29,
212 return_address: usize,
213 ) Allocator.Error!usize {
190 const new_size_aligned = mem.alignForward(new_size, mem.page_size);214 const new_size_aligned = mem.alignForward(new_size, mem.page_size);
191215
192 if (builtin.os.tag == .windows) {216 if (builtin.os.tag == .windows) {
...@@ -201,7 +225,7 @@ const PageAllocator = struct {...@@ -201,7 +225,7 @@ const PageAllocator = struct {
201 w.VirtualFree(buf_unaligned.ptr, 0, w.MEM_RELEASE);225 w.VirtualFree(buf_unaligned.ptr, 0, w.MEM_RELEASE);
202 return 0;226 return 0;
203 }227 }
204 if (new_size < buf_unaligned.len) {228 if (new_size <= buf_unaligned.len) {
205 const base_addr = @ptrToInt(buf_unaligned.ptr);229 const base_addr = @ptrToInt(buf_unaligned.ptr);
206 const old_addr_end = base_addr + buf_unaligned.len;230 const old_addr_end = base_addr + buf_unaligned.len;
207 const new_addr_end = mem.alignForward(base_addr + new_size, mem.page_size);231 const new_addr_end = mem.alignForward(base_addr + new_size, mem.page_size);
...@@ -216,10 +240,10 @@ const PageAllocator = struct {...@@ -216,10 +240,10 @@ const PageAllocator = struct {
216 }240 }
217 return alignPageAllocLen(new_size_aligned, new_size, len_align);241 return alignPageAllocLen(new_size_aligned, new_size, len_align);
218 }242 }
219 if (new_size == buf_unaligned.len) {243 const old_size_aligned = mem.alignForward(buf_unaligned.len, mem.page_size);
244 if (new_size_aligned <= old_size_aligned) {
220 return alignPageAllocLen(new_size_aligned, new_size, len_align);245 return alignPageAllocLen(new_size_aligned, new_size, len_align);
221 }246 }
222 // new_size > buf_unaligned.len not implemented
223 return error.OutOfMemory;247 return error.OutOfMemory;
224 }248 }
225249
...@@ -229,6 +253,7 @@ const PageAllocator = struct {...@@ -229,6 +253,7 @@ const PageAllocator = struct {
229253
230 if (new_size_aligned < buf_aligned_len) {254 if (new_size_aligned < buf_aligned_len) {
231 const ptr = @intToPtr([*]align(mem.page_size) u8, @ptrToInt(buf_unaligned.ptr) + new_size_aligned);255 const ptr = @intToPtr([*]align(mem.page_size) u8, @ptrToInt(buf_unaligned.ptr) + new_size_aligned);
256 // TODO: if the next_mmap_addr_hint is within the unmapped range, update it
232 os.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]);257 os.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]);
233 if (new_size_aligned == 0)258 if (new_size_aligned == 0)
234 return 0;259 return 0;
...@@ -236,6 +261,7 @@ const PageAllocator = struct {...@@ -236,6 +261,7 @@ const PageAllocator = struct {
236 }261 }
237262
238 // TODO: call mremap263 // TODO: call mremap
264 // TODO: if the next_mmap_addr_hint is within the remapped range, update it
239 return error.OutOfMemory;265 return error.OutOfMemory;
240 }266 }
241};267};
...@@ -332,7 +358,7 @@ const WasmPageAllocator = struct {...@@ -332,7 +358,7 @@ const WasmPageAllocator = struct {
332 return mem.alignForward(memsize, mem.page_size) / mem.page_size;358 return mem.alignForward(memsize, mem.page_size) / mem.page_size;
333 }359 }
334360
335 fn alloc(allocator: *Allocator, len: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 {361 fn alloc(allocator: *Allocator, len: usize, alignment: u29, len_align: u29, ra: usize) error{OutOfMemory}![]u8 {
336 const page_count = nPages(len);362 const page_count = nPages(len);
337 const page_idx = try allocPages(page_count, alignment);363 const page_idx = try allocPages(page_count, alignment);
338 return @intToPtr([*]u8, page_idx * mem.page_size)[0..alignPageAllocLen(page_count * mem.page_size, len, len_align)];364 return @intToPtr([*]u8, page_idx * mem.page_size)[0..alignPageAllocLen(page_count * mem.page_size, len, len_align)];
...@@ -385,7 +411,14 @@ const WasmPageAllocator = struct {...@@ -385,7 +411,14 @@ const WasmPageAllocator = struct {
385 }411 }
386 }412 }
387413
388 fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize {414 fn resize(
415 allocator: *Allocator,
416 buf: []u8,
417 buf_align: u29,
418 new_len: usize,
419 len_align: u29,
420 return_address: usize,
421 ) error{OutOfMemory}!usize {
389 const aligned_len = mem.alignForward(buf.len, mem.page_size);422 const aligned_len = mem.alignForward(buf.len, mem.page_size);
390 if (new_len > aligned_len) return error.OutOfMemory;423 if (new_len > aligned_len) return error.OutOfMemory;
391 const current_n = nPages(aligned_len);424 const current_n = nPages(aligned_len);
...@@ -425,7 +458,13 @@ pub const HeapAllocator = switch (builtin.os.tag) {...@@ -425,7 +458,13 @@ pub const HeapAllocator = switch (builtin.os.tag) {
425 return @intToPtr(*align(1) usize, @ptrToInt(buf.ptr) + buf.len);458 return @intToPtr(*align(1) usize, @ptrToInt(buf.ptr) + buf.len);
426 }459 }
427460
428 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 {461 fn alloc(
462 allocator: *Allocator,
463 n: usize,
464 ptr_align: u29,
465 len_align: u29,
466 return_address: usize,
467 ) error{OutOfMemory}![]u8 {
429 const self = @fieldParentPtr(HeapAllocator, "allocator", allocator);468 const self = @fieldParentPtr(HeapAllocator, "allocator", allocator);
430469
431 const amt = n + ptr_align - 1 + @sizeOf(usize);470 const amt = n + ptr_align - 1 + @sizeOf(usize);
...@@ -452,7 +491,14 @@ pub const HeapAllocator = switch (builtin.os.tag) {...@@ -452,7 +491,14 @@ pub const HeapAllocator = switch (builtin.os.tag) {
452 return buf;491 return buf;
453 }492 }
454493
455 fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize {494 fn resize(
495 allocator: *Allocator,
496 buf: []u8,
497 buf_align: u29,
498 new_size: usize,
499 len_align: u29,
500 return_address: usize,
501 ) error{OutOfMemory}!usize {
456 const self = @fieldParentPtr(HeapAllocator, "allocator", allocator);502 const self = @fieldParentPtr(HeapAllocator, "allocator", allocator);
457 if (new_size == 0) {503 if (new_size == 0) {
458 os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*));504 os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*));
...@@ -524,7 +570,7 @@ pub const FixedBufferAllocator = struct {...@@ -524,7 +570,7 @@ pub const FixedBufferAllocator = struct {
524 return buf.ptr + buf.len == self.buffer.ptr + self.end_index;570 return buf.ptr + buf.len == self.buffer.ptr + self.end_index;
525 }571 }
526572
527 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 {573 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29, ra: usize) ![]u8 {
528 const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);574 const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);
529 const aligned_addr = mem.alignForward(@ptrToInt(self.buffer.ptr) + self.end_index, ptr_align);575 const aligned_addr = mem.alignForward(@ptrToInt(self.buffer.ptr) + self.end_index, ptr_align);
530 const adjusted_index = aligned_addr - @ptrToInt(self.buffer.ptr);576 const adjusted_index = aligned_addr - @ptrToInt(self.buffer.ptr);
...@@ -538,7 +584,14 @@ pub const FixedBufferAllocator = struct {...@@ -538,7 +584,14 @@ pub const FixedBufferAllocator = struct {
538 return result;584 return result;
539 }585 }
540586
541 fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) Allocator.Error!usize {587 fn resize(
588 allocator: *Allocator,
589 buf: []u8,
590 buf_align: u29,
591 new_size: usize,
592 len_align: u29,
593 return_address: usize,
594 ) Allocator.Error!usize {
542 const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);595 const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);
543 assert(self.ownsSlice(buf)); // sanity check596 assert(self.ownsSlice(buf)); // sanity check
544597
...@@ -588,7 +641,7 @@ pub const ThreadSafeFixedBufferAllocator = blk: {...@@ -588,7 +641,7 @@ pub const ThreadSafeFixedBufferAllocator = blk: {
588 };641 };
589 }642 }
590643
591 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 {644 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29, ra: usize) ![]u8 {
592 const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator);645 const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator);
593 var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst);646 var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst);
594 while (true) {647 while (true) {
...@@ -636,18 +689,31 @@ pub fn StackFallbackAllocator(comptime size: usize) type {...@@ -636,18 +689,31 @@ pub fn StackFallbackAllocator(comptime size: usize) type {
636 return &self.allocator;689 return &self.allocator;
637 }690 }
638691
639 fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![*]u8 {692 fn alloc(
693 allocator: *Allocator,
694 len: usize,
695 ptr_align: u29,
696 len_align: u29,
697 return_address: usize,
698 ) error{OutOfMemory}![*]u8 {
640 const self = @fieldParentPtr(Self, "allocator", allocator);699 const self = @fieldParentPtr(Self, "allocator", allocator);
641 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, ptr_align) catch700 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, ptr_align) catch
642 return fallback_allocator.alloc(len, ptr_align);701 return fallback_allocator.alloc(len, ptr_align);
643 }702 }
644703
645 fn resize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!void {704 fn resize(
705 self: *Allocator,
706 buf: []u8,
707 buf_align: u29,
708 new_len: usize,
709 len_align: u29,
710 return_address: usize,
711 ) error{OutOfMemory}!void {
646 const self = @fieldParentPtr(Self, "allocator", allocator);712 const self = @fieldParentPtr(Self, "allocator", allocator);
647 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {713 if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
648 try self.fixed_buffer_allocator.callResizeFn(buf, new_len);714 try self.fixed_buffer_allocator.resize(buf, new_len);
649 } else {715 } else {
650 try self.fallback_allocator.callResizeFn(buf, new_len);716 try self.fallback_allocator.resize(buf, new_len);
651 }717 }
652 }718 }
653 };719 };
...@@ -932,7 +998,7 @@ pub fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator....@@ -932,7 +998,7 @@ pub fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator.
932 slice[60] = 0x34;998 slice[60] = 0x34;
933999
934 // realloc to a smaller size but with a larger alignment1000 // realloc to a smaller size but with a larger alignment
935 slice = try allocator.alignedRealloc(slice, mem.page_size * 32, alloc_size / 2);1001 slice = try allocator.reallocAdvanced(slice, mem.page_size * 32, alloc_size / 2, .exact);
936 testing.expect(slice[0] == 0x12);1002 testing.expect(slice[0] == 0x12);
937 testing.expect(slice[60] == 0x34);1003 testing.expect(slice[60] == 0x34);
938}1004}
lib/std/heap/arena_allocator.zig+2-2
...@@ -49,7 +49,7 @@ pub const ArenaAllocator = struct {...@@ -49,7 +49,7 @@ pub const ArenaAllocator = struct {
49 const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16);49 const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16);
50 const big_enough_len = prev_len + actual_min_size;50 const big_enough_len = prev_len + actual_min_size;
51 const len = big_enough_len + big_enough_len / 2;51 const len = big_enough_len + big_enough_len / 2;
52 const buf = try self.child_allocator.callAllocFn(len, @alignOf(BufNode), 1);52 const buf = try self.child_allocator.allocFn(self.child_allocator, len, @alignOf(BufNode), 1, @returnAddress());
53 const buf_node = @ptrCast(*BufNode, @alignCast(@alignOf(BufNode), buf.ptr));53 const buf_node = @ptrCast(*BufNode, @alignCast(@alignOf(BufNode), buf.ptr));
54 buf_node.* = BufNode{54 buf_node.* = BufNode{
55 .data = buf,55 .data = buf,
...@@ -60,7 +60,7 @@ pub const ArenaAllocator = struct {...@@ -60,7 +60,7 @@ pub const ArenaAllocator = struct {
60 return buf_node;60 return buf_node;
61 }61 }
6262
63 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 {63 fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29, ra: usize) ![]u8 {
64 const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator);64 const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator);
6565
66 var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + ptr_align);66 var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + ptr_align);
lib/std/heap/general_purpose_allocator.zig created+921
...@@ -0,0 +1,921 @@
1//! # General Purpose Allocator
2//!
3//! ## Design Priorities
4//!
5//! ### `OptimizationMode.debug` and `OptimizationMode.release_safe`:
6//!
7//! * Detect double free, and print stack trace of:
8//! - Where it was first allocated
9//! - Where it was freed the first time
10//! - Where it was freed the second time
11//!
12//! * Detect leaks and print stack trace of:
13//! - Where it was allocated
14//!
15//! * When a page of memory is no longer needed, give it back to resident memory
16//! as soon as possible, so that it causes page faults when used.
17//!
18//! * Do not re-use memory slots, so that memory safety is upheld. For small
19//! allocations, this is handled here; for larger ones it is handled in the
20//! backing allocator (by default `std.heap.page_allocator`).
21//!
22//! * Make pointer math errors unlikely to harm memory from
23//! unrelated allocations.
24//!
25//! * It's OK for these mechanisms to cost some extra overhead bytes.
26//!
27//! * It's OK for performance cost for these mechanisms.
28//!
29//! * Rogue memory writes should not harm the allocator's state.
30//!
31//! * Cross platform. Operates based on a backing allocator which makes it work
32//! everywhere, even freestanding.
33//!
34//! * Compile-time configuration.
35//!
36//! ### `OptimizationMode.release_fast` (note: not much work has gone into this use case yet):
37//!
38//! * Low fragmentation is primary concern
39//! * Performance of worst-case latency is secondary concern
40//! * Performance of average-case latency is next
41//! * Finally, having freed memory unmapped, and pointer math errors unlikely to
42//! harm memory from unrelated allocations are nice-to-haves.
43//!
44//! ### `OptimizationMode.release_small` (note: not much work has gone into this use case yet):
45//!
46//! * Small binary code size of the executable is the primary concern.
47//! * Next, defer to the `.release_fast` priority list.
48//!
49//! ## Basic Design:
50//!
51//! Small allocations are divided into buckets:
52//!
53//! ```
54//! index obj_size
55//! 0 1
56//! 1 2
57//! 2 4
58//! 3 8
59//! 4 16
60//! 5 32
61//! 6 64
62//! 7 128
63//! 8 256
64//! 9 512
65//! 10 1024
66//! 11 2048
67//! ```
68//!
69//! The main allocator state has an array of all the "current" buckets for each
70//! size class. Each slot in the array can be null, meaning the bucket for that
71//! size class is not allocated. When the first object is allocated for a given
72//! size class, it allocates 1 page of memory from the OS. This page is
73//! divided into "slots" - one per allocated object. Along with the page of memory
74//! for object slots, as many pages as necessary are allocated to store the
75//! BucketHeader, followed by "used bits", and two stack traces for each slot
76//! (allocation trace and free trace).
77//!
78//! The "used bits" are 1 bit per slot representing whether the slot is used.
79//! Allocations use the data to iterate to find a free slot. Frees assert that the
80//! corresponding bit is 1 and set it to 0.
81//!
82//! Buckets have prev and next pointers. When there is only one bucket for a given
83//! size class, both prev and next point to itself. When all slots of a bucket are
84//! used, a new bucket is allocated, and enters the doubly linked list. The main
85//! allocator state tracks the "current" bucket for each size class. Leak detection
86//! currently only checks the current bucket.
87//!
88//! Resizing detects if the size class is unchanged or smaller, in which case the same
89//! pointer is returned unmodified. If a larger size class is required,
90//! `error.OutOfMemory` is returned.
91//!
92//! Large objects are allocated directly using the backing allocator and their metadata is stored
93//! in a `std.HashMap` using the backing allocator.
94
95const std = @import("std");
96const math = std.math;
97const assert = std.debug.assert;
98const mem = std.mem;
99const Allocator = std.mem.Allocator;
100const page_size = std.mem.page_size;
101const StackTrace = std.builtin.StackTrace;
102
103/// Integer type for pointing to slots in a small allocation
104const SlotIndex = std.meta.Int(false, math.log2(page_size) + 1);
105
106const sys_can_stack_trace = switch (std.Target.current.cpu.arch) {
107 // Observed to go into an infinite loop.
108 // TODO: Make this work.
109 .mips,
110 .mipsel,
111 => false,
112
113 // `@returnAddress()` in LLVM 10 gives
114 // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address".
115 .wasm32,
116 .wasm64,
117 => std.Target.current.os.tag == .emscripten,
118
119 else => true,
120};
121const default_sys_stack_trace_frames: usize = if (sys_can_stack_trace) 4 else 0;
122const default_stack_trace_frames: usize = switch (std.builtin.mode) {
123 .Debug => default_sys_stack_trace_frames,
124 else => 0,
125};
126
127pub const Config = struct {
128 /// Number of stack frames to capture.
129 stack_trace_frames: usize = default_stack_trace_frames,
130
131 /// If true, the allocator will have two fields:
132 /// * `total_requested_bytes` which tracks the total allocated bytes of memory requested.
133 /// * `requested_memory_limit` which causes allocations to return `error.OutOfMemory`
134 /// when the `total_requested_bytes` exceeds this limit.
135 /// If false, these fields will be `void`.
136 enable_memory_limit: bool = false,
137
138 /// Whether to enable safety checks.
139 safety: bool = std.debug.runtime_safety,
140
141 /// Whether the allocator may be used simultaneously from multiple threads.
142 thread_safe: bool = !std.builtin.single_threaded,
143};
144
145pub fn GeneralPurposeAllocator(comptime config: Config) type {
146 return struct {
147 allocator: Allocator = Allocator{
148 .allocFn = alloc,
149 .resizeFn = resize,
150 },
151 backing_allocator: *Allocator = std.heap.page_allocator,
152 buckets: [small_bucket_count]?*BucketHeader = [1]?*BucketHeader{null} ** small_bucket_count,
153 large_allocations: LargeAllocTable = .{},
154
155 total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init,
156 requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init,
157
158 mutex: @TypeOf(mutex_init) = mutex_init,
159
160 const Self = @This();
161
162 const total_requested_bytes_init = if (config.enable_memory_limit) @as(usize, 0) else {};
163 const requested_memory_limit_init = if (config.enable_memory_limit) @as(usize, math.maxInt(usize)) else {};
164
165 const mutex_init = if (config.thread_safe) std.Mutex{} else std.mutex.Dummy{};
166
167 const stack_n = config.stack_trace_frames;
168 const one_trace_size = @sizeOf(usize) * stack_n;
169 const traces_per_slot = 2;
170
171 pub const Error = mem.Allocator.Error;
172
173 const small_bucket_count = math.log2(page_size);
174 const largest_bucket_object_size = 1 << (small_bucket_count - 1);
175
176 const LargeAlloc = struct {
177 bytes: []u8,
178 stack_addresses: [stack_n]usize,
179
180 fn dumpStackTrace(self: *LargeAlloc) void {
181 var len: usize = 0;
182 while (len < stack_n and self.stack_addresses[len] != 0) {
183 len += 1;
184 }
185 const stack_trace = StackTrace{
186 .instruction_addresses = &self.stack_addresses,
187 .index = len,
188 };
189 std.debug.dumpStackTrace(stack_trace);
190 }
191 };
192 const LargeAllocTable = std.AutoHashMapUnmanaged(usize, LargeAlloc);
193
194 // Bucket: In memory, in order:
195 // * BucketHeader
196 // * bucket_used_bits: [N]u8, // 1 bit for every slot; 1 byte for every 8 slots
197 // * stack_trace_addresses: [N]usize, // traces_per_slot for every allocation
198
199 const BucketHeader = struct {
200 prev: *BucketHeader,
201 next: *BucketHeader,
202 page: [*]align(page_size) u8,
203 alloc_cursor: SlotIndex,
204 used_count: SlotIndex,
205
206 fn usedBits(bucket: *BucketHeader, index: usize) *u8 {
207 return @intToPtr(*u8, @ptrToInt(bucket) + @sizeOf(BucketHeader) + index);
208 }
209
210 fn stackTracePtr(
211 bucket: *BucketHeader,
212 size_class: usize,
213 slot_index: SlotIndex,
214 trace_kind: TraceKind,
215 ) *[stack_n]usize {
216 const start_ptr = @ptrCast([*]u8, bucket) + bucketStackFramesStart(size_class);
217 const addr = start_ptr + one_trace_size * traces_per_slot * slot_index +
218 @enumToInt(trace_kind) * @as(usize, one_trace_size);
219 return @ptrCast(*[stack_n]usize, @alignCast(@alignOf(usize), addr));
220 }
221
222 fn captureStackTrace(
223 bucket: *BucketHeader,
224 ret_addr: usize,
225 size_class: usize,
226 slot_index: SlotIndex,
227 trace_kind: TraceKind,
228 ) void {
229 // Initialize them to 0. When determining the count we must look
230 // for non zero addresses.
231 const stack_addresses = bucket.stackTracePtr(size_class, slot_index, trace_kind);
232 collectStackTrace(ret_addr, stack_addresses);
233 }
234 };
235
236 fn bucketStackTrace(
237 bucket: *BucketHeader,
238 size_class: usize,
239 slot_index: SlotIndex,
240 trace_kind: TraceKind,
241 ) StackTrace {
242 const stack_addresses = bucket.stackTracePtr(size_class, slot_index, trace_kind);
243 var len: usize = 0;
244 while (len < stack_n and stack_addresses[len] != 0) {
245 len += 1;
246 }
247 return StackTrace{
248 .instruction_addresses = stack_addresses,
249 .index = len,
250 };
251 }
252
253 fn bucketStackFramesStart(size_class: usize) usize {
254 return mem.alignForward(
255 @sizeOf(BucketHeader) + usedBitsCount(size_class),
256 @alignOf(usize),
257 );
258 }
259
260 fn bucketSize(size_class: usize) usize {
261 const slot_count = @divExact(page_size, size_class);
262 return bucketStackFramesStart(size_class) + one_trace_size * traces_per_slot * slot_count;
263 }
264
265 fn usedBitsCount(size_class: usize) usize {
266 const slot_count = @divExact(page_size, size_class);
267 if (slot_count < 8) return 1;
268 return @divExact(slot_count, 8);
269 }
270
271 fn detectLeaksInBucket(
272 bucket: *BucketHeader,
273 size_class: usize,
274 used_bits_count: usize,
275 ) bool {
276 var leaks = false;
277 var used_bits_byte: usize = 0;
278 while (used_bits_byte < used_bits_count) : (used_bits_byte += 1) {
279 const used_byte = bucket.usedBits(used_bits_byte).*;
280 if (used_byte != 0) {
281 var bit_index: u3 = 0;
282 while (true) : (bit_index += 1) {
283 const is_used = @truncate(u1, used_byte >> bit_index) != 0;
284 if (is_used) {
285 std.debug.print("\nMemory leak detected:\n", .{});
286 const slot_index = @intCast(SlotIndex, used_bits_byte * 8 + bit_index);
287 const stack_trace = bucketStackTrace(
288 bucket,
289 size_class,
290 slot_index,
291 .alloc,
292 );
293 std.debug.dumpStackTrace(stack_trace);
294 leaks = true;
295 }
296 if (bit_index == math.maxInt(u3))
297 break;
298 }
299 }
300 }
301 return leaks;
302 }
303
304 /// Returns whether there were leaks.
305 pub fn deinit(self: *Self) bool {
306 var leaks = false;
307 for (self.buckets) |optional_bucket, bucket_i| {
308 const first_bucket = optional_bucket orelse continue;
309 const size_class = @as(usize, 1) << @intCast(math.Log2Int(usize), bucket_i);
310 const used_bits_count = usedBitsCount(size_class);
311 var bucket = first_bucket;
312 while (true) {
313 leaks = detectLeaksInBucket(bucket, size_class, used_bits_count) or leaks;
314 bucket = bucket.next;
315 if (bucket == first_bucket)
316 break;
317 }
318 }
319 for (self.large_allocations.items()) |*large_alloc| {
320 std.debug.print("\nMemory leak detected (0x{x}):\n", .{@ptrToInt(large_alloc.value.bytes.ptr)});
321 large_alloc.value.dumpStackTrace();
322 leaks = true;
323 }
324 self.large_allocations.deinit(self.backing_allocator);
325 self.* = undefined;
326 return leaks;
327 }
328
329 fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void {
330 if (stack_n == 0) return;
331 mem.set(usize, addresses, 0);
332 var stack_trace = StackTrace{
333 .instruction_addresses = addresses,
334 .index = 0,
335 };
336 std.debug.captureStackTrace(first_trace_addr, &stack_trace);
337 }
338
339 fn allocSlot(self: *Self, size_class: usize, trace_addr: usize) Error![*]u8 {
340 const bucket_index = math.log2(size_class);
341 const first_bucket = self.buckets[bucket_index] orelse try self.createBucket(
342 size_class,
343 bucket_index,
344 );
345 var bucket = first_bucket;
346 const slot_count = @divExact(page_size, size_class);
347 while (bucket.alloc_cursor == slot_count) {
348 const prev_bucket = bucket;
349 bucket = prev_bucket.next;
350 if (bucket == first_bucket) {
351 // make a new one
352 bucket = try self.createBucket(size_class, bucket_index);
353 bucket.prev = prev_bucket;
354 bucket.next = prev_bucket.next;
355 prev_bucket.next = bucket;
356 bucket.next.prev = bucket;
357 }
358 }
359 // change the allocator's current bucket to be this one
360 self.buckets[bucket_index] = bucket;
361
362 const slot_index = bucket.alloc_cursor;
363 bucket.alloc_cursor += 1;
364
365 var used_bits_byte = bucket.usedBits(slot_index / 8);
366 const used_bit_index: u3 = @intCast(u3, slot_index % 8); // TODO cast should be unnecessary
367 used_bits_byte.* |= (@as(u8, 1) << used_bit_index);
368 bucket.used_count += 1;
369 bucket.captureStackTrace(trace_addr, size_class, slot_index, .alloc);
370 return bucket.page + slot_index * size_class;
371 }
372
373 fn searchBucket(
374 self: *Self,
375 bucket_index: usize,
376 addr: usize,
377 ) ?*BucketHeader {
378 const first_bucket = self.buckets[bucket_index] orelse return null;
379 var bucket = first_bucket;
380 while (true) {
381 const in_bucket_range = (addr >= @ptrToInt(bucket.page) and
382 addr < @ptrToInt(bucket.page) + page_size);
383 if (in_bucket_range) return bucket;
384 bucket = bucket.prev;
385 if (bucket == first_bucket) {
386 return null;
387 }
388 self.buckets[bucket_index] = bucket;
389 }
390 }
391
392 fn freeSlot(
393 self: *Self,
394 bucket: *BucketHeader,
395 bucket_index: usize,
396 size_class: usize,
397 slot_index: SlotIndex,
398 used_byte: *u8,
399 used_bit_index: u3,
400 trace_addr: usize,
401 ) void {
402 // Capture stack trace to be the "first free", in case a double free happens.
403 bucket.captureStackTrace(trace_addr, size_class, slot_index, .free);
404
405 used_byte.* &= ~(@as(u8, 1) << used_bit_index);
406 bucket.used_count -= 1;
407 if (bucket.used_count == 0) {
408 if (bucket.next == bucket) {
409 // it's the only bucket and therefore the current one
410 self.buckets[bucket_index] = null;
411 } else {
412 bucket.next.prev = bucket.prev;
413 bucket.prev.next = bucket.next;
414 self.buckets[bucket_index] = bucket.prev;
415 }
416 self.backing_allocator.free(bucket.page[0..page_size]);
417 const bucket_size = bucketSize(size_class);
418 const bucket_slice = @ptrCast([*]align(@alignOf(BucketHeader)) u8, bucket)[0..bucket_size];
419 self.backing_allocator.free(bucket_slice);
420 } else {
421 // TODO Set the slot data to undefined.
422 // Related: https://github.com/ziglang/zig/issues/4298
423 }
424 }
425
426 /// This function assumes the object is in the large object storage regardless
427 /// of the parameters.
428 fn resizeLarge(
429 self: *Self,
430 old_mem: []u8,
431 old_align: u29,
432 new_size: usize,
433 len_align: u29,
434 ret_addr: usize,
435 ) Error!usize {
436 const entry = self.large_allocations.getEntry(@ptrToInt(old_mem.ptr)) orelse {
437 if (config.safety) {
438 @panic("Invalid free");
439 } else {
440 unreachable;
441 }
442 };
443
444 if (config.safety and old_mem.len != entry.value.bytes.len) {
445 std.debug.print("\nAllocation size {} bytes does not match free size {}. Allocated here:\n", .{
446 entry.value.bytes.len,
447 old_mem.len,
448 });
449 entry.value.dumpStackTrace();
450
451 @panic("\nFree here:");
452 }
453
454 const result_len = try self.backing_allocator.resizeFn(self.backing_allocator, old_mem, old_align, new_size, len_align, ret_addr);
455
456 if (result_len == 0) {
457 self.large_allocations.removeAssertDiscard(@ptrToInt(old_mem.ptr));
458 return 0;
459 }
460
461 entry.value.bytes = old_mem.ptr[0..result_len];
462 collectStackTrace(ret_addr, &entry.value.stack_addresses);
463 return result_len;
464 }
465
466 pub fn setRequestedMemoryLimit(self: *Self, limit: usize) void {
467 self.requested_memory_limit = limit;
468 }
469
470 fn resize(
471 allocator: *Allocator,
472 old_mem: []u8,
473 old_align: u29,
474 new_size: usize,
475 len_align: u29,
476 ret_addr: usize,
477 ) Error!usize {
478 const self = @fieldParentPtr(Self, "allocator", allocator);
479
480 const held = self.mutex.acquire();
481 defer held.release();
482
483 const prev_req_bytes = self.total_requested_bytes;
484 if (config.enable_memory_limit) {
485 const new_req_bytes = prev_req_bytes + new_size - old_mem.len;
486 if (new_req_bytes > prev_req_bytes and new_req_bytes > self.requested_memory_limit) {
487 return error.OutOfMemory;
488 }
489 self.total_requested_bytes = new_req_bytes;
490 }
491 errdefer if (config.enable_memory_limit) {
492 self.total_requested_bytes = prev_req_bytes;
493 };
494
495 assert(old_mem.len != 0);
496
497 const aligned_size = math.max(old_mem.len, old_align);
498 if (aligned_size > largest_bucket_object_size) {
499 return self.resizeLarge(old_mem, old_align, new_size, len_align, ret_addr);
500 }
501 const size_class_hint = math.ceilPowerOfTwoAssert(usize, aligned_size);
502
503 var bucket_index = math.log2(size_class_hint);
504 var size_class: usize = size_class_hint;
505 const bucket = while (bucket_index < small_bucket_count) : (bucket_index += 1) {
506 if (self.searchBucket(bucket_index, @ptrToInt(old_mem.ptr))) |bucket| {
507 break bucket;
508 }
509 size_class *= 2;
510 } else {
511 return self.resizeLarge(old_mem, old_align, new_size, len_align, ret_addr);
512 };
513 const byte_offset = @ptrToInt(old_mem.ptr) - @ptrToInt(bucket.page);
514 const slot_index = @intCast(SlotIndex, byte_offset / size_class);
515 const used_byte_index = slot_index / 8;
516 const used_bit_index = @intCast(u3, slot_index % 8);
517 const used_byte = bucket.usedBits(used_byte_index);
518 const is_used = @truncate(u1, used_byte.* >> used_bit_index) != 0;
519 if (!is_used) {
520 if (config.safety) {
521 // print allocation stack trace
522 std.debug.print("\nDouble free detected, allocated here:\n", .{});
523 const alloc_stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc);
524 std.debug.dumpStackTrace(alloc_stack_trace);
525 std.debug.print("\nFirst free here:\n", .{});
526 const free_stack_trace = bucketStackTrace(bucket, size_class, slot_index, .free);
527 std.debug.dumpStackTrace(free_stack_trace);
528 @panic("\nSecond free here:");
529 } else {
530 unreachable;
531 }
532 }
533 if (new_size == 0) {
534 self.freeSlot(bucket, bucket_index, size_class, slot_index, used_byte, used_bit_index, ret_addr);
535 return @as(usize, 0);
536 }
537 const new_aligned_size = math.max(new_size, old_align);
538 const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size);
539 if (new_size_class <= size_class) {
540 return new_size;
541 }
542 return error.OutOfMemory;
543 }
544
545 fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Error![]u8 {
546 const self = @fieldParentPtr(Self, "allocator", allocator);
547
548 const held = self.mutex.acquire();
549 defer held.release();
550
551 const prev_req_bytes = self.total_requested_bytes;
552 if (config.enable_memory_limit) {
553 const new_req_bytes = prev_req_bytes + len;
554 if (new_req_bytes > self.requested_memory_limit) {
555 return error.OutOfMemory;
556 }
557 self.total_requested_bytes = new_req_bytes;
558 }
559 errdefer if (config.enable_memory_limit) {
560 self.total_requested_bytes = prev_req_bytes;
561 };
562
563 const new_aligned_size = math.max(len, ptr_align);
564 if (new_aligned_size > largest_bucket_object_size) {
565 try self.large_allocations.ensureCapacity(
566 self.backing_allocator,
567 self.large_allocations.entries.items.len + 1,
568 );
569
570 const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr);
571
572 const gop = self.large_allocations.getOrPutAssumeCapacity(@ptrToInt(slice.ptr));
573 assert(!gop.found_existing); // This would mean the kernel double-mapped pages.
574 gop.entry.value.bytes = slice;
575 collectStackTrace(ret_addr, &gop.entry.value.stack_addresses);
576
577 return slice;
578 } else {
579 const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size);
580 const ptr = try self.allocSlot(new_size_class, ret_addr);
581 return ptr[0..len];
582 }
583 }
584
585 fn createBucket(self: *Self, size_class: usize, bucket_index: usize) Error!*BucketHeader {
586 const page = try self.backing_allocator.allocAdvanced(u8, page_size, page_size, .exact);
587 errdefer self.backing_allocator.free(page);
588
589 const bucket_size = bucketSize(size_class);
590 const bucket_bytes = try self.backing_allocator.allocAdvanced(u8, @alignOf(BucketHeader), bucket_size, .exact);
591 const ptr = @ptrCast(*BucketHeader, bucket_bytes.ptr);
592 ptr.* = BucketHeader{
593 .prev = ptr,
594 .next = ptr,
595 .page = page.ptr,
596 .alloc_cursor = 0,
597 .used_count = 0,
598 };
599 self.buckets[bucket_index] = ptr;
600 // Set the used bits to all zeroes
601 @memset(@as(*[1]u8, ptr.usedBits(0)), 0, usedBitsCount(size_class));
602 return ptr;
603 }
604 };
605}
606
607const TraceKind = enum {
608 alloc,
609 free,
610};
611
612const test_config = Config{};
613
614test "small allocations - free in same order" {
615 var gpa = GeneralPurposeAllocator(test_config){};
616 defer std.testing.expect(!gpa.deinit());
617 const allocator = &gpa.allocator;
618
619 var list = std.ArrayList(*u64).init(std.testing.allocator);
620 defer list.deinit();
621
622 var i: usize = 0;
623 while (i < 513) : (i += 1) {
624 const ptr = try allocator.create(u64);
625 try list.append(ptr);
626 }
627
628 for (list.items) |ptr| {
629 allocator.destroy(ptr);
630 }
631}
632
633test "small allocations - free in reverse order" {
634 var gpa = GeneralPurposeAllocator(test_config){};
635 defer std.testing.expect(!gpa.deinit());
636 const allocator = &gpa.allocator;
637
638 var list = std.ArrayList(*u64).init(std.testing.allocator);
639 defer list.deinit();
640
641 var i: usize = 0;
642 while (i < 513) : (i += 1) {
643 const ptr = try allocator.create(u64);
644 try list.append(ptr);
645 }
646
647 while (list.popOrNull()) |ptr| {
648 allocator.destroy(ptr);
649 }
650}
651
652test "large allocations" {
653 var gpa = GeneralPurposeAllocator(test_config){};
654 defer std.testing.expect(!gpa.deinit());
655 const allocator = &gpa.allocator;
656
657 const ptr1 = try allocator.alloc(u64, 42768);
658 const ptr2 = try allocator.alloc(u64, 52768);
659 allocator.free(ptr1);
660 const ptr3 = try allocator.alloc(u64, 62768);
661 allocator.free(ptr3);
662 allocator.free(ptr2);
663}
664
665test "realloc" {
666 var gpa = GeneralPurposeAllocator(test_config){};
667 defer std.testing.expect(!gpa.deinit());
668 const allocator = &gpa.allocator;
669
670 var slice = try allocator.alignedAlloc(u8, @alignOf(u32), 1);
671 defer allocator.free(slice);
672 slice[0] = 0x12;
673
674 // This reallocation should keep its pointer address.
675 const old_slice = slice;
676 slice = try allocator.realloc(slice, 2);
677 std.testing.expect(old_slice.ptr == slice.ptr);
678 std.testing.expect(slice[0] == 0x12);
679 slice[1] = 0x34;
680
681 // This requires upgrading to a larger size class
682 slice = try allocator.realloc(slice, 17);
683 std.testing.expect(slice[0] == 0x12);
684 std.testing.expect(slice[1] == 0x34);
685}
686
687test "shrink" {
688 var gpa = GeneralPurposeAllocator(test_config){};
689 defer std.testing.expect(!gpa.deinit());
690 const allocator = &gpa.allocator;
691
692 var slice = try allocator.alloc(u8, 20);
693 defer allocator.free(slice);
694
695 mem.set(u8, slice, 0x11);
696
697 slice = allocator.shrink(slice, 17);
698
699 for (slice) |b| {
700 std.testing.expect(b == 0x11);
701 }
702
703 slice = allocator.shrink(slice, 16);
704
705 for (slice) |b| {
706 std.testing.expect(b == 0x11);
707 }
708}
709
710test "large object - grow" {
711 var gpa = GeneralPurposeAllocator(test_config){};
712 defer std.testing.expect(!gpa.deinit());
713 const allocator = &gpa.allocator;
714
715 var slice1 = try allocator.alloc(u8, page_size * 2 - 20);
716 defer allocator.free(slice1);
717
718 const old = slice1;
719 slice1 = try allocator.realloc(slice1, page_size * 2 - 10);
720 std.testing.expect(slice1.ptr == old.ptr);
721
722 slice1 = try allocator.realloc(slice1, page_size * 2);
723 std.testing.expect(slice1.ptr == old.ptr);
724
725 slice1 = try allocator.realloc(slice1, page_size * 2 + 1);
726}
727
728test "realloc small object to large object" {
729 var gpa = GeneralPurposeAllocator(test_config){};
730 defer std.testing.expect(!gpa.deinit());
731 const allocator = &gpa.allocator;
732
733 var slice = try allocator.alloc(u8, 70);
734 defer allocator.free(slice);
735 slice[0] = 0x12;
736 slice[60] = 0x34;
737
738 // This requires upgrading to a large object
739 const large_object_size = page_size * 2 + 50;
740 slice = try allocator.realloc(slice, large_object_size);
741 std.testing.expect(slice[0] == 0x12);
742 std.testing.expect(slice[60] == 0x34);
743}
744
745test "shrink large object to large object" {
746 var gpa = GeneralPurposeAllocator(test_config){};
747 defer std.testing.expect(!gpa.deinit());
748 const allocator = &gpa.allocator;
749
750 var slice = try allocator.alloc(u8, page_size * 2 + 50);
751 defer allocator.free(slice);
752 slice[0] = 0x12;
753 slice[60] = 0x34;
754
755 slice = try allocator.resize(slice, page_size * 2 + 1);
756 std.testing.expect(slice[0] == 0x12);
757 std.testing.expect(slice[60] == 0x34);
758
759 slice = allocator.shrink(slice, page_size * 2 + 1);
760 std.testing.expect(slice[0] == 0x12);
761 std.testing.expect(slice[60] == 0x34);
762
763 slice = try allocator.realloc(slice, page_size * 2);
764 std.testing.expect(slice[0] == 0x12);
765 std.testing.expect(slice[60] == 0x34);
766}
767
768test "shrink large object to large object with larger alignment" {
769 var gpa = GeneralPurposeAllocator(test_config){};
770 defer std.testing.expect(!gpa.deinit());
771 const allocator = &gpa.allocator;
772
773 var debug_buffer: [1000]u8 = undefined;
774 const debug_allocator = &std.heap.FixedBufferAllocator.init(&debug_buffer).allocator;
775
776 const alloc_size = page_size * 2 + 50;
777 var slice = try allocator.alignedAlloc(u8, 16, alloc_size);
778 defer allocator.free(slice);
779
780 const big_alignment: usize = switch (std.Target.current.os.tag) {
781 .windows => page_size * 32, // Windows aligns to 64K.
782 else => page_size * 2,
783 };
784 // This loop allocates until we find a page that is not aligned to the big
785 // alignment. Then we shrink the allocation after the loop, but increase the
786 // alignment to the higher one, that we know will force it to realloc.
787 var stuff_to_free = std.ArrayList([]align(16) u8).init(debug_allocator);
788 while (mem.isAligned(@ptrToInt(slice.ptr), big_alignment)) {
789 try stuff_to_free.append(slice);
790 slice = try allocator.alignedAlloc(u8, 16, alloc_size);
791 }
792 while (stuff_to_free.popOrNull()) |item| {
793 allocator.free(item);
794 }
795 slice[0] = 0x12;
796 slice[60] = 0x34;
797
798 slice = try allocator.reallocAdvanced(slice, big_alignment, alloc_size / 2, .exact);
799 std.testing.expect(slice[0] == 0x12);
800 std.testing.expect(slice[60] == 0x34);
801}
802
803test "realloc large object to small object" {
804 var gpa = GeneralPurposeAllocator(test_config){};
805 defer std.testing.expect(!gpa.deinit());
806 const allocator = &gpa.allocator;
807
808 var slice = try allocator.alloc(u8, page_size * 2 + 50);
809 defer allocator.free(slice);
810 slice[0] = 0x12;
811 slice[16] = 0x34;
812
813 slice = try allocator.realloc(slice, 19);
814 std.testing.expect(slice[0] == 0x12);
815 std.testing.expect(slice[16] == 0x34);
816}
817
818test "non-page-allocator backing allocator" {
819 var gpa = GeneralPurposeAllocator(.{}){ .backing_allocator = std.testing.allocator };
820 defer std.testing.expect(!gpa.deinit());
821 const allocator = &gpa.allocator;
822
823 const ptr = try allocator.create(i32);
824 defer allocator.destroy(ptr);
825}
826
827test "realloc large object to larger alignment" {
828 var gpa = GeneralPurposeAllocator(test_config){};
829 defer std.testing.expect(!gpa.deinit());
830 const allocator = &gpa.allocator;
831
832 var debug_buffer: [1000]u8 = undefined;
833 const debug_allocator = &std.heap.FixedBufferAllocator.init(&debug_buffer).allocator;
834
835 var slice = try allocator.alignedAlloc(u8, 16, page_size * 2 + 50);
836 defer allocator.free(slice);
837
838 const big_alignment: usize = switch (std.Target.current.os.tag) {
839 .windows => page_size * 32, // Windows aligns to 64K.
840 else => page_size * 2,
841 };
842 // This loop allocates until we find a page that is not aligned to the big alignment.
843 var stuff_to_free = std.ArrayList([]align(16) u8).init(debug_allocator);
844 while (mem.isAligned(@ptrToInt(slice.ptr), big_alignment)) {
845 try stuff_to_free.append(slice);
846 slice = try allocator.alignedAlloc(u8, 16, page_size * 2 + 50);
847 }
848 while (stuff_to_free.popOrNull()) |item| {
849 allocator.free(item);
850 }
851 slice[0] = 0x12;
852 slice[16] = 0x34;
853
854 slice = try allocator.reallocAdvanced(slice, 32, page_size * 2 + 100, .exact);
855 std.testing.expect(slice[0] == 0x12);
856 std.testing.expect(slice[16] == 0x34);
857
858 slice = try allocator.reallocAdvanced(slice, 32, page_size * 2 + 25, .exact);
859 std.testing.expect(slice[0] == 0x12);
860 std.testing.expect(slice[16] == 0x34);
861
862 slice = try allocator.reallocAdvanced(slice, big_alignment, page_size * 2 + 100, .exact);
863 std.testing.expect(slice[0] == 0x12);
864 std.testing.expect(slice[16] == 0x34);
865}
866
867test "large object shrinks to small but allocation fails during shrink" {
868 var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, 3);
869 var gpa = GeneralPurposeAllocator(.{}){ .backing_allocator = &failing_allocator.allocator };
870 defer std.testing.expect(!gpa.deinit());
871 const allocator = &gpa.allocator;
872
873 var slice = try allocator.alloc(u8, page_size * 2 + 50);
874 defer allocator.free(slice);
875 slice[0] = 0x12;
876 slice[3] = 0x34;
877
878 // Next allocation will fail in the backing allocator of the GeneralPurposeAllocator
879
880 slice = allocator.shrink(slice, 4);
881 std.testing.expect(slice[0] == 0x12);
882 std.testing.expect(slice[3] == 0x34);
883}
884
885test "objects of size 1024 and 2048" {
886 var gpa = GeneralPurposeAllocator(test_config){};
887 defer std.testing.expect(!gpa.deinit());
888 const allocator = &gpa.allocator;
889
890 const slice = try allocator.alloc(u8, 1025);
891 const slice2 = try allocator.alloc(u8, 3000);
892
893 allocator.free(slice);
894 allocator.free(slice2);
895}
896
897test "setting a memory cap" {
898 var gpa = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){};
899 defer std.testing.expect(!gpa.deinit());
900 const allocator = &gpa.allocator;
901
902 gpa.setRequestedMemoryLimit(1010);
903
904 const small = try allocator.create(i32);
905 std.testing.expect(gpa.total_requested_bytes == 4);
906
907 const big = try allocator.alloc(u8, 1000);
908 std.testing.expect(gpa.total_requested_bytes == 1004);
909
910 std.testing.expectError(error.OutOfMemory, allocator.create(u64));
911
912 allocator.destroy(small);
913 std.testing.expect(gpa.total_requested_bytes == 1000);
914
915 allocator.free(big);
916 std.testing.expect(gpa.total_requested_bytes == 0);
917
918 const exact = try allocator.alloc(u8, 1010);
919 std.testing.expect(gpa.total_requested_bytes == 1010);
920 allocator.free(exact);
921}
lib/std/heap/logging_allocator.zig+19-6
...@@ -23,10 +23,16 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {...@@ -23,10 +23,16 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
23 };23 };
24 }24 }
2525
26 fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 {26 fn alloc(
27 allocator: *Allocator,
28 len: usize,
29 ptr_align: u29,
30 len_align: u29,
31 ra: usize,
32 ) error{OutOfMemory}![]u8 {
27 const self = @fieldParentPtr(Self, "allocator", allocator);33 const self = @fieldParentPtr(Self, "allocator", allocator);
28 self.out_stream.print("alloc : {}", .{len}) catch {};34 self.out_stream.print("alloc : {}", .{len}) catch {};
29 const result = self.parent_allocator.callAllocFn(len, ptr_align, len_align);35 const result = self.parent_allocator.allocFn(self.parent_allocator, len, ptr_align, len_align, ra);
30 if (result) |buff| {36 if (result) |buff| {
31 self.out_stream.print(" success!\n", .{}) catch {};37 self.out_stream.print(" success!\n", .{}) catch {};
32 } else |err| {38 } else |err| {
...@@ -35,7 +41,14 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {...@@ -35,7 +41,14 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
35 return result;41 return result;
36 }42 }
3743
38 fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize {44 fn resize(
45 allocator: *Allocator,
46 buf: []u8,
47 buf_align: u29,
48 new_len: usize,
49 len_align: u29,
50 ra: usize,
51 ) error{OutOfMemory}!usize {
39 const self = @fieldParentPtr(Self, "allocator", allocator);52 const self = @fieldParentPtr(Self, "allocator", allocator);
40 if (new_len == 0) {53 if (new_len == 0) {
41 self.out_stream.print("free : {}\n", .{buf.len}) catch {};54 self.out_stream.print("free : {}\n", .{buf.len}) catch {};
...@@ -44,7 +57,7 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {...@@ -44,7 +57,7 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
44 } else {57 } else {
45 self.out_stream.print("expand: {} to {}", .{ buf.len, new_len }) catch {};58 self.out_stream.print("expand: {} to {}", .{ buf.len, new_len }) catch {};
46 }59 }
47 if (self.parent_allocator.callResizeFn(buf, new_len, len_align)) |resized_len| {60 if (self.parent_allocator.resizeFn(self.parent_allocator, buf, buf_align, new_len, len_align, ra)) |resized_len| {
48 if (new_len > buf.len) {61 if (new_len > buf.len) {
49 self.out_stream.print(" success!\n", .{}) catch {};62 self.out_stream.print(" success!\n", .{}) catch {};
50 }63 }
...@@ -74,9 +87,9 @@ test "LoggingAllocator" {...@@ -74,9 +87,9 @@ test "LoggingAllocator" {
74 const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator;87 const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator;
7588
76 var a = try allocator.alloc(u8, 10);89 var a = try allocator.alloc(u8, 10);
77 a.len = allocator.shrinkBytes(a, 5, 0);90 a = allocator.shrink(a, 5);
78 std.debug.assert(a.len == 5);91 std.debug.assert(a.len == 5);
79 std.testing.expectError(error.OutOfMemory, allocator.callResizeFn(a, 20, 0));92 std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
80 allocator.free(a);93 allocator.free(a);
8194
82 std.testing.expectEqualSlices(u8,95 std.testing.expectEqualSlices(u8,
lib/std/math.zig+4
...@@ -837,6 +837,10 @@ pub fn ceilPowerOfTwo(comptime T: type, value: T) (error{Overflow}!T) {...@@ -837,6 +837,10 @@ pub fn ceilPowerOfTwo(comptime T: type, value: T) (error{Overflow}!T) {
837 return @intCast(T, x);837 return @intCast(T, x);
838}838}
839839
840pub fn ceilPowerOfTwoAssert(comptime T: type, value: T) T {
841 return ceilPowerOfTwo(T, value) catch unreachable;
842}
843
840test "math.ceilPowerOfTwoPromote" {844test "math.ceilPowerOfTwoPromote" {
841 testCeilPowerOfTwoPromote();845 testCeilPowerOfTwoPromote();
842 comptime testCeilPowerOfTwoPromote();846 comptime testCeilPowerOfTwoPromote();
lib/std/mem.zig+22-385
...@@ -8,391 +8,13 @@ const meta = std.meta;...@@ -8,391 +8,13 @@ const meta = std.meta;
8const trait = meta.trait;8const trait = meta.trait;
9const testing = std.testing;9const testing = std.testing;
1010
11// https://github.com/ziglang/zig/issues/256411/// https://github.com/ziglang/zig/issues/2564
12pub const page_size = switch (builtin.arch) {12pub const page_size = switch (builtin.arch) {
13 .wasm32, .wasm64 => 64 * 1024,13 .wasm32, .wasm64 => 64 * 1024,
14 else => 4 * 1024,14 else => 4 * 1024,
15};15};
1616
17pub const Allocator = struct {17pub const Allocator = @import("mem/Allocator.zig");
18 pub const Error = error{OutOfMemory};
19
20 /// Attempt to allocate at least `len` bytes aligned to `ptr_align`.
21 ///
22 /// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes,
23 /// otherwise, the length must be aligned to `len_align`.
24 ///
25 /// `len` must be greater than or equal to `len_align` and must be aligned by `len_align`.
26 allocFn: fn (self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Error![]u8,
27
28 /// Attempt to expand or shrink memory in place. `buf.len` must equal the most recent
29 /// length returned by `allocFn` or `resizeFn`.
30 ///
31 /// Passing a `new_len` of 0 frees and invalidates the buffer such that it can no
32 /// longer be passed to `resizeFn`.
33 ///
34 /// error.OutOfMemory can only be returned if `new_len` is greater than `buf.len`.
35 /// If `buf` cannot be expanded to accomodate `new_len`, then the allocation MUST be
36 /// unmodified and error.OutOfMemory MUST be returned.
37 ///
38 /// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes,
39 /// otherwise, the length must be aligned to `len_align`.
40 ///
41 /// `new_len` must be greater than or equal to `len_align` and must be aligned by `len_align`.
42 resizeFn: fn (self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize,
43
44 pub fn callAllocFn(self: *Allocator, new_len: usize, alignment: u29, len_align: u29) Error![]u8 {
45 return self.allocFn(self, new_len, alignment, len_align);
46 }
47
48 pub fn callResizeFn(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize {
49 return self.resizeFn(self, buf, new_len, len_align);
50 }
51
52 /// Set to resizeFn if in-place resize is not supported.
53 pub fn noResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize {
54 if (new_len > buf.len)
55 return error.OutOfMemory;
56 return new_len;
57 }
58
59 /// Call `resizeFn`, but caller guarantees that `new_len` <= `buf.len` meaning
60 /// error.OutOfMemory should be impossible.
61 pub fn shrinkBytes(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) usize {
62 assert(new_len <= buf.len);
63 return self.callResizeFn(buf, new_len, len_align) catch unreachable;
64 }
65
66 /// Realloc is used to modify the size or alignment of an existing allocation,
67 /// as well as to provide the allocator with an opportunity to move an allocation
68 /// to a better location.
69 /// When the size/alignment is greater than the previous allocation, this function
70 /// returns `error.OutOfMemory` when the requested new allocation could not be granted.
71 /// When the size/alignment is less than or equal to the previous allocation,
72 /// this function returns `error.OutOfMemory` when the allocator decides the client
73 /// would be better off keeping the extra alignment/size. Clients will call
74 /// `callResizeFn` when they require the allocator to track a new alignment/size,
75 /// and so this function should only return success when the allocator considers
76 /// the reallocation desirable from the allocator's perspective.
77 /// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
78 /// reallocation failure, even when `new_n` <= `old_mem.len`. A `FixedBufferAllocator`
79 /// would always return `error.OutOfMemory` for `reallocFn` when the size/alignment
80 /// is less than or equal to the old allocation, because it cannot reclaim the memory,
81 /// and thus the `std.ArrayList` would be better off retaining its capacity.
82 /// When `reallocFn` returns,
83 /// `return_value[0..min(old_mem.len, new_byte_count)]` must be the same
84 /// as `old_mem` was when `reallocFn` is called. The bytes of
85 /// `return_value[old_mem.len..]` have undefined values.
86 /// The returned slice must have its pointer aligned at least to `new_alignment` bytes.
87 fn reallocBytes(
88 self: *Allocator,
89 /// Guaranteed to be the same as what was returned from most recent call to
90 /// `allocFn` or `resizeFn`.
91 /// If `old_mem.len == 0` then this is a new allocation and `new_byte_count`
92 /// is guaranteed to be >= 1.
93 old_mem: []u8,
94 /// If `old_mem.len == 0` then this is `undefined`, otherwise:
95 /// Guaranteed to be the same as what was passed to `allocFn`.
96 /// Guaranteed to be >= 1.
97 /// Guaranteed to be a power of 2.
98 old_alignment: u29,
99 /// If `new_byte_count` is 0 then this is a free and it is guaranteed that
100 /// `old_mem.len != 0`.
101 new_byte_count: usize,
102 /// Guaranteed to be >= 1.
103 /// Guaranteed to be a power of 2.
104 /// Returned slice's pointer must have this alignment.
105 new_alignment: u29,
106 /// 0 indicates the length of the slice returned MUST match `new_byte_count` exactly
107 /// non-zero means the length of the returned slice must be aligned by `len_align`
108 /// `new_len` must be aligned by `len_align`
109 len_align: u29,
110 ) Error![]u8 {
111 if (old_mem.len == 0) {
112 const new_mem = try self.callAllocFn(new_byte_count, new_alignment, len_align);
113 @memset(new_mem.ptr, undefined, new_byte_count);
114 return new_mem;
115 }
116
117 if (isAligned(@ptrToInt(old_mem.ptr), new_alignment)) {
118 if (new_byte_count <= old_mem.len) {
119 const shrunk_len = self.shrinkBytes(old_mem, new_byte_count, len_align);
120 return old_mem.ptr[0..shrunk_len];
121 }
122 if (self.callResizeFn(old_mem, new_byte_count, len_align)) |resized_len| {
123 assert(resized_len >= new_byte_count);
124 @memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count);
125 return old_mem.ptr[0..resized_len];
126 } else |_| {}
127 }
128 if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) {
129 return error.OutOfMemory;
130 }
131 return self.moveBytes(old_mem, new_byte_count, new_alignment, len_align);
132 }
133
134 /// Move the given memory to a new location in the given allocator to accomodate a new
135 /// size and alignment.
136 fn moveBytes(self: *Allocator, old_mem: []u8, new_len: usize, new_alignment: u29, len_align: u29) Error![]u8 {
137 assert(old_mem.len > 0);
138 assert(new_len > 0);
139 const new_mem = try self.callAllocFn(new_len, new_alignment, len_align);
140 @memcpy(new_mem.ptr, old_mem.ptr, std.math.min(new_len, old_mem.len));
141 // DISABLED TO AVOID BUGS IN TRANSLATE C
142 // use './zig build test-translate-c' to reproduce, some of the symbols in the
143 // generated C code will be a sequence of 0xaa (the undefined value), meaning
144 // it is printing data that has been freed
145 //@memset(old_mem.ptr, undefined, old_mem.len);
146 _ = self.shrinkBytes(old_mem, 0, 0);
147 return new_mem;
148 }
149
150 /// Returns a pointer to undefined memory.
151 /// Call `destroy` with the result to free the memory.
152 pub fn create(self: *Allocator, comptime T: type) Error!*T {
153 if (@sizeOf(T) == 0) return &(T{});
154 const slice = try self.alloc(T, 1);
155 return &slice[0];
156 }
157
158 /// `ptr` should be the return value of `create`, or otherwise
159 /// have the same address and alignment property.
160 pub fn destroy(self: *Allocator, ptr: anytype) void {
161 const T = @TypeOf(ptr).Child;
162 if (@sizeOf(T) == 0) return;
163 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
164 _ = self.shrinkBytes(non_const_ptr[0..@sizeOf(T)], 0, 0);
165 }
166
167 /// Allocates an array of `n` items of type `T` and sets all the
168 /// items to `undefined`. Depending on the Allocator
169 /// implementation, it may be required to call `free` once the
170 /// memory is no longer needed, to avoid a resource leak. If the
171 /// `Allocator` implementation is unknown, then correct code will
172 /// call `free` when done.
173 ///
174 /// For allocating a single item, see `create`.
175 pub fn alloc(self: *Allocator, comptime T: type, n: usize) Error![]T {
176 return self.alignedAlloc(T, null, n);
177 }
178
179 pub fn allocWithOptions(
180 self: *Allocator,
181 comptime Elem: type,
182 n: usize,
183 /// null means naturally aligned
184 comptime optional_alignment: ?u29,
185 comptime optional_sentinel: ?Elem,
186 ) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
187 if (optional_sentinel) |sentinel| {
188 const ptr = try self.alignedAlloc(Elem, optional_alignment, n + 1);
189 ptr[n] = sentinel;
190 return ptr[0..n :sentinel];
191 } else {
192 return self.alignedAlloc(Elem, optional_alignment, n);
193 }
194 }
195
196 fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?u29, comptime sentinel: ?Elem) type {
197 if (sentinel) |s| {
198 return [:s]align(alignment orelse @alignOf(Elem)) Elem;
199 } else {
200 return []align(alignment orelse @alignOf(Elem)) Elem;
201 }
202 }
203
204 /// Allocates an array of `n + 1` items of type `T` and sets the first `n`
205 /// items to `undefined` and the last item to `sentinel`. Depending on the
206 /// Allocator implementation, it may be required to call `free` once the
207 /// memory is no longer needed, to avoid a resource leak. If the
208 /// `Allocator` implementation is unknown, then correct code will
209 /// call `free` when done.
210 ///
211 /// For allocating a single item, see `create`.
212 ///
213 /// Deprecated; use `allocWithOptions`.
214 pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem {
215 return self.allocWithOptions(Elem, n, null, sentinel);
216 }
217
218 /// Deprecated: use `allocAdvanced`
219 pub fn alignedAlloc(
220 self: *Allocator,
221 comptime T: type,
222 /// null means naturally aligned
223 comptime alignment: ?u29,
224 n: usize,
225 ) Error![]align(alignment orelse @alignOf(T)) T {
226 return self.allocAdvanced(T, alignment, n, .exact);
227 }
228
229 const Exact = enum { exact, at_least };
230 pub fn allocAdvanced(
231 self: *Allocator,
232 comptime T: type,
233 /// null means naturally aligned
234 comptime alignment: ?u29,
235 n: usize,
236 exact: Exact,
237 ) Error![]align(alignment orelse @alignOf(T)) T {
238 const a = if (alignment) |a| blk: {
239 if (a == @alignOf(T)) return allocAdvanced(self, T, null, n, exact);
240 break :blk a;
241 } else @alignOf(T);
242
243 if (n == 0) {
244 return @as([*]align(a) T, undefined)[0..0];
245 }
246
247 const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
248 // TODO The `if (alignment == null)` blocks are workarounds for zig not being able to
249 // access certain type information about T without creating a circular dependency in async
250 // functions that heap-allocate their own frame with @Frame(func).
251 const sizeOfT = if (alignment == null) @intCast(u29, @divExact(byte_count, n)) else @sizeOf(T);
252 const byte_slice = try self.callAllocFn(byte_count, a, if (exact == .exact) @as(u29, 0) else sizeOfT);
253 switch (exact) {
254 .exact => assert(byte_slice.len == byte_count),
255 .at_least => assert(byte_slice.len >= byte_count),
256 }
257 @memset(byte_slice.ptr, undefined, byte_slice.len);
258 if (alignment == null) {
259 // This if block is a workaround (see comment above)
260 return @intToPtr([*]T, @ptrToInt(byte_slice.ptr))[0..@divExact(byte_slice.len, @sizeOf(T))];
261 } else {
262 return mem.bytesAsSlice(T, @alignCast(a, byte_slice));
263 }
264 }
265
266 /// This function requests a new byte size for an existing allocation,
267 /// which can be larger, smaller, or the same size as the old memory
268 /// allocation.
269 /// This function is preferred over `shrink`, because it can fail, even
270 /// when shrinking. This gives the allocator a chance to perform a
271 /// cheap shrink operation if possible, or otherwise return OutOfMemory,
272 /// indicating that the caller should keep their capacity, for example
273 /// in `std.ArrayList.shrink`.
274 /// If you need guaranteed success, call `shrink`.
275 /// If `new_n` is 0, this is the same as `free` and it always succeeds.
276 pub fn realloc(self: *Allocator, old_mem: anytype, new_n: usize) t: {
277 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
278 break :t Error![]align(Slice.alignment) Slice.child;
279 } {
280 const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment;
281 return self.reallocAdvanced(old_mem, old_alignment, new_n, .exact);
282 }
283
284 pub fn reallocAtLeast(self: *Allocator, old_mem: anytype, new_n: usize) t: {
285 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
286 break :t Error![]align(Slice.alignment) Slice.child;
287 } {
288 const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment;
289 return self.reallocAdvanced(old_mem, old_alignment, new_n, .at_least);
290 }
291
292 // Deprecated: use `reallocAdvanced`
293 pub fn alignedRealloc(
294 self: *Allocator,
295 old_mem: anytype,
296 comptime new_alignment: u29,
297 new_n: usize,
298 ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
299 return self.reallocAdvanced(old_mem, new_alignment, new_n, .exact);
300 }
301
302 /// This is the same as `realloc`, except caller may additionally request
303 /// a new alignment, which can be larger, smaller, or the same as the old
304 /// allocation.
305 pub fn reallocAdvanced(
306 self: *Allocator,
307 old_mem: anytype,
308 comptime new_alignment: u29,
309 new_n: usize,
310 exact: Exact,
311 ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
312 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
313 const T = Slice.child;
314 if (old_mem.len == 0) {
315 return self.allocAdvanced(T, new_alignment, new_n, exact);
316 }
317 if (new_n == 0) {
318 self.free(old_mem);
319 return @as([*]align(new_alignment) T, undefined)[0..0];
320 }
321
322 const old_byte_slice = mem.sliceAsBytes(old_mem);
323 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory;
324 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
325 const new_byte_slice = try self.reallocBytes(old_byte_slice, Slice.alignment, byte_count, new_alignment, if (exact == .exact) @as(u29, 0) else @sizeOf(T));
326 return mem.bytesAsSlice(T, @alignCast(new_alignment, new_byte_slice));
327 }
328
329 /// Prefer calling realloc to shrink if you can tolerate failure, such as
330 /// in an ArrayList data structure with a storage capacity.
331 /// Shrink always succeeds, and `new_n` must be <= `old_mem.len`.
332 /// Returned slice has same alignment as old_mem.
333 /// Shrinking to 0 is the same as calling `free`.
334 pub fn shrink(self: *Allocator, old_mem: anytype, new_n: usize) t: {
335 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
336 break :t []align(Slice.alignment) Slice.child;
337 } {
338 const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment;
339 return self.alignedShrink(old_mem, old_alignment, new_n);
340 }
341
342 /// This is the same as `shrink`, except caller may additionally request
343 /// a new alignment, which must be smaller or the same as the old
344 /// allocation.
345 pub fn alignedShrink(
346 self: *Allocator,
347 old_mem: anytype,
348 comptime new_alignment: u29,
349 new_n: usize,
350 ) []align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
351 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
352 const T = Slice.child;
353
354 if (new_n == old_mem.len)
355 return old_mem;
356 assert(new_n < old_mem.len);
357 assert(new_alignment <= Slice.alignment);
358
359 // Here we skip the overflow checking on the multiplication because
360 // new_n <= old_mem.len and the multiplication didn't overflow for that operation.
361 const byte_count = @sizeOf(T) * new_n;
362
363 const old_byte_slice = mem.sliceAsBytes(old_mem);
364 @memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count);
365 _ = self.shrinkBytes(old_byte_slice, byte_count, 0);
366 return old_mem[0..new_n];
367 }
368
369 /// Free an array allocated with `alloc`. To free a single item,
370 /// see `destroy`.
371 pub fn free(self: *Allocator, memory: anytype) void {
372 const Slice = @typeInfo(@TypeOf(memory)).Pointer;
373 const bytes = mem.sliceAsBytes(memory);
374 const bytes_len = bytes.len + if (Slice.sentinel != null) @sizeOf(Slice.child) else 0;
375 if (bytes_len == 0) return;
376 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
377 @memset(non_const_ptr, undefined, bytes_len);
378 _ = self.shrinkBytes(non_const_ptr[0..bytes_len], 0, 0);
379 }
380
381 /// Copies `m` to newly allocated memory. Caller owns the memory.
382 pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
383 const new_buf = try allocator.alloc(T, m.len);
384 copy(T, new_buf, m);
385 return new_buf;
386 }
387
388 /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
389 pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
390 const new_buf = try allocator.alloc(T, m.len + 1);
391 copy(T, new_buf, m);
392 new_buf[m.len] = 0;
393 return new_buf[0..m.len :0];
394 }
395};
39618
397/// Detects and asserts if the std.mem.Allocator interface is violated by the caller19/// Detects and asserts if the std.mem.Allocator interface is violated by the caller
398/// or the allocator.20/// or the allocator.
...@@ -415,7 +37,13 @@ pub fn ValidationAllocator(comptime T: type) type {...@@ -415,7 +37,13 @@ pub fn ValidationAllocator(comptime T: type) type {
415 if (*T == *Allocator) return &self.underlying_allocator;37 if (*T == *Allocator) return &self.underlying_allocator;
416 return &self.underlying_allocator.allocator;38 return &self.underlying_allocator.allocator;
417 }39 }
418 pub fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 {40 pub fn alloc(
41 allocator: *Allocator,
42 n: usize,
43 ptr_align: u29,
44 len_align: u29,
45 ret_addr: usize,
46 ) Allocator.Error![]u8 {
419 assert(n > 0);47 assert(n > 0);
420 assert(mem.isValidAlign(ptr_align));48 assert(mem.isValidAlign(ptr_align));
421 if (len_align != 0) {49 if (len_align != 0) {
...@@ -424,7 +52,8 @@ pub fn ValidationAllocator(comptime T: type) type {...@@ -424,7 +52,8 @@ pub fn ValidationAllocator(comptime T: type) type {
424 }52 }
42553
426 const self = @fieldParentPtr(@This(), "allocator", allocator);54 const self = @fieldParentPtr(@This(), "allocator", allocator);
427 const result = try self.getUnderlyingAllocatorPtr().callAllocFn(n, ptr_align, len_align);55 const underlying = self.getUnderlyingAllocatorPtr();
56 const result = try underlying.allocFn(underlying, n, ptr_align, len_align, ret_addr);
428 assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align));57 assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align));
429 if (len_align == 0) {58 if (len_align == 0) {
430 assert(result.len == n);59 assert(result.len == n);
...@@ -434,14 +63,22 @@ pub fn ValidationAllocator(comptime T: type) type {...@@ -434,14 +63,22 @@ pub fn ValidationAllocator(comptime T: type) type {
434 }63 }
435 return result;64 return result;
436 }65 }
437 pub fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize {66 pub fn resize(
67 allocator: *Allocator,
68 buf: []u8,
69 buf_align: u29,
70 new_len: usize,
71 len_align: u29,
72 ret_addr: usize,
73 ) Allocator.Error!usize {
438 assert(buf.len > 0);74 assert(buf.len > 0);
439 if (len_align != 0) {75 if (len_align != 0) {
440 assert(mem.isAlignedAnyAlign(new_len, len_align));76 assert(mem.isAlignedAnyAlign(new_len, len_align));
441 assert(new_len >= len_align);77 assert(new_len >= len_align);
442 }78 }
443 const self = @fieldParentPtr(@This(), "allocator", allocator);79 const self = @fieldParentPtr(@This(), "allocator", allocator);
444 const result = try self.getUnderlyingAllocatorPtr().callResizeFn(buf, new_len, len_align);80 const underlying = self.getUnderlyingAllocatorPtr();
81 const result = try underlying.resizeFn(underlying, buf, buf_align, new_len, len_align, ret_addr);
445 if (len_align == 0) {82 if (len_align == 0) {
446 assert(result == new_len);83 assert(result == new_len);
447 } else {84 } else {
...@@ -481,7 +118,7 @@ var failAllocator = Allocator{...@@ -481,7 +118,7 @@ var failAllocator = Allocator{
481 .allocFn = failAllocatorAlloc,118 .allocFn = failAllocatorAlloc,
482 .resizeFn = Allocator.noResize,119 .resizeFn = Allocator.noResize,
483};120};
484fn failAllocatorAlloc(self: *Allocator, n: usize, alignment: u29, len_align: u29) Allocator.Error![]u8 {121fn failAllocatorAlloc(self: *Allocator, n: usize, alignment: u29, len_align: u29, ra: usize) Allocator.Error![]u8 {
485 return error.OutOfMemory;122 return error.OutOfMemory;
486}123}
487124
lib/std/mem/Allocator.zig created+486
...@@ -0,0 +1,486 @@
1//! The standard memory allocation interface.
2
3const std = @import("../std.zig");
4const assert = std.debug.assert;
5const math = std.math;
6const mem = std.mem;
7const Allocator = @This();
8
9pub const Error = error{OutOfMemory};
10
11/// Attempt to allocate at least `len` bytes aligned to `ptr_align`.
12///
13/// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes,
14/// otherwise, the length must be aligned to `len_align`.
15///
16/// `len` must be greater than or equal to `len_align` and must be aligned by `len_align`.
17///
18/// `ret_addr` is optionally provided as the first return address of the allocation call stack.
19/// If the value is `0` it means no return address has been provided.
20allocFn: fn (self: *Allocator, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Error![]u8,
21
22/// Attempt to expand or shrink memory in place. `buf.len` must equal the most recent
23/// length returned by `allocFn` or `resizeFn`. `buf_align` must equal the same value
24/// that was passed as the `ptr_align` parameter to the original `allocFn` call.
25///
26/// Passing a `new_len` of 0 frees and invalidates the buffer such that it can no
27/// longer be passed to `resizeFn`.
28///
29/// error.OutOfMemory can only be returned if `new_len` is greater than `buf.len`.
30/// If `buf` cannot be expanded to accomodate `new_len`, then the allocation MUST be
31/// unmodified and error.OutOfMemory MUST be returned.
32///
33/// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes,
34/// otherwise, the length must be aligned to `len_align`. Note that `len_align` does *not*
35/// provide a way to modify the alignment of a pointer. Rather it provides an API for
36/// accepting more bytes of memory from the allocator than requested.
37///
38/// `new_len` must be greater than or equal to `len_align` and must be aligned by `len_align`.
39///
40/// `ret_addr` is optionally provided as the first return address of the allocation call stack.
41/// If the value is `0` it means no return address has been provided.
42resizeFn: fn (self: *Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) Error!usize,
43
44/// Set to resizeFn if in-place resize is not supported.
45pub fn noResize(
46 self: *Allocator,
47 buf: []u8,
48 buf_align: u29,
49 new_len: usize,
50 len_align: u29,
51 ret_addr: usize,
52) Error!usize {
53 if (new_len > buf.len)
54 return error.OutOfMemory;
55 return new_len;
56}
57
58/// Realloc is used to modify the size or alignment of an existing allocation,
59/// as well as to provide the allocator with an opportunity to move an allocation
60/// to a better location.
61/// When the size/alignment is greater than the previous allocation, this function
62/// returns `error.OutOfMemory` when the requested new allocation could not be granted.
63/// When the size/alignment is less than or equal to the previous allocation,
64/// this function returns `error.OutOfMemory` when the allocator decides the client
65/// would be better off keeping the extra alignment/size. Clients will call
66/// `resizeFn` when they require the allocator to track a new alignment/size,
67/// and so this function should only return success when the allocator considers
68/// the reallocation desirable from the allocator's perspective.
69/// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
70/// reallocation failure, even when `new_n` <= `old_mem.len`. A `FixedBufferAllocator`
71/// would always return `error.OutOfMemory` for `reallocFn` when the size/alignment
72/// is less than or equal to the old allocation, because it cannot reclaim the memory,
73/// and thus the `std.ArrayList` would be better off retaining its capacity.
74/// When `reallocFn` returns,
75/// `return_value[0..min(old_mem.len, new_byte_count)]` must be the same
76/// as `old_mem` was when `reallocFn` is called. The bytes of
77/// `return_value[old_mem.len..]` have undefined values.
78/// The returned slice must have its pointer aligned at least to `new_alignment` bytes.
79fn reallocBytes(
80 self: *Allocator,
81 /// Guaranteed to be the same as what was returned from most recent call to
82 /// `allocFn` or `resizeFn`.
83 /// If `old_mem.len == 0` then this is a new allocation and `new_byte_count`
84 /// is guaranteed to be >= 1.
85 old_mem: []u8,
86 /// If `old_mem.len == 0` then this is `undefined`, otherwise:
87 /// Guaranteed to be the same as what was passed to `allocFn`.
88 /// Guaranteed to be >= 1.
89 /// Guaranteed to be a power of 2.
90 old_alignment: u29,
91 /// If `new_byte_count` is 0 then this is a free and it is guaranteed that
92 /// `old_mem.len != 0`.
93 new_byte_count: usize,
94 /// Guaranteed to be >= 1.
95 /// Guaranteed to be a power of 2.
96 /// Returned slice's pointer must have this alignment.
97 new_alignment: u29,
98 /// 0 indicates the length of the slice returned MUST match `new_byte_count` exactly
99 /// non-zero means the length of the returned slice must be aligned by `len_align`
100 /// `new_len` must be aligned by `len_align`
101 len_align: u29,
102 return_address: usize,
103) Error![]u8 {
104 if (old_mem.len == 0) {
105 const new_mem = try self.allocFn(self, new_byte_count, new_alignment, len_align, return_address);
106 // TODO: https://github.com/ziglang/zig/issues/4298
107 @memset(new_mem.ptr, undefined, new_byte_count);
108 return new_mem;
109 }
110
111 if (mem.isAligned(@ptrToInt(old_mem.ptr), new_alignment)) {
112 if (new_byte_count <= old_mem.len) {
113 const shrunk_len = self.shrinkBytes(old_mem, old_alignment, new_byte_count, len_align, return_address);
114 return old_mem.ptr[0..shrunk_len];
115 }
116 if (self.resizeFn(self, old_mem, old_alignment, new_byte_count, len_align, return_address)) |resized_len| {
117 assert(resized_len >= new_byte_count);
118 // TODO: https://github.com/ziglang/zig/issues/4298
119 @memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count);
120 return old_mem.ptr[0..resized_len];
121 } else |_| {}
122 }
123 if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) {
124 return error.OutOfMemory;
125 }
126 return self.moveBytes(old_mem, old_alignment, new_byte_count, new_alignment, len_align, return_address);
127}
128
129/// Move the given memory to a new location in the given allocator to accomodate a new
130/// size and alignment.
131fn moveBytes(
132 self: *Allocator,
133 old_mem: []u8,
134 old_align: u29,
135 new_len: usize,
136 new_alignment: u29,
137 len_align: u29,
138 return_address: usize,
139) Error![]u8 {
140 assert(old_mem.len > 0);
141 assert(new_len > 0);
142 const new_mem = try self.allocFn(self, new_len, new_alignment, len_align, return_address);
143 @memcpy(new_mem.ptr, old_mem.ptr, math.min(new_len, old_mem.len));
144 // TODO DISABLED TO AVOID BUGS IN TRANSLATE C
145 // TODO see also https://github.com/ziglang/zig/issues/4298
146 // use './zig build test-translate-c' to reproduce, some of the symbols in the
147 // generated C code will be a sequence of 0xaa (the undefined value), meaning
148 // it is printing data that has been freed
149 //@memset(old_mem.ptr, undefined, old_mem.len);
150 _ = self.shrinkBytes(old_mem, old_align, 0, 0, return_address);
151 return new_mem;
152}
153
154/// Returns a pointer to undefined memory.
155/// Call `destroy` with the result to free the memory.
156pub fn create(self: *Allocator, comptime T: type) Error!*T {
157 if (@sizeOf(T) == 0) return &(T{});
158 const slice = try self.allocAdvancedWithRetAddr(T, null, 1, .exact, @returnAddress());
159 return &slice[0];
160}
161
162/// `ptr` should be the return value of `create`, or otherwise
163/// have the same address and alignment property.
164pub fn destroy(self: *Allocator, ptr: anytype) void {
165 const T = @TypeOf(ptr).Child;
166 if (@sizeOf(T) == 0) return;
167 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
168 const ptr_align = @typeInfo(@TypeOf(ptr)).Pointer.alignment;
169 _ = self.shrinkBytes(non_const_ptr[0..@sizeOf(T)], ptr_align, 0, 0, @returnAddress());
170}
171
172/// Allocates an array of `n` items of type `T` and sets all the
173/// items to `undefined`. Depending on the Allocator
174/// implementation, it may be required to call `free` once the
175/// memory is no longer needed, to avoid a resource leak. If the
176/// `Allocator` implementation is unknown, then correct code will
177/// call `free` when done.
178///
179/// For allocating a single item, see `create`.
180pub fn alloc(self: *Allocator, comptime T: type, n: usize) Error![]T {
181 return self.allocAdvancedWithRetAddr(T, null, n, .exact, @returnAddress());
182}
183
184pub fn allocWithOptions(
185 self: *Allocator,
186 comptime Elem: type,
187 n: usize,
188 /// null means naturally aligned
189 comptime optional_alignment: ?u29,
190 comptime optional_sentinel: ?Elem,
191) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
192 return self.allocWithOptionsRetAddr(Elem, n, optional_alignment, optional_sentinel, @returnAddress());
193}
194
195pub fn allocWithOptionsRetAddr(
196 self: *Allocator,
197 comptime Elem: type,
198 n: usize,
199 /// null means naturally aligned
200 comptime optional_alignment: ?u29,
201 comptime optional_sentinel: ?Elem,
202 return_address: usize,
203) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
204 if (optional_sentinel) |sentinel| {
205 const ptr = try self.allocAdvancedWithRetAddr(Elem, optional_alignment, n + 1, .exact, return_address);
206 ptr[n] = sentinel;
207 return ptr[0..n :sentinel];
208 } else {
209 return self.allocAdvancedWithRetAddr(Elem, optional_alignment, n, .exact, return_address);
210 }
211}
212
213fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?u29, comptime sentinel: ?Elem) type {
214 if (sentinel) |s| {
215 return [:s]align(alignment orelse @alignOf(Elem)) Elem;
216 } else {
217 return []align(alignment orelse @alignOf(Elem)) Elem;
218 }
219}
220
221/// Allocates an array of `n + 1` items of type `T` and sets the first `n`
222/// items to `undefined` and the last item to `sentinel`. Depending on the
223/// Allocator implementation, it may be required to call `free` once the
224/// memory is no longer needed, to avoid a resource leak. If the
225/// `Allocator` implementation is unknown, then correct code will
226/// call `free` when done.
227///
228/// For allocating a single item, see `create`.
229///
230/// Deprecated; use `allocWithOptions`.
231pub fn allocSentinel(
232 self: *Allocator,
233 comptime Elem: type,
234 n: usize,
235 comptime sentinel: Elem,
236) Error![:sentinel]Elem {
237 return self.allocWithOptionsRetAddr(Elem, n, null, sentinel, @returnAddress());
238}
239
240/// Deprecated: use `allocAdvanced`
241pub fn alignedAlloc(
242 self: *Allocator,
243 comptime T: type,
244 /// null means naturally aligned
245 comptime alignment: ?u29,
246 n: usize,
247) Error![]align(alignment orelse @alignOf(T)) T {
248 return self.allocAdvancedWithRetAddr(T, alignment, n, .exact, @returnAddress());
249}
250
251pub fn allocAdvanced(
252 self: *Allocator,
253 comptime T: type,
254 /// null means naturally aligned
255 comptime alignment: ?u29,
256 n: usize,
257 exact: Exact,
258) Error![]align(alignment orelse @alignOf(T)) T {
259 return self.allocAdvancedWithRetAddr(T, alignment, n, exact, @returnAddress());
260}
261
262pub const Exact = enum { exact, at_least };
263
264pub fn allocAdvancedWithRetAddr(
265 self: *Allocator,
266 comptime T: type,
267 /// null means naturally aligned
268 comptime alignment: ?u29,
269 n: usize,
270 exact: Exact,
271 return_address: usize,
272) Error![]align(alignment orelse @alignOf(T)) T {
273 const a = if (alignment) |a| blk: {
274 if (a == @alignOf(T)) return allocAdvancedWithRetAddr(self, T, null, n, exact, return_address);
275 break :blk a;
276 } else @alignOf(T);
277
278 if (n == 0) {
279 return @as([*]align(a) T, undefined)[0..0];
280 }
281
282 const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
283 // TODO The `if (alignment == null)` blocks are workarounds for zig not being able to
284 // access certain type information about T without creating a circular dependency in async
285 // functions that heap-allocate their own frame with @Frame(func).
286 const size_of_T = if (alignment == null) @intCast(u29, @divExact(byte_count, n)) else @sizeOf(T);
287 const len_align: u29 = switch (exact) {
288 .exact => 0,
289 .at_least => size_of_T,
290 };
291 const byte_slice = try self.allocFn(self, byte_count, a, len_align, return_address);
292 switch (exact) {
293 .exact => assert(byte_slice.len == byte_count),
294 .at_least => assert(byte_slice.len >= byte_count),
295 }
296 // TODO: https://github.com/ziglang/zig/issues/4298
297 @memset(byte_slice.ptr, undefined, byte_slice.len);
298 if (alignment == null) {
299 // This if block is a workaround (see comment above)
300 return @intToPtr([*]T, @ptrToInt(byte_slice.ptr))[0..@divExact(byte_slice.len, @sizeOf(T))];
301 } else {
302 return mem.bytesAsSlice(T, @alignCast(a, byte_slice));
303 }
304}
305
306/// Increases or decreases the size of an allocation. It is guaranteed to not move the pointer.
307pub fn resize(self: *Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(old_mem) {
308 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
309 const T = Slice.child;
310 if (new_n == 0) {
311 self.free(old_mem);
312 return &[0]T{};
313 }
314 const old_byte_slice = mem.sliceAsBytes(old_mem);
315 const new_byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory;
316 const rc = try self.resizeFn(self, old_byte_slice, Slice.alignment, new_byte_count, 0, @returnAddress());
317 assert(rc == new_byte_count);
318 const new_byte_slice = old_mem.ptr[0..new_byte_count];
319 return mem.bytesAsSlice(T, new_byte_slice);
320}
321
322/// This function requests a new byte size for an existing allocation,
323/// which can be larger, smaller, or the same size as the old memory
324/// allocation.
325/// This function is preferred over `shrink`, because it can fail, even
326/// when shrinking. This gives the allocator a chance to perform a
327/// cheap shrink operation if possible, or otherwise return OutOfMemory,
328/// indicating that the caller should keep their capacity, for example
329/// in `std.ArrayList.shrink`.
330/// If you need guaranteed success, call `shrink`.
331/// If `new_n` is 0, this is the same as `free` and it always succeeds.
332pub fn realloc(self: *Allocator, old_mem: anytype, new_n: usize) t: {
333 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
334 break :t Error![]align(Slice.alignment) Slice.child;
335} {
336 const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment;
337 return self.reallocAdvancedWithRetAddr(old_mem, old_alignment, new_n, .exact, @returnAddress());
338}
339
340pub fn reallocAtLeast(self: *Allocator, old_mem: anytype, new_n: usize) t: {
341 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
342 break :t Error![]align(Slice.alignment) Slice.child;
343} {
344 const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment;
345 return self.reallocAdvancedWithRetAddr(old_mem, old_alignment, new_n, .at_least, @returnAddress());
346}
347
348/// This is the same as `realloc`, except caller may additionally request
349/// a new alignment, which can be larger, smaller, or the same as the old
350/// allocation.
351pub fn reallocAdvanced(
352 self: *Allocator,
353 old_mem: anytype,
354 comptime new_alignment: u29,
355 new_n: usize,
356 exact: Exact,
357) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
358 return self.reallocAdvancedWithRetAddr(old_mem, new_alignment, new_n, exact, @returnAddress());
359}
360
361pub fn reallocAdvancedWithRetAddr(
362 self: *Allocator,
363 old_mem: anytype,
364 comptime new_alignment: u29,
365 new_n: usize,
366 exact: Exact,
367 return_address: usize,
368) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
369 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
370 const T = Slice.child;
371 if (old_mem.len == 0) {
372 return self.allocAdvanced(T, new_alignment, new_n, exact);
373 }
374 if (new_n == 0) {
375 self.free(old_mem);
376 return @as([*]align(new_alignment) T, undefined)[0..0];
377 }
378
379 const old_byte_slice = mem.sliceAsBytes(old_mem);
380 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory;
381 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
382 const len_align: u29 = switch (exact) {
383 .exact => 0,
384 .at_least => @sizeOf(T),
385 };
386 const new_byte_slice = try self.reallocBytes(old_byte_slice, Slice.alignment, byte_count, new_alignment, len_align, return_address);
387 return mem.bytesAsSlice(T, @alignCast(new_alignment, new_byte_slice));
388}
389
390/// Prefer calling realloc to shrink if you can tolerate failure, such as
391/// in an ArrayList data structure with a storage capacity.
392/// Shrink always succeeds, and `new_n` must be <= `old_mem.len`.
393/// Returned slice has same alignment as old_mem.
394/// Shrinking to 0 is the same as calling `free`.
395pub fn shrink(self: *Allocator, old_mem: anytype, new_n: usize) t: {
396 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
397 break :t []align(Slice.alignment) Slice.child;
398} {
399 const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment;
400 return self.alignedShrinkWithRetAddr(old_mem, old_alignment, new_n, @returnAddress());
401}
402
403/// This is the same as `shrink`, except caller may additionally request
404/// a new alignment, which must be smaller or the same as the old
405/// allocation.
406pub fn alignedShrink(
407 self: *Allocator,
408 old_mem: anytype,
409 comptime new_alignment: u29,
410 new_n: usize,
411) []align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
412 return self.alignedShrinkWithRetAddr(old_mem, new_alignment, new_n, @returnAddress());
413}
414
415/// This is the same as `alignedShrink`, except caller may additionally pass
416/// the return address of the first stack frame, which may be relevant for
417/// allocators which collect stack traces.
418pub fn alignedShrinkWithRetAddr(
419 self: *Allocator,
420 old_mem: anytype,
421 comptime new_alignment: u29,
422 new_n: usize,
423 return_address: usize,
424) []align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child {
425 const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
426 const T = Slice.child;
427
428 if (new_n == old_mem.len)
429 return old_mem;
430 assert(new_n < old_mem.len);
431 assert(new_alignment <= Slice.alignment);
432
433 // Here we skip the overflow checking on the multiplication because
434 // new_n <= old_mem.len and the multiplication didn't overflow for that operation.
435 const byte_count = @sizeOf(T) * new_n;
436
437 const old_byte_slice = mem.sliceAsBytes(old_mem);
438 // TODO: https://github.com/ziglang/zig/issues/4298
439 @memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count);
440 _ = self.shrinkBytes(old_byte_slice, Slice.alignment, byte_count, 0, return_address);
441 return old_mem[0..new_n];
442}
443
444/// Free an array allocated with `alloc`. To free a single item,
445/// see `destroy`.
446pub fn free(self: *Allocator, memory: anytype) void {
447 const Slice = @typeInfo(@TypeOf(memory)).Pointer;
448 const bytes = mem.sliceAsBytes(memory);
449 const bytes_len = bytes.len + if (Slice.sentinel != null) @sizeOf(Slice.child) else 0;
450 if (bytes_len == 0) return;
451 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
452 // TODO: https://github.com/ziglang/zig/issues/4298
453 @memset(non_const_ptr, undefined, bytes_len);
454 _ = self.shrinkBytes(non_const_ptr[0..bytes_len], Slice.alignment, 0, 0, @returnAddress());
455}
456
457/// Copies `m` to newly allocated memory. Caller owns the memory.
458pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
459 const new_buf = try allocator.alloc(T, m.len);
460 mem.copy(T, new_buf, m);
461 return new_buf;
462}
463
464/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
465pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
466 const new_buf = try allocator.alloc(T, m.len + 1);
467 mem.copy(T, new_buf, m);
468 new_buf[m.len] = 0;
469 return new_buf[0..m.len :0];
470}
471
472/// Call `resizeFn`, but caller guarantees that `new_len` <= `buf.len` meaning
473/// error.OutOfMemory should be impossible.
474/// This function allows a runtime `buf_align` value. Callers should generally prefer
475/// to call `shrink` directly.
476pub fn shrinkBytes(
477 self: *Allocator,
478 buf: []u8,
479 buf_align: u29,
480 new_len: usize,
481 len_align: u29,
482 return_address: usize,
483) usize {
484 assert(new_len <= buf.len);
485 return self.resizeFn(self, buf, buf_align, new_len, len_align, return_address) catch unreachable;
486}
lib/std/mutex.zig+127-143
...@@ -15,8 +15,7 @@ const ResetEvent = std.ResetEvent;...@@ -15,8 +15,7 @@ const ResetEvent = std.ResetEvent;
15/// deadlock detection.15/// deadlock detection.
16///16///
17/// Example usage:17/// Example usage:
18/// var m = Mutex.init();18/// var m = Mutex{};
19/// defer m.deinit();
20///19///
21/// const lock = m.acquire();20/// const lock = m.acquire();
22/// defer lock.release();21/// defer lock.release();
...@@ -30,141 +29,13 @@ const ResetEvent = std.ResetEvent;...@@ -30,141 +29,13 @@ const ResetEvent = std.ResetEvent;
30/// // ... lock not acquired29/// // ... lock not acquired
31/// }30/// }
32pub const Mutex = if (builtin.single_threaded)31pub const Mutex = if (builtin.single_threaded)
33 struct {32 Dummy
34 lock: @TypeOf(lock_init),
35
36 const lock_init = if (std.debug.runtime_safety) false else {};
37
38 pub const Held = struct {
39 mutex: *Mutex,
40
41 pub fn release(self: Held) void {
42 if (std.debug.runtime_safety) {
43 self.mutex.lock = false;
44 }
45 }
46 };
47
48 /// Create a new mutex in unlocked state.
49 pub fn init() Mutex {
50 return Mutex{ .lock = lock_init };
51 }
52
53 /// Free a mutex created with init. Calling this while the
54 /// mutex is held is illegal behavior.
55 pub fn deinit(self: *Mutex) void {
56 self.* = undefined;
57 }
58
59 /// Try to acquire the mutex without blocking. Returns null if
60 /// the mutex is unavailable. Otherwise returns Held. Call
61 /// release on Held.
62 pub fn tryAcquire(self: *Mutex) ?Held {
63 if (std.debug.runtime_safety) {
64 if (self.lock) return null;
65 self.lock = true;
66 }
67 return Held{ .mutex = self };
68 }
69
70 /// Acquire the mutex. Will deadlock if the mutex is already
71 /// held by the calling thread.
72 pub fn acquire(self: *Mutex) Held {
73 return self.tryAcquire() orelse @panic("deadlock detected");
74 }
75 }
76else if (builtin.os.tag == .windows)33else if (builtin.os.tag == .windows)
77// https://locklessinc.com/articles/keyed_events/34 WindowsMutex
78 extern union {
79 locked: u8,
80 waiters: u32,
81
82 const WAKE = 1 << 8;
83 const WAIT = 1 << 9;
84
85 pub fn init() Mutex {
86 return Mutex{ .waiters = 0 };
87 }
88
89 pub fn deinit(self: *Mutex) void {
90 self.* = undefined;
91 }
92
93 pub fn tryAcquire(self: *Mutex) ?Held {
94 if (@atomicRmw(u8, &self.locked, .Xchg, 1, .Acquire) != 0)
95 return null;
96 return Held{ .mutex = self };
97 }
98
99 pub fn acquire(self: *Mutex) Held {
100 return self.tryAcquire() orelse self.acquireSlow();
101 }
102
103 fn acquireSpinning(self: *Mutex) Held {
104 @setCold(true);
105 while (true) : (SpinLock.yield()) {
106 return self.tryAcquire() orelse continue;
107 }
108 }
109
110 fn acquireSlow(self: *Mutex) Held {
111 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
112 @setCold(true);
113 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
114 const key = @ptrCast(*const c_void, &self.waiters);
115
116 while (true) : (SpinLock.loopHint(1)) {
117 const waiters = @atomicLoad(u32, &self.waiters, .Monotonic);
118
119 // try and take lock if unlocked
120 if ((waiters & 1) == 0) {
121 if (@atomicRmw(u8, &self.locked, .Xchg, 1, .Acquire) == 0) {
122 return Held{ .mutex = self };
123 }
124
125 // otherwise, try and update the waiting count.
126 // then unset the WAKE bit so that another unlocker can wake up a thread.
127 } else if (@cmpxchgWeak(u32, &self.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) {
128 const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
129 assert(rc == .SUCCESS);
130 _ = @atomicRmw(u32, &self.waiters, .Sub, WAKE, .Monotonic);
131 }
132 }
133 }
134
135 pub const Held = struct {
136 mutex: *Mutex,
137
138 pub fn release(self: Held) void {
139 // unlock without a rmw/cmpxchg instruction
140 @atomicStore(u8, @ptrCast(*u8, &self.mutex.locked), 0, .Release);
141 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
142 const key = @ptrCast(*const c_void, &self.mutex.waiters);
143
144 while (true) : (SpinLock.loopHint(1)) {
145 const waiters = @atomicLoad(u32, &self.mutex.waiters, .Monotonic);
146
147 // no one is waiting
148 if (waiters < WAIT) return;
149 // someone grabbed the lock and will do the wake instead
150 if (waiters & 1 != 0) return;
151 // someone else is currently waking up
152 if (waiters & WAKE != 0) return;
153
154 // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up
155 if (@cmpxchgWeak(u32, &self.mutex.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) {
156 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
157 assert(rc == .SUCCESS);
158 return;
159 }
160 }
161 }
162 };
163 }
164else if (builtin.link_libc or builtin.os.tag == .linux)35else if (builtin.link_libc or builtin.os.tag == .linux)
165// stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs36// stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs
166 struct {37 struct {
167 state: usize,38 state: usize = 0,
16839
169 /// number of times to spin trying to acquire the lock.40 /// number of times to spin trying to acquire the lock.
170 /// https://webkit.org/blog/6161/locking-in-webkit/41 /// https://webkit.org/blog/6161/locking-in-webkit/
...@@ -179,14 +50,6 @@ else if (builtin.link_libc or builtin.os.tag == .linux)...@@ -179,14 +50,6 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
179 event: ResetEvent,50 event: ResetEvent,
180 };51 };
18152
182 pub fn init() Mutex {
183 return Mutex{ .state = 0 };
184 }
185
186 pub fn deinit(self: *Mutex) void {
187 self.* = undefined;
188 }
189
190 pub fn tryAcquire(self: *Mutex) ?Held {53 pub fn tryAcquire(self: *Mutex) ?Held {
191 if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)54 if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)
192 return null;55 return null;
...@@ -298,6 +161,128 @@ else if (builtin.link_libc or builtin.os.tag == .linux)...@@ -298,6 +161,128 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
298else161else
299 SpinLock;162 SpinLock;
300163
164/// This has the sematics as `Mutex`, however it does not actually do any
165/// synchronization. Operations are safety-checked no-ops.
166pub const Dummy = struct {
167 lock: @TypeOf(lock_init) = lock_init,
168
169 const lock_init = if (std.debug.runtime_safety) false else {};
170
171 pub const Held = struct {
172 mutex: *Dummy,
173
174 pub fn release(self: Held) void {
175 if (std.debug.runtime_safety) {
176 self.mutex.lock = false;
177 }
178 }
179 };
180
181 /// Create a new mutex in unlocked state.
182 pub const init = Dummy{};
183
184 /// Try to acquire the mutex without blocking. Returns null if
185 /// the mutex is unavailable. Otherwise returns Held. Call
186 /// release on Held.
187 pub fn tryAcquire(self: *Dummy) ?Held {
188 if (std.debug.runtime_safety) {
189 if (self.lock) return null;
190 self.lock = true;
191 }
192 return Held{ .mutex = self };
193 }
194
195 /// Acquire the mutex. Will deadlock if the mutex is already
196 /// held by the calling thread.
197 pub fn acquire(self: *Dummy) Held {
198 return self.tryAcquire() orelse @panic("deadlock detected");
199 }
200};
201
202// https://locklessinc.com/articles/keyed_events/
203const WindowsMutex = struct {
204 state: State = State{ .waiters = 0 },
205
206 const State = extern union {
207 locked: u8,
208 waiters: u32,
209 };
210
211 const WAKE = 1 << 8;
212 const WAIT = 1 << 9;
213
214 pub fn tryAcquire(self: *WindowsMutex) ?Held {
215 if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) != 0)
216 return null;
217 return Held{ .mutex = self };
218 }
219
220 pub fn acquire(self: *WindowsMutex) Held {
221 return self.tryAcquire() orelse self.acquireSlow();
222 }
223
224 fn acquireSpinning(self: *WindowsMutex) Held {
225 @setCold(true);
226 while (true) : (SpinLock.yield()) {
227 return self.tryAcquire() orelse continue;
228 }
229 }
230
231 fn acquireSlow(self: *WindowsMutex) Held {
232 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
233 @setCold(true);
234 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
235 const key = @ptrCast(*const c_void, &self.state.waiters);
236
237 while (true) : (SpinLock.loopHint(1)) {
238 const waiters = @atomicLoad(u32, &self.state.waiters, .Monotonic);
239
240 // try and take lock if unlocked
241 if ((waiters & 1) == 0) {
242 if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) == 0) {
243 return Held{ .mutex = self };
244 }
245
246 // otherwise, try and update the waiting count.
247 // then unset the WAKE bit so that another unlocker can wake up a thread.
248 } else if (@cmpxchgWeak(u32, &self.state.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) {
249 const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
250 assert(rc == .SUCCESS);
251 _ = @atomicRmw(u32, &self.state.waiters, .Sub, WAKE, .Monotonic);
252 }
253 }
254 }
255
256 pub const Held = struct {
257 mutex: *WindowsMutex,
258
259 pub fn release(self: Held) void {
260 // unlock without a rmw/cmpxchg instruction
261 @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release);
262 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
263 const key = @ptrCast(*const c_void, &self.mutex.state.waiters);
264
265 while (true) : (SpinLock.loopHint(1)) {
266 const waiters = @atomicLoad(u32, &self.mutex.state.waiters, .Monotonic);
267
268 // no one is waiting
269 if (waiters < WAIT) return;
270 // someone grabbed the lock and will do the wake instead
271 if (waiters & 1 != 0) return;
272 // someone else is currently waking up
273 if (waiters & WAKE != 0) return;
274
275 // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up
276 if (@cmpxchgWeak(u32, &self.mutex.state.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) {
277 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
278 assert(rc == .SUCCESS);
279 return;
280 }
281 }
282 }
283 };
284};
285
301const TestContext = struct {286const TestContext = struct {
302 mutex: *Mutex,287 mutex: *Mutex,
303 data: i128,288 data: i128,
...@@ -306,8 +291,7 @@ const TestContext = struct {...@@ -306,8 +291,7 @@ const TestContext = struct {
306};291};
307292
308test "std.Mutex" {293test "std.Mutex" {
309 var mutex = Mutex.init();294 var mutex = Mutex{};
310 defer mutex.deinit();
311295
312 var context = TestContext{296 var context = TestContext{
313 .mutex = &mutex,297 .mutex = &mutex,
lib/std/once.zig+1-1
...@@ -10,7 +10,7 @@ pub fn once(comptime f: fn () void) Once(f) {...@@ -10,7 +10,7 @@ pub fn once(comptime f: fn () void) Once(f) {
10pub fn Once(comptime f: fn () void) type {10pub fn Once(comptime f: fn () void) type {
11 return struct {11 return struct {
12 done: bool = false,12 done: bool = false,
13 mutex: std.Mutex = std.Mutex.init(),13 mutex: std.Mutex = std.Mutex{},
1414
15 /// Call the function `f`.15 /// Call the function `f`.
16 /// If `call` is invoked multiple times `f` will be executed only the16 /// If `call` is invoked multiple times `f` will be executed only the
lib/std/special/test_runner.zig+17-11
...@@ -19,15 +19,21 @@ pub fn main() anyerror!void {...@@ -19,15 +19,21 @@ pub fn main() anyerror!void {
19 // ignores the alignment of the slice.19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};20 async_frame_buffer = &[_]u8{};
2121
22 var leaks: usize = 0;
22 for (test_fn_list) |test_fn, i| {23 for (test_fn_list) |test_fn, i| {
23 std.testing.base_allocator_instance.reset();24 std.testing.allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};
25 defer {
26 if (std.testing.allocator_instance.deinit()) {
27 leaks += 1;
28 }
29 }
24 std.testing.log_level = .warn;30 std.testing.log_level = .warn;
2531
26 var test_node = root_node.start(test_fn.name, null);32 var test_node = root_node.start(test_fn.name, null);
27 test_node.activate();33 test_node.activate();
28 progress.refresh();34 progress.refresh();
29 if (progress.terminal == null) {35 if (progress.terminal == null) {
30 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });36 std.debug.print("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
31 }37 }
32 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {38 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
33 .evented => blk: {39 .evented => blk: {
...@@ -42,24 +48,20 @@ pub fn main() anyerror!void {...@@ -42,24 +48,20 @@ pub fn main() anyerror!void {
42 skip_count += 1;48 skip_count += 1;
43 test_node.end();49 test_node.end();
44 progress.log("{}...SKIP (async test)\n", .{test_fn.name});50 progress.log("{}...SKIP (async test)\n", .{test_fn.name});
45 if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{});51 if (progress.terminal == null) std.debug.print("SKIP (async test)\n", .{});
46 continue;52 continue;
47 },53 },
48 } else test_fn.func();54 } else test_fn.func();
49 if (result) |_| {55 if (result) |_| {
50 ok_count += 1;56 ok_count += 1;
51 test_node.end();57 test_node.end();
52 std.testing.allocator_instance.validate() catch |err| switch (err) {58 if (progress.terminal == null) std.debug.print("OK\n", .{});
53 error.Leak => std.debug.panic("", .{}),
54 else => std.debug.panic("error.{}", .{@errorName(err)}),
55 };
56 if (progress.terminal == null) std.debug.warn("OK\n", .{});
57 } else |err| switch (err) {59 } else |err| switch (err) {
58 error.SkipZigTest => {60 error.SkipZigTest => {
59 skip_count += 1;61 skip_count += 1;
60 test_node.end();62 test_node.end();
61 progress.log("{}...SKIP\n", .{test_fn.name});63 progress.log("{}...SKIP\n", .{test_fn.name});
62 if (progress.terminal == null) std.debug.warn("SKIP\n", .{});64 if (progress.terminal == null) std.debug.print("SKIP\n", .{});
63 },65 },
64 else => {66 else => {
65 progress.log("", .{});67 progress.log("", .{});
...@@ -69,9 +71,13 @@ pub fn main() anyerror!void {...@@ -69,9 +71,13 @@ pub fn main() anyerror!void {
69 }71 }
70 root_node.end();72 root_node.end();
71 if (ok_count == test_fn_list.len) {73 if (ok_count == test_fn_list.len) {
72 std.debug.warn("All {} tests passed.\n", .{ok_count});74 std.debug.print("All {} tests passed.\n", .{ok_count});
73 } else {75 } else {
74 std.debug.warn("{} passed; {} skipped.\n", .{ ok_count, skip_count });76 std.debug.print("{} passed; {} skipped.\n", .{ ok_count, skip_count });
77 }
78 if (leaks != 0) {
79 std.debug.print("{} tests leaked memory\n", .{ok_count});
80 std.process.exit(1);
75 }81 }
76}82}
7783
lib/std/std.zig+2-1
...@@ -13,7 +13,8 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM...@@ -13,7 +13,8 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM
13pub const DynLib = @import("dynamic_library.zig").DynLib;13pub const DynLib = @import("dynamic_library.zig").DynLib;
14pub const HashMap = hash_map.HashMap;14pub const HashMap = hash_map.HashMap;
15pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;15pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;
16pub const Mutex = @import("mutex.zig").Mutex;16pub const mutex = @import("mutex.zig");
17pub const Mutex = mutex.Mutex;
17pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;18pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;
18pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;19pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;
19pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;20pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
lib/std/testing.zig+14-16
...@@ -1,18 +1,16 @@...@@ -1,18 +1,16 @@
1const std = @import("std.zig");1const std = @import("std.zig");
2const warn = std.debug.warn;2const print = std.debug.print;
33
4pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator;
5pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator;4pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator;
65
7/// This should only be used in temporary test programs.6/// This should only be used in temporary test programs.
8pub const allocator = &allocator_instance.allocator;7pub const allocator = &allocator_instance.allocator;
9pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.allocator);8pub var allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};
109
11pub const failing_allocator = &failing_allocator_instance.allocator;10pub const failing_allocator = &failing_allocator_instance.allocator;
12pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0);11pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0);
1312
14pub var base_allocator_instance = std.mem.validationWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]));13pub var base_allocator_instance = std.heap.FixedBufferAllocator.init("");
15var allocator_mem: [2 * 1024 * 1024]u8 = undefined;
1614
17/// TODO https://github.com/ziglang/zig/issues/573815/// TODO https://github.com/ziglang/zig/issues/5738
18pub var log_level = std.log.Level.warn;16pub var log_level = std.log.Level.warn;
...@@ -326,22 +324,22 @@ test "expectEqual vector" {...@@ -326,22 +324,22 @@ test "expectEqual vector" {
326324
327pub fn expectEqualStrings(expected: []const u8, actual: []const u8) void {325pub fn expectEqualStrings(expected: []const u8, actual: []const u8) void {
328 if (std.mem.indexOfDiff(u8, actual, expected)) |diff_index| {326 if (std.mem.indexOfDiff(u8, actual, expected)) |diff_index| {
329 warn("\n====== expected this output: =========\n", .{});327 print("\n====== expected this output: =========\n", .{});
330 printWithVisibleNewlines(expected);328 printWithVisibleNewlines(expected);
331 warn("\n======== instead found this: =========\n", .{});329 print("\n======== instead found this: =========\n", .{});
332 printWithVisibleNewlines(actual);330 printWithVisibleNewlines(actual);
333 warn("\n======================================\n", .{});331 print("\n======================================\n", .{});
334332
335 var diff_line_number: usize = 1;333 var diff_line_number: usize = 1;
336 for (expected[0..diff_index]) |value| {334 for (expected[0..diff_index]) |value| {
337 if (value == '\n') diff_line_number += 1;335 if (value == '\n') diff_line_number += 1;
338 }336 }
339 warn("First difference occurs on line {}:\n", .{diff_line_number});337 print("First difference occurs on line {}:\n", .{diff_line_number});
340338
341 warn("expected:\n", .{});339 print("expected:\n", .{});
342 printIndicatorLine(expected, diff_index);340 printIndicatorLine(expected, diff_index);
343341
344 warn("found:\n", .{});342 print("found:\n", .{});
345 printIndicatorLine(actual, diff_index);343 printIndicatorLine(actual, diff_index);
346344
347 @panic("test failure");345 @panic("test failure");
...@@ -362,9 +360,9 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void {...@@ -362,9 +360,9 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void {
362 {360 {
363 var i: usize = line_begin_index;361 var i: usize = line_begin_index;
364 while (i < indicator_index) : (i += 1)362 while (i < indicator_index) : (i += 1)
365 warn(" ", .{});363 print(" ", .{});
366 }364 }
367 warn("^\n", .{});365 print("^\n", .{});
368}366}
369367
370fn printWithVisibleNewlines(source: []const u8) void {368fn printWithVisibleNewlines(source: []const u8) void {
...@@ -372,15 +370,15 @@ fn printWithVisibleNewlines(source: []const u8) void {...@@ -372,15 +370,15 @@ fn printWithVisibleNewlines(source: []const u8) void {
372 while (std.mem.indexOf(u8, source[i..], "\n")) |nl| : (i += nl + 1) {370 while (std.mem.indexOf(u8, source[i..], "\n")) |nl| : (i += nl + 1) {
373 printLine(source[i .. i + nl]);371 printLine(source[i .. i + nl]);
374 }372 }
375 warn("{}␃\n", .{source[i..]}); // End of Text symbol (ETX)373 print("{}␃\n", .{source[i..]}); // End of Text symbol (ETX)
376}374}
377375
378fn printLine(line: []const u8) void {376fn printLine(line: []const u8) void {
379 if (line.len != 0) switch (line[line.len - 1]) {377 if (line.len != 0) switch (line[line.len - 1]) {
380 ' ', '\t' => warn("{}⏎\n", .{line}), // Carriage return symbol,378 ' ', '\t' => print("{}⏎\n", .{line}), // Carriage return symbol,
381 else => {},379 else => {},
382 };380 };
383 warn("{}\n", .{line});381 print("{}\n", .{line});
384}382}
385383
386test "" {384test "" {
lib/std/testing/failing_allocator.zig+17-4
...@@ -45,21 +45,34 @@ pub const FailingAllocator = struct {...@@ -45,21 +45,34 @@ pub const FailingAllocator = struct {
45 };45 };
46 }46 }
4747
48 fn alloc(allocator: *std.mem.Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 {48 fn alloc(
49 allocator: *std.mem.Allocator,
50 len: usize,
51 ptr_align: u29,
52 len_align: u29,
53 return_address: usize,
54 ) error{OutOfMemory}![]u8 {
49 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);55 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
50 if (self.index == self.fail_index) {56 if (self.index == self.fail_index) {
51 return error.OutOfMemory;57 return error.OutOfMemory;
52 }58 }
53 const result = try self.internal_allocator.callAllocFn(len, ptr_align, len_align);59 const result = try self.internal_allocator.allocFn(self.internal_allocator, len, ptr_align, len_align, return_address);
54 self.allocated_bytes += result.len;60 self.allocated_bytes += result.len;
55 self.allocations += 1;61 self.allocations += 1;
56 self.index += 1;62 self.index += 1;
57 return result;63 return result;
58 }64 }
5965
60 fn resize(allocator: *std.mem.Allocator, old_mem: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize {66 fn resize(
67 allocator: *std.mem.Allocator,
68 old_mem: []u8,
69 old_align: u29,
70 new_len: usize,
71 len_align: u29,
72 ra: usize,
73 ) error{OutOfMemory}!usize {
61 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);74 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
62 const r = self.internal_allocator.callResizeFn(old_mem, new_len, len_align) catch |e| {75 const r = self.internal_allocator.resizeFn(self.internal_allocator, old_mem, old_align, new_len, len_align, ra) catch |e| {
63 std.debug.assert(new_len > old_mem.len);76 std.debug.assert(new_len > old_mem.len);
64 return e;77 return e;
65 };78 };
lib/std/testing/leak_count_allocator.zig deleted-51
...@@ -1,51 +0,0 @@
1const std = @import("../std.zig");
2
3/// This allocator is used in front of another allocator and counts the numbers of allocs and frees.
4/// The test runner asserts every alloc has a corresponding free at the end of each test.
5///
6/// The detection algorithm is incredibly primitive and only accounts for number of calls.
7/// This should be replaced by the general purpose debug allocator.
8pub const LeakCountAllocator = struct {
9 count: usize,
10 allocator: std.mem.Allocator,
11 internal_allocator: *std.mem.Allocator,
12
13 pub fn init(allocator: *std.mem.Allocator) LeakCountAllocator {
14 return .{
15 .count = 0,
16 .allocator = .{
17 .allocFn = alloc,
18 .resizeFn = resize,
19 },
20 .internal_allocator = allocator,
21 };
22 }
23
24 fn alloc(allocator: *std.mem.Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 {
25 const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
26 const ptr = try self.internal_allocator.callAllocFn(len, ptr_align, len_align);
27 self.count += 1;
28 return ptr;
29 }
30
31 fn resize(allocator: *std.mem.Allocator, old_mem: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize {
32 const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
33 if (new_size == 0) {
34 if (self.count == 0) {
35 std.debug.panic("error - too many calls to free, most likely double free", .{});
36 }
37 self.count -= 1;
38 }
39 return self.internal_allocator.callResizeFn(old_mem, new_size, len_align) catch |e| {
40 std.debug.assert(new_size > old_mem.len);
41 return e;
42 };
43 }
44
45 pub fn validate(self: LeakCountAllocator) !void {
46 if (self.count > 0) {
47 std.debug.warn("error - detected leaked allocations without matching free: {}\n", .{self.count});
48 return error.Leak;
49 }
50 }
51};
src-self-hosted/link.zig+6-6
...@@ -325,17 +325,17 @@ pub const File = struct {...@@ -325,17 +325,17 @@ pub const File = struct {
325 /// local symbols, they cannot be mixed. So we must buffer all the global symbols and325 /// local symbols, they cannot be mixed. So we must buffer all the global symbols and
326 /// write them at the end. These are only the local symbols. The length of this array326 /// write them at the end. These are only the local symbols. The length of this array
327 /// is the value used for sh_info in the .symtab section.327 /// is the value used for sh_info in the .symtab section.
328 local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){},328 local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
329 global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){},329 global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
330330
331 local_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},331 local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{},
332 global_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},332 global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{},
333 offset_table_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},333 offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
334334
335 /// Same order as in the file. The value is the absolute vaddr value.335 /// Same order as in the file. The value is the absolute vaddr value.
336 /// If the vaddr of the executable program header changes, the entire336 /// If the vaddr of the executable program header changes, the entire
337 /// offset table needs to be rewritten.337 /// offset table needs to be rewritten.
338 offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){},338 offset_table: std.ArrayListUnmanaged(u64) = .{},
339339
340 phdr_table_dirty: bool = false,340 phdr_table_dirty: bool = false,
341 shdr_table_dirty: bool = false,341 shdr_table_dirty: bool = false,
src-self-hosted/main.zig+3-2
...@@ -60,9 +60,10 @@ pub fn log(...@@ -60,9 +60,10 @@ pub fn log(
60 std.debug.print(prefix ++ format, args);60 std.debug.print(prefix ++ format, args);
61}61}
6262
63var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
64
63pub fn main() !void {65pub fn main() !void {
64 // TODO general purpose allocator in the zig std lib66 const gpa = if (std.builtin.link_libc) std.heap.c_allocator else &general_purpose_allocator.allocator;
65 const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator;
66 var arena_instance = std.heap.ArenaAllocator.init(gpa);67 var arena_instance = std.heap.ArenaAllocator.init(gpa);
67 defer arena_instance.deinit();68 defer arena_instance.deinit();
68 const arena = &arena_instance.allocator;69 const arena = &arena_instance.allocator;
src-self-hosted/test.zig-3
...@@ -407,8 +407,6 @@ pub const TestContext = struct {...@@ -407,8 +407,6 @@ pub const TestContext = struct {
407 defer root_node.end();407 defer root_node.end();
408408
409 for (self.cases.items) |case| {409 for (self.cases.items) |case| {
410 std.testing.base_allocator_instance.reset();
411
412 var prg_node = root_node.start(case.name, case.updates.items.len);410 var prg_node = root_node.start(case.name, case.updates.items.len);
413 prg_node.activate();411 prg_node.activate();
414 defer prg_node.end();412 defer prg_node.end();
...@@ -419,7 +417,6 @@ pub const TestContext = struct {...@@ -419,7 +417,6 @@ pub const TestContext = struct {
419 progress.refresh_rate_ns = 0;417 progress.refresh_rate_ns = 0;
420418
421 try self.runOneCase(std.testing.allocator, &prg_node, case);419 try self.runOneCase(std.testing.allocator, &prg_node, case);
422 try std.testing.allocator_instance.validate();
423 }420 }
424 }421 }
425422
src/codegen.cpp+6
...@@ -5886,6 +5886,12 @@ static LLVMValueRef ir_render_breakpoint(CodeGen *g, IrExecutableGen *executable...@@ -5886,6 +5886,12 @@ static LLVMValueRef ir_render_breakpoint(CodeGen *g, IrExecutableGen *executable
5886static LLVMValueRef ir_render_return_address(CodeGen *g, IrExecutableGen *executable,5886static LLVMValueRef ir_render_return_address(CodeGen *g, IrExecutableGen *executable,
5887 IrInstGenReturnAddress *instruction)5887 IrInstGenReturnAddress *instruction)
5888{5888{
5889 if (target_is_wasm(g->zig_target) && g->zig_target->os != OsEmscripten) {
5890 // I got this error from LLVM 10:
5891 // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address"
5892 return LLVMConstNull(get_llvm_type(g, instruction->base.value->type));
5893 }
5894
5889 LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_i32->llvm_type);5895 LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_i32->llvm_type);
5890 LLVMValueRef ptr_val = LLVMBuildCall(g->builder, get_return_address_fn_val(g), &zero, 1, "");5896 LLVMValueRef ptr_val = LLVMBuildCall(g->builder, get_return_address_fn_val(g), &zero, 1, "");
5891 return LLVMBuildPtrToInt(g->builder, ptr_val, g->builtin_types.entry_usize->llvm_type, "");5897 return LLVMBuildPtrToInt(g->builder, ptr_val, g->builtin_types.entry_usize->llvm_type, "");
src/ir.cpp+26-14
...@@ -25067,12 +25067,12 @@ static PtrLen size_enum_index_to_ptr_len(BuiltinPtrSize size_enum_index) {...@@ -25067,12 +25067,12 @@ static PtrLen size_enum_index_to_ptr_len(BuiltinPtrSize size_enum_index) {
25067 zig_unreachable();25067 zig_unreachable();
25068}25068}
2506925069
25070static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_entry) {25070static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, IrInst *source_instr, ZigType *ptr_type_entry) {
25071 Error err;
25072 ZigType *attrs_type;25071 ZigType *attrs_type;
25073 BuiltinPtrSize size_enum_index;25072 BuiltinPtrSize size_enum_index;
25074 if (is_slice(ptr_type_entry)) {25073 if (is_slice(ptr_type_entry)) {
25075 attrs_type = ptr_type_entry->data.structure.fields[slice_ptr_index]->type_entry;25074 TypeStructField *ptr_field = ptr_type_entry->data.structure.fields[slice_ptr_index];
25075 attrs_type = resolve_struct_field_type(ira->codegen, ptr_field);
25076 size_enum_index = BuiltinPtrSizeSlice;25076 size_enum_index = BuiltinPtrSizeSlice;
25077 } else if (ptr_type_entry->id == ZigTypeIdPointer) {25077 } else if (ptr_type_entry->id == ZigTypeIdPointer) {
25078 attrs_type = ptr_type_entry;25078 attrs_type = ptr_type_entry;
...@@ -25081,9 +25081,6 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent...@@ -25081,9 +25081,6 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent
25081 zig_unreachable();25081 zig_unreachable();
25082 }25082 }
2508325083
25084 if ((err = type_resolve(ira->codegen, attrs_type->data.pointer.child_type, ResolveStatusSizeKnown)))
25085 return nullptr;
25086
25087 ZigType *type_info_pointer_type = ir_type_info_get_type(ira, "Pointer", nullptr);25084 ZigType *type_info_pointer_type = ir_type_info_get_type(ira, "Pointer", nullptr);
25088 assertNoError(type_resolve(ira->codegen, type_info_pointer_type, ResolveStatusSizeKnown));25085 assertNoError(type_resolve(ira->codegen, type_info_pointer_type, ResolveStatusSizeKnown));
2508925086
...@@ -25114,9 +25111,18 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent...@@ -25114,9 +25111,18 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent
25114 fields[2]->data.x_bool = attrs_type->data.pointer.is_volatile;25111 fields[2]->data.x_bool = attrs_type->data.pointer.is_volatile;
25115 // alignment: u3225112 // alignment: u32
25116 ensure_field_index(result->type, "alignment", 3);25113 ensure_field_index(result->type, "alignment", 3);
25117 fields[3]->special = ConstValSpecialStatic;
25118 fields[3]->type = ira->codegen->builtin_types.entry_num_lit_int;25114 fields[3]->type = ira->codegen->builtin_types.entry_num_lit_int;
25119 bigint_init_unsigned(&fields[3]->data.x_bigint, get_ptr_align(ira->codegen, attrs_type));25115 if (attrs_type->data.pointer.explicit_alignment != 0) {
25116 fields[3]->special = ConstValSpecialStatic;
25117 bigint_init_unsigned(&fields[3]->data.x_bigint, attrs_type->data.pointer.explicit_alignment);
25118 } else {
25119 LazyValueAlignOf *lazy_align_of = heap::c_allocator.create<LazyValueAlignOf>();
25120 lazy_align_of->ira = ira; ira_ref(ira);
25121 fields[3]->special = ConstValSpecialLazy;
25122 fields[3]->data.x_lazy = &lazy_align_of->base;
25123 lazy_align_of->base.id = LazyValueIdAlignOf;
25124 lazy_align_of->target_type = ir_const_type(ira, source_instr, attrs_type->data.pointer.child_type);
25125 }
25120 // child: type25126 // child: type
25121 ensure_field_index(result->type, "child", 4);25127 ensure_field_index(result->type, "child", 4);
25122 fields[4]->special = ConstValSpecialStatic;25128 fields[4]->special = ConstValSpecialStatic;
...@@ -25130,7 +25136,7 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent...@@ -25130,7 +25136,7 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent
25130 // sentinel: anytype25136 // sentinel: anytype
25131 ensure_field_index(result->type, "sentinel", 6);25137 ensure_field_index(result->type, "sentinel", 6);
25132 fields[6]->special = ConstValSpecialStatic;25138 fields[6]->special = ConstValSpecialStatic;
25133 if (attrs_type->data.pointer.child_type->id != ZigTypeIdOpaque) {25139 if (attrs_type->data.pointer.sentinel != nullptr) {
25134 fields[6]->type = get_optional_type(ira->codegen, attrs_type->data.pointer.child_type);25140 fields[6]->type = get_optional_type(ira->codegen, attrs_type->data.pointer.child_type);
25135 set_optional_payload(fields[6], attrs_type->data.pointer.sentinel);25141 set_optional_payload(fields[6], attrs_type->data.pointer.sentinel);
25136 } else {25142 } else {
...@@ -25165,9 +25171,6 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy...@@ -25165,9 +25171,6 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
25165 assert(type_entry != nullptr);25171 assert(type_entry != nullptr);
25166 assert(!type_is_invalid(type_entry));25172 assert(!type_is_invalid(type_entry));
2516725173
25168 if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
25169 return err;
25170
25171 auto entry = ira->codegen->type_info_cache.maybe_get(type_entry);25174 auto entry = ira->codegen->type_info_cache.maybe_get(type_entry);
25172 if (entry != nullptr) {25175 if (entry != nullptr) {
25173 *out = entry->value;25176 *out = entry->value;
...@@ -25231,7 +25234,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy...@@ -25231,7 +25234,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
25231 }25234 }
25232 case ZigTypeIdPointer:25235 case ZigTypeIdPointer:
25233 {25236 {
25234 result = create_ptr_like_type_info(ira, type_entry);25237 result = create_ptr_like_type_info(ira, source_instr, type_entry);
25235 if (result == nullptr)25238 if (result == nullptr)
25236 return ErrorSemanticAnalyzeFail;25239 return ErrorSemanticAnalyzeFail;
25237 break;25240 break;
...@@ -25317,6 +25320,9 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy...@@ -25317,6 +25320,9 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
25317 }25320 }
25318 case ZigTypeIdEnum:25321 case ZigTypeIdEnum:
25319 {25322 {
25323 if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
25324 return err;
25325
25320 result = ira->codegen->pass1_arena->create<ZigValue>();25326 result = ira->codegen->pass1_arena->create<ZigValue>();
25321 result->special = ConstValSpecialStatic;25327 result->special = ConstValSpecialStatic;
25322 result->type = ir_type_info_get_type(ira, "Enum", nullptr);25328 result->type = ir_type_info_get_type(ira, "Enum", nullptr);
...@@ -25455,6 +25461,9 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy...@@ -25455,6 +25461,9 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
25455 }25461 }
25456 case ZigTypeIdUnion:25462 case ZigTypeIdUnion:
25457 {25463 {
25464 if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
25465 return err;
25466
25458 result = ira->codegen->pass1_arena->create<ZigValue>();25467 result = ira->codegen->pass1_arena->create<ZigValue>();
25459 result->special = ConstValSpecialStatic;25468 result->special = ConstValSpecialStatic;
25460 result->type = ir_type_info_get_type(ira, "Union", nullptr);25469 result->type = ir_type_info_get_type(ira, "Union", nullptr);
...@@ -25545,12 +25554,15 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy...@@ -25545,12 +25554,15 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy
25545 case ZigTypeIdStruct:25554 case ZigTypeIdStruct:
25546 {25555 {
25547 if (type_entry->data.structure.special == StructSpecialSlice) {25556 if (type_entry->data.structure.special == StructSpecialSlice) {
25548 result = create_ptr_like_type_info(ira, type_entry);25557 result = create_ptr_like_type_info(ira, source_instr, type_entry);
25549 if (result == nullptr)25558 if (result == nullptr)
25550 return ErrorSemanticAnalyzeFail;25559 return ErrorSemanticAnalyzeFail;
25551 break;25560 break;
25552 }25561 }
2555325562
25563 if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
25564 return err;
25565
25554 result = ira->codegen->pass1_arena->create<ZigValue>();25566 result = ira->codegen->pass1_arena->create<ZigValue>();
25555 result->special = ConstValSpecialStatic;25567 result->special = ConstValSpecialStatic;
25556 result->type = ir_type_info_get_type(ira, "Struct", nullptr);25568 result->type = ir_type_info_get_type(ira, "Struct", nullptr);