From fd34837265a8cb0ba10ad61584000bbecff9f70a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 23 Feb 2017 17:21:13 -0500 Subject: [PATCH] Breaking up functionality into more modules. --- .gitignore | 15 ++ .idea/interpreter.iml | 2 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 211 ++++++++++++++++++++++++ Makefile | 6 +- datatypes.h | 2 - gc.c | 232 ++++++++++++++++++++++++++ gc.h | 29 ++++ main.c | 61 ++++--- parse.c | 4 +- temp.script | 11 +- thread.c | 78 +++++++++ thread.h | 25 +++ value.c | 16 +- value.h | 6 +- vm.c | 370 ++++-------------------------------------- vm.h | 2 +- 19 files changed, 701 insertions(+), 387 deletions(-) create mode 100644 .idea/interpreter.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 gc.c create mode 100644 gc.h create mode 100644 thread.c create mode 100644 thread.h diff --git a/.gitignore b/.gitignore index 9791f25b..64e8f8ce 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,18 @@ Mkfile.old dkms.conf # End of https://www.gitignore.io/api/c + +# Created by https://www.gitignore.io/api/cmake + +### CMake ### +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake + +# End of https://www.gitignore.io/api/cmake diff --git a/.idea/interpreter.iml b/.idea/interpreter.iml new file mode 100644 index 00000000..f08604bb --- /dev/null +++ b/.idea/interpreter.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..79b3c948 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..3e8301f0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..ee514e9e --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1487818367037 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile index e9c5e9dd..b607f724 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ # TIL -CFLAGS=-std=c99 -Wall -Wextra -Wpedantic -g +CFLAGS=-std=c99 -Wall -Wextra -Wpedantic -g -O3 TARGET=interp PREFIX=/usr/local # C sources -HEADERS=vm.h ds.h compile.h parse.h value.h disasm.h datatypes.h -SOURCES=main.c parse.c value.c vm.c ds.c compile.c disasm.c +HEADERS=vm.h ds.h compile.h parse.h value.h disasm.h datatypes.h gc.h thread.h +SOURCES=main.c parse.c value.c vm.c ds.c compile.c disasm.c gc.c thread.c OBJECTS=$(patsubst %.c,%.o,$(SOURCES)) all: $(TARGET) diff --git a/datatypes.h b/datatypes.h index ff6f7543..914d7df2 100644 --- a/datatypes.h +++ b/datatypes.h @@ -164,8 +164,6 @@ struct Gst { jmp_buf jump; GstValue error; GstValue ret; /* Returned value from VMStart. Also holds errors. */ - /* Object definitions */ - GstValue metas[GST_OBJECT]; }; struct GstParser { diff --git a/gc.c b/gc.c new file mode 100644 index 00000000..64ba6aba --- /dev/null +++ b/gc.c @@ -0,0 +1,232 @@ +#include "datatypes.h" +#include "gc.h" +#include "vm.h" +#include "thread.h" +#include + +/* 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; + + case GST_THREAD: + if (gc_header(x->data.thread)->color != vm->black) { + GstThread *thread = x->data.thread; + GstStackFrame *frame = (GstStackFrame *)thread->data; + GstStackFrame *end = gst_thread_frame(thread); + 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; + } + free(current); + } 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); + return gst_alloc_prepare(vm, malloc(totalSize), totalSize); +} + +/* Allocate some zeroed memory that is tracked for garbage collection */ +void *gst_zalloc(Gst *vm, uint32_t size) { + uint32_t totalSize = size + sizeof(GCMemoryHeader); + return gst_alloc_prepare(vm, calloc(1, totalSize), totalSize); +} + +/* Run garbage collection */ +void gst_collect(Gst *vm) { + if (vm->lock > 0) return; + /* 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; + free(current); + current = next; + } + vm->blocks = NULL; +} \ No newline at end of file diff --git a/gc.h b/gc.h new file mode 100644 index 00000000..d054320c --- /dev/null +++ b/gc.h @@ -0,0 +1,29 @@ +#ifndef gc_h_INCLUDED +#define gc_h_INCLUDED + +#include "datatypes.h" + +/* Makr a value as reachable */ +void gst_mark(Gst *vm, GstValue *x); + +/* 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); + +/* Allocate a chunk memory that will be garbage collected. */ +void *gst_alloc(Gst *vm, uint32_t size); + +/* Allocate zeroed memory to be garbage collected */ +void *gst_zalloc(Gst *vm, uint32_t size); + +/* Run a collection */ +void gst_collect(Gst *vm); + +/* Run a collection if we have alloctaed enough memory since the last + collection */ +void gst_maybe_collect(Gst *vm); + +/* Clear all memory */ +void gst_clear_memory(Gst *vm); + +#endif \ No newline at end of file diff --git a/main.c b/main.c index 0f1028c8..2239e1ef 100644 --- a/main.c +++ b/main.c @@ -7,11 +7,11 @@ #include "value.h" #include "disasm.h" -void string_put(uint8_t * string) { +void string_put(FILE *out, uint8_t * string) { uint32_t i; uint32_t len = gst_string_length(string); for (i = 0; i < len; ++i) - fputc(string[i], stdout); + fputc(string[i], out); } /* Test c function */ @@ -20,7 +20,7 @@ GstValue print(Gst *vm) { GstValue nil; count = gst_count_args(vm); for (j = 0; j < count; ++j) { - string_put(gst_to_string(vm, gst_arg(vm, j))); + string_put(stdout, gst_to_string(vm, gst_arg(vm, j))); fputc('\n', stdout); } nil.type = GST_NIL; @@ -28,7 +28,7 @@ GstValue print(Gst *vm) { } /* A simple repl for debugging */ -void debugRepl() { +void debug_repl(FILE *in, FILE *out) { char buffer[1024] = {0}; const char * reader = buffer; GstValue func; @@ -47,8 +47,9 @@ void debugRepl() { while (p.status == GST_PARSER_PENDING) { /* Get some input if we are done */ if (*reader == '\0') { - printf(">> "); - if (!fgets(buffer, sizeof(buffer), stdin)) { + if (out) + fprintf(out, ">> "); + if (!fgets(buffer, sizeof(buffer), in)) { return; } p.index = 0; @@ -60,13 +61,15 @@ void debugRepl() { /* Check for parsing errors */ if (p.error) { unsigned i; - printf("\n"); - printf("%s\n", buffer); - for (i = 0; i < p.index; ++i) { - printf(" "); + if (out) { + fprintf(out, "\n"); + fprintf(out, "%s\n", buffer); + for (i = 0; i < p.index; ++i) { + fprintf(out, " "); + } + fprintf(out, "^\n"); + fprintf(out, "\nParse error: %s\n", p.error); } - printf("^\n"); - printf("\nParse error: %s\n", p.error); reader = buffer; /* Flush the input buffer */ buffer[0] = '\0'; continue; @@ -80,33 +83,39 @@ void debugRepl() { /* Check for compilation errors */ if (c.error) { - printf("Compiler error: %s\n", c.error); + if (out) { + fprintf(out, "Compiler error: %s\n", c.error); + } reader = buffer; buffer[0] = 0; continue; } /* Print asm */ -/* printf("\n"); - gst_dasm_function(stdout, func.data.function); - printf("\n");*/ + if (out) { + fprintf(out, "\n"); + gst_dasm_function(out, func.data.function); + fprintf(out, "\n"); + } /* Execute function */ gst_load(&vm, func); if (gst_start(&vm)) { - if (vm.crash) { - printf("VM crash: %s\n", vm.crash); - } else { - printf("VM error: "); - string_put(gst_to_string(&vm, vm.error)); - printf("\n"); + if (out) { + if (vm.crash) { + fprintf(out, "VM crash: %s\n", vm.crash); + } else { + fprintf(out, "VM error: "); + string_put(out, gst_to_string(&vm, vm.error)); + printf("\n"); + } } reader = buffer; buffer[0] = 0; continue; - } else { - string_put(gst_to_string(&vm, vm.ret)); - printf("\n"); + } else if (out) { + string_put(out, gst_to_string(&vm, vm.ret)); + fprintf(out, "\n"); } } @@ -114,6 +123,6 @@ void debugRepl() { int main() { printf("Super cool interpreter v0.0\n"); - debugRepl(); + debug_repl(stdin, stdout); return 0; } diff --git a/parse.c b/parse.c index 6ec7f2f8..33910e6e 100644 --- a/parse.c +++ b/parse.c @@ -44,7 +44,7 @@ struct GstParseState { /* Get the top ParseState in the parse stack */ static GstParseState *parser_peek(GstParser *p) { if (!p->count) { - p_error(p, "Parser stack underflow. (Peek)"); + p_error(p, "parser stack underflow"); return NULL; } return p->data + p->count - 1; @@ -53,7 +53,7 @@ static GstParseState *parser_peek(GstParser *p) { /* Remove the top state from the ParseStack */ static GstParseState *parser_pop(GstParser * p) { if (!p->count) { - p_error(p, "Parser stack underflow. (Pop)"); + p_error(p, "parser stack underflow"); return NULL; } return p->data + --p->count; diff --git a/temp.script b/temp.script index d42dfc69..01f70ac4 100644 --- a/temp.script +++ b/temp.script @@ -1 +1,10 @@ -(do (:= a 0) (while (< a 10) (:= a (+ a 1)) (print a)) a) +(do + +(:= fib (fn (n) + (if (< n 2) + 1 + (+ (fib (- n 1)) (fib (- n 2)))))) + +(print 10) +(print (fib 33)) +) diff --git a/thread.c b/thread.c new file mode 100644 index 00000000..3311d798 --- /dev/null +++ b/thread.c @@ -0,0 +1,78 @@ +#include "thread.h" +#include "vm.h" +#include + +/* Get the stack frame pointer for a thread */ +GstStackFrame *gst_thread_frame(GstThread * thread) { + return (GstStackFrame *)(thread->data + thread->count - GST_FRAME_SIZE); +} + +/* Ensure that a thread has enough space in it */ +void gst_thread_ensure(Gst *vm, GstThread *thread, uint32_t size) { + if (size > thread->capacity) { + uint32_t newCap = size * 2; + GstValue *newData = gst_alloc(vm, sizeof(GstValue) * newCap); + memcpy(newData, thread->data, thread->capacity * sizeof(GstValue)); + thread->data = newData; + thread->capacity = newCap; + } +} + +/* Push a stack frame onto a thread */ +void gst_thread_push(Gst *vm, GstThread *thread, GstValue callee, uint32_t size) { + uint16_t oldSize; + uint32_t nextCount, i; + GstStackFrame *frame; + if (thread->count) { + frame = gst_thread_frame(thread); + oldSize = frame->size; + } else { + oldSize = 0; + } + nextCount = thread->count + oldSize + GST_FRAME_SIZE; + gst_thread_ensure(vm, thread, nextCount + size); + thread->count = nextCount; + /* Ensure values start out as nil so as to not confuse + * the garabage collector */ + for (i = nextCount; i < nextCount + size; ++i) + thread->data[i].type = GST_NIL; + vm->base = thread->data + thread->count; + vm->frame = frame = (GstStackFrame *)(vm->base - GST_FRAME_SIZE); + /* Set up the new stack frame */ + frame->prevSize = oldSize; + frame->size = size; + frame->env = NULL; + frame->callee = callee; + frame->errorJump = NULL; +} + +/* Copy the current function stack to the current closure + environment. Call when exiting function with closures. */ +void gst_thread_split_env(Gst *vm) { + GstStackFrame *frame = vm->frame; + GstFuncEnv *env = frame->env; + /* Check for closures */ + if (env) { + GstThread *thread = vm->thread; + uint32_t size = frame->size; + env->thread = NULL; + env->stackOffset = size; + env->values = gst_alloc(vm, sizeof(GstValue) * size); + memcpy(env->values, thread->data + thread->count, size * sizeof(GstValue)); + } +} + +/* Pop the top-most stack frame from stack */ +void gst_thread_pop(Gst *vm) { + GstThread *thread = vm->thread; + GstStackFrame *frame = vm->frame; + uint32_t delta = GST_FRAME_SIZE + frame->prevSize; + if (thread->count) { + gst_thread_split_env(vm); + } else { + gst_crash(vm, "stack underflow"); + } + thread->count -= delta; + vm->base -= delta; + vm->frame = (GstStackFrame *)(vm->base - GST_FRAME_SIZE); +} \ No newline at end of file diff --git a/thread.h b/thread.h new file mode 100644 index 00000000..86736e21 --- /dev/null +++ b/thread.h @@ -0,0 +1,25 @@ +#ifndef THREAD_H +#define THREAD_H + +#include "datatypes.h" + +/* The size of a StackFrame in units of Values. */ +#define GST_FRAME_SIZE ((sizeof(GstStackFrame) + sizeof(GstValue) - 1) / sizeof(GstValue)) + +/* Get the stack frame pointer for a thread */ +GstStackFrame *gst_thread_frame(GstThread * thread); + +/* Ensure that a thread has enough space in it */ +void gst_thread_ensure(Gst *vm, GstThread *thread, uint32_t size); + +/* Push a stack frame onto a thread */ +void gst_thread_push(Gst *vm, GstThread *thread, GstValue callee, uint32_t size); + +/* Copy the current function stack to the current closure + environment. Call when exiting function with closures. */ +void gst_thread_split_env(Gst *vm); + +/* Pop the top-most stack frame from stack */ +void gst_thread_pop(Gst *vm); + +#endif diff --git a/value.c b/value.c index f301a3e9..af72c915 100644 --- a/value.c +++ b/value.c @@ -5,6 +5,11 @@ #include "ds.h" #include "vm.h" +/* Boolean truth definition */ +int gst_truthy(GstValue v) { + return v.type != GST_NIL && !(v.type == GST_BOOLEAN && !v.data.boolean); +} + static uint8_t * load_cstring(Gst *vm, const char *string, uint32_t len) { uint8_t *data = gst_alloc(vm, len + 2 * sizeof(uint32_t)); data += 2 * sizeof(uint32_t); @@ -344,13 +349,4 @@ void gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value) { default: gst_error(vm, "Cannot set."); } -} - -/* Get the meta value associated with a value */ -GstValue gst_meta(Gst *vm, GstValue x) { - switch (x.type) { - default: return vm->metas[x.type]; - case GST_OBJECT: - return x.data.object->meta; - } -} +} \ No newline at end of file diff --git a/value.h b/value.h index 45ec9c93..cb90554f 100644 --- a/value.h +++ b/value.h @@ -3,6 +3,9 @@ #include "datatypes.h" +/* Check for boolean truthiness */ +int gst_truthy(GstValue x); + /* Compare two gst values. All gst values are comparable and strictly * ordered by default. Return 0 if equal, -1 if x is less than y, and * 1 and x is greater than y. */ @@ -26,7 +29,4 @@ uint8_t *gst_to_string(Gst *vm, GstValue x); /* Generate a hash value for a gst object */ uint32_t gst_hash(GstValue x); -/* Get the meta value for a given value */ -GstValue gst_meta(Gst *vm, GstValue x); - #endif /* end of include guard: VALUE_H_1RJPQKFM */ diff --git a/vm.c b/vm.c index f78683cd..8b5bee12 100644 --- a/vm.c +++ b/vm.c @@ -4,310 +4,13 @@ #include "vm.h" #include "value.h" #include "ds.h" +#include "gc.h" +#include "thread.h" -static const char OOM[] = "out of memory"; -static const char NO_UPVALUE[] = "no upvalue"; -static const char EXPECTED_FUNCTION[] = "expected function"; -static const char VMS_EXPECTED_NUMBER_ROP[] = "expected right operand to be number"; -static const char VMS_EXPECTED_NUMBER_LOP[] = "expected left operand to be number"; - -/* The size of a StackFrame in units of Values. */ -#define FRAME_SIZE ((sizeof(GstStackFrame) + sizeof(GstValue) - 1) / sizeof(GstValue)) - -/* Get the stack frame pointer for a thread */ -static GstStackFrame *thread_frame(GstThread * thread) { - return (GstStackFrame *)(thread->data + thread->count - FRAME_SIZE); -} - -/* Ensure that a thread has enough space in it */ -static void thread_ensure(Gst *vm, GstThread *thread, uint32_t size) { - if (size > thread->capacity) { - uint32_t newCap = size * 2; - GstValue *newData = gst_alloc(vm, sizeof(GstValue) * newCap); - memcpy(newData, thread->data, thread->capacity * sizeof(GstValue)); - thread->data = newData; - thread->capacity = newCap; - } -} - -/* Push a stack frame onto a thread */ -static void thread_push(Gst *vm, GstThread *thread, GstValue callee, uint32_t size) { - uint16_t oldSize; - uint32_t nextCount, i; - GstStackFrame *frame; - if (thread->count) { - frame = thread_frame(thread); - oldSize = frame->size; - } else { - oldSize = 0; - } - nextCount = thread->count + oldSize + FRAME_SIZE; - thread_ensure(vm, thread, nextCount + size); - thread->count = nextCount; - /* Ensure values start out as nil so as to not confuse - * the garabage collector */ - for (i = nextCount; i < nextCount + size; ++i) - thread->data[i].type = GST_NIL; - vm->base = thread->data + thread->count; - vm->frame = frame = (GstStackFrame *)(vm->base - FRAME_SIZE); - /* Set up the new stack frame */ - frame->prevSize = oldSize; - frame->size = size; - frame->env = NULL; - frame->callee = callee; - frame->errorJump = NULL; -} - -/* Copy the current function stack to the current closure - environment. Call when exiting function with closures. */ -static void thread_split_env(Gst *vm) { - GstStackFrame *frame = vm->frame; - GstFuncEnv *env = frame->env; - /* Check for closures */ - if (env) { - GstThread *thread = vm->thread; - uint32_t size = frame->size; - env->thread = NULL; - env->stackOffset = size; - env->values = gst_alloc(vm, sizeof(GstValue) * size); - memcpy(env->values, thread->data + thread->count, size * sizeof(GstValue)); - } -} - -/* Pop the top-most stack frame from stack */ -static void thread_pop(Gst *vm) { - GstThread *thread = vm->thread; - GstStackFrame *frame = vm->frame; - uint32_t delta = FRAME_SIZE + frame->prevSize; - if (thread->count) { - thread_split_env(vm); - } else { - gst_crash(vm, "stack underflow"); - } - thread->count -= delta; - vm->base -= delta; - vm->frame = (GstStackFrame *)(vm->base - FRAME_SIZE); -} - - -/* 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; -}; - -/* Forward declaration */ -static void gst_mark(Gst *vm, GstValue *x); - -/* 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 + 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. */ -static 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; - - case GST_THREAD: - if (gc_header(x->data.thread)->color != vm->black) { - GstThread *thread = x->data.thread; - GstStackFrame *frame = (GstStackFrame *)thread->data; - GstStackFrame *end = thread_frame(thread); - 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. */ -static 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; - } - free(current); - } 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, OOM); - } - 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); - return gst_alloc_prepare(vm, malloc(totalSize), totalSize); -} - -/* Allocate some zeroed memory that is tracked for garbage collection */ -void *gst_zalloc(Gst *vm, uint32_t size) { - uint32_t totalSize = size + sizeof(GCMemoryHeader); - return gst_alloc_prepare(vm, calloc(1, totalSize), totalSize); -} - -/* Run garbage collection */ -void gst_collect(Gst *vm) { - if (vm->lock > 0) return; - /* 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); -} +static const char GST_NO_UPVALUE[] = "no upvalue"; +static const char GST_EXPECTED_FUNCTION[] = "expected function"; +static const char GST_EXPECTED_NUMBER_ROP[] = "expected right operand to be number"; +static const char GST_EXPECTED_NUMBER_LOP[] = "expected left operand to be number"; /* Get an upvalue */ static GstValue *gst_vm_upvalue_location(Gst *vm, GstFunction *fn, uint16_t level, uint16_t index) { @@ -317,7 +20,7 @@ static GstValue *gst_vm_upvalue_location(Gst *vm, GstFunction *fn, uint16_t leve return vm->base + index; while (fn && --level) fn = fn->parent; - gst_assert(vm, fn, NO_UPVALUE); + gst_assert(vm, fn, GST_NO_UPVALUE); env = fn->env; if (env->thread) stack = env->thread->data + env->stackOffset; @@ -329,19 +32,14 @@ static GstValue *gst_vm_upvalue_location(Gst *vm, GstFunction *fn, uint16_t leve /* Get a literal */ static GstValue gst_vm_literal(Gst *vm, GstFunction *fn, uint16_t index) { if (index > fn->def->literalsLen) { - gst_error(vm, NO_UPVALUE); + gst_error(vm, GST_NO_UPVALUE); } return fn->def->literals[index]; } -/* Boolean truth definition */ -static int truthy(GstValue v) { - return v.type != GST_NIL && !(v.type == GST_BOOLEAN && !v.data.boolean); -} - /* Return from the vm */ static void gst_vm_return(Gst *vm, GstValue ret) { - thread_pop(vm); + gst_thread_pop(vm); if (vm->thread->count == 0) { gst_exit(vm, ret); } @@ -361,11 +59,11 @@ static void gst_vm_call(Gst *vm) { vm->frame->ret = vm->pc[2]; if (callee.type == GST_FUNCTION) { GstFunction *fn = callee.data.function; - thread_push(vm, thread, callee, fn->def->locals); + gst_thread_push(vm, thread, callee, fn->def->locals); } else if (callee.type == GST_CFUNCTION) { - thread_push(vm, thread, callee, arity); + gst_thread_push(vm, thread, callee, arity); } else { - gst_error(vm, EXPECTED_FUNCTION); + gst_error(vm, GST_EXPECTED_FUNCTION); } oldBase = thread->data + oldCount; if (callee.type == GST_CFUNCTION) { @@ -393,18 +91,18 @@ static void gst_vm_tailcall(Gst *vm) { uint16_t newFrameSize, currentFrameSize; uint32_t i; /* Check for closures */ - thread_split_env(vm); + gst_thread_split_env(vm); if (callee.type == GST_CFUNCTION) { newFrameSize = arity; } else if (callee.type == GST_FUNCTION) { GstFunction * f = callee.data.function; newFrameSize = f->def->locals; } else { - gst_error(vm, EXPECTED_FUNCTION); + gst_error(vm, GST_EXPECTED_FUNCTION); } /* Ensure stack has enough space for copies of arguments */ currentFrameSize = vm->frame->size; - thread_ensure(vm, thread, thread->count + currentFrameSize + arity); + gst_thread_ensure(vm, thread, thread->count + currentFrameSize + arity); vm->base = thread->data + thread->count; /* Copy the arguments into the extra space */ for (i = 0; i < arity; ++i) @@ -432,7 +130,7 @@ static void gst_vm_tailcall(Gst *vm) { static GstValue gst_vm_closure(Gst *vm, uint16_t literal) { GstThread *thread = vm->thread; if (vm->frame->callee.type != GST_FUNCTION) { - gst_error(vm, EXPECTED_FUNCTION); + gst_error(vm, GST_EXPECTED_FUNCTION); } else { GstValue constant, ret; GstFunction *fn, *current; @@ -471,9 +169,9 @@ int gst_start(Gst *vm) { vm->lock = 0; return 0; } else if (n == 2) { - /* Error. Handling TODO. */ + /* Error. */ while (vm->thread->count && !vm->frame->errorJump) { - thread_pop(vm); + gst_thread_pop(vm); } if (vm->thread->count == 0) return n; @@ -499,8 +197,8 @@ int gst_start(Gst *vm) { #define DO_BINARY_MATH(op) \ v1 = vm->base[vm->pc[2]]; \ v2 = vm->base[vm->pc[3]]; \ - gst_assert(vm, v1.type == GST_NUMBER, VMS_EXPECTED_NUMBER_LOP); \ - gst_assert(vm, v2.type == GST_NUMBER, VMS_EXPECTED_NUMBER_ROP); \ + gst_assert(vm, v1.type == GST_NUMBER, GST_EXPECTED_NUMBER_LOP); \ + gst_assert(vm, v2.type == GST_NUMBER, GST_EXPECTED_NUMBER_ROP); \ temp.type = GST_NUMBER; \ temp.data.number = v1.data.number op v2.data.number; \ vm->base[vm->pc[1]] = temp; \ @@ -523,7 +221,7 @@ int gst_start(Gst *vm) { case GST_OP_NOT: /* Boolean unary (Boolean not) */ temp.type = GST_BOOLEAN; - temp.data.boolean = !truthy(vm->base[vm->pc[2]]); + temp.data.boolean = !gst_truthy(vm->base[vm->pc[2]]); vm->base[vm->pc[1]] = temp; vm->pc += 3; break; @@ -571,13 +269,13 @@ int gst_start(Gst *vm) { case GST_OP_UPV: /* Load Up Value */ temp = vm->frame->callee; - gst_assert(vm, temp.type == GST_FUNCTION, EXPECTED_FUNCTION); + gst_assert(vm, temp.type == GST_FUNCTION, GST_EXPECTED_FUNCTION); vm->base[vm->pc[1]] = *gst_vm_upvalue_location(vm, temp.data.function, vm->pc[2], vm->pc[3]); vm->pc += 4; break; case GST_OP_JIF: /* Jump If */ - if (truthy(vm->base[vm->pc[1]])) { + if (gst_truthy(vm->base[vm->pc[1]])) { vm->pc += 4; } else { vm->pc += *((int32_t *)(vm->pc + 2)); @@ -598,14 +296,14 @@ int gst_start(Gst *vm) { case GST_OP_SUV: /* Set Up Value */ temp = vm->frame->callee; - gst_assert(vm, temp.type == GST_FUNCTION, EXPECTED_FUNCTION); + gst_assert(vm, temp.type == GST_FUNCTION, GST_EXPECTED_FUNCTION); *gst_vm_upvalue_location(vm, temp.data.function, vm->pc[2], vm->pc[3]) = vm->base[vm->pc[1]]; vm->pc += 4; break; case GST_OP_CST: /* Load constant value */ temp = vm->frame->callee; - gst_assert(vm, temp.type == GST_FUNCTION, EXPECTED_FUNCTION); + gst_assert(vm, temp.type == GST_FUNCTION, GST_EXPECTED_FUNCTION); vm->base[vm->pc[1]] = gst_vm_literal(vm, temp.data.function, vm->pc[2]); vm->pc += 3; break; @@ -699,7 +397,7 @@ int gst_start(Gst *vm) { GstNumber accum = start; \ for (i = 0; i < count; ++i) { \ v1 = vm->base[vm->pc[3 + i]]; \ - gst_assert(vm, v1.type == GST_NUMBER, "Expected number"); \ + gst_assert(vm, v1.type == GST_NUMBER, "expected number"); \ accum = accum op v1.data.number; \ } \ temp.type = GST_NUMBER; \ @@ -762,7 +460,7 @@ int gst_start(Gst *vm) { } /* Move collection only to places that allocate memory */ - /* This, however, is good for testing */ + /* This, however, is good for testing to ensure no memory leaks */ gst_maybe_collect(vm); } } @@ -801,7 +499,7 @@ void gst_init(Gst *vm) { * a collection pretty much every cycle, which is * obviously horrible for performance. It helps ensure * there are no memory bugs during dev */ - vm->memoryInterval = 0; + vm->memoryInterval = 2000; vm->black = 0; vm->lock = 0; /* Add thread */ @@ -819,10 +517,10 @@ void gst_load(Gst *vm, GstValue callee) { vm->thread = thread; if (callee.type == GST_FUNCTION) { GstFunction *fn = callee.data.function; - thread_push(vm, thread, callee, fn->def->locals); + gst_thread_push(vm, thread, callee, fn->def->locals); vm->pc = fn->def->byteCode; } else if (callee.type == GST_CFUNCTION) { - thread_push(vm, thread, callee, 0); + gst_thread_push(vm, thread, callee, 0); vm->pc = NULL; } else { return; @@ -831,11 +529,5 @@ void gst_load(Gst *vm, GstValue callee) { /* Clear all memory associated with the VM */ void gst_deinit(Gst *vm) { - GCMemoryHeader *current = vm->blocks; - while (current) { - GCMemoryHeader *next = current->next; - free(current); - current = next; - } - vm->blocks = NULL; + gst_clear_memory(vm); } diff --git a/vm.h b/vm.h index 1eba976b..373804c3 100644 --- a/vm.h +++ b/vm.h @@ -7,7 +7,7 @@ /* Exit from the VM normally */ #define gst_exit(vm, r) ((vm)->ret = (r), longjmp((vm)->jump, 1)) -/* Bail from the VM with an error. */ +/* Bail from the VM with an error string. */ #define gst_error(vm, e) ((vm)->error = gst_load_cstring((vm), (e)), longjmp((vm)->jump, 2)) /* Crash. Not catchable, unlike error. */