From fcd4ecd85d815f651954861ea59421392f9ed6ca Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 2 Nov 2022 15:15:18 +0000 Subject: [PATCH] Refactor dynamic memory allocation interface --- src/core/io.zig | 106 ++++++++++++++++++++++++--------------------- src/core/stack.zig | 2 +- src/core/table.zig | 23 +++++----- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/src/core/io.zig b/src/core/io.zig index 43a3698..ec2e660 100644 --- a/src/core/io.zig +++ b/src/core/io.zig @@ -3,66 +3,46 @@ const meta = @import("./meta.zig"); const stack = @import("./stack.zig"); const testing = @import("./testing.zig"); +/// +/// +/// +pub const Allocation = struct { + existing: ?[*]u8, + size: usize +}; + /// /// Dynamic memory allocation interface. /// -pub const Allocator = union (enum) { - bound: struct { - alloc: fn (usize) ?[*]u8, - dealloc: fn ([*]u8) void, - }, +pub const Allocator = meta.Function(Allocation, ?[*]u8); - arena: struct { - buffer: []u8, - cursor: usize = 0, - }, +/// +/// +/// +pub const ArenaAllocator = struct { + region: []u8, + cursor: usize = 0, /// - /// [MakeError.OutOfMemory] is used to indicate there is not enough memory available for a given - /// operation. /// - pub const MakeError = error { - OutOfMemory, - }; - /// - /// Frees `allocation` using `allocator`. - /// - pub fn free(allocator: *Allocator, allocation: anytype) void { - switch (@typeInfo(@TypeOf(allocation))) { - .Pointer => |pointer| if (pointer.size == .Slice) - @compileError("`allocation` cannot be a slice"), + pub fn allocator(arena_allocator: *ArenaAllocator) Allocator { + return Allocator.fromClosure(arena_allocator, struct { + fn call(context: *ArenaAllocator, allocation: Allocation) ?[*]u8 { + _ = allocation; + _ = context; - else => @compileError("`allocation` must be a pointer"), - } - - if (@typeInfo(@TypeOf(allocation)) != .Pointer) - @compileError("`allocation` must be a pointer"); - - // TODO: Implement arena de-allocation. - switch (allocator.*) { - .bound => |bound| bound.dealloc(@ptrCast([*]u8, allocation)), - .arena => {}, - } + return null; + } + }.call); } +}; - /// - /// Attempts to allocate `size` number of `Element`s using `allocator`. - /// - /// Returns the allocation or a [MakeError] if it failed. - /// - pub fn make(allocator: *Allocator, comptime Element: type, size: usize) MakeError![*]Element { - switch (allocator.*) { - .bound => |bound| return @ptrCast([*]Element, @alignCast(@alignOf(Element), - bound.alloc(@sizeOf(Element) * size) orelse return error.OutOfMemory)), - - .arena => |*stack| { - defer stack.cursor += size; - - return @ptrCast([*]Element, @alignCast(@alignOf(Element), stack.buffer.ptr)); - }, - } - } +/// +/// +/// +pub const MakeError = error { + OutOfMemory, }; /// @@ -337,6 +317,24 @@ test "Find first of sequence" { try testing.expect(findFirstOf([]const u8, haystack, &.{"baz", "bar"}, testEquality) == null); } +/// +/// +/// +pub fn free(allocator: Allocator, allocated_memory: anytype) void { + if (allocator.call(.{ + .existing = @ptrCast([*]u8, switch (@typeInfo(@TypeOf(allocated_memory))) { + .Pointer => |info| switch (info.size) { + .One, .Many, .C => allocated_memory, + .Slice => allocated_memory.ptr, + }, + + else => @compileError("`allocated_memory` must be a pointer"), + }), + + .size = 0, + }) != null) unreachable; +} + /// /// Returns a deterministic hash code compiled from each byte in `bytes`. /// @@ -357,6 +355,16 @@ test "Hashing bytes" { try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); } +/// +/// +/// +pub fn makeMany(comptime Element: type, allocator: Allocator, size: usize) MakeError![*]Element { + return @ptrCast([*]Element, @alignCast(@alignOf(Element), allocator.call(.{ + .existing = null, + .size = size, + }) orelse return error.OutOfMemory)); +} + /// /// Swaps the `Data` in `this` with `that`. /// diff --git a/src/core/stack.zig b/src/core/stack.zig index 841f76f..27653e5 100755 --- a/src/core/stack.zig +++ b/src/core/stack.zig @@ -113,7 +113,7 @@ test "Fixed stack of string literals" { /// /// Potential errors that may occur while trying to push one or more elements into a stack. /// -pub const PushError = io.Allocator.MakeError; +pub const PushError = io.MakeError; /// /// Returns an [io.Writer] wrapping `fixed_stack`. diff --git a/src/core/table.zig b/src/core/table.zig index c8c0755..f67213b 100644 --- a/src/core/table.zig +++ b/src/core/table.zig @@ -11,7 +11,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, const Allocator = io.Allocator; return struct { - allocator: *Allocator, + allocator: Allocator, load_limit: f32, buckets: []Bucket, filled: usize, @@ -37,7 +37,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, /// Deinitializes `self`, preventing any further use. /// pub fn deinit(self: *Self) void { - self.allocator.free(self.buckets.ptr); + io.free(self.allocator, self.buckets); self.buckets = &.{}; } @@ -45,13 +45,13 @@ pub fn Hashed(comptime Key: type, comptime Value: type, /// /// Initializes a [Self] using `allocator` as the memory allocation strategy. /// - /// Returns a new [Self] value or an [io.Allocator.MakeError] if initializing failed. + /// Returns a new [Self] value or an [io.MakeError] if initializing failed. /// - pub fn init(allocator: *Allocator) Allocator.MakeError!Self { + pub fn init(allocator: Allocator) io.MakeError!Self { const initial_capacity = 4; return Self{ - .buckets = (try allocator.make(Bucket, initial_capacity))[0 .. initial_capacity], + .buckets = (try io.makeMany(Bucket, allocator, initial_capacity))[0 .. initial_capacity], .filled = 0, .allocator = allocator, .load_limit = 0.75, @@ -99,11 +99,12 @@ pub fn Hashed(comptime Key: type, comptime Value: type, if (self.loadFactor() >= self.load_limit) { const old_buckets = self.buckets; - defer self.allocator.free(old_buckets.ptr); + defer io.free(self.allocator, old_buckets); const bucket_count = old_buckets.len * 2; - self.buckets = (try self.allocator.make(Bucket, bucket_count))[0 .. bucket_count]; + self.buckets = (try io.makeMany(Bucket, self.allocator, + bucket_count))[0 .. bucket_count]; for (old_buckets) |bucket, index| self.buckets[index] = bucket; } @@ -163,7 +164,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, /// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key /// already present. /// -pub const InsertError = io.Allocator.MakeError || error { +pub const InsertError = io.MakeError || error { KeyExists, }; @@ -197,8 +198,10 @@ pub const string_literal_context = KeyContext([]const u8){ test "Hash table manipulation with string literal context" { var buffer = [_]u8{0} ** 1024; - var allocator = io.Allocator{.arena = .{.buffer = &buffer}}; - var table = try Hashed([]const u8, u32, string_literal_context).init(&allocator); + var arena_allocator = io.ArenaAllocator{.region = &buffer}; + + var table = + try Hashed([]const u8, u32, string_literal_context).init(arena_allocator.allocator()); defer table.deinit();