180 lines
4.7 KiB
C
180 lines
4.7 KiB
C
#include "allocator.h"
|
|
#include "panic.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
size_t align_size(size_t size)
|
|
{
|
|
const size_t word_size = sizeof(size_t);
|
|
return (size + word_size) / word_size * word_size;
|
|
}
|
|
|
|
void gc_buffer_construct(GCBuffer* buffer, size_t capacity)
|
|
{
|
|
void* data = malloc(capacity);
|
|
ASSERT(buffer != NULL);
|
|
*buffer = (GCBuffer) {
|
|
.data = data,
|
|
.index = 0,
|
|
.capacity = capacity,
|
|
};
|
|
}
|
|
|
|
void gc_buffer_destroy(GCBuffer* buffer) { free(buffer->data); }
|
|
|
|
void gc_buffer_reset(GCBuffer* buffer) { buffer->index = 0; }
|
|
|
|
void* gc_buffer_alloc(GCBuffer* buffer, size_t size)
|
|
{
|
|
size_t aligned_size = align_size(size);
|
|
if (buffer->capacity - buffer->index < aligned_size) {
|
|
return NULL;
|
|
}
|
|
void* ptr = &((size_t*)buffer->data)[buffer->index];
|
|
buffer->index += aligned_size;
|
|
return ptr;
|
|
}
|
|
|
|
void gc_buffer_resize(GCBuffer* buffer, size_t minimum_capacity)
|
|
{
|
|
size_t aligned_min_cap = align_size(minimum_capacity);
|
|
if (buffer->capacity >= aligned_min_cap) {
|
|
return;
|
|
}
|
|
while (buffer->capacity < minimum_capacity) {
|
|
buffer->capacity *= 2;
|
|
}
|
|
void* new_buffer = realloc(buffer->data, buffer->capacity);
|
|
ASSERT(new_buffer);
|
|
buffer->data = new_buffer;
|
|
}
|
|
|
|
void gc_construct(GC* allocator, size_t start_capacity)
|
|
{
|
|
GCBuffer buffer_a;
|
|
gc_buffer_construct(&buffer_a, start_capacity);
|
|
GCBuffer buffer_b;
|
|
gc_buffer_construct(&buffer_b, start_capacity);
|
|
*allocator = (GC) {
|
|
.alloc = gc_alloc,
|
|
.realloc = gc_realloc,
|
|
.buffer_a = buffer_a,
|
|
.buffer_b = buffer_b,
|
|
.select = GCBufferA,
|
|
};
|
|
}
|
|
|
|
bool gc_buffer_contains(GCBuffer* buffer, void* address)
|
|
{
|
|
return (size_t)address >= (size_t)buffer->data
|
|
&& (size_t)address < (size_t)buffer->data + (size_t)buffer->index;
|
|
}
|
|
|
|
void gc_destroy(GC* allocator)
|
|
{
|
|
gc_buffer_destroy(&allocator->buffer_a);
|
|
gc_buffer_destroy(&allocator->buffer_b);
|
|
}
|
|
|
|
void* gc_alloc(GC* allocator, size_t size)
|
|
{
|
|
size_t full_size = size + sizeof(size_t);
|
|
GCBuffer* buffer = gc_selected_buffer(allocator);
|
|
void* ptr = gc_buffer_alloc(buffer, full_size);
|
|
if (ptr == NULL) {
|
|
return NULL;
|
|
}
|
|
size_t* alloc_size = (size_t*)ptr;
|
|
*alloc_size = size;
|
|
return &((size_t*)ptr)[1];
|
|
}
|
|
|
|
void* gc_realloc(GC* allocator, void* data, size_t size)
|
|
{
|
|
size_t old_size = ((size_t*)data)[-1];
|
|
void* new_ptr = gc_alloc(allocator, size);
|
|
if (new_ptr == NULL) {
|
|
return NULL;
|
|
}
|
|
memcpy(new_ptr, (size_t*)data - sizeof(size_t), old_size + sizeof(size_t));
|
|
size_t* size_ptr = (size_t*)new_ptr;
|
|
*size_ptr = size;
|
|
return &((size_t*)new_ptr)[1];
|
|
}
|
|
|
|
void gc_collect(GC* allocator, void* entries, size_t entries_size)
|
|
{
|
|
gc_select_other(allocator);
|
|
GCBuffer* source = gc_other_buffer(allocator);
|
|
gc_relocate_branches_buffer(allocator, entries, entries_size);
|
|
gc_buffer_reset(source);
|
|
}
|
|
|
|
void gc_relocate_branches(GC* allocator, void* allocation)
|
|
{
|
|
size_t size = ((size_t*)allocation)[-1];
|
|
gc_relocate_branches_buffer(allocator, allocation, size);
|
|
}
|
|
|
|
void gc_relocate_branches_buffer(
|
|
GC* allocator, void* branches, size_t branches_size
|
|
)
|
|
{
|
|
GCBuffer* source = gc_other_buffer(allocator);
|
|
for (size_t i = 0; i < branches_size; ++i) {
|
|
if (gc_buffer_contains(source, &((size_t*)branches)[i])) {
|
|
gc_relocate_allocation(allocator, branches);
|
|
gc_relocate_branches(allocator, branches);
|
|
}
|
|
}
|
|
}
|
|
|
|
void gc_relocate_allocation(GC* allocator, void* allocation)
|
|
{
|
|
GCBuffer* dest = gc_selected_buffer(allocator);
|
|
void* ptr = &((size_t*)allocation)[-1];
|
|
size_t size = ((size_t*)ptr)[0];
|
|
size_t full_size = size + sizeof(size_t);
|
|
void* new_ptr = gc_buffer_alloc(dest, full_size);
|
|
if (new_ptr == NULL) {
|
|
gc_buffer_resize(dest, dest->capacity + full_size);
|
|
new_ptr = gc_buffer_alloc(dest, full_size);
|
|
ASSERT(new_ptr != NULL);
|
|
}
|
|
size_t* selected_size_ptr = &((size_t*)new_ptr)[0];
|
|
*selected_size_ptr = size;
|
|
memcpy(&((size_t*)new_ptr)[1], allocation, size);
|
|
}
|
|
|
|
GCBuffer* gc_selected_buffer(GC* allocator)
|
|
{
|
|
switch (allocator->select) {
|
|
case GCBufferA:
|
|
return &allocator->buffer_a;
|
|
case GCBufferB:
|
|
return &allocator->buffer_b;
|
|
}
|
|
}
|
|
|
|
GCBuffer* gc_other_buffer(GC* allocator)
|
|
{
|
|
switch (allocator->select) {
|
|
case GCBufferA:
|
|
return &allocator->buffer_b;
|
|
case GCBufferB:
|
|
return &allocator->buffer_a;
|
|
}
|
|
}
|
|
|
|
void gc_select_other(GC* allocator)
|
|
{
|
|
switch (allocator->select) {
|
|
case GCBufferA:
|
|
allocator->select = GCBufferB;
|
|
break;
|
|
case GCBufferB:
|
|
allocator->select = GCBufferA;
|
|
break;
|
|
}
|
|
}
|