| author | |
| committer | |
| log | 6d40d374d8d2c0eef0c0c6c557461f35b385d085 |
| tree | 73edc83caa615b69b133d4a8fb305bc24953dc08 |
| parent | 8c96487bb95e45a86f35e7cfa6012c265c0fdcca |
2 files changed, 167 insertions(+), 104 deletions(-)
lib/std/heap.zig+2-104| ... | @@ -12,7 +12,7 @@ const Alignment = std.mem.Alignment; | ... | @@ -12,7 +12,7 @@ const Alignment = std.mem.Alignment; |
| 12 | pub const ArenaAllocator = @import("heap/ArenaAllocator.zig"); | 12 | pub const ArenaAllocator = @import("heap/ArenaAllocator.zig"); |
| 13 | pub const SmpAllocator = @import("heap/SmpAllocator.zig"); | 13 | pub const SmpAllocator = @import("heap/SmpAllocator.zig"); |
| 14 | pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig"); | 14 | pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig"); |
| 15 | pub const StackFirstAllocator = @import("heap/StackFirstAllocator.zig"); | 15 | pub const BufferFirstAllocator = @import("heap/BufferFirstAllocator.zig"); |
| 16 | pub const PageAllocator = @import("heap/PageAllocator.zig"); | 16 | pub const PageAllocator = @import("heap/PageAllocator.zig"); |
| 17 | pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented"); | 17 | pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented"); |
| 18 | pub const BrkAllocator = @import("heap/BrkAllocator.zig"); | 18 | pub const BrkAllocator = @import("heap/BrkAllocator.zig"); |
| ... | @@ -368,89 +368,6 @@ pub const brk_allocator: Allocator = .{ | ... | @@ -368,89 +368,6 @@ pub const brk_allocator: Allocator = .{ |
| 368 | .vtable = &BrkAllocator.vtable, | 368 | .vtable = &BrkAllocator.vtable, |
| 369 | }; | 369 | }; |
| 370 | 370 | ||
| 371 | /// An allocator that attempts to allocate from the given buffer, falling back to | ||
| 372 | /// `fallback_allocator` if this fails. | ||
| 373 | pub const StackFallbackAllocator = struct { | ||
| 374 | const Self = @This(); | ||
| 375 | |||
| 376 | fallback_allocator: Allocator, | ||
| 377 | fixed_buffer_allocator: FixedBufferAllocator, | ||
| 378 | |||
| 379 | pub fn init(buf: []u8, fallback_allocator: Allocator) Self { | ||
| 380 | return .{ | ||
| 381 | .fallback_allocator = fallback_allocator, | ||
| 382 | .fixed_buffer_allocator = .init(buf), | ||
| 383 | }; | ||
| 384 | } | ||
| 385 | |||
| 386 | pub fn allocator(self: *Self) Allocator { | ||
| 387 | return .{ | ||
| 388 | .ptr = self, | ||
| 389 | .vtable = &.{ | ||
| 390 | .alloc = alloc, | ||
| 391 | .resize = resize, | ||
| 392 | .remap = remap, | ||
| 393 | .free = free, | ||
| 394 | }, | ||
| 395 | }; | ||
| 396 | } | ||
| 397 | |||
| 398 | fn alloc( | ||
| 399 | ctx: *anyopaque, | ||
| 400 | len: usize, | ||
| 401 | alignment: Alignment, | ||
| 402 | ra: usize, | ||
| 403 | ) ?[*]u8 { | ||
| 404 | const self: *Self = @ptrCast(@alignCast(ctx)); | ||
| 405 | return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse | ||
| 406 | return self.fallback_allocator.rawAlloc(len, alignment, ra); | ||
| 407 | } | ||
| 408 | |||
| 409 | fn resize( | ||
| 410 | ctx: *anyopaque, | ||
| 411 | buf: []u8, | ||
| 412 | alignment: Alignment, | ||
| 413 | new_len: usize, | ||
| 414 | ra: usize, | ||
| 415 | ) bool { | ||
| 416 | const self: *Self = @ptrCast(@alignCast(ctx)); | ||
| 417 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { | ||
| 418 | return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra); | ||
| 419 | } else { | ||
| 420 | return self.fallback_allocator.rawResize(buf, alignment, new_len, ra); | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | fn remap( | ||
| 425 | context: *anyopaque, | ||
| 426 | memory: []u8, | ||
| 427 | alignment: Alignment, | ||
| 428 | new_len: usize, | ||
| 429 | return_address: usize, | ||
| 430 | ) ?[*]u8 { | ||
| 431 | const self: *Self = @ptrCast(@alignCast(context)); | ||
| 432 | if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) { | ||
| 433 | return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address); | ||
| 434 | } else { | ||
| 435 | return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address); | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | fn free( | ||
| 440 | ctx: *anyopaque, | ||
| 441 | buf: []u8, | ||
| 442 | alignment: Alignment, | ||
| 443 | ra: usize, | ||
| 444 | ) void { | ||
| 445 | const self: *Self = @ptrCast(@alignCast(ctx)); | ||
| 446 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { | ||
| 447 | return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra); | ||
| 448 | } else { | ||
| 449 | return self.fallback_allocator.rawFree(buf, alignment, ra); | ||
| 450 | } | ||
| 451 | } | ||
| 452 | }; | ||
| 453 | |||
| 454 | test c_allocator { | 371 | test c_allocator { |
| 455 | if (builtin.link_libc) { | 372 | if (builtin.link_libc) { |
| 456 | try testAllocator(c_allocator); | 373 | try testAllocator(c_allocator); |
| ... | @@ -501,26 +418,6 @@ test ArenaAllocator { | ... | @@ -501,26 +418,6 @@ test ArenaAllocator { |
| 501 | try testAllocatorAlignedShrink(allocator); | 418 | try testAllocatorAlignedShrink(allocator); |
| 502 | } | 419 | } |
| 503 | 420 | ||
| 504 | test StackFallbackAllocator { | ||
| 505 | var buf: [4096]u8 = undefined; | ||
| 506 | { | ||
| 507 | var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator); | ||
| 508 | try testAllocator(stack_allocator.allocator()); | ||
| 509 | } | ||
| 510 | { | ||
| 511 | var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator); | ||
| 512 | try testAllocatorAligned(stack_allocator.allocator()); | ||
| 513 | } | ||
| 514 | { | ||
| 515 | var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator); | ||
| 516 | try testAllocatorLargeAlignment(stack_allocator.allocator()); | ||
| 517 | } | ||
| 518 | { | ||
| 519 | var stack_allocator: StackFallbackAllocator = .init(&buf, std.testing.allocator); | ||
| 520 | try testAllocatorAlignedShrink(stack_allocator.allocator()); | ||
| 521 | } | ||
| 522 | } | ||
| 523 | |||
| 524 | /// This one should not try alignments that exceed what C malloc can handle. | 421 | /// This one should not try alignments that exceed what C malloc can handle. |
| 525 | pub fn testAllocator(base_allocator: mem.Allocator) !void { | 422 | pub fn testAllocator(base_allocator: mem.Allocator) !void { |
| 526 | var validationAllocator = mem.validationWrap(base_allocator); | 423 | var validationAllocator = mem.validationWrap(base_allocator); |
| ... | @@ -989,6 +886,7 @@ test { | ... | @@ -989,6 +886,7 @@ test { |
| 989 | _ = ArenaAllocator; | 886 | _ = ArenaAllocator; |
| 990 | _ = DebugAllocator(.{}); | 887 | _ = DebugAllocator(.{}); |
| 991 | _ = FixedBufferAllocator; | 888 | _ = FixedBufferAllocator; |
| 889 | _ = BufferFirstAllocator; | ||
| 992 | if (builtin.single_threaded) { | 890 | if (builtin.single_threaded) { |
| 993 | if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) { | 891 | if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) { |
| 994 | _ = brk_allocator; | 892 | _ = brk_allocator; |
lib/std/heap/BufferFirstAllocator.zig created+165| ... | @@ -0,0 +1,165 @@ | ||
| 1 | //! An allocator that attempts to allocate from the given buffer, falling back to | ||
| 2 | //! `fallback_allocator` if this fails. | ||
| 3 | |||
| 4 | const std = @import("../std.zig"); | ||
| 5 | const heap = std.heap; | ||
| 6 | const testing = std.testing; | ||
| 7 | |||
| 8 | const Alignment = std.mem.Alignment; | ||
| 9 | const Allocator = std.mem.Allocator; | ||
| 10 | const FixedBufferAllocator = std.heap.FixedBufferAllocator; | ||
| 11 | |||
| 12 | const BufferFirstAllocator = @This(); | ||
| 13 | |||
| 14 | fallback_allocator: Allocator, | ||
| 15 | fixed_buffer_allocator: FixedBufferAllocator, | ||
| 16 | |||
| 17 | pub fn init(buffer: []u8, fallback_allocator: Allocator) BufferFirstAllocator { | ||
| 18 | return .{ | ||
| 19 | .fallback_allocator = fallback_allocator, | ||
| 20 | .fixed_buffer_allocator = .init(buffer), | ||
| 21 | }; | ||
| 22 | } | ||
| 23 | |||
| 24 | pub fn allocator(self: *BufferFirstAllocator) Allocator { | ||
| 25 | return .{ | ||
| 26 | .ptr = self, | ||
| 27 | .vtable = &.{ | ||
| 28 | .alloc = alloc, | ||
| 29 | .resize = resize, | ||
| 30 | .remap = remap, | ||
| 31 | .free = free, | ||
| 32 | }, | ||
| 33 | }; | ||
| 34 | } | ||
| 35 | |||
| 36 | fn alloc( | ||
| 37 | ctx: *anyopaque, | ||
| 38 | len: usize, | ||
| 39 | alignment: Alignment, | ||
| 40 | ra: usize, | ||
| 41 | ) ?[*]u8 { | ||
| 42 | const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx)); | ||
| 43 | return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse | ||
| 44 | return self.fallback_allocator.rawAlloc(len, alignment, ra); | ||
| 45 | } | ||
| 46 | |||
| 47 | fn resize( | ||
| 48 | ctx: *anyopaque, | ||
| 49 | buf: []u8, | ||
| 50 | alignment: Alignment, | ||
| 51 | new_len: usize, | ||
| 52 | ra: usize, | ||
| 53 | ) bool { | ||
| 54 | const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx)); | ||
| 55 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { | ||
| 56 | return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra); | ||
| 57 | } else { | ||
| 58 | return self.fallback_allocator.rawResize(buf, alignment, new_len, ra); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | fn remap( | ||
| 63 | context: *anyopaque, | ||
| 64 | memory: []u8, | ||
| 65 | alignment: Alignment, | ||
| 66 | new_len: usize, | ||
| 67 | return_address: usize, | ||
| 68 | ) ?[*]u8 { | ||
| 69 | const self: *BufferFirstAllocator = @ptrCast(@alignCast(context)); | ||
| 70 | if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) { | ||
| 71 | return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address); | ||
| 72 | } else { | ||
| 73 | return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | fn free( | ||
| 78 | ctx: *anyopaque, | ||
| 79 | buf: []u8, | ||
| 80 | alignment: Alignment, | ||
| 81 | ra: usize, | ||
| 82 | ) void { | ||
| 83 | const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx)); | ||
| 84 | if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { | ||
| 85 | return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra); | ||
| 86 | } else { | ||
| 87 | return self.fallback_allocator.rawFree(buf, alignment, ra); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | test "BufferFirstAllocator" { | ||
| 92 | // Buffer first specific tests | ||
| 93 | { | ||
| 94 | var buffer: [10]u8 = undefined; | ||
| 95 | var bfa_state: BufferFirstAllocator = .init(&buffer, std.testing.allocator); | ||
| 96 | const bfa = bfa_state.allocator(); | ||
| 97 | |||
| 98 | // We're under the limit, so we should be allocated in the buffer | ||
| 99 | const txt0 = "hellowrld"; | ||
| 100 | const buf0 = try bfa.create(@TypeOf(txt0.*)); | ||
| 101 | buf0.* = txt0.*; | ||
| 102 | try testing.expect(bfa_state.fixed_buffer_allocator.ownsPtr(buf0.ptr)); | ||
| 103 | |||
| 104 | // We're now over the limit, so we should be allocated from the fallback | ||
| 105 | const txt1 = "test!"; | ||
| 106 | const buf1 = try bfa.create(@TypeOf(txt1.*)); | ||
| 107 | buf1.* = txt1.*; | ||
| 108 | try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf1.ptr)); | ||
| 109 | |||
| 110 | // Free the allocation that took up space in the buffer | ||
| 111 | try testing.expectEqualStrings(txt0, buf0); | ||
| 112 | bfa.destroy(buf0); | ||
| 113 | |||
| 114 | // The next allocation would go in the buffer, but it's too big so it doesn't | ||
| 115 | const txt2 = "qwertyqwerty"; | ||
| 116 | const buf2 = try bfa.create(@TypeOf(txt2.*)); | ||
| 117 | buf2.* = txt2.*; | ||
| 118 | try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf2.ptr)); | ||
| 119 | |||
| 120 | // The next allocation is smaller and fits in the buffer | ||
| 121 | const txt3 = "dvorak"; | ||
| 122 | const buf3 = try bfa.create(@TypeOf(txt3.*)); | ||
| 123 | buf3.* = txt3.*; | ||
| 124 | try testing.expect(bfa_state.fixed_buffer_allocator.ownsPtr(buf3.ptr)); | ||
| 125 | |||
| 126 | // The remainder in the buffer is too small for the following allocation so it falls back | ||
| 127 | const txt4 = "moretext"; | ||
| 128 | const buf4 = try bfa.create(@TypeOf(txt4.*)); | ||
| 129 | buf4.* = txt4.*; | ||
| 130 | try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf4.ptr)); | ||
| 131 | |||
| 132 | // Check equality on the remaining buffers and free them | ||
| 133 | try testing.expectEqualStrings(txt1, buf1); | ||
| 134 | bfa.destroy(buf1); | ||
| 135 | try testing.expectEqualStrings(txt2, buf2); | ||
| 136 | bfa.destroy(buf2); | ||
| 137 | try testing.expectEqualStrings(txt3, buf3); | ||
| 138 | bfa.destroy(buf3); | ||
| 139 | try testing.expectEqualStrings(txt4, buf4); | ||
| 140 | bfa.destroy(buf4); | ||
| 141 | |||
| 142 | try testing.expectEqual(0, bfa_state.fixed_buffer_allocator.end_index); | ||
| 143 | } | ||
| 144 | |||
| 145 | // Standard allocator tests | ||
| 146 | { | ||
| 147 | var buf: [4096]u8 = undefined; | ||
| 148 | { | ||
| 149 | var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator); | ||
| 150 | try heap.testAllocator(bfa.allocator()); | ||
| 151 | } | ||
| 152 | { | ||
| 153 | var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator); | ||
| 154 | try heap.testAllocatorAligned(bfa.allocator()); | ||
| 155 | } | ||
| 156 | { | ||
| 157 | var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator); | ||
| 158 | try heap.testAllocatorLargeAlignment(bfa.allocator()); | ||
| 159 | } | ||
| 160 | { | ||
| 161 | var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator); | ||
| 162 | try heap.testAllocatorAlignedShrink(bfa.allocator()); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||