Application Context Implementation #4

Closed
kayomn wants to merge 93 commits from event-loop-dev into main
3 changed files with 71 additions and 60 deletions
Showing only changes of commit fcd4ecd85d - Show all commits

View File

@ -4,67 +4,47 @@ const stack = @import("./stack.zig");
const testing = @import("./testing.zig"); const testing = @import("./testing.zig");
/// ///
/// Dynamic memory allocation interface.
/// ///
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 pub const Allocation = struct {
/// operation. existing: ?[*]u8,
/// size: usize
pub const MakeError = error {
OutOfMemory,
}; };
/// ///
/// Frees `allocation` using `allocator`. /// Dynamic memory allocation interface.
/// ///
pub fn free(allocator: *Allocator, allocation: anytype) void { pub const Allocator = meta.Function(Allocation, ?[*]u8);
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 { pub const ArenaAllocator = struct {
switch (allocator.*) { region: []u8,
.bound => |bound| return @ptrCast([*]Element, @alignCast(@alignOf(Element), cursor: usize = 0,
bound.alloc(@sizeOf(Element) * size) orelse return error.OutOfMemory)),
.arena => |*stack| { ///
defer stack.cursor += size; ///
///
pub fn allocator(arena_allocator: *ArenaAllocator) Allocator {
return Allocator.fromClosure(arena_allocator, struct {
fn call(context: *ArenaAllocator, allocation: Allocation) ?[*]u8 {
_ = allocation;
_ = context;
return @ptrCast([*]Element, @alignCast(@alignOf(Element), stack.buffer.ptr)); return null;
},
} }
}.call);
} }
}; };
///
///
///
pub const MakeError = error {
OutOfMemory,
};
/// ///
/// Closure that captures a reference to readable resources like block devices, memory buffers, /// Closure that captures a reference to readable resources like block devices, memory buffers,
/// network sockets, and more. /// network sockets, and more.
@ -337,6 +317,24 @@ test "Find first of sequence" {
try testing.expect(findFirstOf([]const u8, haystack, &.{"baz", "bar"}, testEquality) == null); 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`. /// 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})); 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`. /// Swaps the `Data` in `this` with `that`.
/// ///

View File

@ -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. /// 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`. /// Returns an [io.Writer] wrapping `fixed_stack`.

View File

@ -11,7 +11,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
const Allocator = io.Allocator; const Allocator = io.Allocator;
return struct { return struct {
allocator: *Allocator, allocator: Allocator,
load_limit: f32, load_limit: f32,
buckets: []Bucket, buckets: []Bucket,
filled: usize, filled: usize,
@ -37,7 +37,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
/// Deinitializes `self`, preventing any further use. /// Deinitializes `self`, preventing any further use.
/// ///
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.free(self.buckets.ptr); io.free(self.allocator, self.buckets);
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. /// 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; const initial_capacity = 4;
return Self{ 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, .filled = 0,
.allocator = allocator, .allocator = allocator,
.load_limit = 0.75, .load_limit = 0.75,
@ -99,11 +99,12 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
if (self.loadFactor() >= self.load_limit) { if (self.loadFactor() >= self.load_limit) {
const old_buckets = self.buckets; 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; 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; 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 /// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key
/// already present. /// already present.
/// ///
pub const InsertError = io.Allocator.MakeError || error { pub const InsertError = io.MakeError || error {
KeyExists, KeyExists,
}; };
@ -197,8 +198,10 @@ pub const string_literal_context = KeyContext([]const u8){
test "Hash table manipulation with string literal context" { test "Hash table manipulation with string literal context" {
var buffer = [_]u8{0} ** 1024; var buffer = [_]u8{0} ** 1024;
var allocator = io.Allocator{.arena = .{.buffer = &buffer}}; var arena_allocator = io.ArenaAllocator{.region = &buffer};
var table = try Hashed([]const u8, u32, string_literal_context).init(&allocator);
var table =
try Hashed([]const u8, u32, string_literal_context).init(arena_allocator.allocator());
defer table.deinit(); defer table.deinit();