| ... | ... | @@ -190,6 +190,104 @@ pub const DirectAllocator = struct { |
| 190 | 190 | } |
| 191 | 191 | }; |
| 192 | 192 | |
| 193 | pub const HeapAllocator = switch (builtin.os) { |
| 194 | .windows => struct { |
| 195 | allocator: Allocator, |
| 196 | heap_handle: ?HeapHandle, |
| 197 | |
| 198 | const HeapHandle = os.windows.HANDLE; |
| 199 | |
| 200 | pub fn init() HeapAllocator { |
| 201 | return HeapAllocator{ |
| 202 | .allocator = Allocator{ |
| 203 | .reallocFn = realloc, |
| 204 | .shrinkFn = shrink, |
| 205 | }, |
| 206 | .heap_handle = null, |
| 207 | }; |
| 208 | } |
| 209 | |
| 210 | pub fn deinit(self: *HeapAllocator) void { |
| 211 | if (self.heap_handle) |heap_handle| { |
| 212 | _ = os.windows.HeapDestroy(heap_handle); |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { |
| 217 | const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); |
| 218 | if (n == 0) |
| 219 | return (([*]u8)(undefined))[0..0]; |
| 220 | |
| 221 | const amt = n + alignment + @sizeOf(usize); |
| 222 | const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, builtin.AtomicOrder.SeqCst); |
| 223 | const heap_handle = optional_heap_handle orelse blk: { |
| 224 | const hh = os.windows.HeapCreate(0, amt, 0) orelse return error.OutOfMemory; |
| 225 | const other_hh = @cmpxchgStrong(?HeapHandle, &self.heap_handle, null, hh, builtin.AtomicOrder.SeqCst, builtin.AtomicOrder.SeqCst) orelse break :blk hh; |
| 226 | _ = os.windows.HeapDestroy(hh); |
| 227 | break :blk other_hh.?; // can't be null because of the cmpxchg |
| 228 | }; |
| 229 | const ptr = os.windows.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; |
| 230 | const root_addr = @ptrToInt(ptr); |
| 231 | const adjusted_addr = mem.alignForward(root_addr, alignment); |
| 232 | const record_addr = adjusted_addr + n; |
| 233 | @intToPtr(*align(1) usize, record_addr).* = root_addr; |
| 234 | return @intToPtr([*]u8, adjusted_addr)[0..n]; |
| 235 | } |
| 236 | |
| 237 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { |
| 238 | return realloc(allocator, old_mem, old_align, new_size, new_align) catch { |
| 239 | const old_adjusted_addr = @ptrToInt(old_mem.ptr); |
| 240 | const old_record_addr = old_adjusted_addr + old_mem.len; |
| 241 | const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; |
| 242 | const old_ptr = @intToPtr(*c_void, root_addr); |
| 243 | const new_record_addr = old_record_addr - new_size + old_mem.len; |
| 244 | @intToPtr(*align(1) usize, new_record_addr).* = root_addr; |
| 245 | return old_mem[0..new_size]; |
| 246 | }; |
| 247 | } |
| 248 | |
| 249 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { |
| 250 | if (old_mem.len == 0) return alloc(allocator, new_size, new_align); |
| 251 | |
| 252 | const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); |
| 253 | const old_adjusted_addr = @ptrToInt(old_mem.ptr); |
| 254 | const old_record_addr = old_adjusted_addr + old_mem.len; |
| 255 | const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; |
| 256 | const old_ptr = @intToPtr(*c_void, root_addr); |
| 257 | |
| 258 | if (new_size == 0) { |
| 259 | if (os.windows.HeapFree(self.heap_handle.?, 0, old_ptr) == 0) unreachable; |
| 260 | return old_mem[0..0]; |
| 261 | } |
| 262 | |
| 263 | const amt = new_size + new_align + @sizeOf(usize); |
| 264 | const new_ptr = os.windows.HeapReAlloc( |
| 265 | self.heap_handle.?, |
| 266 | 0, |
| 267 | old_ptr, |
| 268 | amt, |
| 269 | ) orelse return error.OutOfMemory; |
| 270 | const offset = old_adjusted_addr - root_addr; |
| 271 | const new_root_addr = @ptrToInt(new_ptr); |
| 272 | var new_adjusted_addr = new_root_addr + offset; |
| 273 | const offset_is_valid = new_adjusted_addr + new_size + @sizeOf(usize) <= new_root_addr + amt; |
| 274 | const offset_is_aligned = new_adjusted_addr % new_align == 0; |
| 275 | if (!offset_is_valid or !offset_is_aligned) { |
| 276 | // If HeapReAlloc didn't happen to move the memory to the new alignment, |
| 277 | // or the memory starting at the old offset would be outside of the new allocation, |
| 278 | // then we need to copy the memory to a valid aligned address and use that |
| 279 | const new_aligned_addr = mem.alignForward(new_root_addr, new_align); |
| 280 | @memcpy(@intToPtr([*]u8, new_aligned_addr), @intToPtr([*]u8, new_adjusted_addr), std.math.min(old_mem.len, new_size)); |
| 281 | new_adjusted_addr = new_aligned_addr; |
| 282 | } |
| 283 | const new_record_addr = new_adjusted_addr + new_size; |
| 284 | @intToPtr(*align(1) usize, new_record_addr).* = new_root_addr; |
| 285 | return @intToPtr([*]u8, new_adjusted_addr)[0..new_size]; |
| 286 | } |
| 287 | }, |
| 288 | else => @compileError("Unsupported OS"), |
| 289 | }; |
| 290 | |
| 193 | 291 | /// This allocator takes an existing allocator, wraps it, and provides an interface |
| 194 | 292 | /// where you can allocate without freeing, and then free it all together. |
| 195 | 293 | pub const ArenaAllocator = struct { |
| ... | ... | @@ -590,6 +688,19 @@ test "DirectAllocator" { |
| 590 | 688 | try testAllocatorAlignedShrink(allocator); |
| 591 | 689 | } |
| 592 | 690 | |
| 691 | test "HeapAllocator" { |
| 692 | if (builtin.os == .windows) { |
| 693 | var heap_allocator = HeapAllocator.init(); |
| 694 | defer heap_allocator.deinit(); |
| 695 | |
| 696 | const allocator = &heap_allocator.allocator; |
| 697 | try testAllocator(allocator); |
| 698 | try testAllocatorAligned(allocator, 16); |
| 699 | try testAllocatorLargeAlignment(allocator); |
| 700 | try testAllocatorAlignedShrink(allocator); |
| 701 | } |
| 702 | } |
| 703 | |
| 593 | 704 | test "ArenaAllocator" { |
| 594 | 705 | var direct_allocator = DirectAllocator.init(); |
| 595 | 706 | defer direct_allocator.deinit(); |