Skip to content

Commit

Permalink
implement freeing and object destruction
Browse files Browse the repository at this point in the history
  • Loading branch information
cetio committed May 15, 2024
1 parent 6b293d8 commit a94adbc
Showing 1 changed file with 69 additions and 12 deletions.
81 changes: 69 additions & 12 deletions rt/gc.fn
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
/// Garbage collector and allocation implementation.
module rt.gc;

import rt.sys;

// TODO: Make this thread-safe without using so many atomics?
private struct Object
{
void* ptr;
nuint size;
Object* prev;
atomic nuint size;
atomic Object* prev;
atomic bool free;
// byte[size] data follows this.
}

public struct Root
{
// TODO: Definitely need some atomics here.
void* base;
void* tail;
atomic Object* tail;
nuint size;
nuint free;
atomic nuint free;
}

/**
Expand Down Expand Up @@ -51,12 +51,14 @@ public struct GC
void create(size_t size = pageSize)
{
// TODO: Not cross platform!
// TODO: Implement error handling, this should be a critical error upon failure.
void* ptr = __syscall!(void*)(mmap, void, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
root.base = ptr;
root.tail = ptr;
root.size = size;
}

// TODO: Alignment
// size should never be 0, no checks are performed to ensure that it isn't and if it is the garbage collector will throw a critical error.
void* alloc(nuint size) trusted;
{
// A freed object is available for allocation.
Expand All @@ -65,32 +67,47 @@ public struct GC
Object* obj;
if (obj.free && obj.size >= size)
{
// Fragment the object into 2 objects, one of size and the other of the remainder.
// This will get merged again later when freeing.
if (obj.size != size)
{
Object* frag = (tail.ptr + size) |> Object*;
*frag = Object(frag + 1, obj.size - size, obj.prev);
obj.prev = frag;
obj.size = size;
}

obj.free = false;
bump(obj);
return => obj.ptr;
}
obj = tail.prev;

// There are no objects available to be used.
if (obj == void)
break;
}

// The root currently has enough space to allocate anew.
if (root.free >= size)
{
Object* ptr = root.tail |> Object*;
Object* ptr = root.tail;
return = ptr + 1;

*ptr = Object(return, size);
bump(ptr);

root.free -= size;
root.tail = return + size;
root.tail = (return + size) |> Object*;
return;
}

// The root neither has space nor available objects but is not completely full,
// by doing this allocation we avoid potentially leaking a stupidly large amount of memory,
// or at least preserve bytes that might end up being useful later.
if (root.free != 0)
{
Object* ptr = root.tail |> Object*;
Object* ptr = root.tail;
*ptr = Object(ptr, root.free);
bump(ptr);
}
Expand All @@ -101,13 +118,53 @@ public struct GC
else
create();

Object* ptr = root.base |> Object*;
Object* ptr = root.base;
return = ptr + 1;

*ptr = Object(return, size);
bump(ptr);

root.free -= size;
root.tail = return + size;
root.tail = (return + size) |> Object*;
}

// ptr should be the start of an allocation, not a part of an allocation.
// This is not the same as destroying an object, and is only an optional part of the process.
void free(void* ptr)
{
// TODO: Make object doubly linked so merging can be done.
Object* obj = ptr |> Object* - 1;
obj.free = true;
bump(obj);
}

void destroy(alias O)
{
// Can't free a field, its a part of an allocation and not the whole thing.
if (O->isField)
return;

// We want to iterate over all the objects to see if this is actually a full allocation,
// since we can't free invalid memory or an array element obviously.
while (true)
{
Object* obj;
if (ptr == obj.ptr)
{
free(obj);
break;
}
obj = tail.prev;

// There are no objects available to be checked.
if (obj == void)
break;
}

// Run the dtor to finalize the object.
// The object may or may not be freed after this.
// If the dtor recursively tries to destroy itself this will cause infinite recursion,
// but I don't think it matters because theres no scenario where a dtor should destroy itself.
O->getFunction("dtor")();
}
}

0 comments on commit a94adbc

Please sign in to comment.