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");
pub const Error = error{OutOfMemory};
pub const Log2Align = math.Log2Int(usize);
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = struct {
alloc: *const fn (ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8,
resize: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool,
free: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void,
};
pub fn noResize(
self: *anyopaque,
buf: []u8,
log2_buf_align: u8,
new_len: usize,
ret_addr: usize,
) bool {
_ = self;
_ = buf;
_ = log2_buf_align;
_ = new_len;
_ = ret_addr;
return false;
}
pub fn noFree(
self: *anyopaque,
buf: []u8,
log2_buf_align: u8,
ret_addr: usize,
) void {
_ = self;
_ = buf;
_ = log2_buf_align;
_ = ret_addr;
}
pub inline fn rawAlloc(self: Allocator, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
return self.vtable.alloc(self.ptr, len, ptr_align, ret_addr);
}
pub inline fn rawResize(self: Allocator, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool {
return self.vtable.resize(self.ptr, buf, log2_buf_align, new_len, ret_addr);
}
pub inline fn rawFree(self: Allocator, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
return self.vtable.free(self.ptr, buf, log2_buf_align, ret_addr);
}
pub fn create(self: Allocator, comptime T: type) Error!*T {
if (@sizeOf(T) == 0) return @intToPtr(*T, math.maxInt(usize));
const slice = try self.allocAdvancedWithRetAddr(T, null, 1, @returnAddress());
return &slice[0];
}
pub fn destroy(self: Allocator, ptr: anytype) void {
const info = @typeInfo(@TypeOf(ptr)).Pointer;
const T = info.child;
if (@sizeOf(T) == 0) return;
const non_const_ptr = @ptrCast([*]u8, @constCast(ptr));
self.rawFree(non_const_ptr[0..@sizeOf(T)], math.log2(info.alignment), @returnAddress());
}
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,
comptime optional_alignment: ?u29,
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,
comptime optional_alignment: ?u29,
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: ?u29, comptime sentinel: ?Elem) type {
if (sentinel) |s| {
return [:s]align(alignment orelse @alignOf(Elem)) Elem;
} else {
return []align(alignment orelse @alignOf(Elem)) Elem;
}
}
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,
comptime alignment: ?u29,
n: usize,
) Error![]align(alignment orelse @alignOf(T)) T {
return self.allocAdvancedWithRetAddr(T, alignment, n, @returnAddress());
}
pub fn allocAdvancedWithRetAddr(
self: Allocator,
comptime T: type,
comptime alignment: ?u29,
n: usize,
return_address: usize,
) Error![]align(alignment orelse @alignOf(T)) T {
const a = alignment orelse @alignOf(T);
comptime assert(a <= mem.page_size);
if (n == 0) {
const ptr = comptime std.mem.alignBackward(math.maxInt(usize), a);
return @intToPtr([*]align(a) T, ptr)[0..0];
}
const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
const byte_ptr = self.rawAlloc(byte_count, log2a(a), return_address) orelse return Error.OutOfMemory;
@memset(byte_ptr[0..byte_count], undefined);
const byte_slice = byte_ptr[0..byte_count];
return mem.bytesAsSlice(T, @alignCast(a, byte_slice));
}
pub fn resize(self: Allocator, old_mem: anytype, new_n: usize) bool {
const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
const T = Slice.child;
if (new_n == 0) {
self.free(old_mem);
return true;
}
if (old_mem.len == 0) {
return false;
}
const old_byte_slice = mem.sliceAsBytes(old_mem);
const new_byte_count = math.mul(usize, @sizeOf(T), new_n) catch return false;
return self.rawResize(old_byte_slice, log2a(Slice.alignment), new_byte_count, @returnAddress());
}
pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) t: {
const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
break :t Error![]align(Slice.alignment) Slice.child;
} {
return self.reallocAdvanced(old_mem, new_n, @returnAddress());
}
pub fn reallocAdvanced(
self: Allocator,
old_mem: anytype,
new_n: usize,
return_address: usize,
) t: {
const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
break :t Error![]align(Slice.alignment) Slice.child;
} {
const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
const T = Slice.child;
if (old_mem.len == 0) {
return self.allocAdvancedWithRetAddr(T, Slice.alignment, new_n, return_address);
}
if (new_n == 0) {
self.free(old_mem);
const ptr = comptime std.mem.alignBackward(math.maxInt(usize), Slice.alignment);
return @intToPtr([*]align(Slice.alignment) T, ptr)[0..0];
}
const old_byte_slice = mem.sliceAsBytes(old_mem);
const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory;
if (mem.isAligned(@ptrToInt(old_byte_slice.ptr), Slice.alignment)) {
if (self.rawResize(old_byte_slice, log2a(Slice.alignment), byte_count, return_address)) {
return mem.bytesAsSlice(T, @alignCast(Slice.alignment, old_byte_slice.ptr[0..byte_count]));
}
}
const new_mem = self.rawAlloc(byte_count, log2a(Slice.alignment), 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, log2a(Slice.alignment), return_address);
return mem.bytesAsSlice(T, @alignCast(Slice.alignment, new_mem[0..byte_count]));
}
pub fn free(self: Allocator, memory: anytype) void {
const Slice = @typeInfo(@TypeOf(memory)).Pointer;
const bytes = mem.sliceAsBytes(memory);
const bytes_len = bytes.len + if (Slice.sentinel != null) @sizeOf(Slice.child) else 0;
if (bytes_len == 0) return;
const non_const_ptr = @constCast(bytes.ptr);
@memset(non_const_ptr[0..bytes_len], undefined);
self.rawFree(non_const_ptr[0..bytes_len], log2a(Slice.alignment), @returnAddress());
}
pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) ![]T {
const new_buf = try allocator.alloc(T, m.len);
mem.copy(T, new_buf, m);
return new_buf;
}
pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) ![:0]T {
const new_buf = try allocator.alloc(T, m.len + 1);
mem.copy(T, new_buf, m);
new_buf[m.len] = 0;
return new_buf[0..m.len :0];
}
inline fn log2a(x: anytype) switch (@typeInfo(@TypeOf(x))) {
.Int => math.Log2Int(@TypeOf(x)),
.ComptimeInt => comptime_int,
else => @compileError("int please"),
} {
switch (@typeInfo(@TypeOf(x))) {
.Int => return math.log2_int(@TypeOf(x), x),
.ComptimeInt => return math.log2(x),
else => @compileError("bad"),
}
}