Skip to content

Commit

Permalink
new allocator interface
Browse files Browse the repository at this point in the history
  • Loading branch information
marler8997 committed Jun 1, 2020
1 parent c6764fd commit 1ead3b4
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 386 deletions.
513 changes: 215 additions & 298 deletions lib/std/heap.zig

Large diffs are not rendered by default.

20 changes: 4 additions & 16 deletions lib/std/heap/arena_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ pub const ArenaAllocator = struct {
pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator {
return .{
.allocator = Allocator{
.reallocFn = realloc,
.shrinkFn = shrink,
.allocFn = alloc,
.freeFn = free,
.resizeFn = Allocator.noResize,
},
.child_allocator = child_allocator,
.state = self,
Expand Down Expand Up @@ -81,18 +82,5 @@ pub const ArenaAllocator = struct {
}
}

fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
if (new_size <= old_mem.len and new_align <= new_size) {
// We can't do anything with the memory, so tell the client to keep it.
return error.OutOfMemory;
} else {
const result = try alloc(allocator, new_size, new_align);
@memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len));
return result;
}
}

fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
return old_mem[0..new_size];
}
fn free(allocator: *Allocator, buf: []u8) void { }
};
34 changes: 19 additions & 15 deletions lib/std/heap/logging_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,19 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self {
return Self{
.allocator = Allocator{
.reallocFn = realloc,
.shrinkFn = shrink,
.allocFn = alloc,
.freeFn = free,
.resizeFn = resize,
},
.parent_allocator = parent_allocator,
.out_stream = out_stream,
};
}

fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
fn alloc(allocator: *std.mem.Allocator, len: usize, alignment: u29) error{OutOfMemory}![]u8 {
const self = @fieldParentPtr(Self, "allocator", allocator);
if (old_mem.len == 0) {
self.out_stream.print("allocation of {} ", .{new_size}) catch {};
} else {
self.out_stream.print("resize from {} to {} ", .{ old_mem.len, new_size }) catch {};
}
const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align);
self.out_stream.print("allocation of {} ", .{len}) catch {};
const result = self.parent_allocator.allocMem(len, alignment);
if (result) |buff| {
self.out_stream.print("success!\n", .{}) catch {};
} else |err| {
Expand All @@ -39,13 +36,20 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type {
return result;
}

fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
fn free(allocator: *std.mem.Allocator, buf: []u8) void {
const self = @fieldParentPtr(Self, "allocator", allocator);
self.parent_allocator.freeMem(buf);
self.out_stream.print("free of {} bytes success!\n", .{buf.len}) catch {};
}

fn resize(allocator: *std.mem.Allocator, buf: []u8, new_len: usize) error{OutOfMemory}!void {
const self = @fieldParentPtr(Self, "allocator", allocator);
const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align);
if (new_size == 0) {
self.out_stream.print("free of {} bytes success!\n", .{old_mem.len}) catch {};
} else {
self.out_stream.print("shrink from {} bytes to {} bytes success!\n", .{ old_mem.len, new_size }) catch {};
self.out_stream.print("resize from {} to {} ", .{ buf.len, new_len }) catch {};
const result = self.parent_allocator.resizeMem(buf, new_len);
if (result) |buff| {
self.out_stream.print("success!\n", .{}) catch {};
} else |err| {
self.out_stream.print("failure!\n", .{}) catch {};
}
return result;
}
Expand Down
100 changes: 84 additions & 16 deletions lib/std/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,35 @@ pub const page_size = switch (builtin.arch) {
pub const Allocator = struct {
pub const Error = error{OutOfMemory};

/// Allocate memory.
allocFn: fn (self: *Allocator, len: usize, alignment: u29) Error![]u8,

/// Free memory returned by allocFn, this function must succeed.
freeFn: fn (self: *Allocator, buf: []u8) void,

/// Resizes memory in-place returned by allocFn. This function is optional.
resizeFn: fn (self: *Allocator, buf: []u8, new_len: usize) Error!void,

/// call allocFn with the given allocator
pub fn allocMem(self: *Allocator, new_len: usize, alignment: u29) Error![]u8 {
return self.allocFn(self, new_len, alignment);
}

/// call freeFn with the given allocator
pub fn freeMem(self: *Allocator, buf: []u8) void {
self.freeFn(self, buf);
}

/// call allocFn with the given allocator
pub fn resizeMem(self: *Allocator, buf: []u8, new_len: usize) Error!void {
return self.resizeFn(self, buf, new_len);
}

/// Set to resizeFn if in-place resize is not supported.
pub fn noResize(self: *Allocator, buf: []u8, new_len: usize) Error!void {
return Error.OutOfMemory;
}

/// Realloc is used to modify the size or alignment of an existing allocation,
/// as well as to provide the allocator with an opportunity to move an allocation
/// to a better location.
Expand All @@ -24,7 +53,7 @@ pub const Allocator = struct {
/// When the size/alignment is less than or equal to the previous allocation,
/// this function returns `error.OutOfMemory` when the allocator decides the client
/// would be better off keeping the extra alignment/size. Clients will call
/// `shrinkFn` when they require the allocator to track a new alignment/size,
/// `shrinkMem` when they require the allocator to track a new alignment/size,
/// and so this function should only return success when the allocator considers
/// the reallocation desirable from the allocator's perspective.
/// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
Expand All @@ -37,16 +66,16 @@ pub const Allocator = struct {
/// as `old_mem` was when `reallocFn` is called. The bytes of
/// `return_value[old_mem.len..]` have undefined values.
/// The returned slice must have its pointer aligned at least to `new_alignment` bytes.
reallocFn: fn (
fn reallocMem(
self: *Allocator,
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
/// `reallocFn` or `shrinkMem`.
/// If `old_mem.len == 0` then this is a new allocation and `new_byte_count`
/// is guaranteed to be >= 1.
old_mem: []u8,
/// If `old_mem.len == 0` then this is `undefined`, otherwise:
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
/// `reallocFn` or `shrinkMem`.
/// Guaranteed to be >= 1.
/// Guaranteed to be a power of 2.
old_alignment: u29,
Expand All @@ -57,23 +86,63 @@ pub const Allocator = struct {
/// Guaranteed to be a power of 2.
/// Returned slice's pointer must have this alignment.
new_alignment: u29,
) Error![]u8,
) Error![]u8 {
if (old_mem.len == 0)
return self.allocMem(new_byte_count, new_alignment);
if (new_byte_count == 0) {
self.freeMem(old_mem);
return old_mem[0..0];
}
if (isAligned(@ptrToInt(old_mem.ptr), new_alignment)) {
if (self.resizeMem(old_mem, new_byte_count)) {
return old_mem.ptr[0..new_byte_count];
} else |e| switch (e) {
error.OutOfMemory => {},
}
}
if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) {
return error.OutOfMemory;
}
return self.moveMem(old_mem, new_byte_count, new_alignment);
}

/// This function deallocates memory. It must succeed.
shrinkFn: fn (
/// Move the given memory to a new location in the given allocator to accomodate a new
/// size and alignment.
fn moveMem(self: *Allocator, old_mem: []u8, new_len: usize, new_alignment: u29) Error![]u8 {
assert(old_mem.len > 0);
assert(new_len > 0);
const new_mem = try self.allocMem(new_len, new_alignment);
@memcpy(new_mem.ptr, old_mem.ptr, std.math.min(new_len, old_mem.len));
self.freeMem(old_mem);
return new_mem;
}

/// This function attempts to deallocates memory. It must succeed.
fn shrinkMem(
self: *Allocator,
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
/// `reallocFn` or `shrinkMem`.
old_mem: []u8,
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
/// `reallocFn` or `shrinkMem`.
old_alignment: u29,
/// Guaranteed to be less than or equal to `old_mem.len`.
new_byte_count: usize,
/// If `new_byte_count == 0` then this is `undefined`, otherwise:
/// Guaranteed to be less than or equal to `old_alignment`.
new_alignment: u29,
) []u8,
) []u8 {
assert(new_byte_count <= old_mem.len);
assert(new_alignment <= old_alignment);
if (new_byte_count == 0) {
self.freeMem(old_mem);
} else {
self.resizeMem(old_mem, new_byte_count) catch |e| switch (e) {
error.OutOfMemory => {}, // ignore the error, can't fail
};
}
return old_mem[0..new_byte_count];
}

/// Returns a pointer to undefined memory.
/// Call `destroy` with the result to free the memory.
Expand All @@ -89,8 +158,7 @@ pub const Allocator = struct {
const T = @TypeOf(ptr).Child;
if (@sizeOf(T) == 0) return;
const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
const shrink_result = self.shrinkFn(self, non_const_ptr[0..@sizeOf(T)], @alignOf(T), 0, 1);
assert(shrink_result.len == 0);
self.freeMem(non_const_ptr[0..@sizeOf(T)]);
}

/// Allocates an array of `n` items of type `T` and sets all the
Expand Down Expand Up @@ -161,7 +229,7 @@ pub const Allocator = struct {
}

const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
const byte_slice = try self.reallocFn(self, &[0]u8{}, undefined, byte_count, a);
const byte_slice = try self.allocMem(byte_count, a);
assert(byte_slice.len == byte_count);
@memset(byte_slice.ptr, undefined, byte_slice.len);
if (alignment == null) {
Expand Down Expand Up @@ -215,7 +283,7 @@ pub const Allocator = struct {
const old_byte_slice = mem.sliceAsBytes(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
const byte_slice = try self.reallocFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment);
const byte_slice = try self.reallocMem(old_byte_slice, Slice.alignment, byte_count, new_alignment);
assert(byte_slice.len == byte_count);
if (new_n > old_mem.len) {
@memset(byte_slice.ptr + old_byte_slice.len, undefined, byte_slice.len - old_byte_slice.len);
Expand Down Expand Up @@ -262,7 +330,7 @@ pub const Allocator = struct {

const old_byte_slice = mem.sliceAsBytes(old_mem);
@memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count);
const byte_slice = self.shrinkFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment);
const byte_slice = self.shrinkMem(old_byte_slice, Slice.alignment, byte_count, new_alignment);
assert(byte_slice.len == byte_count);
return mem.bytesAsSlice(T, @alignCast(new_alignment, byte_slice));
}
Expand All @@ -276,7 +344,7 @@ pub const Allocator = struct {
if (bytes_len == 0) return;
const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
@memset(non_const_ptr, undefined, bytes_len);
const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
const shrink_result = self.shrinkMem(non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
assert(shrink_result.len == 0);
}

Expand Down
1 change: 1 addition & 0 deletions lib/std/os/windows/bits.zig
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ pub const FILE_CURRENT = 1;
pub const FILE_END = 2;

pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;
pub const HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010;
pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;
pub const HEAP_NO_SERIALIZE = 0x00000001;

Expand Down
50 changes: 23 additions & 27 deletions lib/std/testing/failing_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,43 +39,39 @@ pub const FailingAllocator = struct {
.allocations = 0,
.deallocations = 0,
.allocator = mem.Allocator{
.reallocFn = realloc,
.shrinkFn = shrink,
.allocFn = alloc,
.freeFn = free,
.resizeFn = resize,
},
};
}

fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
fn alloc(allocator: *std.mem.Allocator, len: usize, alignment: u29) error{OutOfMemory}![]u8 {
const self = @fieldParentPtr(@This(), "allocator", allocator);
if (self.index == self.fail_index) {
return error.OutOfMemory;
}
const result = try self.internal_allocator.reallocFn(
self.internal_allocator,
old_mem,
old_align,
new_size,
new_align,
);
if (new_size < old_mem.len) {
self.freed_bytes += old_mem.len - new_size;
if (new_size == 0)
self.deallocations += 1;
} else if (new_size > old_mem.len) {
self.allocated_bytes += new_size - old_mem.len;
if (old_mem.len == 0)
self.allocations += 1;
}
const result = try self.internal_allocator.allocMem(len, alignment);
self.allocated_bytes += len;
self.allocations += 1;
self.index += 1;
return result;
}

fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
self.freed_bytes += old_mem.len - r.len;
if (new_size == 0)
self.deallocations += 1;
return r;
fn free(allocator: *std.mem.Allocator, buf: []u8) void {
const self = @fieldParentPtr(@This(), "allocator", allocator);
self.internal_allocator.freeMem(buf);
self.freed_bytes += buf.len;
self.deallocations += 1;
}

fn resize(allocator: *std.mem.Allocator, buf: []u8, new_len: usize) error{OutOfMemory}!void {
const self = @fieldParentPtr(@This(), "allocator", allocator);
const result = try self.internal_allocator.resizeMem(buf, new_len);
if (new_len < buf.len) {
self.freed_bytes += buf.len - new_len;
} else {
self.allocated_bytes += new_len - buf.len;
}
}
};
31 changes: 17 additions & 14 deletions lib/std/testing/leak_count_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,34 @@ pub const LeakCountAllocator = struct {
return .{
.count = 0,
.allocator = .{
.reallocFn = realloc,
.shrinkFn = shrink,
.allocFn = alloc,
.freeFn = free,
//.resizeFn = allocator.resizeFn, // this is a compile error?
.resizeFn = resize,
},
.internal_allocator = allocator,
};
}

fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
fn alloc(allocator: *std.mem.Allocator, len: usize, alignment: u29) error{OutOfMemory}![]u8 {
const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
var data = try self.internal_allocator.reallocFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
if (old_mem.len == 0) {
self.count += 1;
}
var data = try self.internal_allocator.allocMem(len, alignment);
self.count += 1;
return data;
}

fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
fn free(allocator: *std.mem.Allocator, buf: []u8) void {
const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
if (new_size == 0) {
if (self.count == 0) {
std.debug.panic("error - too many calls to free, most likely double free", .{});
}
self.count -= 1;
if (self.count == 0) {
std.debug.panic("error - too many calls to free, most likely double free", .{});
}
return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
self.count -= 1;
return self.internal_allocator.freeMem(buf);
}

fn resize(allocator: *std.mem.Allocator, buf: []u8, new_len: usize) error{OutOfMemory}!void {
const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator);
return self.internal_allocator.resizeMem(buf, new_len);
}

pub fn validate(self: LeakCountAllocator) !void {
Expand Down

0 comments on commit 1ead3b4

Please sign in to comment.