Files
zig/lib/std/heap.zig
T
Kendall Condon a9423234a6 add SafeAllocator
Implements a thread-safe allocator with the following guarantees:
* `deinit` reports all leaks and frees all backing memory.
* All allocation mismatches result in either a panic or segmentation
  fault.
* Allocations from other `SafeAllocator` instances cause a panic (if
  `Options.canary` differ).
* Double frees and operation (resize, remap, and free) races panic or
  segmentation fault.

Given the backing allocator does not reuse memory, it does not reuse
memory either and
* Most writes after free will segmentation fault or are eventually
  detected and panic.

`std.heap.DebugAllocator` has been deprecated (I have also deprecated
`std.heap.Check` since this was its last usage and returning a `usize`
leak count is a much cleaner approach).

- General Design

Every allocation is trailed by an `AllocFooter` which contains metadata
for the allocation and stack traces. It is protected by a checksum to
catch corruption from allocation overwrites and report canary
mismatches. An allocation's memory has a minimum alignment of
`AllocFooter` so that the footer is at a fixed offset determined from
the allocation size. An allocation's memory is stored either:
* Inside linearly-filled buckets for small allocations.
* Inside an allocation directly from the backing allocator.

To track allocations, each thread maintains a table of backing
allocations. The table may be modified by other threads in the case of
a producer-consumer operation, so the table is a linked list only
expanded by creating new segments. Each thread maintains a linked list
of free entries, which may contain entries from other threads' tables.

In the case of producer-consumer operations, acquire/release ordering
is assumed to be provided externally. This is also assumed by all other
thread-safe allocators that reuse memory as otherwise there would be
data races on reuse of allocated memory.

- Fuzz Tests

Two fuzz tests have also been added for the allocator. They check that
there is no memory reuse, that returned memory is writable, and that
it is not overwritten. The multi-threaded fuzz test spawns a number of
worker threads which are used for all the test runs. I have run these
tests extensively under TSAN.

- Performance Measurements

Building the standard library tests with a RelaseSafe compiler build
and `-Ddebug-allocator`:

```
Benchmark 1 (3 runs): ./master-out/bin/zig test --zig-lib-dir lib lib/std/std.zig -femit-bin=test --test-no-exec
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          29.4s  ±  157ms    29.2s  … 29.5s           0 ( 0%)        0%
  peak_rss           2.24GB ± 3.49MB    2.23GB … 2.24GB          0 ( 0%)        0%
  cpu_cycles          143G  ±  999M      142G  …  144G           0 ( 0%)        0%
  instructions        268G  ± 5.22M      268G  …  268G           0 ( 0%)        0%
  cache_references   13.1G  ± 88.8M     13.0G  … 13.2G           0 ( 0%)        0%
  cache_misses       2.38G  ± 30.7M     2.35G  … 2.41G           0 ( 0%)        0%
  branch_misses       634M  ± 6.22M      629M  …  641M           0 ( 0%)        0%
Benchmark 2 (3 runs): ./branch-out/bin/zig test --zig-lib-dir lib lib/std/std.zig -femit-bin=test --test-no-exec
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          22.1s  ± 88.6ms    22.0s  … 22.2s           0 ( 0%)        - 24.7% ±  1.0%
  peak_rss           1.11GB ±  799KB    1.11GB … 1.11GB          0 ( 0%)        - 50.3% ±  0.3%
  cpu_cycles          136G  ±  480M      136G  …  137G           0 ( 0%)        -  4.4% ±  1.2%
  instructions        273G  ± 2.07M      273G  …  273G           0 ( 0%)        💩+  1.6% ±  0.0%
  cache_references   12.3G  ± 71.3M     12.2G  … 12.4G           0 ( 0%)        -  6.0% ±  1.4%
  cache_misses       2.02G  ± 11.5M     2.01G  … 2.03G           0 ( 0%)        - 14.9% ±  2.2%
  branch_misses       569M  ± 2.65M      567M  …  572M           0 ( 0%)        - 10.2% ±  1.7%
```
2026-05-25 18:32:36 -07:00

909 lines
34 KiB
Zig

const std = @import("std.zig");
const builtin = @import("builtin");
const root = @import("root");
const assert = std.debug.assert;
const testing = std.testing;
const mem = std.mem;
const c = std.c;
const Allocator = std.mem.Allocator;
const windows = std.os.windows;
const Alignment = std.mem.Alignment;
pub const ArenaAllocator = @import("heap/ArenaAllocator.zig");
pub const SmpAllocator = @import("heap/SmpAllocator.zig");
pub const SafeAllocator = @import("heap/SafeAllocator.zig");
pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
pub const BufferFirstAllocator = @import("heap/BufferFirstAllocator.zig");
pub const PageAllocator = @import("heap/PageAllocator.zig");
pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");
pub const BrkAllocator = @import("heap/BrkAllocator.zig");
/// Deprecated; use `SafeAllocator.Options`.
pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config;
/// Deprecated; use `SafeAllocator`.
pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator;
/// Deprecated.
pub const Check = enum { ok, leak };
/// A memory pool that can allocate objects of a single type very quickly.
/// Use this when you need to allocate a lot of objects of the same type,
/// because it outperforms general purpose allocators.
/// Functions that potentially allocate memory accept an `Allocator` parameter.
pub fn MemoryPool(comptime Item: type) type {
return memory_pool.Extra(Item, .{ .alignment = null });
}
pub const memory_pool = @import("heap/memory_pool.zig");
/// comptime-known minimum page size of the target.
///
/// All pointers from `mmap` or `NtAllocateVirtualMemory` are aligned to at least
/// `page_size_min`, but their actual alignment may be bigger.
///
/// This value can be overridden via `std.options.page_size_min`.
///
/// On many systems, the actual page size can only be determined at runtime
/// with `pageSize`.
pub 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"));
/// comptime-known maximum page size of the target.
///
/// Targeting a system with a larger page size may require overriding
/// `std.options.page_size_max`, as well as providing a corresponding linker
/// option.
///
/// The actual page size can only be determined at runtime with `pageSize`.
pub 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)
@compileError("freestanding/other page_size_max must provided with std.options.page_size_max")
else
@compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_max; populate std.options.page_size_max"));
/// If the page size is comptime-known, return value is comptime.
/// Otherwise, calls `std.options.queryPageSize` which by default queries the
/// host operating system at runtime.
pub inline fn pageSize() usize {
if (page_size_min == page_size_max) return page_size_min;
return std.options.queryPageSize();
}
test pageSize {
assert(std.math.isPowerOfTwo(pageSize()));
}
/// The default implementation of `std.options.queryPageSize`.
/// Asserts that the page size is within `page_size_min` and `page_size_max`
pub fn defaultQueryPageSize() usize {
const global = struct {
var cached_result: std.atomic.Value(usize) = .init(0);
};
var size = global.cached_result.load(.unordered);
if (size > 0) return size;
size = size: switch (builtin.os.tag) {
.linux => if (builtin.link_libc)
@max(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE)), 0)
else
std.os.linux.getauxval(std.elf.AT_PAGESZ),
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
const task_port = std.c.mach_task_self();
// mach_task_self may fail "if there are any resource failures or other errors".
if (task_port == std.c.TASK.NULL) break :size 0;
var info_count = std.c.TASK.VM.INFO_COUNT;
var vm_info: std.c.task_vm_info_data_t = undefined;
vm_info.page_size = 0;
_ = std.c.task_info(
task_port,
std.c.TASK.VM.INFO,
@as(std.c.task_info_t, @ptrCast(&vm_info)),
&info_count,
);
break :size @intCast(vm_info.page_size);
},
.windows => {
var sbi: windows.SYSTEM.BASIC_INFORMATION = undefined;
switch (windows.ntdll.NtQuerySystemInformation(
.Basic,
&sbi,
@sizeOf(windows.SYSTEM.BASIC_INFORMATION),
null,
)) {
.SUCCESS => break :size sbi.PageSize,
else => break :size 0,
}
},
else => if (builtin.link_libc)
@max(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE)), 0)
else if (builtin.os.tag == .freestanding or builtin.os.tag == .other)
@compileError("unsupported target: freestanding/other")
else
@compileError("pageSize on " ++ @tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " is not supported without linking libc, using the default implementation"),
};
if (size == 0) size = page_size_max;
assert(size >= page_size_min);
assert(size <= page_size_max);
global.cached_result.store(size, .unordered);
return size;
}
test defaultQueryPageSize {
if (builtin.cpu.arch.isWasm()) return error.SkipZigTest;
assert(std.math.isPowerOfTwo(defaultQueryPageSize()));
}
/// A wrapper around the C memory allocation API which supports the full `Allocator`
/// interface, including arbitrary alignment. Simple `malloc` calls are used when
/// possible, but large requested alignments may require larger buffers in order to
/// satisfy the request. As well as `malloc`, `realloc`, and `free`, the extension
/// functions `malloc_usable_size` and `posix_memalign` are used when available.
pub const c_allocator: Allocator = .{
.ptr = undefined,
.vtable = &c_allocator_impl.vtable,
};
const c_allocator_impl = struct {
comptime {
if (!builtin.link_libc) {
@compileError("C allocator is only available when linking against libc");
}
}
const vtable: Allocator.VTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
const have_posix_memalign = switch (builtin.os.tag) {
.dragonfly,
.netbsd,
.freebsd,
.illumos,
.openbsd,
.linux,
.driverkit,
.ios,
.maccatalyst,
.macos,
.tvos,
.visionos,
.watchos,
.serenity,
=> true,
else => false,
};
fn allocStrat(need_align: Alignment) union(enum) {
raw,
posix_memalign: if (have_posix_memalign) void else noreturn,
manual_align: if (have_posix_memalign) noreturn else void,
} {
// If `malloc` guarantees `need_align`, always prefer a raw allocation.
if (Alignment.compare(need_align, .lte, .of(c.max_align_t))) {
return .raw;
}
// Use `posix_memalign` if available. Otherwise, we must manually align the allocation.
return if (have_posix_memalign) .posix_memalign else .manual_align;
}
/// If `allocStrat(a) == .manual_align`, an allocation looks like this:
///
/// unaligned_ptr hdr_ptr aligned_ptr
/// v v v
/// +---------------+--------+--------------+
/// | padding | header | usable bytes |
/// +---------------+--------+--------------+
///
/// * `unaligned_ptr` is the raw return value of `malloc`.
/// * `aligned_ptr` is computed by aligning `unaligned_ptr` forward; it is what `alloc` returns.
/// * `hdr_ptr` points to a pointer-sized header directly before the usable space. This header
/// contains the value `unaligned_ptr`, so that we can pass it to `free` later. This is
/// necessary because the width of the padding is unknown.
///
/// This function accepts `aligned_ptr` and offsets it backwards to return `hdr_ptr`.
fn manualAlignHeader(aligned_ptr: [*]u8) *[*]u8 {
return @ptrCast(@alignCast(aligned_ptr - @sizeOf(usize)));
}
fn alloc(
_: *anyopaque,
len: usize,
alignment: Alignment,
return_address: usize,
) ?[*]u8 {
_ = return_address;
assert(len > 0);
switch (allocStrat(alignment)) {
.raw => {
// `std.c.max_align_t` isn't the whole story, because if `len` is smaller than
// every C type with alignment `max_align_t`, the allocation can be less-aligned.
// The implementation need only guarantee that any type of length `len` would be
// suitably aligned.
//
// For instance, if `len == 8` and `alignment == .@"16"`, then `malloc` may not
// fulfil this request, because there is necessarily no C type with 8-byte size
// but 16-byte alignment.
//
// In theory, the resulting rule here would be target-specific, but in practice,
// the smallest type with an alignment of `max_align_t` has the same size (it's
// usually `c_longdouble`), so we can just extend the allocation size up to the
// alignment of `max_align_t` if necessary.
const actual_len = @max(len, @alignOf(std.c.max_align_t));
const ptr = c.malloc(actual_len) orelse return null;
assert(alignment.check(@intFromPtr(ptr)));
return @ptrCast(ptr);
},
.posix_memalign => {
// The posix_memalign only accepts alignment values that are a
// multiple of the pointer size
const effective_alignment = @max(alignment.toByteUnits(), @sizeOf(usize));
var aligned_ptr: ?*anyopaque = undefined;
if (c.posix_memalign(&aligned_ptr, effective_alignment, len) != 0) {
return null;
}
assert(alignment.check(@intFromPtr(aligned_ptr)));
return @ptrCast(aligned_ptr);
},
.manual_align => {
// Overallocate to account for alignment padding and store the original pointer
// returned by `malloc` before the aligned address.
const padded_len = len + @sizeOf(usize) + alignment.toByteUnits() - 1;
const unaligned_ptr: [*]u8 = @ptrCast(c.malloc(padded_len) orelse return null);
const unaligned_addr = @intFromPtr(unaligned_ptr);
const aligned_addr = alignment.forward(unaligned_addr + @sizeOf(usize));
const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr);
manualAlignHeader(aligned_ptr).* = unaligned_ptr;
return aligned_ptr;
},
}
}
fn resize(
_: *anyopaque,
memory: []u8,
alignment: Alignment,
new_len: usize,
return_address: usize,
) bool {
_ = return_address;
assert(new_len > 0);
if (new_len <= memory.len) {
return true; // in-place shrink always works
}
const mallocSize = func: {
if (@TypeOf(c.malloc_size) != void) break :func c.malloc_size;
if (@TypeOf(c.malloc_usable_size) != void) break :func c.malloc_usable_size;
if (@TypeOf(c._msize) != void) break :func c._msize;
return false; // we don't know how much space is actually available
};
const usable_len: usize = switch (allocStrat(alignment)) {
.raw, .posix_memalign => mallocSize(memory.ptr),
.manual_align => usable_len: {
const unaligned_ptr = manualAlignHeader(memory.ptr).*;
const full_len = mallocSize(unaligned_ptr);
const padding = @intFromPtr(memory.ptr) - @intFromPtr(unaligned_ptr);
break :usable_len full_len - padding;
},
};
return new_len <= usable_len;
}
fn remap(
ctx: *anyopaque,
memory: []u8,
alignment: Alignment,
new_len: usize,
return_address: usize,
) ?[*]u8 {
assert(new_len > 0);
// Prefer resizing in-place if possible, since `realloc` could be expensive even if legal.
if (resize(ctx, memory, alignment, new_len, return_address)) {
return memory.ptr;
}
switch (allocStrat(alignment)) {
.raw => {
// `malloc` and friends guarantee the required alignment, so we can try `realloc`.
// C only needs to respect `max_align_t` up to the allocation size due to object
// alignment rules. If necessary, extend the allocation size.
const actual_len = @max(new_len, @alignOf(std.c.max_align_t));
const new_ptr = c.realloc(memory.ptr, actual_len) orelse return null;
assert(alignment.check(@intFromPtr(new_ptr)));
return @ptrCast(new_ptr);
},
.posix_memalign, .manual_align => {
// `realloc` would potentially return a new allocation which does not respect
// the original alignment, so we can't do anything more.
return null;
},
}
}
fn free(
_: *anyopaque,
memory: []u8,
alignment: Alignment,
return_address: usize,
) void {
_ = return_address;
switch (allocStrat(alignment)) {
.raw, .posix_memalign => c.free(memory.ptr),
.manual_align => c.free(manualAlignHeader(memory.ptr).*),
}
}
};
/// On operating systems that support memory mapping, this allocator makes a
/// syscall directly for every allocation and free.
///
/// Otherwise, it falls back to the preferred singleton for the target.
///
/// Thread-safe.
pub const page_allocator: Allocator = if (@hasDecl(root, "os") and
@hasDecl(root.os, "heap") and
@hasDecl(root.os.heap, "page_allocator"))
root.os.heap.page_allocator
else if (builtin.target.cpu.arch.isWasm()) .{
.ptr = undefined,
.vtable = &WasmAllocator.vtable,
} else .{
.ptr = undefined,
.vtable = &PageAllocator.vtable,
};
pub const smp_allocator: Allocator = .{
.ptr = undefined,
.vtable = &SmpAllocator.vtable,
};
/// This allocator is fast, small, and specific to WebAssembly.
pub const wasm_allocator: Allocator = .{
.ptr = undefined,
.vtable = &WasmAllocator.vtable,
};
/// Supports single-threaded WebAssembly and Linux.
pub const brk_allocator: Allocator = .{
.ptr = undefined,
.vtable = &BrkAllocator.vtable,
};
test c_allocator {
if (builtin.link_libc) {
try testAllocator(c_allocator);
try testAllocatorAligned(c_allocator);
try testAllocatorLargeAlignment(c_allocator);
try testAllocatorAlignedShrink(c_allocator);
}
}
test smp_allocator {
if (builtin.single_threaded) return;
try testAllocator(smp_allocator);
try testAllocatorAligned(smp_allocator);
try testAllocatorLargeAlignment(smp_allocator);
try testAllocatorAlignedShrink(smp_allocator);
}
test SafeAllocator {
var instance: SafeAllocator = .init(page_allocator, .{});
defer _ = instance.deinit();
const allocator = instance.allocator();
try testAllocator(allocator);
try testAllocatorAligned(allocator);
try testAllocatorLargeAlignment(allocator);
try testAllocatorAlignedShrink(allocator);
}
test PageAllocator {
const allocator = page_allocator;
try testAllocator(allocator);
try testAllocatorAligned(allocator);
if (!builtin.target.cpu.arch.isWasm()) {
try testAllocatorLargeAlignment(allocator);
try testAllocatorAlignedShrink(allocator);
}
if (builtin.os.tag == .windows) {
const slice = try allocator.alignedAlloc(u8, .fromByteUnits(page_size_min), 128);
slice[0] = 0x12;
slice[127] = 0x34;
allocator.free(slice);
}
{
var buf = try allocator.alloc(u8, pageSize() + 1);
defer allocator.free(buf);
buf = try allocator.realloc(buf, 1); // shrink past the page boundary
}
}
test ArenaAllocator {
var arena_allocator = ArenaAllocator.init(page_allocator);
defer arena_allocator.deinit();
const allocator = arena_allocator.allocator();
try testAllocator(allocator);
try testAllocatorAligned(allocator);
try testAllocatorLargeAlignment(allocator);
try testAllocatorAlignedShrink(allocator);
}
/// This one should not try alignments that exceed what C malloc can handle.
pub fn testAllocator(base_allocator: mem.Allocator) !void {
var validationAllocator = mem.validationWrap(base_allocator);
const allocator = validationAllocator.allocator();
var slice = try allocator.alloc(*i32, 100);
try testing.expect(slice.len == 100);
for (slice, 0..) |*item, i| {
item.* = try allocator.create(i32);
item.*.* = @as(i32, @intCast(i));
}
slice = try allocator.realloc(slice, 20000);
try testing.expect(slice.len == 20000);
for (slice[0..100], 0..) |item, i| {
try testing.expect(item.* == @as(i32, @intCast(i)));
allocator.destroy(item);
}
if (allocator.resize(slice, 50)) {
slice = slice[0..50];
if (allocator.resize(slice, 25)) {
slice = slice[0..25];
try testing.expect(allocator.resize(slice, 0));
slice = slice[0..0];
slice = try allocator.realloc(slice, 10);
try testing.expect(slice.len == 10);
}
}
allocator.free(slice);
// Zero-length allocation
const empty = try allocator.alloc(u8, 0);
allocator.free(empty);
// Allocation with zero-sized types
const zero_bit_ptr = try allocator.create(u0);
zero_bit_ptr.* = 0;
allocator.destroy(zero_bit_ptr);
const zero_len_array = try allocator.create([0]u64);
allocator.destroy(zero_len_array);
const oversize = try allocator.alignedAlloc(u32, null, 5);
try testing.expect(oversize.len >= 5);
for (oversize) |*item| {
item.* = 0xDEADBEEF;
}
allocator.free(oversize);
}
pub fn testAllocatorAligned(base_allocator: mem.Allocator) !void {
var validationAllocator = mem.validationWrap(base_allocator);
const allocator = validationAllocator.allocator();
// Test a few alignment values, smaller and bigger than the type's one
inline for ([_]Alignment{ .@"1", .@"2", .@"4", .@"8", .@"16", .@"32", .@"64" }) |alignment| {
// initial
var slice = try allocator.alignedAlloc(u8, alignment, 10);
try testing.expect(slice.len == 10);
// grow
slice = try allocator.realloc(slice, 100);
try testing.expect(slice.len == 100);
if (allocator.resize(slice, 10)) {
slice = slice[0..10];
}
try testing.expect(allocator.resize(slice, 0));
slice = slice[0..0];
// realloc from zero
slice = try allocator.realloc(slice, 100);
try testing.expect(slice.len == 100);
if (allocator.resize(slice, 10)) {
slice = slice[0..10];
}
try testing.expect(allocator.resize(slice, 0));
}
}
pub fn testAllocatorLargeAlignment(base_allocator: mem.Allocator) !void {
var validationAllocator = mem.validationWrap(base_allocator);
const allocator = validationAllocator.allocator();
const large_align: usize = page_size_min / 2;
var align_mask: usize = undefined;
align_mask = @shlWithOverflow(~@as(usize, 0), @as(Allocator.Log2Align, @ctz(large_align)))[0];
var slice = try allocator.alignedAlloc(u8, .fromByteUnits(large_align), 500);
try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
if (allocator.resize(slice, 100)) {
slice = slice[0..100];
}
slice = try allocator.realloc(slice, 5000);
try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
if (allocator.resize(slice, 10)) {
slice = slice[0..10];
}
slice = try allocator.realloc(slice, 20000);
try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
allocator.free(slice);
}
pub fn testAllocatorAlignedShrink(base_allocator: mem.Allocator) !void {
var validationAllocator = mem.validationWrap(base_allocator);
const allocator = validationAllocator.allocator();
var debug_buffer: [1000]u8 = undefined;
var fib = FixedBufferAllocator.init(&debug_buffer);
const debug_allocator = fib.allocator();
const alloc_size = pageSize() * 2 + 50;
var slice = try allocator.alignedAlloc(u8, .@"16", alloc_size);
defer allocator.free(slice);
var stuff_to_free = std.array_list.Managed([]align(16) u8).init(debug_allocator);
// On Windows, VirtualAlloc returns addresses aligned to a 64K boundary,
// which is 16 pages, hence the 32. This test may require to increase
// the size of the allocations feeding the `allocator` parameter if they
// fail, because of this high over-alignment we want to have.
while (@intFromPtr(slice.ptr) == mem.alignForward(usize, @intFromPtr(slice.ptr), pageSize() * 32)) {
try stuff_to_free.append(slice);
slice = try allocator.alignedAlloc(u8, .@"16", alloc_size);
}
while (stuff_to_free.pop()) |item| {
allocator.free(item);
}
slice[0] = 0x12;
slice[60] = 0x34;
slice = try allocator.reallocAdvanced(slice, alloc_size / 2, 0);
try testing.expect(slice[0] == 0x12);
try testing.expect(slice[60] == 0x34);
}
const page_size_min_default: ?usize = switch (builtin.os.tag) {
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
.x86_64 => 4 << 10,
.aarch64 => 16 << 10,
else => null,
},
.windows => switch (builtin.cpu.arch) {
// -- <https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200>
.x86, .x86_64 => 4 << 10,
// SuperH => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
// DEC Alpha => 8 << 10,
// Itanium => 8 << 10,
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
else => null,
},
.wasi => switch (builtin.cpu.arch) {
.wasm32, .wasm64 => 64 << 10,
else => null,
},
// https://github.com/tianocore/edk2/blob/b158dad150bf02879668f72ce306445250838201/MdePkg/Include/Uefi/UefiBaseType.h#L180-L187
.uefi => 4 << 10,
.freebsd => switch (builtin.cpu.arch) {
// FreeBSD/sys/*
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv32, .riscv64 => 4 << 10,
else => null,
},
.netbsd => switch (builtin.cpu.arch) {
// NetBSD/sys/arch/*
.alpha => 8 << 10,
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.hppa => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.sh, .sheb => 4 << 10,
.sparc => 4 << 10,
.sparc64 => 8 << 10,
.riscv32, .riscv64 => 4 << 10,
// Sun-2
.m68k => 2 << 10,
else => null,
},
.dragonfly => switch (builtin.cpu.arch) {
.x86, .x86_64 => 4 << 10,
else => null,
},
.openbsd => switch (builtin.cpu.arch) {
// OpenBSD/sys/arch/*
.alpha => 8 << 10,
.hppa => 4 << 10,
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
.m88k => 4 << 10,
.mips64, .mips64el => 4 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv64 => 4 << 10,
.sh, .sheb => 4 << 10,
.sparc64 => 8 << 10,
else => null,
},
.illumos => switch (builtin.cpu.arch) {
// src/uts/*/sys/machparam.h
.x86, .x86_64 => 4 << 10,
.sparc, .sparc64 => 8 << 10,
else => null,
},
.fuchsia => switch (builtin.cpu.arch) {
// fuchsia/kernel/arch/*/include/arch/defines.h
.x86_64 => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.riscv64 => 4 << 10,
else => null,
},
// https://github.com/SerenityOS/serenity/blob/62b938b798dc009605b5df8a71145942fc53808b/Kernel/API/POSIX/sys/limits.h#L11-L13
.serenity => 4 << 10,
.haiku => switch (builtin.cpu.arch) {
// haiku/headers/posix/arch/*/limits.h
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.m68k => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv64 => 4 << 10,
.sparc64 => 8 << 10,
.x86, .x86_64 => 4 << 10,
else => null,
},
.hurd => switch (builtin.cpu.arch) {
// gnumach/*/include/mach/*/vm_param.h
.x86, .x86_64 => 4 << 10,
.aarch64 => null,
else => null,
},
.plan9 => switch (builtin.cpu.arch) {
// 9front/sys/src/9/*/mem.h
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
.sparc => 4 << 10,
else => null,
},
.ps3 => switch (builtin.cpu.arch) {
// cell/SDK_doc/en/html/C_and_C++_standard_libraries/stdlib.html
.powerpc64 => 1 << 20, // 1 MiB
else => null,
},
.ps4 => switch (builtin.cpu.arch) {
// https://github.com/ps4dev/ps4sdk/blob/4df9d001b66ae4ec07d9a51b62d1e4c5e270eecc/include/machine/param.h#L95
.x86, .x86_64 => 4 << 10,
else => null,
},
.ps5 => switch (builtin.cpu.arch) {
// https://github.com/PS5Dev/PS5SDK/blob/a2e03a2a0231a3a3397fa6cd087a01ca6d04f273/include/machine/param.h#L95
.x86, .x86_64 => 16 << 10,
else => null,
},
.psp => switch (builtin.cpu.arch) {
// minimum block allocation by testing sceKernel
.mips, .mipsel => 1 << 8, // 256
else => null,
},
// system/lib/libc/musl/arch/emscripten/bits/limits.h
.emscripten => 64 << 10,
.linux => switch (builtin.cpu.arch) {
// Linux/arch/*/Kconfig
.alpha => 8 << 10,
.arc, .arceb => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.csky => 4 << 10,
.hexagon => 4 << 10,
.hppa => 4 << 10,
.loongarch32, .loongarch64 => 4 << 10,
.m68k => 4 << 10,
.microblaze, .microblazeel => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.or1k => 8 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv32, .riscv64 => 4 << 10,
.s390x => 4 << 10,
.sh, .sheb => 4 << 10,
.sparc => 4 << 10,
.sparc64 => 8 << 10,
.x86, .x86_64 => 4 << 10,
.xtensa, .xtensaeb => 4 << 10,
else => null,
},
.freestanding, .other => switch (builtin.cpu.arch) {
.wasm32, .wasm64 => 64 << 10,
.x86, .x86_64 => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
else => null,
},
else => null,
};
const page_size_max_default: ?usize = switch (builtin.os.tag) {
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
.x86_64 => 4 << 10,
.aarch64 => 16 << 10,
else => null,
},
.windows => switch (builtin.cpu.arch) {
// -- <https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200>
.x86, .x86_64 => 4 << 10,
// SuperH => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
// DEC Alpha => 8 << 10,
// Itanium => 8 << 10,
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
else => null,
},
.wasi => switch (builtin.cpu.arch) {
.wasm32, .wasm64 => 64 << 10,
else => null,
},
// https://github.com/tianocore/edk2/blob/b158dad150bf02879668f72ce306445250838201/MdePkg/Include/Uefi/UefiBaseType.h#L180-L187
.uefi => 4 << 10,
.freebsd => switch (builtin.cpu.arch) {
// FreeBSD/sys/*
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv32, .riscv64 => 4 << 10,
else => null,
},
.netbsd => switch (builtin.cpu.arch) {
// NetBSD/sys/arch/*
.alpha => 8 << 10,
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 64 << 10,
.hppa => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 16 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 16 << 10,
.sh, .sheb => 4 << 10,
.sparc => 8 << 10,
.sparc64 => 8 << 10,
.riscv32, .riscv64 => 4 << 10,
.m68k => 8 << 10,
else => null,
},
.dragonfly => switch (builtin.cpu.arch) {
.x86, .x86_64 => 4 << 10,
else => null,
},
.openbsd => switch (builtin.cpu.arch) {
// OpenBSD/sys/arch/*
.alpha => 8 << 10,
.hppa => 4 << 10,
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
.m88k => 4 << 10,
.mips64, .mips64el => 16 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv64 => 4 << 10,
.sh, .sheb => 4 << 10,
.sparc64 => 8 << 10,
else => null,
},
.illumos => switch (builtin.cpu.arch) {
// src/uts/*/sys/machparam.h
.x86, .x86_64 => 4 << 10,
.sparc, .sparc64 => 8 << 10,
else => null,
},
.fuchsia => switch (builtin.cpu.arch) {
// fuchsia/kernel/arch/*/include/arch/defines.h
.x86_64 => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.riscv64 => 4 << 10,
else => null,
},
// https://github.com/SerenityOS/serenity/blob/62b938b798dc009605b5df8a71145942fc53808b/Kernel/API/POSIX/sys/limits.h#L11-L13
.serenity => 4 << 10,
.haiku => switch (builtin.cpu.arch) {
// haiku/headers/posix/arch/*/limits.h
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 4 << 10,
.m68k => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
.riscv64 => 4 << 10,
.sparc64 => 8 << 10,
.x86, .x86_64 => 4 << 10,
else => null,
},
.hurd => switch (builtin.cpu.arch) {
// gnumach/*/include/mach/*/vm_param.h
.x86, .x86_64 => 4 << 10,
.aarch64 => null,
else => null,
},
.plan9 => switch (builtin.cpu.arch) {
// 9front/sys/src/9/*/mem.h
.x86, .x86_64 => 4 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 64 << 10,
.mips, .mipsel, .mips64, .mips64el => 16 << 10,
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
.sparc => 4 << 10,
else => null,
},
.ps3 => switch (builtin.cpu.arch) {
// cell/SDK_doc/en/html/C_and_C++_standard_libraries/stdlib.html
.powerpc64 => 1 << 20, // 1 MiB
else => null,
},
.ps4 => switch (builtin.cpu.arch) {
// https://github.com/ps4dev/ps4sdk/blob/4df9d001b66ae4ec07d9a51b62d1e4c5e270eecc/include/machine/param.h#L95
.x86, .x86_64 => 4 << 10,
else => null,
},
.ps5 => switch (builtin.cpu.arch) {
// https://github.com/PS5Dev/PS5SDK/blob/a2e03a2a0231a3a3397fa6cd087a01ca6d04f273/include/machine/param.h#L95
.x86, .x86_64 => 16 << 10,
else => null,
},
.psp => switch (builtin.cpu.arch) {
// minimum block allocation by testing sceKernel
.mips, .mipsel => 1 << 8, // 256
else => null,
},
// system/lib/libc/musl/arch/emscripten/bits/limits.h
.emscripten => 64 << 10,
.linux => switch (builtin.cpu.arch) {
// Linux/arch/*/Kconfig
.alpha => 8 << 10,
.arc, .arceb => 16 << 10,
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
.aarch64, .aarch64_be => 64 << 10,
.csky => 4 << 10,
.hexagon => 256 << 10,
.hppa => 64 << 10,
.loongarch32, .loongarch64 => 64 << 10,
.m68k => 8 << 10,
.microblaze, .microblazeel => 4 << 10,
.mips, .mipsel, .mips64, .mips64el => 64 << 10,
.or1k => 8 << 10,
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 256 << 10,
.riscv32, .riscv64 => 4 << 10,
.s390x => 4 << 10,
.sh, .sheb => 64 << 10,
.sparc => 4 << 10,
.sparc64 => 8 << 10,
.x86, .x86_64 => 4 << 10,
.xtensa, .xtensaeb => 4 << 10,
else => null,
},
.freestanding => switch (builtin.cpu.arch) {
.wasm32, .wasm64 => 64 << 10,
else => null,
},
else => null,
};
test {
_ = @import("heap/memory_pool.zig");
_ = ArenaAllocator;
_ = DebugAllocator(.{});
_ = SafeAllocator;
_ = FixedBufferAllocator;
_ = BufferFirstAllocator;
if (builtin.single_threaded) {
if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) {
_ = brk_allocator;
}
} else {
_ = smp_allocator;
}
}