much more robust allocator & vector

This commit is contained in:
koniifer 2025-01-01 18:52:08 +00:00
parent 83f4595c74
commit 13538ea0be
9 changed files with 158 additions and 104 deletions

2
build
View file

@ -41,7 +41,7 @@ elif ! [ -w "$build_dir/target/build" ]; then
exit 1
fi
if hbc "$build_dir/src/main.hb" > "$build_dir/target/build/lily.o"; then
if hbc "$build_dir/src/main.hb" --backend-flags=opt_level=speed > "$build_dir/target/build/lily.o"; then
$linker "$build_dir/target/build/lily.o" -o "$build_dir/target/lily"
strip -s "$build_dir/target/lily"
else

View file

@ -15,24 +15,25 @@ SimpleAllocator := struct {
return .(Vec(Allocation, RawAllocator).new(&raw), raw)
}
deinit := fn(self: ^Self): void {
i := 0
loop if i == self.allocations.len() break else {
defer i += 1
alloced := self.allocations.get_unchecked(i)
if target == target_c_native {
target.free(alloced.ptr)
} else if target == target_hbvm_ableos {
target.free(alloced.ptr, alloced.len)
loop if self.allocations.len == 0 break else {
alloced := self.allocations.pop()
if alloced != null {
if target == target_c_native {
target.free(alloced.ptr)
} else if target == target_hbvm_ableos {
target.free(alloced.ptr, alloced.len)
}
}
}
self.allocations.deinit();
*self = Self.new()
self.allocations.deinit()
self.raw.deinit()
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := target.malloc(count * @sizeof(T))
// ! (compiler?) bug: null check broken, so unwrapping (unsafe!)
if target == target_hbvm_ableos {
self.allocations.push(.(@unwrap(ptr), count))
if ptr != null {
self.allocations.push(.(ptr, count * @sizeof(T)))
}
}
return @bitcast(ptr)
}
@ -40,19 +41,21 @@ SimpleAllocator := struct {
if target == target_c_native {
target.free(@bitcast(ptr))
} else if target == target_hbvm_ableos {
alloced := self._find(@bitcast(ptr))
alloced := self._find_and_remove(@bitcast(ptr))
if alloced == null return;
target.free(@bitcast(ptr), alloced.len)
}
}
_find := fn(self: ^Self, ptr: ^void): ?Allocation {
_find_and_remove := fn(self: ^Self, ptr: ^void): ?Allocation {
i := 0
loop if i == self.allocations.len() break else {
loop if i == self.allocations.len break else {
defer i += 1
result := self.allocations.get(i)
if !result.is_ok return null
alloced := result.unwrap_unchecked()
if alloced.ptr == ptr return alloced
alloced := self.allocations.get(i)
if alloced == null return null
if alloced.ptr == ptr {
_ = self.allocations.remove(i)
return alloced
}
}
return null
}
@ -61,21 +64,25 @@ SimpleAllocator := struct {
// ! THIS ALLOCATOR IS *ALSO* TEMPORARY
RawAllocator := struct {
ptr: ^void,
old_ptr: ^void,
size: uint,
$new := fn(): Self return .(@bitcast(0), 0)
old_size: uint,
$new := fn(): Self return .(@bitcast(0), @bitcast(0), 0, 0)
deinit := fn(self: ^Self): void {
if self.size != 0 {
if target == target_c_native {
target.free(@bitcast(self.ptr))
} else if target == target_hbvm_ableos {
target.free(@bitcast(self.ptr), self.size)
}
if target == target_c_native {
target.free(self.ptr)
target.free(self.old_ptr)
} else if target == target_hbvm_ableos {
target.free(self.ptr, self.size)
target.free(self.old_ptr, self.old_size)
};
*self = Self.new()
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := target.malloc(count * @sizeof(T))
if ptr != null {
self.old_ptr = self.ptr
self.old_size = self.size
self.ptr = ptr
self.size = count * @sizeof(T)
}
@ -83,9 +90,9 @@ RawAllocator := struct {
}
free := fn(self: ^Self, $T: type, ptr: ^T): void {
if target == target_c_native {
target.free(@bitcast(self.ptr))
target.free(self.old_ptr)
} else if target == target_hbvm_ableos {
target.free(@bitcast(self.ptr), self.size)
target.free(self.old_ptr, self.old_size)
}
}
}

View file

@ -1,5 +1,6 @@
.{Vec, RawVec} := @use("vec.hb")
.{Vec} := @use("vec.hb")
// mostly placeholder error error type
Error := enum {
KeyNotFound,
OutOfRange,

View file

@ -1,53 +1,70 @@
.{target, target_c_native, target_hbvm_ableos, result: .{Result}} := @use("../lib.hb");
.{target, target_c_native, target_hbvm_ableos, result: .{Result}, null_pointer} := @use("../lib.hb");
.{Error} := @use("lib.hb")
Vec := fn($T: type, $A: type): type return struct {
slice: []T,
// self.slice.len tracks non-null elements
slice: []?T,
allocator: ^A,
cap: uint,
len: uint,
capacity: uint,
$new := fn(allocator: ^A): Self return .{slice: T.[][..], allocator, cap: 0}
$new := fn(allocator: ^A): Self return .{slice: null_pointer(?T)[0..0], allocator, capacity: 0, len: 0}
deinit := fn(self: ^Self): void {
if self.slice.len != 0 {
self.allocator.free(T, self.slice.ptr)
};
*self = Self.new(self.allocator)
// currently does not handle deinit of T if T allocates memory
if self.capacity != 0 self.allocator.free(?T, self.slice.ptr)
self.slice = null_pointer(?T)[0..0]
self.capacity = 0
}
push := fn(self: ^Self, value: T): void {
if self.slice.len == self.cap {
if self.cap == 0 {
self.cap = 1
if self.slice.len == self.capacity {
if self.capacity == 0 {
self.capacity = 1
} else {
self.cap *= 2
self.capacity *= 2
}
// ! (compiler?) bug: null check broken, so unwrapping (unsafe!)
new_alloc := @unwrap(self.allocator.alloc(T, self.cap))
new_alloc := @unwrap(self.allocator.alloc(?T, self.capacity))
if self.slice.len > 0 {
target.memmove(@bitcast(new_alloc), @bitcast(self.slice.ptr), self.slice.len * @sizeof(T))
self.allocator.free(T, self.slice.ptr)
target.memmove(@bitcast(new_alloc), @bitcast(self.slice.ptr), self.slice.len * @sizeof(?T))
self.allocator.free(?T, self.slice.ptr)
}
self.slice.ptr = new_alloc
}
self.slice[self.slice.len] = value
self.slice.len += 1
self.len += 1
}
get := fn(self: ^Self, n: uint): Result(T, Error) {
if n >= self.slice.len return Result(T, Error).err(.OutOfRange)
return Result(T, Error).ok(self.slice[n])
get := fn(self: ^Self, n: uint): ?T {
loop if n >= self.len return null else {
a := self.slice[n]
if a != null return a
n += 1
}
}
get_ref := fn(self: ^Self, n: uint): Result(^T, Error) {
if n >= self.slice.len return Result(T, Error).err(.OutOfRange)
return Result(T, Error).ok(self.slice.ptr + n)
pop := fn(self: ^Self): ?T {
if self.len == 0 return null
n := self.slice.len - 1
loop {
a := self.slice[n]
if a != null {
self.slice.len -= 1
self.len -= 1
return a
}
n -= 1
}
}
$get_unchecked := fn(self: ^Self, n: uint): T return self.slice[n]
$get_ref_unchecked := fn(self: ^Self, n: uint): T return self.slice.ptr + n
pop := fn(self: ^Self): Result(T, Error) {
if self.slice.len == 0 return Result(T, Error).err(.OutOfRange)
self.slice.len -= 1
return Result(T, Error).ok(self.slice[self.slice.len + 1])
remove := fn(self: ^Self, n: uint): ?T {
loop if n >= self.len return null else {
a := self.slice[n]
if a != null {
self.slice[n] = null
self.len -= 1
return a
}
n += 1
}
}
$len := fn(self: ^Self): uint return self.slice.len
$capacity := fn(self: ^Self): uint return self.cap
}

View file

@ -4,11 +4,12 @@ Version := struct {
patch: uint,
}
$STDLIB_VERSION := Version(0, 0, 1)
$VERSION := Version(0, 0, 2)
collections := @use("collections/lib.hb")
result := @use("result.hb")
alloc := @use("alloc.hb")
log := @use("log.hb")
target_c_native := @use("target/c_native.hb")
target_hbvm_ableos := @use("target/hbvm_ableos.hb")
@ -36,6 +37,8 @@ Kind := enum {
Module,
}
$null_pointer := fn($T: type): ^T return @bitcast(0)
Type := fn($T: type): type return struct {
$is_unsigned_int := fn(): bool {
return T == uint | T == u8 | T == u16 | T == u32

37
src/lib/log.hb Normal file
View file

@ -0,0 +1,37 @@
.{target, target_c_native, target_hbvm_ableos} := @use("lib.hb")
LogLevel := enum {
Error,
Warn,
Info,
Debug,
Trace,
}
log := fn(level: LogLevel, str: []u8): void {
if target == target_hbvm_ableos {
return @eca(3, 1, target.LogMsg.(level, str.ptr, str.len), @sizeof(target.LogMsg))
} else if target == target_c_native {
match level {
.Error => target.printf_str("\{1b}[31mERROR\{1b}[0m: %s\n\0".ptr, str.ptr),
.Warn => target.printf_str("\{1b}[33mWARN\{1b}[0m: %s\n\0".ptr, str.ptr),
.Info => target.printf_str("\{1b}[32mINFO\{1b}[0m: %s\n\0".ptr, str.ptr),
.Debug => target.printf_str("\{1b}[34mDEBUG\{1b}[0m: %s\n\0".ptr, str.ptr),
.Trace => target.printf_str("\{1b}[35mTRACE\{1b}[0m: %s\n\0".ptr, str.ptr),
}
}
}
$print := fn(str: []u8): void {
if target == target_c_native {
target.puts(str.ptr)
} else if target == target_hbvm_ableos {
info(str)
}
}
$error := fn(message: []u8): void return log(LogLevel.Error, message)
$warn := fn(message: []u8): void return log(LogLevel.Warn, message)
$info := fn(message: []u8): void return log(LogLevel.Info, message)
$debug := fn(message: []u8): void return log(LogLevel.Debug, message)
$trace := fn(message: []u8): void return log(LogLevel.Trace, message)

View file

@ -1,6 +1,8 @@
malloc := fn(size: uint): ?^void @import()
free := fn(ptr: ^void): void @import()
memmove := fn(dest: ^void, src: ^void, size: uint): void @import()
memcopy := fn(dest: ^void, src: ^void, size: uint): void @import()
memset := fn(dest: ^void, src: ^void, size: uint): void @import()
exit := fn(code: int): void @import()
memcpy := fn(dest: ^void, src: ^void, size: uint): void @import()
memset := fn(dest: ^void, src: u8, size: uint): void @import()
exit := fn(code: int): void @import()
puts := fn(str: ^u8): void @import()
printf := fn(str: ^u8): void @import()

View file

@ -1,63 +1,50 @@
.{log: .{LogLevel}} := @use("../lib.hb")
$PAGE_SIZE := 4096
$MAX_ALLOC := 0xFF
$MAX_FREE := 0xFF
LogMsg := packed struct {level: LogLevel, string: ^u8, strlen: uint}
$calculate_pages := fn(size: uint): uint {
return (size + PAGE_SIZE - 1) / PAGE_SIZE
}
RqPageMsg := packed struct {a: u8, count: u8}
$request_pages := fn(count: u8): ?^void {
RqPageMsg := packed struct {a: u8, count: uint}
$request_pages := fn(count: uint): ?^void {
return @eca(3, 2, &RqPageMsg.(0, count), @sizeof(RqPageMsg))
}
FreePageMsg := packed struct {a: u8, count: u8, ptr: ^void}
$free_pages := fn(ptr: ^void, count: u8): void {
FreePageMsg := packed struct {a: u8, count: uint, ptr: ^void}
$free_pages := fn(ptr: ^void, count: uint): void {
return @eca(3, 2, &FreePageMsg.(1, count, ptr), @sizeof(FreePageMsg))
}
malloc := fn(size: uint): ?^void {
if size == 0 return null
pages := calculate_pages(size)
if pages <= MAX_ALLOC {
return @bitcast(request_pages(@intcast(pages)))
}
ptr := request_pages(MAX_ALLOC)
if ptr == null return null
pages -= MAX_ALLOC
loop if pages <= MAX_ALLOC break else {
if request_pages(MAX_ALLOC) == null return null
pages -= MAX_ALLOC
}
if request_pages(@intcast(pages)) == null return null
return @bitcast(ptr)
return request_pages(pages)
}
free := fn(ptr: ^void, size: uint): void {
if size == 0 | ptr == @bitcast(0) return;
pages := calculate_pages(size)
if pages <= MAX_FREE {
return free_pages(ptr, @intcast(pages))
}
loop if pages <= MAX_FREE break else {
free_pages(ptr, MAX_FREE)
ptr += PAGE_SIZE * MAX_FREE
pages -= MAX_FREE
}
free_pages(ptr, @intcast(pages))
free_pages(ptr, pages)
}
CopyMsg := packed struct {a: u8, count: uint, src: ^void, dest: ^void}
$memcopy := fn(dest: ^void, src: ^void, size: uint): void {
$memcpy := fn(dest: ^void, src: ^void, size: uint): void {
return @eca(3, 2, &CopyMsg.(4, size, src, dest), @sizeof(CopyMsg))
}
SetMsg := packed struct {a: u8, count: uint, size: uint, src: ^void, dest: ^void}
$memset := fn(dest: ^void, src: ^void, size: uint): void {
return @eca(3, 2, &SetMsg.(5, size, 1, src, dest), @sizeof(SetMsg))
$memset := fn(dest: ^void, src: u8, size: uint): void {
return @eca(3, 2, &SetMsg.(5, size, 1, @bitcast(&src), dest), @sizeof(SetMsg))
}
memmove := fn(dest: ^void, src: ^void, size: uint): void {
memcopy(dest, src, size)
memset(src, @bitcast(&0), size)
memcpy(dest, src, size)
memset(src, 0, size)
}
$exit := fn(code: int): void {

View file

@ -1,21 +1,21 @@
std := @use("lib/lib.hb");
lily := @use("lib/lib.hb");
Allocator := std.alloc.SimpleAllocator
Vec := std.collections.Vec
Allocator := lily.alloc.SimpleAllocator
Vec := lily.collections.Vec
main := fn(argc: uint, argv: []^u8): uint {
// ! (runtime) (target_hbvm_ableos?) bug: leaking memory (1 page in this function)
main := fn(argc: uint, argv: []^void): uint {
allocator := Allocator.new()
// ! (compiler?) (target_hbvm_ableos) bug: defer deinit on allocator causes kernel panic
defer allocator.deinit()
vec := Vec(int, Allocator).new(&allocator)
vec := Vec(uint, Allocator).new(&allocator)
defer vec.deinit()
i: int = 0
loop if i == 10 break else {
i := 0
loop if i == 5 break else {
defer i += 1
vec.push(i)
lily.log.trace("pushed to vec\0")
}
// ! (compiler?) (target_c_native?) bug: not popping here causes len & cap to be zero
vec.push(vec.pop().unwrap_or(0))
return vec.len()
return 0
}