| 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 | //! Based on wrapping a stateless Zig Allocator implementation, apropriate for: | |
| 1 | //! Based on wrapping a stateless Zig Allocator implementation, appropriate for: | |
| 2 | 2 | //! - ReleaseFast and ReleaseSmall optimization modes, with multi-threading |
| 3 | 3 | //! enabled. |
| 4 | //! - WebAssembly in single-threaded mode. | |
| 4 | //! - WebAssembly or Linux in single-threaded release modes. | |
| 5 | 5 | //! |
| 6 | 6 | //! Because the libc APIs don't have client alignment and size tracking, in |
| 7 | 7 | //! order to take advantage of Zig allocator implementations, additional |
| ... | ... | @@ -40,7 +40,7 @@ const no_context: *anyopaque = undefined; |
| 40 | 40 | const no_ra: usize = undefined; |
| 41 | 41 | const vtable = switch (builtin.cpu.arch) { |
| 42 | 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 | 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 | 13 | pub const SmpAllocator = @import("heap/SmpAllocator.zig"); |
| 14 | 14 | pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig"); |
| 15 | 15 | pub const PageAllocator = @import("heap/PageAllocator.zig"); |
| 16 | pub const SbrkAllocator = @import("heap/sbrk_allocator.zig").SbrkAllocator; | |
| 17 | 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 | 20 | pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config; |
| 21 | 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 | 356 | else if (builtin.target.cpu.arch.isWasm()) .{ |
| 357 | 357 | .ptr = undefined, |
| 358 | 358 | .vtable = &WasmAllocator.vtable, |
| 359 | } else if (builtin.target.os.tag == .plan9) .{ | |
| 360 | .ptr = undefined, | |
| 361 | .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable, | |
| 362 | 359 | } else .{ |
| 363 | 360 | .ptr = undefined, |
| 364 | 361 | .vtable = &PageAllocator.vtable, |
| ... | ... | @@ -369,16 +366,18 @@ pub const smp_allocator: Allocator = .{ |
| 369 | 366 | .vtable = &SmpAllocator.vtable, |
| 370 | 367 | }; |
| 371 | 368 | |
| 372 | /// This allocator is fast, small, and specific to WebAssembly. In the future, | |
| 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. | |
| 369 | /// This allocator is fast, small, and specific to WebAssembly. | |
| 377 | 370 | pub const wasm_allocator: Allocator = .{ |
| 378 | 371 | .ptr = undefined, |
| 379 | 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 | 381 | /// Returns a `StackFallbackAllocator` allocating using either a |
| 383 | 382 | /// `FixedBufferAllocator` on an array of size `size` and falling back to |
| 384 | 383 | /// `fallback_allocator` if that fails. |
| ... | ... | @@ -1014,9 +1013,11 @@ test { |
| 1014 | 1013 | _ = GeneralPurposeAllocator; |
| 1015 | 1014 | _ = FixedBufferAllocator; |
| 1016 | 1015 | _ = ThreadSafeAllocator; |
| 1017 | _ = SbrkAllocator; | |
| 1018 | if (builtin.target.cpu.arch.isWasm()) { | |
| 1019 | _ = WasmAllocator; | |
| 1016 | if (builtin.single_threaded) { | |
| 1017 | if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) { | |
| 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 | 594 | return @enumFromInt(int); |
| 595 | 595 | } |
| 596 | 596 | |
| 597 | pub fn brk(addr: usize) usize { | |
| 598 | return syscall1(.brk, addr); | |
| 599 | } | |
| 600 | ||
| 597 | 601 | pub fn dup(old: i32) usize { |
| 598 | 602 | return syscall1(.dup, @as(usize, @bitCast(@as(isize, old)))); |
| 599 | 603 | } |