| author | |
| committer | |
| log | 069a6f2432be5fd5e9a3eabd13faa43143001bbd |
| tree | f68e0ce1a1a7e62665be0db0fc701f79c25189aa |
| parent | ab483281d31c852e77fe0afc6a623fedd1574546 |
| parent | f98cffc615bb7ea73eee3bd24ae8a957fe8ebb82 |
| signature |
std: introduce GeneralPurposeAllocator23 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 not | 9360 | 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 | } |
| 265 | 265 | ||
| 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 | } |
| 28 | 28 |
lib/std/debug.zig+2-5| ... | @@ -19,9 +19,6 @@ const windows = std.os.windows; | ... | @@ -19,9 +19,6 @@ const windows = std.os.windows; |
| 19 | 19 | ||
| 20 | pub const leb = @import("debug/leb128.zig"); | 20 | pub const leb = @import("debug/leb128.zig"); |
| 21 | 21 | ||
| 22 | pub const global_allocator = @compileError("Please switch to std.testing.allocator."); | ||
| 23 | pub const failing_allocator = @compileError("Please switch to std.testing.failing_allocator."); | ||
| 24 | |||
| 25 | pub const runtime_safety = switch (builtin.mode) { | 22 | pub 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 | }; |
| 52 | 49 | ||
| 53 | var stderr_mutex = std.Mutex.init(); | 50 | var stderr_mutex = std.Mutex{}; |
| 54 | 51 | ||
| 55 | /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for | 52 | /// 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 { |
| 235 | var panicking: u8 = 0; | 232 | var panicking: u8 = 0; |
| 236 | 233 | ||
| 237 | // Locked to avoid interleaving panic messages from multiple threads. | 234 | // Locked to avoid interleaving panic messages from multiple threads. |
| 238 | var panic_mutex = std.Mutex.init(); | 235 | var panic_mutex = std.Mutex{}; |
| 239 | 236 | ||
| 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; |
| 12 | pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; | 12 | pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; |
| 13 | pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; | 13 | pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; |
| 14 | pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; | 14 | pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; |
| 15 | pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig").GeneralPurposeAllocator; | ||
| 15 | 16 | ||
| 16 | const Allocator = mem.Allocator; | 17 | const Allocator = mem.Allocator; |
| 17 | 18 | ||
| ... | @@ -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 | }; |
| 38 | 39 | ||
| 39 | fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { | 40 | fn 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 | } |
| 55 | 56 | ||
| 56 | fn cResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { | 57 | fn 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 | }; |
| 90 | 98 | ||
| 91 | pub const direct_allocator = @compileError("deprecated; use std.heap.page_allocator"); | ||
| 92 | |||
| 93 | /// Verifies that the adjusted length will still map to the full length | 99 | /// Verifies that the adjusted length will still map to the full length |
| 94 | pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize { | 100 | pub 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 | } |
| 99 | 105 | ||
| 106 | /// TODO Utilize this on Windows. | ||
| 107 | pub var next_mmap_addr_hint: ?[*]align(mem.page_size) u8 = null; | ||
| 108 | |||
| 100 | const PageAllocator = struct { | 109 | const 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); |
| 104 | 113 | ||
| 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=42223 | 121 | // 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; |
| 119 | 128 | ||
| 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 | } |
| 124 | 133 | ||
| 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; |
| 153 | 162 | ||
| 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 | } |
| 157 | 166 | ||
| 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)); |
| 169 | 182 | ||
| 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)); | ||
| 171 | 185 | ||
| 172 | // Unmap the extra bytes that were only requested in order to guarantee | 186 | // 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 in | 187 | // 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 | } |
| 179 | 193 | ||
| 180 | // Unmap extra pages | 194 | // 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 | } |
| 185 | 199 | ||
| 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 | } |
| 188 | 205 | ||
| 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); |
| 191 | 215 | ||
| 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 | } |
| 225 | 249 | ||
| ... | @@ -229,6 +253,7 @@ const PageAllocator = struct { | ... | @@ -229,6 +253,7 @@ const PageAllocator = struct { |
| 229 | 253 | ||
| 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 | } |
| 237 | 262 | ||
| 238 | // TODO: call mremap | 263 | // 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 | } |
| 334 | 360 | ||
| 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 | } |
| 387 | 413 | ||
| 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 | } |
| 427 | 460 | ||
| 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); |
| 430 | 469 | ||
| 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 | } |
| 454 | 493 | ||
| 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 | } |
| 526 | 572 | ||
| 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 | } |
| 540 | 586 | ||
| 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 check | 596 | assert(self.ownsSlice(buf)); // sanity check |
| 544 | 597 | ||
| ... | @@ -588,7 +641,7 @@ pub const ThreadSafeFixedBufferAllocator = blk: { | ... | @@ -588,7 +641,7 @@ pub const ThreadSafeFixedBufferAllocator = blk: { |
| 588 | }; | 641 | }; |
| 589 | } | 642 | } |
| 590 | 643 | ||
| 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 | } |
| 638 | 691 | ||
| 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) catch | 700 | 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 | } |
| 644 | 703 | ||
| 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; |
| 933 | 999 | ||
| 934 | // realloc to a smaller size but with a larger alignment | 1000 | // 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 | } |
| 62 | 62 | ||
| 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); |
| 65 | 65 | ||
| 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 | |||
| 95 | const std = @import("std"); | ||
| 96 | const math = std.math; | ||
| 97 | const assert = std.debug.assert; | ||
| 98 | const mem = std.mem; | ||
| 99 | const Allocator = std.mem.Allocator; | ||
| 100 | const page_size = std.mem.page_size; | ||
| 101 | const StackTrace = std.builtin.StackTrace; | ||
| 102 | |||
| 103 | /// Integer type for pointing to slots in a small allocation | ||
| 104 | const SlotIndex = std.meta.Int(false, math.log2(page_size) + 1); | ||
| 105 | |||
| 106 | const 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 | }; | ||
| 121 | const default_sys_stack_trace_frames: usize = if (sys_can_stack_trace) 4 else 0; | ||
| 122 | const default_stack_trace_frames: usize = switch (std.builtin.mode) { | ||
| 123 | .Debug => default_sys_stack_trace_frames, | ||
| 124 | else => 0, | ||
| 125 | }; | ||
| 126 | |||
| 127 | pub 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 | |||
| 145 | pub 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 | |||
| 607 | const TraceKind = enum { | ||
| 608 | alloc, | ||
| 609 | free, | ||
| 610 | }; | ||
| 611 | |||
| 612 | const test_config = Config{}; | ||
| 613 | |||
| 614 | test "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 | |||
| 633 | test "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 | |||
| 652 | test "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 | |||
| 665 | test "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 | |||
| 687 | test "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 | |||
| 710 | test "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 | |||
| 728 | test "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 | |||
| 745 | test "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 | |||
| 768 | test "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 | |||
| 803 | test "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 | |||
| 818 | test "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 | |||
| 827 | test "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 | |||
| 867 | test "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 | |||
| 885 | test "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 | |||
| 897 | test "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 | } |
| 25 | 25 | ||
| 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 | } |
| 37 | 43 | ||
| 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; |
| 75 | 88 | ||
| 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); |
| 81 | 94 | ||
| 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 | } |
| 839 | 839 | ||
| 840 | pub fn ceilPowerOfTwoAssert(comptime T: type, value: T) T { | ||
| 841 | return ceilPowerOfTwo(T, value) catch unreachable; | ||
| 842 | } | ||
| 843 | |||
| 840 | test "math.ceilPowerOfTwoPromote" { | 844 | test "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; |
| 8 | const trait = meta.trait; | 8 | const trait = meta.trait; |
| 9 | const testing = std.testing; | 9 | const testing = std.testing; |
| 10 | 10 | ||
| 11 | // https://github.com/ziglang/zig/issues/2564 | 11 | /// https://github.com/ziglang/zig/issues/2564 |
| 12 | pub const page_size = switch (builtin.arch) { | 12 | pub 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 | }; |
| 16 | 16 | ||
| 17 | pub const Allocator = struct { | 17 | pub 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 | }; | ||
| 396 | 18 | ||
| 397 | /// Detects and asserts if the std.mem.Allocator interface is violated by the caller | 19 | /// 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 | } |
| 425 | 53 | ||
| 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 | }; |
| 484 | fn failAllocatorAlloc(self: *Allocator, n: usize, alignment: u29, len_align: u29) Allocator.Error![]u8 { | 121 | fn 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 | } |
| 487 | 124 |
lib/std/mem/Allocator.zig created+486| ... | @@ -0,0 +1,486 @@ | ||
| 1 | //! The standard memory allocation interface. | ||
| 2 | |||
| 3 | const std = @import("../std.zig"); | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const math = std.math; | ||
| 6 | const mem = std.mem; | ||
| 7 | const Allocator = @This(); | ||
| 8 | |||
| 9 | pub 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. | ||
| 20 | allocFn: 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. | ||
| 42 | resizeFn: 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. | ||
| 45 | pub 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. | ||
| 79 | fn 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. | ||
| 131 | fn 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. | ||
| 156 | pub 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. | ||
| 164 | pub 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`. | ||
| 180 | pub fn alloc(self: *Allocator, comptime T: type, n: usize) Error![]T { | ||
| 181 | return self.allocAdvancedWithRetAddr(T, null, n, .exact, @returnAddress()); | ||
| 182 | } | ||
| 183 | |||
| 184 | pub 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 | |||
| 195 | pub 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 | |||
| 213 | fn 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`. | ||
| 231 | pub 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` | ||
| 241 | pub 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 | |||
| 251 | pub 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 | |||
| 262 | pub const Exact = enum { exact, at_least }; | ||
| 263 | |||
| 264 | pub 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. | ||
| 307 | pub 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. | ||
| 332 | pub 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 | |||
| 340 | pub 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. | ||
| 351 | pub 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 | |||
| 361 | pub 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`. | ||
| 395 | pub 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. | ||
| 406 | pub 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. | ||
| 418 | pub 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`. | ||
| 446 | pub 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. | ||
| 458 | pub 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. | ||
| 465 | pub 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. | ||
| 476 | pub 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 acquired | 29 | /// // ... lock not acquired |
| 31 | /// } | 30 | /// } |
| 32 | pub const Mutex = if (builtin.single_threaded) | 31 | pub 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 | } | ||
| 76 | else if (builtin.os.tag == .windows) | 33 | else 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 | } | ||
| 164 | else if (builtin.link_libc or builtin.os.tag == .linux) | 35 | else 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.rs | 36 | // 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, |
| 168 | 39 | ||
| 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 | }; |
| 181 | 52 | ||
| 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) |
| 298 | else | 161 | else |
| 299 | SpinLock; | 162 | SpinLock; |
| 300 | 163 | ||
| 164 | /// This has the sematics as `Mutex`, however it does not actually do any | ||
| 165 | /// synchronization. Operations are safety-checked no-ops. | ||
| 166 | pub 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/ | ||
| 203 | const 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 | |||
| 301 | const TestContext = struct { | 286 | const 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 | }; |
| 307 | 292 | ||
| 308 | test "std.Mutex" { | 293 | test "std.Mutex" { |
| 309 | var mutex = Mutex.init(); | 294 | var mutex = Mutex{}; |
| 310 | defer mutex.deinit(); | ||
| 311 | 295 | ||
| 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) { |
| 10 | pub fn Once(comptime f: fn () void) type { | 10 | pub 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{}, |
| 14 | 14 | ||
| 15 | /// Call the function `f`. | 15 | /// Call the function `f`. |
| 16 | /// If `call` is invoked multiple times `f` will be executed only the | 16 | /// 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{}; |
| 21 | 21 | ||
| 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; |
| 25 | 31 | ||
| 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 | } |
| 77 | 83 |
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 |
| 13 | pub const DynLib = @import("dynamic_library.zig").DynLib; | 13 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 14 | pub const HashMap = hash_map.HashMap; | 14 | pub const HashMap = hash_map.HashMap; |
| 15 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; | 15 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; |
| 16 | pub const Mutex = @import("mutex.zig").Mutex; | 16 | pub const mutex = @import("mutex.zig"); |
| 17 | pub const Mutex = mutex.Mutex; | ||
| 17 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; | 18 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; |
| 18 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; | 19 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; |
| 19 | pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; | 20 | pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; |
lib/std/testing.zig+14-16| ... | @@ -1,18 +1,16 @@ | ... | @@ -1,18 +1,16 @@ |
| 1 | const std = @import("std.zig"); | 1 | const std = @import("std.zig"); |
| 2 | const warn = std.debug.warn; | 2 | const print = std.debug.print; |
| 3 | 3 | ||
| 4 | pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator; | ||
| 5 | pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; | 4 | pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; |
| 6 | 5 | ||
| 7 | /// This should only be used in temporary test programs. | 6 | /// This should only be used in temporary test programs. |
| 8 | pub const allocator = &allocator_instance.allocator; | 7 | pub const allocator = &allocator_instance.allocator; |
| 9 | pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.allocator); | 8 | pub var allocator_instance = std.heap.GeneralPurposeAllocator(.{}){}; |
| 10 | 9 | ||
| 11 | pub const failing_allocator = &failing_allocator_instance.allocator; | 10 | pub const failing_allocator = &failing_allocator_instance.allocator; |
| 12 | pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); | 11 | pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); |
| 13 | 12 | ||
| 14 | pub var base_allocator_instance = std.mem.validationWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); | 13 | pub var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); |
| 15 | var allocator_mem: [2 * 1024 * 1024]u8 = undefined; | ||
| 16 | 14 | ||
| 17 | /// TODO https://github.com/ziglang/zig/issues/5738 | 15 | /// TODO https://github.com/ziglang/zig/issues/5738 |
| 18 | pub var log_level = std.log.Level.warn; | 16 | pub var log_level = std.log.Level.warn; |
| ... | @@ -326,22 +324,22 @@ test "expectEqual vector" { | ... | @@ -326,22 +324,22 @@ test "expectEqual vector" { |
| 326 | 324 | ||
| 327 | pub fn expectEqualStrings(expected: []const u8, actual: []const u8) void { | 325 | pub 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", .{}); |
| 334 | 332 | ||
| 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}); |
| 340 | 338 | ||
| 341 | warn("expected:\n", .{}); | 339 | print("expected:\n", .{}); |
| 342 | printIndicatorLine(expected, diff_index); | 340 | printIndicatorLine(expected, diff_index); |
| 343 | 341 | ||
| 344 | warn("found:\n", .{}); | 342 | print("found:\n", .{}); |
| 345 | printIndicatorLine(actual, diff_index); | 343 | printIndicatorLine(actual, diff_index); |
| 346 | 344 | ||
| 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 | } |
| 369 | 367 | ||
| 370 | fn printWithVisibleNewlines(source: []const u8) void { | 368 | fn 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 | } |
| 377 | 375 | ||
| 378 | fn printLine(line: []const u8) void { | 376 | fn 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 | } |
| 385 | 383 | ||
| 386 | test "" { | 384 | test "" { |
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 | } |
| 47 | 47 | ||
| 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 | } |
| 59 | 65 | ||
| 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 @@ | ||
| 1 | const 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. | ||
| 8 | pub 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 and | 325 | /// 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 array | 326 | /// 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) = .{}, |
| 330 | 330 | ||
| 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) = .{}, |
| 334 | 334 | ||
| 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 entire | 336 | /// 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) = .{}, |
| 339 | 339 | ||
| 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 | } |
| 62 | 62 | ||
| 63 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; | ||
| 64 | |||
| 63 | pub fn main() !void { | 65 | pub fn main() !void { |
| 64 | // TODO general purpose allocator in the zig std lib | 66 | 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(); |
| 408 | 408 | ||
| 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; |
| 420 | 418 | ||
| 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 | } |
| 425 | 422 |
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 |
| 5886 | static LLVMValueRef ir_render_return_address(CodeGen *g, IrExecutableGen *executable, | 5886 | static 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 | } |
| 25069 | 25069 | ||
| 25070 | static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_entry) { | 25070 | static 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 | } |
| 25083 | 25083 | ||
| 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)); |
| 25089 | 25086 | ||
| ... | @@ -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: u32 | 25112 | // 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: type | 25126 | // 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: anytype | 25136 | // 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)); |
| 25167 | 25173 | ||
| 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 | } |
| 25553 | 25562 | ||
| 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); |