| author | |
| committer | |
| log | 0cfe8e5d6ff06eed0cde6aed0c009a58ceffc395 |
| tree | d0dd3f43e534528d5c99ae28506c846a7d9063d0 |
| parent | 626b5eccab7264e579ce58f56be5fbc3aa42efc4 |
| parent | a728436992415d1bce44b0c63938f6443a4e9a11 |
| signature |
new allocator interface10 files changed, 610 insertions(+), 457 deletions(-)
lib/std/array_list.zig+2-2| ... | @@ -219,7 +219,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -219,7 +219,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { |
| 219 | if (better_capacity >= new_capacity) break; | 219 | if (better_capacity >= new_capacity) break; |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | const new_memory = try self.allocator.realloc(self.allocatedSlice(), better_capacity); | 222 | const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); |
| 223 | self.items.ptr = new_memory.ptr; | 223 | self.items.ptr = new_memory.ptr; |
| 224 | self.capacity = new_memory.len; | 224 | self.capacity = new_memory.len; |
| 225 | } | 225 | } |
| ... | @@ -441,7 +441,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ | ... | @@ -441,7 +441,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ |
| 441 | if (better_capacity >= new_capacity) break; | 441 | if (better_capacity >= new_capacity) break; |
| 442 | } | 442 | } |
| 443 | 443 | ||
| 444 | const new_memory = try allocator.realloc(self.allocatedSlice(), better_capacity); | 444 | const new_memory = try allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); |
| 445 | self.items.ptr = new_memory.ptr; | 445 | self.items.ptr = new_memory.ptr; |
| 446 | self.capacity = new_memory.len; | 446 | self.capacity = new_memory.len; |
| 447 | } | 447 | } |
lib/std/c.zig+11| ... | @@ -233,6 +233,17 @@ pub extern "c" fn setuid(uid: c_uint) c_int; | ... | @@ -233,6 +233,17 @@ pub extern "c" fn setuid(uid: c_uint) c_int; |
| 233 | 233 | ||
| 234 | pub extern "c" fn aligned_alloc(alignment: usize, size: usize) ?*c_void; | 234 | pub extern "c" fn aligned_alloc(alignment: usize, size: usize) ?*c_void; |
| 235 | pub extern "c" fn malloc(usize) ?*c_void; | 235 | pub extern "c" fn malloc(usize) ?*c_void; |
| 236 | |||
| 237 | pub usingnamespace switch (builtin.os.tag) { | ||
| 238 | .linux, .freebsd, .kfreebsd, .netbsd, .openbsd => struct { | ||
| 239 | pub extern "c" fn malloc_usable_size(?*const c_void) usize; | ||
| 240 | }, | ||
| 241 | .macosx, .ios, .watchos, .tvos => struct { | ||
| 242 | pub extern "c" fn malloc_size(?*const c_void) usize; | ||
| 243 | }, | ||
| 244 | else => struct {}, | ||
| 245 | }; | ||
| 246 | |||
| 236 | pub extern "c" fn realloc(?*c_void, usize) ?*c_void; | 247 | pub extern "c" fn realloc(?*c_void, usize) ?*c_void; |
| 237 | pub extern "c" fn free(*c_void) void; | 248 | pub extern "c" fn free(*c_void) void; |
| 238 | pub extern "c" fn posix_memalign(memptr: **c_void, alignment: usize, size: usize) c_int; | 249 | pub extern "c" fn posix_memalign(memptr: **c_void, alignment: usize, size: usize) c_int; |
lib/std/heap.zig+265-319| ... | @@ -15,23 +15,54 @@ pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; | ... | @@ -15,23 +15,54 @@ pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; |
| 15 | 15 | ||
| 16 | const Allocator = mem.Allocator; | 16 | const Allocator = mem.Allocator; |
| 17 | 17 | ||
| 18 | usingnamespace if (comptime @hasDecl(c, "malloc_size")) struct { | ||
| 19 | pub const supports_malloc_size = true; | ||
| 20 | pub const malloc_size = c.malloc_size; | ||
| 21 | } else if (comptime @hasDecl(c, "malloc_usable_size")) struct { | ||
| 22 | pub const supports_malloc_size = true; | ||
| 23 | pub const malloc_size = c.malloc_usable_size; | ||
| 24 | } else struct { | ||
| 25 | pub const supports_malloc_size = false; | ||
| 26 | }; | ||
| 27 | |||
| 18 | pub const c_allocator = &c_allocator_state; | 28 | pub const c_allocator = &c_allocator_state; |
| 19 | var c_allocator_state = Allocator{ | 29 | var c_allocator_state = Allocator{ |
| 20 | .reallocFn = cRealloc, | 30 | .allocFn = cAlloc, |
| 21 | .shrinkFn = cShrink, | 31 | .resizeFn = cResize, |
| 22 | }; | 32 | }; |
| 23 | 33 | ||
| 24 | fn cRealloc(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 34 | fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { |
| 25 | assert(new_align <= @alignOf(c_longdouble)); | 35 | assert(ptr_align <= @alignOf(c_longdouble)); |
| 26 | const old_ptr = if (old_mem.len == 0) null else @ptrCast(*c_void, old_mem.ptr); | 36 | const ptr = @ptrCast([*]u8, c.malloc(len) orelse return error.OutOfMemory); |
| 27 | const buf = c.realloc(old_ptr, new_size) orelse return error.OutOfMemory; | 37 | if (len_align == 0) { |
| 28 | return @ptrCast([*]u8, buf)[0..new_size]; | 38 | return ptr[0..len]; |
| 39 | } | ||
| 40 | const full_len = init: { | ||
| 41 | if (supports_malloc_size) { | ||
| 42 | const s = malloc_size(ptr); | ||
| 43 | assert(s >= len); | ||
| 44 | break :init s; | ||
| 45 | } | ||
| 46 | break :init len; | ||
| 47 | }; | ||
| 48 | return ptr[0..mem.alignBackwardAnyAlign(full_len, len_align)]; | ||
| 29 | } | 49 | } |
| 30 | 50 | ||
| 31 | fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 51 | fn cResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { |
| 32 | const old_ptr = @ptrCast(*c_void, old_mem.ptr); | 52 | if (new_len == 0) { |
| 33 | const buf = c.realloc(old_ptr, new_size) orelse return old_mem[0..new_size]; | 53 | c.free(buf.ptr); |
| 34 | return @ptrCast([*]u8, buf)[0..new_size]; | 54 | return 0; |
| 55 | } | ||
| 56 | if (new_len <= buf.len) { | ||
| 57 | return mem.alignAllocLen(buf.len, new_len, len_align); | ||
| 58 | } | ||
| 59 | if (supports_malloc_size) { | ||
| 60 | const full_len = malloc_size(buf.ptr); | ||
| 61 | if (new_len <= full_len) { | ||
| 62 | return mem.alignAllocLen(full_len, new_len, len_align); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | return error.OutOfMemory; | ||
| 35 | } | 66 | } |
| 36 | 67 | ||
| 37 | /// This allocator makes a syscall directly for every allocation and free. | 68 | /// This allocator makes a syscall directly for every allocation and free. |
| ... | @@ -44,19 +75,27 @@ else | ... | @@ -44,19 +75,27 @@ else |
| 44 | &page_allocator_state; | 75 | &page_allocator_state; |
| 45 | 76 | ||
| 46 | var page_allocator_state = Allocator{ | 77 | var page_allocator_state = Allocator{ |
| 47 | .reallocFn = PageAllocator.realloc, | 78 | .allocFn = PageAllocator.alloc, |
| 48 | .shrinkFn = PageAllocator.shrink, | 79 | .resizeFn = PageAllocator.resize, |
| 49 | }; | 80 | }; |
| 50 | var wasm_page_allocator_state = Allocator{ | 81 | var wasm_page_allocator_state = Allocator{ |
| 51 | .reallocFn = WasmPageAllocator.realloc, | 82 | .allocFn = WasmPageAllocator.alloc, |
| 52 | .shrinkFn = WasmPageAllocator.shrink, | 83 | .resizeFn = WasmPageAllocator.resize, |
| 53 | }; | 84 | }; |
| 54 | 85 | ||
| 55 | pub const direct_allocator = @compileError("deprecated; use std.heap.page_allocator"); | 86 | pub const direct_allocator = @compileError("deprecated; use std.heap.page_allocator"); |
| 56 | 87 | ||
| 88 | /// Verifies that the adjusted length will still map to the full length | ||
| 89 | pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize { | ||
| 90 | const aligned_len = mem.alignAllocLen(full_len, len, len_align); | ||
| 91 | assert(mem.alignForward(aligned_len, mem.page_size) == full_len); | ||
| 92 | return aligned_len; | ||
| 93 | } | ||
| 94 | |||
| 57 | const PageAllocator = struct { | 95 | const PageAllocator = struct { |
| 58 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { | 96 | fn alloc(allocator: *Allocator, n: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 { |
| 59 | if (n == 0) return &[0]u8{}; | 97 | assert(n > 0); |
| 98 | const alignedLen = mem.alignForward(n, mem.page_size); | ||
| 60 | 99 | ||
| 61 | if (builtin.os.tag == .windows) { | 100 | if (builtin.os.tag == .windows) { |
| 62 | const w = os.windows; | 101 | const w = os.windows; |
| ... | @@ -68,21 +107,21 @@ const PageAllocator = struct { | ... | @@ -68,21 +107,21 @@ const PageAllocator = struct { |
| 68 | // see https://devblogs.microsoft.com/oldnewthing/?p=42223 | 107 | // see https://devblogs.microsoft.com/oldnewthing/?p=42223 |
| 69 | const addr = w.VirtualAlloc( | 108 | const addr = w.VirtualAlloc( |
| 70 | null, | 109 | null, |
| 71 | n, | 110 | alignedLen, |
| 72 | w.MEM_COMMIT | w.MEM_RESERVE, | 111 | w.MEM_COMMIT | w.MEM_RESERVE, |
| 73 | w.PAGE_READWRITE, | 112 | w.PAGE_READWRITE, |
| 74 | ) catch return error.OutOfMemory; | 113 | ) catch return error.OutOfMemory; |
| 75 | 114 | ||
| 76 | // If the allocation is sufficiently aligned, use it. | 115 | // If the allocation is sufficiently aligned, use it. |
| 77 | if (@ptrToInt(addr) & (alignment - 1) == 0) { | 116 | if (@ptrToInt(addr) & (alignment - 1) == 0) { |
| 78 | return @ptrCast([*]u8, addr)[0..n]; | 117 | return @ptrCast([*]u8, addr)[0..alignPageAllocLen(alignedLen, n, len_align)]; |
| 79 | } | 118 | } |
| 80 | 119 | ||
| 81 | // If it wasn't, actually do an explicitely aligned allocation. | 120 | // If it wasn't, actually do an explicitely aligned allocation. |
| 82 | w.VirtualFree(addr, 0, w.MEM_RELEASE); | 121 | w.VirtualFree(addr, 0, w.MEM_RELEASE); |
| 83 | const alloc_size = n + alignment; | 122 | const alloc_size = n + alignment - mem.page_size; |
| 84 | 123 | ||
| 85 | const final_addr = while (true) { | 124 | while (true) { |
| 86 | // Reserve a range of memory large enough to find a sufficiently | 125 | // Reserve a range of memory large enough to find a sufficiently |
| 87 | // aligned address. | 126 | // aligned address. |
| 88 | const reserved_addr = w.VirtualAlloc( | 127 | const reserved_addr = w.VirtualAlloc( |
| ... | @@ -102,48 +141,50 @@ const PageAllocator = struct { | ... | @@ -102,48 +141,50 @@ const PageAllocator = struct { |
| 102 | // until it succeeds. | 141 | // until it succeeds. |
| 103 | const ptr = w.VirtualAlloc( | 142 | const ptr = w.VirtualAlloc( |
| 104 | @intToPtr(*c_void, aligned_addr), | 143 | @intToPtr(*c_void, aligned_addr), |
| 105 | n, | 144 | alignedLen, |
| 106 | w.MEM_COMMIT | w.MEM_RESERVE, | 145 | w.MEM_COMMIT | w.MEM_RESERVE, |
| 107 | w.PAGE_READWRITE, | 146 | w.PAGE_READWRITE, |
| 108 | ) catch continue; | 147 | ) catch continue; |
| 109 | 148 | ||
| 110 | return @ptrCast([*]u8, ptr)[0..n]; | 149 | return @ptrCast([*]u8, ptr)[0..alignPageAllocLen(alignedLen, n, len_align)]; |
| 111 | }; | 150 | } |
| 112 | |||
| 113 | return @ptrCast([*]u8, final_addr)[0..n]; | ||
| 114 | } | 151 | } |
| 115 | 152 | ||
| 116 | const alloc_size = if (alignment <= mem.page_size) n else n + alignment; | 153 | const maxDropLen = alignment - std.math.min(alignment, mem.page_size); |
| 154 | const allocLen = if (maxDropLen <= alignedLen - n) alignedLen | ||
| 155 | else mem.alignForward(alignedLen + maxDropLen, mem.page_size); | ||
| 117 | const slice = os.mmap( | 156 | const slice = os.mmap( |
| 118 | null, | 157 | null, |
| 119 | mem.alignForward(alloc_size, mem.page_size), | 158 | allocLen, |
| 120 | os.PROT_READ | os.PROT_WRITE, | 159 | os.PROT_READ | os.PROT_WRITE, |
| 121 | os.MAP_PRIVATE | os.MAP_ANONYMOUS, | 160 | os.MAP_PRIVATE | os.MAP_ANONYMOUS, |
| 122 | -1, | 161 | -1, |
| 123 | 0, | 162 | 0, |
| 124 | ) catch return error.OutOfMemory; | 163 | ) catch return error.OutOfMemory; |
| 125 | if (alloc_size == n) return slice[0..n]; | 164 | assert(mem.isAligned(@ptrToInt(slice.ptr), mem.page_size)); |
| 126 | 165 | ||
| 127 | const aligned_addr = mem.alignForward(@ptrToInt(slice.ptr), alignment); | 166 | const aligned_addr = mem.alignForward(@ptrToInt(slice.ptr), alignment); |
| 128 | 167 | ||
| 129 | // Unmap the extra bytes that were only requested in order to guarantee | 168 | // Unmap the extra bytes that were only requested in order to guarantee |
| 130 | // that the range of memory we were provided had a proper alignment in | 169 | // that the range of memory we were provided had a proper alignment in |
| 131 | // it somewhere. The extra bytes could be at the beginning, or end, or both. | 170 | // it somewhere. The extra bytes could be at the beginning, or end, or both. |
| 132 | const unused_start_len = aligned_addr - @ptrToInt(slice.ptr); | 171 | const dropLen = aligned_addr - @ptrToInt(slice.ptr); |
| 133 | if (unused_start_len != 0) { | 172 | if (dropLen != 0) { |
| 134 | os.munmap(slice[0..unused_start_len]); | 173 | os.munmap(slice[0..dropLen]); |
| 135 | } | 174 | } |
| 136 | const aligned_end_addr = mem.alignForward(aligned_addr + n, mem.page_size); | 175 | |
| 137 | const unused_end_len = @ptrToInt(slice.ptr) + slice.len - aligned_end_addr; | 176 | // Unmap extra pages |
| 138 | if (unused_end_len != 0) { | 177 | const alignedBufferLen = allocLen - dropLen; |
| 139 | os.munmap(@intToPtr([*]align(mem.page_size) u8, aligned_end_addr)[0..unused_end_len]); | 178 | if (alignedBufferLen > alignedLen) { |
| 179 | os.munmap(@alignCast(mem.page_size, @intToPtr([*]u8, aligned_addr))[alignedLen..alignedBufferLen]); | ||
| 140 | } | 180 | } |
| 141 | 181 | ||
| 142 | return @intToPtr([*]u8, aligned_addr)[0..n]; | 182 | return @intToPtr([*]u8, aligned_addr)[0..alignPageAllocLen(alignedLen, n, len_align)]; |
| 143 | } | 183 | } |
| 144 | 184 | ||
| 145 | fn shrink(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 185 | fn resize(allocator: *Allocator, buf_unaligned: []u8, new_size: usize, len_align: u29) Allocator.Error!usize { |
| 146 | const old_mem = @alignCast(mem.page_size, old_mem_unaligned); | 186 | const new_size_aligned = mem.alignForward(new_size, mem.page_size); |
| 187 | |||
| 147 | if (builtin.os.tag == .windows) { | 188 | if (builtin.os.tag == .windows) { |
| 148 | const w = os.windows; | 189 | const w = os.windows; |
| 149 | if (new_size == 0) { | 190 | if (new_size == 0) { |
| ... | @@ -153,100 +194,45 @@ const PageAllocator = struct { | ... | @@ -153,100 +194,45 @@ const PageAllocator = struct { |
| 153 | // is reserved in the initial allocation call to VirtualAlloc." | 194 | // is reserved in the initial allocation call to VirtualAlloc." |
| 154 | // So we can only use MEM_RELEASE when actually releasing the | 195 | // So we can only use MEM_RELEASE when actually releasing the |
| 155 | // whole allocation. | 196 | // whole allocation. |
| 156 | w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE); | 197 | w.VirtualFree(buf_unaligned.ptr, 0, w.MEM_RELEASE); |
| 157 | } else { | 198 | return 0; |
| 158 | const base_addr = @ptrToInt(old_mem.ptr); | 199 | } |
| 159 | const old_addr_end = base_addr + old_mem.len; | 200 | if (new_size < buf_unaligned.len) { |
| 160 | const new_addr_end = base_addr + new_size; | 201 | const base_addr = @ptrToInt(buf_unaligned.ptr); |
| 161 | const new_addr_end_rounded = mem.alignForward(new_addr_end, mem.page_size); | 202 | const old_addr_end = base_addr + buf_unaligned.len; |
| 162 | if (old_addr_end > new_addr_end_rounded) { | 203 | const new_addr_end = mem.alignForward(base_addr + new_size, mem.page_size); |
| 204 | if (old_addr_end > new_addr_end) { | ||
| 163 | // For shrinking that is not releasing, we will only | 205 | // For shrinking that is not releasing, we will only |
| 164 | // decommit the pages not needed anymore. | 206 | // decommit the pages not needed anymore. |
| 165 | w.VirtualFree( | 207 | w.VirtualFree( |
| 166 | @intToPtr(*c_void, new_addr_end_rounded), | 208 | @intToPtr(*c_void, new_addr_end), |
| 167 | old_addr_end - new_addr_end_rounded, | 209 | old_addr_end - new_addr_end, |
| 168 | w.MEM_DECOMMIT, | 210 | w.MEM_DECOMMIT, |
| 169 | ); | 211 | ); |
| 170 | } | 212 | } |
| 213 | return alignPageAllocLen(new_size_aligned, new_size, len_align); | ||
| 171 | } | 214 | } |
| 172 | return old_mem[0..new_size]; | 215 | if (new_size == buf_unaligned.len) { |
| 173 | } | 216 | return alignPageAllocLen(new_size_aligned, new_size, len_align); |
| 174 | const base_addr = @ptrToInt(old_mem.ptr); | ||
| 175 | const old_addr_end = base_addr + old_mem.len; | ||
| 176 | const new_addr_end = base_addr + new_size; | ||
| 177 | const new_addr_end_rounded = mem.alignForward(new_addr_end, mem.page_size); | ||
| 178 | if (old_addr_end > new_addr_end_rounded) { | ||
| 179 | const ptr = @intToPtr([*]align(mem.page_size) u8, new_addr_end_rounded); | ||
| 180 | os.munmap(ptr[0 .. old_addr_end - new_addr_end_rounded]); | ||
| 181 | } | ||
| 182 | return old_mem[0..new_size]; | ||
| 183 | } | ||
| 184 | |||
| 185 | fn realloc(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | ||
| 186 | const old_mem = @alignCast(mem.page_size, old_mem_unaligned); | ||
| 187 | if (builtin.os.tag == .windows) { | ||
| 188 | if (old_mem.len == 0) { | ||
| 189 | return alloc(allocator, new_size, new_align); | ||
| 190 | } | 217 | } |
| 218 | // new_size > buf_unaligned.len not implemented | ||
| 219 | return error.OutOfMemory; | ||
| 220 | } | ||
| 191 | 221 | ||
| 192 | if (new_size <= old_mem.len and new_align <= old_align) { | 222 | const buf_aligned_len = mem.alignForward(buf_unaligned.len, mem.page_size); |
| 193 | return shrink(allocator, old_mem, old_align, new_size, new_align); | 223 | if (new_size_aligned == buf_aligned_len) |
| 194 | } | 224 | return alignPageAllocLen(new_size_aligned, new_size, len_align); |
| 195 | |||
| 196 | const w = os.windows; | ||
| 197 | const base_addr = @ptrToInt(old_mem.ptr); | ||
| 198 | |||
| 199 | if (new_align > old_align and base_addr & (new_align - 1) != 0) { | ||
| 200 | // Current allocation doesn't satisfy the new alignment. | ||
| 201 | // For now we'll do a new one no matter what, but maybe | ||
| 202 | // there is something smarter to do instead. | ||
| 203 | const result = try alloc(allocator, new_size, new_align); | ||
| 204 | assert(old_mem.len != 0); | ||
| 205 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 206 | w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE); | ||
| 207 | |||
| 208 | return result; | ||
| 209 | } | ||
| 210 | |||
| 211 | const old_addr_end = base_addr + old_mem.len; | ||
| 212 | const old_addr_end_rounded = mem.alignForward(old_addr_end, mem.page_size); | ||
| 213 | const new_addr_end = base_addr + new_size; | ||
| 214 | const new_addr_end_rounded = mem.alignForward(new_addr_end, mem.page_size); | ||
| 215 | if (new_addr_end_rounded == old_addr_end_rounded) { | ||
| 216 | // The reallocation fits in the already allocated pages. | ||
| 217 | return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; | ||
| 218 | } | ||
| 219 | assert(new_addr_end_rounded > old_addr_end_rounded); | ||
| 220 | |||
| 221 | // We need to commit new pages. | ||
| 222 | const additional_size = new_addr_end - old_addr_end_rounded; | ||
| 223 | const realloc_addr = w.kernel32.VirtualAlloc( | ||
| 224 | @intToPtr(*c_void, old_addr_end_rounded), | ||
| 225 | additional_size, | ||
| 226 | w.MEM_COMMIT | w.MEM_RESERVE, | ||
| 227 | w.PAGE_READWRITE, | ||
| 228 | ) orelse { | ||
| 229 | // Committing new pages at the end of the existing allocation | ||
| 230 | // failed, we need to try a new one. | ||
| 231 | const new_alloc_mem = try alloc(allocator, new_size, new_align); | ||
| 232 | @memcpy(new_alloc_mem.ptr, old_mem.ptr, old_mem.len); | ||
| 233 | w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE); | ||
| 234 | |||
| 235 | return new_alloc_mem; | ||
| 236 | }; | ||
| 237 | 225 | ||
| 238 | assert(@ptrToInt(realloc_addr) == old_addr_end_rounded); | 226 | if (new_size_aligned < buf_aligned_len) { |
| 239 | return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; | 227 | const ptr = @intToPtr([*]align(mem.page_size) u8, @ptrToInt(buf_unaligned.ptr) + new_size_aligned); |
| 240 | } | 228 | os.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]); |
| 241 | if (new_size <= old_mem.len and new_align <= old_align) { | 229 | if (new_size_aligned == 0) |
| 242 | return shrink(allocator, old_mem, old_align, new_size, new_align); | 230 | return 0; |
| 243 | } | 231 | return alignPageAllocLen(new_size_aligned, new_size, len_align); |
| 244 | const result = try alloc(allocator, new_size, new_align); | ||
| 245 | if (old_mem.len != 0) { | ||
| 246 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 247 | os.munmap(old_mem); | ||
| 248 | } | 232 | } |
| 249 | return result; | 233 | |
| 234 | // TODO: call mremap | ||
| 235 | return error.OutOfMemory; | ||
| 250 | } | 236 | } |
| 251 | }; | 237 | }; |
| 252 | 238 | ||
| ... | @@ -338,16 +324,24 @@ const WasmPageAllocator = struct { | ... | @@ -338,16 +324,24 @@ const WasmPageAllocator = struct { |
| 338 | } | 324 | } |
| 339 | 325 | ||
| 340 | fn nPages(memsize: usize) usize { | 326 | fn nPages(memsize: usize) usize { |
| 341 | return std.mem.alignForward(memsize, std.mem.page_size) / std.mem.page_size; | 327 | return mem.alignForward(memsize, mem.page_size) / mem.page_size; |
| 342 | } | 328 | } |
| 343 | 329 | ||
| 344 | fn alloc(allocator: *Allocator, page_count: usize, alignment: u29) error{OutOfMemory}!usize { | 330 | fn alloc(allocator: *Allocator, len: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 { |
| 345 | var idx = conventional.useRecycled(page_count); | 331 | const page_count = nPages(len); |
| 346 | if (idx != FreeBlock.not_found) { | 332 | const page_idx = try allocPages(page_count); |
| 347 | return idx; | 333 | return @intToPtr([*]u8, page_idx * mem.page_size) |
| 334 | [0..alignPageAllocLen(page_count * mem.page_size, len, len_align)]; | ||
| 335 | } | ||
| 336 | fn allocPages(page_count: usize) !usize { | ||
| 337 | { | ||
| 338 | const idx = conventional.useRecycled(page_count); | ||
| 339 | if (idx != FreeBlock.not_found) { | ||
| 340 | return idx; | ||
| 341 | } | ||
| 348 | } | 342 | } |
| 349 | 343 | ||
| 350 | idx = extended.useRecycled(page_count); | 344 | const idx = extended.useRecycled(page_count); |
| 351 | if (idx != FreeBlock.not_found) { | 345 | if (idx != FreeBlock.not_found) { |
| 352 | return idx + extendedOffset(); | 346 | return idx + extendedOffset(); |
| 353 | } | 347 | } |
| ... | @@ -360,51 +354,36 @@ const WasmPageAllocator = struct { | ... | @@ -360,51 +354,36 @@ const WasmPageAllocator = struct { |
| 360 | return @intCast(usize, prev_page_count); | 354 | return @intCast(usize, prev_page_count); |
| 361 | } | 355 | } |
| 362 | 356 | ||
| 363 | pub fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) Allocator.Error![]u8 { | 357 | fn freePages(start: usize, end: usize) void { |
| 364 | if (new_align > std.mem.page_size) { | 358 | if (start < extendedOffset()) { |
| 365 | return error.OutOfMemory; | 359 | conventional.recycle(start, std.math.min(extendedOffset(), end) - start); |
| 366 | } | 360 | } |
| 367 | 361 | if (end > extendedOffset()) { | |
| 368 | if (nPages(new_size) == nPages(old_mem.len)) { | 362 | var new_end = end; |
| 369 | return old_mem.ptr[0..new_size]; | 363 | if (!extended.isInitialized()) { |
| 370 | } else if (new_size < old_mem.len) { | 364 | // Steal the last page from the memory currently being recycled |
| 371 | return shrink(allocator, old_mem, old_align, new_size, new_align); | 365 | // TODO: would it be better if we use the first page instead? |
| 372 | } else { | 366 | new_end -= 1; |
| 373 | const page_idx = try alloc(allocator, nPages(new_size), new_align); | 367 | |
| 374 | const new_mem = @intToPtr([*]u8, page_idx * std.mem.page_size)[0..new_size]; | 368 | extended.data = @intToPtr([*]u128, new_end * mem.page_size)[0 .. mem.page_size / @sizeOf(u128)]; |
| 375 | std.mem.copy(u8, new_mem, old_mem); | 369 | // Since this is the first page being freed and we consume it, assume *nothing* is free. |
| 376 | _ = shrink(allocator, old_mem, old_align, 0, 0); | 370 | mem.set(u128, extended.data, PageStatus.none_free); |
| 377 | return new_mem; | 371 | } |
| 372 | const clamped_start = std.math.max(extendedOffset(), start); | ||
| 373 | extended.recycle(clamped_start - extendedOffset(), new_end - clamped_start); | ||
| 378 | } | 374 | } |
| 379 | } | 375 | } |
| 380 | 376 | ||
| 381 | pub fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 377 | fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize { |
| 382 | @setCold(true); | 378 | const aligned_len = mem.alignForward(buf.len, mem.page_size); |
| 383 | const free_start = nPages(@ptrToInt(old_mem.ptr) + new_size); | 379 | if (new_len > aligned_len) return error.OutOfMemory; |
| 384 | var free_end = nPages(@ptrToInt(old_mem.ptr) + old_mem.len); | 380 | const current_n = nPages(aligned_len); |
| 385 | 381 | const new_n = nPages(new_len); | |
| 386 | if (free_end > free_start) { | 382 | if (new_n != current_n) { |
| 387 | if (free_start < extendedOffset()) { | 383 | const base = nPages(@ptrToInt(buf.ptr)); |
| 388 | const clamped_end = std.math.min(extendedOffset(), free_end); | 384 | freePages(base + new_n, base + current_n); |
| 389 | conventional.recycle(free_start, clamped_end - free_start); | ||
| 390 | } | ||
| 391 | |||
| 392 | if (free_end > extendedOffset()) { | ||
| 393 | if (!extended.isInitialized()) { | ||
| 394 | // Steal the last page from the memory currently being recycled | ||
| 395 | // TODO: would it be better if we use the first page instead? | ||
| 396 | free_end -= 1; | ||
| 397 | |||
| 398 | extended.data = @intToPtr([*]u128, free_end * std.mem.page_size)[0 .. std.mem.page_size / @sizeOf(u128)]; | ||
| 399 | // Since this is the first page being freed and we consume it, assume *nothing* is free. | ||
| 400 | std.mem.set(u128, extended.data, PageStatus.none_free); | ||
| 401 | } | ||
| 402 | const clamped_start = std.math.max(extendedOffset(), free_start); | ||
| 403 | extended.recycle(clamped_start - extendedOffset(), free_end - clamped_start); | ||
| 404 | } | ||
| 405 | } | 385 | } |
| 406 | 386 | return if (new_len == 0) 0 else alignPageAllocLen(new_n * mem.page_size, new_len, len_align); | |
| 407 | return old_mem[0..new_size]; | ||
| 408 | } | 387 | } |
| 409 | }; | 388 | }; |
| 410 | 389 | ||
| ... | @@ -418,8 +397,8 @@ pub const HeapAllocator = switch (builtin.os.tag) { | ... | @@ -418,8 +397,8 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 418 | pub fn init() HeapAllocator { | 397 | pub fn init() HeapAllocator { |
| 419 | return HeapAllocator{ | 398 | return HeapAllocator{ |
| 420 | .allocator = Allocator{ | 399 | .allocator = Allocator{ |
| 421 | .reallocFn = realloc, | 400 | .allocFn = alloc, |
| 422 | .shrinkFn = shrink, | 401 | .resizeFn = resize, |
| 423 | }, | 402 | }, |
| 424 | .heap_handle = null, | 403 | .heap_handle = null, |
| 425 | }; | 404 | }; |
| ... | @@ -431,11 +410,14 @@ pub const HeapAllocator = switch (builtin.os.tag) { | ... | @@ -431,11 +410,14 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 431 | } | 410 | } |
| 432 | } | 411 | } |
| 433 | 412 | ||
| 434 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { | 413 | fn getRecordPtr(buf: []u8) *align(1) usize { |
| 414 | return @intToPtr(*align(1) usize, @ptrToInt(buf.ptr) + buf.len); | ||
| 415 | } | ||
| 416 | |||
| 417 | fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { | ||
| 435 | const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); | 418 | const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); |
| 436 | if (n == 0) return &[0]u8{}; | ||
| 437 | 419 | ||
| 438 | const amt = n + alignment + @sizeOf(usize); | 420 | const amt = n + ptr_align - 1 + @sizeOf(usize); |
| 439 | const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, builtin.AtomicOrder.SeqCst); | 421 | const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, builtin.AtomicOrder.SeqCst); |
| 440 | const heap_handle = optional_heap_handle orelse blk: { | 422 | const heap_handle = optional_heap_handle orelse blk: { |
| 441 | const options = if (builtin.single_threaded) os.windows.HEAP_NO_SERIALIZE else 0; | 423 | const options = if (builtin.single_threaded) os.windows.HEAP_NO_SERIALIZE else 0; |
| ... | @@ -446,66 +428,60 @@ pub const HeapAllocator = switch (builtin.os.tag) { | ... | @@ -446,66 +428,60 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 446 | }; | 428 | }; |
| 447 | const ptr = os.windows.kernel32.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; | 429 | const ptr = os.windows.kernel32.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; |
| 448 | const root_addr = @ptrToInt(ptr); | 430 | const root_addr = @ptrToInt(ptr); |
| 449 | const adjusted_addr = mem.alignForward(root_addr, alignment); | 431 | const aligned_addr = mem.alignForward(root_addr, ptr_align); |
| 450 | const record_addr = adjusted_addr + n; | 432 | const return_len = init: { |
| 451 | @intToPtr(*align(1) usize, record_addr).* = root_addr; | 433 | if (len_align == 0) break :init n; |
| 452 | return @intToPtr([*]u8, adjusted_addr)[0..n]; | 434 | const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr); |
| 453 | } | 435 | assert(full_len != std.math.maxInt(usize)); |
| 454 | 436 | assert(full_len >= amt); | |
| 455 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 437 | break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align); |
| 456 | return realloc(allocator, old_mem, old_align, new_size, new_align) catch { | ||
| 457 | const old_adjusted_addr = @ptrToInt(old_mem.ptr); | ||
| 458 | const old_record_addr = old_adjusted_addr + old_mem.len; | ||
| 459 | const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; | ||
| 460 | const old_ptr = @intToPtr(*c_void, root_addr); | ||
| 461 | const new_record_addr = old_record_addr - new_size + old_mem.len; | ||
| 462 | @intToPtr(*align(1) usize, new_record_addr).* = root_addr; | ||
| 463 | return old_mem[0..new_size]; | ||
| 464 | }; | 438 | }; |
| 439 | const buf = @intToPtr([*]u8, aligned_addr)[0..return_len]; | ||
| 440 | getRecordPtr(buf).* = root_addr; | ||
| 441 | return buf; | ||
| 465 | } | 442 | } |
| 466 | 443 | ||
| 467 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 444 | fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize { |
| 468 | if (old_mem.len == 0) return alloc(allocator, new_size, new_align); | ||
| 469 | |||
| 470 | const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); | 445 | const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); |
| 471 | const old_adjusted_addr = @ptrToInt(old_mem.ptr); | ||
| 472 | const old_record_addr = old_adjusted_addr + old_mem.len; | ||
| 473 | const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; | ||
| 474 | const old_ptr = @intToPtr(*c_void, root_addr); | ||
| 475 | |||
| 476 | if (new_size == 0) { | 446 | if (new_size == 0) { |
| 477 | os.windows.HeapFree(self.heap_handle.?, 0, old_ptr); | 447 | os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void ,getRecordPtr(buf).*)); |
| 478 | return old_mem[0..0]; | 448 | return 0; |
| 479 | } | 449 | } |
| 480 | 450 | ||
| 481 | const amt = new_size + new_align + @sizeOf(usize); | 451 | const root_addr = getRecordPtr(buf).*; |
| 452 | const align_offset = @ptrToInt(buf.ptr) - root_addr; | ||
| 453 | const amt = align_offset + new_size + @sizeOf(usize); | ||
| 482 | const new_ptr = os.windows.kernel32.HeapReAlloc( | 454 | const new_ptr = os.windows.kernel32.HeapReAlloc( |
| 483 | self.heap_handle.?, | 455 | self.heap_handle.?, |
| 484 | 0, | 456 | os.windows.HEAP_REALLOC_IN_PLACE_ONLY, |
| 485 | old_ptr, | 457 | @intToPtr(*c_void, root_addr), |
| 486 | amt, | 458 | amt, |
| 487 | ) orelse return error.OutOfMemory; | 459 | ) orelse return error.OutOfMemory; |
| 488 | const offset = old_adjusted_addr - root_addr; | 460 | assert(new_ptr == @intToPtr(*c_void, root_addr)); |
| 489 | const new_root_addr = @ptrToInt(new_ptr); | 461 | const return_len = init: { |
| 490 | var new_adjusted_addr = new_root_addr + offset; | 462 | if (len_align == 0) break :init new_size; |
| 491 | const offset_is_valid = new_adjusted_addr + new_size + @sizeOf(usize) <= new_root_addr + amt; | 463 | const full_len = os.windows.kernel32.HeapSize(self.heap_handle.?, 0, new_ptr); |
| 492 | const offset_is_aligned = new_adjusted_addr % new_align == 0; | 464 | assert(full_len != std.math.maxInt(usize)); |
| 493 | if (!offset_is_valid or !offset_is_aligned) { | 465 | assert(full_len >= amt); |
| 494 | // If HeapReAlloc didn't happen to move the memory to the new alignment, | 466 | break :init mem.alignBackwardAnyAlign(full_len - align_offset, len_align); |
| 495 | // or the memory starting at the old offset would be outside of the new allocation, | 467 | }; |
| 496 | // then we need to copy the memory to a valid aligned address and use that | 468 | getRecordPtr(buf.ptr[0..return_len]).* = root_addr; |
| 497 | const new_aligned_addr = mem.alignForward(new_root_addr, new_align); | 469 | return return_len; |
| 498 | @memcpy(@intToPtr([*]u8, new_aligned_addr), @intToPtr([*]u8, new_adjusted_addr), std.math.min(old_mem.len, new_size)); | ||
| 499 | new_adjusted_addr = new_aligned_addr; | ||
| 500 | } | ||
| 501 | const new_record_addr = new_adjusted_addr + new_size; | ||
| 502 | @intToPtr(*align(1) usize, new_record_addr).* = new_root_addr; | ||
| 503 | return @intToPtr([*]u8, new_adjusted_addr)[0..new_size]; | ||
| 504 | } | 470 | } |
| 505 | }, | 471 | }, |
| 506 | else => @compileError("Unsupported OS"), | 472 | else => @compileError("Unsupported OS"), |
| 507 | }; | 473 | }; |
| 508 | 474 | ||
| 475 | fn sliceContainsPtr(container: []u8, ptr: [*]u8) bool { | ||
| 476 | return @ptrToInt(ptr) >= @ptrToInt(container.ptr) and | ||
| 477 | @ptrToInt(ptr) < (@ptrToInt(container.ptr) + container.len); | ||
| 478 | } | ||
| 479 | |||
| 480 | fn sliceContainsSlice(container: []u8, slice: []u8) bool { | ||
| 481 | return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and | ||
| 482 | (@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len); | ||
| 483 | } | ||
| 484 | |||
| 509 | pub const FixedBufferAllocator = struct { | 485 | pub const FixedBufferAllocator = struct { |
| 510 | allocator: Allocator, | 486 | allocator: Allocator, |
| 511 | end_index: usize, | 487 | end_index: usize, |
| ... | @@ -514,19 +490,33 @@ pub const FixedBufferAllocator = struct { | ... | @@ -514,19 +490,33 @@ pub const FixedBufferAllocator = struct { |
| 514 | pub fn init(buffer: []u8) FixedBufferAllocator { | 490 | pub fn init(buffer: []u8) FixedBufferAllocator { |
| 515 | return FixedBufferAllocator{ | 491 | return FixedBufferAllocator{ |
| 516 | .allocator = Allocator{ | 492 | .allocator = Allocator{ |
| 517 | .reallocFn = realloc, | 493 | .allocFn = alloc, |
| 518 | .shrinkFn = shrink, | 494 | .resizeFn = resize, |
| 519 | }, | 495 | }, |
| 520 | .buffer = buffer, | 496 | .buffer = buffer, |
| 521 | .end_index = 0, | 497 | .end_index = 0, |
| 522 | }; | 498 | }; |
| 523 | } | 499 | } |
| 524 | 500 | ||
| 525 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { | 501 | pub fn ownsPtr(self: *FixedBufferAllocator, ptr: [*]u8) bool { |
| 502 | return sliceContainsPtr(self.buffer, ptr); | ||
| 503 | } | ||
| 504 | |||
| 505 | pub fn ownsSlice(self: *FixedBufferAllocator, slice: []u8) bool { | ||
| 506 | return sliceContainsSlice(self.buffer, slice); | ||
| 507 | } | ||
| 508 | |||
| 509 | /// NOTE: this will not work in all cases, if the last allocation had an adjusted_index | ||
| 510 | /// then we won't be able to determine what the last allocation was. This is because | ||
| 511 | /// the alignForward operation done in alloc is not reverisible. | ||
| 512 | pub fn isLastAllocation(self: *FixedBufferAllocator, buf: []u8) bool { | ||
| 513 | return buf.ptr + buf.len == self.buffer.ptr + self.end_index; | ||
| 514 | } | ||
| 515 | |||
| 516 | fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { | ||
| 526 | const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); | 517 | const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); |
| 527 | const addr = @ptrToInt(self.buffer.ptr) + self.end_index; | 518 | const aligned_addr = mem.alignForward(@ptrToInt(self.buffer.ptr) + self.end_index, ptr_align); |
| 528 | const adjusted_addr = mem.alignForward(addr, alignment); | 519 | const adjusted_index = aligned_addr - @ptrToInt(self.buffer.ptr); |
| 529 | const adjusted_index = self.end_index + (adjusted_addr - addr); | ||
| 530 | const new_end_index = adjusted_index + n; | 520 | const new_end_index = adjusted_index + n; |
| 531 | if (new_end_index > self.buffer.len) { | 521 | if (new_end_index > self.buffer.len) { |
| 532 | return error.OutOfMemory; | 522 | return error.OutOfMemory; |
| ... | @@ -537,30 +527,28 @@ pub const FixedBufferAllocator = struct { | ... | @@ -537,30 +527,28 @@ pub const FixedBufferAllocator = struct { |
| 537 | return result; | 527 | return result; |
| 538 | } | 528 | } |
| 539 | 529 | ||
| 540 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 530 | fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) Allocator.Error!usize { |
| 541 | const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); | 531 | const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); |
| 542 | assert(old_mem.len <= self.end_index); | 532 | assert(self.ownsSlice(buf)); // sanity check |
| 543 | if (old_mem.ptr == self.buffer.ptr + self.end_index - old_mem.len and | 533 | |
| 544 | mem.alignForward(@ptrToInt(old_mem.ptr), new_align) == @ptrToInt(old_mem.ptr)) | 534 | if (!self.isLastAllocation(buf)) { |
| 545 | { | 535 | if (new_size > buf.len) |
| 546 | const start_index = self.end_index - old_mem.len; | 536 | return error.OutOfMemory; |
| 547 | const new_end_index = start_index + new_size; | 537 | return if (new_size == 0) 0 else mem.alignAllocLen(buf.len, new_size, len_align); |
| 548 | if (new_end_index > self.buffer.len) return error.OutOfMemory; | ||
| 549 | const result = self.buffer[start_index..new_end_index]; | ||
| 550 | self.end_index = new_end_index; | ||
| 551 | return result; | ||
| 552 | } else if (new_size <= old_mem.len and new_align <= old_align) { | ||
| 553 | // We can't do anything with the memory, so tell the client to keep it. | ||
| 554 | return error.OutOfMemory; | ||
| 555 | } else { | ||
| 556 | const result = try alloc(allocator, new_size, new_align); | ||
| 557 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 558 | return result; | ||
| 559 | } | 538 | } |
| 560 | } | ||
| 561 | 539 | ||
| 562 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 540 | if (new_size <= buf.len) { |
| 563 | return old_mem[0..new_size]; | 541 | const sub = buf.len - new_size; |
| 542 | self.end_index -= sub; | ||
| 543 | return if (new_size == 0) 0 else mem.alignAllocLen(buf.len - sub, new_size, len_align); | ||
| 544 | } | ||
| 545 | |||
| 546 | const add = new_size - buf.len; | ||
| 547 | if (add + self.end_index > self.buffer.len) { | ||
| 548 | return error.OutOfMemory; | ||
| 549 | } | ||
| 550 | self.end_index += add; | ||
| 551 | return new_size; | ||
| 564 | } | 552 | } |
| 565 | 553 | ||
| 566 | pub fn reset(self: *FixedBufferAllocator) void { | 554 | pub fn reset(self: *FixedBufferAllocator) void { |
| ... | @@ -581,20 +569,20 @@ pub const ThreadSafeFixedBufferAllocator = blk: { | ... | @@ -581,20 +569,20 @@ pub const ThreadSafeFixedBufferAllocator = blk: { |
| 581 | pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator { | 569 | pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator { |
| 582 | return ThreadSafeFixedBufferAllocator{ | 570 | return ThreadSafeFixedBufferAllocator{ |
| 583 | .allocator = Allocator{ | 571 | .allocator = Allocator{ |
| 584 | .reallocFn = realloc, | 572 | .allocFn = alloc, |
| 585 | .shrinkFn = shrink, | 573 | .resizeFn = Allocator.noResize, |
| 586 | }, | 574 | }, |
| 587 | .buffer = buffer, | 575 | .buffer = buffer, |
| 588 | .end_index = 0, | 576 | .end_index = 0, |
| 589 | }; | 577 | }; |
| 590 | } | 578 | } |
| 591 | 579 | ||
| 592 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { | 580 | fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { |
| 593 | const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator); | 581 | const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator); |
| 594 | var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst); | 582 | var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst); |
| 595 | while (true) { | 583 | while (true) { |
| 596 | const addr = @ptrToInt(self.buffer.ptr) + end_index; | 584 | const addr = @ptrToInt(self.buffer.ptr) + end_index; |
| 597 | const adjusted_addr = mem.alignForward(addr, alignment); | 585 | const adjusted_addr = mem.alignForward(addr, ptr_align); |
| 598 | const adjusted_index = end_index + (adjusted_addr - addr); | 586 | const adjusted_index = end_index + (adjusted_addr - addr); |
| 599 | const new_end_index = adjusted_index + n; | 587 | const new_end_index = adjusted_index + n; |
| 600 | if (new_end_index > self.buffer.len) { | 588 | if (new_end_index > self.buffer.len) { |
| ... | @@ -604,21 +592,6 @@ pub const ThreadSafeFixedBufferAllocator = blk: { | ... | @@ -604,21 +592,6 @@ pub const ThreadSafeFixedBufferAllocator = blk: { |
| 604 | } | 592 | } |
| 605 | } | 593 | } |
| 606 | 594 | ||
| 607 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | ||
| 608 | if (new_size <= old_mem.len and new_align <= old_align) { | ||
| 609 | // We can't do anything useful with the memory, tell the client to keep it. | ||
| 610 | return error.OutOfMemory; | ||
| 611 | } else { | ||
| 612 | const result = try alloc(allocator, new_size, new_align); | ||
| 613 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 614 | return result; | ||
| 615 | } | ||
| 616 | } | ||
| 617 | |||
| 618 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | ||
| 619 | return old_mem[0..new_size]; | ||
| 620 | } | ||
| 621 | |||
| 622 | pub fn reset(self: *ThreadSafeFixedBufferAllocator) void { | 595 | pub fn reset(self: *ThreadSafeFixedBufferAllocator) void { |
| 623 | self.end_index = 0; | 596 | self.end_index = 0; |
| 624 | } | 597 | } |
| ... | @@ -632,8 +605,8 @@ pub fn stackFallback(comptime size: usize, fallback_allocator: *Allocator) Stack | ... | @@ -632,8 +605,8 @@ pub fn stackFallback(comptime size: usize, fallback_allocator: *Allocator) Stack |
| 632 | .fallback_allocator = fallback_allocator, | 605 | .fallback_allocator = fallback_allocator, |
| 633 | .fixed_buffer_allocator = undefined, | 606 | .fixed_buffer_allocator = undefined, |
| 634 | .allocator = Allocator{ | 607 | .allocator = Allocator{ |
| 635 | .reallocFn = StackFallbackAllocator(size).realloc, | 608 | .allocFn = StackFallbackAllocator(size).realloc, |
| 636 | .shrinkFn = StackFallbackAllocator(size).shrink, | 609 | .resizeFn = StackFallbackAllocator(size).resize, |
| 637 | }, | 610 | }, |
| 638 | }; | 611 | }; |
| 639 | } | 612 | } |
| ... | @@ -652,58 +625,19 @@ pub fn StackFallbackAllocator(comptime size: usize) type { | ... | @@ -652,58 +625,19 @@ pub fn StackFallbackAllocator(comptime size: usize) type { |
| 652 | return &self.allocator; | 625 | return &self.allocator; |
| 653 | } | 626 | } |
| 654 | 627 | ||
| 655 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 628 | fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![*]u8 { |
| 656 | const self = @fieldParentPtr(Self, "allocator", allocator); | 629 | const self = @fieldParentPtr(Self, "allocator", allocator); |
| 657 | const in_buffer = @ptrToInt(old_mem.ptr) >= @ptrToInt(&self.buffer) and | 630 | return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, ptr_align) catch |
| 658 | @ptrToInt(old_mem.ptr) < @ptrToInt(&self.buffer) + self.buffer.len; | 631 | return fallback_allocator.alloc(len, ptr_align); |
| 659 | if (in_buffer) { | ||
| 660 | return FixedBufferAllocator.realloc( | ||
| 661 | &self.fixed_buffer_allocator.allocator, | ||
| 662 | old_mem, | ||
| 663 | old_align, | ||
| 664 | new_size, | ||
| 665 | new_align, | ||
| 666 | ) catch { | ||
| 667 | const result = try self.fallback_allocator.reallocFn( | ||
| 668 | self.fallback_allocator, | ||
| 669 | &[0]u8{}, | ||
| 670 | undefined, | ||
| 671 | new_size, | ||
| 672 | new_align, | ||
| 673 | ); | ||
| 674 | mem.copy(u8, result, old_mem); | ||
| 675 | return result; | ||
| 676 | }; | ||
| 677 | } | ||
| 678 | return self.fallback_allocator.reallocFn( | ||
| 679 | self.fallback_allocator, | ||
| 680 | old_mem, | ||
| 681 | old_align, | ||
| 682 | new_size, | ||
| 683 | new_align, | ||
| 684 | ); | ||
| 685 | } | 632 | } |
| 686 | 633 | ||
| 687 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 634 | fn resize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!void { |
| 688 | const self = @fieldParentPtr(Self, "allocator", allocator); | 635 | const self = @fieldParentPtr(Self, "allocator", allocator); |
| 689 | const in_buffer = @ptrToInt(old_mem.ptr) >= @ptrToInt(&self.buffer) and | 636 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { |
| 690 | @ptrToInt(old_mem.ptr) < @ptrToInt(&self.buffer) + self.buffer.len; | 637 | try self.fixed_buffer_allocator.callResizeFn(buf, new_len); |
| 691 | if (in_buffer) { | 638 | } else { |
| 692 | return FixedBufferAllocator.shrink( | 639 | try self.fallback_allocator.callResizeFn(buf, new_len); |
| 693 | &self.fixed_buffer_allocator.allocator, | ||
| 694 | old_mem, | ||
| 695 | old_align, | ||
| 696 | new_size, | ||
| 697 | new_align, | ||
| 698 | ); | ||
| 699 | } | 640 | } |
| 700 | return self.fallback_allocator.shrinkFn( | ||
| 701 | self.fallback_allocator, | ||
| 702 | old_mem, | ||
| 703 | old_align, | ||
| 704 | new_size, | ||
| 705 | new_align, | ||
| 706 | ); | ||
| 707 | } | 641 | } |
| 708 | }; | 642 | }; |
| 709 | } | 643 | } |
| ... | @@ -718,8 +652,8 @@ test "c_allocator" { | ... | @@ -718,8 +652,8 @@ test "c_allocator" { |
| 718 | 652 | ||
| 719 | test "WasmPageAllocator internals" { | 653 | test "WasmPageAllocator internals" { |
| 720 | if (comptime std.Target.current.isWasm()) { | 654 | if (comptime std.Target.current.isWasm()) { |
| 721 | const conventional_memsize = WasmPageAllocator.conventional.totalPages() * std.mem.page_size; | 655 | const conventional_memsize = WasmPageAllocator.conventional.totalPages() * mem.page_size; |
| 722 | const initial = try page_allocator.alloc(u8, std.mem.page_size); | 656 | const initial = try page_allocator.alloc(u8, mem.page_size); |
| 723 | std.debug.assert(@ptrToInt(initial.ptr) < conventional_memsize); // If this isn't conventional, the rest of these tests don't make sense. Also we have a serious memory leak in the test suite. | 657 | std.debug.assert(@ptrToInt(initial.ptr) < conventional_memsize); // If this isn't conventional, the rest of these tests don't make sense. Also we have a serious memory leak in the test suite. |
| 724 | 658 | ||
| 725 | var inplace = try page_allocator.realloc(initial, 1); | 659 | var inplace = try page_allocator.realloc(initial, 1); |
| ... | @@ -799,7 +733,7 @@ test "ArenaAllocator" { | ... | @@ -799,7 +733,7 @@ test "ArenaAllocator" { |
| 799 | 733 | ||
| 800 | var test_fixed_buffer_allocator_memory: [800000 * @sizeOf(u64)]u8 = undefined; | 734 | var test_fixed_buffer_allocator_memory: [800000 * @sizeOf(u64)]u8 = undefined; |
| 801 | test "FixedBufferAllocator" { | 735 | test "FixedBufferAllocator" { |
| 802 | var fixed_buffer_allocator = FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); | 736 | var fixed_buffer_allocator = mem.validationWrap(FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..])); |
| 803 | 737 | ||
| 804 | try testAllocator(&fixed_buffer_allocator.allocator); | 738 | try testAllocator(&fixed_buffer_allocator.allocator); |
| 805 | try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); | 739 | try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); |
| ... | @@ -865,7 +799,10 @@ test "ThreadSafeFixedBufferAllocator" { | ... | @@ -865,7 +799,10 @@ test "ThreadSafeFixedBufferAllocator" { |
| 865 | try testAllocatorAlignedShrink(&fixed_buffer_allocator.allocator); | 799 | try testAllocatorAlignedShrink(&fixed_buffer_allocator.allocator); |
| 866 | } | 800 | } |
| 867 | 801 | ||
| 868 | fn testAllocator(allocator: *mem.Allocator) !void { | 802 | fn testAllocator(base_allocator: *mem.Allocator) !void { |
| 803 | var validationAllocator = mem.validationWrap(base_allocator); | ||
| 804 | const allocator = &validationAllocator.allocator; | ||
| 805 | |||
| 869 | var slice = try allocator.alloc(*i32, 100); | 806 | var slice = try allocator.alloc(*i32, 100); |
| 870 | testing.expect(slice.len == 100); | 807 | testing.expect(slice.len == 100); |
| 871 | for (slice) |*item, i| { | 808 | for (slice) |*item, i| { |
| ... | @@ -893,7 +830,10 @@ fn testAllocator(allocator: *mem.Allocator) !void { | ... | @@ -893,7 +830,10 @@ fn testAllocator(allocator: *mem.Allocator) !void { |
| 893 | allocator.free(slice); | 830 | allocator.free(slice); |
| 894 | } | 831 | } |
| 895 | 832 | ||
| 896 | fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !void { | 833 | fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void { |
| 834 | var validationAllocator = mem.validationWrap(base_allocator); | ||
| 835 | const allocator = &validationAllocator.allocator; | ||
| 836 | |||
| 897 | // initial | 837 | // initial |
| 898 | var slice = try allocator.alignedAlloc(u8, alignment, 10); | 838 | var slice = try allocator.alignedAlloc(u8, alignment, 10); |
| 899 | testing.expect(slice.len == 10); | 839 | testing.expect(slice.len == 10); |
| ... | @@ -917,7 +857,10 @@ fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !voi | ... | @@ -917,7 +857,10 @@ fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !voi |
| 917 | testing.expect(slice.len == 0); | 857 | testing.expect(slice.len == 0); |
| 918 | } | 858 | } |
| 919 | 859 | ||
| 920 | fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!void { | 860 | fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Error!void { |
| 861 | var validationAllocator = mem.validationWrap(base_allocator); | ||
| 862 | const allocator = &validationAllocator.allocator; | ||
| 863 | |||
| 921 | //Maybe a platform's page_size is actually the same as or | 864 | //Maybe a platform's page_size is actually the same as or |
| 922 | // very near usize? | 865 | // very near usize? |
| 923 | if (mem.page_size << 2 > maxInt(usize)) return; | 866 | if (mem.page_size << 2 > maxInt(usize)) return; |
| ... | @@ -946,7 +889,10 @@ fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!vo | ... | @@ -946,7 +889,10 @@ fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!vo |
| 946 | allocator.free(slice); | 889 | allocator.free(slice); |
| 947 | } | 890 | } |
| 948 | 891 | ||
| 949 | fn testAllocatorAlignedShrink(allocator: *mem.Allocator) mem.Allocator.Error!void { | 892 | fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator.Error!void { |
| 893 | var validationAllocator = mem.validationWrap(base_allocator); | ||
| 894 | const allocator = &validationAllocator.allocator; | ||
| 895 | |||
| 950 | var debug_buffer: [1000]u8 = undefined; | 896 | var debug_buffer: [1000]u8 = undefined; |
| 951 | const debug_allocator = &FixedBufferAllocator.init(&debug_buffer).allocator; | 897 | const debug_allocator = &FixedBufferAllocator.init(&debug_buffer).allocator; |
| 952 | 898 |
lib/std/heap/arena_allocator.zig+6-21| ... | @@ -20,8 +20,8 @@ pub const ArenaAllocator = struct { | ... | @@ -20,8 +20,8 @@ pub const ArenaAllocator = struct { |
| 20 | pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator { | 20 | pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator { |
| 21 | return .{ | 21 | return .{ |
| 22 | .allocator = Allocator{ | 22 | .allocator = Allocator{ |
| 23 | .reallocFn = realloc, | 23 | .allocFn = alloc, |
| 24 | .shrinkFn = shrink, | 24 | .resizeFn = Allocator.noResize, |
| 25 | }, | 25 | }, |
| 26 | .child_allocator = child_allocator, | 26 | .child_allocator = child_allocator, |
| 27 | .state = self, | 27 | .state = self, |
| ... | @@ -61,18 +61,18 @@ pub const ArenaAllocator = struct { | ... | @@ -61,18 +61,18 @@ pub const ArenaAllocator = struct { |
| 61 | return buf_node; | 61 | return buf_node; |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { | 64 | fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { |
| 65 | const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); | 65 | const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); |
| 66 | 66 | ||
| 67 | var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment); | 67 | var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + ptr_align); |
| 68 | while (true) { | 68 | while (true) { |
| 69 | const cur_buf = cur_node.data[@sizeOf(BufNode)..]; | 69 | const cur_buf = cur_node.data[@sizeOf(BufNode)..]; |
| 70 | const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index; | 70 | const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index; |
| 71 | const adjusted_addr = mem.alignForward(addr, alignment); | 71 | const adjusted_addr = mem.alignForward(addr, ptr_align); |
| 72 | const adjusted_index = self.state.end_index + (adjusted_addr - addr); | 72 | const adjusted_index = self.state.end_index + (adjusted_addr - addr); |
| 73 | const new_end_index = adjusted_index + n; | 73 | const new_end_index = adjusted_index + n; |
| 74 | if (new_end_index > cur_buf.len) { | 74 | if (new_end_index > cur_buf.len) { |
| 75 | cur_node = try self.createNode(cur_buf.len, n + alignment); | 75 | cur_node = try self.createNode(cur_buf.len, n + ptr_align); |
| 76 | continue; | 76 | continue; |
| 77 | } | 77 | } |
| 78 | const result = cur_buf[adjusted_index..new_end_index]; | 78 | const result = cur_buf[adjusted_index..new_end_index]; |
| ... | @@ -80,19 +80,4 @@ pub const ArenaAllocator = struct { | ... | @@ -80,19 +80,4 @@ pub const ArenaAllocator = struct { |
| 80 | return result; | 80 | return result; |
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | |||
| 84 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | ||
| 85 | if (new_size <= old_mem.len and new_align <= new_size) { | ||
| 86 | // We can't do anything with the memory, so tell the client to keep it. | ||
| 87 | return error.OutOfMemory; | ||
| 88 | } else { | ||
| 89 | const result = try alloc(allocator, new_size, new_align); | ||
| 90 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 91 | return result; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | ||
| 96 | return old_mem[0..new_size]; | ||
| 97 | } | ||
| 98 | }; | 83 | }; |
lib/std/heap/logging_allocator.zig+37-24| ... | @@ -15,39 +15,45 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { | ... | @@ -15,39 +15,45 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { |
| 15 | pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self { | 15 | pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self { |
| 16 | return Self{ | 16 | return Self{ |
| 17 | .allocator = Allocator{ | 17 | .allocator = Allocator{ |
| 18 | .reallocFn = realloc, | 18 | .allocFn = alloc, |
| 19 | .shrinkFn = shrink, | 19 | .resizeFn = resize, |
| 20 | }, | 20 | }, |
| 21 | .parent_allocator = parent_allocator, | 21 | .parent_allocator = parent_allocator, |
| 22 | .out_stream = out_stream, | 22 | .out_stream = out_stream, |
| 23 | }; | 23 | }; |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 26 | fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { |
| 27 | const self = @fieldParentPtr(Self, "allocator", allocator); | 27 | const self = @fieldParentPtr(Self, "allocator", allocator); |
| 28 | if (old_mem.len == 0) { | 28 | self.out_stream.print("alloc : {}", .{len}) catch {}; |
| 29 | self.out_stream.print("allocation of {} ", .{new_size}) catch {}; | 29 | const result = self.parent_allocator.callAllocFn(len, ptr_align, len_align); |
| 30 | } else { | ||
| 31 | self.out_stream.print("resize from {} to {} ", .{ old_mem.len, new_size }) catch {}; | ||
| 32 | } | ||
| 33 | const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align); | ||
| 34 | if (result) |buff| { | 30 | if (result) |buff| { |
| 35 | self.out_stream.print("success!\n", .{}) catch {}; | 31 | self.out_stream.print(" success!\n", .{}) catch {}; |
| 36 | } else |err| { | 32 | } else |err| { |
| 37 | self.out_stream.print("failure!\n", .{}) catch {}; | 33 | self.out_stream.print(" failure!\n", .{}) catch {}; |
| 38 | } | 34 | } |
| 39 | return result; | 35 | return result; |
| 40 | } | 36 | } |
| 41 | 37 | ||
| 42 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 38 | fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize { |
| 43 | const self = @fieldParentPtr(Self, "allocator", allocator); | 39 | const self = @fieldParentPtr(Self, "allocator", allocator); |
| 44 | const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align); | 40 | if (new_len == 0) { |
| 45 | if (new_size == 0) { | 41 | self.out_stream.print("free : {}\n", .{buf.len}) catch {}; |
| 46 | self.out_stream.print("free of {} bytes success!\n", .{old_mem.len}) catch {}; | 42 | } else if (new_len <= buf.len) { |
| 43 | self.out_stream.print("shrink: {} to {}\n", .{buf.len, new_len}) catch {}; | ||
| 47 | } else { | 44 | } else { |
| 48 | self.out_stream.print("shrink from {} bytes to {} bytes success!\n", .{ old_mem.len, new_size }) catch {}; | 45 | self.out_stream.print("expand: {} to {}", .{ buf.len, new_len }) catch {}; |
| 46 | } | ||
| 47 | if (self.parent_allocator.callResizeFn(buf, new_len, len_align)) |resized_len| { | ||
| 48 | if (new_len > buf.len) { | ||
| 49 | self.out_stream.print(" success!\n", .{}) catch {}; | ||
| 50 | } | ||
| 51 | return resized_len; | ||
| 52 | } else |e| { | ||
| 53 | std.debug.assert(new_len > buf.len); | ||
| 54 | self.out_stream.print(" failure!\n", .{}) catch {}; | ||
| 55 | return e; | ||
| 49 | } | 56 | } |
| 50 | return result; | ||
| 51 | } | 57 | } |
| 52 | }; | 58 | }; |
| 53 | } | 59 | } |
| ... | @@ -60,17 +66,24 @@ pub fn loggingAllocator( | ... | @@ -60,17 +66,24 @@ pub fn loggingAllocator( |
| 60 | } | 66 | } |
| 61 | 67 | ||
| 62 | test "LoggingAllocator" { | 68 | test "LoggingAllocator" { |
| 63 | var buf: [255]u8 = undefined; | 69 | var log_buf: [255]u8 = undefined; |
| 64 | var fbs = std.io.fixedBufferStream(&buf); | 70 | var fbs = std.io.fixedBufferStream(&log_buf); |
| 65 | 71 | ||
| 66 | const allocator = &loggingAllocator(std.testing.allocator, fbs.outStream()).allocator; | 72 | var allocator_buf: [10]u8 = undefined; |
| 73 | var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf)); | ||
| 74 | const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator; | ||
| 67 | 75 | ||
| 68 | const ptr = try allocator.alloc(u8, 10); | 76 | var a = try allocator.alloc(u8, 10); |
| 69 | allocator.free(ptr); | 77 | a.len = allocator.shrinkBytes(a, 5, 0); |
| 78 | std.debug.assert(a.len == 5); | ||
| 79 | std.testing.expectError(error.OutOfMemory, allocator.callResizeFn(a, 20, 0)); | ||
| 80 | allocator.free(a); | ||
| 70 | 81 | ||
| 71 | std.testing.expectEqualSlices(u8, | 82 | std.testing.expectEqualSlices(u8, |
| 72 | \\allocation of 10 success! | 83 | \\alloc : 10 success! |
| 73 | \\free of 10 bytes success! | 84 | \\shrink: 10 to 5 |
| 85 | \\expand: 5 to 20 failure! | ||
| 86 | \\free : 5 | ||
| 74 | \\ | 87 | \\ |
| 75 | , fbs.getWritten()); | 88 | , fbs.getWritten()); |
| 76 | } | 89 | } |
lib/std/mem.zig+258-57| ... | @@ -16,6 +16,52 @@ pub const page_size = switch (builtin.arch) { | ... | @@ -16,6 +16,52 @@ pub const page_size = switch (builtin.arch) { |
| 16 | pub const Allocator = struct { | 16 | pub const Allocator = struct { |
| 17 | pub const Error = error{OutOfMemory}; | 17 | pub const Error = error{OutOfMemory}; |
| 18 | 18 | ||
| 19 | /// Attempt to allocate at least `len` bytes aligned to `ptr_align`. | ||
| 20 | /// | ||
| 21 | /// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes, | ||
| 22 | /// otherwise, the length must be aligned to `len_align`. | ||
| 23 | /// | ||
| 24 | /// `len` must be greater than or equal to `len_align` and must be aligned by `len_align`. | ||
| 25 | allocFn: fn (self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Error![]u8, | ||
| 26 | |||
| 27 | /// Attempt to expand or shrink memory in place. `buf.len` must equal the most recent | ||
| 28 | /// length returned by `allocFn` or `resizeFn`. | ||
| 29 | /// | ||
| 30 | /// Passing a `new_len` of 0 frees and invalidates the buffer such that it can no | ||
| 31 | /// longer be passed to `resizeFn`. | ||
| 32 | /// | ||
| 33 | /// error.OutOfMemory can only be returned if `new_len` is greater than `buf.len`. | ||
| 34 | /// If `buf` cannot be expanded to accomodate `new_len`, then the allocation MUST be | ||
| 35 | /// unmodified and error.OutOfMemory MUST be returned. | ||
| 36 | /// | ||
| 37 | /// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes, | ||
| 38 | /// otherwise, the length must be aligned to `len_align`. | ||
| 39 | /// | ||
| 40 | /// `new_len` must be greater than or equal to `len_align` and must be aligned by `len_align`. | ||
| 41 | resizeFn: fn (self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize, | ||
| 42 | |||
| 43 | pub fn callAllocFn(self: *Allocator, new_len: usize, alignment: u29, len_align: u29) Error![]u8 { | ||
| 44 | return self.allocFn(self, new_len, alignment, len_align); | ||
| 45 | } | ||
| 46 | |||
| 47 | pub fn callResizeFn(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize { | ||
| 48 | return self.resizeFn(self, buf, new_len, len_align); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Set to resizeFn if in-place resize is not supported. | ||
| 52 | pub fn noResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize { | ||
| 53 | if (new_len > buf.len) | ||
| 54 | return error.OutOfMemory; | ||
| 55 | return new_len; | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Call `resizeFn`, but caller guarantees that `new_len` <= `buf.len` meaning | ||
| 59 | /// error.OutOfMemory should be impossible. | ||
| 60 | pub fn shrinkBytes(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) usize { | ||
| 61 | assert(new_len <= buf.len); | ||
| 62 | return self.callResizeFn(buf, new_len, len_align) catch unreachable; | ||
| 63 | } | ||
| 64 | |||
| 19 | /// Realloc is used to modify the size or alignment of an existing allocation, | 65 | /// Realloc is used to modify the size or alignment of an existing allocation, |
| 20 | /// as well as to provide the allocator with an opportunity to move an allocation | 66 | /// as well as to provide the allocator with an opportunity to move an allocation |
| 21 | /// to a better location. | 67 | /// to a better location. |
| ... | @@ -24,7 +70,7 @@ pub const Allocator = struct { | ... | @@ -24,7 +70,7 @@ pub const Allocator = struct { |
| 24 | /// When the size/alignment is less than or equal to the previous allocation, | 70 | /// When the size/alignment is less than or equal to the previous allocation, |
| 25 | /// this function returns `error.OutOfMemory` when the allocator decides the client | 71 | /// this function returns `error.OutOfMemory` when the allocator decides the client |
| 26 | /// would be better off keeping the extra alignment/size. Clients will call | 72 | /// would be better off keeping the extra alignment/size. Clients will call |
| 27 | /// `shrinkFn` when they require the allocator to track a new alignment/size, | 73 | /// `callResizeFn` when they require the allocator to track a new alignment/size, |
| 28 | /// and so this function should only return success when the allocator considers | 74 | /// and so this function should only return success when the allocator considers |
| 29 | /// the reallocation desirable from the allocator's perspective. | 75 | /// the reallocation desirable from the allocator's perspective. |
| 30 | /// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle | 76 | /// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle |
| ... | @@ -37,16 +83,15 @@ pub const Allocator = struct { | ... | @@ -37,16 +83,15 @@ pub const Allocator = struct { |
| 37 | /// as `old_mem` was when `reallocFn` is called. The bytes of | 83 | /// as `old_mem` was when `reallocFn` is called. The bytes of |
| 38 | /// `return_value[old_mem.len..]` have undefined values. | 84 | /// `return_value[old_mem.len..]` have undefined values. |
| 39 | /// The returned slice must have its pointer aligned at least to `new_alignment` bytes. | 85 | /// The returned slice must have its pointer aligned at least to `new_alignment` bytes. |
| 40 | reallocFn: fn ( | 86 | fn reallocBytes( |
| 41 | self: *Allocator, | 87 | self: *Allocator, |
| 42 | /// Guaranteed to be the same as what was returned from most recent call to | 88 | /// Guaranteed to be the same as what was returned from most recent call to |
| 43 | /// `reallocFn` or `shrinkFn`. | 89 | /// `allocFn` or `resizeFn`. |
| 44 | /// If `old_mem.len == 0` then this is a new allocation and `new_byte_count` | 90 | /// If `old_mem.len == 0` then this is a new allocation and `new_byte_count` |
| 45 | /// is guaranteed to be >= 1. | 91 | /// is guaranteed to be >= 1. |
| 46 | old_mem: []u8, | 92 | old_mem: []u8, |
| 47 | /// If `old_mem.len == 0` then this is `undefined`, otherwise: | 93 | /// If `old_mem.len == 0` then this is `undefined`, otherwise: |
| 48 | /// Guaranteed to be the same as what was returned from most recent call to | 94 | /// Guaranteed to be the same as what was passed to `allocFn`. |
| 49 | /// `reallocFn` or `shrinkFn`. | ||
| 50 | /// Guaranteed to be >= 1. | 95 | /// Guaranteed to be >= 1. |
| 51 | /// Guaranteed to be a power of 2. | 96 | /// Guaranteed to be a power of 2. |
| 52 | old_alignment: u29, | 97 | old_alignment: u29, |
| ... | @@ -57,23 +102,52 @@ pub const Allocator = struct { | ... | @@ -57,23 +102,52 @@ pub const Allocator = struct { |
| 57 | /// Guaranteed to be a power of 2. | 102 | /// Guaranteed to be a power of 2. |
| 58 | /// Returned slice's pointer must have this alignment. | 103 | /// Returned slice's pointer must have this alignment. |
| 59 | new_alignment: u29, | 104 | new_alignment: u29, |
| 60 | ) Error![]u8, | 105 | /// 0 indicates the length of the slice returned MUST match `new_byte_count` exactly |
| 106 | /// non-zero means the length of the returned slice must be aligned by `len_align` | ||
| 107 | /// `new_len` must be aligned by `len_align` | ||
| 108 | len_align: u29, | ||
| 109 | ) Error![]u8 { | ||
| 110 | if (old_mem.len == 0) { | ||
| 111 | const new_mem = try self.callAllocFn(new_byte_count, new_alignment, len_align); | ||
| 112 | @memset(new_mem.ptr, undefined, new_byte_count); | ||
| 113 | return new_mem; | ||
| 114 | } | ||
| 61 | 115 | ||
| 62 | /// This function deallocates memory. It must succeed. | 116 | if (isAligned(@ptrToInt(old_mem.ptr), new_alignment)) { |
| 63 | shrinkFn: fn ( | 117 | if (new_byte_count <= old_mem.len) { |
| 64 | self: *Allocator, | 118 | const shrunk_len = self.shrinkBytes(old_mem, new_byte_count, len_align); |
| 65 | /// Guaranteed to be the same as what was returned from most recent call to | 119 | if (shrunk_len < old_mem.len) { |
| 66 | /// `reallocFn` or `shrinkFn`. | 120 | @memset(old_mem.ptr + shrunk_len, undefined, old_mem.len - shrunk_len); |
| 67 | old_mem: []u8, | 121 | } |
| 68 | /// Guaranteed to be the same as what was returned from most recent call to | 122 | return old_mem.ptr[0..shrunk_len]; |
| 69 | /// `reallocFn` or `shrinkFn`. | 123 | } |
| 70 | old_alignment: u29, | 124 | if (self.callResizeFn(old_mem, new_byte_count, len_align)) |resized_len| { |
| 71 | /// Guaranteed to be less than or equal to `old_mem.len`. | 125 | assert(resized_len >= new_byte_count); |
| 72 | new_byte_count: usize, | 126 | @memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count); |
| 73 | /// If `new_byte_count == 0` then this is `undefined`, otherwise: | 127 | return old_mem.ptr[0..resized_len]; |
| 74 | /// Guaranteed to be less than or equal to `old_alignment`. | 128 | } else |_| { } |
| 75 | new_alignment: u29, | 129 | } |
| 76 | ) []u8, | 130 | if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) { |
| 131 | return error.OutOfMemory; | ||
| 132 | } | ||
| 133 | return self.moveBytes(old_mem, new_byte_count, new_alignment, len_align); | ||
| 134 | } | ||
| 135 | |||
| 136 | /// Move the given memory to a new location in the given allocator to accomodate a new | ||
| 137 | /// size and alignment. | ||
| 138 | fn moveBytes(self: *Allocator, old_mem: []u8, new_len: usize, new_alignment: u29, len_align: u29) Error![]u8 { | ||
| 139 | assert(old_mem.len > 0); | ||
| 140 | assert(new_len > 0); | ||
| 141 | const new_mem = try self.callAllocFn(new_len, new_alignment, len_align); | ||
| 142 | @memcpy(new_mem.ptr, old_mem.ptr, std.math.min(new_len, old_mem.len)); | ||
| 143 | // DISABLED TO AVOID BUGS IN TRANSLATE C | ||
| 144 | // use './zig build test-translate-c' to reproduce, some of the symbols in the | ||
| 145 | // generated C code will be a sequence of 0xaa (the undefined value), meaning | ||
| 146 | // it is printing data that has been freed | ||
| 147 | //@memset(old_mem.ptr, undefined, old_mem.len); | ||
| 148 | _ = self.shrinkBytes(old_mem, 0, 0); | ||
| 149 | return new_mem; | ||
| 150 | } | ||
| 77 | 151 | ||
| 78 | /// Returns a pointer to undefined memory. | 152 | /// Returns a pointer to undefined memory. |
| 79 | /// Call `destroy` with the result to free the memory. | 153 | /// Call `destroy` with the result to free the memory. |
| ... | @@ -89,8 +163,7 @@ pub const Allocator = struct { | ... | @@ -89,8 +163,7 @@ pub const Allocator = struct { |
| 89 | const T = @TypeOf(ptr).Child; | 163 | const T = @TypeOf(ptr).Child; |
| 90 | if (@sizeOf(T) == 0) return; | 164 | if (@sizeOf(T) == 0) return; |
| 91 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr)); | 165 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr)); |
| 92 | const shrink_result = self.shrinkFn(self, non_const_ptr[0..@sizeOf(T)], @alignOf(T), 0, 1); | 166 | _ = self.shrinkBytes(non_const_ptr[0..@sizeOf(T)], 0, 0); |
| 93 | assert(shrink_result.len == 0); | ||
| 94 | } | 167 | } |
| 95 | 168 | ||
| 96 | /// Allocates an array of `n` items of type `T` and sets all the | 169 | /// Allocates an array of `n` items of type `T` and sets all the |
| ... | @@ -144,15 +217,28 @@ pub const Allocator = struct { | ... | @@ -144,15 +217,28 @@ pub const Allocator = struct { |
| 144 | return self.allocWithOptions(Elem, n, null, sentinel); | 217 | return self.allocWithOptions(Elem, n, null, sentinel); |
| 145 | } | 218 | } |
| 146 | 219 | ||
| 220 | /// Deprecated: use `allocAdvanced` | ||
| 147 | pub fn alignedAlloc( | 221 | pub fn alignedAlloc( |
| 148 | self: *Allocator, | 222 | self: *Allocator, |
| 149 | comptime T: type, | 223 | comptime T: type, |
| 150 | /// null means naturally aligned | 224 | /// null means naturally aligned |
| 151 | comptime alignment: ?u29, | 225 | comptime alignment: ?u29, |
| 152 | n: usize, | 226 | n: usize, |
| 227 | ) Error![]align(alignment orelse @alignOf(T)) T { | ||
| 228 | return self.allocAdvanced(T, alignment, n, .exact); | ||
| 229 | } | ||
| 230 | |||
| 231 | const Exact = enum {exact,at_least}; | ||
| 232 | pub fn allocAdvanced( | ||
| 233 | self: *Allocator, | ||
| 234 | comptime T: type, | ||
| 235 | /// null means naturally aligned | ||
| 236 | comptime alignment: ?u29, | ||
| 237 | n: usize, | ||
| 238 | exact: Exact, | ||
| 153 | ) Error![]align(alignment orelse @alignOf(T)) T { | 239 | ) Error![]align(alignment orelse @alignOf(T)) T { |
| 154 | const a = if (alignment) |a| blk: { | 240 | const a = if (alignment) |a| blk: { |
| 155 | if (a == @alignOf(T)) return alignedAlloc(self, T, null, n); | 241 | if (a == @alignOf(T)) return allocAdvanced(self, T, null, n, exact); |
| 156 | break :blk a; | 242 | break :blk a; |
| 157 | } else @alignOf(T); | 243 | } else @alignOf(T); |
| 158 | 244 | ||
| ... | @@ -161,15 +247,19 @@ pub const Allocator = struct { | ... | @@ -161,15 +247,19 @@ pub const Allocator = struct { |
| 161 | } | 247 | } |
| 162 | 248 | ||
| 163 | const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; | 249 | const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; |
| 164 | const byte_slice = try self.reallocFn(self, &[0]u8{}, undefined, byte_count, a); | 250 | // TODO The `if (alignment == null)` blocks are workarounds for zig not being able to |
| 165 | assert(byte_slice.len == byte_count); | 251 | // access certain type information about T without creating a circular dependency in async |
| 252 | // functions that heap-allocate their own frame with @Frame(func). | ||
| 253 | const sizeOfT = if (alignment == null) @intCast(u29, @divExact(byte_count, n)) else @sizeOf(T); | ||
| 254 | const byte_slice = try self.callAllocFn(byte_count, a, if (exact == .exact) @as(u29, 0) else sizeOfT); | ||
| 255 | switch (exact) { | ||
| 256 | .exact => assert(byte_slice.len == byte_count), | ||
| 257 | .at_least => assert(byte_slice.len >= byte_count), | ||
| 258 | } | ||
| 166 | @memset(byte_slice.ptr, undefined, byte_slice.len); | 259 | @memset(byte_slice.ptr, undefined, byte_slice.len); |
| 167 | if (alignment == null) { | 260 | if (alignment == null) { |
| 168 | // TODO This is a workaround for zig not being able to successfully do | 261 | // This if block is a workaround (see comment above) |
| 169 | // @bytesToSlice(T, @alignCast(a, byte_slice)) without resolving alignment of T, | 262 | return @intToPtr([*]T, @ptrToInt(byte_slice.ptr))[0..@divExact(byte_slice.len, @sizeOf(T))]; |
| 170 | // which causes a circular dependency in async functions which try to heap-allocate | ||
| 171 | // their own frame with @Frame(func). | ||
| 172 | return @intToPtr([*]T, @ptrToInt(byte_slice.ptr))[0..n]; | ||
| 173 | } else { | 263 | } else { |
| 174 | return mem.bytesAsSlice(T, @alignCast(a, byte_slice)); | 264 | return mem.bytesAsSlice(T, @alignCast(a, byte_slice)); |
| 175 | } | 265 | } |
| ... | @@ -190,22 +280,41 @@ pub const Allocator = struct { | ... | @@ -190,22 +280,41 @@ pub const Allocator = struct { |
| 190 | break :t Error![]align(Slice.alignment) Slice.child; | 280 | break :t Error![]align(Slice.alignment) Slice.child; |
| 191 | } { | 281 | } { |
| 192 | const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; | 282 | const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; |
| 193 | return self.alignedRealloc(old_mem, old_alignment, new_n); | 283 | return self.reallocAdvanced(old_mem, old_alignment, new_n, .exact); |
| 284 | } | ||
| 285 | |||
| 286 | pub fn reallocAtLeast(self: *Allocator, old_mem: var, new_n: usize) t: { | ||
| 287 | const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; | ||
| 288 | break :t Error![]align(Slice.alignment) Slice.child; | ||
| 289 | } { | ||
| 290 | const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; | ||
| 291 | return self.reallocAdvanced(old_mem, old_alignment, new_n, .at_least); | ||
| 292 | } | ||
| 293 | |||
| 294 | // Deprecated: use `reallocAdvanced` | ||
| 295 | pub fn alignedRealloc( | ||
| 296 | self: *Allocator, | ||
| 297 | old_mem: var, | ||
| 298 | comptime new_alignment: u29, | ||
| 299 | new_n: usize, | ||
| 300 | ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { | ||
| 301 | return self.reallocAdvanced(old_mem, new_alignment, new_n, .exact); | ||
| 194 | } | 302 | } |
| 195 | 303 | ||
| 196 | /// This is the same as `realloc`, except caller may additionally request | 304 | /// This is the same as `realloc`, except caller may additionally request |
| 197 | /// a new alignment, which can be larger, smaller, or the same as the old | 305 | /// a new alignment, which can be larger, smaller, or the same as the old |
| 198 | /// allocation. | 306 | /// allocation. |
| 199 | pub fn alignedRealloc( | 307 | pub fn reallocAdvanced( |
| 200 | self: *Allocator, | 308 | self: *Allocator, |
| 201 | old_mem: var, | 309 | old_mem: var, |
| 202 | comptime new_alignment: u29, | 310 | comptime new_alignment: u29, |
| 203 | new_n: usize, | 311 | new_n: usize, |
| 312 | exact: Exact, | ||
| 204 | ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { | 313 | ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { |
| 205 | const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; | 314 | const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; |
| 206 | const T = Slice.child; | 315 | const T = Slice.child; |
| 207 | if (old_mem.len == 0) { | 316 | if (old_mem.len == 0) { |
| 208 | return self.alignedAlloc(T, new_alignment, new_n); | 317 | return self.allocAdvanced(T, new_alignment, new_n, exact); |
| 209 | } | 318 | } |
| 210 | if (new_n == 0) { | 319 | if (new_n == 0) { |
| 211 | self.free(old_mem); | 320 | self.free(old_mem); |
| ... | @@ -215,12 +324,9 @@ pub const Allocator = struct { | ... | @@ -215,12 +324,9 @@ pub const Allocator = struct { |
| 215 | const old_byte_slice = mem.sliceAsBytes(old_mem); | 324 | const old_byte_slice = mem.sliceAsBytes(old_mem); |
| 216 | const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; | 325 | const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; |
| 217 | // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure | 326 | // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure |
| 218 | const byte_slice = try self.reallocFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment); | 327 | const new_byte_slice = try self.reallocBytes(old_byte_slice, Slice.alignment, byte_count, new_alignment, |
| 219 | assert(byte_slice.len == byte_count); | 328 | if (exact == .exact) @as(u29, 0) else @sizeOf(T)); |
| 220 | if (new_n > old_mem.len) { | 329 | return mem.bytesAsSlice(T, @alignCast(new_alignment, new_byte_slice)); |
| 221 | @memset(byte_slice.ptr + old_byte_slice.len, undefined, byte_slice.len - old_byte_slice.len); | ||
| 222 | } | ||
| 223 | return mem.bytesAsSlice(T, @alignCast(new_alignment, byte_slice)); | ||
| 224 | } | 330 | } |
| 225 | 331 | ||
| 226 | /// Prefer calling realloc to shrink if you can tolerate failure, such as | 332 | /// Prefer calling realloc to shrink if you can tolerate failure, such as |
| ... | @@ -248,12 +354,9 @@ pub const Allocator = struct { | ... | @@ -248,12 +354,9 @@ pub const Allocator = struct { |
| 248 | const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; | 354 | const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; |
| 249 | const T = Slice.child; | 355 | const T = Slice.child; |
| 250 | 356 | ||
| 251 | if (new_n == 0) { | 357 | if (new_n == old_mem.len) |
| 252 | self.free(old_mem); | 358 | return old_mem; |
| 253 | return old_mem[0..0]; | 359 | assert(new_n < old_mem.len); |
| 254 | } | ||
| 255 | |||
| 256 | assert(new_n <= old_mem.len); | ||
| 257 | assert(new_alignment <= Slice.alignment); | 360 | assert(new_alignment <= Slice.alignment); |
| 258 | 361 | ||
| 259 | // Here we skip the overflow checking on the multiplication because | 362 | // Here we skip the overflow checking on the multiplication because |
| ... | @@ -262,9 +365,8 @@ pub const Allocator = struct { | ... | @@ -262,9 +365,8 @@ pub const Allocator = struct { |
| 262 | 365 | ||
| 263 | const old_byte_slice = mem.sliceAsBytes(old_mem); | 366 | const old_byte_slice = mem.sliceAsBytes(old_mem); |
| 264 | @memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count); | 367 | @memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count); |
| 265 | const byte_slice = self.shrinkFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment); | 368 | _ = self.shrinkBytes(old_byte_slice, byte_count, 0); |
| 266 | assert(byte_slice.len == byte_count); | 369 | return old_mem[0..new_n]; |
| 267 | return mem.bytesAsSlice(T, @alignCast(new_alignment, byte_slice)); | ||
| 268 | } | 370 | } |
| 269 | 371 | ||
| 270 | /// Free an array allocated with `alloc`. To free a single item, | 372 | /// Free an array allocated with `alloc`. To free a single item, |
| ... | @@ -276,8 +378,7 @@ pub const Allocator = struct { | ... | @@ -276,8 +378,7 @@ pub const Allocator = struct { |
| 276 | if (bytes_len == 0) return; | 378 | if (bytes_len == 0) return; |
| 277 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); | 379 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); |
| 278 | @memset(non_const_ptr, undefined, bytes_len); | 380 | @memset(non_const_ptr, undefined, bytes_len); |
| 279 | const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); | 381 | _ = self.shrinkBytes(non_const_ptr[0..bytes_len], 0, 0); |
| 280 | assert(shrink_result.len == 0); | ||
| 281 | } | 382 | } |
| 282 | 383 | ||
| 283 | /// Copies `m` to newly allocated memory. Caller owns the memory. | 384 | /// Copies `m` to newly allocated memory. Caller owns the memory. |
| ... | @@ -296,16 +397,94 @@ pub const Allocator = struct { | ... | @@ -296,16 +397,94 @@ pub const Allocator = struct { |
| 296 | } | 397 | } |
| 297 | }; | 398 | }; |
| 298 | 399 | ||
| 400 | /// Detects and asserts if the std.mem.Allocator interface is violated by the caller | ||
| 401 | /// or the allocator. | ||
| 402 | pub fn ValidationAllocator(comptime T: type) type { return struct { | ||
| 403 | const Self = @This(); | ||
| 404 | allocator: Allocator, | ||
| 405 | underlying_allocator: T, | ||
| 406 | pub fn init(allocator: T) @This() { | ||
| 407 | return .{ | ||
| 408 | .allocator = .{ | ||
| 409 | .allocFn = alloc, | ||
| 410 | .resizeFn = resize, | ||
| 411 | }, | ||
| 412 | .underlying_allocator = allocator, | ||
| 413 | }; | ||
| 414 | } | ||
| 415 | fn getUnderlyingAllocatorPtr(self: *@This()) *Allocator { | ||
| 416 | if (T == *Allocator) return self.underlying_allocator; | ||
| 417 | if (*T == *Allocator) return &self.underlying_allocator; | ||
| 418 | return &self.underlying_allocator.allocator; | ||
| 419 | } | ||
| 420 | pub fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { | ||
| 421 | assert(n > 0); | ||
| 422 | assert(mem.isValidAlign(ptr_align)); | ||
| 423 | if (len_align != 0) { | ||
| 424 | assert(mem.isAlignedAnyAlign(n, len_align)); | ||
| 425 | assert(n >= len_align); | ||
| 426 | } | ||
| 427 | |||
| 428 | const self = @fieldParentPtr(@This(), "allocator", allocator); | ||
| 429 | const result = try self.getUnderlyingAllocatorPtr().callAllocFn(n, ptr_align, len_align); | ||
| 430 | assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align)); | ||
| 431 | if (len_align == 0) { | ||
| 432 | assert(result.len == n); | ||
| 433 | } else { | ||
| 434 | assert(result.len >= n); | ||
| 435 | assert(mem.isAlignedAnyAlign(result.len, len_align)); | ||
| 436 | } | ||
| 437 | return result; | ||
| 438 | } | ||
| 439 | pub fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { | ||
| 440 | assert(buf.len > 0); | ||
| 441 | if (len_align != 0) { | ||
| 442 | assert(mem.isAlignedAnyAlign(new_len, len_align)); | ||
| 443 | assert(new_len >= len_align); | ||
| 444 | } | ||
| 445 | const self = @fieldParentPtr(@This(), "allocator", allocator); | ||
| 446 | const result = try self.getUnderlyingAllocatorPtr().callResizeFn(buf, new_len, len_align); | ||
| 447 | if (len_align == 0) { | ||
| 448 | assert(result == new_len); | ||
| 449 | } else { | ||
| 450 | assert(result >= new_len); | ||
| 451 | assert(mem.isAlignedAnyAlign(result, len_align)); | ||
| 452 | } | ||
| 453 | return result; | ||
| 454 | } | ||
| 455 | pub usingnamespace if (T == *Allocator or !@hasDecl(T, "reset")) struct {} else struct { | ||
| 456 | pub fn reset(self: *Self) void { | ||
| 457 | self.underlying_allocator.reset(); | ||
| 458 | } | ||
| 459 | }; | ||
| 460 | };} | ||
| 461 | |||
| 462 | pub fn validationWrap(allocator: var) ValidationAllocator(@TypeOf(allocator)) { | ||
| 463 | return ValidationAllocator(@TypeOf(allocator)).init(allocator); | ||
| 464 | } | ||
| 465 | |||
| 466 | /// An allocator helper function. Adjusts an allocation length satisfy `len_align`. | ||
| 467 | /// `full_len` should be the full capacity of the allocation which may be greater | ||
| 468 | /// than the `len` that was requsted. This function should only be used by allocators | ||
| 469 | /// that are unaffected by `len_align`. | ||
| 470 | pub fn alignAllocLen(full_len: usize, alloc_len: usize, len_align: u29) usize { | ||
| 471 | assert(alloc_len > 0); | ||
| 472 | assert(alloc_len >= len_align); | ||
| 473 | assert(full_len >= alloc_len); | ||
| 474 | if (len_align == 0) | ||
| 475 | return alloc_len; | ||
| 476 | const adjusted = alignBackwardAnyAlign(full_len, len_align); | ||
| 477 | assert(adjusted >= alloc_len); | ||
| 478 | return adjusted; | ||
| 479 | } | ||
| 480 | |||
| 299 | var failAllocator = Allocator{ | 481 | var failAllocator = Allocator{ |
| 300 | .reallocFn = failAllocatorRealloc, | 482 | .allocFn = failAllocatorAlloc, |
| 301 | .shrinkFn = failAllocatorShrink, | 483 | .resizeFn = Allocator.noResize, |
| 302 | }; | 484 | }; |
| 303 | fn failAllocatorRealloc(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 485 | fn failAllocatorAlloc(self: *Allocator, n: usize, alignment: u29, len_align: u29) Allocator.Error![]u8 { |
| 304 | return error.OutOfMemory; | 486 | return error.OutOfMemory; |
| 305 | } | 487 | } |
| 306 | fn failAllocatorShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | ||
| 307 | @panic("failAllocatorShrink should never be called because it cannot allocate"); | ||
| 308 | } | ||
| 309 | 488 | ||
| 310 | test "mem.Allocator basics" { | 489 | test "mem.Allocator basics" { |
| 311 | testing.expectError(error.OutOfMemory, failAllocator.alloc(u8, 1)); | 490 | testing.expectError(error.OutOfMemory, failAllocator.alloc(u8, 1)); |
| ... | @@ -2190,6 +2369,15 @@ test "alignForward" { | ... | @@ -2190,6 +2369,15 @@ test "alignForward" { |
| 2190 | testing.expect(alignForward(17, 8) == 24); | 2369 | testing.expect(alignForward(17, 8) == 24); |
| 2191 | } | 2370 | } |
| 2192 | 2371 | ||
| 2372 | /// Round an address up to the previous aligned address | ||
| 2373 | /// Unlike `alignBackward`, `alignment` can be any positive number, not just a power of 2. | ||
| 2374 | pub fn alignBackwardAnyAlign(i: usize, alignment: usize) usize { | ||
| 2375 | if (@popCount(usize, alignment) == 1) | ||
| 2376 | return alignBackward(i, alignment); | ||
| 2377 | assert(alignment != 0); | ||
| 2378 | return i - @mod(i, alignment); | ||
| 2379 | } | ||
| 2380 | |||
| 2193 | /// Round an address up to the previous aligned address | 2381 | /// Round an address up to the previous aligned address |
| 2194 | /// The alignment must be a power of 2 and greater than 0. | 2382 | /// The alignment must be a power of 2 and greater than 0. |
| 2195 | pub fn alignBackward(addr: usize, alignment: usize) usize { | 2383 | pub fn alignBackward(addr: usize, alignment: usize) usize { |
| ... | @@ -2206,6 +2394,19 @@ pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T { | ... | @@ -2206,6 +2394,19 @@ pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T { |
| 2206 | return addr & ~(alignment - 1); | 2394 | return addr & ~(alignment - 1); |
| 2207 | } | 2395 | } |
| 2208 | 2396 | ||
| 2397 | /// Returns whether `alignment` is a valid alignment, meaning it is | ||
| 2398 | /// a positive power of 2. | ||
| 2399 | pub fn isValidAlign(alignment: u29) bool { | ||
| 2400 | return @popCount(u29, alignment) == 1; | ||
| 2401 | } | ||
| 2402 | |||
| 2403 | pub fn isAlignedAnyAlign(i: usize, alignment: usize) bool { | ||
| 2404 | if (@popCount(usize, alignment) == 1) | ||
| 2405 | return isAligned(i, alignment); | ||
| 2406 | assert(alignment != 0); | ||
| 2407 | return 0 == @mod(i, alignment); | ||
| 2408 | } | ||
| 2409 | |||
| 2209 | /// Given an address and an alignment, return true if the address is a multiple of the alignment | 2410 | /// Given an address and an alignment, return true if the address is a multiple of the alignment |
| 2210 | /// The alignment must be a power of 2 and greater than 0. | 2411 | /// The alignment must be a power of 2 and greater than 0. |
| 2211 | pub fn isAligned(addr: usize, alignment: usize) bool { | 2412 | pub fn isAligned(addr: usize, alignment: usize) bool { |
lib/std/os/windows/bits.zig+1| ... | @@ -593,6 +593,7 @@ pub const FILE_CURRENT = 1; | ... | @@ -593,6 +593,7 @@ pub const FILE_CURRENT = 1; |
| 593 | pub const FILE_END = 2; | 593 | pub const FILE_END = 2; |
| 594 | 594 | ||
| 595 | pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; | 595 | pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; |
| 596 | pub const HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010; | ||
| 596 | pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; | 597 | pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; |
| 597 | pub const HEAP_NO_SERIALIZE = 0x00000001; | 598 | pub const HEAP_NO_SERIALIZE = 0x00000001; |
| 598 | 599 |
lib/std/testing.zig+1-1| ... | @@ -11,7 +11,7 @@ pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.al | ... | @@ -11,7 +11,7 @@ pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.al |
| 11 | pub const failing_allocator = &failing_allocator_instance.allocator; | 11 | pub const failing_allocator = &failing_allocator_instance.allocator; |
| 12 | pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); | 12 | pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); |
| 13 | 13 | ||
| 14 | pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); | 14 | pub var base_allocator_instance = std.mem.validationWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); |
| 15 | var allocator_mem: [2 * 1024 * 1024]u8 = undefined; | 15 | var allocator_mem: [2 * 1024 * 1024]u8 = undefined; |
| 16 | 16 | ||
| 17 | /// This function is intended to be used only in tests. It prints diagnostics to stderr | 17 | /// This function is intended to be used only in tests. It prints diagnostics to stderr |
lib/std/testing/failing_allocator.zig+18-23| ... | @@ -39,43 +39,38 @@ pub const FailingAllocator = struct { | ... | @@ -39,43 +39,38 @@ pub const FailingAllocator = struct { |
| 39 | .allocations = 0, | 39 | .allocations = 0, |
| 40 | .deallocations = 0, | 40 | .deallocations = 0, |
| 41 | .allocator = mem.Allocator{ | 41 | .allocator = mem.Allocator{ |
| 42 | .reallocFn = realloc, | 42 | .allocFn = alloc, |
| 43 | .shrinkFn = shrink, | 43 | .resizeFn = resize, |
| 44 | }, | 44 | }, |
| 45 | }; | 45 | }; |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 48 | fn alloc(allocator: *std.mem.Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { |
| 49 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); | 49 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); |
| 50 | if (self.index == self.fail_index) { | 50 | if (self.index == self.fail_index) { |
| 51 | return error.OutOfMemory; | 51 | return error.OutOfMemory; |
| 52 | } | 52 | } |
| 53 | const result = try self.internal_allocator.reallocFn( | 53 | const result = try self.internal_allocator.callAllocFn(len, ptr_align, len_align); |
| 54 | self.internal_allocator, | 54 | self.allocated_bytes += result.len; |
| 55 | old_mem, | 55 | self.allocations += 1; |
| 56 | old_align, | ||
| 57 | new_size, | ||
| 58 | new_align, | ||
| 59 | ); | ||
| 60 | if (new_size < old_mem.len) { | ||
| 61 | self.freed_bytes += old_mem.len - new_size; | ||
| 62 | if (new_size == 0) | ||
| 63 | self.deallocations += 1; | ||
| 64 | } else if (new_size > old_mem.len) { | ||
| 65 | self.allocated_bytes += new_size - old_mem.len; | ||
| 66 | if (old_mem.len == 0) | ||
| 67 | self.allocations += 1; | ||
| 68 | } | ||
| 69 | self.index += 1; | 56 | self.index += 1; |
| 70 | return result; | 57 | return result; |
| 71 | } | 58 | } |
| 72 | 59 | ||
| 73 | fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 60 | fn resize(allocator: *std.mem.Allocator, old_mem: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize { |
| 74 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); | 61 | const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); |
| 75 | const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | 62 | const r = self.internal_allocator.callResizeFn(old_mem, new_len, len_align) catch |e| { |
| 76 | self.freed_bytes += old_mem.len - r.len; | 63 | std.debug.assert(new_len > old_mem.len); |
| 77 | if (new_size == 0) | 64 | return e; |
| 65 | }; | ||
| 66 | if (new_len == 0) { | ||
| 78 | self.deallocations += 1; | 67 | self.deallocations += 1; |
| 68 | self.freed_bytes += old_mem.len; | ||
| 69 | } else if (r < old_mem.len) { | ||
| 70 | self.freed_bytes += old_mem.len - r; | ||
| 71 | } else { | ||
| 72 | self.allocated_bytes += r - old_mem.len; | ||
| 73 | } | ||
| 79 | return r; | 74 | return r; |
| 80 | } | 75 | } |
| 81 | }; | 76 | }; |
lib/std/testing/leak_count_allocator.zig+11-10| ... | @@ -14,23 +14,21 @@ pub const LeakCountAllocator = struct { | ... | @@ -14,23 +14,21 @@ pub const LeakCountAllocator = struct { |
| 14 | return .{ | 14 | return .{ |
| 15 | .count = 0, | 15 | .count = 0, |
| 16 | .allocator = .{ | 16 | .allocator = .{ |
| 17 | .reallocFn = realloc, | 17 | .allocFn = alloc, |
| 18 | .shrinkFn = shrink, | 18 | .resizeFn = resize, |
| 19 | }, | 19 | }, |
| 20 | .internal_allocator = allocator, | 20 | .internal_allocator = allocator, |
| 21 | }; | 21 | }; |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 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); | 25 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); |
| 26 | var data = try self.internal_allocator.reallocFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | 26 | const ptr = try self.internal_allocator.callAllocFn(len, ptr_align, len_align); |
| 27 | if (old_mem.len == 0) { | 27 | self.count += 1; |
| 28 | self.count += 1; | 28 | return ptr; |
| 29 | } | ||
| 30 | return data; | ||
| 31 | } | 29 | } |
| 32 | 30 | ||
| 33 | fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 31 | fn resize(allocator: *std.mem.Allocator, old_mem: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize { |
| 34 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); | 32 | const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); |
| 35 | if (new_size == 0) { | 33 | if (new_size == 0) { |
| 36 | if (self.count == 0) { | 34 | if (self.count == 0) { |
| ... | @@ -38,7 +36,10 @@ pub const LeakCountAllocator = struct { | ... | @@ -38,7 +36,10 @@ pub const LeakCountAllocator = struct { |
| 38 | } | 36 | } |
| 39 | self.count -= 1; | 37 | self.count -= 1; |
| 40 | } | 38 | } |
| 41 | return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); | 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 | }; | ||
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | pub fn validate(self: LeakCountAllocator) !void { | 45 | pub fn validate(self: LeakCountAllocator) !void { |