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 SbrkAllocator = @import("heap/sbrk_allocator.zig").SbrkAllocator; pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig"); pub const WasmAllocator = @import("heap/WasmAllocator.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; /// TODO Utilize this on Windows. pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null; /// 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( .SystemBasicInformation, &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. /// /// For an allocator that directly calls `malloc`/`realloc`/`free`, with no padding /// or special handling, see `raw_c_allocator`. 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 => { // 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(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)); 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).*), } } }; /// Asserts that allocations have alignments which `malloc` can satisfy. This means that /// the requested alignment is no greater than `@min(@alignOf(std.c.max_align_t), size)`. /// /// This allocator is rarely appropriate to use. In general, prefer `c_allocator`, which /// does not have any special requirements of its input, but is still highly efficient for /// allocation requests which obey `malloc` alignment rules. pub const raw_c_allocator: Allocator = .{ .ptr = undefined, .vtable = &raw_c_allocator_vtable, }; const raw_c_allocator_vtable: Allocator.VTable = .{ .alloc = rawCAlloc, .resize = rawCResize, .remap = rawCRemap, .free = rawCFree, }; fn rawCAlloc( context: *anyopaque, len: usize, alignment: Alignment, return_address: usize, ) ?[*]u8 { _ = context; _ = return_address; // `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 check that `alignment <= len`. assert(alignment.toByteUnits() <= len); assert(Alignment.compare(alignment, .lte, .of(std.c.max_align_t))); return @ptrCast(c.malloc(len)); } fn rawCResize( context: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, return_address: usize, ) bool { _ = context; _ = memory; _ = alignment; _ = new_len; _ = return_address; return false; } fn rawCRemap( context: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, return_address: usize, ) ?[*]u8 { _ = context; _ = return_address; // See `rawCMalloc` for an explanation of this `assert` call. assert(alignment.toByteUnits() <= new_len); return @ptrCast(c.realloc(memory.ptr, new_len)); } fn rawCFree( context: *anyopaque, memory: []u8, alignment: Alignment, return_address: usize, ) void { _ = context; _ = alignment; _ = return_address; c.free(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 if (builtin.target.os.tag == .plan9) .{ .ptr = undefined, .vtable = &SbrkAllocator(std.os.plan9.sbrk).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. In the future, /// this will be the implementation automatically selected by /// `GeneralPurposeAllocator` when compiling in `ReleaseSmall` mode for wasm32 /// and wasm64 architectures. /// Until then, it is available here to play with. pub const wasm_allocator: Allocator = .{ .ptr = undefined, .vtable = &WasmAllocator.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 raw_c_allocator { if (builtin.link_libc) { try testAllocator(raw_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) { // -- .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) { // -- .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; _ = SbrkAllocator; if (builtin.target.cpu.arch.isWasm()) { _ = WasmAllocator; } if (!builtin.single_threaded) _ = smp_allocator; }