From 295f2b0511688bc76af156c90be43eabf8f34f37 Mon Sep 17 00:00:00 2001 From: Linuxperoxo Date: Mon, 26 Jan 2026 19:12:42 -0300 Subject: [PATCH 1/6] allocators update --- lib/saturn/kernel/memory/memory.zig | 11 +-- lib/saturn/kernel/memory/sba/sba.zig | 80 +++++++++++++--------- lib/saturn/kernel/memory/sba/test.zig | 2 +- lib/saturn/kernel/memory/vtable/vtable.zig | 43 ++++++++++++ 4 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 lib/saturn/kernel/memory/vtable/vtable.zig diff --git a/lib/saturn/kernel/memory/memory.zig b/lib/saturn/kernel/memory/memory.zig index 0fb0dae..a4e5572 100644 --- a/lib/saturn/kernel/memory/memory.zig +++ b/lib/saturn/kernel/memory/memory.zig @@ -3,15 +3,6 @@ // │ Author: Linuxperoxo │ // └──────────────────────────────────────────────┘ -// Esse arquivo contem alocadores de -// memory independentes, ou seja, nao -// depende de forma alguma da implementacao -// mm da arquitetura - pub const soa: type = @import("soa/soa.zig"); pub const sba: type = @import("sba/sba.zig"); - -pub fn kmalloc(comptime T: type, _: u32) anyerror![]T { - var slice: []T = undefined; - return slice[0..1]; -} +pub const vtable: type = @import("vtable/vtable.zig"); diff --git a/lib/saturn/kernel/memory/sba/sba.zig b/lib/saturn/kernel/memory/sba/sba.zig index c51aa1d..5a83a52 100644 --- a/lib/saturn/kernel/memory/sba/sba.zig +++ b/lib/saturn/kernel/memory/sba/sba.zig @@ -8,6 +8,31 @@ const mm: type = @import("root").code.mm; const config: type = @import("root").config; const types: type = @import("types.zig"); const std: type = @import("std"); +const vtable: type = if(!builtin.is_test) @import("root").lib.memory.vtable else struct { + pub const AllocVTable_T: type = struct { + fn_alloc: *const fn(self: *const @This(), bytes: usize) anyerror![]u8, + fn_free: *const fn(self: *const @This(), ptr: []u8) anyerror!void, + private: *anyopaque, + + pub fn alloc(self: *const @This(), comptime T: type, N: usize) anyerror![]T { + return @alignCast(@ptrCast( + (try self.fn_alloc(self, @sizeOf(T) * N)) + )); + } + + pub fn free(self: *const @This(), ptr: anytype) anyerror!void { + return self.fn_free(self, comptime sw: switch(@typeInfo(@TypeOf(ptr))) { + .pointer => |p| { + if(p.size == .c or p.size == .many) + continue :sw @typeInfo(void); + break :sw @alignCast(@ptrCast(ptr[0..if(p.size == .one) 1 else ptr.len])); + }, + + else => @compileError("expect slice or single pointer to free. Found \"" ++ @typeName(@TypeOf(ptr)) ++ "\""), + }); + } + }; +}; // === Saturn Byte Allocator === @@ -79,8 +104,8 @@ pub fn buildByteAllocator( fn pool_init(pool: *Pool_T) err_T!void { if(builtin.is_test) { var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; - var allocator = gpa.allocator(); - pool.bytes = allocator.alloc(u8, total_bytes_of_pool_test) catch return err_T.PoolInitFailed; + var alc = gpa.allocator(); + pool.bytes = alc.alloc(u8, total_bytes_of_pool_test) catch return err_T.PoolInitFailed; return; } switch(config.arch.options.Target) { @@ -238,8 +263,8 @@ pub fn buildByteAllocator( return current_pool.bytes.?[cast_block_to_byte(index)..cast_block_to_byte(index + blocks_to_alloc)]; } - pub fn alloc(self: *@This(), comptime T: type, N: usize) err_T![]T { - const bytes: usize = @sizeOf(T) * N; + fn alloc(self_vtable: *const vtable.AllocVTable_T, bytes: usize) err_T![]u8 { + const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); self.top = self.top orelse &self.root; if(bytes == 0) return err_T.ZeroBytes; if(self.root.bytes == null) { @@ -249,19 +274,22 @@ pub fn buildByteAllocator( }); } if(comptime personality.resize) { - return @as([]T, @alignCast(@ptrCast(try @call(.always_inline, alloc_resized_frame, .{ + return @call(.always_inline, alloc_resized_frame, .{ self, bytes - })))); + }); } - return @as([]T, @alignCast(@ptrCast(try @call(.always_inline, alloc_sigle_frame, .{ + return @call(.always_inline, alloc_sigle_frame, .{ self, bytes - })))); + }); } fn free_resized_frame(self: *@This(), ptr: []u8) err_T!void { const parent_pool, const alloc_pool = self.found_pool_of_ptr(ptr); if(alloc_pool == null) return err_T.IndexOutBounds; const block_to_free: usize = cast_bytes_to_block(ptr.len); + //fn_init: *const fn(self: *const @This()) anyerror!void, + //fn_deinit: *const fn(self: *const @This()) void, + //fn_is_initialized: *const fn(self: *const @This()) bool, const initial_block: usize = cast_bytes_to_block( @intFromPtr(ptr.ptr) - @intFromPtr(&alloc_pool.?.bytes.?[0]) ); @@ -307,36 +335,24 @@ pub fn buildByteAllocator( self.root.flags.full = 0; } - pub fn free(self: *@This(), ptr: anytype) err_T!void { - const byte_slice_fn = comptime sw0: switch(@typeInfo(@TypeOf(ptr))) { - .pointer => |ptr_info| { - switch(ptr_info.size) { - .slice => break :sw0 opaque { - pub fn cast(slice: []ptr_info.child) []u8 { - return @as([]u8, @ptrCast(slice)); - } - }.cast, - .one => break :sw0 opaque { - pub fn cast(single: *ptr_info.child) []u8 { - return @as([]u8, @ptrCast(@as([*]ptr_info.child, @ptrCast(single))[0..1])); - } - }.cast, - else => continue :sw0 @typeInfo(void), - } - }, - else => @compileError("expect slice to free or single pointer"), - }; - const byte_slice = @call(.always_inline, byte_slice_fn, .{ - ptr - }); + fn free(self_vtable: *const vtable.AllocVTable_T, ptr: []u8) anyerror!void { + const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); if(comptime personality.resize) { return @call(.always_inline, free_resized_frame, .{ - self, byte_slice + self, ptr }); } return @call(.always_inline, free_single_frame, .{ - self, byte_slice + self, ptr }); } + + pub fn allocator(self: *@This()) vtable.AllocVTable_T { + return vtable.AllocVTable_T { + .fn_alloc = &alloc, + .fn_free = &free, + .private = self, + }; + } }; } diff --git a/lib/saturn/kernel/memory/sba/test.zig b/lib/saturn/kernel/memory/sba/test.zig index 9a0e35a..e372920 100644 --- a/lib/saturn/kernel/memory/sba/test.zig +++ b/lib/saturn/kernel/memory/sba/test.zig @@ -73,7 +73,7 @@ fn bitmap_check(comptime Pool_T: type, pool: *Pool_T, state: u1, index: usize) b } test "SBA Alloc Test For Single Frame" { - var sba_allocator: SBASingle_T = .{}; + var sba_allocator = @constCast(&SBASingle_T {}).allocator(); var old_ptr: ?[]u8 = null; for(0..SBASingle_T.Pool_T.pool_bitmap_len) |_| { const ptr = try sba_allocator.alloc(u8, 1); diff --git a/lib/saturn/kernel/memory/vtable/vtable.zig b/lib/saturn/kernel/memory/vtable/vtable.zig new file mode 100644 index 0000000..988b1ac --- /dev/null +++ b/lib/saturn/kernel/memory/vtable/vtable.zig @@ -0,0 +1,43 @@ +// ┌──────────────────────────────────────────────┐ +// │ (c) 2026 Linuxperoxo • FILE: vtable.zig │ +// │ Author: Linuxperoxo │ +// └──────────────────────────────────────────────┘ + +pub const AllocVTable_T: type = struct { + fn_alloc: *const fn(self: *const @This(), bytes: usize) anyerror![]u8, + fn_free: *const fn(self: *const @This(), ptr: []u8) anyerror!void, + //fn_init: *const fn(self: *const @This()) anyerror!void, + //fn_deinit: *const fn(self: *const @This()) void, + //fn_is_initialized: *const fn(self: *const @This()) bool, + private: *anyopaque, + + pub fn alloc(self: *const @This(), comptime T: type, N: usize) anyerror![]T { + return @alignCast(@ptrCast( + (self.fn_alloc(self, @sizeOf(T) * N)) + )); + } + + pub fn free(self: *const @This(), ptr: anytype) anyerror!void { + return self.fn_free(self, comptime sw: switch(@typeInfo(@TypeOf(ptr))) { + .pointer => |p| { + if(p.size == .c or p.size == .many) + continue :sw @typeInfo(void); + break :sw @alignCast(@ptrCast(ptr[0..if(p.size == .one) 1 else ptr.len])); + }, + + else => @compileError("expect slice or single pointer to free. Found \"" ++ @typeName(@TypeOf(ptr)) ++ "\""), + }); + } + + pub fn init(_: *const @This()) anyerror!void { + //return self.fn_init(); + } + + pub fn deinit(_: *const @This()) void { + //return self.fn_deinit(); + } + + pub fn is_initialized(_: *const @This()) bool { + //return self.fn_is_initialized(); + } +}; From 83e893d6ec2efc686b4b67f3efc4079bfe2bfb9d Mon Sep 17 00:00:00 2001 From: Linuxperoxo Date: Tue, 27 Jan 2026 12:44:34 -0300 Subject: [PATCH 2/6] sba ref --- lib/saturn/kernel/memory/sba/sba.zig | 447 +++++++++------------ lib/saturn/kernel/memory/sba/types.zig | 17 +- lib/saturn/kernel/memory/vtable/vtable.zig | 18 +- 3 files changed, 195 insertions(+), 287 deletions(-) diff --git a/lib/saturn/kernel/memory/sba/sba.zig b/lib/saturn/kernel/memory/sba/sba.zig index 5a83a52..313a763 100644 --- a/lib/saturn/kernel/memory/sba/sba.zig +++ b/lib/saturn/kernel/memory/sba/sba.zig @@ -3,10 +3,11 @@ // │ Author: Linuxperoxo │ // └──────────────────────────────────────────────┘ +const ar: type = @import("root").ar; const builtin: type = @import("builtin"); const mm: type = @import("root").code.mm; -const config: type = @import("root").config; const types: type = @import("types.zig"); +const config: type = @import("root").config; const std: type = @import("std"); const vtable: type = if(!builtin.is_test) @import("root").lib.memory.vtable else struct { pub const AllocVTable_T: type = struct { @@ -45,312 +46,232 @@ const total_bytes_of_pool = if(builtin.is_test) total_bytes_of_pool_test else sw pub fn buildByteAllocator( comptime block: ?comptime_int, - comptime personality: types.Personality_T, + comptime options: types.Options_T, ) type { return struct { - root: Pool_T = .{}, - top: ?*Pool_T = null, - pools: if(builtin.is_test and personality.resize) usize else void = if(builtin.is_test and personality.resize) 1 else {}, + pub const Debug_T: type = if(options.debug) struct { + allocs: usize = 0, + pools: usize = 0, + bytes: usize = 0, + bmarks: usize = 0, + } else void; - // esse calculo e equivalente a fazer: - // - // var blocks_reserved = block / @sizeOf(Pool_T); - // if((block % @sizeOf(Pool_T)) != 0) blocks_reserved += 1; - pub const blocks_reserved = if(personality.resize) ((@sizeOf(Pool_T) + block_size - 1) / block_size) else 0; - pub const block_size = block orelse default_block_size; + const block_size: comptime_int = block orelse default_block_size; + const vector_blocks: comptime_int = ((total_bytes_of_pool + block_size) / block_size); - pub const Pool_T: type = struct { - bytes: ?[]u8 = null, - refs: usize = blocks_reserved, - next: ?usize = null, - bitmap: [pool_bitmap_len]u1 = r: { - var map = [_]u1 { - 0 - } ** pool_bitmap_len; - if(!personality.resize) break :r map; - for(0..blocks_reserved) |i| - map[i] = 1; - break :r map; + const BitmapInt_T: type = @Type(.{ + .int = .{ + .bits = vector_blocks, + .signedness = .unsigned, }, - flags: packed struct(u8) { + }); + + pub const Pool_T: type = struct { + pool: ?*[total_bytes_of_pool]u8 = null, + prev: ?*Pool_T = null, + bitmap: @Vector(vector_blocks, u1) = @splat(1), + private: *anyopaque, + flags: packed struct { + child: u1 = 0, full: u1 = 0, - hit: u2 = 0, - parent: u1 = 0, - reserved: u4 = 0, } = .{}, - private: Private_T = if(Private_T == void) {} else undefined, - - pub const pool_bitmap_len = total_bytes_of_pool / block_size; - - pub const Private_T: type = if(builtin.is_test) void else switch(config.arch.options.Target) { - .i386 => mm.AllocPage_T, - else => void, - }; - }; - - pub const err_T: type = error { - PoolInitFailed, - PoolResizeFailed, - OutOfMemory, - IndexOutBounds, - UndefinedAction, - MemoryFrag, - ZeroBytes, - NonPoolInitialized, - PoolOverflow, - DoubleFree, - }; - fn pool_init(pool: *Pool_T) err_T!void { - if(builtin.is_test) { - var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; - var alc = gpa.allocator(); - pool.bytes = alc.alloc(u8, total_bytes_of_pool_test) catch return err_T.PoolInitFailed; - return; + pub fn child(self: *@This()) ?*Pool_T { + return if(self.pool == null) null else + @alignCast(@ptrCast(&self.pool.?[0])); } - switch(config.arch.options.Target) { - .i386 => { - pool.private = @call(.never_inline, mm.alloc_page, .{}) catch return err_T.PoolInitFailed; - pool.bytes = pool.private.virtual; - }, - else => unreachable, - } - } - fn pool_deinit(pool: *Pool_T) err_T!void { - // nao precisa de free para test - if(builtin.is_test) return; - switch(config.arch.options.Target) { - .i386 => @call(.never_inline, mm.free_page, .{ - &pool.private - }) catch return err_T.PoolInitFailed, - else => unreachable, + pub fn init(self: *@This()) anyerror!void { + self.* = .{}; + self.pool = sw: switch(comptime ar.target_code.target) { + .i386 => { + const page = try mm.alloc_page(); + self.private = page; + break :sw @alignCast(@ptrCast(page.virtual.ptr)); + }, + else => unreachable, + }; + if(comptime options.resize) { + // deixa blocos reservados para expandir o alocador + // quando esse pool estiver cheio + const initial_block_index: usize = 0; + const total_blocks: usize = (block_size + @sizeOf(Pool_T)) / block_size; + for(initial_block_index..total_blocks) |i| + self.bitmap[i] = 0; + } } - } - fn resize(self: *@This()) err_T!void { - const pool_config = opaque { - pub fn config(pool: *Pool_T) void { - for(0..blocks_reserved) |i| { - pool.bitmap[i] = 1; + pub fn deinit(self: *@This()) anyerror!void { + const private: *anyopaque = self.private; + if(comptime options.resize) { + if(self.flags.child == 1) { + // caso o pool atual tenha um filho + @as(*Pool_T, @alignCast(@ptrCast( + // * caso tenha um pai, passamos o filhos do pool + // atual para seu pai + // * caso nao tenha um pai, substituimos o pool + // atual pelo seu filho + if(self.prev != null) &self.prev.?.pool.?[0] else self + ))).* = self.child().?.*; + } else { + // caso tenha um pai, invalidamos esse pool como filho + if(self.prev != null) self.prev.?.flags.child = 0; } - for(blocks_reserved..Pool_T.pool_bitmap_len) |i| { - pool.bitmap[i] = 0; - } - pool.refs = blocks_reserved; - pool.next = null; - pool.flags = .{ - .full = 0, - .hit = 0, - .parent = 0, - .reserved = 0, - }; } - }.config; - const pool: *Pool_T = @ptrCast(@alignCast(&self.top.?.bytes.?[0])); - try @call(.always_inline, pool_init, .{ - pool - }); - @call(.always_inline, pool_config, .{ - pool - }); - self.top.?.flags.parent = 1; - self.top = pool; - if(builtin.is_test and personality.resize) { - self.pools += 1; + switch(comptime ar.target_code.target) { + .i386 => try mm.free_page(@alignCast(@ptrCast(private))), + else => unreachable, + } + self.pool = null; } - } - inline fn check_blocks_range(pool: *Pool_T, blocks: usize, locale: usize, state: ?u1) struct { index: ?usize, result: bool } { - return r: { - if((locale + blocks) > pool.bitmap.len) break :r .{ - .index = null, - .result = false, - }; - for(locale..(locale + blocks)) |i| { - if(pool.bitmap[i] != state orelse 1) break :r .{ - .index = @intCast(i), - .result = false, - }; - } - break :r .{ - .index = null, - .result = true, - }; - }; - } + pub fn alloc(self: *@This(), bytes: usize) Err_T![]u8 { + if(self.pool == null) return Err_T.NoNInitialized; + if(self.flags.full == 1) return Err_T.WithOutBlocks; - inline fn cast_block_to_byte(blocks: usize) usize { - return blocks * block_size; - } + var free_blocks: BitmapInt_T = @bitCast(self.bitmap); - inline fn cast_bytes_to_block(bytes: usize) usize { - return @intCast((block_size + bytes - 1) / block_size); - } + const pop_count = @popCount(free_blocks); + if(pop_count == 0 or (pop_count * block_size) < bytes) { + self.flags.full = 1; + return Err_T.WithOutBlocks; + } - inline fn mark_blocks(pool: *Pool_T, index: usize, blocks: usize) err_T!void { - // total_bytes_of_pool / block_size = bitmap.len - if((index + blocks) > pool.bitmap.len) { - return err_T.IndexOutBounds; - } - for(index..(index + blocks)) |i| - pool.bitmap[i] = 1; - } + const total_blocks: usize = (block_size + bytes) / block_size; + var initial_block_index: usize = 0; + var bit_sequence: usize = 0; + for(0..total_blocks) |_| { + bit_sequence <<= 1; + bit_sequence |= 1; + } + r: { + while(free_blocks != 0) { + initial_block_index = @ctz(free_blocks); + free_blocks >>= initial_block_index; - inline fn found_pool_of_ptr(self: *@This(), ptr: []u8) struct { ?*Pool_T, ?*Pool_T } { - var child_pool: ?*Pool_T = null; - var parent_pool: ?*Pool_T = null; - var current_pool: *Pool_T = &self.root; - while(true) { - if(check_bounds(current_pool, ptr)) { - child_pool = current_pool; break; + if(@popCount(free_blocks & bit_sequence) == total_blocks) + break :r {}; + } } - if(current_pool.flags.parent == 0) break; - parent_pool = current_pool; - current_pool = @alignCast(@ptrCast(¤t_pool.bytes.?[0])); - } - return .{ - parent_pool, - child_pool, - }; - } - inline fn check_bounds(pool: *Pool_T, ptr: []u8) bool { - return if(@intFromPtr(ptr.ptr) < @intFromPtr(&pool.bytes.?[0])) false else (@intFromPtr(ptr.ptr) - @intFromPtr(&pool.bytes.?[0])) < total_bytes_of_pool; - } + for(0..total_blocks) |i| + self.bitmap[initial_block_index + i] = 0; + self.flags.full = @intFromBool((free_blocks >> total_blocks) == 0); - fn alloc_sigle_frame(self: *@This(), bytes: usize) err_T![]u8 { - if(self.root.flags.full == 1) return err_T.OutOfMemory; - var index: usize = self.root.next orelse 0; - const blocks_to_alloc: usize = cast_bytes_to_block(bytes); - for(index..self.root.bitmap.len) |_| { - const check = check_blocks_range(&self.root, blocks_to_alloc, index, 0); - if(check.result) break; - if(check.index == null) return err_T.MemoryFrag; - index = check.index.? + 1; + return self.pool.?[ + initial_block_index * block_size + .. + ((initial_block_index * block_size) + bytes) + ]; } - try mark_blocks(&self.root, index, blocks_to_alloc); - self.root.refs += blocks_to_alloc; - self.root.flags.full = if(self.root.refs >= self.root.bitmap.len) 1 else 0; - return self.root.bytes.?[cast_block_to_byte(index)..cast_block_to_byte(index + blocks_to_alloc)]; - } - fn alloc_resized_frame(self: *@This(), bytes: usize) err_T![]u8 { - var current_pool: *Pool_T = r: { - if(self.top.?.flags.full == 1) try @call(.never_inline, resize, .{ - self - }); - break :r self.top.?; - }; - var index: usize = current_pool.next orelse blocks_reserved; - const blocks_to_alloc: usize = cast_bytes_to_block(bytes); - for(index..current_pool.bitmap.len) |_| { - const check = check_blocks_range(current_pool, blocks_to_alloc, index, 0); - if(check.result) break; - if(check.index == null) { - try @call(.never_inline, resize, .{ - self - }); - current_pool = self.top.?; - index = blocks_reserved; - break; - } - index = check.index.? + 1; + pub fn free(self: *@This(), ptr: []u8) Err_T!void { + if(self.pool == null) return Err_T.NoNInitialized; + if(((@intFromPtr(ptr) - self.pool.?) + ptr.len) >= total_bytes_of_pool) return Err_T.IndexOutBounds; + + const initial_block_index: usize = (@intFromPtr(ptr) - self.pool.?) / block_size; + const total_blocks: usize = (block_size + ptr.len) / block_size; + + for(0..total_blocks) |i| + self.bitmap[initial_block_index + i] = 1; + self.flags.full = 0; } - try mark_blocks(current_pool, index, blocks_to_alloc); - current_pool.refs += blocks_to_alloc; - current_pool.flags.full = if(current_pool.refs >= current_pool.bitmap.len) 1 else 0; - return current_pool.bytes.?[cast_block_to_byte(index)..cast_block_to_byte(index + blocks_to_alloc)]; + }; + + pub const Err_T: type = error { + AlreadyInitialized, + IndexOutBounds, + NoNInitialized, + WithOutMemory, + ResizeFailed, + }; + + root: Pool_T, + err: ?Err_T, + debug: Debug_T, + + fn init(self_vtable: *const vtable.AllocVTable_T) Err_T!void { + const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); + try self.root.init(); } - fn alloc(self_vtable: *const vtable.AllocVTable_T, bytes: usize) err_T![]u8 { + fn deinit(self_vtable: *const vtable.AllocVTable_T) void { const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); - self.top = self.top orelse &self.root; - if(bytes == 0) return err_T.ZeroBytes; - if(self.root.bytes == null) { - @branchHint(.cold); - try @call(.never_inline, pool_init, .{ - &self.root - }); - } - if(comptime personality.resize) { - return @call(.always_inline, alloc_resized_frame, .{ - self, bytes - }); - } - return @call(.always_inline, alloc_sigle_frame, .{ - self, bytes - }); + try self.root.deinit(); } - fn free_resized_frame(self: *@This(), ptr: []u8) err_T!void { - const parent_pool, const alloc_pool = self.found_pool_of_ptr(ptr); - if(alloc_pool == null) return err_T.IndexOutBounds; - const block_to_free: usize = cast_bytes_to_block(ptr.len); - //fn_init: *const fn(self: *const @This()) anyerror!void, - //fn_deinit: *const fn(self: *const @This()) void, - //fn_is_initialized: *const fn(self: *const @This()) bool, - const initial_block: usize = cast_bytes_to_block( - @intFromPtr(ptr.ptr) - @intFromPtr(&alloc_pool.?.bytes.?[0]) - ); - const check = check_blocks_range(alloc_pool.?, block_to_free, initial_block, null); // NULL == 1 - if(check.index != null and !check.result) return err_T.DoubleFree; - if((alloc_pool.?.refs - block_to_free) == blocks_reserved and parent_pool != null) { - @branchHint(.cold); - self.top = if(alloc_pool.?.flags.parent == 0) parent_pool else self.top; - parent_pool.?.flags.parent = alloc_pool.?.flags.parent; - try @call(.never_inline, pool_deinit, .{ - alloc_pool.? - }); - if(alloc_pool.?.flags.parent == 1) { - @branchHint(.cold); - const dest: *Pool_T = @alignCast(@ptrCast(&parent_pool.?.bytes.?[0])); - const src: *Pool_T = @alignCast(@ptrCast(&alloc_pool.?.bytes.?[0])); - dest.* = src.*; - } - if(builtin.is_test) - self.pools -= 1; - return; - } - for(initial_block..(initial_block + block_to_free)) |i| { - alloc_pool.?.bitmap[i] = 0; + fn alloc(self_vtable: *const vtable.AllocVTable_T, bytes: usize) Err_T![]u8 { + const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); + sw: switch((enum { alloc }).alloc) { + .alloc => { + var current_pool: *Pool_T = &self.root; + while(current_pool.alloc(bytes)) |allocation| { + // DEBUG + if(comptime options.debug) { + self.debug.allocs += 1; + self.debug.bytes += bytes; + self.debug.bmarks += (block_size + bytes) / block_size; + } + return allocation; + } else |err| switch(err) { + Err_T.NoNInitialized => return err, + Err_T.WithOutMemory => { + if(comptime !options.resize) return err; + const child: *Pool_T = current_pool.child().?; + if(current_pool.flags.child == 0) { + child.init() catch return Err_T.ResizeFailed; + child.prev = current_pool; + current_pool.flags.child = 1; + // DEBUG + if(comptime options.debug) + self.debug.pools += 1; + } + current_pool = child; + continue :sw .alloc; + }, + else => unreachable, + } + }, } - alloc_pool.?.refs -= block_to_free; - alloc_pool.?.flags.full = 0; } - fn free_single_frame(self: *@This(), ptr: []u8) err_T!void { - if(self.root.bytes == null) return err_T.NonPoolInitialized; - if(!check_bounds(&self.root, ptr)) return err_T.IndexOutBounds; - const block_to_free: usize = cast_bytes_to_block(ptr.len); - const initial_block: usize = cast_bytes_to_block( - @intFromPtr(ptr.ptr) - @intFromPtr(&self.root.bytes.?[0]) - ); - const check = check_blocks_range(&self.root, block_to_free, initial_block, null); // NULL == 1 - if(check.index != null and !check.result) return err_T.DoubleFree; - for(initial_block..(initial_block + block_to_free)) |i| { - self.root.bitmap[i] = 0; + fn free(self_vtable: *const vtable.AllocVTable_T, ptr: []u8) Err_T!void { + const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); + sw: switch((enum { free }).free) { + .free => { + var current_pool: *Pool_T = &self.root; + while(current_pool.free(ptr)) |_| { + if(~@as(BitmapInt_T, @bitCast(current_pool.bitmap)) == 1) + current_pool.deinit(); + // DEBUG + if(comptime options.debug) { + self.debug.allocs += 1; + self.debug.bytes += ptr.len; + self.debug.bmarks += (block_size + ptr.len) / block_size; + } + return; + } else |err| { + if(comptime !options.resize) return err; + current_pool = if(current_pool.flags.child == 0) return err else + @alignCast(@ptrCast(¤t_pool.pool.?[0])); + continue :sw .free; + } + } } - self.root.refs -= block_to_free; - self.root.flags.full = 0; } - fn free(self_vtable: *const vtable.AllocVTable_T, ptr: []u8) anyerror!void { + fn is_initialized(self_vtable: *const vtable.AllocVTable_T) bool { const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); - if(comptime personality.resize) { - return @call(.always_inline, free_resized_frame, .{ - self, ptr - }); - } - return @call(.always_inline, free_single_frame, .{ - self, ptr - }); + return (self.root.pool != null); } pub fn allocator(self: *@This()) vtable.AllocVTable_T { return vtable.AllocVTable_T { + .fn_init = &init, + .fn_deinit = &deinit, .fn_alloc = &alloc, .fn_free = &free, + .fn_is_initialized = &is_initialized, .private = self, }; } diff --git a/lib/saturn/kernel/memory/sba/types.zig b/lib/saturn/kernel/memory/sba/types.zig index 9195680..0adfdb6 100644 --- a/lib/saturn/kernel/memory/sba/types.zig +++ b/lib/saturn/kernel/memory/sba/types.zig @@ -3,20 +3,7 @@ // │ Author: Linuxperoxo │ // └──────────────────────────────────────────────┘ -const mm: type = @import("root").mm; -const config: type = @import("root").config; - -pub const Personality_T: type = struct { +pub const Options_T: type = struct { resize: bool = true, - resizeErr: bool = false, + debug: bool = false, }; - -//pub const Cache_T: type = struct { -// size: CacheSize_T = .auto, -// -// pub const CacheSize_T: type = enum(u3) { -// small = 4, -// large = 2, -// huge = 1, -// }; -//}; diff --git a/lib/saturn/kernel/memory/vtable/vtable.zig b/lib/saturn/kernel/memory/vtable/vtable.zig index 988b1ac..2a5f71f 100644 --- a/lib/saturn/kernel/memory/vtable/vtable.zig +++ b/lib/saturn/kernel/memory/vtable/vtable.zig @@ -6,9 +6,9 @@ pub const AllocVTable_T: type = struct { fn_alloc: *const fn(self: *const @This(), bytes: usize) anyerror![]u8, fn_free: *const fn(self: *const @This(), ptr: []u8) anyerror!void, - //fn_init: *const fn(self: *const @This()) anyerror!void, - //fn_deinit: *const fn(self: *const @This()) void, - //fn_is_initialized: *const fn(self: *const @This()) bool, + fn_init: *const fn(self: *const @This()) anyerror!void, + fn_deinit: *const fn(self: *const @This()) void, + fn_is_initialized: *const fn(self: *const @This()) bool, private: *anyopaque, pub fn alloc(self: *const @This(), comptime T: type, N: usize) anyerror![]T { @@ -29,15 +29,15 @@ pub const AllocVTable_T: type = struct { }); } - pub fn init(_: *const @This()) anyerror!void { - //return self.fn_init(); + pub fn init(self: *const @This()) anyerror!void { + return self.fn_init(); } - pub fn deinit(_: *const @This()) void { - //return self.fn_deinit(); + pub fn deinit(self: *const @This()) void { + return self.fn_deinit(); } - pub fn is_initialized(_: *const @This()) bool { - //return self.fn_is_initialized(); + pub fn is_initialized(self: *const @This()) bool { + return self.fn_is_initialized(); } }; From f5403c8667dc74acf1b32500873a7610f64e15f7 Mon Sep 17 00:00:00 2001 From: Linuxperoxo Date: Sun, 1 Feb 2026 16:48:23 -0300 Subject: [PATCH 3/6] add Allocator_T --- .../kernel/memory/allocator/allocator.zig | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/saturn/kernel/memory/allocator/allocator.zig diff --git a/lib/saturn/kernel/memory/allocator/allocator.zig b/lib/saturn/kernel/memory/allocator/allocator.zig new file mode 100644 index 0000000..a09b27f --- /dev/null +++ b/lib/saturn/kernel/memory/allocator/allocator.zig @@ -0,0 +1,62 @@ +// ┌─────────────────────────────────────────────────┐ +// │ (c) 2026 Linuxperoxo • FILE: allocator.zig │ +// │ Author: Linuxperoxo │ +// └─────────────────────────────────────────────────┘ + +pub const Err_T: type = error { + InitFailed, + IndexOutBounds, + WithoutMemory, + AlreadyInitialized, + NoNInitialized, + InternalError, +}; + +pub const VTable_T: type = struct { + alloc: *const fn(alloc_self: *anyopaque, len: usize) Err_T![]u8, + free: *const fn(alloc_self: *anyopaque, ptr: []u8) Err_T!void, + init: *const fn(alloc_self: *anyopaque) Err_T!void, + deinit: *const fn(alloc_self: *anyopaque) Err_T!void, + is_initialized: *const fn(alloc_self: *anyopaque) bool, +}; + +pub const Allocator_T: type = struct { + vtable: *const VTable_T, + private: *anyopaque, + + pub fn alloc(self: Allocator_T, comptime T: type, num: usize) Err_T![]T { + return @alignCast(@ptrCast( + (try self.vtable.alloc(self.private, num))[0..(@sizeOf(T) * num)] + )); + } + + pub fn free(self: Allocator_T, ptr: anytype) Err_T!void { + const ptr_size, const ptr_child = comptime sw: switch(@typeInfo(@TypeOf(ptr))) { + .pointer => |ptr_info| { + if(ptr_info.size == .c or ptr_info.size == .many) + continue :sw @typeInfo(void); + break :sw .{ + ptr_info.size, + ptr_info.child + }; + }, + else => @compileError("expect slice or single pointer to free. Found \"" ++ @typeName(@TypeOf(ptr)) ++ "\""), + }; + const slice = ptr[0..@sizeOf(ptr_child) * ( + if(comptime ptr_size == .one) 1 else ptr.len + )]; + return self.vtable.free(self.private, slice); + } + + pub fn init(self: Allocator_T) Err_T!void { + return self.vtable.init(self.private); + } + + pub fn deinit(self: Allocator_T) void { + return self.vtable.deinit(self.private); + } + + pub fn is_initialized(self: Allocator_T) bool { + return self.vtable.is_initialized(self.private); + } +}; From d286b3faea65459f29ebe55c1af28f9c897dab3b Mon Sep 17 00:00:00 2001 From: Linuxperoxo Date: Sun, 1 Feb 2026 20:02:19 -0300 Subject: [PATCH 4/6] sba ref --- lib/saturn/kernel/memory/memory.zig | 2 +- lib/saturn/kernel/memory/sba/sba.zig | 477 +++++++++++++-------- lib/saturn/kernel/memory/sba/test.zig | 177 -------- lib/saturn/kernel/memory/vtable/vtable.zig | 43 -- 4 files changed, 309 insertions(+), 390 deletions(-) delete mode 100644 lib/saturn/kernel/memory/sba/test.zig delete mode 100644 lib/saturn/kernel/memory/vtable/vtable.zig diff --git a/lib/saturn/kernel/memory/memory.zig b/lib/saturn/kernel/memory/memory.zig index a4e5572..228747f 100644 --- a/lib/saturn/kernel/memory/memory.zig +++ b/lib/saturn/kernel/memory/memory.zig @@ -5,4 +5,4 @@ pub const soa: type = @import("soa/soa.zig"); pub const sba: type = @import("sba/sba.zig"); -pub const vtable: type = @import("vtable/vtable.zig"); +pub const allocator: type = @import("allocator/allocator.zig"); diff --git a/lib/saturn/kernel/memory/sba/sba.zig b/lib/saturn/kernel/memory/sba/sba.zig index 313a763..a317c0c 100644 --- a/lib/saturn/kernel/memory/sba/sba.zig +++ b/lib/saturn/kernel/memory/sba/sba.zig @@ -9,30 +9,63 @@ const mm: type = @import("root").code.mm; const types: type = @import("types.zig"); const config: type = @import("root").config; const std: type = @import("std"); -const vtable: type = if(!builtin.is_test) @import("root").lib.memory.vtable else struct { - pub const AllocVTable_T: type = struct { - fn_alloc: *const fn(self: *const @This(), bytes: usize) anyerror![]u8, - fn_free: *const fn(self: *const @This(), ptr: []u8) anyerror!void, - private: *anyopaque, - - pub fn alloc(self: *const @This(), comptime T: type, N: usize) anyerror![]T { - return @alignCast(@ptrCast( - (try self.fn_alloc(self, @sizeOf(T) * N)) - )); - } - pub fn free(self: *const @This(), ptr: anytype) anyerror!void { - return self.fn_free(self, comptime sw: switch(@typeInfo(@TypeOf(ptr))) { - .pointer => |p| { - if(p.size == .c or p.size == .many) - continue :sw @typeInfo(void); - break :sw @alignCast(@ptrCast(ptr[0..if(p.size == .one) 1 else ptr.len])); - }, +const Err_T: type = if(!builtin.is_test) @import("root").lib.memory.allocator.Err_T else error { + InitFailed, + IndexOutBounds, + WithoutMemory, + AlreadyInitialized, + NoNInitialized, + InternalError, +}; - else => @compileError("expect slice or single pointer to free. Found \"" ++ @typeName(@TypeOf(ptr)) ++ "\""), - }); - } - }; +const VTable_T: type = if(!builtin.is_test) @import("root").lib.memory.allocator.VTable_T else struct { + alloc: *const fn(alloc_self: *anyopaque, len: usize) Err_T![]u8, + free: *const fn(alloc_self: *anyopaque, ptr: []u8) Err_T!void, + init: *const fn(alloc_self: *anyopaque) Err_T!void, + deinit: *const fn(alloc_self: *anyopaque) Err_T!void, + is_initialized: *const fn(alloc_self: *anyopaque) bool, +}; + +const Allocator_T: type = if(!builtin.is_test) @import("root").lib.memory.allocator.Allocator_T else struct { + vtable: *const VTable_T, + private: *anyopaque, + + pub fn alloc(self: Allocator_T, comptime T: type, num: usize) Err_T![]T { + return @alignCast(@ptrCast( + (try self.vtable.alloc(self.private, num))[0..(@sizeOf(T) * num)] + )); + } + + pub fn free(self: Allocator_T, ptr: anytype) Err_T!void { + const ptr_size, const ptr_child = comptime sw: switch(@typeInfo(@TypeOf(ptr))) { + .pointer => |ptr_info| { + if(ptr_info.size == .c or ptr_info.size == .many) + continue :sw @typeInfo(void); + break :sw .{ + ptr_info.size, + ptr_info.child + }; + }, + else => @compileError("expect slice or single pointer to free. Found \"" ++ @typeName(@TypeOf(ptr)) ++ "\""), + }; + const slice = ptr[0..@sizeOf(ptr_child) * ( + if(comptime ptr_size == .one) 1 else ptr.len + )]; + return self.vtable.free(self.private, slice); + } + + pub fn init(self: Allocator_T) Err_T!void { + return self.vtable.init(self.private); + } + + pub fn deinit(self: Allocator_T) void { + return self.vtable.deinit(self.private); + } + + pub fn is_initialized(self: Allocator_T) bool { + return self.vtable.is_initialized(self.private); + } }; // === Saturn Byte Allocator === @@ -53,11 +86,13 @@ pub fn buildByteAllocator( allocs: usize = 0, pools: usize = 0, bytes: usize = 0, + size: usize = block_size, + blocks: usize = vector_blocks, bmarks: usize = 0, } else void; const block_size: comptime_int = block orelse default_block_size; - const vector_blocks: comptime_int = ((total_bytes_of_pool + block_size) / block_size); + const vector_blocks: comptime_int = total_bytes_of_pool / block_size; const BitmapInt_T: type = @Type(.{ .int = .{ @@ -66,214 +101,318 @@ pub fn buildByteAllocator( }, }); + const ExpectInt_T: type = @Type(.{ + .int = .{ + .bits = vector_blocks + 1, + .signedness = .unsigned, + }, + }); + + const Bitmap_T: type = @Vector(vector_blocks, u1); + pub const Pool_T: type = struct { pool: ?*[total_bytes_of_pool]u8 = null, prev: ?*Pool_T = null, - bitmap: @Vector(vector_blocks, u1) = @splat(1), - private: *anyopaque, + bitmap: Bitmap_T = @splat(1), + private: ?*anyopaque = null, flags: packed struct { child: u1 = 0, full: u1 = 0, } = .{}, - pub fn child(self: *@This()) ?*Pool_T { - return if(self.pool == null) null else - @alignCast(@ptrCast(&self.pool.?[0])); + // ============================== TEST + inline fn test_init_pool(self: *@This()) Err_T!void { + var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; + var gpa_allocator = gpa.allocator(); + self.pool = @alignCast(@ptrCast(gpa_allocator.alloc(u8, total_bytes_of_pool) catch unreachable)); + self.private = &gpa_allocator; } - pub fn init(self: *@This()) anyerror!void { - self.* = .{}; - self.pool = sw: switch(comptime ar.target_code.target) { + inline fn test_deinit_pool(_: *@This()) Err_T!void { + return; + } + // ============================== + + // ============================ TARGET + inline fn target_init_pool(self: *@This()) Err_T!void { + switch(comptime ar.target_code.target) { .i386 => { - const page = try mm.alloc_page(); + const page = ar.target_code.mm.alloc_page() + catch return Err_T.InternalError; + self.pool = page.virtual; self.private = page; - break :sw @alignCast(@ptrCast(page.virtual.ptr)); }, - else => unreachable, - }; - if(comptime options.resize) { - // deixa blocos reservados para expandir o alocador - // quando esse pool estiver cheio - const initial_block_index: usize = 0; - const total_blocks: usize = (block_size + @sizeOf(Pool_T)) / block_size; - for(initial_block_index..total_blocks) |i| - self.bitmap[i] = 0; + else => @compileError(""), } } - pub fn deinit(self: *@This()) anyerror!void { - const private: *anyopaque = self.private; - if(comptime options.resize) { - if(self.flags.child == 1) { - // caso o pool atual tenha um filho - @as(*Pool_T, @alignCast(@ptrCast( - // * caso tenha um pai, passamos o filhos do pool - // atual para seu pai - // * caso nao tenha um pai, substituimos o pool - // atual pelo seu filho - if(self.prev != null) &self.prev.?.pool.?[0] else self - ))).* = self.child().?.*; - } else { - // caso tenha um pai, invalidamos esse pool como filho - if(self.prev != null) self.prev.?.flags.child = 0; - } - } + inline fn target_deinit_pool(_: *@This()) Err_T!void { switch(comptime ar.target_code.target) { - .i386 => try mm.free_page(@alignCast(@ptrCast(private))), - else => unreachable, + .i386 => { + + }, + else => @compileError(""), } - self.pool = null; } + // ================================== - pub fn alloc(self: *@This(), bytes: usize) Err_T![]u8 { - if(self.pool == null) return Err_T.NoNInitialized; - if(self.flags.full == 1) return Err_T.WithOutBlocks; + // ================================== AUX + inline fn child(self: *@This()) *Pool_T { + return @alignCast(@ptrCast(&self.pool.?[0])); + } - var free_blocks: BitmapInt_T = @bitCast(self.bitmap); + inline fn initialized(self: *@This()) Err_T!void { + return if(self.pool == null) Err_T.NoNInitialized else + {}; + } - const pop_count = @popCount(free_blocks); - if(pop_count == 0 or (pop_count * block_size) < bytes) { - self.flags.full = 1; - return Err_T.WithOutBlocks; - } + inline fn any(self: *@This()) Err_T!void { + return if(@as(BitmapInt_T, @bitCast(self.bitmap)) == 0 or self.flags.full == 1) Err_T.WithoutMemory else + {}; + } - const total_blocks: usize = (block_size + bytes) / block_size; - var initial_block_index: usize = 0; - var bit_sequence: usize = 0; - for(0..total_blocks) |_| { - bit_sequence <<= 1; - bit_sequence |= 1; - } - r: { - while(free_blocks != 0) { - initial_block_index = @ctz(free_blocks); - free_blocks >>= initial_block_index; + inline fn outbounds(self: *@This(), ptr: []u8) Err_T!void { + return if((@intFromPtr(ptr.ptr) - @intFromPtr(self.pool.?)) > total_bytes_of_pool) Err_T.IndexOutBounds else + {}; + } + + inline fn empty(self: *@This()) bool { + return ~@as(BitmapInt_T, @bitCast(self.bitmap)) == 0; + } + // ==================================== + + pub noinline fn init(self: *@This()) Err_T!void { + self.* = .{}; + return if(!builtin.is_test) self.target_init_pool() else + self.test_init_pool(); + } + + pub noinline fn deinit(self: *@This()) Err_T!void { + if(self.pool == null) return; + if(!builtin.is_test) try self.target_deinit_pool() else + try self.test_deinit_pool(); + self.* = .{}; + } + + pub noinline fn alloc(self: *@This(), bytes: usize) Err_T![]u8 { + try self.initialized(); + try self.any(); + + const blocks: usize = (block_size + (bytes - 1)) / block_size; + // ExpectInt_T deve ter 1 bit extra que BitmapInt_T isso evita um overflow fazendo shift em @intCast(blocks) caso + // blocks for exatamente o tamanho total do bitmap + const expect: BitmapInt_T = @truncate((@as(ExpectInt_T, 1) << @intCast(blocks)) - 1); - if(@popCount(free_blocks & bit_sequence) == total_blocks) + var bitmap: BitmapInt_T = @bitCast(self.bitmap); + var ctz: usize = @ctz(bitmap); + var initial_block_index: usize = ctz; + + bitmap >>= @intCast(ctz); + + r: { + while(bitmap != 0) { + if((bitmap & expect) == expect) break :r {}; + bitmap >>= @intCast(blocks); + ctz = @ctz(bitmap); + bitmap >>= @intCast(ctz); + initial_block_index += (blocks + ctz); } + return Err_T.WithoutMemory; } - for(0..total_blocks) |i| + const pool_initial_index: usize = initial_block_index * block_size; + const pool_final_index: usize = pool_initial_index + (blocks * block_size); + + for(0..blocks) |i| self.bitmap[initial_block_index + i] = 0; - self.flags.full = @intFromBool((free_blocks >> total_blocks) == 0); + self.flags.full = @intFromBool((bitmap >> @intCast(blocks)) == 0); - return self.pool.?[ - initial_block_index * block_size - .. - ((initial_block_index * block_size) + bytes) - ]; + return self.pool.?[pool_initial_index..pool_final_index]; } - pub fn free(self: *@This(), ptr: []u8) Err_T!void { - if(self.pool == null) return Err_T.NoNInitialized; - if(((@intFromPtr(ptr) - self.pool.?) + ptr.len) >= total_bytes_of_pool) return Err_T.IndexOutBounds; + pub noinline fn free(self: *@This(), ptr: []u8) Err_T!void { + try self.initialized(); + try self.outbounds(ptr); - const initial_block_index: usize = (@intFromPtr(ptr) - self.pool.?) / block_size; - const total_blocks: usize = (block_size + ptr.len) / block_size; + const blocks: usize = (block_size + (ptr.len - 1)) / block_size; + const initial_block_index: usize = (@intFromPtr(ptr.ptr) - @intFromPtr(self.pool.?)) / block_size; - for(0..total_blocks) |i| + for(0..blocks) |i| { self.bitmap[initial_block_index + i] = 1; + } self.flags.full = 0; } }; - pub const Err_T: type = error { - AlreadyInitialized, - IndexOutBounds, - NoNInitialized, - WithOutMemory, - ResizeFailed, - }; + root: Pool_T = .{}, + debug: Debug_T = .{}, - root: Pool_T, - err: ?Err_T, - debug: Debug_T, - - fn init(self_vtable: *const vtable.AllocVTable_T) Err_T!void { - const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); + noinline fn init(context: *anyopaque) Err_T!void { + const self: *@This() = @alignCast(@ptrCast(context)); try self.root.init(); + // ============== DEBUG + if(comptime options.debug) { + self.debug.pools += 1; + } + // ===================== } - fn deinit(self_vtable: *const vtable.AllocVTable_T) void { - const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); + noinline fn deinit(context: *anyopaque) Err_T!void { + const self: *@This() = @alignCast(@ptrCast(context)); try self.root.deinit(); + // ============== DEBUG + if(comptime options.debug) { + self.debug.pools -= 1; + } + // ===================== } - fn alloc(self_vtable: *const vtable.AllocVTable_T, bytes: usize) Err_T![]u8 { - const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); - sw: switch((enum { alloc }).alloc) { - .alloc => { - var current_pool: *Pool_T = &self.root; - while(current_pool.alloc(bytes)) |allocation| { - // DEBUG - if(comptime options.debug) { - self.debug.allocs += 1; - self.debug.bytes += bytes; - self.debug.bmarks += (block_size + bytes) / block_size; - } - return allocation; - } else |err| switch(err) { - Err_T.NoNInitialized => return err, - Err_T.WithOutMemory => { - if(comptime !options.resize) return err; - const child: *Pool_T = current_pool.child().?; - if(current_pool.flags.child == 0) { - child.init() catch return Err_T.ResizeFailed; - child.prev = current_pool; - current_pool.flags.child = 1; - // DEBUG - if(comptime options.debug) - self.debug.pools += 1; - } - current_pool = child; - continue :sw .alloc; - }, - else => unreachable, + noinline fn alloc(context: *anyopaque, bytes: usize) Err_T![]u8 { + const self: *@This() = @as(*@This(), @alignCast(@ptrCast(context))); + var current_pool: *Pool_T = &self.root; + while(true) { + while(current_pool.alloc(bytes)) |allocation| { + // ============= DEBUG + if(comptime options.debug) { + self.debug.allocs += 1; + self.debug.bytes += bytes; } - }, + // ================== + return allocation; + } else |err| switch(err) { + Err_T.WithoutMemory => { + if(comptime !options.resize) return err; + if(current_pool.flags.child == 0) { + try current_pool.child().init(); + current_pool.flags.child = 1; + // ============== DEBUG + if(comptime options.debug) { + self.debug.pools += 1; + } + // ===================== + } + current_pool = current_pool.child(); + }, + else => return err, + } } + unreachable; } - fn free(self_vtable: *const vtable.AllocVTable_T, ptr: []u8) Err_T!void { - const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); - sw: switch((enum { free }).free) { - .free => { - var current_pool: *Pool_T = &self.root; - while(current_pool.free(ptr)) |_| { - if(~@as(BitmapInt_T, @bitCast(current_pool.bitmap)) == 1) - current_pool.deinit(); - // DEBUG + noinline fn free(context: *anyopaque , ptr: []u8) Err_T!void { + const self: *@This() = @as(*@This(), @alignCast(@ptrCast(context))); + var current_pool: *Pool_T = &self.root; + while(true) { + while(current_pool.free(ptr)) |_| { + if(current_pool.empty() and current_pool.flags.child == 1) { + if(current_pool.prev == null) { + const child_pool: Pool_T = current_pool.child().*; + try current_pool.deinit(); + current_pool.* = child_pool; + } else { + @as(*Pool_T, @alignCast(@ptrCast(¤t_pool.prev.?.pool.?[0]))).* + = @as(*Pool_T, @alignCast(@ptrCast(¤t_pool.pool.?[0]))).*; + try current_pool.deinit(); + } + // ============= DEBUG if(comptime options.debug) { - self.debug.allocs += 1; - self.debug.bytes += ptr.len; - self.debug.bmarks += (block_size + ptr.len) / block_size; + self.debug.pools -= 1; } - return; - } else |err| { - if(comptime !options.resize) return err; - current_pool = if(current_pool.flags.child == 0) return err else - @alignCast(@ptrCast(¤t_pool.pool.?[0])); - continue :sw .free; + // =================== } + // ============= DEBUG + if(comptime options.debug) { + self.debug.allocs -= 1; + self.debug.bytes -= ptr.len; + } + // =================== + return; + } else |err| switch(err) { + Err_T.IndexOutBounds => { + if(comptime !options.resize) return err; + if(current_pool.flags.child == 0) return err; + current_pool = current_pool.child(); + }, + else => return err, } } + unreachable; } - fn is_initialized(self_vtable: *const vtable.AllocVTable_T) bool { - const self: *@This() = @alignCast(@ptrCast(self_vtable.private)); + noinline fn is_initialized(context: *anyopaque) bool { + const self: *@This() = @alignCast(@ptrCast(context)); return (self.root.pool != null); } - pub fn allocator(self: *@This()) vtable.AllocVTable_T { - return vtable.AllocVTable_T { - .fn_init = &init, - .fn_deinit = &deinit, - .fn_alloc = &alloc, - .fn_free = &free, - .fn_is_initialized = &is_initialized, + pub inline fn allocator(self: *@This()) Allocator_T { + return Allocator_T { .private = self, + .vtable = &VTable_T { + .alloc = &alloc, + .free = &free, + .init = &init, + .deinit = &deinit, + .is_initialized = &is_initialized, + } }; } }; } + +// TEST WITHOUT RESIZE + +test "Full Alloc" { + var sba = buildByteAllocator(null, .{ + .resize = false, + .debug = true, + }) {}; + + var allocator = sba.allocator(); + try allocator.init(); + + var current: []u8 = undefined; + var prev: []u8 = @as([*]u8, @ptrFromInt(0x10))[0..1]; + + for(0..sba.debug.blocks) |_| { + current = try allocator.alloc(u8, 1); + if(@intFromPtr(current.ptr) <= @intFromPtr(prev.ptr)) + return error.PointerOverrider; + prev = current; + } + if(allocator.alloc(u8, 1)) |_| { + return error.AllocWithoutSpace; + } else |_| { + return; + } +} + +test "Full Free" { + +} + +// TEST WITH RESIZE + +test "Resized Full Alloc" { + var sba = buildByteAllocator(null, .{ + .resize = true, + .debug = true, + }) {}; + + var allocator = sba.allocator(); + try allocator.init(); + + var current: []u8 = undefined; + //var prev: []u8 = @as([*]u8, @ptrFromInt(0x10))[0..1]; + + for(0..sba.debug.blocks * 12) |_| { + current = try allocator.alloc(u8, 1); + } + + if(sba.debug.pools != 12) + return error.ResizeMiss; + + std.debug.print("{any}\n", .{ + sba.debug + }); +} diff --git a/lib/saturn/kernel/memory/sba/test.zig b/lib/saturn/kernel/memory/sba/test.zig deleted file mode 100644 index e372920..0000000 --- a/lib/saturn/kernel/memory/sba/test.zig +++ /dev/null @@ -1,177 +0,0 @@ -// ┌──────────────────────────────────────────────┐ -// │ (c) 2025 Linuxperoxo • FILE: test.zig │ -// │ Author: Linuxperoxo │ -// └──────────────────────────────────────────────┘ - -// === Teste Info === -// -// OpenSaturn: 0.1.1 -// OS: Gentoo Linux x86_64 -// Zig: 0.15.2 -// Tester: Linuxperoxo -// Status: OK - -const std: type = @import("std"); - -const block_size: usize = 0x10; - -const buildByteAllocator = @import("sba.zig").buildByteAllocator; - -const TestSingleErr_T: type = error { - BlockAlignMiss, - NonFullAlloc, - NonFullFree, -}; - -const TestResizedErr_T: type = TestSingleErr_T || error { - NonResize, - ResizeOutOfTime, - InvalidPoolNum, - NonNewFrame, - NonReachRoot, - InvalidParent, -}; - -const SBASingle_T: type = buildByteAllocator( - block_size, - .{ - .resize = false, - } -); - -const SBASinglePool_T: type = SBASingle_T.Pool_T; - -const SBAResized_T: type = buildByteAllocator( - block_size, - .{ - .resize = true, - } -); - -const SBAResizedPool_T: type = SBAResized_T.Pool_T; - -fn full_alloc(comptime SBA_T: type, allocator: *SBA_T) anyerror!void { - for(if(SBA_T == SBASingle_T) 0 else SBA_T.blocks_reserved..SBA_T.Pool_T.pool_bitmap_len) |_| { - _ = try allocator.alloc(u8, 1); - } -} - -fn full_free(comptime SBA_T: type, pool: *SBA_T.Pool_T, allocator: *SBA_T, index: usize) anyerror!void { - for(index..SBA_T.Pool_T.pool_bitmap_len) |i| { - const slice: []u8 = @as([*]u8, @alignCast(@ptrCast(&pool.bytes.?[i * SBA_T.block_size])))[0..1]; - try allocator.free( - slice - ); - } -} - -fn bitmap_check(comptime Pool_T: type, pool: *Pool_T, state: u1, index: usize) bool { - for(index..Pool_T.pool_bitmap_len) |i| { - if(pool.bitmap[i] != state) return false; - } - return true; -} - -test "SBA Alloc Test For Single Frame" { - var sba_allocator = @constCast(&SBASingle_T {}).allocator(); - var old_ptr: ?[]u8 = null; - for(0..SBASingle_T.Pool_T.pool_bitmap_len) |_| { - const ptr = try sba_allocator.alloc(u8, 1); - if(old_ptr != null) { - if((@intFromPtr(ptr.ptr) - @intFromPtr(old_ptr.?.ptr)) != SBASingle_T.block_size) return TestSingleErr_T.BlockAlignMiss; - } - old_ptr = ptr; - } - if(!bitmap_check(SBASingle_T.Pool_T, &sba_allocator.root, 1, 0)) return TestSingleErr_T.NonFullAlloc; -} - -test "SBA Free Test For Single Frame" { - var sba_allocator: SBASingle_T = .{}; - for(0..4) |_| { - try full_alloc(SBASingle_T, &sba_allocator); - try full_free(SBASingle_T, &sba_allocator.root, &sba_allocator, 0); - } - if(!bitmap_check(SBASingle_T.Pool_T, &sba_allocator.root, 0, 0)) return TestSingleErr_T.NonFullFree; -} - -test "SBA Alloc Test For Resized Frame" { - var sba_allocator: SBAResized_T = .{}; - var old_ptr: ?[]u8 = null; - for(SBAResized_T.blocks_reserved..SBAResized_T.Pool_T.pool_bitmap_len) |_| { - const ptr = try sba_allocator.alloc(u8, 1); - if(old_ptr != null) { - if((@intFromPtr(ptr.ptr) - @intFromPtr(old_ptr.?.ptr)) != SBAResized_T.block_size) return TestResizedErr_T.BlockAlignMiss; - } - old_ptr = ptr; - } - if(!bitmap_check(SBAResized_T.Pool_T, sba_allocator.top.?, 1, SBAResized_T.blocks_reserved)) return TestResizedErr_T.NonFullAlloc; - - // resized test - - if(sba_allocator.pools > 1) return TestResizedErr_T.ResizeOutOfTime; - _ = try sba_allocator.alloc(u8, 1); - if(sba_allocator.pools == 1) return TestResizedErr_T.NonResize; - old_ptr = null; - for(SBAResized_T.blocks_reserved..SBAResized_T.Pool_T.pool_bitmap_len - 1) |_| { - const ptr = try sba_allocator.alloc(u8, 1); - if(old_ptr != null) { - if((@intFromPtr(ptr.ptr) - @intFromPtr(old_ptr.?.ptr)) != SBAResized_T.block_size) return TestResizedErr_T.BlockAlignMiss; - } - old_ptr = ptr; - } - if(!bitmap_check(SBAResized_T.Pool_T, sba_allocator.top.?, 1, SBAResized_T.blocks_reserved)) return TestResizedErr_T.NonFullAlloc; -} - -test "SBA Free Test For Resized Root Frame" { - var sba_allocator: SBAResized_T = .{}; - try full_alloc(SBAResized_T, &sba_allocator); - try full_free(SBAResized_T, sba_allocator.top.?, &sba_allocator, SBAResized_T.blocks_reserved); - full_free(SBAResized_T, sba_allocator.top.?, &sba_allocator, SBAResized_T.blocks_reserved) catch |err| switch(err) { - SBAResized_T.err_T.DoubleFree => {}, - else => return err, - }; - if(!bitmap_check(SBAResized_T.Pool_T, sba_allocator.top.?, 0, SBAResized_T.blocks_reserved)) return TestResizedErr_T.NonFullFree; -} - -test "SBA Free Test For Resized Top Frame" { - var sba_allocator: SBAResized_T = .{}; - for(0..4) |_| { - try full_alloc(SBAResized_T, &sba_allocator); - } - for(0..4) |_| { - try full_free(SBAResized_T, sba_allocator.top.?, &sba_allocator, SBAResized_T.blocks_reserved); - } - if(&sba_allocator.root != sba_allocator.top.?) return TestResizedErr_T.NonReachRoot; - if(sba_allocator.root.flags.parent == 1) return TestResizedErr_T.InvalidParent; - if(sba_allocator.pools > 1) return TestResizedErr_T.InvalidPoolNum; - if(sba_allocator.root.refs != SBAResized_T.blocks_reserved) return TestResizedErr_T.NonFullFree; -} - -test "SBA Free Test For Resized Mid Frame" { - var sba_allocator: SBAResized_T = .{}; - for(0..64) |_| { - try full_alloc(SBAResized_T, &sba_allocator); - } - const pool: *SBAResized_T.Pool_T = @alignCast(@ptrCast(&sba_allocator.root.bytes.?[0])); - for(0..63) |_| { - try full_free(SBAResized_T, pool, &sba_allocator, SBAResized_T.blocks_reserved); - } - try full_free(SBAResized_T, sba_allocator.top.?, &sba_allocator, SBAResized_T.blocks_reserved); - if(&sba_allocator.root != sba_allocator.top.?) return TestResizedErr_T.NonReachRoot; - if(sba_allocator.root.flags.parent == 1) return TestResizedErr_T.InvalidParent; - if(sba_allocator.pools > 1) return TestResizedErr_T.InvalidPoolNum; - if(sba_allocator.root.refs != SBAResized_T.blocks_reserved) return TestResizedErr_T.NonFullFree; -} - -test "SBA Resize After Free For Resized Frame" { - var sba_allocator: SBAResized_T = .{}; - try full_alloc(SBAResized_T, &sba_allocator); - try full_alloc(SBAResized_T, &sba_allocator); - try full_free(SBAResized_T, sba_allocator.top.?, &sba_allocator, SBAResized_T.blocks_reserved); - if(&sba_allocator.root != sba_allocator.top.?) return TestResizedErr_T.NonReachRoot; - const free_pool: *SBAResized_T.Pool_T = @alignCast(@ptrCast(&sba_allocator.top.?.bytes.?[0])); - const unsed_frame: []u8 = free_pool.bytes.?; - try full_alloc(SBAResized_T, &sba_allocator); - const new_frame: []u8 = free_pool.bytes.?; - if(@intFromPtr(new_frame.ptr) == @intFromPtr(unsed_frame.ptr)) return TestResizedErr_T.NonNewFrame; -} diff --git a/lib/saturn/kernel/memory/vtable/vtable.zig b/lib/saturn/kernel/memory/vtable/vtable.zig deleted file mode 100644 index 2a5f71f..0000000 --- a/lib/saturn/kernel/memory/vtable/vtable.zig +++ /dev/null @@ -1,43 +0,0 @@ -// ┌──────────────────────────────────────────────┐ -// │ (c) 2026 Linuxperoxo • FILE: vtable.zig │ -// │ Author: Linuxperoxo │ -// └──────────────────────────────────────────────┘ - -pub const AllocVTable_T: type = struct { - fn_alloc: *const fn(self: *const @This(), bytes: usize) anyerror![]u8, - fn_free: *const fn(self: *const @This(), ptr: []u8) anyerror!void, - fn_init: *const fn(self: *const @This()) anyerror!void, - fn_deinit: *const fn(self: *const @This()) void, - fn_is_initialized: *const fn(self: *const @This()) bool, - private: *anyopaque, - - pub fn alloc(self: *const @This(), comptime T: type, N: usize) anyerror![]T { - return @alignCast(@ptrCast( - (self.fn_alloc(self, @sizeOf(T) * N)) - )); - } - - pub fn free(self: *const @This(), ptr: anytype) anyerror!void { - return self.fn_free(self, comptime sw: switch(@typeInfo(@TypeOf(ptr))) { - .pointer => |p| { - if(p.size == .c or p.size == .many) - continue :sw @typeInfo(void); - break :sw @alignCast(@ptrCast(ptr[0..if(p.size == .one) 1 else ptr.len])); - }, - - else => @compileError("expect slice or single pointer to free. Found \"" ++ @typeName(@TypeOf(ptr)) ++ "\""), - }); - } - - pub fn init(self: *const @This()) anyerror!void { - return self.fn_init(); - } - - pub fn deinit(self: *const @This()) void { - return self.fn_deinit(); - } - - pub fn is_initialized(self: *const @This()) bool { - return self.fn_is_initialized(); - } -}; From 21576dad1b9d49fe9c36433aa8ac05a1e77d44e0 Mon Sep 17 00:00:00 2001 From: Linuxperoxo Date: Mon, 2 Feb 2026 12:29:46 -0300 Subject: [PATCH 5/6] deinit for resized sba --- .../kernel/memory/allocator/allocator.zig | 2 +- lib/saturn/kernel/memory/sba/sba.zig | 51 ++++++++++++------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/saturn/kernel/memory/allocator/allocator.zig b/lib/saturn/kernel/memory/allocator/allocator.zig index a09b27f..3ff2705 100644 --- a/lib/saturn/kernel/memory/allocator/allocator.zig +++ b/lib/saturn/kernel/memory/allocator/allocator.zig @@ -52,7 +52,7 @@ pub const Allocator_T: type = struct { return self.vtable.init(self.private); } - pub fn deinit(self: Allocator_T) void { + pub fn deinit(self: Allocator_T) Err_T!void { return self.vtable.deinit(self.private); } diff --git a/lib/saturn/kernel/memory/sba/sba.zig b/lib/saturn/kernel/memory/sba/sba.zig index a317c0c..9fca25b 100644 --- a/lib/saturn/kernel/memory/sba/sba.zig +++ b/lib/saturn/kernel/memory/sba/sba.zig @@ -59,7 +59,7 @@ const Allocator_T: type = if(!builtin.is_test) @import("root").lib.memory.alloca return self.vtable.init(self.private); } - pub fn deinit(self: Allocator_T) void { + pub fn deinit(self: Allocator_T) Err_T!void { return self.vtable.deinit(self.private); } @@ -120,7 +120,7 @@ pub fn buildByteAllocator( full: u1 = 0, } = .{}, - // ============================== TEST +// ============================== TEST inline fn test_init_pool(self: *@This()) Err_T!void { var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; var gpa_allocator = gpa.allocator(); @@ -131,9 +131,9 @@ pub fn buildByteAllocator( inline fn test_deinit_pool(_: *@This()) Err_T!void { return; } - // ============================== +// ============================== - // ============================ TARGET +// ============================ TARGET inline fn target_init_pool(self: *@This()) Err_T!void { switch(comptime ar.target_code.target) { .i386 => { @@ -154,9 +154,9 @@ pub fn buildByteAllocator( else => @compileError(""), } } - // ================================== +// ================================== - // ================================== AUX +// ================================== AUX inline fn child(self: *@This()) *Pool_T { return @alignCast(@ptrCast(&self.pool.?[0])); } @@ -179,7 +179,7 @@ pub fn buildByteAllocator( inline fn empty(self: *@This()) bool { return ~@as(BitmapInt_T, @bitCast(self.bitmap)) == 0; } - // ==================================== +// ==================================== pub noinline fn init(self: *@This()) Err_T!void { self.* = .{}; @@ -248,6 +248,16 @@ pub fn buildByteAllocator( root: Pool_T = .{}, debug: Debug_T = .{}, +// =================== AUX + inline fn last_pool(self: *@This()) *Pool_T { + var current_pool: *Pool_T = &self.root; + while(current_pool.flags.child == 1) + current_pool = current_pool.child(); + return current_pool; + } +// ======================= + +// =================== ALLOCATOR OPS noinline fn init(context: *anyopaque) Err_T!void { const self: *@This() = @alignCast(@ptrCast(context)); try self.root.init(); @@ -260,12 +270,19 @@ pub fn buildByteAllocator( noinline fn deinit(context: *anyopaque) Err_T!void { const self: *@This() = @alignCast(@ptrCast(context)); - try self.root.deinit(); - // ============== DEBUG - if(comptime options.debug) { - self.debug.pools -= 1; + + var last: ?*Pool_T = self.last_pool(); + var prev: ?*Pool_T = null; + + while(last != null) : (last = prev) { + prev = last.?.prev; + try last.?.deinit(); + // ============== DEBUG + if(comptime options.debug) { + self.debug.pools -= 1; + } + // ===================== } - // ===================== } noinline fn alloc(context: *anyopaque, bytes: usize) Err_T![]u8 { @@ -285,6 +302,7 @@ pub fn buildByteAllocator( if(comptime !options.resize) return err; if(current_pool.flags.child == 0) { try current_pool.child().init(); + current_pool.child().prev = current_pool; current_pool.flags.child = 1; // ============== DEBUG if(comptime options.debug) { @@ -344,6 +362,7 @@ pub fn buildByteAllocator( const self: *@This() = @alignCast(@ptrCast(context)); return (self.root.pool != null); } +// ======================= pub inline fn allocator(self: *@This()) Allocator_T { return Allocator_T { @@ -388,7 +407,7 @@ test "Full Alloc" { } test "Full Free" { - + } // TEST WITH RESIZE @@ -403,7 +422,6 @@ test "Resized Full Alloc" { try allocator.init(); var current: []u8 = undefined; - //var prev: []u8 = @as([*]u8, @ptrFromInt(0x10))[0..1]; for(0..sba.debug.blocks * 12) |_| { current = try allocator.alloc(u8, 1); @@ -411,8 +429,5 @@ test "Resized Full Alloc" { if(sba.debug.pools != 12) return error.ResizeMiss; - - std.debug.print("{any}\n", .{ - sba.debug - }); + try allocator.deinit(); } From a72972bc919275c8b571233da878a057d8a88e60 Mon Sep 17 00:00:00 2001 From: Linuxperoxo Date: Mon, 2 Feb 2026 23:33:00 -0300 Subject: [PATCH 6/6] free test --- lib/saturn/kernel/memory/sba/sba.zig | 70 +++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/lib/saturn/kernel/memory/sba/sba.zig b/lib/saturn/kernel/memory/sba/sba.zig index 9fca25b..757265e 100644 --- a/lib/saturn/kernel/memory/sba/sba.zig +++ b/lib/saturn/kernel/memory/sba/sba.zig @@ -17,6 +17,7 @@ const Err_T: type = if(!builtin.is_test) @import("root").lib.memory.allocator.Er AlreadyInitialized, NoNInitialized, InternalError, + DoubleFree, }; const VTable_T: type = if(!builtin.is_test) @import("root").lib.memory.allocator.VTable_T else struct { @@ -88,7 +89,6 @@ pub fn buildByteAllocator( bytes: usize = 0, size: usize = block_size, blocks: usize = vector_blocks, - bmarks: usize = 0, } else void; const block_size: comptime_int = block orelse default_block_size; @@ -172,8 +172,9 @@ pub fn buildByteAllocator( } inline fn outbounds(self: *@This(), ptr: []u8) Err_T!void { - return if((@intFromPtr(ptr.ptr) - @intFromPtr(self.pool.?)) > total_bytes_of_pool) Err_T.IndexOutBounds else - {}; + return if((@intFromPtr(ptr.ptr) > @intFromPtr(&self.pool.?[comptime(total_bytes_of_pool - 1)])) + or @intFromPtr(ptr.ptr) < @intFromPtr(&self.pool.?[0])) Err_T.IndexOutBounds else + {}; } inline fn empty(self: *@This()) bool { @@ -239,6 +240,8 @@ pub fn buildByteAllocator( const initial_block_index: usize = (@intFromPtr(ptr.ptr) - @intFromPtr(self.pool.?)) / block_size; for(0..blocks) |i| { + if(self.bitmap[initial_block_index + i] == 1) + return Err_T.DoubleFree; self.bitmap[initial_block_index + i] = 1; } self.flags.full = 0; @@ -328,6 +331,7 @@ pub fn buildByteAllocator( const child_pool: Pool_T = current_pool.child().*; try current_pool.deinit(); current_pool.* = child_pool; + current_pool.prev = null; } else { @as(*Pool_T, @alignCast(@ptrCast(¤t_pool.prev.?.pool.?[0]))).* = @as(*Pool_T, @alignCast(@ptrCast(¤t_pool.pool.?[0]))).*; @@ -407,12 +411,45 @@ test "Full Alloc" { } test "Full Free" { - + var sba = buildByteAllocator(null, .{ + .resize = false, + .debug = true, + }) {}; + + const BitmapInt_T: type = @Type(.{ + .int = .{ + .bits = @truncate(total_bytes_of_pool_test / 16), + .signedness = .unsigned, + }, + }); + + var allocator = sba.allocator(); + try allocator.init(); + + var allocs: [256][]u8 = undefined; + for(0..sba.debug.blocks) |i| { + allocs[i] = try allocator.alloc(u8, 1); + } + + for(0..sba.debug.blocks) |i| { + try allocator.free( + allocs[i] + ); + allocator.free(allocs[i]) catch |err| switch(err) { + Err_T.DoubleFree => continue, + else => return err, + }; + return error.NoNDoubleFree; + } + + if(~@as(BitmapInt_T, @bitCast(sba.root.bitmap)) != 0) return error.BusyBlock; + if(sba.debug.allocs != 0) return error.HaveAllocs; + if(sba.debug.bytes != 0) return error.HaveBytes; } // TEST WITH RESIZE -test "Resized Full Alloc" { +test "Resized Full Alloc And Free" { var sba = buildByteAllocator(null, .{ .resize = true, .debug = true, @@ -421,13 +458,22 @@ test "Resized Full Alloc" { var allocator = sba.allocator(); try allocator.init(); - var current: []u8 = undefined; - - for(0..sba.debug.blocks * 12) |_| { - current = try allocator.alloc(u8, 1); + var allocs: [256 * 12][]u8 = undefined; + for(0..sba.debug.blocks * 12) |i| { + allocs[i] = try allocator.alloc(u8, 1); } - if(sba.debug.pools != 12) - return error.ResizeMiss; - try allocator.deinit(); + if(sba.debug.pools != 12) return error.ResizeMiss; + if(sba.debug.bytes != (sba.debug.blocks * 12)) return error.ByteMiss; + if(sba.debug.allocs != (sba.debug.blocks * 12)) return error.AllocMiss; + + for(0..12) |i| { + const total_pool: usize = sba.debug.pools; + for(0..256) |j| { + try allocator.free( + allocs[(256 * i) + j] + ); + } + if((total_pool - 1) != sba.debug.pools and total_pool > 1) return error.EmptyChildPool; + } }