From d49608f7bb9a6660a24890f99958e578f71da379 Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 2 Nov 2022 10:04:37 +0000 Subject: [PATCH] Initial work on allocator replacement --- src/core/io.zig | 61 ++++++++++++++++++++++++++++++++++++++++++++-- src/core/stack.zig | 2 +- src/core/table.zig | 31 ++++++++++++----------- src/ona/sys.zig | 13 ++++++++++ 4 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/core/io.zig b/src/core/io.zig index 5102493..5490c0f 100644 --- a/src/core/io.zig +++ b/src/core/io.zig @@ -4,9 +4,66 @@ const stack = @import("./stack.zig"); const testing = @import("./testing.zig"); /// +/// Dynamic memory allocation interface. /// -/// -pub const Allocator = @import("std").mem.Allocator; +pub const Allocator = union (enum) { + bound: struct { + alloc: fn (usize) ?[*]u8, + dealloc: fn ([*]u8) void, + }, + + arena: struct { + buffer: []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"), + + 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 => {}, + } + } + + /// + /// 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)); + }, + } + } +}; /// /// Closure that captures a reference to readable resources like block devices, memory buffers, diff --git a/src/core/stack.zig b/src/core/stack.zig index 6caa0fb..98c5c4b 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.Error; +pub const PushError = io.Allocator.MakeError; /// /// Returns an [io.Writer] wrapping `fixed_stack`. diff --git a/src/core/table.zig b/src/core/table.zig index ddf60b7..c8c0755 100644 --- a/src/core/table.zig +++ b/src/core/table.zig @@ -1,4 +1,5 @@ const io = @import("./io.zig"); +const testing = @import("./testing.zig"); /// /// Returns a hash-backed table type of `Value`s indexed by `Key` and using `key_context` as the key @@ -10,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, @@ -36,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); + self.allocator.free(self.buckets.ptr); self.buckets = &.{}; } @@ -44,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.Error] if initializing failed. + /// Returns a new [Self] value or an [io.Allocator.MakeError] if initializing failed. /// - pub fn init(allocator: Allocator) Allocator.Error!Self { + pub fn init(allocator: *Allocator) Allocator.MakeError!Self { const initial_capacity = 4; return Self{ - .buckets = try allocator.alloc(Bucket, initial_capacity), + .buckets = (try allocator.make(Bucket, initial_capacity))[0 .. initial_capacity], .filled = 0, .allocator = allocator, .load_limit = 0.75, @@ -98,9 +99,11 @@ 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); + defer self.allocator.free(old_buckets.ptr); - self.buckets = try self.allocator.alloc(Bucket, old_buckets.len * 2); + const bucket_count = old_buckets.len * 2; + + self.buckets = (try self.allocator.make(Bucket, bucket_count))[0 .. bucket_count]; for (old_buckets) |bucket, index| self.buckets[index] = bucket; } @@ -160,7 +163,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.Error || error { +pub const InsertError = io.Allocator.MakeError || error { KeyExists, }; @@ -193,16 +196,16 @@ pub const string_literal_context = KeyContext([]const u8){ }; test "Hash table manipulation with string literal context" { - const testing = @import("std").testing; - - var table = try Hashed([]const u8, u32, string_literal_context).init(testing.allocator); + var buffer = [_]u8{0} ** 1024; + var allocator = io.Allocator{.arena = .{.buffer = &buffer}}; + var table = try Hashed([]const u8, u32, string_literal_context).init(&allocator); defer table.deinit(); const foo = @as(u32, 69); - try testing.expectEqual(table.remove("foo"), null); + try testing.expect(table.remove("foo") == null); try table.insert("foo", foo); - try testing.expectEqual(table.remove("foo"), foo); - try testing.expectEqual(table.remove("foo"), null); + try testing.expect(table.remove("foo").? == foo); + try testing.expect(table.remove("foo") == null); } diff --git a/src/ona/sys.zig b/src/ona/sys.zig index 38422fe..d3f33a0 100644 --- a/src/ona/sys.zig +++ b/src/ona/sys.zig @@ -428,6 +428,19 @@ pub const RunError = error { InitFailure, }; +/// +/// Returns a [core.io.Allocator] bound to the underlying system allocator. +/// +pub fn allocator() core.io.Allocator { + // TODO: Add leak detection. + return .{ + .bound = .{ + .alloc = ext.SDL_alloc, + .dealloc = ext.SDL_free, + }, + }; +} + /// /// Runs a graphical application referenced by `run` with `error` as its error set. ///