269 lines
5.9 KiB
C
269 lines
5.9 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "compiler.h"
|
|
#include "memory.h"
|
|
#include "vm.h"
|
|
|
|
#ifdef DEBUG_LOG_GC
|
|
#include <stdio.h>
|
|
#include "debug.h"
|
|
#endif
|
|
|
|
#define GC_HEAP_GROW_FACTOR 2
|
|
|
|
void* reallocate(void* pointer, size_t oldSize, size_t newSize)
|
|
{
|
|
vm.bytesAllocated += newSize - oldSize;
|
|
if (newSize > oldSize) {
|
|
#ifdef DEBUG_STRESS_GC
|
|
collectGarbage();
|
|
#endif
|
|
}
|
|
|
|
if (vm.bytesAllocated > vm.nextGC) {
|
|
collectGarbage();
|
|
}
|
|
|
|
if (newSize == 0) {
|
|
free(pointer);
|
|
return nullptr;
|
|
}
|
|
|
|
void* result = realloc(pointer, newSize);
|
|
if (result == nullptr) {
|
|
exit(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void markObject(Obj* object)
|
|
{
|
|
if (object == nullptr) {
|
|
return;
|
|
}
|
|
if (object->isMarked) {
|
|
return;
|
|
}
|
|
#ifdef DEBUG_LOG_GC
|
|
printf("%p mark ", (void*)object);
|
|
printValue(OBJ_VAL(object));
|
|
printf("\n");
|
|
#endif
|
|
|
|
object->isMarked = true;
|
|
|
|
if (vm.grayCapacity < vm.grayCount + 1) {
|
|
vm.grayCapacity = GROW_CAPACITY(vm.grayCapacity);
|
|
vm.grayStack = (Obj**)realloc(vm.grayStack, sizeof(Obj*) * vm.grayCapacity);
|
|
|
|
if (vm.grayStack == nullptr) {
|
|
fprintf(stderr, "failed to realloc grayStack\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
vm.grayStack[vm.grayCount++] = object;
|
|
}
|
|
|
|
void markValue(Value value)
|
|
{
|
|
if (IS_OBJ(value)) {
|
|
markObject(AS_OBJ(value));
|
|
}
|
|
}
|
|
|
|
static void markArray(ValueArray* array)
|
|
{
|
|
for (auto i = 0; i < array->count; i++) {
|
|
markValue(array->values[i]);
|
|
}
|
|
}
|
|
|
|
static void blackenObject(Obj* object)
|
|
{
|
|
#ifdef DEBUG_LOG_GC
|
|
printf("%p blacken ", (void*)object);
|
|
printValue(OBJ_VAL(object));
|
|
printf("\n");
|
|
#endif
|
|
|
|
switch (object->type) {
|
|
case OBJ_BOUND_METHOD:
|
|
{
|
|
ObjBoundMethod* bound = (ObjBoundMethod*)object;
|
|
markValue(bound->receiver);
|
|
markObject((Obj*)bound->method);
|
|
}
|
|
break;
|
|
case OBJ_CLASS:
|
|
{
|
|
ObjClass* klass = (ObjClass*)object;
|
|
markObject((Obj*)klass->name);
|
|
markTable(&klass->methods);
|
|
}
|
|
break;
|
|
case OBJ_CLOSURE:
|
|
{
|
|
ObjClosure* closure = (ObjClosure*)object;
|
|
markObject((Obj*)closure->function);
|
|
for (auto i = 0; i < closure->upvalueCount; i++) {
|
|
markObject((Obj*)closure->upvalues[i]);
|
|
}
|
|
}
|
|
break;
|
|
case OBJ_FUNCTION:
|
|
{
|
|
ObjFunction* function = (ObjFunction*)object;
|
|
markObject((Obj*)function->name);
|
|
markArray(&function->chunk.constants);
|
|
}
|
|
break;
|
|
case OBJ_INSTANCE:
|
|
{
|
|
ObjInstance* instance = (ObjInstance*)object;
|
|
markObject((Obj*)instance->klass);
|
|
markTable(&instance->fields);
|
|
}
|
|
break;
|
|
case OBJ_UPVALUE:
|
|
markValue(((ObjUpvalue*)object)->closed);
|
|
break;
|
|
case OBJ_NATIVE:
|
|
case OBJ_STRING:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void freeObject(Obj* object)
|
|
{
|
|
#ifdef DEBUG_LOG_GC
|
|
printf("%p free type %d\n", (void*)object, object->type);
|
|
#endif
|
|
|
|
switch (object->type) {
|
|
case OBJ_BOUND_METHOD:
|
|
FREE(ObjBoundMethod, object);
|
|
break;
|
|
case OBJ_CLASS:
|
|
{
|
|
ObjClass* klass = (ObjClass*)object;
|
|
freeTable(&klass->methods);
|
|
FREE(ObjClass, object);
|
|
}
|
|
break;
|
|
case OBJ_CLOSURE:
|
|
FREE(ObjClosure, object);
|
|
break;
|
|
case OBJ_FUNCTION:
|
|
{
|
|
ObjFunction* function = (ObjFunction*)object;
|
|
freeChunk(&function->chunk);
|
|
FREE(ObjFunction, object);
|
|
}
|
|
break;
|
|
case OBJ_INSTANCE:
|
|
{
|
|
ObjInstance* instance = (ObjInstance*)object;
|
|
freeTable(&instance->fields);
|
|
FREE(ObjInstance, object);
|
|
}
|
|
break;
|
|
case OBJ_NATIVE:
|
|
FREE(ObjNative, object);
|
|
break;
|
|
case OBJ_STRING:
|
|
{
|
|
ObjString* string = (ObjString*)object;
|
|
FREE_ARRAY(char, string->chars, string->length + 1);
|
|
FREE(ObjString, object);
|
|
}
|
|
break;
|
|
case OBJ_UPVALUE:
|
|
FREE(ObjUpvalue, object);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void markRoots()
|
|
{
|
|
for (Value* slot = vm.stack; slot < vm.stackTop; slot++) {
|
|
markValue(*slot);
|
|
}
|
|
|
|
for (auto i = 0; i < vm.frameCount; i++) {
|
|
markObject((Obj*)vm.frames[i].closure);
|
|
}
|
|
|
|
for (ObjUpvalue* upvalue = vm.openUpvalues; upvalue != nullptr; upvalue = upvalue->next) {
|
|
markObject((Obj*)upvalue);
|
|
}
|
|
|
|
markTable(&vm.globals);
|
|
markCompilerRoots();
|
|
markObject((Obj*)vm.initString);
|
|
}
|
|
|
|
static void traceReferences()
|
|
{
|
|
while (vm.grayCount > 0) {
|
|
Obj* object = vm.grayStack[--vm.grayCount];
|
|
blackenObject(object);
|
|
}
|
|
}
|
|
|
|
static void sweep()
|
|
{
|
|
Obj* previous = nullptr;
|
|
Obj* object = vm.objects;
|
|
while (object != nullptr) {
|
|
if (object->isMarked) {
|
|
object->isMarked = false;
|
|
previous = object;
|
|
object = object->next;
|
|
} else {
|
|
Obj* unreached = object;
|
|
object = object->next;
|
|
if (previous != nullptr) {
|
|
previous->next = object;
|
|
} else {
|
|
vm.objects = object;
|
|
}
|
|
|
|
freeObject(unreached);
|
|
}
|
|
}
|
|
}
|
|
|
|
void collectGarbage()
|
|
{
|
|
#ifdef DEBUG_LOG_GC
|
|
printf("-- gc begin\n");
|
|
size_t before = vm.bytesAllocated;
|
|
#endif
|
|
|
|
markRoots();
|
|
traceReferences();
|
|
tableRemoveWhite(&vm.strings);
|
|
sweep();
|
|
|
|
vm.nextGC = vm.bytesAllocated * GC_HEAP_GROW_FACTOR;
|
|
|
|
#ifdef DEBUG_LOG_GC
|
|
printf("-- gc end\n");
|
|
printf(" collected %zu bytes (from %zu to %zu) next at %zu\n", before - vm.bytesAllocated, before, vm.bytesAllocated, vm.nextGC);
|
|
#endif
|
|
}
|
|
|
|
void freeObjects()
|
|
{
|
|
Obj* object = vm.objects;
|
|
while (object != nullptr) {
|
|
Obj* next = object->next;
|
|
freeObject(object);
|
|
object = next;
|
|
}
|
|
|
|
free(vm.grayStack);
|
|
}
|
|
|