1const std = @import("std.zig");
2const builtin = @import("builtin");
3const root = @import("root");
4const assert = std.debug.assert;
5const testing = std.testing;
6const mem = std.mem;
7const c = std.c;
8const Allocator = std.mem.Allocator;
9const windows = std.os.windows;
10const Alignment = std.mem.Alignment;
11
12pub const ArenaAllocator = @import("heap/ArenaAllocator.zig");
13pub const SmpAllocator = @import("heap/SmpAllocator.zig");
14pub const SafeAllocator = @import("heap/SafeAllocator.zig");
15pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
16pub const BufferFirstAllocator = @import("heap/BufferFirstAllocator.zig");
17pub const PageAllocator = @import("heap/PageAllocator.zig");
18pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");
19pub const BrkAllocator = @import("heap/BrkAllocator.zig");
20
21/// Deprecated; use `SafeAllocator.Options`.
22pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config;
23/// Deprecated; use `SafeAllocator`.
24pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator;
25/// Deprecated.
26pub const Check = enum { ok, leak };
27
28/// A memory pool that can allocate objects of a single type very quickly.
29/// Use this when you need to allocate a lot of objects of the same type,
30/// because it outperforms general purpose allocators.
31/// Functions that potentially allocate memory accept an `Allocator` parameter.
32pub fn MemoryPool(comptime Item: type) type {
33 return memory_pool.Extra(Item, .{ .alignment = null });
34}
35pub const memory_pool = @import("heap/memory_pool.zig");
36
37/// comptime-known minimum page size of the target.
38///
39/// All pointers from `mmap` or `NtAllocateVirtualMemory` are aligned to at least
40/// `page_size_min`, but their actual alignment may be bigger.
41///
42/// This value can be overridden via `std.options.page_size_min`.
43///
44/// On many systems, the actual page size can only be determined at runtime
45/// with `pageSize`.
46pub const page_size_min: usize = std.options.page_size_min orelse (page_size_min_default orelse @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min"));
47/// comptime-known maximum page size of the target.
48///
49/// Targeting a system with a larger page size may require overriding
50/// `std.options.page_size_max`, as well as providing a corresponding linker
51/// option.
52///
53/// The actual page size can only be determined at runtime with `pageSize`.
54pub const page_size_max: usize = std.options.page_size_max orelse (page_size_max_default orelse if (builtin.os.tag == .freestanding or builtin.os.tag == .other)
55 @compileError("freestanding/other page_size_max must provided with std.options.page_size_max")
56else
57 @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_max; populate std.options.page_size_max"));
58
59/// If the page size is comptime-known, return value is comptime.
60/// Otherwise, calls `std.options.queryPageSize` which by default queries the
61/// host operating system at runtime.
62pub inline fn pageSize() usize {
63 if (page_size_min == page_size_max) return page_size_min;
64 return std.options.queryPageSize();
65}
66
67test pageSize {
68 assert(std.math.isPowerOfTwo(pageSize()));
69}
70
71/// The default implementation of `std.options.queryPageSize`.
72/// Asserts that the page size is within `page_size_min` and `page_size_max`
73pub fn defaultQueryPageSize() usize {
74 const global = struct {
75 var cached_result: std.atomic.Value(usize) = .init(0);
76 };
77 var size = global.cached_result.load(.unordered);
78 if (size > 0) return size;
79 size = size: switch (builtin.os.tag) {
80 .linux => if (builtin.link_libc)
81 @max(std.c.sysconf(@backingInt(std.c._SC.PAGESIZE)), 0)
82 else
83 std.os.linux.getauxval(std.elf.AT_PAGESZ),
84 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
85 const task_port = std.c.mach_task_self();
86 // mach_task_self may fail "if there are any resource failures or other errors".
87 if (task_port == std.c.TASK.NULL) break :size 0;
88 var info_count = std.c.TASK.VM.INFO_COUNT;
89 var vm_info: std.c.task_vm_info_data_t = undefined;
90 vm_info.page_size = 0;
91 _ = std.c.task_info(
92 task_port,
93 std.c.TASK.VM.INFO,
94 @as(std.c.task_info_t, @ptrCast(&vm_info)),
95 &info_count,
96 );
97 break :size @intCast(vm_info.page_size);
98 },
99 .windows => {
100 var sbi: windows.SYSTEM.BASIC_INFORMATION = undefined;
101 switch (windows.ntdll.NtQuerySystemInformation(
102 .Basic,
103 &sbi,
104 @sizeOf(windows.SYSTEM.BASIC_INFORMATION),
105 null,
106 )) {
107 .SUCCESS => break :size sbi.PageSize,
108 else => break :size 0,
109 }
110 },
111 else => if (builtin.link_libc)
112 @max(std.c.sysconf(@backingInt(std.c._SC.PAGESIZE)), 0)
113 else if (builtin.os.tag == .freestanding or builtin.os.tag == .other)
114 @compileError("unsupported target: freestanding/other")
115 else
116 @compileError("pageSize on " ++ @tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " is not supported without linking libc, using the default implementation"),
117 };
118 if (size == 0) size = page_size_max;
119
120 assert(size >= page_size_min);
121 assert(size <= page_size_max);
122 global.cached_result.store(size, .unordered);
123
124 return size;
125}
126
127test defaultQueryPageSize {
128 if (builtin.cpu.arch.isWasm()) return error.SkipZigTest;
129 assert(std.math.isPowerOfTwo(defaultQueryPageSize()));
130}
131
132/// A wrapper around the C memory allocation API which supports the full `Allocator`
133/// interface, including arbitrary alignment. Simple `malloc` calls are used when
134/// possible, but large requested alignments may require larger buffers in order to
135/// satisfy the request. As well as `malloc`, `realloc`, and `free`, the extension
136/// functions `malloc_usable_size` and `posix_memalign` are used when available.
137pub const c_allocator: Allocator = .{
138 .ptr = undefined,
139 .vtable = &c_allocator_impl.vtable,
140};
141const c_allocator_impl = struct {
142 comptime {
143 if (!builtin.link_libc) {
144 @compileError("C allocator is only available when linking against libc");
145 }
146 }
147
148 const vtable: Allocator.VTable = .{
149 .alloc = alloc,
150 .resize = resize,
151 .remap = remap,
152 .free = free,
153 };
154
155 const have_posix_memalign = switch (builtin.os.tag) {
156 .dragonfly,
157 .netbsd,
158 .freebsd,
159 .illumos,
160 .openbsd,
161 .linux,
162 .driverkit,
163 .ios,
164 .maccatalyst,
165 .macos,
166 .tvos,
167 .visionos,
168 .watchos,
169 .serenity,
170 => true,
171 else => false,
172 };
173
174 fn allocStrat(need_align: Alignment) union(enum) {
175 raw,
176 posix_memalign: if (have_posix_memalign) void else noreturn,
177 manual_align: if (have_posix_memalign) noreturn else void,
178 } {
179 // If `malloc` guarantees `need_align`, always prefer a raw allocation.
180 if (Alignment.compare(need_align, .lte, .of(c.max_align_t))) {
181 return .raw;
182 }
183 // Use `posix_memalign` if available. Otherwise, we must manually align the allocation.
184 return if (have_posix_memalign) .posix_memalign else .manual_align;
185 }
186
187 /// If `allocStrat(a) == .manual_align`, an allocation looks like this:
188 ///
189 /// unaligned_ptr hdr_ptr aligned_ptr
190 /// v v v
191 /// +---------------+--------+--------------+
192 /// | padding | header | usable bytes |
193 /// +---------------+--------+--------------+
194 ///
195 /// * `unaligned_ptr` is the raw return value of `malloc`.
196 /// * `aligned_ptr` is computed by aligning `unaligned_ptr` forward; it is what `alloc` returns.
197 /// * `hdr_ptr` points to a pointer-sized header directly before the usable space. This header
198 /// contains the value `unaligned_ptr`, so that we can pass it to `free` later. This is
199 /// necessary because the width of the padding is unknown.
200 ///
201 /// This function accepts `aligned_ptr` and offsets it backwards to return `hdr_ptr`.
202 fn manualAlignHeader(aligned_ptr: [*]u8) *[*]u8 {
203 return @ptrCast(@alignCast(aligned_ptr - @sizeOf(usize)));
204 }
205
206 fn alloc(
207 _: *anyopaque,
208 len: usize,
209 alignment: Alignment,
210 return_address: usize,
211 ) ?[*]u8 {
212 _ = return_address;
213 assert(len > 0);
214 switch (allocStrat(alignment)) {
215 .raw => {
216 // `std.c.max_align_t` isn't the whole story, because if `len` is smaller than
217 // every C type with alignment `max_align_t`, the allocation can be less-aligned.
218 // The implementation need only guarantee that any type of length `len` would be
219 // suitably aligned.
220 //
221 // For instance, if `len == 8` and `alignment == .@"16"`, then `malloc` may not
222 // fulfil this request, because there is necessarily no C type with 8-byte size
223 // but 16-byte alignment.
224 //
225 // In theory, the resulting rule here would be target-specific, but in practice,
226 // the smallest type with an alignment of `max_align_t` has the same size (it's
227 // usually `c_longdouble`), so we can just extend the allocation size up to the
228 // alignment of `max_align_t` if necessary.
229 const actual_len = @max(len, @alignOf(std.c.max_align_t));
230 const ptr = c.malloc(actual_len) orelse return null;
231 assert(alignment.check(@intFromPtr(ptr)));
232 return @ptrCast(ptr);
233 },
234 .posix_memalign => {
235 // The posix_memalign only accepts alignment values that are a
236 // multiple of the pointer size
237 const effective_alignment = @max(alignment.toByteUnits(), @sizeOf(usize));
238 var aligned_ptr: ?*anyopaque = undefined;
239 if (c.posix_memalign(&aligned_ptr, effective_alignment, len) != 0) {
240 return null;
241 }
242 assert(alignment.check(@intFromPtr(aligned_ptr)));
243 return @ptrCast(aligned_ptr);
244 },
245 .manual_align => {
246 // Overallocate to account for alignment padding and store the original pointer
247 // returned by `malloc` before the aligned address.
248 const padded_len = len + @sizeOf(usize) + alignment.toByteUnits() - 1;
249 const unaligned_ptr: [*]u8 = @ptrCast(c.malloc(padded_len) orelse return null);
250 const unaligned_addr = @intFromPtr(unaligned_ptr);
251 const aligned_addr = alignment.forward(unaligned_addr + @sizeOf(usize));
252 const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr);
253 manualAlignHeader(aligned_ptr).* = unaligned_ptr;
254 return aligned_ptr;
255 },
256 }
257 }
258
259 fn resize(
260 _: *anyopaque,
261 memory: []u8,
262 alignment: Alignment,
263 new_len: usize,
264 return_address: usize,
265 ) bool {
266 _ = return_address;
267 assert(new_len > 0);
268 if (new_len <= memory.len) {
269 return true; // in-place shrink always works
270 }
271 const mallocSize = func: {
272 if (@TypeOf(c.malloc_size) != void) break :func c.malloc_size;
273 if (@TypeOf(c.malloc_usable_size) != void) break :func c.malloc_usable_size;
274 if (@TypeOf(c._msize) != void) break :func c._msize;
275 return false; // we don't know how much space is actually available
276 };
277 const usable_len: usize = switch (allocStrat(alignment)) {
278 .raw, .posix_memalign => mallocSize(memory.ptr),
279 .manual_align => usable_len: {
280 const unaligned_ptr = manualAlignHeader(memory.ptr).*;
281 const full_len = mallocSize(unaligned_ptr);
282 const padding = @intFromPtr(memory.ptr) - @intFromPtr(unaligned_ptr);
283 break :usable_len full_len - padding;
284 },
285 };
286 return new_len <= usable_len;
287 }
288
289 fn remap(
290 ctx: *anyopaque,
291 memory: []u8,
292 alignment: Alignment,
293 new_len: usize,
294 return_address: usize,
295 ) ?[*]u8 {
296 assert(new_len > 0);
297 // Prefer resizing in-place if possible, since `realloc` could be expensive even if legal.
298 if (resize(ctx, memory, alignment, new_len, return_address)) {
299 return memory.ptr;
300 }
301 switch (allocStrat(alignment)) {
302 .raw => {
303 // `malloc` and friends guarantee the required alignment, so we can try `realloc`.
304 // C only needs to respect `max_align_t` up to the allocation size due to object
305 // alignment rules. If necessary, extend the allocation size.
306 const actual_len = @max(new_len, @alignOf(std.c.max_align_t));
307 const new_ptr = c.realloc(memory.ptr, actual_len) orelse return null;
308 assert(alignment.check(@intFromPtr(new_ptr)));
309 return @ptrCast(new_ptr);
310 },
311 .posix_memalign, .manual_align => {
312 // `realloc` would potentially return a new allocation which does not respect
313 // the original alignment, so we can't do anything more.
314 return null;
315 },
316 }
317 }
318
319 fn free(
320 _: *anyopaque,
321 memory: []u8,
322 alignment: Alignment,
323 return_address: usize,
324 ) void {
325 _ = return_address;
326 switch (allocStrat(alignment)) {
327 .raw, .posix_memalign => c.free(memory.ptr),
328 .manual_align => c.free(manualAlignHeader(memory.ptr).*),
329 }
330 }
331};
332
333/// On operating systems that support memory mapping, this allocator makes a
334/// syscall directly for every allocation and free.
335///
336/// Otherwise, it falls back to the preferred singleton for the target.
337///
338/// Thread-safe.
339pub const page_allocator: Allocator = if (@hasDecl(root, "os") and
340 @hasDecl(root.os, "heap") and
341 @hasDecl(root.os.heap, "page_allocator"))
342 root.os.heap.page_allocator
343else if (builtin.target.cpu.arch.isWasm()) .{
344 .ptr = undefined,
345 .vtable = &WasmAllocator.vtable,
346} else .{
347 .ptr = undefined,
348 .vtable = &PageAllocator.vtable,
349};
350
351pub const smp_allocator: Allocator = .{
352 .ptr = undefined,
353 .vtable = &SmpAllocator.vtable,
354};
355
356/// This allocator is fast, small, and specific to WebAssembly.
357pub const wasm_allocator: Allocator = .{
358 .ptr = undefined,
359 .vtable = &WasmAllocator.vtable,
360};
361
362/// Supports single-threaded WebAssembly and Linux.
363pub const brk_allocator: Allocator = .{
364 .ptr = undefined,
365 .vtable = &BrkAllocator.vtable,
366};
367
368test c_allocator {
369 if (builtin.link_libc) {
370 try testAllocator(c_allocator);
371 try testAllocatorAligned(c_allocator);
372 try testAllocatorLargeAlignment(c_allocator);
373 try testAllocatorAlignedShrink(c_allocator);
374 }
375}
376
377test smp_allocator {
378 if (builtin.single_threaded) return;
379 try testAllocator(smp_allocator);
380 try testAllocatorAligned(smp_allocator);
381 try testAllocatorLargeAlignment(smp_allocator);
382 try testAllocatorAlignedShrink(smp_allocator);
383}
384
385test SafeAllocator {
386 var instance: SafeAllocator = .init(page_allocator, .{});
387 defer _ = instance.deinit();
388 const allocator = instance.allocator();
389
390 try testAllocator(allocator);
391 try testAllocatorAligned(allocator);
392 try testAllocatorLargeAlignment(allocator);
393 try testAllocatorAlignedShrink(allocator);
394}
395
396test PageAllocator {
397 const allocator = page_allocator;
398 try testAllocator(allocator);
399 try testAllocatorAligned(allocator);
400 if (!builtin.target.cpu.arch.isWasm()) {
401 try testAllocatorLargeAlignment(allocator);
402 try testAllocatorAlignedShrink(allocator);
403 }
404
405 if (builtin.os.tag == .windows) {
406 const slice = try allocator.alignedAlloc(u8, .fromByteUnits(page_size_min), 128);
407 slice[0] = 0x12;
408 slice[127] = 0x34;
409 allocator.free(slice);
410 }
411 {
412 var buf = try allocator.alloc(u8, pageSize() + 1);
413 defer allocator.free(buf);
414 buf = try allocator.realloc(buf, 1); // shrink past the page boundary
415 }
416}
417
418test ArenaAllocator {
419 var arena_allocator = ArenaAllocator.init(page_allocator);
420 defer arena_allocator.deinit();
421 const allocator = arena_allocator.allocator();
422
423 try testAllocator(allocator);
424 try testAllocatorAligned(allocator);
425 try testAllocatorLargeAlignment(allocator);
426 try testAllocatorAlignedShrink(allocator);
427}
428
429/// This one should not try alignments that exceed what C malloc can handle.
430pub fn testAllocator(base_allocator: mem.Allocator) !void {
431 var validationAllocator = mem.validationWrap(base_allocator);
432 const allocator = validationAllocator.allocator();
433
434 var slice = try allocator.alloc(*i32, 100);
435 try testing.expect(slice.len == 100);
436 for (slice, 0..) |*item, i| {
437 item.* = try allocator.create(i32);
438 item.*.* = @as(i32, @intCast(i));
439 }
440
441 slice = try allocator.realloc(slice, 20000);
442 try testing.expect(slice.len == 20000);
443
444 for (slice[0..100], 0..) |item, i| {
445 try testing.expect(item.* == @as(i32, @intCast(i)));
446 allocator.destroy(item);
447 }
448
449 if (allocator.resize(slice, 50)) {
450 slice = slice[0..50];
451 if (allocator.resize(slice, 25)) {
452 slice = slice[0..25];
453 try testing.expect(allocator.resize(slice, 0));
454 slice = slice[0..0];
455 slice = try allocator.realloc(slice, 10);
456 try testing.expect(slice.len == 10);
457 }
458 }
459 allocator.free(slice);
460
461 // Zero-length allocation
462 const empty = try allocator.alloc(u8, 0);
463 allocator.free(empty);
464 // Allocation with zero-sized types
465 const zero_bit_ptr = try allocator.create(u0);
466 zero_bit_ptr.* = 0;
467 allocator.destroy(zero_bit_ptr);
468 const zero_len_array = try allocator.create([0]u64);
469 allocator.destroy(zero_len_array);
470
471 const oversize = try allocator.alignedAlloc(u32, null, 5);
472 try testing.expect(oversize.len >= 5);
473 for (oversize) |*item| {
474 item.* = 0xDEADBEEF;
475 }
476 allocator.free(oversize);
477}
478
479pub fn testAllocatorAligned(base_allocator: mem.Allocator) !void {
480 var validationAllocator = mem.validationWrap(base_allocator);
481 const allocator = validationAllocator.allocator();
482
483 // Test a few alignment values, smaller and bigger than the type's one
484 inline for ([_]Alignment{ .@"1", .@"2", .@"4", .@"8", .@"16", .@"32", .@"64" }) |alignment| {
485 // initial
486 var slice = try allocator.alignedAlloc(u8, alignment, 10);
487 try testing.expect(slice.len == 10);
488 // grow
489 slice = try allocator.realloc(slice, 100);
490 try testing.expect(slice.len == 100);
491 if (allocator.resize(slice, 10)) {
492 slice = slice[0..10];
493 }
494 try testing.expect(allocator.resize(slice, 0));
495 slice = slice[0..0];
496 // realloc from zero
497 slice = try allocator.realloc(slice, 100);
498 try testing.expect(slice.len == 100);
499 if (allocator.resize(slice, 10)) {
500 slice = slice[0..10];
501 }
502 try testing.expect(allocator.resize(slice, 0));
503 }
504}
505
506pub fn testAllocatorLargeAlignment(base_allocator: mem.Allocator) !void {
507 var validationAllocator = mem.validationWrap(base_allocator);
508 const allocator = validationAllocator.allocator();
509
510 const large_align: usize = page_size_min / 2;
511
512 var align_mask: usize = undefined;
513 align_mask = @shlWithOverflow(~@as(usize, 0), @as(Allocator.Log2Align, @ctz(large_align)))[0];
514
515 var slice = try allocator.alignedAlloc(u8, .fromByteUnits(large_align), 500);
516 try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
517
518 if (allocator.resize(slice, 100)) {
519 slice = slice[0..100];
520 }
521
522 slice = try allocator.realloc(slice, 5000);
523 try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
524
525 if (allocator.resize(slice, 10)) {
526 slice = slice[0..10];
527 }
528
529 slice = try allocator.realloc(slice, 20000);
530 try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
531
532 allocator.free(slice);
533}
534
535pub fn testAllocatorAlignedShrink(base_allocator: mem.Allocator) !void {
536 var validationAllocator = mem.validationWrap(base_allocator);
537 const allocator = validationAllocator.allocator();
538
539 var debug_buffer: [1000]u8 = undefined;
540 var fib = FixedBufferAllocator.init(&debug_buffer);
541 const debug_allocator = fib.allocator();
542
543 const alloc_size = pageSize() * 2 + 50;
544 var slice = try allocator.alignedAlloc(u8, .@"16", alloc_size);
545 defer allocator.free(slice);
546
547 var stuff_to_free = std.array_list.Managed([]align(16) u8).init(debug_allocator);
548 // On Windows, VirtualAlloc returns addresses aligned to a 64K boundary,
549 // which is 16 pages, hence the 32. This test may require to increase
550 // the size of the allocations feeding the `allocator` parameter if they
551 // fail, because of this high over-alignment we want to have.
552 while (@intFromPtr(slice.ptr) == mem.alignForward(usize, @intFromPtr(slice.ptr), pageSize() * 32)) {
553 try stuff_to_free.append(slice);
554 slice = try allocator.alignedAlloc(u8, .@"16", alloc_size);
555 }
556 while (stuff_to_free.pop()) |item| {
557 allocator.free(item);
558 }
559 slice[0] = 0x12;
560 slice[60] = 0x34;
561
562 slice = try allocator.reallocAdvanced(slice, alloc_size / 2, 0);
563 try testing.expect(slice[0] == 0x12);
564 try testing.expect(slice[60] == 0x34);
565}
566
567const page_size_min_default: ?usize = switch (builtin.os.tag) {
568 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
569 .x86_64 => 4 << 10,
570 .aarch64 => 16 << 10,
571 else => null,
572 },
573 .windows => switch (builtin.cpu.arch) {
574 // -- <https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200>
575 .x86, .x86_64 => 4 << 10,
576 // SuperH => 4 << 10,
577 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
578 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
579 // DEC Alpha => 8 << 10,
580 // Itanium => 8 << 10,
581 .thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
582 else => null,
583 },
584 .wasi => switch (builtin.cpu.arch) {
585 .wasm32, .wasm64 => 64 << 10,
586 else => null,
587 },
588 // https://github.com/tianocore/edk2/blob/b158dad150bf02879668f72ce306445250838201/MdePkg/Include/Uefi/UefiBaseType.h#L180-L187
589 .uefi => 4 << 10,
590 .freebsd => switch (builtin.cpu.arch) {
591 // FreeBSD/sys/*
592 .x86, .x86_64 => 4 << 10,
593 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
594 .aarch64, .aarch64_be => 4 << 10,
595 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
596 .riscv32, .riscv64 => 4 << 10,
597 else => null,
598 },
599 .netbsd => switch (builtin.cpu.arch) {
600 // NetBSD/sys/arch/*
601 .alpha => 8 << 10,
602 .x86, .x86_64 => 4 << 10,
603 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
604 .aarch64, .aarch64_be => 4 << 10,
605 .hppa => 4 << 10,
606 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
607 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
608 .sh, .sheb => 4 << 10,
609 .sparc => 4 << 10,
610 .sparc64 => 8 << 10,
611 .riscv32, .riscv64 => 4 << 10,
612 // Sun-2
613 .m68k => 2 << 10,
614 else => null,
615 },
616 .dragonfly => switch (builtin.cpu.arch) {
617 .x86, .x86_64 => 4 << 10,
618 else => null,
619 },
620 .openbsd => switch (builtin.cpu.arch) {
621 // OpenBSD/sys/arch/*
622 .alpha => 8 << 10,
623 .hppa => 4 << 10,
624 .x86, .x86_64 => 4 << 10,
625 .thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
626 .m88k => 4 << 10,
627 .mips64, .mips64el => 4 << 10,
628 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
629 .riscv64 => 4 << 10,
630 .sh, .sheb => 4 << 10,
631 .sparc64 => 8 << 10,
632 else => null,
633 },
634 .illumos => switch (builtin.cpu.arch) {
635 // src/uts/*/sys/machparam.h
636 .x86, .x86_64 => 4 << 10,
637 .sparc, .sparc64 => 8 << 10,
638 else => null,
639 },
640 .fuchsia => switch (builtin.cpu.arch) {
641 // fuchsia/kernel/arch/*/include/arch/defines.h
642 .x86_64 => 4 << 10,
643 .aarch64, .aarch64_be => 4 << 10,
644 .riscv64 => 4 << 10,
645 else => null,
646 },
647 // https://github.com/SerenityOS/serenity/blob/62b938b798dc009605b5df8a71145942fc53808b/Kernel/API/POSIX/sys/limits.h#L11-L13
648 .serenity => 4 << 10,
649 .haiku => switch (builtin.cpu.arch) {
650 // haiku/headers/posix/arch/*/limits.h
651 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
652 .aarch64, .aarch64_be => 4 << 10,
653 .m68k => 4 << 10,
654 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
655 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
656 .riscv64 => 4 << 10,
657 .sparc64 => 8 << 10,
658 .x86, .x86_64 => 4 << 10,
659 else => null,
660 },
661 .hurd => switch (builtin.cpu.arch) {
662 // gnumach/*/include/mach/*/vm_param.h
663 .x86, .x86_64 => 4 << 10,
664 .aarch64 => null,
665 else => null,
666 },
667 .plan9 => switch (builtin.cpu.arch) {
668 // 9front/sys/src/9/*/mem.h
669 .x86, .x86_64 => 4 << 10,
670 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
671 .aarch64, .aarch64_be => 4 << 10,
672 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
673 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
674 .sparc => 4 << 10,
675 else => null,
676 },
677 .ps3 => switch (builtin.cpu.arch) {
678 // cell/SDK_doc/en/html/C_and_C++_standard_libraries/stdlib.html
679 .powerpc64 => 1 << 20, // 1 MiB
680 else => null,
681 },
682 .ps4 => switch (builtin.cpu.arch) {
683 // https://github.com/ps4dev/ps4sdk/blob/4df9d001b66ae4ec07d9a51b62d1e4c5e270eecc/include/machine/param.h#L95
684 .x86, .x86_64 => 4 << 10,
685 else => null,
686 },
687 .ps5 => switch (builtin.cpu.arch) {
688 // https://github.com/PS5Dev/PS5SDK/blob/a2e03a2a0231a3a3397fa6cd087a01ca6d04f273/include/machine/param.h#L95
689 .x86, .x86_64 => 16 << 10,
690 else => null,
691 },
692 .psp => switch (builtin.cpu.arch) {
693 // minimum block allocation by testing sceKernel
694 .mips, .mipsel => 1 << 8, // 256
695 else => null,
696 },
697 // system/lib/libc/musl/arch/emscripten/bits/limits.h
698 .emscripten => 64 << 10,
699 .linux => switch (builtin.cpu.arch) {
700 // Linux/arch/*/Kconfig
701 .alpha => 8 << 10,
702 .arc, .arceb => 4 << 10,
703 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
704 .aarch64, .aarch64_be => 4 << 10,
705 .csky => 4 << 10,
706 .hexagon => 4 << 10,
707 .hppa => 4 << 10,
708 .loongarch32, .loongarch64 => 4 << 10,
709 .m68k => 4 << 10,
710 .microblaze, .microblazeel => 4 << 10,
711 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
712 .or1k => 8 << 10,
713 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
714 .riscv32, .riscv64 => 4 << 10,
715 .s390x => 4 << 10,
716 .sh, .sheb => 4 << 10,
717 .sparc => 4 << 10,
718 .sparc64 => 8 << 10,
719 .x86, .x86_64 => 4 << 10,
720 .xtensa, .xtensaeb => 4 << 10,
721 else => null,
722 },
723 .freestanding, .other => switch (builtin.cpu.arch) {
724 .wasm32, .wasm64 => 64 << 10,
725 .x86, .x86_64 => 4 << 10,
726 .aarch64, .aarch64_be => 4 << 10,
727 else => null,
728 },
729 else => null,
730};
731
732const page_size_max_default: ?usize = switch (builtin.os.tag) {
733 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
734 .x86_64 => 4 << 10,
735 .aarch64 => 16 << 10,
736 else => null,
737 },
738 .windows => switch (builtin.cpu.arch) {
739 // -- <https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200>
740 .x86, .x86_64 => 4 << 10,
741 // SuperH => 4 << 10,
742 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
743 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
744 // DEC Alpha => 8 << 10,
745 // Itanium => 8 << 10,
746 .thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
747 else => null,
748 },
749 .wasi => switch (builtin.cpu.arch) {
750 .wasm32, .wasm64 => 64 << 10,
751 else => null,
752 },
753 // https://github.com/tianocore/edk2/blob/b158dad150bf02879668f72ce306445250838201/MdePkg/Include/Uefi/UefiBaseType.h#L180-L187
754 .uefi => 4 << 10,
755 .freebsd => switch (builtin.cpu.arch) {
756 // FreeBSD/sys/*
757 .x86, .x86_64 => 4 << 10,
758 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
759 .aarch64, .aarch64_be => 4 << 10,
760 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
761 .riscv32, .riscv64 => 4 << 10,
762 else => null,
763 },
764 .netbsd => switch (builtin.cpu.arch) {
765 // NetBSD/sys/arch/*
766 .alpha => 8 << 10,
767 .x86, .x86_64 => 4 << 10,
768 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
769 .aarch64, .aarch64_be => 64 << 10,
770 .hppa => 4 << 10,
771 .mips, .mipsel, .mips64, .mips64el => 16 << 10,
772 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 16 << 10,
773 .sh, .sheb => 4 << 10,
774 .sparc => 8 << 10,
775 .sparc64 => 8 << 10,
776 .riscv32, .riscv64 => 4 << 10,
777 .m68k => 8 << 10,
778 else => null,
779 },
780 .dragonfly => switch (builtin.cpu.arch) {
781 .x86, .x86_64 => 4 << 10,
782 else => null,
783 },
784 .openbsd => switch (builtin.cpu.arch) {
785 // OpenBSD/sys/arch/*
786 .alpha => 8 << 10,
787 .hppa => 4 << 10,
788 .x86, .x86_64 => 4 << 10,
789 .thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
790 .m88k => 4 << 10,
791 .mips64, .mips64el => 16 << 10,
792 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
793 .riscv64 => 4 << 10,
794 .sh, .sheb => 4 << 10,
795 .sparc64 => 8 << 10,
796 else => null,
797 },
798 .illumos => switch (builtin.cpu.arch) {
799 // src/uts/*/sys/machparam.h
800 .x86, .x86_64 => 4 << 10,
801 .sparc, .sparc64 => 8 << 10,
802 else => null,
803 },
804 .fuchsia => switch (builtin.cpu.arch) {
805 // fuchsia/kernel/arch/*/include/arch/defines.h
806 .x86_64 => 4 << 10,
807 .aarch64, .aarch64_be => 4 << 10,
808 .riscv64 => 4 << 10,
809 else => null,
810 },
811 // https://github.com/SerenityOS/serenity/blob/62b938b798dc009605b5df8a71145942fc53808b/Kernel/API/POSIX/sys/limits.h#L11-L13
812 .serenity => 4 << 10,
813 .haiku => switch (builtin.cpu.arch) {
814 // haiku/headers/posix/arch/*/limits.h
815 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
816 .aarch64, .aarch64_be => 4 << 10,
817 .m68k => 4 << 10,
818 .mips, .mipsel, .mips64, .mips64el => 4 << 10,
819 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
820 .riscv64 => 4 << 10,
821 .sparc64 => 8 << 10,
822 .x86, .x86_64 => 4 << 10,
823 else => null,
824 },
825 .hurd => switch (builtin.cpu.arch) {
826 // gnumach/*/include/mach/*/vm_param.h
827 .x86, .x86_64 => 4 << 10,
828 .aarch64 => null,
829 else => null,
830 },
831 .plan9 => switch (builtin.cpu.arch) {
832 // 9front/sys/src/9/*/mem.h
833 .x86, .x86_64 => 4 << 10,
834 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
835 .aarch64, .aarch64_be => 64 << 10,
836 .mips, .mipsel, .mips64, .mips64el => 16 << 10,
837 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
838 .sparc => 4 << 10,
839 else => null,
840 },
841 .ps3 => switch (builtin.cpu.arch) {
842 // cell/SDK_doc/en/html/C_and_C++_standard_libraries/stdlib.html
843 .powerpc64 => 1 << 20, // 1 MiB
844 else => null,
845 },
846 .ps4 => switch (builtin.cpu.arch) {
847 // https://github.com/ps4dev/ps4sdk/blob/4df9d001b66ae4ec07d9a51b62d1e4c5e270eecc/include/machine/param.h#L95
848 .x86, .x86_64 => 4 << 10,
849 else => null,
850 },
851 .ps5 => switch (builtin.cpu.arch) {
852 // https://github.com/PS5Dev/PS5SDK/blob/a2e03a2a0231a3a3397fa6cd087a01ca6d04f273/include/machine/param.h#L95
853 .x86, .x86_64 => 16 << 10,
854 else => null,
855 },
856 .psp => switch (builtin.cpu.arch) {
857 // minimum block allocation by testing sceKernel
858 .mips, .mipsel => 1 << 8, // 256
859 else => null,
860 },
861 // system/lib/libc/musl/arch/emscripten/bits/limits.h
862 .emscripten => 64 << 10,
863 .linux => switch (builtin.cpu.arch) {
864 // Linux/arch/*/Kconfig
865 .alpha => 8 << 10,
866 .arc, .arceb => 16 << 10,
867 .thumb, .thumbeb, .arm, .armeb => 4 << 10,
868 .aarch64, .aarch64_be => 64 << 10,
869 .csky => 4 << 10,
870 .hexagon => 256 << 10,
871 .hppa => 64 << 10,
872 .loongarch32, .loongarch64 => 64 << 10,
873 .m68k => 8 << 10,
874 .microblaze, .microblazeel => 4 << 10,
875 .mips, .mipsel, .mips64, .mips64el => 64 << 10,
876 .or1k => 8 << 10,
877 .powerpc, .powerpc64, .powerpc64le, .powerpcle => 256 << 10,
878 .riscv32, .riscv64 => 4 << 10,
879 .s390x => 4 << 10,
880 .sh, .sheb => 64 << 10,
881 .sparc => 4 << 10,
882 .sparc64 => 8 << 10,
883 .x86, .x86_64 => 4 << 10,
884 .xtensa, .xtensaeb => 4 << 10,
885 else => null,
886 },
887 .freestanding => switch (builtin.cpu.arch) {
888 .wasm32, .wasm64 => 64 << 10,
889 else => null,
890 },
891 else => null,
892};
893
894test {
895 _ = @import("heap/memory_pool.zig");
896 _ = ArenaAllocator;
897 _ = DebugAllocator(.{});
898 _ = SafeAllocator;
899 _ = FixedBufferAllocator;
900 _ = BufferFirstAllocator;
901 if (builtin.single_threaded) {
902 if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) {
903 _ = brk_allocator;
904 }
905 } else {
906 _ = smp_allocator;
907 }
908}