zig/lib/std/heap.zig
Alex Rønne Petersen c8dd050305
std.heap.PageAllocator: hint mmaps in the same direction as stack growth
The old logic was fine for targets where the stack grows up (so, literally just
hppa), but problematic on targets where it grows down, because we could hint
that we wanted an allocation to happen in an area of the address space that the
kernel expects to be able to expand the stack into. The kernel is happy to
satisfy such a hint despite the obvious problems this leads to later down the
road.

Co-authored-by: rpkak <rpkak@noreply.codeberg.org>
2026-02-21 23:39:20 +01:00

1020 lines
38 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/arena_allocator.zig").ArenaAllocator;
pub const SmpAllocator = @import("heap/SmpAllocator.zig");
pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
pub const PageAllocator = @import("heap/PageAllocator.zig");
pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");
pub const BrkAllocator = @import("heap/BrkAllocator.zig");
pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config;
pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator;
pub const Check = enum { ok, leak };
/// Deprecated; to be removed after 0.14.0 is tagged.
pub const GeneralPurposeAllocatorConfig = DebugAllocatorConfig;
/// Deprecated; to be removed after 0.14.0 is tagged.
pub const GeneralPurposeAllocator = DebugAllocator;
/// 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");
/// Deprecated; use `memory_pool.Aligned`.
pub const MemoryPoolAligned = memory_pool.Aligned;
/// Deprecated; use `memory_pool.Extra`.
pub const MemoryPoolExtra = memory_pool.Extra;
/// Deprecated; use `memory_pool.Options`.
pub const MemoryPoolOptions = memory_pool.Options;
/// 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 1);
//`orelse 1` is a workaround for https://codeberg.org/ziglang/zig/issues/30842
//@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,
};
/// Returns a `StackFallbackAllocator` allocating using either a
/// `FixedBufferAllocator` on an array of size `size` and falling back to
/// `fallback_allocator` if that fails.
pub fn stackFallback(comptime size: usize, fallback_allocator: Allocator) StackFallbackAllocator(size) {
return StackFallbackAllocator(size){
.buffer = undefined,
.fallback_allocator = fallback_allocator,
.fixed_buffer_allocator = undefined,
};
}
/// An allocator that attempts to allocate using a
/// `FixedBufferAllocator` using an array of size `size`. If the
/// allocation fails, it will fall back to using
/// `fallback_allocator`. Easily created with `stackFallback`.
pub fn StackFallbackAllocator(comptime size: usize) type {
return struct {
const Self = @This();
buffer: [size]u8,
fallback_allocator: Allocator,
fixed_buffer_allocator: FixedBufferAllocator,
get_called: if (std.debug.runtime_safety) bool else void =
if (std.debug.runtime_safety) false else {},
/// This function both fetches a `Allocator` interface to this
/// allocator *and* resets the internal buffer allocator.
pub fn get(self: *Self) Allocator {
if (std.debug.runtime_safety) {
assert(!self.get_called); // `get` called multiple times; instead use `const allocator = stackFallback(N).get();`
self.get_called = true;
}
self.fixed_buffer_allocator = FixedBufferAllocator.init(self.buffer[0..]);
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
},
};
}
/// Unlike most std allocators `StackFallbackAllocator` modifies
/// its internal state before returning an implementation of
/// the`Allocator` interface and therefore also doesn't use
/// the usual `.allocator()` method.
pub const allocator = @compileError("use 'const allocator = stackFallback(N).get();' instead");
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: Alignment,
ra: usize,
) ?[*]u8 {
const self: *Self = @ptrCast(@alignCast(ctx));
return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
return self.fallback_allocator.rawAlloc(len, alignment, ra);
}
fn resize(
ctx: *anyopaque,
buf: []u8,
alignment: Alignment,
new_len: usize,
ra: usize,
) bool {
const self: *Self = @ptrCast(@alignCast(ctx));
if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
} else {
return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
}
}
fn remap(
context: *anyopaque,
memory: []u8,
alignment: Alignment,
new_len: usize,
return_address: usize,
) ?[*]u8 {
const self: *Self = @ptrCast(@alignCast(context));
if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
} else {
return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
}
}
fn free(
ctx: *anyopaque,
buf: []u8,
alignment: Alignment,
ra: usize,
) void {
const self: *Self = @ptrCast(@alignCast(ctx));
if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
} else {
return self.fallback_allocator.rawFree(buf, alignment, ra);
}
}
};
}
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 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);
}
test "StackFallbackAllocator" {
{
var stack_allocator = stackFallback(4096, std.testing.allocator);
try testAllocator(stack_allocator.get());
}
{
var stack_allocator = stackFallback(4096, std.testing.allocator);
try testAllocatorAligned(stack_allocator.get());
}
{
var stack_allocator = stackFallback(4096, std.testing.allocator);
try testAllocatorLargeAlignment(stack_allocator.get());
}
{
var stack_allocator = stackFallback(4096, std.testing.allocator);
try testAllocatorAlignedShrink(stack_allocator.get());
}
}
/// 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,
.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,
},
// 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,
.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,
},
// 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;
_ = GeneralPurposeAllocator;
_ = FixedBufferAllocator;
_ = ThreadSafeAllocator;
if (builtin.single_threaded) {
if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) {
_ = brk_allocator;
}
} else {
_ = smp_allocator;
}
}