mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-30 12:56:37 +03:00
a9423234a6
Implements a thread-safe allocator with the following guarantees: * `deinit` reports all leaks and frees all backing memory. * All allocation mismatches result in either a panic or segmentation fault. * Allocations from other `SafeAllocator` instances cause a panic (if `Options.canary` differ). * Double frees and operation (resize, remap, and free) races panic or segmentation fault. Given the backing allocator does not reuse memory, it does not reuse memory either and * Most writes after free will segmentation fault or are eventually detected and panic. `std.heap.DebugAllocator` has been deprecated (I have also deprecated `std.heap.Check` since this was its last usage and returning a `usize` leak count is a much cleaner approach). - General Design Every allocation is trailed by an `AllocFooter` which contains metadata for the allocation and stack traces. It is protected by a checksum to catch corruption from allocation overwrites and report canary mismatches. An allocation's memory has a minimum alignment of `AllocFooter` so that the footer is at a fixed offset determined from the allocation size. An allocation's memory is stored either: * Inside linearly-filled buckets for small allocations. * Inside an allocation directly from the backing allocator. To track allocations, each thread maintains a table of backing allocations. The table may be modified by other threads in the case of a producer-consumer operation, so the table is a linked list only expanded by creating new segments. Each thread maintains a linked list of free entries, which may contain entries from other threads' tables. In the case of producer-consumer operations, acquire/release ordering is assumed to be provided externally. This is also assumed by all other thread-safe allocators that reuse memory as otherwise there would be data races on reuse of allocated memory. - Fuzz Tests Two fuzz tests have also been added for the allocator. They check that there is no memory reuse, that returned memory is writable, and that it is not overwritten. The multi-threaded fuzz test spawns a number of worker threads which are used for all the test runs. I have run these tests extensively under TSAN. - Performance Measurements Building the standard library tests with a RelaseSafe compiler build and `-Ddebug-allocator`: ``` Benchmark 1 (3 runs): ./master-out/bin/zig test --zig-lib-dir lib lib/std/std.zig -femit-bin=test --test-no-exec measurement mean ± σ min … max outliers delta wall_time 29.4s ± 157ms 29.2s … 29.5s 0 ( 0%) 0% peak_rss 2.24GB ± 3.49MB 2.23GB … 2.24GB 0 ( 0%) 0% cpu_cycles 143G ± 999M 142G … 144G 0 ( 0%) 0% instructions 268G ± 5.22M 268G … 268G 0 ( 0%) 0% cache_references 13.1G ± 88.8M 13.0G … 13.2G 0 ( 0%) 0% cache_misses 2.38G ± 30.7M 2.35G … 2.41G 0 ( 0%) 0% branch_misses 634M ± 6.22M 629M … 641M 0 ( 0%) 0% Benchmark 2 (3 runs): ./branch-out/bin/zig test --zig-lib-dir lib lib/std/std.zig -femit-bin=test --test-no-exec measurement mean ± σ min … max outliers delta wall_time 22.1s ± 88.6ms 22.0s … 22.2s 0 ( 0%) ⚡- 24.7% ± 1.0% peak_rss 1.11GB ± 799KB 1.11GB … 1.11GB 0 ( 0%) ⚡- 50.3% ± 0.3% cpu_cycles 136G ± 480M 136G … 137G 0 ( 0%) ⚡- 4.4% ± 1.2% instructions 273G ± 2.07M 273G … 273G 0 ( 0%) 💩+ 1.6% ± 0.0% cache_references 12.3G ± 71.3M 12.2G … 12.4G 0 ( 0%) ⚡- 6.0% ± 1.4% cache_misses 2.02G ± 11.5M 2.01G … 2.03G 0 ( 0%) ⚡- 14.9% ± 2.2% branch_misses 569M ± 2.65M 567M … 572M 0 ( 0%) ⚡- 10.2% ± 1.7% ```
909 lines
34 KiB
Zig
909 lines
34 KiB
Zig
const std = @import("std.zig");
|
|
const builtin = @import("builtin");
|
|
const root = @import("root");
|
|
const assert = std.debug.assert;
|
|
const testing = std.testing;
|
|
const mem = std.mem;
|
|
const c = std.c;
|
|
const Allocator = std.mem.Allocator;
|
|
const windows = std.os.windows;
|
|
const Alignment = std.mem.Alignment;
|
|
|
|
pub const ArenaAllocator = @import("heap/ArenaAllocator.zig");
|
|
pub const SmpAllocator = @import("heap/SmpAllocator.zig");
|
|
pub const SafeAllocator = @import("heap/SafeAllocator.zig");
|
|
pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
|
|
pub const BufferFirstAllocator = @import("heap/BufferFirstAllocator.zig");
|
|
pub const PageAllocator = @import("heap/PageAllocator.zig");
|
|
pub const WasmAllocator = if (builtin.single_threaded) BrkAllocator else @compileError("unimplemented");
|
|
pub const BrkAllocator = @import("heap/BrkAllocator.zig");
|
|
|
|
/// Deprecated; use `SafeAllocator.Options`.
|
|
pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config;
|
|
/// Deprecated; use `SafeAllocator`.
|
|
pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator;
|
|
/// Deprecated.
|
|
pub const Check = enum { ok, leak };
|
|
|
|
/// A memory pool that can allocate objects of a single type very quickly.
|
|
/// Use this when you need to allocate a lot of objects of the same type,
|
|
/// because it outperforms general purpose allocators.
|
|
/// Functions that potentially allocate memory accept an `Allocator` parameter.
|
|
pub fn MemoryPool(comptime Item: type) type {
|
|
return memory_pool.Extra(Item, .{ .alignment = null });
|
|
}
|
|
pub const memory_pool = @import("heap/memory_pool.zig");
|
|
|
|
/// comptime-known minimum page size of the target.
|
|
///
|
|
/// All pointers from `mmap` or `NtAllocateVirtualMemory` are aligned to at least
|
|
/// `page_size_min`, but their actual alignment may be bigger.
|
|
///
|
|
/// This value can be overridden via `std.options.page_size_min`.
|
|
///
|
|
/// On many systems, the actual page size can only be determined at runtime
|
|
/// with `pageSize`.
|
|
pub const page_size_min: usize = std.options.page_size_min orelse (page_size_min_default orelse @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min"));
|
|
/// comptime-known maximum page size of the target.
|
|
///
|
|
/// Targeting a system with a larger page size may require overriding
|
|
/// `std.options.page_size_max`, as well as providing a corresponding linker
|
|
/// option.
|
|
///
|
|
/// The actual page size can only be determined at runtime with `pageSize`.
|
|
pub const page_size_max: usize = std.options.page_size_max orelse (page_size_max_default orelse if (builtin.os.tag == .freestanding or builtin.os.tag == .other)
|
|
@compileError("freestanding/other page_size_max must provided with std.options.page_size_max")
|
|
else
|
|
@compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_max; populate std.options.page_size_max"));
|
|
|
|
/// If the page size is comptime-known, return value is comptime.
|
|
/// Otherwise, calls `std.options.queryPageSize` which by default queries the
|
|
/// host operating system at runtime.
|
|
pub inline fn pageSize() usize {
|
|
if (page_size_min == page_size_max) return page_size_min;
|
|
return std.options.queryPageSize();
|
|
}
|
|
|
|
test pageSize {
|
|
assert(std.math.isPowerOfTwo(pageSize()));
|
|
}
|
|
|
|
/// The default implementation of `std.options.queryPageSize`.
|
|
/// Asserts that the page size is within `page_size_min` and `page_size_max`
|
|
pub fn defaultQueryPageSize() usize {
|
|
const global = struct {
|
|
var cached_result: std.atomic.Value(usize) = .init(0);
|
|
};
|
|
var size = global.cached_result.load(.unordered);
|
|
if (size > 0) return size;
|
|
size = size: switch (builtin.os.tag) {
|
|
.linux => if (builtin.link_libc)
|
|
@max(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE)), 0)
|
|
else
|
|
std.os.linux.getauxval(std.elf.AT_PAGESZ),
|
|
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
|
|
const task_port = std.c.mach_task_self();
|
|
// mach_task_self may fail "if there are any resource failures or other errors".
|
|
if (task_port == std.c.TASK.NULL) break :size 0;
|
|
var info_count = std.c.TASK.VM.INFO_COUNT;
|
|
var vm_info: std.c.task_vm_info_data_t = undefined;
|
|
vm_info.page_size = 0;
|
|
_ = std.c.task_info(
|
|
task_port,
|
|
std.c.TASK.VM.INFO,
|
|
@as(std.c.task_info_t, @ptrCast(&vm_info)),
|
|
&info_count,
|
|
);
|
|
break :size @intCast(vm_info.page_size);
|
|
},
|
|
.windows => {
|
|
var sbi: windows.SYSTEM.BASIC_INFORMATION = undefined;
|
|
switch (windows.ntdll.NtQuerySystemInformation(
|
|
.Basic,
|
|
&sbi,
|
|
@sizeOf(windows.SYSTEM.BASIC_INFORMATION),
|
|
null,
|
|
)) {
|
|
.SUCCESS => break :size sbi.PageSize,
|
|
else => break :size 0,
|
|
}
|
|
},
|
|
else => if (builtin.link_libc)
|
|
@max(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE)), 0)
|
|
else if (builtin.os.tag == .freestanding or builtin.os.tag == .other)
|
|
@compileError("unsupported target: freestanding/other")
|
|
else
|
|
@compileError("pageSize on " ++ @tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " is not supported without linking libc, using the default implementation"),
|
|
};
|
|
if (size == 0) size = page_size_max;
|
|
|
|
assert(size >= page_size_min);
|
|
assert(size <= page_size_max);
|
|
global.cached_result.store(size, .unordered);
|
|
|
|
return size;
|
|
}
|
|
|
|
test defaultQueryPageSize {
|
|
if (builtin.cpu.arch.isWasm()) return error.SkipZigTest;
|
|
assert(std.math.isPowerOfTwo(defaultQueryPageSize()));
|
|
}
|
|
|
|
/// A wrapper around the C memory allocation API which supports the full `Allocator`
|
|
/// interface, including arbitrary alignment. Simple `malloc` calls are used when
|
|
/// possible, but large requested alignments may require larger buffers in order to
|
|
/// satisfy the request. As well as `malloc`, `realloc`, and `free`, the extension
|
|
/// functions `malloc_usable_size` and `posix_memalign` are used when available.
|
|
pub const c_allocator: Allocator = .{
|
|
.ptr = undefined,
|
|
.vtable = &c_allocator_impl.vtable,
|
|
};
|
|
const c_allocator_impl = struct {
|
|
comptime {
|
|
if (!builtin.link_libc) {
|
|
@compileError("C allocator is only available when linking against libc");
|
|
}
|
|
}
|
|
|
|
const vtable: Allocator.VTable = .{
|
|
.alloc = alloc,
|
|
.resize = resize,
|
|
.remap = remap,
|
|
.free = free,
|
|
};
|
|
|
|
const have_posix_memalign = switch (builtin.os.tag) {
|
|
.dragonfly,
|
|
.netbsd,
|
|
.freebsd,
|
|
.illumos,
|
|
.openbsd,
|
|
.linux,
|
|
.driverkit,
|
|
.ios,
|
|
.maccatalyst,
|
|
.macos,
|
|
.tvos,
|
|
.visionos,
|
|
.watchos,
|
|
.serenity,
|
|
=> true,
|
|
else => false,
|
|
};
|
|
|
|
fn allocStrat(need_align: Alignment) union(enum) {
|
|
raw,
|
|
posix_memalign: if (have_posix_memalign) void else noreturn,
|
|
manual_align: if (have_posix_memalign) noreturn else void,
|
|
} {
|
|
// If `malloc` guarantees `need_align`, always prefer a raw allocation.
|
|
if (Alignment.compare(need_align, .lte, .of(c.max_align_t))) {
|
|
return .raw;
|
|
}
|
|
// Use `posix_memalign` if available. Otherwise, we must manually align the allocation.
|
|
return if (have_posix_memalign) .posix_memalign else .manual_align;
|
|
}
|
|
|
|
/// If `allocStrat(a) == .manual_align`, an allocation looks like this:
|
|
///
|
|
/// unaligned_ptr hdr_ptr aligned_ptr
|
|
/// v v v
|
|
/// +---------------+--------+--------------+
|
|
/// | padding | header | usable bytes |
|
|
/// +---------------+--------+--------------+
|
|
///
|
|
/// * `unaligned_ptr` is the raw return value of `malloc`.
|
|
/// * `aligned_ptr` is computed by aligning `unaligned_ptr` forward; it is what `alloc` returns.
|
|
/// * `hdr_ptr` points to a pointer-sized header directly before the usable space. This header
|
|
/// contains the value `unaligned_ptr`, so that we can pass it to `free` later. This is
|
|
/// necessary because the width of the padding is unknown.
|
|
///
|
|
/// This function accepts `aligned_ptr` and offsets it backwards to return `hdr_ptr`.
|
|
fn manualAlignHeader(aligned_ptr: [*]u8) *[*]u8 {
|
|
return @ptrCast(@alignCast(aligned_ptr - @sizeOf(usize)));
|
|
}
|
|
|
|
fn alloc(
|
|
_: *anyopaque,
|
|
len: usize,
|
|
alignment: Alignment,
|
|
return_address: usize,
|
|
) ?[*]u8 {
|
|
_ = return_address;
|
|
assert(len > 0);
|
|
switch (allocStrat(alignment)) {
|
|
.raw => {
|
|
// `std.c.max_align_t` isn't the whole story, because if `len` is smaller than
|
|
// every C type with alignment `max_align_t`, the allocation can be less-aligned.
|
|
// The implementation need only guarantee that any type of length `len` would be
|
|
// suitably aligned.
|
|
//
|
|
// For instance, if `len == 8` and `alignment == .@"16"`, then `malloc` may not
|
|
// fulfil this request, because there is necessarily no C type with 8-byte size
|
|
// but 16-byte alignment.
|
|
//
|
|
// In theory, the resulting rule here would be target-specific, but in practice,
|
|
// the smallest type with an alignment of `max_align_t` has the same size (it's
|
|
// usually `c_longdouble`), so we can just extend the allocation size up to the
|
|
// alignment of `max_align_t` if necessary.
|
|
const actual_len = @max(len, @alignOf(std.c.max_align_t));
|
|
const ptr = c.malloc(actual_len) orelse return null;
|
|
assert(alignment.check(@intFromPtr(ptr)));
|
|
return @ptrCast(ptr);
|
|
},
|
|
.posix_memalign => {
|
|
// The posix_memalign only accepts alignment values that are a
|
|
// multiple of the pointer size
|
|
const effective_alignment = @max(alignment.toByteUnits(), @sizeOf(usize));
|
|
var aligned_ptr: ?*anyopaque = undefined;
|
|
if (c.posix_memalign(&aligned_ptr, effective_alignment, len) != 0) {
|
|
return null;
|
|
}
|
|
assert(alignment.check(@intFromPtr(aligned_ptr)));
|
|
return @ptrCast(aligned_ptr);
|
|
},
|
|
.manual_align => {
|
|
// Overallocate to account for alignment padding and store the original pointer
|
|
// returned by `malloc` before the aligned address.
|
|
const padded_len = len + @sizeOf(usize) + alignment.toByteUnits() - 1;
|
|
const unaligned_ptr: [*]u8 = @ptrCast(c.malloc(padded_len) orelse return null);
|
|
const unaligned_addr = @intFromPtr(unaligned_ptr);
|
|
const aligned_addr = alignment.forward(unaligned_addr + @sizeOf(usize));
|
|
const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr);
|
|
manualAlignHeader(aligned_ptr).* = unaligned_ptr;
|
|
return aligned_ptr;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn resize(
|
|
_: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
new_len: usize,
|
|
return_address: usize,
|
|
) bool {
|
|
_ = return_address;
|
|
assert(new_len > 0);
|
|
if (new_len <= memory.len) {
|
|
return true; // in-place shrink always works
|
|
}
|
|
const mallocSize = func: {
|
|
if (@TypeOf(c.malloc_size) != void) break :func c.malloc_size;
|
|
if (@TypeOf(c.malloc_usable_size) != void) break :func c.malloc_usable_size;
|
|
if (@TypeOf(c._msize) != void) break :func c._msize;
|
|
return false; // we don't know how much space is actually available
|
|
};
|
|
const usable_len: usize = switch (allocStrat(alignment)) {
|
|
.raw, .posix_memalign => mallocSize(memory.ptr),
|
|
.manual_align => usable_len: {
|
|
const unaligned_ptr = manualAlignHeader(memory.ptr).*;
|
|
const full_len = mallocSize(unaligned_ptr);
|
|
const padding = @intFromPtr(memory.ptr) - @intFromPtr(unaligned_ptr);
|
|
break :usable_len full_len - padding;
|
|
},
|
|
};
|
|
return new_len <= usable_len;
|
|
}
|
|
|
|
fn remap(
|
|
ctx: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
new_len: usize,
|
|
return_address: usize,
|
|
) ?[*]u8 {
|
|
assert(new_len > 0);
|
|
// Prefer resizing in-place if possible, since `realloc` could be expensive even if legal.
|
|
if (resize(ctx, memory, alignment, new_len, return_address)) {
|
|
return memory.ptr;
|
|
}
|
|
switch (allocStrat(alignment)) {
|
|
.raw => {
|
|
// `malloc` and friends guarantee the required alignment, so we can try `realloc`.
|
|
// C only needs to respect `max_align_t` up to the allocation size due to object
|
|
// alignment rules. If necessary, extend the allocation size.
|
|
const actual_len = @max(new_len, @alignOf(std.c.max_align_t));
|
|
const new_ptr = c.realloc(memory.ptr, actual_len) orelse return null;
|
|
assert(alignment.check(@intFromPtr(new_ptr)));
|
|
return @ptrCast(new_ptr);
|
|
},
|
|
.posix_memalign, .manual_align => {
|
|
// `realloc` would potentially return a new allocation which does not respect
|
|
// the original alignment, so we can't do anything more.
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn free(
|
|
_: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
return_address: usize,
|
|
) void {
|
|
_ = return_address;
|
|
switch (allocStrat(alignment)) {
|
|
.raw, .posix_memalign => c.free(memory.ptr),
|
|
.manual_align => c.free(manualAlignHeader(memory.ptr).*),
|
|
}
|
|
}
|
|
};
|
|
|
|
/// On operating systems that support memory mapping, this allocator makes a
|
|
/// syscall directly for every allocation and free.
|
|
///
|
|
/// Otherwise, it falls back to the preferred singleton for the target.
|
|
///
|
|
/// Thread-safe.
|
|
pub const page_allocator: Allocator = if (@hasDecl(root, "os") and
|
|
@hasDecl(root.os, "heap") and
|
|
@hasDecl(root.os.heap, "page_allocator"))
|
|
root.os.heap.page_allocator
|
|
else if (builtin.target.cpu.arch.isWasm()) .{
|
|
.ptr = undefined,
|
|
.vtable = &WasmAllocator.vtable,
|
|
} else .{
|
|
.ptr = undefined,
|
|
.vtable = &PageAllocator.vtable,
|
|
};
|
|
|
|
pub const smp_allocator: Allocator = .{
|
|
.ptr = undefined,
|
|
.vtable = &SmpAllocator.vtable,
|
|
};
|
|
|
|
/// This allocator is fast, small, and specific to WebAssembly.
|
|
pub const wasm_allocator: Allocator = .{
|
|
.ptr = undefined,
|
|
.vtable = &WasmAllocator.vtable,
|
|
};
|
|
|
|
/// Supports single-threaded WebAssembly and Linux.
|
|
pub const brk_allocator: Allocator = .{
|
|
.ptr = undefined,
|
|
.vtable = &BrkAllocator.vtable,
|
|
};
|
|
|
|
test c_allocator {
|
|
if (builtin.link_libc) {
|
|
try testAllocator(c_allocator);
|
|
try testAllocatorAligned(c_allocator);
|
|
try testAllocatorLargeAlignment(c_allocator);
|
|
try testAllocatorAlignedShrink(c_allocator);
|
|
}
|
|
}
|
|
|
|
test smp_allocator {
|
|
if (builtin.single_threaded) return;
|
|
try testAllocator(smp_allocator);
|
|
try testAllocatorAligned(smp_allocator);
|
|
try testAllocatorLargeAlignment(smp_allocator);
|
|
try testAllocatorAlignedShrink(smp_allocator);
|
|
}
|
|
|
|
test SafeAllocator {
|
|
var instance: SafeAllocator = .init(page_allocator, .{});
|
|
defer _ = instance.deinit();
|
|
const allocator = instance.allocator();
|
|
|
|
try testAllocator(allocator);
|
|
try testAllocatorAligned(allocator);
|
|
try testAllocatorLargeAlignment(allocator);
|
|
try testAllocatorAlignedShrink(allocator);
|
|
}
|
|
|
|
test PageAllocator {
|
|
const allocator = page_allocator;
|
|
try testAllocator(allocator);
|
|
try testAllocatorAligned(allocator);
|
|
if (!builtin.target.cpu.arch.isWasm()) {
|
|
try testAllocatorLargeAlignment(allocator);
|
|
try testAllocatorAlignedShrink(allocator);
|
|
}
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
const slice = try allocator.alignedAlloc(u8, .fromByteUnits(page_size_min), 128);
|
|
slice[0] = 0x12;
|
|
slice[127] = 0x34;
|
|
allocator.free(slice);
|
|
}
|
|
{
|
|
var buf = try allocator.alloc(u8, pageSize() + 1);
|
|
defer allocator.free(buf);
|
|
buf = try allocator.realloc(buf, 1); // shrink past the page boundary
|
|
}
|
|
}
|
|
|
|
test ArenaAllocator {
|
|
var arena_allocator = ArenaAllocator.init(page_allocator);
|
|
defer arena_allocator.deinit();
|
|
const allocator = arena_allocator.allocator();
|
|
|
|
try testAllocator(allocator);
|
|
try testAllocatorAligned(allocator);
|
|
try testAllocatorLargeAlignment(allocator);
|
|
try testAllocatorAlignedShrink(allocator);
|
|
}
|
|
|
|
/// This one should not try alignments that exceed what C malloc can handle.
|
|
pub fn testAllocator(base_allocator: mem.Allocator) !void {
|
|
var validationAllocator = mem.validationWrap(base_allocator);
|
|
const allocator = validationAllocator.allocator();
|
|
|
|
var slice = try allocator.alloc(*i32, 100);
|
|
try testing.expect(slice.len == 100);
|
|
for (slice, 0..) |*item, i| {
|
|
item.* = try allocator.create(i32);
|
|
item.*.* = @as(i32, @intCast(i));
|
|
}
|
|
|
|
slice = try allocator.realloc(slice, 20000);
|
|
try testing.expect(slice.len == 20000);
|
|
|
|
for (slice[0..100], 0..) |item, i| {
|
|
try testing.expect(item.* == @as(i32, @intCast(i)));
|
|
allocator.destroy(item);
|
|
}
|
|
|
|
if (allocator.resize(slice, 50)) {
|
|
slice = slice[0..50];
|
|
if (allocator.resize(slice, 25)) {
|
|
slice = slice[0..25];
|
|
try testing.expect(allocator.resize(slice, 0));
|
|
slice = slice[0..0];
|
|
slice = try allocator.realloc(slice, 10);
|
|
try testing.expect(slice.len == 10);
|
|
}
|
|
}
|
|
allocator.free(slice);
|
|
|
|
// Zero-length allocation
|
|
const empty = try allocator.alloc(u8, 0);
|
|
allocator.free(empty);
|
|
// Allocation with zero-sized types
|
|
const zero_bit_ptr = try allocator.create(u0);
|
|
zero_bit_ptr.* = 0;
|
|
allocator.destroy(zero_bit_ptr);
|
|
const zero_len_array = try allocator.create([0]u64);
|
|
allocator.destroy(zero_len_array);
|
|
|
|
const oversize = try allocator.alignedAlloc(u32, null, 5);
|
|
try testing.expect(oversize.len >= 5);
|
|
for (oversize) |*item| {
|
|
item.* = 0xDEADBEEF;
|
|
}
|
|
allocator.free(oversize);
|
|
}
|
|
|
|
pub fn testAllocatorAligned(base_allocator: mem.Allocator) !void {
|
|
var validationAllocator = mem.validationWrap(base_allocator);
|
|
const allocator = validationAllocator.allocator();
|
|
|
|
// Test a few alignment values, smaller and bigger than the type's one
|
|
inline for ([_]Alignment{ .@"1", .@"2", .@"4", .@"8", .@"16", .@"32", .@"64" }) |alignment| {
|
|
// initial
|
|
var slice = try allocator.alignedAlloc(u8, alignment, 10);
|
|
try testing.expect(slice.len == 10);
|
|
// grow
|
|
slice = try allocator.realloc(slice, 100);
|
|
try testing.expect(slice.len == 100);
|
|
if (allocator.resize(slice, 10)) {
|
|
slice = slice[0..10];
|
|
}
|
|
try testing.expect(allocator.resize(slice, 0));
|
|
slice = slice[0..0];
|
|
// realloc from zero
|
|
slice = try allocator.realloc(slice, 100);
|
|
try testing.expect(slice.len == 100);
|
|
if (allocator.resize(slice, 10)) {
|
|
slice = slice[0..10];
|
|
}
|
|
try testing.expect(allocator.resize(slice, 0));
|
|
}
|
|
}
|
|
|
|
pub fn testAllocatorLargeAlignment(base_allocator: mem.Allocator) !void {
|
|
var validationAllocator = mem.validationWrap(base_allocator);
|
|
const allocator = validationAllocator.allocator();
|
|
|
|
const large_align: usize = page_size_min / 2;
|
|
|
|
var align_mask: usize = undefined;
|
|
align_mask = @shlWithOverflow(~@as(usize, 0), @as(Allocator.Log2Align, @ctz(large_align)))[0];
|
|
|
|
var slice = try allocator.alignedAlloc(u8, .fromByteUnits(large_align), 500);
|
|
try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
|
|
|
|
if (allocator.resize(slice, 100)) {
|
|
slice = slice[0..100];
|
|
}
|
|
|
|
slice = try allocator.realloc(slice, 5000);
|
|
try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
|
|
|
|
if (allocator.resize(slice, 10)) {
|
|
slice = slice[0..10];
|
|
}
|
|
|
|
slice = try allocator.realloc(slice, 20000);
|
|
try testing.expect(@intFromPtr(slice.ptr) & align_mask == @intFromPtr(slice.ptr));
|
|
|
|
allocator.free(slice);
|
|
}
|
|
|
|
pub fn testAllocatorAlignedShrink(base_allocator: mem.Allocator) !void {
|
|
var validationAllocator = mem.validationWrap(base_allocator);
|
|
const allocator = validationAllocator.allocator();
|
|
|
|
var debug_buffer: [1000]u8 = undefined;
|
|
var fib = FixedBufferAllocator.init(&debug_buffer);
|
|
const debug_allocator = fib.allocator();
|
|
|
|
const alloc_size = pageSize() * 2 + 50;
|
|
var slice = try allocator.alignedAlloc(u8, .@"16", alloc_size);
|
|
defer allocator.free(slice);
|
|
|
|
var stuff_to_free = std.array_list.Managed([]align(16) u8).init(debug_allocator);
|
|
// On Windows, VirtualAlloc returns addresses aligned to a 64K boundary,
|
|
// which is 16 pages, hence the 32. This test may require to increase
|
|
// the size of the allocations feeding the `allocator` parameter if they
|
|
// fail, because of this high over-alignment we want to have.
|
|
while (@intFromPtr(slice.ptr) == mem.alignForward(usize, @intFromPtr(slice.ptr), pageSize() * 32)) {
|
|
try stuff_to_free.append(slice);
|
|
slice = try allocator.alignedAlloc(u8, .@"16", alloc_size);
|
|
}
|
|
while (stuff_to_free.pop()) |item| {
|
|
allocator.free(item);
|
|
}
|
|
slice[0] = 0x12;
|
|
slice[60] = 0x34;
|
|
|
|
slice = try allocator.reallocAdvanced(slice, alloc_size / 2, 0);
|
|
try testing.expect(slice[0] == 0x12);
|
|
try testing.expect(slice[60] == 0x34);
|
|
}
|
|
|
|
const page_size_min_default: ?usize = switch (builtin.os.tag) {
|
|
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
|
|
.x86_64 => 4 << 10,
|
|
.aarch64 => 16 << 10,
|
|
else => null,
|
|
},
|
|
.windows => switch (builtin.cpu.arch) {
|
|
// -- <https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200>
|
|
.x86, .x86_64 => 4 << 10,
|
|
// SuperH => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
|
|
// DEC Alpha => 8 << 10,
|
|
// Itanium => 8 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
|
|
else => null,
|
|
},
|
|
.wasi => switch (builtin.cpu.arch) {
|
|
.wasm32, .wasm64 => 64 << 10,
|
|
else => null,
|
|
},
|
|
// https://github.com/tianocore/edk2/blob/b158dad150bf02879668f72ce306445250838201/MdePkg/Include/Uefi/UefiBaseType.h#L180-L187
|
|
.uefi => 4 << 10,
|
|
.freebsd => switch (builtin.cpu.arch) {
|
|
// FreeBSD/sys/*
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv32, .riscv64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.netbsd => switch (builtin.cpu.arch) {
|
|
// NetBSD/sys/arch/*
|
|
.alpha => 8 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.hppa => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.sh, .sheb => 4 << 10,
|
|
.sparc => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
.riscv32, .riscv64 => 4 << 10,
|
|
// Sun-2
|
|
.m68k => 2 << 10,
|
|
else => null,
|
|
},
|
|
.dragonfly => switch (builtin.cpu.arch) {
|
|
.x86, .x86_64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.openbsd => switch (builtin.cpu.arch) {
|
|
// OpenBSD/sys/arch/*
|
|
.alpha => 8 << 10,
|
|
.hppa => 4 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
|
|
.m88k => 4 << 10,
|
|
.mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv64 => 4 << 10,
|
|
.sh, .sheb => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
else => null,
|
|
},
|
|
.illumos => switch (builtin.cpu.arch) {
|
|
// src/uts/*/sys/machparam.h
|
|
.x86, .x86_64 => 4 << 10,
|
|
.sparc, .sparc64 => 8 << 10,
|
|
else => null,
|
|
},
|
|
.fuchsia => switch (builtin.cpu.arch) {
|
|
// fuchsia/kernel/arch/*/include/arch/defines.h
|
|
.x86_64 => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.riscv64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
// https://github.com/SerenityOS/serenity/blob/62b938b798dc009605b5df8a71145942fc53808b/Kernel/API/POSIX/sys/limits.h#L11-L13
|
|
.serenity => 4 << 10,
|
|
.haiku => switch (builtin.cpu.arch) {
|
|
// haiku/headers/posix/arch/*/limits.h
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.m68k => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv64 => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.hurd => switch (builtin.cpu.arch) {
|
|
// gnumach/*/include/mach/*/vm_param.h
|
|
.x86, .x86_64 => 4 << 10,
|
|
.aarch64 => null,
|
|
else => null,
|
|
},
|
|
.plan9 => switch (builtin.cpu.arch) {
|
|
// 9front/sys/src/9/*/mem.h
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
|
|
.sparc => 4 << 10,
|
|
else => null,
|
|
},
|
|
.ps3 => switch (builtin.cpu.arch) {
|
|
// cell/SDK_doc/en/html/C_and_C++_standard_libraries/stdlib.html
|
|
.powerpc64 => 1 << 20, // 1 MiB
|
|
else => null,
|
|
},
|
|
.ps4 => switch (builtin.cpu.arch) {
|
|
// https://github.com/ps4dev/ps4sdk/blob/4df9d001b66ae4ec07d9a51b62d1e4c5e270eecc/include/machine/param.h#L95
|
|
.x86, .x86_64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.ps5 => switch (builtin.cpu.arch) {
|
|
// https://github.com/PS5Dev/PS5SDK/blob/a2e03a2a0231a3a3397fa6cd087a01ca6d04f273/include/machine/param.h#L95
|
|
.x86, .x86_64 => 16 << 10,
|
|
else => null,
|
|
},
|
|
.psp => switch (builtin.cpu.arch) {
|
|
// minimum block allocation by testing sceKernel
|
|
.mips, .mipsel => 1 << 8, // 256
|
|
else => null,
|
|
},
|
|
// system/lib/libc/musl/arch/emscripten/bits/limits.h
|
|
.emscripten => 64 << 10,
|
|
.linux => switch (builtin.cpu.arch) {
|
|
// Linux/arch/*/Kconfig
|
|
.alpha => 8 << 10,
|
|
.arc, .arceb => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.csky => 4 << 10,
|
|
.hexagon => 4 << 10,
|
|
.hppa => 4 << 10,
|
|
.loongarch32, .loongarch64 => 4 << 10,
|
|
.m68k => 4 << 10,
|
|
.microblaze, .microblazeel => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.or1k => 8 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv32, .riscv64 => 4 << 10,
|
|
.s390x => 4 << 10,
|
|
.sh, .sheb => 4 << 10,
|
|
.sparc => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.xtensa, .xtensaeb => 4 << 10,
|
|
else => null,
|
|
},
|
|
.freestanding, .other => switch (builtin.cpu.arch) {
|
|
.wasm32, .wasm64 => 64 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
else => null,
|
|
},
|
|
else => null,
|
|
};
|
|
|
|
const page_size_max_default: ?usize = switch (builtin.os.tag) {
|
|
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
|
|
.x86_64 => 4 << 10,
|
|
.aarch64 => 16 << 10,
|
|
else => null,
|
|
},
|
|
.windows => switch (builtin.cpu.arch) {
|
|
// -- <https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200>
|
|
.x86, .x86_64 => 4 << 10,
|
|
// SuperH => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
|
|
// DEC Alpha => 8 << 10,
|
|
// Itanium => 8 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
|
|
else => null,
|
|
},
|
|
.wasi => switch (builtin.cpu.arch) {
|
|
.wasm32, .wasm64 => 64 << 10,
|
|
else => null,
|
|
},
|
|
// https://github.com/tianocore/edk2/blob/b158dad150bf02879668f72ce306445250838201/MdePkg/Include/Uefi/UefiBaseType.h#L180-L187
|
|
.uefi => 4 << 10,
|
|
.freebsd => switch (builtin.cpu.arch) {
|
|
// FreeBSD/sys/*
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv32, .riscv64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.netbsd => switch (builtin.cpu.arch) {
|
|
// NetBSD/sys/arch/*
|
|
.alpha => 8 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 64 << 10,
|
|
.hppa => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 16 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 16 << 10,
|
|
.sh, .sheb => 4 << 10,
|
|
.sparc => 8 << 10,
|
|
.sparc64 => 8 << 10,
|
|
.riscv32, .riscv64 => 4 << 10,
|
|
.m68k => 8 << 10,
|
|
else => null,
|
|
},
|
|
.dragonfly => switch (builtin.cpu.arch) {
|
|
.x86, .x86_64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.openbsd => switch (builtin.cpu.arch) {
|
|
// OpenBSD/sys/arch/*
|
|
.alpha => 8 << 10,
|
|
.hppa => 4 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb, .aarch64, .aarch64_be => 4 << 10,
|
|
.m88k => 4 << 10,
|
|
.mips64, .mips64el => 16 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv64 => 4 << 10,
|
|
.sh, .sheb => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
else => null,
|
|
},
|
|
.illumos => switch (builtin.cpu.arch) {
|
|
// src/uts/*/sys/machparam.h
|
|
.x86, .x86_64 => 4 << 10,
|
|
.sparc, .sparc64 => 8 << 10,
|
|
else => null,
|
|
},
|
|
.fuchsia => switch (builtin.cpu.arch) {
|
|
// fuchsia/kernel/arch/*/include/arch/defines.h
|
|
.x86_64 => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.riscv64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
// https://github.com/SerenityOS/serenity/blob/62b938b798dc009605b5df8a71145942fc53808b/Kernel/API/POSIX/sys/limits.h#L11-L13
|
|
.serenity => 4 << 10,
|
|
.haiku => switch (builtin.cpu.arch) {
|
|
// haiku/headers/posix/arch/*/limits.h
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 4 << 10,
|
|
.m68k => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 4 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 4 << 10,
|
|
.riscv64 => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.hurd => switch (builtin.cpu.arch) {
|
|
// gnumach/*/include/mach/*/vm_param.h
|
|
.x86, .x86_64 => 4 << 10,
|
|
.aarch64 => null,
|
|
else => null,
|
|
},
|
|
.plan9 => switch (builtin.cpu.arch) {
|
|
// 9front/sys/src/9/*/mem.h
|
|
.x86, .x86_64 => 4 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 64 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 16 << 10,
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => 4 << 10,
|
|
.sparc => 4 << 10,
|
|
else => null,
|
|
},
|
|
.ps3 => switch (builtin.cpu.arch) {
|
|
// cell/SDK_doc/en/html/C_and_C++_standard_libraries/stdlib.html
|
|
.powerpc64 => 1 << 20, // 1 MiB
|
|
else => null,
|
|
},
|
|
.ps4 => switch (builtin.cpu.arch) {
|
|
// https://github.com/ps4dev/ps4sdk/blob/4df9d001b66ae4ec07d9a51b62d1e4c5e270eecc/include/machine/param.h#L95
|
|
.x86, .x86_64 => 4 << 10,
|
|
else => null,
|
|
},
|
|
.ps5 => switch (builtin.cpu.arch) {
|
|
// https://github.com/PS5Dev/PS5SDK/blob/a2e03a2a0231a3a3397fa6cd087a01ca6d04f273/include/machine/param.h#L95
|
|
.x86, .x86_64 => 16 << 10,
|
|
else => null,
|
|
},
|
|
.psp => switch (builtin.cpu.arch) {
|
|
// minimum block allocation by testing sceKernel
|
|
.mips, .mipsel => 1 << 8, // 256
|
|
else => null,
|
|
},
|
|
// system/lib/libc/musl/arch/emscripten/bits/limits.h
|
|
.emscripten => 64 << 10,
|
|
.linux => switch (builtin.cpu.arch) {
|
|
// Linux/arch/*/Kconfig
|
|
.alpha => 8 << 10,
|
|
.arc, .arceb => 16 << 10,
|
|
.thumb, .thumbeb, .arm, .armeb => 4 << 10,
|
|
.aarch64, .aarch64_be => 64 << 10,
|
|
.csky => 4 << 10,
|
|
.hexagon => 256 << 10,
|
|
.hppa => 64 << 10,
|
|
.loongarch32, .loongarch64 => 64 << 10,
|
|
.m68k => 8 << 10,
|
|
.microblaze, .microblazeel => 4 << 10,
|
|
.mips, .mipsel, .mips64, .mips64el => 64 << 10,
|
|
.or1k => 8 << 10,
|
|
.powerpc, .powerpc64, .powerpc64le, .powerpcle => 256 << 10,
|
|
.riscv32, .riscv64 => 4 << 10,
|
|
.s390x => 4 << 10,
|
|
.sh, .sheb => 64 << 10,
|
|
.sparc => 4 << 10,
|
|
.sparc64 => 8 << 10,
|
|
.x86, .x86_64 => 4 << 10,
|
|
.xtensa, .xtensaeb => 4 << 10,
|
|
else => null,
|
|
},
|
|
.freestanding => switch (builtin.cpu.arch) {
|
|
.wasm32, .wasm64 => 64 << 10,
|
|
else => null,
|
|
},
|
|
else => null,
|
|
};
|
|
|
|
test {
|
|
_ = @import("heap/memory_pool.zig");
|
|
_ = ArenaAllocator;
|
|
_ = DebugAllocator(.{});
|
|
_ = SafeAllocator;
|
|
_ = FixedBufferAllocator;
|
|
_ = BufferFirstAllocator;
|
|
if (builtin.single_threaded) {
|
|
if (builtin.cpu.arch.isWasm() or (builtin.os.tag == .linux and !builtin.link_libc)) {
|
|
_ = brk_allocator;
|
|
}
|
|
} else {
|
|
_ = smp_allocator;
|
|
}
|
|
}
|