Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

heap.zig

File

lib/std/heap.zig

Code

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(@backingInt(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(@backingInt(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;
    }
}