much more robust allocator & vector
This commit is contained in:
parent
83f4595c74
commit
13538ea0be
2
build
2
build
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
.{Vec, RawVec} := @use("vec.hb")
|
||||
.{Vec} := @use("vec.hb")
|
||||
|
||||
// mostly placeholder error error type
|
||||
Error := enum {
|
||||
KeyNotFound,
|
||||
OutOfRange,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
37
src/lib/log.hb
Normal 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)
|
|
@ -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()
|
|
@ -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 {
|
||||
|
|
22
src/main.hb
22
src/main.hb
|
@ -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
|
||||
}
|
Loading…
Reference in a new issue