mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-31 05:15:29 +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% ```
532 lines
19 KiB
Zig
532 lines
19 KiB
Zig
//! The standard memory allocation interface.
|
|
|
|
const std = @import("../std.zig");
|
|
const assert = std.debug.assert;
|
|
const math = std.math;
|
|
const mem = std.mem;
|
|
const Allocator = @This();
|
|
const builtin = @import("builtin");
|
|
const Alignment = std.mem.Alignment;
|
|
|
|
pub const Error = error{OutOfMemory};
|
|
pub const Log2Align = math.Log2Int(usize);
|
|
|
|
/// The type erased pointer to the allocator implementation.
|
|
///
|
|
/// Any comparison of this field may result in illegal behavior, since it may
|
|
/// be set to `undefined` in cases where the allocator implementation does not
|
|
/// have any associated state.
|
|
ptr: *anyopaque,
|
|
vtable: *const VTable,
|
|
|
|
pub const VTable = struct {
|
|
/// Return a pointer to `len` bytes with specified `alignment`, or return
|
|
/// `null` indicating the allocation failed.
|
|
///
|
|
/// `new_len` must be greater than zero.
|
|
///
|
|
/// `ret_addr` is optionally provided as the first return address of the
|
|
/// allocation call stack. If the value is `0` it means no return address
|
|
/// has been provided.
|
|
alloc: *const fn (*anyopaque, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8,
|
|
|
|
/// Attempt to expand or shrink memory in place.
|
|
///
|
|
/// `memory.len` must equal the length requested from the most recent
|
|
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
|
|
/// equal the same value that was passed as the `alignment` parameter to
|
|
/// the original `alloc` call.
|
|
///
|
|
/// A result of `true` indicates the resize was successful and the
|
|
/// allocation now has the same address but a size of `new_len`. `false`
|
|
/// indicates the resize could not be completed without moving the
|
|
/// allocation to a different address.
|
|
///
|
|
/// `new_len` must be greater than zero.
|
|
///
|
|
/// `ret_addr` is optionally provided as the first return address of the
|
|
/// allocation call stack. If the value is `0` it means no return address
|
|
/// has been provided.
|
|
resize: *const fn (*anyopaque, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool,
|
|
|
|
/// Attempt to expand or shrink memory, allowing relocation.
|
|
///
|
|
/// `memory.len` must equal the length requested from the most recent
|
|
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
|
|
/// equal the same value that was passed as the `alignment` parameter to
|
|
/// the original `alloc` call.
|
|
///
|
|
/// A non-`null` return value indicates the resize was successful. The
|
|
/// allocation may have same address, or may have been relocated. In either
|
|
/// case, the allocation now has size of `new_len`. A `null` return value
|
|
/// indicates that the resize would be equivalent to allocating new memory,
|
|
/// copying the bytes from the old memory, and then freeing the old memory.
|
|
/// In such case, it is more efficient for the caller to perform the copy.
|
|
///
|
|
/// `new_len` must be greater than zero.
|
|
///
|
|
/// `ret_addr` is optionally provided as the first return address of the
|
|
/// allocation call stack. If the value is `0` it means no return address
|
|
/// has been provided.
|
|
remap: *const fn (*anyopaque, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8,
|
|
|
|
/// Free and invalidate a region of memory.
|
|
///
|
|
/// `memory.len` must equal the length requested from the most recent
|
|
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
|
|
/// equal the same value that was passed as the `alignment` parameter to
|
|
/// the original `alloc` call.
|
|
///
|
|
/// `ret_addr` is optionally provided as the first return address of the
|
|
/// allocation call stack. If the value is `0` it means no return address
|
|
/// has been provided.
|
|
free: *const fn (*anyopaque, memory: []u8, alignment: Alignment, ret_addr: usize) void,
|
|
};
|
|
|
|
pub fn noAlloc(
|
|
self: *anyopaque,
|
|
len: usize,
|
|
alignment: Alignment,
|
|
ret_addr: usize,
|
|
) ?[*]u8 {
|
|
_ = self;
|
|
_ = len;
|
|
_ = alignment;
|
|
_ = ret_addr;
|
|
return null;
|
|
}
|
|
|
|
pub fn noResize(
|
|
self: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
new_len: usize,
|
|
ret_addr: usize,
|
|
) bool {
|
|
_ = self;
|
|
_ = memory;
|
|
_ = alignment;
|
|
_ = new_len;
|
|
_ = ret_addr;
|
|
return false;
|
|
}
|
|
|
|
pub fn noRemap(
|
|
self: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
new_len: usize,
|
|
ret_addr: usize,
|
|
) ?[*]u8 {
|
|
_ = self;
|
|
_ = memory;
|
|
_ = alignment;
|
|
_ = new_len;
|
|
_ = ret_addr;
|
|
return null;
|
|
}
|
|
|
|
pub fn noFree(
|
|
self: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
ret_addr: usize,
|
|
) void {
|
|
_ = self;
|
|
_ = memory;
|
|
_ = alignment;
|
|
_ = ret_addr;
|
|
}
|
|
|
|
/// This function is not intended to be called except from within the
|
|
/// implementation of an `Allocator`.
|
|
pub inline fn rawAlloc(a: Allocator, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 {
|
|
return a.vtable.alloc(a.ptr, len, alignment, ret_addr);
|
|
}
|
|
|
|
/// This function is not intended to be called except from within the
|
|
/// implementation of an `Allocator`.
|
|
pub inline fn rawResize(a: Allocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool {
|
|
return a.vtable.resize(a.ptr, memory, alignment, new_len, ret_addr);
|
|
}
|
|
|
|
/// This function is not intended to be called except from within the
|
|
/// implementation of an `Allocator`.
|
|
pub inline fn rawRemap(a: Allocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
|
|
return a.vtable.remap(a.ptr, memory, alignment, new_len, ret_addr);
|
|
}
|
|
|
|
/// This function is not intended to be called except from within the
|
|
/// implementation of an `Allocator`.
|
|
pub inline fn rawFree(a: Allocator, memory: []u8, alignment: Alignment, ret_addr: usize) void {
|
|
return a.vtable.free(a.ptr, memory, alignment, ret_addr);
|
|
}
|
|
|
|
/// Returns a pointer to undefined memory.
|
|
/// Call `destroy` with the result to free the memory.
|
|
pub fn create(a: Allocator, comptime T: type) Error!*T {
|
|
if (@sizeOf(T) == 0) {
|
|
const ptr = comptime std.mem.alignBackward(usize, math.maxInt(usize), @alignOf(T));
|
|
return @ptrFromInt(ptr);
|
|
}
|
|
const ptr: *T = @ptrCast(try a.allocBytesWithAlignment(.of(T), @sizeOf(T), @returnAddress()));
|
|
return ptr;
|
|
}
|
|
|
|
/// `ptr` should be the return value of `create`, or otherwise
|
|
/// have the same address and alignment property.
|
|
pub fn destroy(self: Allocator, ptr: anytype) void {
|
|
const info = @typeInfo(@TypeOf(ptr)).pointer;
|
|
if (info.size != .one) @compileError("ptr must be a single item pointer");
|
|
const T = info.child;
|
|
if (@sizeOf(T) == 0) return;
|
|
const non_const_ptr = @as([*]u8, @ptrCast(@constCast(ptr)));
|
|
self.rawFree(
|
|
non_const_ptr[0..@sizeOf(T)],
|
|
.fromByteUnits(info.alignment orelse @alignOf(T)),
|
|
@returnAddress(),
|
|
);
|
|
}
|
|
|
|
/// Allocates an array of `n` items of type `T` and sets all the
|
|
/// items to `undefined`. Depending on the Allocator
|
|
/// implementation, it may be required to call `free` once the
|
|
/// memory is no longer needed, to avoid a resource leak. If the
|
|
/// `Allocator` implementation is unknown, then correct code will
|
|
/// call `free` when done.
|
|
///
|
|
/// For allocating a single item, see `create`.
|
|
pub fn alloc(self: Allocator, comptime T: type, n: usize) Error![]T {
|
|
return self.allocAdvancedWithRetAddr(T, null, n, @returnAddress());
|
|
}
|
|
|
|
pub fn allocWithOptions(
|
|
self: Allocator,
|
|
comptime Elem: type,
|
|
n: usize,
|
|
/// null means naturally aligned
|
|
comptime optional_alignment: ?Alignment,
|
|
comptime optional_sentinel: ?Elem,
|
|
) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
|
|
return self.allocWithOptionsRetAddr(Elem, n, optional_alignment, optional_sentinel, @returnAddress());
|
|
}
|
|
|
|
pub fn allocWithOptionsRetAddr(
|
|
self: Allocator,
|
|
comptime Elem: type,
|
|
n: usize,
|
|
/// null means naturally aligned
|
|
comptime optional_alignment: ?Alignment,
|
|
comptime optional_sentinel: ?Elem,
|
|
return_address: usize,
|
|
) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
|
|
if (optional_sentinel) |sentinel| {
|
|
const ptr = try self.allocAdvancedWithRetAddr(Elem, optional_alignment, n + 1, return_address);
|
|
ptr[n] = sentinel;
|
|
return ptr[0..n :sentinel];
|
|
} else {
|
|
return self.allocAdvancedWithRetAddr(Elem, optional_alignment, n, return_address);
|
|
}
|
|
}
|
|
|
|
fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?Alignment, comptime sentinel: ?Elem) type {
|
|
if (sentinel) |s| {
|
|
return [:s]align(if (alignment) |a| a.toByteUnits() else @alignOf(Elem)) Elem;
|
|
} else {
|
|
return []align(if (alignment) |a| a.toByteUnits() else @alignOf(Elem)) Elem;
|
|
}
|
|
}
|
|
|
|
/// Allocates an array of `n + 1` items of type `T` and sets the first `n`
|
|
/// items to `undefined` and the last item to `sentinel`. Depending on the
|
|
/// Allocator implementation, it may be required to call `free` once the
|
|
/// memory is no longer needed, to avoid a resource leak. If the
|
|
/// `Allocator` implementation is unknown, then correct code will
|
|
/// call `free` when done.
|
|
///
|
|
/// For allocating a single item, see `create`.
|
|
pub fn allocSentinel(
|
|
self: Allocator,
|
|
comptime Elem: type,
|
|
n: usize,
|
|
comptime sentinel: Elem,
|
|
) Error![:sentinel]Elem {
|
|
return self.allocWithOptionsRetAddr(Elem, n, null, sentinel, @returnAddress());
|
|
}
|
|
|
|
pub fn alignedAlloc(
|
|
self: Allocator,
|
|
comptime T: type,
|
|
/// null means naturally aligned
|
|
comptime alignment: ?Alignment,
|
|
n: usize,
|
|
) Error![]align(if (alignment) |a| a.toByteUnits() else @alignOf(T)) T {
|
|
return self.allocAdvancedWithRetAddr(T, alignment, n, @returnAddress());
|
|
}
|
|
|
|
pub inline fn allocAdvancedWithRetAddr(
|
|
self: Allocator,
|
|
comptime T: type,
|
|
/// null means naturally aligned
|
|
comptime alignment: ?Alignment,
|
|
n: usize,
|
|
return_address: usize,
|
|
) Error![]align(if (alignment) |a| a.toByteUnits() else @alignOf(T)) T {
|
|
const a: Alignment = alignment orelse comptime .of(T);
|
|
const ptr: [*]align(a.toByteUnits()) T = @ptrCast(try self.allocWithSizeAndAlignment(@sizeOf(T), a, n, return_address));
|
|
return ptr[0..n];
|
|
}
|
|
|
|
fn allocWithSizeAndAlignment(
|
|
self: Allocator,
|
|
comptime size: usize,
|
|
comptime alignment: Alignment,
|
|
n: usize,
|
|
return_address: usize,
|
|
) Error![*]align(alignment.toByteUnits()) u8 {
|
|
const byte_count = math.mul(usize, size, n) catch return error.OutOfMemory;
|
|
return self.allocBytesWithAlignment(alignment, byte_count, return_address);
|
|
}
|
|
|
|
fn allocBytesWithAlignment(
|
|
self: Allocator,
|
|
comptime alignment: Alignment,
|
|
byte_count: usize,
|
|
return_address: usize,
|
|
) Error![*]align(alignment.toByteUnits()) u8 {
|
|
if (byte_count == 0) {
|
|
const ptr = comptime alignment.backward(math.maxInt(usize));
|
|
return @as([*]align(alignment.toByteUnits()) u8, @ptrFromInt(ptr));
|
|
}
|
|
|
|
const byte_ptr = self.rawAlloc(byte_count, alignment, return_address) orelse return error.OutOfMemory;
|
|
@memset(byte_ptr[0..byte_count], undefined);
|
|
return @alignCast(byte_ptr);
|
|
}
|
|
|
|
/// Request to modify the size of an allocation.
|
|
///
|
|
/// It is guaranteed to not move the pointer, however the allocator
|
|
/// implementation may refuse the resize request by returning `false`.
|
|
///
|
|
/// `allocation` may be an empty slice, in which case `false` is returned,
|
|
/// unless `new_len` is also 0, in which case `true` is returned.
|
|
///
|
|
/// `new_len` may be zero, in which case the allocation is freed.
|
|
pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
|
|
const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
|
|
comptime assert(slice_info.size == .slice);
|
|
const T = slice_info.child;
|
|
if (new_len == 0) {
|
|
self.free(allocation);
|
|
return true;
|
|
}
|
|
if (allocation.len == 0) {
|
|
return false;
|
|
}
|
|
const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
|
|
// I would like to use saturating multiplication here, but LLVM cannot lower it
|
|
// on WebAssembly: https://github.com/ziglang/zig/issues/9660
|
|
//const new_len_bytes = new_len *| @sizeOf(T);
|
|
const new_len_bytes = math.mul(usize, @sizeOf(T), new_len) catch return false;
|
|
return self.rawResize(
|
|
old_memory,
|
|
.fromByteUnits(slice_info.alignment orelse @alignOf(T)),
|
|
new_len_bytes,
|
|
@returnAddress(),
|
|
);
|
|
}
|
|
|
|
/// Request to modify the size of an allocation, allowing relocation.
|
|
///
|
|
/// A non-`null` return value indicates the resize was successful. The
|
|
/// allocation may have same address, or may have been relocated. In either
|
|
/// case, the allocation now has size of `new_len`. A `null` return value
|
|
/// indicates that the resize would be equivalent to allocating new memory,
|
|
/// copying the bytes from the old memory, and then freeing the old memory.
|
|
/// In such case, it is more efficient for the caller to perform those
|
|
/// operations.
|
|
///
|
|
/// `allocation` may be an empty slice, in which case `null` is returned,
|
|
/// unless `new_len` is also 0, in which case `allocation` is returned.
|
|
///
|
|
/// `new_len` may be zero, in which case the allocation is freed.
|
|
///
|
|
/// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`.
|
|
pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allocation) {
|
|
const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
|
|
comptime assert(slice_info.size == .slice);
|
|
const T = slice_info.child;
|
|
|
|
if (new_len == 0) {
|
|
self.free(allocation);
|
|
return allocation[0..0];
|
|
}
|
|
if (allocation.len == 0) {
|
|
return null;
|
|
}
|
|
if (@sizeOf(T) == 0) {
|
|
var new_memory = allocation;
|
|
new_memory.len = new_len;
|
|
return new_memory;
|
|
}
|
|
const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
|
|
// I would like to use saturating multiplication here, but LLVM cannot lower it
|
|
// on WebAssembly: https://github.com/ziglang/zig/issues/9660
|
|
//const new_len_bytes = new_len *| @sizeOf(T);
|
|
const new_len_bytes = math.mul(usize, @sizeOf(T), new_len) catch return null;
|
|
const new_ptr = self.rawRemap(
|
|
old_memory,
|
|
.fromByteUnits(slice_info.alignment orelse @alignOf(T)),
|
|
new_len_bytes,
|
|
@returnAddress(),
|
|
) orelse return null;
|
|
return @ptrCast(@alignCast(new_ptr[0..new_len_bytes]));
|
|
}
|
|
|
|
/// This function requests a new size for an existing allocation, which
|
|
/// can be larger, smaller, or the same size as the old memory allocation.
|
|
/// The result is an array of `new_n` items of the same type as the existing
|
|
/// allocation.
|
|
///
|
|
/// If `new_n` is 0, this is the same as `free` and it always succeeds.
|
|
///
|
|
/// `old_mem` may have length zero, which makes a new allocation.
|
|
///
|
|
/// This function only fails on out-of-memory conditions, unlike:
|
|
/// * `remap` which returns `null` when the `Allocator` implementation cannot
|
|
/// do the realloc more efficiently than the caller
|
|
/// * `resize` which returns `false` when the `Allocator` implementation cannot
|
|
/// change the size without relocating the allocation.
|
|
pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(old_mem) {
|
|
return self.reallocAdvanced(old_mem, new_n, @returnAddress());
|
|
}
|
|
|
|
pub fn reallocAdvanced(
|
|
self: Allocator,
|
|
old_mem: anytype,
|
|
new_n: usize,
|
|
return_address: usize,
|
|
) Error!@TypeOf(old_mem) {
|
|
const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;
|
|
comptime assert(slice_info.size == .slice);
|
|
const T = slice_info.child;
|
|
if (old_mem.len == 0) {
|
|
return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.alignment), new_n, return_address);
|
|
}
|
|
if (new_n == 0) {
|
|
self.free(old_mem);
|
|
const alignment = slice_info.alignment orelse @alignOf(T);
|
|
const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment);
|
|
const ptr: *align(alignment) [0]T = @ptrFromInt(addr);
|
|
return ptr;
|
|
}
|
|
|
|
const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem)));
|
|
const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory;
|
|
// Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
|
|
if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.alignment orelse @alignOf(T)), byte_count, return_address)) |p| {
|
|
return @ptrCast(@alignCast(p[0..byte_count]));
|
|
}
|
|
|
|
const new_mem = self.rawAlloc(byte_count, .fromByteUnits(slice_info.alignment orelse @alignOf(T)), return_address) orelse
|
|
return error.OutOfMemory;
|
|
const copy_len = @min(byte_count, old_byte_slice.len);
|
|
@memcpy(new_mem[0..copy_len], old_byte_slice[0..copy_len]);
|
|
@memset(old_byte_slice, undefined);
|
|
self.rawFree(old_byte_slice, .fromByteUnits(slice_info.alignment orelse @alignOf(T)), return_address);
|
|
|
|
return @ptrCast(@alignCast(new_mem[0..byte_count]));
|
|
}
|
|
|
|
/// Free an array allocated with `alloc`.
|
|
/// If memory has length 0, free is a no-op.
|
|
/// To free a single item, see `destroy`.
|
|
pub fn free(self: Allocator, memory: anytype) void {
|
|
const slice_info = @typeInfo(@TypeOf(memory)).pointer;
|
|
comptime assert(slice_info.size == .slice);
|
|
const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
|
|
if (bytes.len == 0) return;
|
|
@memset(bytes, undefined);
|
|
self.rawFree(bytes, .fromByteUnits(slice_info.alignment orelse @alignOf(slice_info.child)), @returnAddress());
|
|
}
|
|
|
|
/// Copies `m` to newly allocated memory. Caller owns the memory.
|
|
pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
|
|
const new_buf = try allocator.alloc(T, m.len);
|
|
@memcpy(new_buf, m);
|
|
return new_buf;
|
|
}
|
|
|
|
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
|
|
pub fn dupeSentinel(
|
|
allocator: Allocator,
|
|
comptime T: type,
|
|
m: []const T,
|
|
comptime sentinel: T,
|
|
) Error![:sentinel]T {
|
|
const new_buf = try allocator.alloc(T, m.len + 1);
|
|
@memcpy(new_buf[0..m.len], m);
|
|
new_buf[m.len] = sentinel;
|
|
return new_buf[0..m.len :sentinel];
|
|
}
|
|
|
|
/// An allocator that always fails to allocate.
|
|
pub const failing: Allocator = .{
|
|
.ptr = undefined,
|
|
.vtable = &.{
|
|
.alloc = noAlloc,
|
|
.resize = unreachableResize,
|
|
.remap = unreachableRemap,
|
|
.free = unreachableFree,
|
|
},
|
|
};
|
|
|
|
fn unreachableResize(
|
|
self: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
new_len: usize,
|
|
ret_addr: usize,
|
|
) bool {
|
|
_ = self;
|
|
_ = memory;
|
|
_ = alignment;
|
|
_ = new_len;
|
|
_ = ret_addr;
|
|
unreachable;
|
|
}
|
|
|
|
fn unreachableRemap(
|
|
self: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
new_len: usize,
|
|
ret_addr: usize,
|
|
) ?[*]u8 {
|
|
_ = self;
|
|
_ = memory;
|
|
_ = alignment;
|
|
_ = new_len;
|
|
_ = ret_addr;
|
|
unreachable;
|
|
}
|
|
|
|
fn unreachableFree(
|
|
self: *anyopaque,
|
|
memory: []u8,
|
|
alignment: Alignment,
|
|
ret_addr: usize,
|
|
) void {
|
|
_ = self;
|
|
_ = memory;
|
|
_ = alignment;
|
|
_ = ret_addr;
|
|
unreachable;
|
|
}
|
|
|
|
test failing {
|
|
const f: Allocator = .failing;
|
|
try std.testing.expectError(error.OutOfMemory, f.alloc(u8, 123));
|
|
}
|