diff --git a/lib/saturn/kernel/memory/allocator/allocator.zig b/lib/saturn/kernel/memory/allocator/allocator.zig new file mode 100644 index 0000000..3ff2705 --- /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) Err_T!void { + return self.vtable.deinit(self.private); + } + + pub fn is_initialized(self: Allocator_T) bool { + return self.vtable.is_initialized(self.private); + } +}; diff --git a/lib/saturn/kernel/memory/memory.zig b/lib/saturn/kernel/memory/memory.zig index 0fb0dae..228747f 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 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 c51aa1d..757265e 100644 --- a/lib/saturn/kernel/memory/sba/sba.zig +++ b/lib/saturn/kernel/memory/sba/sba.zig @@ -3,12 +3,72 @@ // │ 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 Err_T: type = if(!builtin.is_test) @import("root").lib.memory.allocator.Err_T else error { + InitFailed, + IndexOutBounds, + WithoutMemory, + AlreadyInitialized, + NoNInitialized, + InternalError, + DoubleFree, +}; + +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) Err_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 === const total_bytes_of_pool_test: comptime_int = 4096; @@ -20,323 +80,400 @@ 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, + size: usize = block_size, + blocks: usize = vector_blocks, + } 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; - 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) { - 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; + const ExpectInt_T: type = @Type(.{ + .int = .{ + .bits = vector_blocks + 1, + .signedness = .unsigned, + }, + }); - pub const Private_T: type = if(builtin.is_test) void else switch(config.arch.options.Target) { - .i386 => mm.AllocPage_T, - else => void, - }; - }; + const Bitmap_T: type = @Vector(vector_blocks, u1); - pub const err_T: type = error { - PoolInitFailed, - PoolResizeFailed, - OutOfMemory, - IndexOutBounds, - UndefinedAction, - MemoryFrag, - ZeroBytes, - NonPoolInitialized, - PoolOverflow, - DoubleFree, - }; + pub const Pool_T: type = struct { + pool: ?*[total_bytes_of_pool]u8 = null, + prev: ?*Pool_T = null, + bitmap: Bitmap_T = @splat(1), + private: ?*anyopaque = null, + flags: packed struct { + child: u1 = 0, + full: u1 = 0, + } = .{}, - fn pool_init(pool: *Pool_T) err_T!void { - if(builtin.is_test) { +// ============================== TEST + inline fn test_init_pool(self: *@This()) Err_T!void { var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; - var allocator = gpa.allocator(); - pool.bytes = allocator.alloc(u8, total_bytes_of_pool_test) catch return err_T.PoolInitFailed; - return; + var gpa_allocator = gpa.allocator(); + self.pool = @alignCast(@ptrCast(gpa_allocator.alloc(u8, total_bytes_of_pool) catch unreachable)); + self.private = &gpa_allocator; } - 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, + + inline fn test_deinit_pool(_: *@This()) Err_T!void { + return; } - } +// ============================== - 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, +// ============================ TARGET + inline fn target_init_pool(self: *@This()) Err_T!void { + switch(comptime ar.target_code.target) { + .i386 => { + const page = ar.target_code.mm.alloc_page() + catch return Err_T.InternalError; + self.pool = page.virtual; + self.private = page; + }, + else => @compileError(""), + } } - } - 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; - } - 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, - }; + inline fn target_deinit_pool(_: *@This()) Err_T!void { + switch(comptime ar.target_code.target) { + .i386 => { + + }, + else => @compileError(""), } - }.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; } - } +// ================================== - 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, - }; +// ================================== AUX + inline fn child(self: *@This()) *Pool_T { + return @alignCast(@ptrCast(&self.pool.?[0])); + } + + inline fn initialized(self: *@This()) Err_T!void { + return if(self.pool == null) Err_T.NoNInitialized else + {}; + } + + 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 + {}; + } + + inline fn outbounds(self: *@This(), ptr: []u8) Err_T!void { + 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 { + 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); + + 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; } - break :r .{ - .index = null, - .result = true, - }; - }; - } - inline fn cast_block_to_byte(blocks: usize) usize { - return blocks * block_size; - } + const pool_initial_index: usize = initial_block_index * block_size; + const pool_final_index: usize = pool_initial_index + (blocks * block_size); - inline fn cast_bytes_to_block(bytes: usize) usize { - return @intCast((block_size + bytes - 1) / block_size); - } + for(0..blocks) |i| + self.bitmap[initial_block_index + i] = 0; + self.flags.full = @intFromBool((bitmap >> @intCast(blocks)) == 0); - 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; + return self.pool.?[pool_initial_index..pool_final_index]; } - for(index..(index + blocks)) |i| - pool.bitmap[i] = 1; - } - 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; + pub noinline fn free(self: *@This(), ptr: []u8) Err_T!void { + try self.initialized(); + try self.outbounds(ptr); + + 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..blocks) |i| { + if(self.bitmap[initial_block_index + i] == 1) + return Err_T.DoubleFree; + self.bitmap[initial_block_index + i] = 1; } - if(current_pool.flags.parent == 0) break; - parent_pool = current_pool; - current_pool = @alignCast(@ptrCast(¤t_pool.bytes.?[0])); + self.flags.full = 0; } - return .{ - parent_pool, - child_pool, - }; - } + }; + + root: Pool_T = .{}, + debug: Debug_T = .{}, - 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; +// =================== 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; } +// ======================= - 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; +// =================== ALLOCATOR OPS + 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; } - 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; + noinline fn deinit(context: *anyopaque) Err_T!void { + const self: *@This() = @alignCast(@ptrCast(context)); + + 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; } - index = check.index.? + 1; + // ===================== } - 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 fn alloc(self: *@This(), comptime T: type, N: usize) err_T![]T { - const bytes: usize = @sizeOf(T) * N; - 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 @as([]T, @alignCast(@ptrCast(try @call(.always_inline, alloc_resized_frame, .{ - self, bytes - })))); + 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.child().prev = current_pool; + current_pool.flags.child = 1; + // ============== DEBUG + if(comptime options.debug) { + self.debug.pools += 1; + } + // ===================== + } + current_pool = current_pool.child(); + }, + else => return err, + } } - return @as([]T, @alignCast(@ptrCast(try @call(.always_inline, alloc_sigle_frame, .{ - self, bytes - })))); + unreachable; } - 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); - 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.*; + 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; + current_pool.prev = null; + } 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.pools -= 1; + } + // =================== + } + // ============= 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, } - if(builtin.is_test) - self.pools -= 1; - return; - } - for(initial_block..(initial_block + block_to_free)) |i| { - alloc_pool.?.bitmap[i] = 0; } - alloc_pool.?.refs -= block_to_free; - alloc_pool.?.flags.full = 0; + unreachable; } - 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; - } - self.root.refs -= block_to_free; - self.root.flags.full = 0; + noinline fn is_initialized(context: *anyopaque) bool { + const self: *@This() = @alignCast(@ptrCast(context)); + return (self.root.pool != null); } +// ======================= - 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"), + 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, + } }; - const byte_slice = @call(.always_inline, byte_slice_fn, .{ - ptr - }); - if(comptime personality.resize) { - return @call(.always_inline, free_resized_frame, .{ - self, byte_slice - }); - } - return @call(.always_inline, free_single_frame, .{ - self, byte_slice - }); } }; } + +// 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" { + 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 And Free" { + var sba = buildByteAllocator(null, .{ + .resize = true, + .debug = true, + }) {}; + + var allocator = sba.allocator(); + try allocator.init(); + + 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; + 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; + } +} diff --git a/lib/saturn/kernel/memory/sba/test.zig b/lib/saturn/kernel/memory/sba/test.zig deleted file mode 100644 index 9a0e35a..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: SBASingle_T = .{}; - 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/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, -// }; -//};