| author | |
| committer | |
| log | f68cda738ad0d3e9bc0f328befad301d9e23756e |
| tree | a08f43aa38d630a71f0686dc9bda9181454d1a0b |
| parent | 23866b1f81010277b204d6f3f5db23d020a76400 |
| signature |
9 files changed, 458 insertions(+), 160 deletions(-)
lib/std/heap.zig+107-34| ... | ... | @@ -132,10 +132,6 @@ const CAllocator = struct { |
| 132 | 132 | ) Allocator.Error!usize { |
| 133 | 133 | _ = buf_align; |
| 134 | 134 | _ = return_address; |
| 135 | if (new_len == 0) { | |
| 136 | alignedFree(buf.ptr); | |
| 137 | return 0; | |
| 138 | } | |
| 139 | 135 | if (new_len <= buf.len) { |
| 140 | 136 | return mem.alignAllocLen(buf.len, new_len, len_align); |
| 141 | 137 | } |
| ... | ... | @@ -147,6 +143,17 @@ const CAllocator = struct { |
| 147 | 143 | } |
| 148 | 144 | return error.OutOfMemory; |
| 149 | 145 | } |
| 146 | ||
| 147 | fn free( | |
| 148 | _: *c_void, | |
| 149 | buf: []u8, | |
| 150 | buf_align: u29, | |
| 151 | return_address: usize, | |
| 152 | ) void { | |
| 153 | _ = buf_align; | |
| 154 | _ = return_address; | |
| 155 | alignedFree(buf.ptr); | |
| 156 | } | |
| 150 | 157 | }; |
| 151 | 158 | |
| 152 | 159 | /// Supports the full Allocator interface, including alignment, and exploiting |
| ... | ... | @@ -159,6 +166,7 @@ pub const c_allocator = Allocator{ |
| 159 | 166 | const c_allocator_vtable = Allocator.VTable{ |
| 160 | 167 | .alloc = CAllocator.alloc, |
| 161 | 168 | .resize = CAllocator.resize, |
| 169 | .free = CAllocator.free, | |
| 162 | 170 | }; |
| 163 | 171 | |
| 164 | 172 | /// Asserts allocations are within `@alignOf(std.c.max_align_t)` and directly calls |
| ... | ... | @@ -173,6 +181,7 @@ pub const raw_c_allocator = Allocator{ |
| 173 | 181 | const raw_c_allocator_vtable = Allocator.VTable{ |
| 174 | 182 | .alloc = rawCAlloc, |
| 175 | 183 | .resize = rawCResize, |
| 184 | .free = rawCFree, | |
| 176 | 185 | }; |
| 177 | 186 | |
| 178 | 187 | fn rawCAlloc( |
| ... | ... | @@ -199,16 +208,23 @@ fn rawCResize( |
| 199 | 208 | ) Allocator.Error!usize { |
| 200 | 209 | _ = old_align; |
| 201 | 210 | _ = ret_addr; |
| 202 | if (new_len == 0) { | |
| 203 | c.free(buf.ptr); | |
| 204 | return 0; | |
| 205 | } | |
| 206 | 211 | if (new_len <= buf.len) { |
| 207 | 212 | return mem.alignAllocLen(buf.len, new_len, len_align); |
| 208 | 213 | } |
| 209 | 214 | return error.OutOfMemory; |
| 210 | 215 | } |
| 211 | 216 | |
| 217 | fn rawCFree( | |
| 218 | _: *c_void, | |
| 219 | buf: []u8, | |
| 220 | old_align: u29, | |
| 221 | ret_addr: usize, | |
| 222 | ) void { | |
| 223 | _ = old_align; | |
| 224 | _ = ret_addr; | |
| 225 | c.free(buf.ptr); | |
| 226 | } | |
| 227 | ||
| 212 | 228 | /// This allocator makes a syscall directly for every allocation and free. |
| 213 | 229 | /// Thread-safe and lock-free. |
| 214 | 230 | pub const page_allocator = if (builtin.target.isWasm()) |
| ... | ... | @@ -238,6 +254,7 @@ const PageAllocator = struct { |
| 238 | 254 | const vtable = Allocator.VTable{ |
| 239 | 255 | .alloc = alloc, |
| 240 | 256 | .resize = resize, |
| 257 | .free = free, | |
| 241 | 258 | }; |
| 242 | 259 | |
| 243 | 260 | fn alloc(_: *c_void, n: usize, alignment: u29, len_align: u29, ra: usize) error{OutOfMemory}![]u8 { |
| ... | ... | @@ -351,16 +368,6 @@ const PageAllocator = struct { |
| 351 | 368 | |
| 352 | 369 | if (builtin.os.tag == .windows) { |
| 353 | 370 | const w = os.windows; |
| 354 | if (new_size == 0) { | |
| 355 | // From the docs: | |
| 356 | // "If the dwFreeType parameter is MEM_RELEASE, this parameter | |
| 357 | // must be 0 (zero). The function frees the entire region that | |
| 358 | // is reserved in the initial allocation call to VirtualAlloc." | |
| 359 | // So we can only use MEM_RELEASE when actually releasing the | |
| 360 | // whole allocation. | |
| 361 | w.VirtualFree(buf_unaligned.ptr, 0, w.MEM_RELEASE); | |
| 362 | return 0; | |
| 363 | } | |
| 364 | 371 | if (new_size <= buf_unaligned.len) { |
| 365 | 372 | const base_addr = @ptrToInt(buf_unaligned.ptr); |
| 366 | 373 | const old_addr_end = base_addr + buf_unaligned.len; |
| ... | ... | @@ -391,8 +398,6 @@ const PageAllocator = struct { |
| 391 | 398 | const ptr = @alignCast(mem.page_size, buf_unaligned.ptr + new_size_aligned); |
| 392 | 399 | // TODO: if the next_mmap_addr_hint is within the unmapped range, update it |
| 393 | 400 | os.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]); |
| 394 | if (new_size_aligned == 0) | |
| 395 | return 0; | |
| 396 | 401 | return alignPageAllocLen(new_size_aligned, new_size, len_align); |
| 397 | 402 | } |
| 398 | 403 | |
| ... | ... | @@ -400,6 +405,19 @@ const PageAllocator = struct { |
| 400 | 405 | // TODO: if the next_mmap_addr_hint is within the remapped range, update it |
| 401 | 406 | return error.OutOfMemory; |
| 402 | 407 | } |
| 408 | ||
| 409 | fn free(_: *c_void, buf_unaligned: []u8, buf_align: u29, return_address: usize) void { | |
| 410 | _ = buf_align; | |
| 411 | _ = return_address; | |
| 412 | ||
| 413 | if (builtin.os.tag == .windows) { | |
| 414 | os.windows.VirtualFree(buf_unaligned.ptr, 0, os.windows.MEM_RELEASE); | |
| 415 | } else { | |
| 416 | const buf_aligned_len = mem.alignForward(buf_unaligned.len, mem.page_size); | |
| 417 | const ptr = @alignCast(mem.page_size, buf_unaligned.ptr); | |
| 418 | os.munmap(ptr[0..buf_aligned_len]); | |
| 419 | } | |
| 420 | } | |
| 403 | 421 | }; |
| 404 | 422 | |
| 405 | 423 | const WasmPageAllocator = struct { |
| ... | ... | @@ -412,6 +430,7 @@ const WasmPageAllocator = struct { |
| 412 | 430 | const vtable = Allocator.VTable{ |
| 413 | 431 | .alloc = alloc, |
| 414 | 432 | .resize = resize, |
| 433 | .free = free, | |
| 415 | 434 | }; |
| 416 | 435 | |
| 417 | 436 | const PageStatus = enum(u1) { |
| ... | ... | @@ -571,7 +590,21 @@ const WasmPageAllocator = struct { |
| 571 | 590 | const base = nPages(@ptrToInt(buf.ptr)); |
| 572 | 591 | freePages(base + new_n, base + current_n); |
| 573 | 592 | } |
| 574 | return if (new_len == 0) 0 else alignPageAllocLen(new_n * mem.page_size, new_len, len_align); | |
| 593 | return alignPageAllocLen(new_n * mem.page_size, new_len, len_align); | |
| 594 | } | |
| 595 | ||
| 596 | fn free( | |
| 597 | _: *c_void, | |
| 598 | buf: []u8, | |
| 599 | buf_align: u29, | |
| 600 | return_address: usize, | |
| 601 | ) void { | |
| 602 | _ = buf_align; | |
| 603 | _ = return_address; | |
| 604 | const aligned_len = mem.alignForward(buf.len, mem.page_size); | |
| 605 | const current_n = nPages(aligned_len); | |
| 606 | const base = nPages(@ptrToInt(buf.ptr)); | |
| 607 | freePages(base, base + current_n); | |
| 575 | 608 | } |
| 576 | 609 | }; |
| 577 | 610 | |
| ... | ... | @@ -588,7 +621,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 588 | 621 | } |
| 589 | 622 | |
| 590 | 623 | pub fn allocator(self: *HeapAllocator) Allocator { |
| 591 | return Allocator.init(self, alloc, resize); | |
| 624 | return Allocator.init(self, alloc, resize, free); | |
| 592 | 625 | } |
| 593 | 626 | |
| 594 | 627 | pub fn deinit(self: *HeapAllocator) void { |
| ... | ... | @@ -644,10 +677,6 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 644 | 677 | ) error{OutOfMemory}!usize { |
| 645 | 678 | _ = buf_align; |
| 646 | 679 | _ = return_address; |
| 647 | if (new_size == 0) { | |
| 648 | os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*)); | |
| 649 | return 0; | |
| 650 | } | |
| 651 | 680 | |
| 652 | 681 | const root_addr = getRecordPtr(buf).*; |
| 653 | 682 | const align_offset = @ptrToInt(buf.ptr) - root_addr; |
| ... | ... | @@ -669,6 +698,17 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 669 | 698 | getRecordPtr(buf.ptr[0..return_len]).* = root_addr; |
| 670 | 699 | return return_len; |
| 671 | 700 | } |
| 701 | ||
| 702 | fn free( | |
| 703 | self: *HeapAllocator, | |
| 704 | buf: []u8, | |
| 705 | buf_align: u29, | |
| 706 | return_address: usize, | |
| 707 | ) void { | |
| 708 | _ = buf_align; | |
| 709 | _ = return_address; | |
| 710 | os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*)); | |
| 711 | } | |
| 672 | 712 | }, |
| 673 | 713 | else => @compileError("Unsupported OS"), |
| 674 | 714 | }; |
| ... | ... | @@ -696,13 +736,18 @@ pub const FixedBufferAllocator = struct { |
| 696 | 736 | |
| 697 | 737 | /// *WARNING* using this at the same time as the interface returned by `threadSafeAllocator` is not thread safe |
| 698 | 738 | pub fn allocator(self: *FixedBufferAllocator) Allocator { |
| 699 | return Allocator.init(self, alloc, resize); | |
| 739 | return Allocator.init(self, alloc, resize, free); | |
| 700 | 740 | } |
| 701 | 741 | |
| 702 | 742 | /// Provides a lock free thread safe `Allocator` interface to the underlying `FixedBufferAllocator` |
| 703 | 743 | /// *WARNING* using this at the same time as the interface returned by `getAllocator` is not thread safe |
| 704 | 744 | pub fn threadSafeAllocator(self: *FixedBufferAllocator) Allocator { |
| 705 | return Allocator.init(self, threadSafeAlloc, Allocator.NoResize(FixedBufferAllocator).noResize); | |
| 745 | return Allocator.init( | |
| 746 | self, | |
| 747 | threadSafeAlloc, | |
| 748 | Allocator.NoResize(FixedBufferAllocator).noResize, | |
| 749 | Allocator.NoOpFree(FixedBufferAllocator).noOpFree, | |
| 750 | ); | |
| 706 | 751 | } |
| 707 | 752 | |
| 708 | 753 | pub fn ownsPtr(self: *FixedBufferAllocator, ptr: [*]u8) bool { |
| ... | ... | @@ -715,7 +760,7 @@ pub const FixedBufferAllocator = struct { |
| 715 | 760 | |
| 716 | 761 | /// NOTE: this will not work in all cases, if the last allocation had an adjusted_index |
| 717 | 762 | /// then we won't be able to determine what the last allocation was. This is because |
| 718 | /// the alignForward operation done in alloc is not reverisible. | |
| 763 | /// the alignForward operation done in alloc is not reversible. | |
| 719 | 764 | pub fn isLastAllocation(self: *FixedBufferAllocator, buf: []u8) bool { |
| 720 | 765 | return buf.ptr + buf.len == self.buffer.ptr + self.end_index; |
| 721 | 766 | } |
| ... | ... | @@ -751,13 +796,13 @@ pub const FixedBufferAllocator = struct { |
| 751 | 796 | if (!self.isLastAllocation(buf)) { |
| 752 | 797 | if (new_size > buf.len) |
| 753 | 798 | return error.OutOfMemory; |
| 754 | return if (new_size == 0) 0 else mem.alignAllocLen(buf.len, new_size, len_align); | |
| 799 | return mem.alignAllocLen(buf.len, new_size, len_align); | |
| 755 | 800 | } |
| 756 | 801 | |
| 757 | 802 | if (new_size <= buf.len) { |
| 758 | 803 | const sub = buf.len - new_size; |
| 759 | 804 | self.end_index -= sub; |
| 760 | return if (new_size == 0) 0 else mem.alignAllocLen(buf.len - sub, new_size, len_align); | |
| 805 | return mem.alignAllocLen(buf.len - sub, new_size, len_align); | |
| 761 | 806 | } |
| 762 | 807 | |
| 763 | 808 | const add = new_size - buf.len; |
| ... | ... | @@ -768,6 +813,21 @@ pub const FixedBufferAllocator = struct { |
| 768 | 813 | return new_size; |
| 769 | 814 | } |
| 770 | 815 | |
| 816 | fn free( | |
| 817 | self: *FixedBufferAllocator, | |
| 818 | buf: []u8, | |
| 819 | buf_align: u29, | |
| 820 | return_address: usize, | |
| 821 | ) void { | |
| 822 | _ = buf_align; | |
| 823 | _ = return_address; | |
| 824 | assert(self.ownsSlice(buf)); // sanity check | |
| 825 | ||
| 826 | if (self.isLastAllocation(buf)) { | |
| 827 | self.end_index -= buf.len; | |
| 828 | } | |
| 829 | } | |
| 830 | ||
| 771 | 831 | fn threadSafeAlloc(self: *FixedBufferAllocator, n: usize, ptr_align: u29, len_align: u29, ra: usize) ![]u8 { |
| 772 | 832 | _ = len_align; |
| 773 | 833 | _ = ra; |
| ... | ... | @@ -810,7 +870,7 @@ pub fn StackFallbackAllocator(comptime size: usize) type { |
| 810 | 870 | /// WARNING: This functions both fetches a `std.mem.Allocator` interface to this allocator *and* resets the internal buffer allocator |
| 811 | 871 | pub fn get(self: *Self) Allocator { |
| 812 | 872 | self.fixed_buffer_allocator = FixedBufferAllocator.init(self.buffer[0..]); |
| 813 | return Allocator.init(self, alloc, resize); | |
| 873 | return Allocator.init(self, alloc, resize, free); | |
| 814 | 874 | } |
| 815 | 875 | |
| 816 | 876 | fn alloc( |
| ... | ... | @@ -821,7 +881,7 @@ pub fn StackFallbackAllocator(comptime size: usize) type { |
| 821 | 881 | return_address: usize, |
| 822 | 882 | ) error{OutOfMemory}![]u8 { |
| 823 | 883 | return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, ptr_align, len_align, return_address) catch |
| 824 | return self.fallback_allocator.vtable.alloc(self.fallback_allocator.ptr, len, ptr_align, len_align, return_address); | |
| 884 | return self.fallback_allocator.rawAlloc(len, ptr_align, len_align, return_address); | |
| 825 | 885 | } |
| 826 | 886 | |
| 827 | 887 | fn resize( |
| ... | ... | @@ -835,7 +895,20 @@ pub fn StackFallbackAllocator(comptime size: usize) type { |
| 835 | 895 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { |
| 836 | 896 | return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, buf_align, new_len, len_align, return_address); |
| 837 | 897 | } else { |
| 838 | return self.fallback_allocator.vtable.resize(self.fallback_allocator.ptr, buf, buf_align, new_len, len_align, return_address); | |
| 898 | return self.fallback_allocator.rawResize(buf, buf_align, new_len, len_align, return_address); | |
| 899 | } | |
| 900 | } | |
| 901 | ||
| 902 | fn free( | |
| 903 | self: *Self, | |
| 904 | buf: []u8, | |
| 905 | buf_align: u29, | |
| 906 | return_address: usize, | |
| 907 | ) void { | |
| 908 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { | |
| 909 | return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, buf_align, return_address); | |
| 910 | } else { | |
| 911 | return self.fallback_allocator.rawFree(buf, buf_align, return_address); | |
| 839 | 912 | } |
| 840 | 913 | } |
| 841 | 914 | }; |
lib/std/heap/arena_allocator.zig+14-2| ... | ... | @@ -24,7 +24,7 @@ pub const ArenaAllocator = struct { |
| 24 | 24 | }; |
| 25 | 25 | |
| 26 | 26 | pub fn allocator(self: *ArenaAllocator) Allocator { |
| 27 | return Allocator.init(self, alloc, resize); | |
| 27 | return Allocator.init(self, alloc, resize, free); | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | const BufNode = std.SinglyLinkedList([]u8).Node; |
| ... | ... | @@ -47,7 +47,7 @@ pub const ArenaAllocator = struct { |
| 47 | 47 | const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16); |
| 48 | 48 | const big_enough_len = prev_len + actual_min_size; |
| 49 | 49 | const len = big_enough_len + big_enough_len / 2; |
| 50 | const buf = try self.child_allocator.vtable.alloc(self.child_allocator.ptr, len, @alignOf(BufNode), 1, @returnAddress()); | |
| 50 | const buf = try self.child_allocator.rawAlloc(len, @alignOf(BufNode), 1, @returnAddress()); | |
| 51 | 51 | const buf_node = @ptrCast(*BufNode, @alignCast(@alignOf(BufNode), buf.ptr)); |
| 52 | 52 | buf_node.* = BufNode{ |
| 53 | 53 | .data = buf, |
| ... | ... | @@ -111,4 +111,16 @@ pub const ArenaAllocator = struct { |
| 111 | 111 | return error.OutOfMemory; |
| 112 | 112 | } |
| 113 | 113 | } |
| 114 | ||
| 115 | fn free(self: *ArenaAllocator, buf: []u8, buf_align: u29, ret_addr: usize) void { | |
| 116 | _ = buf_align; | |
| 117 | _ = ret_addr; | |
| 118 | ||
| 119 | const cur_node = self.state.buffer_list.first orelse return; | |
| 120 | const cur_buf = cur_node.data[@sizeOf(BufNode)..]; | |
| 121 | ||
| 122 | if (@ptrToInt(cur_buf.ptr) + self.state.end_index == @ptrToInt(buf.ptr) + buf.len) { | |
| 123 | self.state.end_index -= buf.len; | |
| 124 | } | |
| 125 | } | |
| 114 | 126 | }; |
lib/std/heap/general_purpose_allocator.zig+171-74| ... | ... | @@ -281,7 +281,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 281 | 281 | }; |
| 282 | 282 | |
| 283 | 283 | pub fn allocator(self: *Self) Allocator { |
| 284 | return Allocator.init(self, alloc, resize); | |
| 284 | return Allocator.init(self, alloc, resize, free); | |
| 285 | 285 | } |
| 286 | 286 | |
| 287 | 287 | fn bucketStackTrace( |
| ... | ... | @@ -388,7 +388,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 388 | 388 | var it = self.large_allocations.iterator(); |
| 389 | 389 | while (it.next()) |large| { |
| 390 | 390 | if (large.value_ptr.freed) { |
| 391 | _ = self.backing_allocator.vtable.resize(self.backing_allocator.ptr, large.value_ptr.bytes, large.value_ptr.ptr_align, 0, 0, @returnAddress()) catch unreachable; | |
| 391 | self.backing_allocator.rawFree(large.value_ptr.bytes, large.value_ptr.ptr_align, @returnAddress()); | |
| 392 | 392 | } |
| 393 | 393 | } |
| 394 | 394 | } |
| ... | ... | @@ -529,9 +529,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 529 | 529 | if (config.retain_metadata and entry.value_ptr.freed) { |
| 530 | 530 | if (config.safety) { |
| 531 | 531 | reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); |
| 532 | // Recoverable if this is a free. | |
| 533 | if (new_size == 0) | |
| 534 | return @as(usize, 0); | |
| 535 | 532 | @panic("Unrecoverable double free"); |
| 536 | 533 | } else { |
| 537 | 534 | unreachable; |
| ... | ... | @@ -555,7 +552,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 555 | 552 | |
| 556 | 553 | // Do memory limit accounting with requested sizes rather than what backing_allocator returns |
| 557 | 554 | // because if we want to return error.OutOfMemory, we have to leave allocation untouched, and |
| 558 | // that is impossible to guarantee after calling backing_allocator.vtable.resize. | |
| 555 | // that is impossible to guarantee after calling backing_allocator.rawResize. | |
| 559 | 556 | const prev_req_bytes = self.total_requested_bytes; |
| 560 | 557 | if (config.enable_memory_limit) { |
| 561 | 558 | const new_req_bytes = prev_req_bytes + new_size - entry.value_ptr.requested_size; |
| ... | ... | @@ -568,29 +565,12 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 568 | 565 | self.total_requested_bytes = prev_req_bytes; |
| 569 | 566 | }; |
| 570 | 567 | |
| 571 | const result_len = if (config.never_unmap and new_size == 0) | |
| 572 | 0 | |
| 573 | else | |
| 574 | try self.backing_allocator.vtable.resize(self.backing_allocator.ptr, old_mem, old_align, new_size, len_align, ret_addr); | |
| 568 | const result_len = try self.backing_allocator.rawResize(old_mem, old_align, new_size, len_align, ret_addr); | |
| 575 | 569 | |
| 576 | 570 | if (config.enable_memory_limit) { |
| 577 | 571 | entry.value_ptr.requested_size = new_size; |
| 578 | 572 | } |
| 579 | 573 | |
| 580 | if (result_len == 0) { | |
| 581 | if (config.verbose_log) { | |
| 582 | log.info("large free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr }); | |
| 583 | } | |
| 584 | ||
| 585 | if (!config.retain_metadata) { | |
| 586 | assert(self.large_allocations.remove(@ptrToInt(old_mem.ptr))); | |
| 587 | } else { | |
| 588 | entry.value_ptr.freed = true; | |
| 589 | entry.value_ptr.captureStackTrace(ret_addr, .free); | |
| 590 | } | |
| 591 | return 0; | |
| 592 | } | |
| 593 | ||
| 594 | 574 | if (config.verbose_log) { |
| 595 | 575 | log.info("large resize {d} bytes at {*} to {d}", .{ |
| 596 | 576 | old_mem.len, old_mem.ptr, new_size, |
| ... | ... | @@ -601,6 +581,64 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 601 | 581 | return result_len; |
| 602 | 582 | } |
| 603 | 583 | |
| 584 | /// This function assumes the object is in the large object storage regardless | |
| 585 | /// of the parameters. | |
| 586 | fn freeLarge( | |
| 587 | self: *Self, | |
| 588 | old_mem: []u8, | |
| 589 | old_align: u29, | |
| 590 | ret_addr: usize, | |
| 591 | ) void { | |
| 592 | _ = old_align; | |
| 593 | ||
| 594 | const entry = self.large_allocations.getEntry(@ptrToInt(old_mem.ptr)) orelse { | |
| 595 | if (config.safety) { | |
| 596 | @panic("Invalid free"); | |
| 597 | } else { | |
| 598 | unreachable; | |
| 599 | } | |
| 600 | }; | |
| 601 | ||
| 602 | if (config.retain_metadata and entry.value_ptr.freed) { | |
| 603 | if (config.safety) { | |
| 604 | reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); | |
| 605 | return; | |
| 606 | } else { | |
| 607 | unreachable; | |
| 608 | } | |
| 609 | } | |
| 610 | ||
| 611 | if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { | |
| 612 | var addresses: [stack_n]usize = [1]usize{0} ** stack_n; | |
| 613 | var free_stack_trace = StackTrace{ | |
| 614 | .instruction_addresses = &addresses, | |
| 615 | .index = 0, | |
| 616 | }; | |
| 617 | std.debug.captureStackTrace(ret_addr, &free_stack_trace); | |
| 618 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {s} Free: {s}", .{ | |
| 619 | entry.value_ptr.bytes.len, | |
| 620 | old_mem.len, | |
| 621 | entry.value_ptr.getStackTrace(.alloc), | |
| 622 | free_stack_trace, | |
| 623 | }); | |
| 624 | } | |
| 625 | ||
| 626 | if (config.enable_memory_limit) { | |
| 627 | self.total_requested_bytes -= entry.value_ptr.requested_size; | |
| 628 | } | |
| 629 | ||
| 630 | if (config.verbose_log) { | |
| 631 | log.info("large free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr }); | |
| 632 | } | |
| 633 | ||
| 634 | if (!config.retain_metadata) { | |
| 635 | assert(self.large_allocations.remove(@ptrToInt(old_mem.ptr))); | |
| 636 | } else { | |
| 637 | entry.value_ptr.freed = true; | |
| 638 | entry.value_ptr.captureStackTrace(ret_addr, .free); | |
| 639 | } | |
| 640 | } | |
| 641 | ||
| 604 | 642 | pub fn setRequestedMemoryLimit(self: *Self, limit: usize) void { |
| 605 | 643 | self.requested_memory_limit = limit; |
| 606 | 644 | } |
| ... | ... | @@ -656,9 +694,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 656 | 694 | if (!is_used) { |
| 657 | 695 | if (config.safety) { |
| 658 | 696 | reportDoubleFree(ret_addr, bucketStackTrace(bucket, size_class, slot_index, .alloc), bucketStackTrace(bucket, size_class, slot_index, .free)); |
| 659 | // Recoverable if this is a free. | |
| 660 | if (new_size == 0) | |
| 661 | return @as(usize, 0); | |
| 662 | 697 | @panic("Unrecoverable double free"); |
| 663 | 698 | } else { |
| 664 | 699 | unreachable; |
| ... | ... | @@ -678,52 +713,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 678 | 713 | self.total_requested_bytes = prev_req_bytes; |
| 679 | 714 | }; |
| 680 | 715 | |
| 681 | if (new_size == 0) { | |
| 682 | // Capture stack trace to be the "first free", in case a double free happens. | |
| 683 | bucket.captureStackTrace(ret_addr, size_class, slot_index, .free); | |
| 684 | ||
| 685 | used_byte.* &= ~(@as(u8, 1) << used_bit_index); | |
| 686 | bucket.used_count -= 1; | |
| 687 | if (bucket.used_count == 0) { | |
| 688 | if (bucket.next == bucket) { | |
| 689 | // it's the only bucket and therefore the current one | |
| 690 | self.buckets[bucket_index] = null; | |
| 691 | } else { | |
| 692 | bucket.next.prev = bucket.prev; | |
| 693 | bucket.prev.next = bucket.next; | |
| 694 | self.buckets[bucket_index] = bucket.prev; | |
| 695 | } | |
| 696 | if (!config.never_unmap) { | |
| 697 | self.backing_allocator.free(bucket.page[0..page_size]); | |
| 698 | } | |
| 699 | if (!config.retain_metadata) { | |
| 700 | self.freeBucket(bucket, size_class); | |
| 701 | } else { | |
| 702 | // move alloc_cursor to end so we can tell size_class later | |
| 703 | const slot_count = @divExact(page_size, size_class); | |
| 704 | bucket.alloc_cursor = @truncate(SlotIndex, slot_count); | |
| 705 | if (self.empty_buckets) |prev_bucket| { | |
| 706 | // empty_buckets is ordered newest to oldest through prev so that if | |
| 707 | // config.never_unmap is false and backing_allocator reuses freed memory | |
| 708 | // then searchBuckets will always return the newer, relevant bucket | |
| 709 | bucket.prev = prev_bucket; | |
| 710 | bucket.next = prev_bucket.next; | |
| 711 | prev_bucket.next = bucket; | |
| 712 | bucket.next.prev = bucket; | |
| 713 | } else { | |
| 714 | bucket.prev = bucket; | |
| 715 | bucket.next = bucket; | |
| 716 | } | |
| 717 | self.empty_buckets = bucket; | |
| 718 | } | |
| 719 | } else { | |
| 720 | @memset(old_mem.ptr, undefined, old_mem.len); | |
| 721 | } | |
| 722 | if (config.verbose_log) { | |
| 723 | log.info("small free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr }); | |
| 724 | } | |
| 725 | return @as(usize, 0); | |
| 726 | } | |
| 727 | 716 | const new_aligned_size = math.max(new_size, old_align); |
| 728 | 717 | const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size); |
| 729 | 718 | if (new_size_class <= size_class) { |
| ... | ... | @@ -740,6 +729,114 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 740 | 729 | return error.OutOfMemory; |
| 741 | 730 | } |
| 742 | 731 | |
| 732 | fn free( | |
| 733 | self: *Self, | |
| 734 | old_mem: []u8, | |
| 735 | old_align: u29, | |
| 736 | ret_addr: usize, | |
| 737 | ) void { | |
| 738 | const held = self.mutex.acquire(); | |
| 739 | defer held.release(); | |
| 740 | ||
| 741 | assert(old_mem.len != 0); | |
| 742 | ||
| 743 | const aligned_size = math.max(old_mem.len, old_align); | |
| 744 | if (aligned_size > largest_bucket_object_size) { | |
| 745 | self.freeLarge(old_mem, old_align, ret_addr); | |
| 746 | return; | |
| 747 | } | |
| 748 | const size_class_hint = math.ceilPowerOfTwoAssert(usize, aligned_size); | |
| 749 | ||
| 750 | var bucket_index = math.log2(size_class_hint); | |
| 751 | var size_class: usize = size_class_hint; | |
| 752 | const bucket = while (bucket_index < small_bucket_count) : (bucket_index += 1) { | |
| 753 | if (searchBucket(self.buckets[bucket_index], @ptrToInt(old_mem.ptr))) |bucket| { | |
| 754 | // move bucket to head of list to optimize search for nearby allocations | |
| 755 | self.buckets[bucket_index] = bucket; | |
| 756 | break bucket; | |
| 757 | } | |
| 758 | size_class *= 2; | |
| 759 | } else blk: { | |
| 760 | if (config.retain_metadata) { | |
| 761 | if (!self.large_allocations.contains(@ptrToInt(old_mem.ptr))) { | |
| 762 | // object not in active buckets or a large allocation, so search empty buckets | |
| 763 | if (searchBucket(self.empty_buckets, @ptrToInt(old_mem.ptr))) |bucket| { | |
| 764 | // bucket is empty so is_used below will always be false and we exit there | |
| 765 | break :blk bucket; | |
| 766 | } else { | |
| 767 | @panic("Invalid free"); | |
| 768 | } | |
| 769 | } | |
| 770 | } | |
| 771 | self.freeLarge(old_mem, old_align, ret_addr); | |
| 772 | return; | |
| 773 | }; | |
| 774 | const byte_offset = @ptrToInt(old_mem.ptr) - @ptrToInt(bucket.page); | |
| 775 | const slot_index = @intCast(SlotIndex, byte_offset / size_class); | |
| 776 | const used_byte_index = slot_index / 8; | |
| 777 | const used_bit_index = @intCast(u3, slot_index % 8); | |
| 778 | const used_byte = bucket.usedBits(used_byte_index); | |
| 779 | const is_used = @truncate(u1, used_byte.* >> used_bit_index) != 0; | |
| 780 | if (!is_used) { | |
| 781 | if (config.safety) { | |
| 782 | reportDoubleFree(ret_addr, bucketStackTrace(bucket, size_class, slot_index, .alloc), bucketStackTrace(bucket, size_class, slot_index, .free)); | |
| 783 | // Recoverable if this is a free. | |
| 784 | return; | |
| 785 | } else { | |
| 786 | unreachable; | |
| 787 | } | |
| 788 | } | |
| 789 | ||
| 790 | // Definitely an in-use small alloc now. | |
| 791 | if (config.enable_memory_limit) { | |
| 792 | self.total_requested_bytes -= old_mem.len; | |
| 793 | } | |
| 794 | ||
| 795 | // Capture stack trace to be the "first free", in case a double free happens. | |
| 796 | bucket.captureStackTrace(ret_addr, size_class, slot_index, .free); | |
| 797 | ||
| 798 | used_byte.* &= ~(@as(u8, 1) << used_bit_index); | |
| 799 | bucket.used_count -= 1; | |
| 800 | if (bucket.used_count == 0) { | |
| 801 | if (bucket.next == bucket) { | |
| 802 | // it's the only bucket and therefore the current one | |
| 803 | self.buckets[bucket_index] = null; | |
| 804 | } else { | |
| 805 | bucket.next.prev = bucket.prev; | |
| 806 | bucket.prev.next = bucket.next; | |
| 807 | self.buckets[bucket_index] = bucket.prev; | |
| 808 | } | |
| 809 | if (!config.never_unmap) { | |
| 810 | self.backing_allocator.free(bucket.page[0..page_size]); | |
| 811 | } | |
| 812 | if (!config.retain_metadata) { | |
| 813 | self.freeBucket(bucket, size_class); | |
| 814 | } else { | |
| 815 | // move alloc_cursor to end so we can tell size_class later | |
| 816 | const slot_count = @divExact(page_size, size_class); | |
| 817 | bucket.alloc_cursor = @truncate(SlotIndex, slot_count); | |
| 818 | if (self.empty_buckets) |prev_bucket| { | |
| 819 | // empty_buckets is ordered newest to oldest through prev so that if | |
| 820 | // config.never_unmap is false and backing_allocator reuses freed memory | |
| 821 | // then searchBuckets will always return the newer, relevant bucket | |
| 822 | bucket.prev = prev_bucket; | |
| 823 | bucket.next = prev_bucket.next; | |
| 824 | prev_bucket.next = bucket; | |
| 825 | bucket.next.prev = bucket; | |
| 826 | } else { | |
| 827 | bucket.prev = bucket; | |
| 828 | bucket.next = bucket; | |
| 829 | } | |
| 830 | self.empty_buckets = bucket; | |
| 831 | } | |
| 832 | } else { | |
| 833 | @memset(old_mem.ptr, undefined, old_mem.len); | |
| 834 | } | |
| 835 | if (config.verbose_log) { | |
| 836 | log.info("small free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr }); | |
| 837 | } | |
| 838 | } | |
| 839 | ||
| 743 | 840 | // Returns true if an allocation of `size` bytes is within the specified |
| 744 | 841 | // limits if enable_memory_limit is true |
| 745 | 842 | fn isAllocationAllowed(self: *Self, size: usize) bool { |
| ... | ... | @@ -764,7 +861,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 764 | 861 | const new_aligned_size = math.max(len, ptr_align); |
| 765 | 862 | if (new_aligned_size > largest_bucket_object_size) { |
| 766 | 863 | try self.large_allocations.ensureUnusedCapacity(self.backing_allocator, 1); |
| 767 | const slice = try self.backing_allocator.vtable.alloc(self.backing_allocator.ptr, len, ptr_align, len_align, ret_addr); | |
| 864 | const slice = try self.backing_allocator.rawAlloc(len, ptr_align, len_align, ret_addr); | |
| 768 | 865 | |
| 769 | 866 | const gop = self.large_allocations.getOrPutAssumeCapacity(@ptrToInt(slice.ptr)); |
| 770 | 867 | if (config.retain_metadata and !config.never_unmap) { |
lib/std/heap/log_to_writer_allocator.zig+14-6| ... | ... | @@ -18,7 +18,7 @@ pub fn LogToWriterAllocator(comptime Writer: type) type { |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | 20 | pub fn allocator(self: *Self) Allocator { |
| 21 | return Allocator.init(self, alloc, resize); | |
| 21 | return Allocator.init(self, alloc, resize, free); | |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | 24 | fn alloc( |
| ... | ... | @@ -29,7 +29,7 @@ pub fn LogToWriterAllocator(comptime Writer: type) type { |
| 29 | 29 | ra: usize, |
| 30 | 30 | ) error{OutOfMemory}![]u8 { |
| 31 | 31 | self.writer.print("alloc : {}", .{len}) catch {}; |
| 32 | const result = self.parent_allocator.vtable.alloc(self.parent_allocator.ptr, len, ptr_align, len_align, ra); | |
| 32 | const result = self.parent_allocator.rawAlloc(len, ptr_align, len_align, ra); | |
| 33 | 33 | if (result) |_| { |
| 34 | 34 | self.writer.print(" success!\n", .{}) catch {}; |
| 35 | 35 | } else |_| { |
| ... | ... | @@ -46,14 +46,12 @@ pub fn LogToWriterAllocator(comptime Writer: type) type { |
| 46 | 46 | len_align: u29, |
| 47 | 47 | ra: usize, |
| 48 | 48 | ) error{OutOfMemory}!usize { |
| 49 | if (new_len == 0) { | |
| 50 | self.writer.print("free : {}\n", .{buf.len}) catch {}; | |
| 51 | } else if (new_len <= buf.len) { | |
| 49 | if (new_len <= buf.len) { | |
| 52 | 50 | self.writer.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {}; |
| 53 | 51 | } else { |
| 54 | 52 | self.writer.print("expand: {} to {}", .{ buf.len, new_len }) catch {}; |
| 55 | 53 | } |
| 56 | if (self.parent_allocator.vtable.resize(self.parent_allocator.ptr, buf, buf_align, new_len, len_align, ra)) |resized_len| { | |
| 54 | if (self.parent_allocator.rawResize(buf, buf_align, new_len, len_align, ra)) |resized_len| { | |
| 57 | 55 | if (new_len > buf.len) { |
| 58 | 56 | self.writer.print(" success!\n", .{}) catch {}; |
| 59 | 57 | } |
| ... | ... | @@ -64,6 +62,16 @@ pub fn LogToWriterAllocator(comptime Writer: type) type { |
| 64 | 62 | return e; |
| 65 | 63 | } |
| 66 | 64 | } |
| 65 | ||
| 66 | fn free( | |
| 67 | self: *Self, | |
| 68 | buf: []u8, | |
| 69 | buf_align: u29, | |
| 70 | ra: usize, | |
| 71 | ) void { | |
| 72 | self.writer.print("free : {}\n", .{buf.len}) catch {}; | |
| 73 | self.parent_allocator.rawFree(buf, buf_align, ra); | |
| 74 | } | |
| 67 | 75 | }; |
| 68 | 76 | } |
| 69 | 77 |
lib/std/heap/logging_allocator.zig+14-6| ... | ... | @@ -33,7 +33,7 @@ pub fn ScopedLoggingAllocator( |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | 35 | pub fn allocator(self: *Self) Allocator { |
| 36 | return Allocator.init(self, alloc, resize); | |
| 36 | return Allocator.init(self, alloc, resize, free); | |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | 39 | // This function is required as the `std.log.log` function is not public |
| ... | ... | @@ -53,7 +53,7 @@ pub fn ScopedLoggingAllocator( |
| 53 | 53 | len_align: u29, |
| 54 | 54 | ra: usize, |
| 55 | 55 | ) error{OutOfMemory}![]u8 { |
| 56 | const result = self.parent_allocator.vtable.alloc(self.parent_allocator.ptr, len, ptr_align, len_align, ra); | |
| 56 | const result = self.parent_allocator.rawAlloc(len, ptr_align, len_align, ra); | |
| 57 | 57 | if (result) |_| { |
| 58 | 58 | logHelper( |
| 59 | 59 | success_log_level, |
| ... | ... | @@ -78,10 +78,8 @@ pub fn ScopedLoggingAllocator( |
| 78 | 78 | len_align: u29, |
| 79 | 79 | ra: usize, |
| 80 | 80 | ) error{OutOfMemory}!usize { |
| 81 | if (self.parent_allocator.vtable.resize(self.parent_allocator.ptr, buf, buf_align, new_len, len_align, ra)) |resized_len| { | |
| 82 | if (new_len == 0) { | |
| 83 | logHelper(success_log_level, "free - success - len: {}", .{buf.len}); | |
| 84 | } else if (new_len <= buf.len) { | |
| 81 | if (self.parent_allocator.rawResize(buf, buf_align, new_len, len_align, ra)) |resized_len| { | |
| 82 | if (new_len <= buf.len) { | |
| 85 | 83 | logHelper( |
| 86 | 84 | success_log_level, |
| 87 | 85 | "shrink - success - {} to {}, len_align: {}, buf_align: {}", |
| ... | ... | @@ -106,6 +104,16 @@ pub fn ScopedLoggingAllocator( |
| 106 | 104 | return err; |
| 107 | 105 | } |
| 108 | 106 | } |
| 107 | ||
| 108 | fn free( | |
| 109 | self: *Self, | |
| 110 | buf: []u8, | |
| 111 | buf_align: u29, | |
| 112 | ra: usize, | |
| 113 | ) void { | |
| 114 | self.parent_allocator.rawFree(buf, buf_align, ra); | |
| 115 | logHelper(success_log_level, "free - len: {}", .{buf.len}); | |
| 116 | } | |
| 109 | 117 | }; |
| 110 | 118 | } |
| 111 | 119 |
lib/std/mem.zig+17-3| ... | ... | @@ -47,7 +47,7 @@ pub fn ValidationAllocator(comptime T: type) type { |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | pub fn allocator(self: *Self) Allocator { |
| 50 | return Allocator.init(self, alloc, resize); | |
| 50 | return Allocator.init(self, alloc, resize, free); | |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | 53 | fn getUnderlyingAllocatorPtr(self: *Self) Allocator { |
| ... | ... | @@ -70,7 +70,7 @@ pub fn ValidationAllocator(comptime T: type) type { |
| 70 | 70 | } |
| 71 | 71 | |
| 72 | 72 | const underlying = self.getUnderlyingAllocatorPtr(); |
| 73 | const result = try underlying.vtable.alloc(underlying.ptr, n, ptr_align, len_align, ret_addr); | |
| 73 | const result = try underlying.rawAlloc(n, ptr_align, len_align, ret_addr); | |
| 74 | 74 | assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align)); |
| 75 | 75 | if (len_align == 0) { |
| 76 | 76 | assert(result.len == n); |
| ... | ... | @@ -95,7 +95,7 @@ pub fn ValidationAllocator(comptime T: type) type { |
| 95 | 95 | assert(new_len >= len_align); |
| 96 | 96 | } |
| 97 | 97 | const underlying = self.getUnderlyingAllocatorPtr(); |
| 98 | const result = try underlying.vtable.resize(underlying.ptr, buf, buf_align, new_len, len_align, ret_addr); | |
| 98 | const result = try underlying.rawResize(buf, buf_align, new_len, len_align, ret_addr); | |
| 99 | 99 | if (len_align == 0) { |
| 100 | 100 | assert(result == new_len); |
| 101 | 101 | } else { |
| ... | ... | @@ -104,6 +104,19 @@ pub fn ValidationAllocator(comptime T: type) type { |
| 104 | 104 | } |
| 105 | 105 | return result; |
| 106 | 106 | } |
| 107 | ||
| 108 | pub fn free( | |
| 109 | self: *Self, | |
| 110 | buf: []u8, | |
| 111 | buf_align: u29, | |
| 112 | ret_addr: usize, | |
| 113 | ) void { | |
| 114 | _ = self; | |
| 115 | _ = buf_align; | |
| 116 | _ = ret_addr; | |
| 117 | assert(buf.len > 0); | |
| 118 | } | |
| 119 | ||
| 107 | 120 | pub usingnamespace if (T == Allocator or !@hasDecl(T, "reset")) struct {} else struct { |
| 108 | 121 | pub fn reset(self: *Self) void { |
| 109 | 122 | self.underlying_allocator.reset(); |
| ... | ... | @@ -139,6 +152,7 @@ const fail_allocator = Allocator{ |
| 139 | 152 | const failAllocator_vtable = Allocator.VTable{ |
| 140 | 153 | .alloc = failAllocatorAlloc, |
| 141 | 154 | .resize = Allocator.NoResize(c_void).noResize, |
| 155 | .free = Allocator.NoOpFree(c_void).noOpFree, | |
| 142 | 156 | }; |
| 143 | 157 | |
| 144 | 158 | fn failAllocatorAlloc(_: *c_void, n: usize, alignment: u29, len_align: u29, ra: usize) Allocator.Error![]u8 { |
lib/std/mem/Allocator.zig+93-21| ... | ... | @@ -5,6 +5,7 @@ const assert = std.debug.assert; |
| 5 | 5 | const math = std.math; |
| 6 | 6 | const mem = std.mem; |
| 7 | 7 | const Allocator = @This(); |
| 8 | const builtin = @import("builtin"); | |
| 8 | 9 | |
| 9 | 10 | pub const Error = error{OutOfMemory}; |
| 10 | 11 | |
| ... | ... | @@ -28,9 +29,6 @@ pub const VTable = struct { |
| 28 | 29 | /// length returned by `alloc` or `resize`. `buf_align` must equal the same value |
| 29 | 30 | /// that was passed as the `ptr_align` parameter to the original `alloc` call. |
| 30 | 31 | /// |
| 31 | /// Passing a `new_len` of 0 frees and invalidates the buffer such that it can no | |
| 32 | /// longer be passed to `resize`. | |
| 33 | /// | |
| 34 | 32 | /// error.OutOfMemory can only be returned if `new_len` is greater than `buf.len`. |
| 35 | 33 | /// If `buf` cannot be expanded to accomodate `new_len`, then the allocation MUST be |
| 36 | 34 | /// unmodified and error.OutOfMemory MUST be returned. |
| ... | ... | @@ -40,36 +38,54 @@ pub const VTable = struct { |
| 40 | 38 | /// provide a way to modify the alignment of a pointer. Rather it provides an API for |
| 41 | 39 | /// accepting more bytes of memory from the allocator than requested. |
| 42 | 40 | /// |
| 43 | /// `new_len` must be greater than or equal to `len_align` and must be aligned by `len_align`. | |
| 41 | /// `new_len` must be greater than zero, greater than or equal to `len_align` and must be aligned by `len_align`. | |
| 44 | 42 | /// |
| 45 | 43 | /// `ret_addr` is optionally provided as the first return address of the allocation call stack. |
| 46 | 44 | /// If the value is `0` it means no return address has been provided. |
| 47 | 45 | resize: fn (ptr: *c_void, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) Error!usize, |
| 46 | ||
| 47 | /// Free and invalidate a buffer. `buf.len` must equal the most recent length returned by `alloc` or `resize`. | |
| 48 | /// `buf_align` must equal the same value that was passed as the `ptr_align` parameter to the original `alloc` call. | |
| 49 | /// | |
| 50 | /// `ret_addr` is optionally provided as the first return address of the allocation call stack. | |
| 51 | /// If the value is `0` it means no return address has been provided. | |
| 52 | free: fn (ptr: *c_void, buf: []u8, buf_align: u29, ret_addr: usize) void, | |
| 48 | 53 | }; |
| 49 | 54 | |
| 50 | 55 | pub fn init( |
| 51 | 56 | pointer: anytype, |
| 52 | 57 | comptime allocFn: fn (ptr: @TypeOf(pointer), len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Error![]u8, |
| 53 | 58 | comptime resizeFn: fn (ptr: @TypeOf(pointer), buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) Error!usize, |
| 59 | comptime freeFn: fn (ptr: @TypeOf(pointer), buf: []u8, buf_align: u29, ret_addr: usize) void, | |
| 54 | 60 | ) Allocator { |
| 55 | 61 | const Ptr = @TypeOf(pointer); |
| 56 | assert(@typeInfo(Ptr) == .Pointer); // Must be a pointer | |
| 57 | assert(@typeInfo(Ptr).Pointer.size == .One); // Must be a single-item pointer | |
| 62 | const ptr_info = @typeInfo(Ptr); | |
| 63 | ||
| 64 | assert(ptr_info == .Pointer); // Must be a pointer | |
| 65 | assert(ptr_info.Pointer.size == .One); // Must be a single-item pointer | |
| 66 | ||
| 67 | const alignment = ptr_info.Pointer.alignment; | |
| 68 | ||
| 58 | 69 | const gen = struct { |
| 59 | 70 | fn alloc(ptr: *c_void, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Error![]u8 { |
| 60 | const alignment = @typeInfo(Ptr).Pointer.alignment; | |
| 61 | 71 | const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); |
| 62 | return allocFn(self, len, ptr_align, len_align, ret_addr); | |
| 72 | return @call(.{ .modifier = .always_inline }, allocFn, .{ self, len, ptr_align, len_align, ret_addr }); | |
| 63 | 73 | } |
| 64 | 74 | fn resize(ptr: *c_void, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) Error!usize { |
| 65 | const alignment = @typeInfo(Ptr).Pointer.alignment; | |
| 75 | assert(new_len != 0); | |
| 76 | const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); | |
| 77 | return @call(.{ .modifier = .always_inline }, resizeFn, .{ self, buf, buf_align, new_len, len_align, ret_addr }); | |
| 78 | } | |
| 79 | fn free(ptr: *c_void, buf: []u8, buf_align: u29, ret_addr: usize) void { | |
| 66 | 80 | const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); |
| 67 | return resizeFn(self, buf, buf_align, new_len, len_align, ret_addr); | |
| 81 | @call(.{ .modifier = .always_inline }, freeFn, .{ self, buf, buf_align, ret_addr }); | |
| 68 | 82 | } |
| 69 | 83 | }; |
| 84 | ||
| 70 | 85 | const vtable = VTable{ |
| 71 | 86 | .alloc = gen.alloc, |
| 72 | 87 | .resize = gen.resize, |
| 88 | .free = gen.free, | |
| 73 | 89 | }; |
| 74 | 90 | |
| 75 | 91 | return .{ |
| ... | ... | @@ -100,6 +116,56 @@ pub fn NoResize(comptime AllocatorType: type) type { |
| 100 | 116 | }; |
| 101 | 117 | } |
| 102 | 118 | |
| 119 | /// Set freeFn to `NoOpFree(AllocatorType).noOpFree` if free is a no-op. | |
| 120 | pub fn NoOpFree(comptime AllocatorType: type) type { | |
| 121 | return struct { | |
| 122 | pub fn noOpFree( | |
| 123 | self: *AllocatorType, | |
| 124 | buf: []u8, | |
| 125 | buf_align: u29, | |
| 126 | ret_addr: usize, | |
| 127 | ) void { | |
| 128 | _ = self; | |
| 129 | _ = buf; | |
| 130 | _ = buf_align; | |
| 131 | _ = ret_addr; | |
| 132 | } | |
| 133 | }; | |
| 134 | } | |
| 135 | ||
| 136 | /// Set freeFn to `PanicFree(AllocatorType).noOpFree` if free is not a supported operation. | |
| 137 | pub fn PanicFree(comptime AllocatorType: type) type { | |
| 138 | return struct { | |
| 139 | pub fn noOpFree( | |
| 140 | self: *AllocatorType, | |
| 141 | buf: []u8, | |
| 142 | buf_align: u29, | |
| 143 | ret_addr: usize, | |
| 144 | ) void { | |
| 145 | _ = self; | |
| 146 | _ = buf; | |
| 147 | _ = buf_align; | |
| 148 | _ = ret_addr; | |
| 149 | @panic("free is not a supported operation for the allocator: " ++ @typeName(AllocatorType)); | |
| 150 | } | |
| 151 | }; | |
| 152 | } | |
| 153 | ||
| 154 | /// This function is not intended to be called except from within the implementation of an Allocator | |
| 155 | pub inline fn rawAlloc(self: Allocator, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) Error![]u8 { | |
| 156 | return self.vtable.alloc(self.ptr, len, ptr_align, len_align, ret_addr); | |
| 157 | } | |
| 158 | ||
| 159 | /// This function is not intended to be called except from within the implementation of an Allocator | |
| 160 | pub inline fn rawResize(self: Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) Error!usize { | |
| 161 | return self.vtable.resize(self.ptr, buf, buf_align, new_len, len_align, ret_addr); | |
| 162 | } | |
| 163 | ||
| 164 | /// This function is not intended to be called except from within the implementation of an Allocator | |
| 165 | pub inline fn rawFree(self: Allocator, buf: []u8, buf_align: u29, ret_addr: usize) void { | |
| 166 | return self.vtable.free(self.ptr, buf, buf_align, ret_addr); | |
| 167 | } | |
| 168 | ||
| 103 | 169 | /// Realloc is used to modify the size or alignment of an existing allocation, |
| 104 | 170 | /// as well as to provide the allocator with an opportunity to move an allocation |
| 105 | 171 | /// to a better location. |
| ... | ... | @@ -133,8 +199,7 @@ fn reallocBytes( |
| 133 | 199 | /// Guaranteed to be >= 1. |
| 134 | 200 | /// Guaranteed to be a power of 2. |
| 135 | 201 | old_alignment: u29, |
| 136 | /// If `new_byte_count` is 0 then this is a free and it is guaranteed that | |
| 137 | /// `old_mem.len != 0`. | |
| 202 | /// `new_byte_count` must be greater than zero | |
| 138 | 203 | new_byte_count: usize, |
| 139 | 204 | /// Guaranteed to be >= 1. |
| 140 | 205 | /// Guaranteed to be a power of 2. |
| ... | ... | @@ -147,18 +212,20 @@ fn reallocBytes( |
| 147 | 212 | return_address: usize, |
| 148 | 213 | ) Error![]u8 { |
| 149 | 214 | if (old_mem.len == 0) { |
| 150 | const new_mem = try self.vtable.alloc(self.ptr, new_byte_count, new_alignment, len_align, return_address); | |
| 215 | const new_mem = try self.rawAlloc(new_byte_count, new_alignment, len_align, return_address); | |
| 151 | 216 | // TODO: https://github.com/ziglang/zig/issues/4298 |
| 152 | 217 | @memset(new_mem.ptr, undefined, new_byte_count); |
| 153 | 218 | return new_mem; |
| 154 | 219 | } |
| 155 | 220 | |
| 221 | assert(new_byte_count > 0); // `new_byte_count` must greater than zero, this is a resize not a free | |
| 222 | ||
| 156 | 223 | if (mem.isAligned(@ptrToInt(old_mem.ptr), new_alignment)) { |
| 157 | 224 | if (new_byte_count <= old_mem.len) { |
| 158 | 225 | const shrunk_len = self.shrinkBytes(old_mem, old_alignment, new_byte_count, len_align, return_address); |
| 159 | 226 | return old_mem.ptr[0..shrunk_len]; |
| 160 | 227 | } |
| 161 | if (self.vtable.resize(self.ptr, old_mem, old_alignment, new_byte_count, len_align, return_address)) |resized_len| { | |
| 228 | if (self.rawResize(old_mem, old_alignment, new_byte_count, len_align, return_address)) |resized_len| { | |
| 162 | 229 | assert(resized_len >= new_byte_count); |
| 163 | 230 | // TODO: https://github.com/ziglang/zig/issues/4298 |
| 164 | 231 | @memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count); |
| ... | ... | @@ -184,11 +251,11 @@ fn moveBytes( |
| 184 | 251 | ) Error![]u8 { |
| 185 | 252 | assert(old_mem.len > 0); |
| 186 | 253 | assert(new_len > 0); |
| 187 | const new_mem = try self.vtable.alloc(self.ptr, new_len, new_alignment, len_align, return_address); | |
| 254 | const new_mem = try self.rawAlloc(new_len, new_alignment, len_align, return_address); | |
| 188 | 255 | @memcpy(new_mem.ptr, old_mem.ptr, math.min(new_len, old_mem.len)); |
| 189 | 256 | // TODO https://github.com/ziglang/zig/issues/4298 |
| 190 | 257 | @memset(old_mem.ptr, undefined, old_mem.len); |
| 191 | _ = self.shrinkBytes(old_mem, old_align, 0, 0, return_address); | |
| 258 | self.rawFree(old_mem, old_align, return_address); | |
| 192 | 259 | return new_mem; |
| 193 | 260 | } |
| 194 | 261 | |
| ... | ... | @@ -207,7 +274,7 @@ pub fn destroy(self: Allocator, ptr: anytype) void { |
| 207 | 274 | const T = info.child; |
| 208 | 275 | if (@sizeOf(T) == 0) return; |
| 209 | 276 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr)); |
| 210 | _ = self.shrinkBytes(non_const_ptr[0..@sizeOf(T)], info.alignment, 0, 0, @returnAddress()); | |
| 277 | self.rawFree(non_const_ptr[0..@sizeOf(T)], info.alignment, @returnAddress()); | |
| 211 | 278 | } |
| 212 | 279 | |
| 213 | 280 | /// Allocates an array of `n` items of type `T` and sets all the |
| ... | ... | @@ -326,7 +393,7 @@ pub fn allocAdvancedWithRetAddr( |
| 326 | 393 | .exact => 0, |
| 327 | 394 | .at_least => size_of_T, |
| 328 | 395 | }; |
| 329 | const byte_slice = try self.vtable.alloc(self.ptr, byte_count, a, len_align, return_address); | |
| 396 | const byte_slice = try self.rawAlloc(byte_count, a, len_align, return_address); | |
| 330 | 397 | switch (exact) { |
| 331 | 398 | .exact => assert(byte_slice.len == byte_count), |
| 332 | 399 | .at_least => assert(byte_slice.len >= byte_count), |
| ... | ... | @@ -351,7 +418,7 @@ pub fn resize(self: Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(old |
| 351 | 418 | } |
| 352 | 419 | const old_byte_slice = mem.sliceAsBytes(old_mem); |
| 353 | 420 | const new_byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; |
| 354 | const rc = try self.vtable.resize(self.ptr, old_byte_slice, Slice.alignment, new_byte_count, 0, @returnAddress()); | |
| 421 | const rc = try self.rawResize(old_byte_slice, Slice.alignment, new_byte_count, 0, @returnAddress()); | |
| 355 | 422 | assert(rc == new_byte_count); |
| 356 | 423 | const new_byte_slice = old_byte_slice.ptr[0..new_byte_count]; |
| 357 | 424 | return mem.bytesAsSlice(T, new_byte_slice); |
| ... | ... | @@ -465,6 +532,11 @@ pub fn alignedShrinkWithRetAddr( |
| 465 | 532 | |
| 466 | 533 | if (new_n == old_mem.len) |
| 467 | 534 | return old_mem; |
| 535 | if (new_n == 0) { | |
| 536 | self.free(old_mem); | |
| 537 | return @as([*]align(new_alignment) T, undefined)[0..0]; | |
| 538 | } | |
| 539 | ||
| 468 | 540 | assert(new_n < old_mem.len); |
| 469 | 541 | assert(new_alignment <= Slice.alignment); |
| 470 | 542 | |
| ... | ... | @@ -489,7 +561,7 @@ pub fn free(self: Allocator, memory: anytype) void { |
| 489 | 561 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); |
| 490 | 562 | // TODO: https://github.com/ziglang/zig/issues/4298 |
| 491 | 563 | @memset(non_const_ptr, undefined, bytes_len); |
| 492 | _ = self.shrinkBytes(non_const_ptr[0..bytes_len], Slice.alignment, 0, 0, @returnAddress()); | |
| 564 | self.rawFree(non_const_ptr[0..bytes_len], Slice.alignment, @returnAddress()); | |
| 493 | 565 | } |
| 494 | 566 | |
| 495 | 567 | /// Copies `m` to newly allocated memory. Caller owns the memory. |
| ... | ... | @@ -520,5 +592,5 @@ pub fn shrinkBytes( |
| 520 | 592 | return_address: usize, |
| 521 | 593 | ) usize { |
| 522 | 594 | assert(new_len <= buf.len); |
| 523 | return self.vtable.resize(self.ptr, buf, buf_align, new_len, len_align, return_address) catch unreachable; | |
| 595 | return self.rawResize(buf, buf_align, new_len, len_align, return_address) catch unreachable; | |
| 524 | 596 | } |
lib/std/testing/failing_allocator.zig+15-7| ... | ... | @@ -41,7 +41,7 @@ pub const FailingAllocator = struct { |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | pub fn allocator(self: *FailingAllocator) mem.Allocator { |
| 44 | return mem.Allocator.init(self, alloc, resize); | |
| 44 | return mem.Allocator.init(self, alloc, resize, free); | |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | fn alloc( |
| ... | ... | @@ -54,7 +54,7 @@ pub const FailingAllocator = struct { |
| 54 | 54 | if (self.index == self.fail_index) { |
| 55 | 55 | return error.OutOfMemory; |
| 56 | 56 | } |
| 57 | const result = try self.internal_allocator.vtable.alloc(self.internal_allocator.ptr, len, ptr_align, len_align, return_address); | |
| 57 | const result = try self.internal_allocator.rawAlloc(len, ptr_align, len_align, return_address); | |
| 58 | 58 | self.allocated_bytes += result.len; |
| 59 | 59 | self.allocations += 1; |
| 60 | 60 | self.index += 1; |
| ... | ... | @@ -69,18 +69,26 @@ pub const FailingAllocator = struct { |
| 69 | 69 | len_align: u29, |
| 70 | 70 | ra: usize, |
| 71 | 71 | ) error{OutOfMemory}!usize { |
| 72 | const r = self.internal_allocator.vtable.resize(self.internal_allocator.ptr, old_mem, old_align, new_len, len_align, ra) catch |e| { | |
| 72 | const r = self.internal_allocator.rawResize(old_mem, old_align, new_len, len_align, ra) catch |e| { | |
| 73 | 73 | std.debug.assert(new_len > old_mem.len); |
| 74 | 74 | return e; |
| 75 | 75 | }; |
| 76 | if (new_len == 0) { | |
| 77 | self.deallocations += 1; | |
| 78 | self.freed_bytes += old_mem.len; | |
| 79 | } else if (r < old_mem.len) { | |
| 76 | if (r < old_mem.len) { | |
| 80 | 77 | self.freed_bytes += old_mem.len - r; |
| 81 | 78 | } else { |
| 82 | 79 | self.allocated_bytes += r - old_mem.len; |
| 83 | 80 | } |
| 84 | 81 | return r; |
| 85 | 82 | } |
| 83 | ||
| 84 | fn free( | |
| 85 | self: *FailingAllocator, | |
| 86 | old_mem: []u8, | |
| 87 | old_align: u29, | |
| 88 | ra: usize, | |
| 89 | ) void { | |
| 90 | self.internal_allocator.rawFree(old_mem, old_align, ra); | |
| 91 | self.deallocations += 1; | |
| 92 | self.freed_bytes += old_mem.len; | |
| 93 | } | |
| 86 | 94 | }; |
src/tracy.zig+13-7| ... | ... | @@ -155,13 +155,10 @@ pub fn TracyAllocator(comptime name: ?[:0]const u8) type { |
| 155 | 155 | } |
| 156 | 156 | } |
| 157 | 157 | |
| 158 | if (resized_len != 0) { | |
| 159 | // this was a shrink or a resize | |
| 160 | if (name) |n| { | |
| 161 | allocNamed(buf.ptr, resized_len, n); | |
| 162 | } else { | |
| 163 | alloc(buf.ptr, resized_len); | |
| 164 | } | |
| 158 | if (name) |n| { | |
| 159 | allocNamed(buf.ptr, resized_len, n); | |
| 160 | } else { | |
| 161 | alloc(buf.ptr, resized_len); | |
| 165 | 162 | } |
| 166 | 163 | |
| 167 | 164 | return resized_len; |
| ... | ... | @@ -172,6 +169,15 @@ pub fn TracyAllocator(comptime name: ?[:0]const u8) type { |
| 172 | 169 | return err; |
| 173 | 170 | } |
| 174 | 171 | } |
| 172 | ||
| 173 | fn freeFn(self: *Self, buf: []u8, buf_align: u29, ret_addr: usize) void { | |
| 174 | self.parent_allocator.rawFree(buf, buf_align, ret_addr); | |
| 175 | if (name) |n| { | |
| 176 | freeNamed(buf.ptr, n); | |
| 177 | } else { | |
| 178 | free(buf.ptr); | |
| 179 | } | |
| 180 | } | |
| 175 | 181 | }; |
| 176 | 182 | } |
| 177 | 183 |