2017-02-23 22:21:13 +00:00
|
|
|
#include "datatypes.h"
|
|
|
|
#include "gc.h"
|
|
|
|
#include "vm.h"
|
2017-03-01 01:20:29 +00:00
|
|
|
#include "util.h"
|
2017-02-23 22:21:13 +00:00
|
|
|
|
|
|
|
/* The metadata header associated with an allocated block of memory */
|
|
|
|
#define gc_header(mem) ((GCMemoryHeader *)(mem) - 1)
|
|
|
|
|
|
|
|
/* Memory header struct. Node of a linked list of memory blocks. */
|
|
|
|
typedef struct GCMemoryHeader GCMemoryHeader;
|
|
|
|
struct GCMemoryHeader {
|
|
|
|
GCMemoryHeader * next;
|
|
|
|
uint32_t color : 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Helper to mark function environments */
|
|
|
|
static void gst_mark_funcenv(Gst *vm, GstFuncEnv *env) {
|
|
|
|
if (gc_header(env)->color != vm->black) {
|
|
|
|
GstValue temp;
|
|
|
|
gc_header(env)->color = vm->black;
|
|
|
|
if (env->thread) {
|
|
|
|
temp.type = GST_THREAD;
|
|
|
|
temp.data.thread = env->thread;
|
|
|
|
gst_mark(vm, &temp);
|
|
|
|
}
|
|
|
|
if (env->values) {
|
|
|
|
uint32_t count = env->stackOffset;
|
|
|
|
uint32_t i;
|
|
|
|
gc_header(env->values)->color = vm->black;
|
|
|
|
for (i = 0; i < count; ++i)
|
|
|
|
gst_mark(vm, env->values + i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* GC helper to mark a FuncDef */
|
|
|
|
static void gst_mark_funcdef(Gst *vm, GstFuncDef *def) {
|
|
|
|
if (gc_header(def)->color != vm->black) {
|
|
|
|
gc_header(def)->color = vm->black;
|
|
|
|
gc_header(def->byteCode)->color = vm->black;
|
|
|
|
uint32_t count, i;
|
|
|
|
if (def->literals) {
|
|
|
|
count = def->literalsLen;
|
|
|
|
gc_header(def->literals)->color = vm->black;
|
|
|
|
for (i = 0; i < count; ++i) {
|
|
|
|
/* If the literal is a NIL type, it actually
|
|
|
|
* contains a FuncDef */
|
|
|
|
if (def->literals[i].type == GST_NIL) {
|
|
|
|
gst_mark_funcdef(vm, (GstFuncDef *) def->literals[i].data.pointer);
|
|
|
|
} else {
|
|
|
|
gst_mark(vm, def->literals + i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Helper to mark a stack frame. Returns the next frame. */
|
|
|
|
static GstStackFrame *gst_mark_stackframe(Gst *vm, GstStackFrame *frame) {
|
|
|
|
uint32_t i;
|
|
|
|
GstValue *stack = (GstValue *)frame + GST_FRAME_SIZE;
|
|
|
|
gst_mark(vm, &frame->callee);
|
|
|
|
if (frame->env)
|
|
|
|
gst_mark_funcenv(vm, frame->env);
|
|
|
|
for (i = 0; i < frame->size; ++i)
|
|
|
|
gst_mark(vm, stack + i);
|
|
|
|
return (GstStackFrame *)(stack + frame->size);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Mark allocated memory associated with a value. This is
|
|
|
|
* the main function for doing the garbage collection mark phase. */
|
|
|
|
void gst_mark(Gst *vm, GstValue *x) {
|
|
|
|
switch (x->type) {
|
|
|
|
case GST_NIL:
|
|
|
|
case GST_BOOLEAN:
|
|
|
|
case GST_NUMBER:
|
|
|
|
case GST_CFUNCTION:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GST_STRING:
|
|
|
|
gc_header(gst_string_raw(x->data.string))->color = vm->black;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GST_BYTEBUFFER:
|
|
|
|
gc_header(x->data.buffer)->color = vm->black;
|
|
|
|
gc_header(x->data.buffer->data)->color = vm->black;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GST_ARRAY:
|
|
|
|
if (gc_header(x->data.array)->color != vm->black) {
|
|
|
|
uint32_t i, count;
|
|
|
|
count = x->data.array->count;
|
|
|
|
gc_header(x->data.array)->color = vm->black;
|
|
|
|
gc_header(x->data.array->data)->color = vm->black;
|
|
|
|
for (i = 0; i < count; ++i)
|
|
|
|
gst_mark(vm, x->data.array->data + i);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-03-07 20:29:40 +00:00
|
|
|
case GST_TUPLE:
|
|
|
|
if (gc_header(gst_tuple_raw(x->data.tuple))->color != vm->black) {
|
|
|
|
uint32_t i, count;
|
|
|
|
count = gst_tuple_length(x->data.tuple);
|
|
|
|
gc_header(gst_tuple_raw(x->data.tuple))->color = vm->black;
|
|
|
|
for (i = 0; i < count; ++i)
|
|
|
|
gst_mark(vm, x->data.tuple + i);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-02-23 22:21:13 +00:00
|
|
|
case GST_THREAD:
|
|
|
|
if (gc_header(x->data.thread)->color != vm->black) {
|
|
|
|
GstThread *thread = x->data.thread;
|
|
|
|
GstStackFrame *frame = (GstStackFrame *)thread->data;
|
2017-02-26 16:47:50 +00:00
|
|
|
GstStackFrame *end = (GstStackFrame *)(thread->data +
|
|
|
|
thread->count - GST_FRAME_SIZE);
|
2017-02-23 22:21:13 +00:00
|
|
|
gc_header(thread)->color = vm->black;
|
|
|
|
gc_header(thread->data)->color = vm->black;
|
|
|
|
while (frame <= end)
|
|
|
|
frame = gst_mark_stackframe(vm, frame);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GST_FUNCTION:
|
|
|
|
if (gc_header(x->data.function)->color != vm->black) {
|
|
|
|
GstFunction *f = x->data.function;
|
|
|
|
gc_header(f)->color = vm->black;
|
|
|
|
gst_mark_funcdef(vm, f->def);
|
|
|
|
if (f->env)
|
|
|
|
gst_mark_funcenv(vm, f->env);
|
|
|
|
if (f->parent) {
|
|
|
|
GstValue temp;
|
|
|
|
temp.type = GST_FUNCTION;
|
|
|
|
temp.data.function = f->parent;
|
|
|
|
gst_mark(vm, &temp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GST_OBJECT:
|
|
|
|
if (gc_header(x->data.object)->color != vm->black) {
|
|
|
|
uint32_t i;
|
|
|
|
GstBucket *bucket;
|
|
|
|
gc_header(x->data.object)->color = vm->black;
|
|
|
|
gc_header(x->data.object->buckets)->color = vm->black;
|
|
|
|
for (i = 0; i < x->data.object->capacity; ++i) {
|
|
|
|
bucket = x->data.object->buckets[i];
|
|
|
|
while (bucket) {
|
|
|
|
gc_header(bucket)->color = vm->black;
|
|
|
|
gst_mark(vm, &bucket->key);
|
|
|
|
gst_mark(vm, &bucket->value);
|
|
|
|
bucket = bucket->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Iterate over all allocated memory, and free memory that is not
|
|
|
|
* marked as reachable. Flip the gc color flag for next sweep. */
|
|
|
|
void gst_sweep(Gst *vm) {
|
|
|
|
GCMemoryHeader *previous = NULL;
|
|
|
|
GCMemoryHeader *current = vm->blocks;
|
|
|
|
GCMemoryHeader *next;
|
|
|
|
while (current) {
|
|
|
|
next = current->next;
|
|
|
|
if (current->color != vm->black) {
|
|
|
|
if (previous) {
|
|
|
|
previous->next = next;
|
|
|
|
} else {
|
|
|
|
vm->blocks = next;
|
|
|
|
}
|
2017-03-01 01:20:29 +00:00
|
|
|
gst_raw_free(current);
|
2017-02-23 22:21:13 +00:00
|
|
|
} else {
|
|
|
|
previous = current;
|
|
|
|
}
|
|
|
|
current = next;
|
|
|
|
}
|
|
|
|
/* Rotate flag */
|
|
|
|
vm->black = !vm->black;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prepare a memory block */
|
|
|
|
static void *gst_alloc_prepare(Gst *vm, char *rawBlock, uint32_t size) {
|
|
|
|
GCMemoryHeader *mdata;
|
|
|
|
if (rawBlock == NULL) {
|
|
|
|
gst_crash(vm, "out of memory");
|
|
|
|
}
|
|
|
|
vm->nextCollection += size;
|
|
|
|
mdata = (GCMemoryHeader *)rawBlock;
|
|
|
|
mdata->next = vm->blocks;
|
|
|
|
vm->blocks = mdata;
|
|
|
|
mdata->color = !vm->black;
|
|
|
|
return rawBlock + sizeof(GCMemoryHeader);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate some memory that is tracked for garbage collection */
|
|
|
|
void *gst_alloc(Gst *vm, uint32_t size) {
|
|
|
|
uint32_t totalSize = size + sizeof(GCMemoryHeader);
|
2017-03-01 01:20:29 +00:00
|
|
|
return gst_alloc_prepare(vm, gst_raw_alloc(totalSize), totalSize);
|
2017-02-23 22:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate some zeroed memory that is tracked for garbage collection */
|
|
|
|
void *gst_zalloc(Gst *vm, uint32_t size) {
|
|
|
|
uint32_t totalSize = size + sizeof(GCMemoryHeader);
|
2017-03-01 01:20:29 +00:00
|
|
|
return gst_alloc_prepare(vm, gst_raw_calloc(1, totalSize), totalSize);
|
2017-02-23 22:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Run garbage collection */
|
|
|
|
void gst_collect(Gst *vm) {
|
|
|
|
/* Thread can be null */
|
|
|
|
if (vm->thread) {
|
|
|
|
GstValue thread;
|
|
|
|
thread.type = GST_THREAD;
|
|
|
|
thread.data.thread = vm->thread;
|
|
|
|
gst_mark(vm, &thread);
|
|
|
|
}
|
|
|
|
gst_mark(vm, &vm->ret);
|
|
|
|
gst_mark(vm, &vm->error);
|
|
|
|
gst_sweep(vm);
|
|
|
|
vm->nextCollection = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Run garbage collection if needed */
|
|
|
|
void gst_maybe_collect(Gst *vm) {
|
|
|
|
if (vm->nextCollection >= vm->memoryInterval)
|
|
|
|
gst_collect(vm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free all allocated memory */
|
|
|
|
void gst_clear_memory(Gst *vm) {
|
|
|
|
GCMemoryHeader *current = vm->blocks;
|
|
|
|
while (current) {
|
|
|
|
GCMemoryHeader *next = current->next;
|
2017-03-01 01:20:29 +00:00
|
|
|
gst_raw_free(current);
|
2017-02-23 22:21:13 +00:00
|
|
|
current = next;
|
|
|
|
}
|
|
|
|
vm->blocks = NULL;
|
2017-03-01 01:20:29 +00:00
|
|
|
}
|
2017-03-07 20:29:40 +00:00
|
|
|
|
|
|
|
/* Header for managed memory blocks */
|
|
|
|
struct MMHeader {
|
|
|
|
struct MMHeader *next;
|
|
|
|
struct MMHeader *previous;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Initialize managed memory */
|
|
|
|
void gst_mm_init(GstManagedMemory *mm) {
|
|
|
|
*mm = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate some managed memory */
|
|
|
|
void *gst_mm_alloc(GstManagedMemory *mm, uint32_t size) {
|
|
|
|
struct MMHeader *mem = gst_raw_alloc(size + sizeof(struct MMHeader));
|
|
|
|
if (mem == NULL)
|
|
|
|
return NULL;
|
|
|
|
mem->next = *mm;
|
|
|
|
mem->previous = NULL;
|
|
|
|
*mm = mem;
|
|
|
|
return mem + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Intialize zeroed managed memory */
|
|
|
|
void *gst_mm_zalloc(GstManagedMemory *mm, uint32_t size) {
|
|
|
|
struct MMHeader *mem = gst_raw_calloc(1, size + sizeof(struct MMHeader));
|
|
|
|
if (mem == NULL)
|
|
|
|
return NULL;
|
|
|
|
mem->next = *mm;
|
|
|
|
mem->previous = NULL;
|
|
|
|
*mm = mem;
|
|
|
|
return mem + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free a memory block used in managed memory */
|
|
|
|
void gst_mm_free(GstManagedMemory *mm, void *block) {
|
|
|
|
struct MMHeader *mem = (struct MMHeader *)(((char *)block) - sizeof(struct MMHeader));
|
|
|
|
if (mem->previous != NULL) {
|
|
|
|
mem->previous->next = mem->next;
|
|
|
|
} else {
|
|
|
|
*mm = mem->next;
|
|
|
|
}
|
|
|
|
gst_raw_free(mem);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free all memory in managed memory */
|
|
|
|
void gst_mm_clear(GstManagedMemory *mm) {
|
|
|
|
struct MMHeader *block = (struct MMHeader *)(*mm);
|
|
|
|
struct MMHeader *next;
|
|
|
|
while (block != NULL) {
|
|
|
|
next = block->next;
|
|
|
|
free(block);
|
|
|
|
block = next;
|
|
|
|
};
|
|
|
|
*mm = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Analog to realloc */
|
|
|
|
void *gst_mm_realloc(GstManagedMemory *mm, void *block, uint32_t nsize) {
|
|
|
|
struct MMHeader *mem = gst_raw_realloc(block, nsize + sizeof(struct MMHeader));
|
|
|
|
if (mem == NULL)
|
|
|
|
return NULL;
|
|
|
|
mem->next = *mm;
|
|
|
|
mem->previous = NULL;
|
|
|
|
*mm = mem;
|
|
|
|
return mem + 1;
|
|
|
|
}
|