janet/src/core/gc.c

407 lines
13 KiB
C

/*
* Copyright (c) 2018 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <janet/janet.h>
#include "state.h"
#include "symcache.h"
#include "gc.h"
/* GC State */
JANET_THREAD_LOCAL void *janet_vm_blocks;
JANET_THREAD_LOCAL uint32_t janet_vm_gc_interval;
JANET_THREAD_LOCAL uint32_t janet_vm_next_collection;
JANET_THREAD_LOCAL int janet_vm_gc_suspend = 0;
/* Roots */
JANET_THREAD_LOCAL Janet *janet_vm_roots;
JANET_THREAD_LOCAL uint32_t janet_vm_root_count;
JANET_THREAD_LOCAL uint32_t janet_vm_root_capacity;
/* Helpers for marking the various gc types */
static void janet_mark_funcenv(JanetFuncEnv *env);
static void janet_mark_funcdef(JanetFuncDef *def);
static void janet_mark_function(JanetFunction *func);
static void janet_mark_array(JanetArray *array);
static void janet_mark_table(JanetTable *table);
static void janet_mark_struct(const JanetKV *st);
static void janet_mark_tuple(const Janet *tuple);
static void janet_mark_buffer(JanetBuffer *buffer);
static void janet_mark_string(const uint8_t *str);
static void janet_mark_fiber(JanetFiber *fiber);
static void janet_mark_abstract(void *adata);
/* Mark a value */
void janet_mark(Janet x) {
switch (janet_type(x)) {
default: break;
case JANET_STRING:
case JANET_SYMBOL: janet_mark_string(janet_unwrap_string(x)); break;
case JANET_FUNCTION: janet_mark_function(janet_unwrap_function(x)); break;
case JANET_ARRAY: janet_mark_array(janet_unwrap_array(x)); break;
case JANET_TABLE: janet_mark_table(janet_unwrap_table(x)); break;
case JANET_STRUCT: janet_mark_struct(janet_unwrap_struct(x)); break;
case JANET_TUPLE: janet_mark_tuple(janet_unwrap_tuple(x)); break;
case JANET_BUFFER: janet_mark_buffer(janet_unwrap_buffer(x)); break;
case JANET_FIBER: janet_mark_fiber(janet_unwrap_fiber(x)); break;
case JANET_ABSTRACT: janet_mark_abstract(janet_unwrap_abstract(x)); break;
}
}
static void janet_mark_string(const uint8_t *str) {
janet_gc_mark(janet_string_raw(str));
}
static void janet_mark_buffer(JanetBuffer *buffer) {
janet_gc_mark(buffer);
}
static void janet_mark_abstract(void *adata) {
if (janet_gc_reachable(janet_abstract_header(adata)))
return;
janet_gc_mark(janet_abstract_header(adata));
if (janet_abstract_header(adata)->type->gcmark) {
janet_abstract_header(adata)->type->gcmark(adata, janet_abstract_size(adata));
}
}
/* Mark a bunch of items in memory */
static void janet_mark_many(const Janet *values, int32_t n) {
const Janet *end = values + n;
while (values < end) {
janet_mark(*values);
values += 1;
}
}
/* Mark a bunch of key values items in memory */
static void janet_mark_kvs(const JanetKV *kvs, int32_t n) {
const JanetKV *end = kvs + n;
while (kvs < end) {
janet_mark(kvs->key);
janet_mark(kvs->value);
kvs++;
}
}
static void janet_mark_array(JanetArray *array) {
if (janet_gc_reachable(array))
return;
janet_gc_mark(array);
janet_mark_many(array->data, array->count);
}
static void janet_mark_table(JanetTable *table) {
recur: /* Manual tail recursion */
if (janet_gc_reachable(table))
return;
janet_gc_mark(table);
janet_mark_kvs(table->data, table->capacity);
if (table->proto) {
table = table->proto;
goto recur;
}
}
static void janet_mark_struct(const JanetKV *st) {
if (janet_gc_reachable(janet_struct_raw(st)))
return;
janet_gc_mark(janet_struct_raw(st));
janet_mark_kvs(st, janet_struct_capacity(st));
}
static void janet_mark_tuple(const Janet *tuple) {
if (janet_gc_reachable(janet_tuple_raw(tuple)))
return;
janet_gc_mark(janet_tuple_raw(tuple));
janet_mark_many(tuple, janet_tuple_length(tuple));
}
/* Helper to mark function environments */
static void janet_mark_funcenv(JanetFuncEnv *env) {
if (janet_gc_reachable(env))
return;
janet_gc_mark(env);
if (env->offset) {
/* On stack */
janet_mark_fiber(env->as.fiber);
} else {
/* Not on stack */
janet_mark_many(env->as.values, env->length);
}
}
/* GC helper to mark a FuncDef */
static void janet_mark_funcdef(JanetFuncDef *def) {
int32_t i;
if (janet_gc_reachable(def))
return;
janet_gc_mark(def);
janet_mark_many(def->constants, def->constants_length);
for (i = 0; i < def->defs_length; ++i) {
janet_mark_funcdef(def->defs[i]);
}
if (def->source)
janet_mark_string(def->source);
if (def->name)
janet_mark_string(def->name);
}
static void janet_mark_function(JanetFunction *func) {
int32_t i;
int32_t numenvs;
if (janet_gc_reachable(func))
return;
janet_gc_mark(func);
numenvs = func->def->environments_length;
for (i = 0; i < numenvs; ++i) {
janet_mark_funcenv(func->envs[i]);
}
janet_mark_funcdef(func->def);
}
static void janet_mark_fiber(JanetFiber *fiber) {
int32_t i, j;
JanetStackFrame *frame;
recur:
if (janet_gc_reachable(fiber))
return;
janet_gc_mark(fiber);
if (fiber->root)
janet_mark_function(fiber->root);
i = fiber->frame;
j = fiber->stackstart - JANET_FRAME_SIZE;
while (i > 0) {
frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE);
if (NULL != frame->func)
janet_mark_function(frame->func);
if (NULL != frame->env)
janet_mark_funcenv(frame->env);
/* Mark all values in the stack frame */
janet_mark_many(fiber->data + i, j - i);
j = i - JANET_FRAME_SIZE;
i = frame->prevframe;
}
/* Explicit tail recursion */
if (fiber->child) {
fiber = fiber->child;
goto recur;
}
}
/* Deinitialize a block of memory */
static void janet_deinit_block(JanetGCMemoryHeader *block) {
void *mem = ((char *)(block + 1));
JanetAbstractHeader *h = (JanetAbstractHeader *)mem;
switch (block->flags & JANET_MEM_TYPEBITS) {
default:
case JANET_MEMORY_FUNCTION:
break; /* Do nothing for non gc types */
case JANET_MEMORY_SYMBOL:
janet_symbol_deinit((const uint8_t *)mem + 2 * sizeof(int32_t));
break;
case JANET_MEMORY_ARRAY:
janet_array_deinit((JanetArray*) mem);
break;
case JANET_MEMORY_TABLE:
janet_table_deinit((JanetTable*) mem);
break;
case JANET_MEMORY_FIBER:
free(((JanetFiber *)mem)->data);
break;
case JANET_MEMORY_BUFFER:
janet_buffer_deinit((JanetBuffer *) mem);
break;
case JANET_MEMORY_ABSTRACT:
if (h->type->gc) {
if (h->type->gc((void *)(h + 1), h->size)) {
/* finalizer failed. try again later? Panic? For now do nothing. */
;
}
}
break;
case JANET_MEMORY_FUNCENV:
{
JanetFuncEnv *env = (JanetFuncEnv *)mem;
if (0 == env->offset)
free(env->as.values);
}
break;
case JANET_MEMORY_FUNCDEF:
{
JanetFuncDef *def = (JanetFuncDef *)mem;
/* TODO - get this all with one alloc and one free */
free(def->defs);
free(def->environments);
free(def->constants);
free(def->bytecode);
free(def->sourcemap);
}
break;
}
}
/* Iterate over all allocated memory, and free memory that is not
* marked as reachable. Flip the gc color flag for next sweep. */
void janet_sweep() {
JanetGCMemoryHeader *previous = NULL;
JanetGCMemoryHeader *current = janet_vm_blocks;
JanetGCMemoryHeader *next;
while (NULL != current) {
next = current->next;
if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) {
previous = current;
current->flags &= ~JANET_MEM_REACHABLE;
} else {
janet_deinit_block(current);
if (NULL != previous) {
previous->next = next;
} else {
janet_vm_blocks = next;
}
free(current);
}
current = next;
}
}
/* Allocate some memory that is tracked for garbage collection */
void *janet_gcalloc(enum JanetMemoryType type, size_t size) {
JanetGCMemoryHeader *mdata;
size_t total = size + sizeof(JanetGCMemoryHeader);
/* Make sure everything is inited */
janet_assert(NULL != janet_vm_cache, "please initialize janet before use");
void *mem = malloc(total);
/* Check for bad malloc */
if (NULL == mem) {
JANET_OUT_OF_MEMORY;
}
mdata = (JanetGCMemoryHeader *)mem;
/* Configure block */
mdata->flags = type;
/* Prepend block to heap list */
janet_vm_next_collection += (int32_t) size;
mdata->next = janet_vm_blocks;
janet_vm_blocks = mdata;
return (char *) mem + sizeof(JanetGCMemoryHeader);
}
/* Run garbage collection */
void janet_collect(void) {
uint32_t i;
if (janet_vm_gc_suspend) return;
for (i = 0; i < janet_vm_root_count; i++)
janet_mark(janet_vm_roots[i]);
janet_sweep();
janet_vm_next_collection = 0;
}
/* Add a root value to the GC. This prevents the GC from removing a value
* and all of its children. If gcroot is called on a value n times, unroot
* must also be called n times to remove it as a gc root. */
void janet_gcroot(Janet root) {
uint32_t newcount = janet_vm_root_count + 1;
if (newcount > janet_vm_root_capacity) {
uint32_t newcap = 2 * newcount;
janet_vm_roots = realloc(janet_vm_roots, sizeof(Janet) * newcap);
if (NULL == janet_vm_roots) {
JANET_OUT_OF_MEMORY;
}
janet_vm_root_capacity = newcap;
}
janet_vm_roots[janet_vm_root_count] = root;
janet_vm_root_count = newcount;
}
/* Identity equality for GC purposes */
static int janet_gc_idequals(Janet lhs, Janet rhs) {
if (janet_type(lhs) != janet_type(rhs))
return 0;
switch (janet_type(lhs)) {
case JANET_TRUE:
case JANET_FALSE:
case JANET_NIL:
return 1;
case JANET_INTEGER:
return janet_unwrap_integer(lhs) == janet_unwrap_integer(rhs);
case JANET_REAL:
return janet_unwrap_real(lhs) == janet_unwrap_real(rhs);
default:
return janet_unwrap_pointer(lhs) == janet_unwrap_pointer(rhs);
}
}
/* Remove a root value from the GC. This allows the gc to potentially reclaim
* a value and all its children. */
int janet_gcunroot(Janet root) {
Janet *vtop = janet_vm_roots + janet_vm_root_count;
Janet *v = janet_vm_roots;
/* Search from top to bottom as access is most likely LIFO */
for (v = janet_vm_roots; v < vtop; v++) {
if (janet_gc_idequals(root, *v)) {
*v = janet_vm_roots[--janet_vm_root_count];
return 1;
}
}
return 0;
}
/* Remove a root value from the GC. This sets the effective reference count to 0. */
int janet_gcunrootall(Janet root) {
Janet *vtop = janet_vm_roots + janet_vm_root_count;
Janet *v = janet_vm_roots;
int ret = 0;
/* Search from top to bottom as access is most likely LIFO */
for (v = janet_vm_roots; v < vtop; v++) {
if (janet_gc_idequals(root, *v)) {
*v = janet_vm_roots[--janet_vm_root_count];
vtop--;
ret = 1;
}
}
return ret;
}
/* Free all allocated memory */
void janet_clear_memory(void) {
JanetGCMemoryHeader *current = janet_vm_blocks;
while (NULL != current) {
janet_deinit_block(current);
JanetGCMemoryHeader *next = current->next;
free(current);
current = next;
}
janet_vm_blocks = NULL;
}
/* Primitives for suspending GC. */
int janet_gclock(void) { return janet_vm_gc_suspend++; }
void janet_gcunlock(int handle) { janet_vm_gc_suspend = handle; }