adjust allocator spec to use slices as return type

optimise raw allocator
make allocators use new spec
remove redundant `lily.` prefix from lily tests
kill SimpleAllocator (nobody liked it)
This commit is contained in:
koniifer 2025-02-02 14:57:11 +00:00
parent 1c2b7f8e83
commit d1334ada04
10 changed files with 84 additions and 162 deletions

View file

@ -13,14 +13,14 @@ Allocator := struct {
deinit := fn(self: ^Self): void
/// should return null on failure.
/// should dealloc any intermediate allocations on failure.
alloc := fn(self: ^Self, $T: type, count: uint): ?^T
alloc := fn(self: ^Self, $T: type, count: uint): ?[]T
/// same behaviour as alloc, except:
/// must be zeroed.
alloc_zeroed := fn(self: ^Self, $T: type, count: uint): ?^T
alloc_zeroed := fn(self: ^Self, $T: type, count: uint): ?[]T
/// same behaviour as alloc, except:
/// must move data to new allocation,
/// must ensure the old allocation is freed at some point.
realloc := fn(self: ^Self, $T: type, ptr: ^T, new_count: uint): ?^T
realloc := fn(self: ^Self, $T: type, ptr: ^T, new_count: uint): ?[]T
/// must dealloc or schedule the freeing of the given allocation
dealloc := fn(self: ^Self, $T: type, ptr: ^T): void
}

View file

@ -49,7 +49,7 @@ ArenaAllocator := struct {
self.last_alloc_size = 0
log.debug("deinit: arena allocator")
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
alloc := fn(self: ^Self, $T: type, count: uint): ?[]T {
size := @sizeof(T) * count
if Config.debug_assertions() & size == 0 {
log.error("arena: zero sized allocation")
@ -72,13 +72,14 @@ ArenaAllocator := struct {
self.last_alloc_size = size
self.offset = aligned + size
log.debug("arena: allocated")
return @bitcast(ptr)
return @as(^T, @bitcast(ptr))[0..count]
}
alloc_zeroed := Self.alloc
realloc := fn(self: ^Self, $T: type, ptr: ^T, new_count: uint): ?^T {
realloc := fn(self: ^Self, $T: type, ptr: ^T, new_count: uint): ?[]T {
r0 := @as(^u8, @bitcast(ptr)) != self.current_block.ptr + self.last_alloc_start
r1 := self.last_alloc_start + self.last_alloc_size != self.offset
if r0 | r1 {
// ! (libc) (compiler) bug: checking r0 | r1 here gives "not yet implemented: bool"
if Target.current() != .LibC if r0 | r1 {
if Config.debug_assertions() {
log.error("arena: realloc only supports last allocation")
}
@ -89,13 +90,13 @@ ArenaAllocator := struct {
if Config.debug_assertions() {
log.warn("arena: useless reallocation (new_size <= old_size)")
}
return ptr
return ptr[0..new_count]
}
additional := size - self.last_alloc_size
if self.offset + additional <= self.current_block.len {
self.offset += additional
self.last_alloc_size = size
return ptr
return ptr[0..new_count]
}
new_size := alloc_size(size)
// ! (libc) (compiler) bug: null check broken. unwrapping.
@ -108,7 +109,7 @@ ArenaAllocator := struct {
self.last_alloc_start = 0
self.last_alloc_size = size
log.debug("arena: reallocated")
return @bitcast(new_ptr)
return @as(^T, @bitcast(new_ptr))[0..new_count]
}
$dealloc := fn(self: ^Self, $T: type, ptr: ^T): void {
if Config.debug_assertions() log.error("arena: dealloc called. (makes no sense)")

View file

@ -1,3 +1,2 @@
.{RawAllocator} := @use("raw.hb");
.{SimpleAllocator} := @use("simple.hb");
.{ArenaAllocator} := @use("arena.hb");
.{ArenaAllocator} := @use("arena.hb")

View file

@ -1,57 +1,58 @@
.{Config, Target, Type, log, collections: .{Vec}} := @use("../lib.hb");
RawAllocator := struct {
ptr: ^u8,
size: uint,
$new := fn(): Self return .(Type(^u8).uninit(), 0)
deinit := fn(self: ^Self): void {
self.dealloc(u8, Type(^u8).uninit());
*self = Self.new()
log.debug("deinit: raw allocator")
slice: []u8,
$new := fn(): Self return .(Type([]u8).uninit())
$deinit := fn(self: ^Self): void {
self.dealloc(void, Type(^void).uninit())
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
if Target.calculate_pages(self.size) == Target.calculate_pages(count * @sizeof(T)) {
self.size = count * @sizeof(T)
return @bitcast(self.ptr)
}
// ! (libc) (compiler) bug: null check broken. unwrapping.
ptr := @unwrap(Target.alloc(count * @sizeof(T)))
self.ptr = ptr
self.size = count * @sizeof(T)
$alloc_zeroed := fn(self: ^Self, $T: type, count: uint): ?[]T {
return Self._alloc_common(self, T, count, true)
}
$alloc := fn(self: ^Self, $T: type, count: uint): ?[]T {
return Self._alloc_common(self, T, count, false)
}
realloc := fn(self: ^Self, $T: type, ptr: ^T, count: uint): ?[]T {
size := count * @sizeof(T);
if size == 0 return Type([]T).uninit();
if self.slice.len == 0 return null;
log.debug("allocated: raw")
return @bitcast(ptr)
}
alloc_zeroed := fn(self: ^Self, $T: type, count: uint): ?^T {
if Target.calculate_pages(self.size) == Target.calculate_pages(count * @sizeof(T)) {
self.size = count * @sizeof(T)
return @bitcast(self.ptr)
if Target.calculate_pages(self.slice.len) >= Target.calculate_pages(size) {
return @as(^T, @bitcast(self.slice.ptr))[0..count]
}
// ! (libc) (compiler) bug: null check broken. unwrapping.
ptr := @unwrap(Target.alloc_zeroed(count * @sizeof(T)))
self.ptr = ptr
self.size = count * @sizeof(T)
log.debug("allocated: raw")
return @bitcast(ptr)
}
realloc := fn(self: ^Self, $T: type, ptr: ^T, count: uint): ?^T {
if self.size == 0 return null
if Target.calculate_pages(self.size) == Target.calculate_pages(count * @sizeof(T)) {
self.size = count * @sizeof(T)
return @bitcast(self.ptr)
}
log.debug("reallocated: raw")
// ! (libc) (compiler) bug: null check broken. unwrapping.
new_ptr := @unwrap(Target.realloc(self.ptr, self.size, count * @sizeof(T)))
self.ptr = new_ptr
self.size = count * @sizeof(T)
return @bitcast(new_ptr)
new_ptr := @unwrap(Target.realloc(self.slice.ptr, self.slice.len, size));
self.slice = new_ptr[0..size];
log.debug("reallocated: raw");
return @as(^T, @bitcast(new_ptr))[0..count]
}
// ! INLINING THIS FUNCTION CAUSES MISCOMPILATION!! DO NOT INLINE IT!! :) :) :)
dealloc := fn(self: ^Self, $T: type, ptr: ^T): void {
if self.size == 0 return;
Target.dealloc(self.ptr, self.size)
log.debug("deallocated: raw")
if self.slice.len > 0 {
Target.dealloc(self.slice.ptr, self.slice.len)
log.debug("deallocated: raw")
};
*self = Self.new()
}
_alloc_common := fn(self: ^Self, $T: type, count: uint, zeroed: bool): ?[]T {
size := count * @sizeof(T);
if size == 0 return Type([]T).uninit();
if Target.calculate_pages(self.slice.len) >= Target.calculate_pages(size) {
return @as(^T, @bitcast(self.slice.ptr))[0..count]
}
if self.slice.len > 0 {
Target.dealloc(self.slice.ptr, self.slice.len)
}
ptr := Type(^u8).uninit()
if zeroed {
ptr = @unwrap(Target.alloc_zeroed(size))
} else {
ptr = @unwrap(Target.alloc(size))
}
self.slice = ptr[0..size]
log.debug("allocated: raw")
return @as(^T, @bitcast(ptr))[0..count]
}
}

View file

@ -1,91 +0,0 @@
.{Config, Target, Type, log, collections: .{Vec}} := @use("../lib.hb");
.{RawAllocator} := @use("lib.hb")
Allocation := struct {
ptr: ^u8,
len: uint,
}
// ! THIS ALLOCATOR IS TEMPORARY
SimpleAllocator := struct {
allocations: Vec(Allocation, RawAllocator),
raw: RawAllocator,
$new := fn(): Self {
raw := RawAllocator.new()
return .(Vec(Allocation, RawAllocator).new(&raw), raw)
}
deinit := fn(self: ^Self): void {
loop if self.allocations.len() == 0 break else {
alloced := self.allocations.pop_unchecked()
match Target.current() {
.LibC => Target.dealloc(alloced.ptr),
.AbleOS => Target.dealloc(alloced.ptr, alloced.len),
}
}
self.allocations.deinit()
self.raw.deinit()
log.debug("deinit: allocator")
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := Target.alloc(count * @sizeof(T))
if Target.current() == .AbleOS {
if ptr != null self.allocations.push(.(ptr, count * @sizeof(T)))
}
log.debug("allocated")
return @bitcast(ptr)
}
alloc_zeroed := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := Target.alloc_zeroed(count * @sizeof(T))
if Target.current() == .AbleOS {
if ptr != null self.allocations.push(.(ptr, count * @sizeof(T)))
}
return @bitcast(ptr)
}
realloc := fn(self: ^Self, $T: type, ptr: ^T, count: uint): ?^T {
match Target.current() {
.AbleOS => {
alloced := self._find_and_remove(@bitcast(ptr))
if alloced == null return null
new_ptr := Target.realloc(@bitcast(ptr), alloced.len, count * @sizeof(T))
if new_ptr != null {
self.allocations.push(.(new_ptr, count * @sizeof(T)))
} else {
self.allocations.push(alloced)
}
return @bitcast(new_ptr)
},
.LibC => {
new_ptr := Target.realloc(@bitcast(ptr), count * @sizeof(T))
log.debug("reallocated")
return @bitcast(new_ptr)
},
}
}
dealloc := fn(self: ^Self, $T: type, ptr: ^T): void {
match Target.current() {
.AbleOS => {
alloced := self._find_and_remove(@bitcast(ptr))
if alloced != null Target.dealloc(@bitcast(ptr), alloced.len)
},
.LibC => {
Target.dealloc(@bitcast(ptr))
},
}
log.debug("freed")
}
_find_and_remove := fn(self: ^Self, ptr: ^u8): ?Allocation {
i := 0
loop if i == self.allocations.len() break else {
defer i += 1
alloced := self.allocations.get_unchecked(i)
if alloced.ptr == ptr {
_ = self.allocations.swap_remove(i)
return alloced
}
}
return null
}
}

View file

@ -34,13 +34,13 @@ Vec := fn($T: type, $Allocator: type): type return struct {
if self.cap == 0 {
// ! (libc) (compiler) bug: null check broken, so unwrapping (unsafe!)
new_alloc := @unwrap(self.allocator.alloc(T, 1))
self.slice.ptr = new_alloc
self.cap = 1
self.slice.ptr = new_alloc.ptr
self.cap = new_alloc.len
} else {
// ! (libc) (compiler) bug: null check broken, so unwrapping (unsafe!)
new_alloc := @unwrap(self.allocator.realloc(T, self.slice.ptr, self.cap * 2))
self.slice.ptr = new_alloc
self.cap *= 2
self.slice.ptr = new_alloc.ptr
self.cap = new_alloc.len
}
}
self.slice[self.slice.len] = value

View file

@ -70,14 +70,26 @@ main := fn(argc: uint, argv: []^void): uint {
// lily.print(map.get("asdfasdf!"))
// lily.print(map.get("Hello, World!"))
id := lily.process.fork()
lily.print("hello, world")
// id := lily.process.fork()
// lily.print("hello, world")
if id == 0 {
lily.print("child")
} else {
lily.print("parent")
// if id == 0 {
// lily.print("child")
// } else {
// lily.print("parent")
// }
Allocator := lily.alloc.ArenaAllocator
allocator := Allocator.new()
defer allocator.deinit()
vec := lily.collections.Vec(uint, Allocator).new(&allocator)
i := 0
// ! (libc) (compiler) bug: i > 512 causes SIGSEGV
loop if i == 1024 break else {
defer i += 1
vec.push(i)
}
lily.print(vec.slice.len)
return 0
}

View file

@ -10,8 +10,8 @@ main := fn(): u8 {
if b == null return 1
c := allocator.alloc(u8, 100)
if c == null return 1
d := allocator.realloc(u8, c, 100)
d := allocator.realloc(u8, c.ptr, 100)
if d == null return 1
if d != c return 1
if d.ptr != c.ptr return 1
return 0
}

View file

@ -11,8 +11,8 @@ main := fn(): u8 {
if b == null return 1
c := allocator.alloc(u8, 100)
if c == null return 1
d := allocator.realloc(u8, c, 100)
d := allocator.realloc(u8, c.ptr, 100)
if d == null return 1
if d != c return 1
if d.ptr != c.ptr return 1
return 0
}