| author | |
| committer | |
| log | 6ccabbd4e558dd0d56c471408c4cf29e0992dc1c |
| tree | 0756cc89b01f788c55a853ad3f521ed342a3b355 |
| parent | 6744160211b2ab480e4c479e46440c76f00c1810 |
6 files changed, 372 insertions(+), 523 deletions(-)
lib/c/malloc.zig+3-3| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | //! Based on wrapping a stateless Zig Allocator implementation, apropriate for: | 1 | //! Based on wrapping a stateless Zig Allocator implementation, appropriate for: |
| 2 | //! - ReleaseFast and ReleaseSmall optimization modes, with multi-threading | 2 | //! - ReleaseFast and ReleaseSmall optimization modes, with multi-threading |
| 3 | //! enabled. | 3 | //! enabled. |
| 4 | //! - WebAssembly in single-threaded mode. | 4 | //! - WebAssembly or Linux in single-threaded release modes. |
| 5 | //! | 5 | //! |
| 6 | //! Because the libc APIs don't have client alignment and size tracking, in | 6 | //! Because the libc APIs don't have client alignment and size tracking, in |
| 7 | //! order to take advantage of Zig allocator implementations, additional | 7 | //! order to take advantage of Zig allocator implementations, additional |
| ... | @@ -40,7 +40,7 @@ const no_context: *anyopaque = undefined; | ... | @@ -40,7 +40,7 @@ const no_context: *anyopaque = undefined; |
| 40 | const no_ra: usize = undefined; | 40 | const no_ra: usize = undefined; |
| 41 | const vtable = switch (builtin.cpu.arch) { | 41 | const vtable = switch (builtin.cpu.arch) { |
| 42 | .wasm32, .wasm64 => std.heap.WasmAllocator.vtable, | 42 | .wasm32, .wasm64 => std.heap.WasmAllocator.vtable, |
| 43 | else => std.heap.SmpAllocator.vtable, | 43 | else => if (builtin.single_threaded) std.heap.BrkAllocator.vtable else std.heap.SmpAllocator.vtable, |
| 44 | }; | 44 | }; |
| 45 | 45 | ||
| 46 | /// Needed because libc memory allocators don't provide old alignment and size | 46 | /// Needed because libc memory allocators don't provide old alignment and size |
lib/std/heap.zig+15-14| ... | @@ -13,9 +13,9 @@ pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; | ... | @@ -13,9 +13,9 @@ pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; |
| 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 PageAllocator = @import("heap/PageAllocator.zig"); | 15 | pub const PageAllocator = @import("heap/PageAllocator.zig"); |
| 16 | pub const SbrkAllocator = @import("heap/sbrk_allocator.zig").SbrkAllocator; | ||
| 17 | pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig"); | 16 | pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig"); |
| 18 | pub const WasmAllocator = @import("heap/WasmAllocator.zig"); | 17 | pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented"); |
| 18 | pub const BrkAllocator = @import("heap/BrkAllocator.zig"); | ||
| 19 | 19 | ||
| 20 | pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config; | 20 | pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config; |
| 21 | pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator; | 21 | pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator; |
| ... | @@ -356,9 +356,6 @@ pub const page_allocator: Allocator = if (@hasDecl(root, "os") and | ... | @@ -356,9 +356,6 @@ pub const page_allocator: Allocator = if (@hasDecl(root, "os") and |
| 356 | else if (builtin.target.cpu.arch.isWasm()) .{ | 356 | else if (builtin.target.cpu.arch.isWasm()) .{ |
| 357 | .ptr = undefined, | 357 | .ptr = undefined, |
| 358 | .vtable = &WasmAllocator.vtable, | 358 | .vtable = &WasmAllocator.vtable, |
| 359 | } else if (builtin.target.os.tag == .plan9) .{ | ||
| 360 | .ptr = undefined, | ||
| 361 | .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable, | ||
| 362 | } else .{ | 359 | } else .{ |
| 363 | .ptr = undefined, | 360 | .ptr = undefined, |
| 364 | .vtable = &PageAllocator.vtable, | 361 | .vtable = &PageAllocator.vtable, |
| ... | @@ -369,16 +366,18 @@ pub const smp_allocator: Allocator = .{ | ... | @@ -369,16 +366,18 @@ pub const smp_allocator: Allocator = .{ |
| 369 | .vtable = &SmpAllocator.vtable, | 366 | .vtable = &SmpAllocator.vtable, |
| 370 | }; | 367 | }; |
| 371 | 368 | ||
| 372 | /// This allocator is fast, small, and specific to WebAssembly. In the future, | 369 | /// This allocator is fast, small, and specific to WebAssembly. |
| 373 | /// this will be the implementation automatically selected by | ||
| 374 | /// `GeneralPurposeAllocator` when compiling in `ReleaseSmall` mode for wasm32 | ||
| 375 | /// and wasm64 architectures. | ||
| 376 | /// Until then, it is available here to play with. | ||
| 377 | pub const wasm_allocator: Allocator = .{ | 370 | pub const wasm_allocator: Allocator = .{ |
| 378 | .ptr = undefined, | 371 | .ptr = undefined, |
| 379 | .vtable = &WasmAllocator.vtable, | 372 | .vtable = &WasmAllocator.vtable, |
| 380 | }; | 373 | }; |
| 381 | 374 | ||
| 375 | /// Supports single-threaded WebAssembly and Linux. | ||
| 376 | pub const brk_allocator: Allocator = .{ | ||
| 377 | .ptr = undefined, | ||
| 378 | .vtable = &BrkAllocator.vtable, | ||
| 379 | }; | ||
| 380 | |||
| 382 | /// Returns a `StackFallbackAllocator` allocating using either a | 381 | /// Returns a `StackFallbackAllocator` allocating using either a |
| 383 | /// `FixedBufferAllocator` on an array of size `size` and falling back to | 382 | /// `FixedBufferAllocator` on an array of size `size` and falling back to |
| 384 | /// `fallback_allocator` if that fails. | 383 | /// `fallback_allocator` if that fails. |
| ... | @@ -1014,9 +1013,11 @@ test { | ... | @@ -1014,9 +1013,11 @@ test { |
| 1014 | _ = GeneralPurposeAllocator; | 1013 | _ = GeneralPurposeAllocator; |
| 1015 | _ = FixedBufferAllocator; | 1014 | _ = FixedBufferAllocator; |
| 1016 | _ = ThreadSafeAllocator; | 1015 | _ = ThreadSafeAllocator; |
| 1017 | _ = SbrkAllocator; | 1016 | if (builtin.single_threaded) { |
| 1018 | if (builtin.target.cpu.arch.isWasm()) { | 1017 | if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) { |
| 1019 | _ = WasmAllocator; | 1018 | _ = brk_allocator; |
| 1019 | } | ||
| 1020 | } else { | ||
| 1021 | _ = smp_allocator; | ||
| 1020 | } | 1022 | } |
| 1021 | if (!builtin.single_threaded) _ = smp_allocator; | ||
| 1022 | } | 1023 | } |
lib/std/heap/BrkAllocator.zig created+350| ... | @@ -0,0 +1,350 @@ | ||
| 1 | //! Supports single-threaded targets that have a sbrk-like primitive which includes | ||
| 2 | //! Linux and WebAssembly. | ||
| 3 | //! | ||
| 4 | //! On Linux, assumes exclusive access to the brk syscall. | ||
| 5 | const BrkAllocator = @This(); | ||
| 6 | const builtin = @import("builtin"); | ||
| 7 | |||
| 8 | const std = @import("../std.zig"); | ||
| 9 | const Allocator = std.mem.Allocator; | ||
| 10 | const Alignment = std.mem.Alignment; | ||
| 11 | const assert = std.debug.assert; | ||
| 12 | const math = std.math; | ||
| 13 | const page_size_max = std.heap.page_size_max; | ||
| 14 | |||
| 15 | comptime { | ||
| 16 | if (!builtin.single_threaded) @compileError("unsupported"); | ||
| 17 | } | ||
| 18 | |||
| 19 | next_addrs: [size_class_count]usize = @splat(0), | ||
| 20 | /// For each size class, points to the freed pointer. | ||
| 21 | frees: [size_class_count]usize = @splat(0), | ||
| 22 | /// For each big size class, points to the freed pointer. | ||
| 23 | big_frees: [big_size_class_count]usize = @splat(0), | ||
| 24 | prev_brk: usize = 0, | ||
| 25 | |||
| 26 | var global: BrkAllocator = .{}; | ||
| 27 | |||
| 28 | pub const vtable: Allocator.VTable = .{ | ||
| 29 | .alloc = alloc, | ||
| 30 | .resize = resize, | ||
| 31 | .remap = remap, | ||
| 32 | .free = free, | ||
| 33 | }; | ||
| 34 | |||
| 35 | pub const Error = Allocator.Error; | ||
| 36 | |||
| 37 | const max_usize = math.maxInt(usize); | ||
| 38 | const ushift = math.Log2Int(usize); | ||
| 39 | const bigpage_size = 64 * 1024; | ||
| 40 | const pages_per_bigpage = bigpage_size / page_size_max; | ||
| 41 | const bigpage_count = max_usize / bigpage_size; | ||
| 42 | |||
| 43 | comptime { | ||
| 44 | assert(bigpage_size >= page_size_max); | ||
| 45 | } | ||
| 46 | |||
| 47 | /// Because of storing free list pointers, the minimum size class is 3. | ||
| 48 | const min_class = math.log2(math.ceilPowerOfTwoAssert(usize, 1 + @sizeOf(usize))); | ||
| 49 | const size_class_count = math.log2(bigpage_size) - min_class; | ||
| 50 | /// 0 - 1 bigpage | ||
| 51 | /// 1 - 2 bigpages | ||
| 52 | /// 2 - 4 bigpages | ||
| 53 | /// etc. | ||
| 54 | const big_size_class_count = math.log2(bigpage_count); | ||
| 55 | |||
| 56 | fn alloc(ctx: *anyopaque, len: usize, alignment: Alignment, return_address: usize) ?[*]u8 { | ||
| 57 | _ = ctx; | ||
| 58 | _ = return_address; | ||
| 59 | // Make room for the freelist next pointer. | ||
| 60 | const actual_len = @max(len +| @sizeOf(usize), alignment.toByteUnits()); | ||
| 61 | const slot_size = math.ceilPowerOfTwo(usize, actual_len) catch return null; | ||
| 62 | const class = math.log2(slot_size) - min_class; | ||
| 63 | if (class < size_class_count) { | ||
| 64 | const addr = a: { | ||
| 65 | const top_free_ptr = global.frees[class]; | ||
| 66 | if (top_free_ptr != 0) { | ||
| 67 | const node: *usize = @ptrFromInt(top_free_ptr + (slot_size - @sizeOf(usize))); | ||
| 68 | global.frees[class] = node.*; | ||
| 69 | break :a top_free_ptr; | ||
| 70 | } | ||
| 71 | |||
| 72 | const next_addr = global.next_addrs[class]; | ||
| 73 | if (next_addr % page_size_max == 0) { | ||
| 74 | const addr = allocBigPages(1); | ||
| 75 | if (addr == 0) return null; | ||
| 76 | //std.debug.print("allocated fresh slot_size={d} class={d} addr=0x{x}\n", .{ | ||
| 77 | // slot_size, class, addr, | ||
| 78 | //}); | ||
| 79 | global.next_addrs[class] = addr + slot_size; | ||
| 80 | break :a addr; | ||
| 81 | } else { | ||
| 82 | global.next_addrs[class] = next_addr + slot_size; | ||
| 83 | break :a next_addr; | ||
| 84 | } | ||
| 85 | }; | ||
| 86 | return @ptrFromInt(addr); | ||
| 87 | } | ||
| 88 | const bigpages_needed = bigPagesNeeded(actual_len); | ||
| 89 | return @ptrFromInt(allocBigPages(bigpages_needed)); | ||
| 90 | } | ||
| 91 | |||
| 92 | fn resize( | ||
| 93 | ctx: *anyopaque, | ||
| 94 | buf: []u8, | ||
| 95 | alignment: Alignment, | ||
| 96 | new_len: usize, | ||
| 97 | return_address: usize, | ||
| 98 | ) bool { | ||
| 99 | _ = ctx; | ||
| 100 | _ = return_address; | ||
| 101 | // We don't want to move anything from one size class to another, but we | ||
| 102 | // can recover bytes in between powers of two. | ||
| 103 | const buf_align = alignment.toByteUnits(); | ||
| 104 | const old_actual_len = @max(buf.len + @sizeOf(usize), buf_align); | ||
| 105 | const new_actual_len = @max(new_len +| @sizeOf(usize), buf_align); | ||
| 106 | const old_small_slot_size = math.ceilPowerOfTwoAssert(usize, old_actual_len); | ||
| 107 | const old_small_class = math.log2(old_small_slot_size) - min_class; | ||
| 108 | if (old_small_class < size_class_count) { | ||
| 109 | const new_small_slot_size = math.ceilPowerOfTwo(usize, new_actual_len) catch return false; | ||
| 110 | return old_small_slot_size == new_small_slot_size; | ||
| 111 | } else { | ||
| 112 | const old_bigpages_needed = bigPagesNeeded(old_actual_len); | ||
| 113 | const old_big_slot_pages = math.ceilPowerOfTwoAssert(usize, old_bigpages_needed); | ||
| 114 | const new_bigpages_needed = bigPagesNeeded(new_actual_len); | ||
| 115 | const new_big_slot_pages = math.ceilPowerOfTwo(usize, new_bigpages_needed) catch return false; | ||
| 116 | return old_big_slot_pages == new_big_slot_pages; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | fn remap( | ||
| 121 | context: *anyopaque, | ||
| 122 | memory: []u8, | ||
| 123 | alignment: Alignment, | ||
| 124 | new_len: usize, | ||
| 125 | return_address: usize, | ||
| 126 | ) ?[*]u8 { | ||
| 127 | return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null; | ||
| 128 | } | ||
| 129 | |||
| 130 | fn free( | ||
| 131 | ctx: *anyopaque, | ||
| 132 | buf: []u8, | ||
| 133 | alignment: Alignment, | ||
| 134 | return_address: usize, | ||
| 135 | ) void { | ||
| 136 | _ = ctx; | ||
| 137 | _ = return_address; | ||
| 138 | const buf_align = alignment.toByteUnits(); | ||
| 139 | const actual_len = @max(buf.len + @sizeOf(usize), buf_align); | ||
| 140 | const slot_size = math.ceilPowerOfTwoAssert(usize, actual_len); | ||
| 141 | const class = math.log2(slot_size) - min_class; | ||
| 142 | const addr = @intFromPtr(buf.ptr); | ||
| 143 | if (class < size_class_count) { | ||
| 144 | const node: *usize = @ptrFromInt(addr + (slot_size - @sizeOf(usize))); | ||
| 145 | node.* = global.frees[class]; | ||
| 146 | global.frees[class] = addr; | ||
| 147 | } else { | ||
| 148 | const bigpages_needed = bigPagesNeeded(actual_len); | ||
| 149 | const pow2_pages = math.ceilPowerOfTwoAssert(usize, bigpages_needed); | ||
| 150 | const big_slot_size_bytes = pow2_pages * bigpage_size; | ||
| 151 | const node: *usize = @ptrFromInt(addr + (big_slot_size_bytes - @sizeOf(usize))); | ||
| 152 | const big_class = math.log2(pow2_pages); | ||
| 153 | node.* = global.big_frees[big_class]; | ||
| 154 | global.big_frees[big_class] = addr; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | inline fn bigPagesNeeded(byte_count: usize) usize { | ||
| 159 | return (byte_count + (bigpage_size + (@sizeOf(usize) - 1))) / bigpage_size; | ||
| 160 | } | ||
| 161 | |||
| 162 | fn allocBigPages(n: usize) usize { | ||
| 163 | const pow2_pages = math.ceilPowerOfTwoAssert(usize, n); | ||
| 164 | const slot_size_bytes = pow2_pages * bigpage_size; | ||
| 165 | const class = math.log2(pow2_pages); | ||
| 166 | |||
| 167 | const top_free_ptr = global.big_frees[class]; | ||
| 168 | if (top_free_ptr != 0) { | ||
| 169 | const node: *usize = @ptrFromInt(top_free_ptr + (slot_size_bytes - @sizeOf(usize))); | ||
| 170 | global.big_frees[class] = node.*; | ||
| 171 | return top_free_ptr; | ||
| 172 | } | ||
| 173 | |||
| 174 | if (builtin.cpu.arch.isWasm()) { | ||
| 175 | const page_index = @wasmMemoryGrow(0, pow2_pages * pages_per_bigpage); | ||
| 176 | if (page_index == -1) return 0; | ||
| 177 | return @as(usize, @intCast(page_index)) * page_size_max; | ||
| 178 | } else if (builtin.os.tag == .linux) { | ||
| 179 | const start_brk = s: { | ||
| 180 | const start_brk = global.prev_brk; | ||
| 181 | break :s if (start_brk == 0) std.os.linux.brk(0) else start_brk; | ||
| 182 | }; | ||
| 183 | const end_brk = start_brk + pow2_pages * pages_per_bigpage * page_size_max; | ||
| 184 | const new_prev_brk = std.os.linux.brk(end_brk); | ||
| 185 | global.prev_brk = new_prev_brk; | ||
| 186 | if (new_prev_brk != end_brk) return 0; | ||
| 187 | return start_brk; | ||
| 188 | } else { | ||
| 189 | @compileError("no sbrk-like OS primitive available"); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | const test_ally: Allocator = .{ | ||
| 194 | .ptr = undefined, | ||
| 195 | .vtable = &vtable, | ||
| 196 | }; | ||
| 197 | |||
| 198 | test "small allocations - free in same order" { | ||
| 199 | var list: [513]*u64 = undefined; | ||
| 200 | |||
| 201 | var i: usize = 0; | ||
| 202 | while (i < 513) : (i += 1) { | ||
| 203 | const ptr = try test_ally.create(u64); | ||
| 204 | list[i] = ptr; | ||
| 205 | } | ||
| 206 | |||
| 207 | for (list) |ptr| { | ||
| 208 | test_ally.destroy(ptr); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | test "small allocations - free in reverse order" { | ||
| 213 | var list: [513]*u64 = undefined; | ||
| 214 | |||
| 215 | var i: usize = 0; | ||
| 216 | while (i < 513) : (i += 1) { | ||
| 217 | const ptr = try test_ally.create(u64); | ||
| 218 | list[i] = ptr; | ||
| 219 | } | ||
| 220 | |||
| 221 | i = list.len; | ||
| 222 | while (i > 0) { | ||
| 223 | i -= 1; | ||
| 224 | const ptr = list[i]; | ||
| 225 | test_ally.destroy(ptr); | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | test "large allocations" { | ||
| 230 | const ptr1 = try test_ally.alloc(u64, 42768); | ||
| 231 | const ptr2 = try test_ally.alloc(u64, 52768); | ||
| 232 | test_ally.free(ptr1); | ||
| 233 | const ptr3 = try test_ally.alloc(u64, 62768); | ||
| 234 | test_ally.free(ptr3); | ||
| 235 | test_ally.free(ptr2); | ||
| 236 | } | ||
| 237 | |||
| 238 | test "very large allocation" { | ||
| 239 | try std.testing.expectError(error.OutOfMemory, test_ally.alloc(u8, math.maxInt(usize))); | ||
| 240 | } | ||
| 241 | |||
| 242 | test "realloc" { | ||
| 243 | var slice = try test_ally.alignedAlloc(u8, .of(u32), 1); | ||
| 244 | defer test_ally.free(slice); | ||
| 245 | slice[0] = 0x12; | ||
| 246 | |||
| 247 | // This reallocation should keep its pointer address. | ||
| 248 | const old_slice = slice; | ||
| 249 | slice = try test_ally.realloc(slice, 2); | ||
| 250 | try std.testing.expect(old_slice.ptr == slice.ptr); | ||
| 251 | try std.testing.expect(slice[0] == 0x12); | ||
| 252 | slice[1] = 0x34; | ||
| 253 | |||
| 254 | // This requires upgrading to a larger size class | ||
| 255 | slice = try test_ally.realloc(slice, 17); | ||
| 256 | try std.testing.expect(slice[0] == 0x12); | ||
| 257 | try std.testing.expect(slice[1] == 0x34); | ||
| 258 | } | ||
| 259 | |||
| 260 | test "shrink" { | ||
| 261 | var slice = try test_ally.alloc(u8, 20); | ||
| 262 | defer test_ally.free(slice); | ||
| 263 | |||
| 264 | @memset(slice, 0x11); | ||
| 265 | |||
| 266 | try std.testing.expect(test_ally.resize(slice, 17)); | ||
| 267 | slice = slice[0..17]; | ||
| 268 | |||
| 269 | for (slice) |b| { | ||
| 270 | try std.testing.expect(b == 0x11); | ||
| 271 | } | ||
| 272 | |||
| 273 | try std.testing.expect(test_ally.resize(slice, 16)); | ||
| 274 | slice = slice[0..16]; | ||
| 275 | |||
| 276 | for (slice) |b| { | ||
| 277 | try std.testing.expect(b == 0x11); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | test "large object - grow" { | ||
| 282 | if (builtin.os.tag == .linux) return error.SkipZigTest; | ||
| 283 | |||
| 284 | var slice1 = try test_ally.alloc(u8, bigpage_size * 2 - 20); | ||
| 285 | defer test_ally.free(slice1); | ||
| 286 | |||
| 287 | const old = slice1; | ||
| 288 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2 - 10); | ||
| 289 | try std.testing.expectEqual(slice1.ptr, old.ptr); | ||
| 290 | |||
| 291 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2); | ||
| 292 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2 + 1); | ||
| 293 | } | ||
| 294 | |||
| 295 | test "realloc small object to large object" { | ||
| 296 | var slice = try test_ally.alloc(u8, 70); | ||
| 297 | defer test_ally.free(slice); | ||
| 298 | slice[0] = 0x12; | ||
| 299 | slice[60] = 0x34; | ||
| 300 | |||
| 301 | // This requires upgrading to a large object | ||
| 302 | const large_object_size = bigpage_size * 2 + 50; | ||
| 303 | slice = try test_ally.realloc(slice, large_object_size); | ||
| 304 | try std.testing.expect(slice[0] == 0x12); | ||
| 305 | try std.testing.expect(slice[60] == 0x34); | ||
| 306 | } | ||
| 307 | |||
| 308 | test "shrink large object to large object" { | ||
| 309 | var slice = try test_ally.alloc(u8, bigpage_size * 2 + 50); | ||
| 310 | defer test_ally.free(slice); | ||
| 311 | slice[0] = 0x12; | ||
| 312 | slice[60] = 0x34; | ||
| 313 | |||
| 314 | try std.testing.expect(test_ally.resize(slice, bigpage_size * 2 + 1)); | ||
| 315 | slice = slice[0 .. bigpage_size * 2 + 1]; | ||
| 316 | try std.testing.expect(slice[0] == 0x12); | ||
| 317 | try std.testing.expect(slice[60] == 0x34); | ||
| 318 | |||
| 319 | try std.testing.expect(test_ally.resize(slice, bigpage_size * 2 + 1)); | ||
| 320 | try std.testing.expect(slice[0] == 0x12); | ||
| 321 | try std.testing.expect(slice[60] == 0x34); | ||
| 322 | |||
| 323 | slice = try test_ally.realloc(slice, bigpage_size * 2); | ||
| 324 | try std.testing.expect(slice[0] == 0x12); | ||
| 325 | try std.testing.expect(slice[60] == 0x34); | ||
| 326 | } | ||
| 327 | |||
| 328 | test "realloc large object to small object" { | ||
| 329 | var slice = try test_ally.alloc(u8, bigpage_size * 2 + 50); | ||
| 330 | defer test_ally.free(slice); | ||
| 331 | slice[0] = 0x12; | ||
| 332 | slice[16] = 0x34; | ||
| 333 | |||
| 334 | slice = try test_ally.realloc(slice, 19); | ||
| 335 | try std.testing.expect(slice[0] == 0x12); | ||
| 336 | try std.testing.expect(slice[16] == 0x34); | ||
| 337 | } | ||
| 338 | |||
| 339 | test "objects of size 1024 and 2048" { | ||
| 340 | const slice = try test_ally.alloc(u8, 1025); | ||
| 341 | const slice2 = try test_ally.alloc(u8, 3000); | ||
| 342 | |||
| 343 | test_ally.free(slice); | ||
| 344 | test_ally.free(slice2); | ||
| 345 | } | ||
| 346 | |||
| 347 | test "standard allocator tests" { | ||
| 348 | try std.heap.testAllocator(test_ally); | ||
| 349 | try std.heap.testAllocatorAligned(test_ally); | ||
| 350 | } | ||
lib/std/heap/WasmAllocator.zig deleted-326| ... | @@ -1,326 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | const mem = std.mem; | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | const wasm = std.wasm; | ||
| 7 | const math = std.math; | ||
| 8 | |||
| 9 | comptime { | ||
| 10 | if (!builtin.target.cpu.arch.isWasm()) { | ||
| 11 | @compileError("only available for wasm32 arch"); | ||
| 12 | } | ||
| 13 | if (!builtin.single_threaded) { | ||
| 14 | @compileError("TODO implement support for multi-threaded wasm"); | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | pub const vtable: Allocator.VTable = .{ | ||
| 19 | .alloc = alloc, | ||
| 20 | .resize = resize, | ||
| 21 | .remap = remap, | ||
| 22 | .free = free, | ||
| 23 | }; | ||
| 24 | |||
| 25 | pub const Error = Allocator.Error; | ||
| 26 | |||
| 27 | const max_usize = math.maxInt(usize); | ||
| 28 | const ushift = math.Log2Int(usize); | ||
| 29 | const bigpage_size = 64 * 1024; | ||
| 30 | const pages_per_bigpage = bigpage_size / wasm.page_size; | ||
| 31 | const bigpage_count = max_usize / bigpage_size; | ||
| 32 | |||
| 33 | /// Because of storing free list pointers, the minimum size class is 3. | ||
| 34 | const min_class = math.log2(math.ceilPowerOfTwoAssert(usize, 1 + @sizeOf(usize))); | ||
| 35 | const size_class_count = math.log2(bigpage_size) - min_class; | ||
| 36 | /// 0 - 1 bigpage | ||
| 37 | /// 1 - 2 bigpages | ||
| 38 | /// 2 - 4 bigpages | ||
| 39 | /// etc. | ||
| 40 | const big_size_class_count = math.log2(bigpage_count); | ||
| 41 | |||
| 42 | var next_addrs: [size_class_count]usize = @splat(0); | ||
| 43 | /// For each size class, points to the freed pointer. | ||
| 44 | var frees: [size_class_count]usize = @splat(0); | ||
| 45 | /// For each big size class, points to the freed pointer. | ||
| 46 | var big_frees: [big_size_class_count]usize = @splat(0); | ||
| 47 | |||
| 48 | fn alloc(ctx: *anyopaque, len: usize, alignment: mem.Alignment, return_address: usize) ?[*]u8 { | ||
| 49 | _ = ctx; | ||
| 50 | _ = return_address; | ||
| 51 | // Make room for the freelist next pointer. | ||
| 52 | const actual_len = @max(len +| @sizeOf(usize), alignment.toByteUnits()); | ||
| 53 | const slot_size = math.ceilPowerOfTwo(usize, actual_len) catch return null; | ||
| 54 | const class = math.log2(slot_size) - min_class; | ||
| 55 | if (class < size_class_count) { | ||
| 56 | const addr = a: { | ||
| 57 | const top_free_ptr = frees[class]; | ||
| 58 | if (top_free_ptr != 0) { | ||
| 59 | const node: *usize = @ptrFromInt(top_free_ptr + (slot_size - @sizeOf(usize))); | ||
| 60 | frees[class] = node.*; | ||
| 61 | break :a top_free_ptr; | ||
| 62 | } | ||
| 63 | |||
| 64 | const next_addr = next_addrs[class]; | ||
| 65 | if (next_addr % wasm.page_size == 0) { | ||
| 66 | const addr = allocBigPages(1); | ||
| 67 | if (addr == 0) return null; | ||
| 68 | //std.debug.print("allocated fresh slot_size={d} class={d} addr=0x{x}\n", .{ | ||
| 69 | // slot_size, class, addr, | ||
| 70 | //}); | ||
| 71 | next_addrs[class] = addr + slot_size; | ||
| 72 | break :a addr; | ||
| 73 | } else { | ||
| 74 | next_addrs[class] = next_addr + slot_size; | ||
| 75 | break :a next_addr; | ||
| 76 | } | ||
| 77 | }; | ||
| 78 | return @ptrFromInt(addr); | ||
| 79 | } | ||
| 80 | const bigpages_needed = bigPagesNeeded(actual_len); | ||
| 81 | return @ptrFromInt(allocBigPages(bigpages_needed)); | ||
| 82 | } | ||
| 83 | |||
| 84 | fn resize( | ||
| 85 | ctx: *anyopaque, | ||
| 86 | buf: []u8, | ||
| 87 | alignment: mem.Alignment, | ||
| 88 | new_len: usize, | ||
| 89 | return_address: usize, | ||
| 90 | ) bool { | ||
| 91 | _ = ctx; | ||
| 92 | _ = return_address; | ||
| 93 | // We don't want to move anything from one size class to another, but we | ||
| 94 | // can recover bytes in between powers of two. | ||
| 95 | const buf_align = alignment.toByteUnits(); | ||
| 96 | const old_actual_len = @max(buf.len + @sizeOf(usize), buf_align); | ||
| 97 | const new_actual_len = @max(new_len +| @sizeOf(usize), buf_align); | ||
| 98 | const old_small_slot_size = math.ceilPowerOfTwoAssert(usize, old_actual_len); | ||
| 99 | const old_small_class = math.log2(old_small_slot_size) - min_class; | ||
| 100 | if (old_small_class < size_class_count) { | ||
| 101 | const new_small_slot_size = math.ceilPowerOfTwo(usize, new_actual_len) catch return false; | ||
| 102 | return old_small_slot_size == new_small_slot_size; | ||
| 103 | } else { | ||
| 104 | const old_bigpages_needed = bigPagesNeeded(old_actual_len); | ||
| 105 | const old_big_slot_pages = math.ceilPowerOfTwoAssert(usize, old_bigpages_needed); | ||
| 106 | const new_bigpages_needed = bigPagesNeeded(new_actual_len); | ||
| 107 | const new_big_slot_pages = math.ceilPowerOfTwo(usize, new_bigpages_needed) catch return false; | ||
| 108 | return old_big_slot_pages == new_big_slot_pages; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | fn remap( | ||
| 113 | context: *anyopaque, | ||
| 114 | memory: []u8, | ||
| 115 | alignment: mem.Alignment, | ||
| 116 | new_len: usize, | ||
| 117 | return_address: usize, | ||
| 118 | ) ?[*]u8 { | ||
| 119 | return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null; | ||
| 120 | } | ||
| 121 | |||
| 122 | fn free( | ||
| 123 | ctx: *anyopaque, | ||
| 124 | buf: []u8, | ||
| 125 | alignment: mem.Alignment, | ||
| 126 | return_address: usize, | ||
| 127 | ) void { | ||
| 128 | _ = ctx; | ||
| 129 | _ = return_address; | ||
| 130 | const buf_align = alignment.toByteUnits(); | ||
| 131 | const actual_len = @max(buf.len + @sizeOf(usize), buf_align); | ||
| 132 | const slot_size = math.ceilPowerOfTwoAssert(usize, actual_len); | ||
| 133 | const class = math.log2(slot_size) - min_class; | ||
| 134 | const addr = @intFromPtr(buf.ptr); | ||
| 135 | if (class < size_class_count) { | ||
| 136 | const node: *usize = @ptrFromInt(addr + (slot_size - @sizeOf(usize))); | ||
| 137 | node.* = frees[class]; | ||
| 138 | frees[class] = addr; | ||
| 139 | } else { | ||
| 140 | const bigpages_needed = bigPagesNeeded(actual_len); | ||
| 141 | const pow2_pages = math.ceilPowerOfTwoAssert(usize, bigpages_needed); | ||
| 142 | const big_slot_size_bytes = pow2_pages * bigpage_size; | ||
| 143 | const node: *usize = @ptrFromInt(addr + (big_slot_size_bytes - @sizeOf(usize))); | ||
| 144 | const big_class = math.log2(pow2_pages); | ||
| 145 | node.* = big_frees[big_class]; | ||
| 146 | big_frees[big_class] = addr; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | inline fn bigPagesNeeded(byte_count: usize) usize { | ||
| 151 | return (byte_count + (bigpage_size + (@sizeOf(usize) - 1))) / bigpage_size; | ||
| 152 | } | ||
| 153 | |||
| 154 | fn allocBigPages(n: usize) usize { | ||
| 155 | const pow2_pages = math.ceilPowerOfTwoAssert(usize, n); | ||
| 156 | const slot_size_bytes = pow2_pages * bigpage_size; | ||
| 157 | const class = math.log2(pow2_pages); | ||
| 158 | |||
| 159 | const top_free_ptr = big_frees[class]; | ||
| 160 | if (top_free_ptr != 0) { | ||
| 161 | const node: *usize = @ptrFromInt(top_free_ptr + (slot_size_bytes - @sizeOf(usize))); | ||
| 162 | big_frees[class] = node.*; | ||
| 163 | return top_free_ptr; | ||
| 164 | } | ||
| 165 | |||
| 166 | const page_index = @wasmMemoryGrow(0, pow2_pages * pages_per_bigpage); | ||
| 167 | if (page_index == -1) return 0; | ||
| 168 | return @as(usize, @intCast(page_index)) * wasm.page_size; | ||
| 169 | } | ||
| 170 | |||
| 171 | const test_ally: Allocator = .{ | ||
| 172 | .ptr = undefined, | ||
| 173 | .vtable = &vtable, | ||
| 174 | }; | ||
| 175 | |||
| 176 | test "small allocations - free in same order" { | ||
| 177 | var list: [513]*u64 = undefined; | ||
| 178 | |||
| 179 | var i: usize = 0; | ||
| 180 | while (i < 513) : (i += 1) { | ||
| 181 | const ptr = try test_ally.create(u64); | ||
| 182 | list[i] = ptr; | ||
| 183 | } | ||
| 184 | |||
| 185 | for (list) |ptr| { | ||
| 186 | test_ally.destroy(ptr); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | test "small allocations - free in reverse order" { | ||
| 191 | var list: [513]*u64 = undefined; | ||
| 192 | |||
| 193 | var i: usize = 0; | ||
| 194 | while (i < 513) : (i += 1) { | ||
| 195 | const ptr = try test_ally.create(u64); | ||
| 196 | list[i] = ptr; | ||
| 197 | } | ||
| 198 | |||
| 199 | i = list.len; | ||
| 200 | while (i > 0) { | ||
| 201 | i -= 1; | ||
| 202 | const ptr = list[i]; | ||
| 203 | test_ally.destroy(ptr); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | test "large allocations" { | ||
| 208 | const ptr1 = try test_ally.alloc(u64, 42768); | ||
| 209 | const ptr2 = try test_ally.alloc(u64, 52768); | ||
| 210 | test_ally.free(ptr1); | ||
| 211 | const ptr3 = try test_ally.alloc(u64, 62768); | ||
| 212 | test_ally.free(ptr3); | ||
| 213 | test_ally.free(ptr2); | ||
| 214 | } | ||
| 215 | |||
| 216 | test "very large allocation" { | ||
| 217 | try std.testing.expectError(error.OutOfMemory, test_ally.alloc(u8, math.maxInt(usize))); | ||
| 218 | } | ||
| 219 | |||
| 220 | test "realloc" { | ||
| 221 | var slice = try test_ally.alignedAlloc(u8, .of(u32), 1); | ||
| 222 | defer test_ally.free(slice); | ||
| 223 | slice[0] = 0x12; | ||
| 224 | |||
| 225 | // This reallocation should keep its pointer address. | ||
| 226 | const old_slice = slice; | ||
| 227 | slice = try test_ally.realloc(slice, 2); | ||
| 228 | try std.testing.expect(old_slice.ptr == slice.ptr); | ||
| 229 | try std.testing.expect(slice[0] == 0x12); | ||
| 230 | slice[1] = 0x34; | ||
| 231 | |||
| 232 | // This requires upgrading to a larger size class | ||
| 233 | slice = try test_ally.realloc(slice, 17); | ||
| 234 | try std.testing.expect(slice[0] == 0x12); | ||
| 235 | try std.testing.expect(slice[1] == 0x34); | ||
| 236 | } | ||
| 237 | |||
| 238 | test "shrink" { | ||
| 239 | var slice = try test_ally.alloc(u8, 20); | ||
| 240 | defer test_ally.free(slice); | ||
| 241 | |||
| 242 | @memset(slice, 0x11); | ||
| 243 | |||
| 244 | try std.testing.expect(test_ally.resize(slice, 17)); | ||
| 245 | slice = slice[0..17]; | ||
| 246 | |||
| 247 | for (slice) |b| { | ||
| 248 | try std.testing.expect(b == 0x11); | ||
| 249 | } | ||
| 250 | |||
| 251 | try std.testing.expect(test_ally.resize(slice, 16)); | ||
| 252 | slice = slice[0..16]; | ||
| 253 | |||
| 254 | for (slice) |b| { | ||
| 255 | try std.testing.expect(b == 0x11); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | test "large object - grow" { | ||
| 260 | var slice1 = try test_ally.alloc(u8, bigpage_size * 2 - 20); | ||
| 261 | defer test_ally.free(slice1); | ||
| 262 | |||
| 263 | const old = slice1; | ||
| 264 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2 - 10); | ||
| 265 | try std.testing.expect(slice1.ptr == old.ptr); | ||
| 266 | |||
| 267 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2); | ||
| 268 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2 + 1); | ||
| 269 | } | ||
| 270 | |||
| 271 | test "realloc small object to large object" { | ||
| 272 | var slice = try test_ally.alloc(u8, 70); | ||
| 273 | defer test_ally.free(slice); | ||
| 274 | slice[0] = 0x12; | ||
| 275 | slice[60] = 0x34; | ||
| 276 | |||
| 277 | // This requires upgrading to a large object | ||
| 278 | const large_object_size = bigpage_size * 2 + 50; | ||
| 279 | slice = try test_ally.realloc(slice, large_object_size); | ||
| 280 | try std.testing.expect(slice[0] == 0x12); | ||
| 281 | try std.testing.expect(slice[60] == 0x34); | ||
| 282 | } | ||
| 283 | |||
| 284 | test "shrink large object to large object" { | ||
| 285 | var slice = try test_ally.alloc(u8, bigpage_size * 2 + 50); | ||
| 286 | defer test_ally.free(slice); | ||
| 287 | slice[0] = 0x12; | ||
| 288 | slice[60] = 0x34; | ||
| 289 | |||
| 290 | try std.testing.expect(test_ally.resize(slice, bigpage_size * 2 + 1)); | ||
| 291 | slice = slice[0 .. bigpage_size * 2 + 1]; | ||
| 292 | try std.testing.expect(slice[0] == 0x12); | ||
| 293 | try std.testing.expect(slice[60] == 0x34); | ||
| 294 | |||
| 295 | try std.testing.expect(test_ally.resize(slice, bigpage_size * 2 + 1)); | ||
| 296 | try std.testing.expect(slice[0] == 0x12); | ||
| 297 | try std.testing.expect(slice[60] == 0x34); | ||
| 298 | |||
| 299 | slice = try test_ally.realloc(slice, bigpage_size * 2); | ||
| 300 | try std.testing.expect(slice[0] == 0x12); | ||
| 301 | try std.testing.expect(slice[60] == 0x34); | ||
| 302 | } | ||
| 303 | |||
| 304 | test "realloc large object to small object" { | ||
| 305 | var slice = try test_ally.alloc(u8, bigpage_size * 2 + 50); | ||
| 306 | defer test_ally.free(slice); | ||
| 307 | slice[0] = 0x12; | ||
| 308 | slice[16] = 0x34; | ||
| 309 | |||
| 310 | slice = try test_ally.realloc(slice, 19); | ||
| 311 | try std.testing.expect(slice[0] == 0x12); | ||
| 312 | try std.testing.expect(slice[16] == 0x34); | ||
| 313 | } | ||
| 314 | |||
| 315 | test "objects of size 1024 and 2048" { | ||
| 316 | const slice = try test_ally.alloc(u8, 1025); | ||
| 317 | const slice2 = try test_ally.alloc(u8, 3000); | ||
| 318 | |||
| 319 | test_ally.free(slice); | ||
| 320 | test_ally.free(slice2); | ||
| 321 | } | ||
| 322 | |||
| 323 | test "standard allocator tests" { | ||
| 324 | try std.heap.testAllocator(test_ally); | ||
| 325 | try std.heap.testAllocatorAligned(test_ally); | ||
| 326 | } | ||
lib/std/heap/sbrk_allocator.zig deleted-180| ... | @@ -1,180 +0,0 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | |||
| 3 | const std = @import("../std.zig"); | ||
| 4 | const Io = std.Io; | ||
| 5 | const math = std.math; | ||
| 6 | const Allocator = std.mem.Allocator; | ||
| 7 | const mem = std.mem; | ||
| 8 | const heap = std.heap; | ||
| 9 | const assert = std.debug.assert; | ||
| 10 | |||
| 11 | pub fn SbrkAllocator(comptime sbrk: *const fn (n: usize) usize) type { | ||
| 12 | return struct { | ||
| 13 | pub const vtable: Allocator.VTable = .{ | ||
| 14 | .alloc = alloc, | ||
| 15 | .resize = resize, | ||
| 16 | .remap = remap, | ||
| 17 | .free = free, | ||
| 18 | }; | ||
| 19 | |||
| 20 | pub const Error = Allocator.Error; | ||
| 21 | |||
| 22 | const max_usize = math.maxInt(usize); | ||
| 23 | const ushift = math.Log2Int(usize); | ||
| 24 | const bigpage_size = 64 * 1024; | ||
| 25 | const pages_per_bigpage = bigpage_size / heap.pageSize(); | ||
| 26 | const bigpage_count = max_usize / bigpage_size; | ||
| 27 | |||
| 28 | /// Because of storing free list pointers, the minimum size class is 3. | ||
| 29 | const min_class = math.log2(math.ceilPowerOfTwoAssert(usize, 1 + @sizeOf(usize))); | ||
| 30 | const size_class_count = math.log2(bigpage_size) - min_class; | ||
| 31 | /// 0 - 1 bigpage | ||
| 32 | /// 1 - 2 bigpages | ||
| 33 | /// 2 - 4 bigpages | ||
| 34 | /// etc. | ||
| 35 | const big_size_class_count = math.log2(bigpage_count); | ||
| 36 | |||
| 37 | var next_addrs = [1]usize{0} ** size_class_count; | ||
| 38 | /// For each size class, points to the freed pointer. | ||
| 39 | var frees = [1]usize{0} ** size_class_count; | ||
| 40 | /// For each big size class, points to the freed pointer. | ||
| 41 | var big_frees = [1]usize{0} ** big_size_class_count; | ||
| 42 | |||
| 43 | // TODO don't do the naive locking strategy | ||
| 44 | var mutex: Io.Mutex = .{}; | ||
| 45 | fn alloc(ctx: *anyopaque, len: usize, alignment: mem.Alignment, return_address: usize) ?[*]u8 { | ||
| 46 | _ = ctx; | ||
| 47 | _ = return_address; | ||
| 48 | Io.Threaded.mutexLock(&mutex); | ||
| 49 | defer Io.Threaded.mutexUnlock(&mutex); | ||
| 50 | // Make room for the freelist next pointer. | ||
| 51 | const actual_len = @max(len +| @sizeOf(usize), alignment.toByteUnits()); | ||
| 52 | const slot_size = math.ceilPowerOfTwo(usize, actual_len) catch return null; | ||
| 53 | const class = math.log2(slot_size) - min_class; | ||
| 54 | if (class < size_class_count) { | ||
| 55 | const addr = a: { | ||
| 56 | const top_free_ptr = frees[class]; | ||
| 57 | if (top_free_ptr != 0) { | ||
| 58 | const node = @as(*usize, @ptrFromInt(top_free_ptr + (slot_size - @sizeOf(usize)))); | ||
| 59 | frees[class] = node.*; | ||
| 60 | break :a top_free_ptr; | ||
| 61 | } | ||
| 62 | |||
| 63 | const next_addr = next_addrs[class]; | ||
| 64 | if (next_addr % heap.pageSize() == 0) { | ||
| 65 | const addr = allocBigPages(1); | ||
| 66 | if (addr == 0) return null; | ||
| 67 | //std.debug.print("allocated fresh slot_size={d} class={d} addr=0x{x}\n", .{ | ||
| 68 | // slot_size, class, addr, | ||
| 69 | //}); | ||
| 70 | next_addrs[class] = addr + slot_size; | ||
| 71 | break :a addr; | ||
| 72 | } else { | ||
| 73 | next_addrs[class] = next_addr + slot_size; | ||
| 74 | break :a next_addr; | ||
| 75 | } | ||
| 76 | }; | ||
| 77 | return @as([*]u8, @ptrFromInt(addr)); | ||
| 78 | } | ||
| 79 | const bigpages_needed = bigPagesNeeded(actual_len); | ||
| 80 | const addr = allocBigPages(bigpages_needed); | ||
| 81 | return @as([*]u8, @ptrFromInt(addr)); | ||
| 82 | } | ||
| 83 | |||
| 84 | fn resize( | ||
| 85 | ctx: *anyopaque, | ||
| 86 | buf: []u8, | ||
| 87 | alignment: mem.Alignment, | ||
| 88 | new_len: usize, | ||
| 89 | return_address: usize, | ||
| 90 | ) bool { | ||
| 91 | _ = ctx; | ||
| 92 | _ = return_address; | ||
| 93 | Io.Threaded.mutexLock(&mutex); | ||
| 94 | defer Io.Threaded.mutexUnlock(&mutex); | ||
| 95 | // We don't want to move anything from one size class to another, but we | ||
| 96 | // can recover bytes in between powers of two. | ||
| 97 | const buf_align = alignment.toByteUnits(); | ||
| 98 | const old_actual_len = @max(buf.len + @sizeOf(usize), buf_align); | ||
| 99 | const new_actual_len = @max(new_len +| @sizeOf(usize), buf_align); | ||
| 100 | const old_small_slot_size = math.ceilPowerOfTwoAssert(usize, old_actual_len); | ||
| 101 | const old_small_class = math.log2(old_small_slot_size) - min_class; | ||
| 102 | if (old_small_class < size_class_count) { | ||
| 103 | const new_small_slot_size = math.ceilPowerOfTwo(usize, new_actual_len) catch return false; | ||
| 104 | return old_small_slot_size == new_small_slot_size; | ||
| 105 | } else { | ||
| 106 | const old_bigpages_needed = bigPagesNeeded(old_actual_len); | ||
| 107 | const old_big_slot_pages = math.ceilPowerOfTwoAssert(usize, old_bigpages_needed); | ||
| 108 | const new_bigpages_needed = bigPagesNeeded(new_actual_len); | ||
| 109 | const new_big_slot_pages = math.ceilPowerOfTwo(usize, new_bigpages_needed) catch return false; | ||
| 110 | return old_big_slot_pages == new_big_slot_pages; | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | fn remap( | ||
| 115 | context: *anyopaque, | ||
| 116 | memory: []u8, | ||
| 117 | alignment: mem.Alignment, | ||
| 118 | new_len: usize, | ||
| 119 | return_address: usize, | ||
| 120 | ) ?[*]u8 { | ||
| 121 | return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null; | ||
| 122 | } | ||
| 123 | |||
| 124 | fn free( | ||
| 125 | ctx: *anyopaque, | ||
| 126 | buf: []u8, | ||
| 127 | alignment: mem.Alignment, | ||
| 128 | return_address: usize, | ||
| 129 | ) void { | ||
| 130 | _ = ctx; | ||
| 131 | _ = return_address; | ||
| 132 | Io.Threaded.mutexLock(&mutex); | ||
| 133 | defer Io.Threaded.mutexUnlock(&mutex); | ||
| 134 | const buf_align = alignment.toByteUnits(); | ||
| 135 | const actual_len = @max(buf.len + @sizeOf(usize), buf_align); | ||
| 136 | const slot_size = math.ceilPowerOfTwoAssert(usize, actual_len); | ||
| 137 | const class = math.log2(slot_size) - min_class; | ||
| 138 | const addr = @intFromPtr(buf.ptr); | ||
| 139 | if (class < size_class_count) { | ||
| 140 | const node = @as(*usize, @ptrFromInt(addr + (slot_size - @sizeOf(usize)))); | ||
| 141 | node.* = frees[class]; | ||
| 142 | frees[class] = addr; | ||
| 143 | } else { | ||
| 144 | const bigpages_needed = bigPagesNeeded(actual_len); | ||
| 145 | const pow2_pages = math.ceilPowerOfTwoAssert(usize, bigpages_needed); | ||
| 146 | const big_slot_size_bytes = pow2_pages * bigpage_size; | ||
| 147 | const node = @as(*usize, @ptrFromInt(addr + (big_slot_size_bytes - @sizeOf(usize)))); | ||
| 148 | const big_class = math.log2(pow2_pages); | ||
| 149 | node.* = big_frees[big_class]; | ||
| 150 | big_frees[big_class] = addr; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | inline fn bigPagesNeeded(byte_count: usize) usize { | ||
| 155 | return (byte_count + (bigpage_size + (@sizeOf(usize) - 1))) / bigpage_size; | ||
| 156 | } | ||
| 157 | |||
| 158 | fn allocBigPages(n: usize) usize { | ||
| 159 | const pow2_pages = math.ceilPowerOfTwoAssert(usize, n); | ||
| 160 | const slot_size_bytes = pow2_pages * bigpage_size; | ||
| 161 | const class = math.log2(pow2_pages); | ||
| 162 | |||
| 163 | const top_free_ptr = big_frees[class]; | ||
| 164 | if (top_free_ptr != 0) { | ||
| 165 | const node = @as(*usize, @ptrFromInt(top_free_ptr + (slot_size_bytes - @sizeOf(usize)))); | ||
| 166 | big_frees[class] = node.*; | ||
| 167 | return top_free_ptr; | ||
| 168 | } | ||
| 169 | return sbrk(pow2_pages * pages_per_bigpage * heap.pageSize()); | ||
| 170 | } | ||
| 171 | }; | ||
| 172 | } | ||
| 173 | |||
| 174 | test SbrkAllocator { | ||
| 175 | _ = SbrkAllocator(struct { | ||
| 176 | fn sbrk(_: usize) usize { | ||
| 177 | return 0; | ||
| 178 | } | ||
| 179 | }.sbrk); | ||
| 180 | } | ||
lib/std/os/linux.zig+4| ... | @@ -594,6 +594,10 @@ pub fn errno(r: usize) E { | ... | @@ -594,6 +594,10 @@ pub fn errno(r: usize) E { |
| 594 | return @enumFromInt(int); | 594 | return @enumFromInt(int); |
| 595 | } | 595 | } |
| 596 | 596 | ||
| 597 | pub fn brk(addr: usize) usize { | ||
| 598 | return syscall1(.brk, addr); | ||
| 599 | } | ||
| 600 | |||
| 597 | pub fn dup(old: i32) usize { | 601 | pub fn dup(old: i32) usize { |
| 598 | return syscall1(.dup, @as(usize, @bitCast(@as(isize, old)))); | 602 | return syscall1(.dup, @as(usize, @bitCast(@as(isize, old)))); |
| 599 | } | 603 | } |