From b46f0d9ff430a9f0c96371074096eb7e5ff6408a Mon Sep 17 00:00:00 2001 From: retoor Date: Fri, 5 Dec 2025 01:12:39 +0100 Subject: [PATCH] UPdate. --- runtime/gc/gc.c | 139 +++++++++++++++++++++++++++++++ runtime/gc/gc.h | 85 +++++++++++++++++++ runtime/gc/gc_heap.c | 188 ++++++++++++++++++++++++++++++++++++++++++ runtime/gc/gc_heap.h | 17 ++++ runtime/gc/gc_mark.c | 108 ++++++++++++++++++++++++ runtime/gc/gc_mark.h | 13 +++ runtime/gc/gc_roots.c | 126 ++++++++++++++++++++++++++++ runtime/gc/gc_roots.h | 12 +++ runtime/gc/gc_sweep.c | 114 +++++++++++++++++++++++++ runtime/gc/gc_sweep.h | 9 ++ tests/test_gc.c | 134 ++++++++++++++++++++++++++++++ 11 files changed, 945 insertions(+) create mode 100644 runtime/gc/gc.c create mode 100644 runtime/gc/gc.h create mode 100644 runtime/gc/gc_heap.c create mode 100644 runtime/gc/gc_heap.h create mode 100644 runtime/gc/gc_mark.c create mode 100644 runtime/gc/gc_mark.h create mode 100644 runtime/gc/gc_roots.c create mode 100644 runtime/gc/gc_roots.h create mode 100644 runtime/gc/gc_sweep.c create mode 100644 runtime/gc/gc_sweep.h create mode 100644 tests/test_gc.c diff --git a/runtime/gc/gc.c b/runtime/gc/gc.c new file mode 100644 index 0000000..a1aabcb --- /dev/null +++ b/runtime/gc/gc.c @@ -0,0 +1,139 @@ +#include "gc.h" +#include "gc_heap.h" +#include "gc_mark.h" +#include "gc_sweep.h" +#include "gc_roots.h" +#include "../runtime.h" +#include +#include + +static RavaVM_t *g_current_vm = NULL; +static RavaGCStats_t g_stats; + +void rava_gc_init(void) { + rava_gc_heap_init(); + rava_gc_mark_init(); + memset(&g_stats, 0, sizeof(g_stats)); + g_current_vm = NULL; +} + +void rava_gc_shutdown(void) { + RavaGCHeap_t *heap = rava_gc_get_heap(); + RavaGCHeader_t *obj = heap->all_objects; + while (obj) { + RavaGCHeader_t *next = obj->gc_next; + rava_gc_finalize_object(obj); + obj = next; + } + heap->all_objects = NULL; + + rava_gc_heap_shutdown(); +} + +void rava_gc_set_vm(RavaVM_t *vm) { + g_current_vm = vm; +} + +void* rava_gc_alloc(size_t size, uint8_t type) { + RavaGCState_t *state = rava_gc_get_state(); + + if (state->gc_enabled && !state->gc_active) { + rava_gc_collect_if_needed(); + } + + void *ptr = rava_gc_heap_alloc(size, type); + if (ptr) { + g_stats.objects_allocated++; + g_stats.bytes_allocated += size; + g_stats.current_live_objects++; + g_stats.current_live_bytes += size; + if (g_stats.current_live_bytes > g_stats.peak_live_bytes) { + g_stats.peak_live_bytes = g_stats.current_live_bytes; + } + } + + return ptr; +} + +void rava_gc_free(void *ptr) { + (void)ptr; +} + +void rava_gc_collect(void) { + RavaGCState_t *state = rava_gc_get_state(); + + if (state->gc_active) { + return; + } + + state->gc_active = true; + state->objects_marked = 0; + state->bytes_marked = 0; + state->gray_count = 0; + + rava_gc_scan_roots(g_current_vm); + + rava_gc_process_gray_stack(); + + size_t live_before = g_stats.current_live_objects; + rava_gc_sweep(); + + rava_gc_recycle_blocks(); + + size_t freed = live_before - state->objects_marked; + g_stats.objects_freed += freed; + g_stats.current_live_objects = state->objects_marked; + g_stats.current_live_bytes = state->bytes_marked; + g_stats.total_collections++; + + state->gc_active = false; +} + +void rava_gc_collect_if_needed(void) { + RavaGCHeap_t *heap = rava_gc_get_heap(); + + if (heap->total_allocated > heap->gc_threshold) { + rava_gc_collect(); + } +} + +void rava_gc_enable(void) { + RavaGCState_t *state = rava_gc_get_state(); + state->gc_enabled = true; +} + +void rava_gc_disable(void) { + RavaGCState_t *state = rava_gc_get_state(); + state->gc_enabled = false; +} + +bool rava_gc_is_enabled(void) { + RavaGCState_t *state = rava_gc_get_state(); + return state->gc_enabled; +} + +void rava_gc_pin(void *ptr) { + if (!ptr) return; + RavaGCHeader_t *header = (RavaGCHeader_t*)ptr; + header->gc_flags |= RAVA_GC_FLAG_PINNED; +} + +void rava_gc_unpin(void *ptr) { + if (!ptr) return; + RavaGCHeader_t *header = (RavaGCHeader_t*)ptr; + header->gc_flags &= ~RAVA_GC_FLAG_PINNED; +} + +RavaGCStats_t rava_gc_get_stats(void) { + return g_stats; +} + +void rava_gc_print_stats(void) { + printf("GC Statistics:\n"); + printf(" Total collections: %zu\n", g_stats.total_collections); + printf(" Objects allocated: %zu\n", g_stats.objects_allocated); + printf(" Objects freed: %zu\n", g_stats.objects_freed); + printf(" Current live objects: %zu\n", g_stats.current_live_objects); + printf(" Current live bytes: %zu\n", g_stats.current_live_bytes); + printf(" Peak live bytes: %zu\n", g_stats.peak_live_bytes); +} diff --git a/runtime/gc/gc.h b/runtime/gc/gc.h new file mode 100644 index 0000000..c536136 --- /dev/null +++ b/runtime/gc/gc.h @@ -0,0 +1,85 @@ +#ifndef RAVA_GC_H +#define RAVA_GC_H + +#include +#include +#include + +#define RAVA_GC_BLOCK_SIZE (32 * 1024) +#define RAVA_GC_LINE_SIZE 128 +#define RAVA_GC_LINES_PER_BLOCK (RAVA_GC_BLOCK_SIZE / RAVA_GC_LINE_SIZE) + +#define RAVA_GC_GRAY_STACK_SIZE 4096 + +#define RAVA_GC_INITIAL_THRESHOLD (1024 * 1024) + +#define RAVA_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1)) + +struct RavaGCHeader; +typedef struct RavaGCHeader RavaGCHeader_t; + +typedef struct RavaGCBlock { + uint8_t line_marks[RAVA_GC_LINES_PER_BLOCK]; + uint8_t flags; + uint16_t free_lines; + struct RavaGCBlock *next; + struct RavaGCBlock *prev; + uint8_t data[RAVA_GC_BLOCK_SIZE]; +} RavaGCBlock_t; + +#define RAVA_GC_BLOCK_FLAG_EVACUATE 0x01 +#define RAVA_GC_BLOCK_FLAG_FULL 0x02 + +typedef struct RavaGCHeap { + RavaGCBlock_t *all_blocks; + RavaGCBlock_t *current_block; + uint8_t *bump_ptr; + uint8_t *block_limit; + RavaGCBlock_t *free_blocks; + RavaGCBlock_t *recyclable_blocks; + size_t total_allocated; + size_t live_bytes; + size_t gc_threshold; + RavaGCHeader_t *all_objects; +} RavaGCHeap_t; + +typedef struct RavaGCStats { + size_t total_collections; + size_t objects_allocated; + size_t objects_freed; + size_t bytes_allocated; + size_t bytes_freed; + size_t current_live_objects; + size_t current_live_bytes; + size_t peak_live_bytes; +} RavaGCStats_t; + +typedef struct RavaGCState { + RavaGCHeader_t *gray_stack[RAVA_GC_GRAY_STACK_SIZE]; + size_t gray_count; + bool gc_active; + bool gc_enabled; + size_t objects_marked; + size_t bytes_marked; +} RavaGCState_t; + +void rava_gc_init(void); +void rava_gc_shutdown(void); + +void* rava_gc_alloc(size_t size, uint8_t type); +void rava_gc_free(void *ptr); + +void rava_gc_collect(void); +void rava_gc_collect_if_needed(void); + +void rava_gc_enable(void); +void rava_gc_disable(void); +bool rava_gc_is_enabled(void); + +void rava_gc_pin(void *ptr); +void rava_gc_unpin(void *ptr); + +RavaGCStats_t rava_gc_get_stats(void); +void rava_gc_print_stats(void); + +#endif diff --git a/runtime/gc/gc_heap.c b/runtime/gc/gc_heap.c new file mode 100644 index 0000000..c15975f --- /dev/null +++ b/runtime/gc/gc_heap.c @@ -0,0 +1,188 @@ +#include "gc_heap.h" +#include "../runtime.h" +#include +#include + +static RavaGCHeap_t g_heap; + +void rava_gc_heap_init(void) { + memset(&g_heap, 0, sizeof(g_heap)); + g_heap.gc_threshold = RAVA_GC_INITIAL_THRESHOLD; + g_heap.all_blocks = NULL; + g_heap.current_block = NULL; + g_heap.free_blocks = NULL; + g_heap.recyclable_blocks = NULL; + g_heap.all_objects = NULL; +} + +void rava_gc_heap_shutdown(void) { + RavaGCBlock_t *block = g_heap.all_blocks; + while (block) { + RavaGCBlock_t *next = block->next; + free(block); + block = next; + } + + block = g_heap.free_blocks; + while (block) { + RavaGCBlock_t *next = block->next; + free(block); + block = next; + } + + memset(&g_heap, 0, sizeof(g_heap)); +} + +static RavaGCBlock_t* _rava_gc_create_block(void) { + RavaGCBlock_t *block = calloc(1, sizeof(RavaGCBlock_t)); + if (!block) return NULL; + + block->flags = 0; + block->free_lines = RAVA_GC_LINES_PER_BLOCK; + memset(block->line_marks, 0, sizeof(block->line_marks)); + + return block; +} + +bool rava_gc_request_block(void) { + if (g_heap.free_blocks) { + RavaGCBlock_t *block = g_heap.free_blocks; + g_heap.free_blocks = block->next; + + block->next = g_heap.all_blocks; + block->prev = NULL; + if (g_heap.all_blocks) { + g_heap.all_blocks->prev = block; + } + g_heap.all_blocks = block; + + g_heap.current_block = block; + g_heap.bump_ptr = block->data; + g_heap.block_limit = block->data + RAVA_GC_BLOCK_SIZE; + return true; + } + + if (g_heap.recyclable_blocks) { + RavaGCBlock_t *block = g_heap.recyclable_blocks; + g_heap.recyclable_blocks = block->next; + + block->next = g_heap.all_blocks; + block->prev = NULL; + if (g_heap.all_blocks) { + g_heap.all_blocks->prev = block; + } + g_heap.all_blocks = block; + + g_heap.current_block = block; + g_heap.bump_ptr = block->data; + g_heap.block_limit = block->data + RAVA_GC_BLOCK_SIZE; + return true; + } + + RavaGCBlock_t *block = _rava_gc_create_block(); + if (!block) return false; + + block->next = g_heap.all_blocks; + block->prev = NULL; + if (g_heap.all_blocks) { + g_heap.all_blocks->prev = block; + } + g_heap.all_blocks = block; + + g_heap.current_block = block; + g_heap.bump_ptr = block->data; + g_heap.block_limit = block->data + RAVA_GC_BLOCK_SIZE; + + return true; +} + +void* rava_gc_heap_alloc(size_t size, uint8_t type) { + size = RAVA_ALIGN(size, 8); + + if (size > RAVA_GC_BLOCK_SIZE) { + RavaGCHeader_t *obj = calloc(1, size); + if (!obj) return NULL; + + obj->gc_type = type; + obj->gc_mark = RAVA_GC_WHITE; + obj->gc_age = 0; + obj->gc_flags = RAVA_GC_FLAG_PINNED; + obj->gc_size = (uint32_t)size; + + obj->gc_next = g_heap.all_objects; + g_heap.all_objects = obj; + + g_heap.total_allocated += size; + return obj; + } + + if (!g_heap.current_block || g_heap.bump_ptr + size > g_heap.block_limit) { + if (!rava_gc_request_block()) { + return NULL; + } + } + + RavaGCHeader_t *obj = (RavaGCHeader_t*)g_heap.bump_ptr; + g_heap.bump_ptr += size; + g_heap.total_allocated += size; + + obj->gc_type = type; + obj->gc_mark = RAVA_GC_WHITE; + obj->gc_age = 0; + obj->gc_flags = 0; + obj->gc_size = (uint32_t)size; + + obj->gc_next = g_heap.all_objects; + g_heap.all_objects = obj; + + return obj; +} + +void rava_gc_heap_free(void *ptr, size_t size) { + (void)ptr; + (void)size; +} + +void rava_gc_recycle_blocks(void) { + RavaGCBlock_t *block = g_heap.all_blocks; + RavaGCBlock_t *prev = NULL; + + while (block) { + RavaGCBlock_t *next = block->next; + + bool has_live = false; + for (size_t i = 0; i < RAVA_GC_LINES_PER_BLOCK; i++) { + if (block->line_marks[i]) { + has_live = true; + break; + } + } + + if (!has_live && block != g_heap.current_block) { + if (prev) { + prev->next = next; + } else { + g_heap.all_blocks = next; + } + if (next) { + next->prev = prev; + } + + block->next = g_heap.free_blocks; + block->prev = NULL; + g_heap.free_blocks = block; + + memset(block->line_marks, 0, sizeof(block->line_marks)); + block->flags = 0; + block->free_lines = RAVA_GC_LINES_PER_BLOCK; + } else { + prev = block; + } + + block = next; + } +} + +RavaGCHeap_t* rava_gc_get_heap(void) { + return &g_heap; +} diff --git a/runtime/gc/gc_heap.h b/runtime/gc/gc_heap.h new file mode 100644 index 0000000..0b1192a --- /dev/null +++ b/runtime/gc/gc_heap.h @@ -0,0 +1,17 @@ +#ifndef RAVA_GC_HEAP_H +#define RAVA_GC_HEAP_H + +#include "gc.h" + +void rava_gc_heap_init(void); +void rava_gc_heap_shutdown(void); + +void* rava_gc_heap_alloc(size_t size, uint8_t type); +void rava_gc_heap_free(void *ptr, size_t size); + +bool rava_gc_request_block(void); +void rava_gc_recycle_blocks(void); + +RavaGCHeap_t* rava_gc_get_heap(void); + +#endif diff --git a/runtime/gc/gc_mark.c b/runtime/gc/gc_mark.c new file mode 100644 index 0000000..521301a --- /dev/null +++ b/runtime/gc/gc_mark.c @@ -0,0 +1,108 @@ +#include "gc_mark.h" +#include "gc_roots.h" +#include "../runtime.h" +#include + +static RavaGCState_t g_gc_state; + +void rava_gc_mark_init(void) { + memset(&g_gc_state, 0, sizeof(g_gc_state)); + g_gc_state.gc_enabled = true; +} + +RavaGCState_t* rava_gc_get_state(void) { + return &g_gc_state; +} + +void rava_gc_mark_object(RavaGCHeader_t *obj) { + if (!obj || obj->gc_mark != RAVA_GC_WHITE) { + return; + } + + obj->gc_mark = RAVA_GC_GRAY; + + if (g_gc_state.gray_count < RAVA_GC_GRAY_STACK_SIZE) { + g_gc_state.gray_stack[g_gc_state.gray_count++] = obj; + } else { + rava_gc_process_object(obj); + } +} + +static void _rava_gc_trace_object(RavaObject_t *obj) { + for (size_t i = 0; i < obj->field_count; i++) { + rava_gc_mark_value(obj->field_values[i]); + } +} + +static void _rava_gc_trace_array(RavaArray_t *arr) { + if (arr->element_type == RAVA_VAL_OBJECT || + arr->element_type == RAVA_VAL_ARRAY) { + RavaValue_t *values = (RavaValue_t*)arr->data; + for (size_t i = 0; i < arr->length; i++) { + rava_gc_mark_value(values[i]); + } + } +} + +static void _rava_gc_trace_arraylist(RavaArrayList_t *list) { + for (size_t i = 0; i < list->size; i++) { + rava_gc_mark_value(list->data[i]); + } +} + +static void _rava_gc_trace_hashmap(RavaHashMap_t *map) { + for (size_t i = 0; i < map->bucket_count; i++) { + if (map->buckets[i].occupied) { + rava_gc_mark_value(map->buckets[i].value); + } + } +} + +static void _rava_gc_trace_methodref(RavaMethodRef_t *ref) { + if (ref->has_target) { + rava_gc_mark_value(ref->target); + } +} + +void rava_gc_process_object(RavaGCHeader_t *obj) { + obj->gc_mark = RAVA_GC_BLACK; + g_gc_state.objects_marked++; + g_gc_state.bytes_marked += obj->gc_size; + + switch (obj->gc_type) { + case RAVA_GC_TYPE_OBJECT: { + RavaObject_t *o = (RavaObject_t*)obj; + _rava_gc_trace_object(o); + break; + } + case RAVA_GC_TYPE_ARRAY: { + RavaArray_t *a = (RavaArray_t*)obj; + _rava_gc_trace_array(a); + break; + } + case RAVA_GC_TYPE_ARRAYLIST: { + RavaArrayList_t *l = (RavaArrayList_t*)obj; + _rava_gc_trace_arraylist(l); + break; + } + case RAVA_GC_TYPE_HASHMAP: { + RavaHashMap_t *m = (RavaHashMap_t*)obj; + _rava_gc_trace_hashmap(m); + break; + } + case RAVA_GC_TYPE_METHODREF: { + RavaMethodRef_t *r = (RavaMethodRef_t*)obj; + _rava_gc_trace_methodref(r); + break; + } + default: + break; + } +} + +void rava_gc_process_gray_stack(void) { + while (g_gc_state.gray_count > 0) { + RavaGCHeader_t *obj = g_gc_state.gray_stack[--g_gc_state.gray_count]; + rava_gc_process_object(obj); + } +} diff --git a/runtime/gc/gc_mark.h b/runtime/gc/gc_mark.h new file mode 100644 index 0000000..a5c07bd --- /dev/null +++ b/runtime/gc/gc_mark.h @@ -0,0 +1,13 @@ +#ifndef RAVA_GC_MARK_H +#define RAVA_GC_MARK_H + +#include "gc.h" + +void rava_gc_mark_init(void); +void rava_gc_mark_object(RavaGCHeader_t *obj); +void rava_gc_process_gray_stack(void); +void rava_gc_process_object(RavaGCHeader_t *obj); + +RavaGCState_t* rava_gc_get_state(void); + +#endif diff --git a/runtime/gc/gc_roots.c b/runtime/gc/gc_roots.c new file mode 100644 index 0000000..7de9de3 --- /dev/null +++ b/runtime/gc/gc_roots.c @@ -0,0 +1,126 @@ +#include "gc_roots.h" +#include "gc_mark.h" +#include "../runtime.h" +#include "../fastframe.h" +#include "../nanbox.h" + +void rava_gc_mark_value(RavaValue_t value) { + switch (value.type) { + case RAVA_VAL_OBJECT: + if (value.data.object_val) { + rava_gc_mark_object(&value.data.object_val->gc); + } + break; + case RAVA_VAL_ARRAY: + if (value.data.array_val) { + rava_gc_mark_object(&value.data.array_val->gc); + } + break; + case RAVA_VAL_ARRAYLIST: + if (value.data.arraylist_val) { + rava_gc_mark_object(&value.data.arraylist_val->gc); + } + break; + case RAVA_VAL_HASHMAP: + if (value.data.hashmap_val) { + rava_gc_mark_object(&value.data.hashmap_val->gc); + } + break; + case RAVA_VAL_SOCKET: + if (value.data.socket_val) { + rava_gc_mark_object(&value.data.socket_val->gc); + } + break; + case RAVA_VAL_STREAM: + if (value.data.stream_val) { + rava_gc_mark_object(&value.data.stream_val->gc); + } + break; + case RAVA_VAL_METHOD_REF: + if (value.data.method_ref_val) { + rava_gc_mark_object(&value.data.method_ref_val->gc); + } + break; + default: + break; + } +} + +void rava_gc_mark_nanbox(uint64_t v) { + if (rava_nanbox_is_object(v)) { + void *ptr = rava_nanbox_as_object(v); + if (ptr) { + RavaObject_t *obj = (RavaObject_t*)ptr; + rava_gc_mark_object(&obj->gc); + } + } else if (rava_nanbox_is_array(v)) { + void *ptr = rava_nanbox_as_array(v); + if (ptr) { + RavaArray_t *arr = (RavaArray_t*)ptr; + rava_gc_mark_object(&arr->gc); + } + } +} + +void rava_gc_mark_string(const char *str) { + (void)str; +} + +void rava_gc_scan_roots(RavaVM_t *vm) { + if (!vm) return; + + if (vm->call_stack) { + for (size_t i = 0; i < vm->call_stack->count; i++) { + RavaCallFrame_t *frame = vm->call_stack->frames[i]; + if (!frame) continue; + + for (size_t j = 0; j < frame->local_count; j++) { + rava_gc_mark_value(frame->locals[j]); + } + + if (frame->operand_stack) { + for (size_t j = 0; j < frame->operand_stack->top; j++) { + rava_gc_mark_value(frame->operand_stack->values[j]); + } + } + + if (frame->has_this) { + rava_gc_mark_value(frame->this_ref); + } + } + } + + extern size_t rava_frame_depth; + extern FastFrame_t rava_frame_pool[]; + for (size_t i = 0; i < rava_frame_depth; i++) { + FastFrame_t *frame = &rava_frame_pool[i]; + + for (size_t j = 0; j < RAVA_MAX_LOCALS_FIXED; j++) { + rava_gc_mark_nanbox(frame->locals[j]); + } + + for (size_t j = 0; j < frame->stack_top; j++) { + rava_gc_mark_nanbox(frame->stack[j]); + } + + if (frame->has_this) { + rava_gc_mark_nanbox(frame->this_ref); + } + } + + if (vm->static_fields) { + for (size_t i = 0; i < vm->static_fields->count; i++) { + rava_gc_mark_value(vm->static_fields->fields[i].value); + } + } + + for (size_t i = 0; i < RAVA_INTERN_TABLE_SIZE; i++) { + if (vm->intern_table.strings[i]) { + rava_gc_mark_string(vm->intern_table.strings[i]); + } + } + + if (vm->has_exception) { + rava_gc_mark_value(vm->exception_value); + } +} diff --git a/runtime/gc/gc_roots.h b/runtime/gc/gc_roots.h new file mode 100644 index 0000000..dd86d0b --- /dev/null +++ b/runtime/gc/gc_roots.h @@ -0,0 +1,12 @@ +#ifndef RAVA_GC_ROOTS_H +#define RAVA_GC_ROOTS_H + +#include "gc.h" +#include "../runtime.h" + +void rava_gc_scan_roots(RavaVM_t *vm); +void rava_gc_mark_value(RavaValue_t value); +void rava_gc_mark_nanbox(uint64_t v); +void rava_gc_mark_string(const char *str); + +#endif diff --git a/runtime/gc/gc_sweep.c b/runtime/gc/gc_sweep.c new file mode 100644 index 0000000..bcaf347 --- /dev/null +++ b/runtime/gc/gc_sweep.c @@ -0,0 +1,114 @@ +#include "gc_sweep.h" +#include "gc_heap.h" +#include "../runtime.h" +#include +#include + +static void _rava_gc_finalize_object_data(RavaObject_t *o) { + free(o->class_name); + for (size_t i = 0; i < o->field_count; i++) { + free(o->field_names[i]); + } + free(o->field_names); + free(o->field_values); +} + +static void _rava_gc_finalize_array_data(RavaArray_t *a) { + free(a->data); +} + +static void _rava_gc_finalize_arraylist_data(RavaArrayList_t *l) { + free(l->data); +} + +static void _rava_gc_finalize_hashmap_data(RavaHashMap_t *m) { + for (size_t i = 0; i < m->bucket_count; i++) { + if (m->buckets[i].occupied) { + free(m->buckets[i].key); + } + } + free(m->buckets); +} + +static void _rava_gc_finalize_socket_data(RavaSocket_t *s) { + if (!s->closed && s->fd >= 0) { + close(s->fd); + } +} + +static void _rava_gc_finalize_methodref_data(RavaMethodRef_t *r) { + free(r->class_name); + free(r->method_name); +} + +void rava_gc_finalize_object(RavaGCHeader_t *obj) { + switch (obj->gc_type) { + case RAVA_GC_TYPE_OBJECT: { + RavaObject_t *o = (RavaObject_t*)obj; + _rava_gc_finalize_object_data(o); + break; + } + case RAVA_GC_TYPE_ARRAY: { + RavaArray_t *a = (RavaArray_t*)obj; + _rava_gc_finalize_array_data(a); + break; + } + case RAVA_GC_TYPE_ARRAYLIST: { + RavaArrayList_t *l = (RavaArrayList_t*)obj; + _rava_gc_finalize_arraylist_data(l); + break; + } + case RAVA_GC_TYPE_HASHMAP: { + RavaHashMap_t *m = (RavaHashMap_t*)obj; + _rava_gc_finalize_hashmap_data(m); + break; + } + case RAVA_GC_TYPE_SOCKET: { + RavaSocket_t *s = (RavaSocket_t*)obj; + _rava_gc_finalize_socket_data(s); + break; + } + case RAVA_GC_TYPE_METHODREF: { + RavaMethodRef_t *r = (RavaMethodRef_t*)obj; + _rava_gc_finalize_methodref_data(r); + break; + } + default: + break; + } + + if (obj->gc_flags & RAVA_GC_FLAG_PINNED) { + free(obj); + } +} + +void rava_gc_sweep(void) { + RavaGCHeap_t *heap = rava_gc_get_heap(); + RavaGCHeader_t **ptr = &heap->all_objects; + size_t freed_count = 0; + size_t freed_bytes = 0; + + while (*ptr) { + RavaGCHeader_t *obj = *ptr; + + if (obj->gc_mark == RAVA_GC_WHITE) { + *ptr = obj->gc_next; + freed_bytes += obj->gc_size; + freed_count++; + rava_gc_finalize_object(obj); + } else { + obj->gc_mark = RAVA_GC_WHITE; + obj->gc_age++; + ptr = &obj->gc_next; + } + } + + heap->live_bytes = heap->total_allocated - freed_bytes; + heap->total_allocated = heap->live_bytes; + + if (heap->live_bytes > 0) { + heap->gc_threshold = heap->live_bytes * 2; + } else { + heap->gc_threshold = RAVA_GC_INITIAL_THRESHOLD; + } +} diff --git a/runtime/gc/gc_sweep.h b/runtime/gc/gc_sweep.h new file mode 100644 index 0000000..e99d9d9 --- /dev/null +++ b/runtime/gc/gc_sweep.h @@ -0,0 +1,9 @@ +#ifndef RAVA_GC_SWEEP_H +#define RAVA_GC_SWEEP_H + +#include "gc.h" + +void rava_gc_sweep(void); +void rava_gc_finalize_object(RavaGCHeader_t *obj); + +#endif diff --git a/tests/test_gc.c b/tests/test_gc.c new file mode 100644 index 0000000..5289345 --- /dev/null +++ b/tests/test_gc.c @@ -0,0 +1,134 @@ +#include "test_utils.h" + +UnittestTestResult_t* test_gc_basic_allocation(void) { + UNITTEST_BEGIN_TEST("TestGC", "test_gc_basic_allocation"); + + const char *source = + "public class Test {\n" + " public static int main() {\n" + " int[] arr = new int[100];\n" + " for (int i = 0; i < 100; i++) {\n" + " arr[i] = i;\n" + " }\n" + " return arr[99];\n" + " }\n" + "}\n"; + + RAVA_TEST_RUN(_unittest_result, source, "Test", "main", 99, + "Should allocate array without crash"); + + UNITTEST_END_TEST(); +} + +UnittestTestResult_t* test_gc_object_allocation(void) { + UNITTEST_BEGIN_TEST("TestGC", "test_gc_object_allocation"); + + const char *source = + "public class Point {\n" + " public int x;\n" + " public int y;\n" + "}\n" + "public class Test {\n" + " public static int main() {\n" + " Point p = new Point();\n" + " p.x = 10;\n" + " p.y = 20;\n" + " return p.x + p.y;\n" + " }\n" + "}\n"; + + RAVA_TEST_RUN(_unittest_result, source, "Test", "main", 30, + "Should allocate object without crash"); + + UNITTEST_END_TEST(); +} + +UnittestTestResult_t* test_gc_many_allocations(void) { + UNITTEST_BEGIN_TEST("TestGC", "test_gc_many_allocations"); + + const char *source = + "public class Test {\n" + " public static int main() {\n" + " int sum = 0;\n" + " for (int i = 0; i < 1000; i++) {\n" + " int[] temp = new int[10];\n" + " temp[0] = i;\n" + " sum = sum + temp[0];\n" + " }\n" + " return sum;\n" + " }\n" + "}\n"; + + RAVA_TEST_RUN(_unittest_result, source, "Test", "main", 499500, + "Should handle many allocations"); + + UNITTEST_END_TEST(); +} + +UnittestTestResult_t* test_gc_arraylist(void) { + UNITTEST_BEGIN_TEST("TestGC", "test_gc_arraylist"); + + const char *source = + "public class Test {\n" + " public static int main() {\n" + " ArrayList list = new ArrayList();\n" + " for (int i = 0; i < 100; i++) {\n" + " list.add(i);\n" + " }\n" + " return list.size();\n" + " }\n" + "}\n"; + + RAVA_TEST_RUN(_unittest_result, source, "Test", "main", 100, + "Should handle ArrayList allocations"); + + UNITTEST_END_TEST(); +} + +UnittestTestResult_t* test_gc_hashmap(void) { + UNITTEST_BEGIN_TEST("TestGC", "test_gc_hashmap"); + + const char *source = + "public class Test {\n" + " public static int main() {\n" + " HashMap map = new HashMap();\n" + " map.put(\"one\", 1);\n" + " map.put(\"two\", 2);\n" + " map.put(\"three\", 3);\n" + " return map.size();\n" + " }\n" + "}\n"; + + RAVA_TEST_RUN(_unittest_result, source, "Test", "main", 3, + "Should handle HashMap allocations"); + + UNITTEST_END_TEST(); +} + +int main(int argc, char **argv) { + UnittestConfig_t *config = unittest_config_create(); + config->verbosity = 2; + + if (argc > 1 && strcmp(argv[1], "--json") == 0) { + config->output_format = UNITTEST_FORMAT_JSON; + config->use_colors = false; + } + + UnittestTestSuite_t *suite = unittest_test_suite_create("GC Tests"); + + UnittestTestCase_t *tc = unittest_test_case_create("TestGC"); + unittest_test_case_add_result(tc, test_gc_basic_allocation()); + unittest_test_case_add_result(tc, test_gc_object_allocation()); + unittest_test_case_add_result(tc, test_gc_many_allocations()); + unittest_test_case_add_result(tc, test_gc_arraylist()); + unittest_test_case_add_result(tc, test_gc_hashmap()); + unittest_test_suite_add_test_case(suite, tc); + + unittest_generate_report(suite, config); + + int failures = suite->total_failed + suite->total_errors; + unittest_test_suite_destroy(suite); + unittest_config_destroy(config); + + return failures > 0 ? 1 : 0; +}