Garbage collection!

This commit is contained in:
Bob Nystrom 2013-11-12 08:32:35 -08:00
parent 0339bcdeca
commit 7e2c200e0d
6 changed files with 460 additions and 134 deletions

25
src/common.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef wren_common_h
#define wren_common_h
// Define this to stress test the GC. It will perform a collection before every
// allocation.
//#define DEBUG_GC_STRESS
// Define this to log memory operations.
//#define TRACE_MEMORY
#ifdef DEBUG
#define ASSERT(condition, message) \
if (!(condition)) { \
printf("ASSERT FAIL " __FILE__ ":%d - %s\n", __LINE__, message); \
abort(); \
}
#else
#define ASSERT(condition, message) ;
#endif
#endif

View File

@ -128,12 +128,7 @@ static void initCompiler(Compiler* compiler, Parser* parser,
initSymbolTable(&compiler->locals); initSymbolTable(&compiler->locals);
compiler->fn = makeFunction(); compiler->fn = newFunction(parser->vm);
// TODO(bob): Hack! make variable sized.
compiler->fn->bytecode = malloc(sizeof(Code) * 1024);
// TODO(bob): Hack! make variable sized.
compiler->fn->constants = malloc(sizeof(Value) * 256);
compiler->fn->numConstants = 0; compiler->fn->numConstants = 0;
} }
@ -637,6 +632,10 @@ static void function(Compiler* compiler)
Compiler fnCompiler; Compiler fnCompiler;
initCompiler(&fnCompiler, compiler->parser, compiler, 0); initCompiler(&fnCompiler, compiler->parser, compiler, 0);
// Add the function to the constant table. Do this immediately so that it's
// reachable by the GC.
compiler->fn->constants[compiler->fn->numConstants++] = (Value)fnCompiler.fn;
if (match(&fnCompiler, TOKEN_LEFT_BRACE)) if (match(&fnCompiler, TOKEN_LEFT_BRACE))
{ {
// Block body. // Block body.
@ -665,9 +664,6 @@ static void function(Compiler* compiler)
emit(&fnCompiler, CODE_END); emit(&fnCompiler, CODE_END);
// Add the function to the constant table.
compiler->fn->constants[compiler->fn->numConstants++] = (Value)fnCompiler.fn;
// Compile the code to load it. // Compile the code to load it.
emit(compiler, CODE_CONSTANT); emit(compiler, CODE_CONSTANT);
emit(compiler, compiler->fn->numConstants - 1); emit(compiler, compiler->fn->numConstants - 1);
@ -722,7 +718,8 @@ static void number(Compiler* compiler)
} }
// Define a constant for the literal. // Define a constant for the literal.
int constant = addConstant(compiler, (Value)makeNum((double)value)); int constant = addConstant(compiler,
(Value)newNum(compiler->parser->vm, (double)value));
// Compile the code to load the constant. // Compile the code to load the constant.
emit(compiler, CODE_CONSTANT); emit(compiler, CODE_CONSTANT);
@ -735,15 +732,13 @@ static void string(Compiler* compiler)
// TODO(bob): Handle escaping. // TODO(bob): Handle escaping.
// Copy the string to the heap. // Ignore the surrounding "".
// Strip the surrounding "" off.
size_t length = token->end - token->start - 2; size_t length = token->end - token->start - 2;
char* text = malloc(length + 1); const char* start = compiler->parser->source + token->start + 1;
strncpy(text, compiler->parser->source + token->start + 1, length);
text[length] = '\0';
// Define a constant for the literal. // Define a constant for the literal.
int constant = addConstant(compiler, (Value)makeString(text)); int constant = addConstant(compiler,
(Value)newString(compiler->parser->vm, start, length));
// Compile the code to load the constant. // Compile the code to load the constant.
emit(compiler, CODE_CONSTANT); emit(compiler, CODE_CONSTANT);
@ -1012,6 +1007,10 @@ void method(Compiler* compiler, int isStatic)
Compiler methodCompiler; Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, 1); initCompiler(&methodCompiler, compiler->parser, compiler, 1);
// Add the block to the constant table. Do this eagerly so it's reachable by
// the GC.
int constant = addConstant(compiler, (Value)methodCompiler.fn);
// TODO(bob): Hackish. // TODO(bob): Hackish.
// Define a fake local slot for the receiver so that later locals have the // Define a fake local slot for the receiver so that later locals have the
// correct slot indices. // correct slot indices.
@ -1079,9 +1078,6 @@ void method(Compiler* compiler, int isStatic)
emit(&methodCompiler, CODE_END); emit(&methodCompiler, CODE_END);
// Add the block to the constant table.
int constant = addConstant(compiler, (Value)methodCompiler.fn);
if (isStatic) if (isStatic)
{ {
emit(compiler, CODE_METACLASS); emit(compiler, CODE_METACLASS);
@ -1180,6 +1176,8 @@ ObjFn* compile(VM* vm, const char* source)
Compiler compiler; Compiler compiler;
initCompiler(&compiler, &parser, NULL, 0); initCompiler(&compiler, &parser, NULL, 0);
pinObj(vm, (Value)compiler.fn);
for (;;) for (;;)
{ {
statement(&compiler); statement(&compiler);
@ -1199,5 +1197,7 @@ ObjFn* compile(VM* vm, const char* source)
emit(&compiler, CODE_END); emit(&compiler, CODE_END);
unpinObj(vm, (Value)compiler.fn);
return parser.hasError ? NULL : compiler.fn; return parser.hasError ? NULL : compiler.fn;
} }

View File

@ -20,20 +20,20 @@ DEF_PRIMITIVE(bool_eqeq)
{ {
if (args[1]->type != OBJ_FALSE && args[1]->type != OBJ_TRUE) if (args[1]->type != OBJ_FALSE && args[1]->type != OBJ_TRUE)
{ {
return makeBool(0); return newBool(vm, 0);
} }
return makeBool(AS_BOOL(args[0]) == AS_BOOL(args[1])); return newBool(vm, AS_BOOL(args[0]) == AS_BOOL(args[1]));
} }
DEF_PRIMITIVE(bool_bangeq) DEF_PRIMITIVE(bool_bangeq)
{ {
if (args[1]->type != OBJ_FALSE && args[1]->type != OBJ_TRUE) if (args[1]->type != OBJ_FALSE && args[1]->type != OBJ_TRUE)
{ {
return makeBool(1); return newBool(vm, 1);
} }
return makeBool(AS_BOOL(args[0]) != AS_BOOL(args[1])); return newBool(vm, AS_BOOL(args[0]) != AS_BOOL(args[1]));
} }
DEF_PRIMITIVE(bool_toString) DEF_PRIMITIVE(bool_toString)
@ -41,15 +41,11 @@ DEF_PRIMITIVE(bool_toString)
// TODO(bob): Intern these strings or something. // TODO(bob): Intern these strings or something.
if (AS_BOOL(args[0])) if (AS_BOOL(args[0]))
{ {
char* result = malloc(5); return (Value)newString(vm, "true", 4);
strcpy(result, "true");
return (Value)makeString(result);
} }
else else
{ {
char* result = malloc(6); return (Value)newString(vm, "false", 5);
strcpy(result, "false");
return (Value)makeString(result);
} }
} }
@ -65,19 +61,19 @@ DEF_PRIMITIVE(fn_call)
DEF_PRIMITIVE(fn_eqeq) DEF_PRIMITIVE(fn_eqeq)
{ {
if (args[1]->type != OBJ_FN) return makeBool(0); if (args[1]->type != OBJ_FN) return newBool(vm, 0);
return makeBool(AS_FN(args[0]) == AS_FN(args[1])); return newBool(vm, AS_FN(args[0]) == AS_FN(args[1]));
} }
DEF_PRIMITIVE(fn_bangeq) DEF_PRIMITIVE(fn_bangeq)
{ {
if (args[1]->type != OBJ_FN) return makeBool(1); if (args[1]->type != OBJ_FN) return newBool(vm, 1);
return makeBool(AS_FN(args[0]) != AS_FN(args[1])); return newBool(vm, AS_FN(args[0]) != AS_FN(args[1]));
} }
DEF_PRIMITIVE(num_abs) DEF_PRIMITIVE(num_abs)
{ {
return (Value)makeNum(fabs(AS_NUM(args[0]))); return (Value)newNum(vm, fabs(AS_NUM(args[0])));
} }
DEF_PRIMITIVE(num_toString) DEF_PRIMITIVE(num_toString)
@ -85,78 +81,74 @@ DEF_PRIMITIVE(num_toString)
// TODO(bob): What size should this be? // TODO(bob): What size should this be?
char temp[100]; char temp[100];
sprintf(temp, "%g", AS_NUM(args[0])); sprintf(temp, "%g", AS_NUM(args[0]));
return (Value)newString(vm, temp, strlen(temp));
size_t size = strlen(temp) + 1;
char* result = malloc(size);
strncpy(result, temp, size);
return (Value)makeString(result);
} }
DEF_PRIMITIVE(num_minus) DEF_PRIMITIVE(num_minus)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return (Value)makeNum(AS_NUM(args[0]) - AS_NUM(args[1])); return (Value)newNum(vm, AS_NUM(args[0]) - AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_plus) DEF_PRIMITIVE(num_plus)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
// TODO(bob): Handle coercion to string if RHS is a string. // TODO(bob): Handle coercion to string if RHS is a string.
return (Value)makeNum(AS_NUM(args[0]) + AS_NUM(args[1])); return (Value)newNum(vm, AS_NUM(args[0]) + AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_multiply) DEF_PRIMITIVE(num_multiply)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return (Value)makeNum(AS_NUM(args[0]) * AS_NUM(args[1])); return (Value)newNum(vm, AS_NUM(args[0]) * AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_divide) DEF_PRIMITIVE(num_divide)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return (Value)makeNum(AS_NUM(args[0]) / AS_NUM(args[1])); return (Value)newNum(vm, AS_NUM(args[0]) / AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_mod) DEF_PRIMITIVE(num_mod)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return (Value)makeNum(fmod(AS_NUM(args[0]), AS_NUM(args[1]))); return (Value)newNum(vm, fmod(AS_NUM(args[0]), AS_NUM(args[1])));
} }
DEF_PRIMITIVE(num_lt) DEF_PRIMITIVE(num_lt)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return makeBool(AS_NUM(args[0]) < AS_NUM(args[1])); return newBool(vm, AS_NUM(args[0]) < AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_gt) DEF_PRIMITIVE(num_gt)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return makeBool(AS_NUM(args[0]) > AS_NUM(args[1])); return newBool(vm, AS_NUM(args[0]) > AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_lte) DEF_PRIMITIVE(num_lte)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return makeBool(AS_NUM(args[0]) <= AS_NUM(args[1])); return newBool(vm, AS_NUM(args[0]) <= AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_gte) DEF_PRIMITIVE(num_gte)
{ {
if (args[1]->type != OBJ_NUM) return vm->unsupported; if (args[1]->type != OBJ_NUM) return vm->unsupported;
return makeBool(AS_NUM(args[0]) >= AS_NUM(args[1])); return newBool(vm, AS_NUM(args[0]) >= AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_eqeq) DEF_PRIMITIVE(num_eqeq)
{ {
if (args[1]->type != OBJ_NUM) return makeBool(0); if (args[1]->type != OBJ_NUM) return newBool(vm, 0);
return makeBool(AS_NUM(args[0]) == AS_NUM(args[1])); return newBool(vm, AS_NUM(args[0]) == AS_NUM(args[1]));
} }
DEF_PRIMITIVE(num_bangeq) DEF_PRIMITIVE(num_bangeq)
{ {
if (args[1]->type != OBJ_NUM) return makeBool(1); if (args[1]->type != OBJ_NUM) return newBool(vm, 1);
return makeBool(AS_NUM(args[0]) != AS_NUM(args[1])); return newBool(vm, AS_NUM(args[0]) != AS_NUM(args[1]));
} }
DEF_PRIMITIVE(string_contains) DEF_PRIMITIVE(string_contains)
@ -166,16 +158,16 @@ DEF_PRIMITIVE(string_contains)
const char* search = AS_STRING(args[1]); const char* search = AS_STRING(args[1]);
// Corner case, the empty string contains the empty string. // Corner case, the empty string contains the empty string.
if (strlen(string) == 0 && strlen(search) == 0) return (Value)makeNum(1); if (strlen(string) == 0 && strlen(search) == 0) return (Value)newNum(vm, 1);
// TODO(bob): Return bool. // TODO(bob): Return bool.
return (Value)makeNum(strstr(string, search) != NULL); return (Value)newNum(vm, strstr(string, search) != NULL);
} }
DEF_PRIMITIVE(string_count) DEF_PRIMITIVE(string_count)
{ {
double count = strlen(AS_STRING(args[0])); double count = strlen(AS_STRING(args[0]));
return (Value)makeNum(count); return (Value)newNum(vm, count);
} }
DEF_PRIMITIVE(string_toString) DEF_PRIMITIVE(string_toString)
@ -194,27 +186,28 @@ DEF_PRIMITIVE(string_plus)
size_t leftLength = strlen(left); size_t leftLength = strlen(left);
size_t rightLength = strlen(right); size_t rightLength = strlen(right);
char* result = malloc(leftLength + rightLength + 1); ObjString* string = newString(vm, NULL, leftLength + rightLength);
strcpy(result, left); strcpy(string->value, left);
strcpy(result + leftLength, right); strcpy(string->value + leftLength, right);
string->value[leftLength + rightLength] = '\0';
return (Value)makeString(result); return (Value)string;
} }
DEF_PRIMITIVE(string_eqeq) DEF_PRIMITIVE(string_eqeq)
{ {
if (args[1]->type != OBJ_STRING) return makeBool(0); if (args[1]->type != OBJ_STRING) return newBool(vm, 0);
const char* a = AS_STRING(args[0]); const char* a = AS_STRING(args[0]);
const char* b = AS_STRING(args[1]); const char* b = AS_STRING(args[1]);
return makeBool(strcmp(a, b) == 0); return newBool(vm, strcmp(a, b) == 0);
} }
DEF_PRIMITIVE(string_bangeq) DEF_PRIMITIVE(string_bangeq)
{ {
if (args[1]->type != OBJ_STRING) return makeBool(1); if (args[1]->type != OBJ_STRING) return newBool(vm, 1);
const char* a = AS_STRING(args[0]); const char* a = AS_STRING(args[0]);
const char* b = AS_STRING(args[1]); const char* b = AS_STRING(args[1]);
return makeBool(strcmp(a, b) != 0); return newBool(vm, strcmp(a, b) != 0);
} }
DEF_PRIMITIVE(io_write) DEF_PRIMITIVE(io_write)
@ -279,8 +272,8 @@ void loadCore(VM* vm)
ObjClass* ioClass = AS_CLASS(findGlobal(vm, "IO")); ObjClass* ioClass = AS_CLASS(findGlobal(vm, "IO"));
PRIMITIVE(ioClass, "write ", io_write); PRIMITIVE(ioClass, "write ", io_write);
ObjClass* unsupportedClass = makeClass(); ObjClass* unsupportedClass = newClass(vm);
// TODO(bob): Make this a distinct object type. // TODO(bob): Make this a distinct object type.
vm->unsupported = (Value)makeInstance(unsupportedClass); vm->unsupported = (Value)newInstance(vm, unsupportedClass);
} }

388
src/vm.c
View File

@ -2,6 +2,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "common.h"
#include "primitives.h" #include "primitives.h"
#include "vm.h" #include "vm.h"
@ -9,10 +10,34 @@ static Value primitive_metaclass_new(VM* vm, Fiber* fiber, Value* args);
VM* newVM() VM* newVM()
{ {
// TODO(bob): Get rid of explicit malloc() here.
VM* vm = malloc(sizeof(VM)); VM* vm = malloc(sizeof(VM));
initSymbolTable(&vm->methods); initSymbolTable(&vm->methods);
initSymbolTable(&vm->globalSymbols); initSymbolTable(&vm->globalSymbols);
// TODO(bob): Get rid of explicit malloc() here.
vm->fiber = malloc(sizeof(Fiber));
vm->fiber->stackSize = 0;
vm->fiber->numFrames = 0;
vm->totalAllocated = 0;
// TODO(bob): Make this configurable.
vm->nextGC = 1024 * 1024;
// Clear out the global variables. This ensures they are NULL before being
// initialized in case we do a garbage collection before one gets initialized.
for (int i = 0; i < MAX_SYMBOLS; i++)
{
vm->globals[i] = NULL;
}
// Clear out the pinned list. We look for NULL empty slots explicitly.
vm->numPinned = 0;
for (int j = 0; j < MAX_PINNED; j++)
{
vm->pinned[j] = NULL;
}
loadCore(vm); loadCore(vm);
return vm; return vm;
@ -25,23 +50,226 @@ void freeVM(VM* vm)
free(vm); free(vm);
} }
void initObj(Obj* obj, ObjType type) void markObj(Value value)
{
Obj* obj = (Obj*)value;
// Don't recurse if already marked. Avoids getting stuck in a loop on cycles.
if (obj->flags & FLAG_MARKED) return;
obj->flags |= FLAG_MARKED;
#ifdef TRACE_MEMORY
static int indent = 0;
indent++;
for (int i = 0; i < indent; i++) printf(" ");
printf("mark ");
printValue((Value)obj);
printf("\n");
#endif
// Traverse the object's fields.
switch (obj->type)
{
case OBJ_CLASS:
{
ObjClass* classObj = AS_CLASS(obj);
// The metaclass.
if (classObj->metaclass != NULL) markObj((Value)classObj->metaclass);
// Method function objects.
for (int i = 0; i < MAX_SYMBOLS; i++)
{
if (classObj->methods[i].type == METHOD_BLOCK)
{
markObj((Value)classObj->methods[i].fn);
}
}
break;
}
case OBJ_FN:
{
// Mark the constants.
ObjFn* fn = AS_FN(obj);
for (int i = 0; i < fn->numConstants; i++)
{
markObj(fn->constants[i]);
}
break;
}
case OBJ_INSTANCE:
// TODO(bob): Mark fields when instances have them.
break;
case OBJ_FALSE:
case OBJ_NULL:
case OBJ_NUM:
case OBJ_STRING:
case OBJ_TRUE:
// Nothing to mark.
break;
}
#ifdef TRACE_MEMORY
indent--;
#endif
}
void freeObj(VM* vm, Obj* obj)
{
#ifdef TRACE_MEMORY
printf("free ");
printValue((Value)obj);
#endif
// Free any additional heap data allocated by the object.
size_t size;
switch (obj->type)
{
case OBJ_FN:
{
// TODO(bob): Don't hardcode array sizes.
size = sizeof(ObjFn) + sizeof(Code) * 1024 + sizeof(Value) * 256;
ObjFn* fn = AS_FN(obj);
free(fn->bytecode);
free(fn->constants);
break;
}
case OBJ_STRING:
// TODO(bob): O(n) calculation here is lame!
size = sizeof(ObjString) + strlen(AS_STRING(obj));
free(AS_STRING(obj));
break;
case OBJ_CLASS:
size = sizeof(ObjClass);
break;
case OBJ_FALSE:
case OBJ_INSTANCE:
case OBJ_NULL:
case OBJ_NUM:
case OBJ_TRUE:
// Nothing to delete.
size = sizeof(Obj);
// TODO(bob): Include size of fields for OBJ_INSTANCE.
break;
}
vm->totalAllocated -= size;
#ifdef TRACE_MEMORY
printf(" (%ld bytes)\n", sizeof(Obj));
#endif
free(obj);
}
void collectGarbage(VM* vm)
{
// Mark all reachable objects.
#ifdef TRACE_MEMORY
printf("-- gc --\n");
#endif
// Global variables.
for (int i = 0; i < vm->globalSymbols.count; i++)
{
// Check for NULL to handle globals that have been defined (at compile time)
// but not yet initialized.
if (vm->globals[i] != NULL) markObj(vm->globals[i]);
}
// Pinned objects.
for (int j = 0; j < vm->numPinned; j++)
{
if (vm->pinned[j] != NULL) markObj(vm->pinned[j]);
}
// Stack functions.
for (int k = 0; k < vm->fiber->numFrames; k++)
{
markObj((Value)vm->fiber->frames[k].fn);
}
// Stack variables.
for (int l = 0; l < vm->fiber->stackSize; l++)
{
markObj(vm->fiber->stack[l]);
}
// Collect any unmarked objects.
Obj** obj = &vm->first;
while (*obj != NULL)
{
if (!((*obj)->flags & FLAG_MARKED))
{
// This object wasn't reached, so remove it from the list and free it.
Obj* unreached = *obj;
*obj = unreached->next;
freeObj(vm, unreached);
}
else
{
// This object was reached, so unmark it (for the next GC) and move on to
// the next.
(*obj)->flags &= ~FLAG_MARKED;
obj = &(*obj)->next;
}
}
}
void* allocate(VM* vm, size_t size)
{
vm->totalAllocated += size;
#ifdef DEBUG_GC_STRESS
collectGarbage(vm);
#else
if (vm->totalAllocated > vm->nextGC)
{
#ifdef TRACE_MEMORY
size_t before = vm->totalAllocated;
#endif
collectGarbage(vm);
vm->nextGC = vm->totalAllocated * 3 / 2;
#ifdef TRACE_MEMORY
printf(
"GC %ld before, %ld after (%ld collected), next at %ld\n",
before, vm->totalAllocated, before - vm->totalAllocated, vm->nextGC);
#endif
}
#endif
// TODO(bob): Let external code provide allocator.
return malloc(size);
}
void initObj(VM* vm, Obj* obj, ObjType type)
{ {
obj->type = type; obj->type = type;
obj->flags = 0; obj->flags = 0;
obj->next = vm->first;
vm->first = obj;
} }
Value makeBool(int value) Value newBool(VM* vm, int value)
{ {
Obj* obj = malloc(sizeof(Obj)); Obj* obj = allocate(vm, sizeof(Obj));
initObj(obj, value ? OBJ_TRUE : OBJ_FALSE); initObj(vm, obj, value ? OBJ_TRUE : OBJ_FALSE);
return obj; return obj;
} }
ObjClass* makeSingleClass() static ObjClass* newSingleClass(VM* vm)
{ {
ObjClass* obj = malloc(sizeof(ObjClass)); ObjClass* obj = allocate(vm, sizeof(ObjClass));
initObj(&obj->obj, OBJ_CLASS); initObj(vm, &obj->obj, OBJ_CLASS);
obj->metaclass = NULL;
for (int i = 0; i < MAX_SYMBOLS; i++) for (int i = 0; i < MAX_SYMBOLS; i++)
{ {
@ -51,53 +279,80 @@ ObjClass* makeSingleClass()
return obj; return obj;
} }
ObjClass* makeClass() ObjClass* newClass(VM* vm)
{ {
ObjClass* classObj = makeSingleClass(); ObjClass* classObj = newSingleClass(vm);
// Make sure this isn't collected when we allocate the metaclass.
pinObj(vm, (Value)classObj);
// Make its metaclass. // Make its metaclass.
// TODO(bob): What is the metaclass's metaclass? // TODO(bob): What is the metaclass's metaclass?
classObj->metaclass = makeSingleClass(); classObj->metaclass = newSingleClass(vm);
unpinObj(vm, (Value)classObj);
return classObj; return classObj;
} }
ObjFn* makeFunction() ObjFn* newFunction(VM* vm)
{ {
ObjFn* fn = malloc(sizeof(ObjFn)); // Allocate these before the function in case they trigger a GC which would
initObj(&fn->obj, OBJ_FN); // free the function.
// TODO(bob): Hack! make variable sized.
unsigned char* bytecode = allocate(vm, sizeof(Code) * 1024);
Value* constants = allocate(vm, sizeof(Value) * 256);
ObjFn* fn = allocate(vm, sizeof(ObjFn));
initObj(vm, &fn->obj, OBJ_FN);
fn->bytecode = bytecode;
fn->constants = constants;
return fn; return fn;
} }
ObjInstance* makeInstance(ObjClass* classObj) ObjInstance* newInstance(VM* vm, ObjClass* classObj)
{ {
ObjInstance* instance = malloc(sizeof(ObjInstance)); ObjInstance* instance = allocate(vm, sizeof(ObjInstance));
initObj(&instance->obj, OBJ_INSTANCE); initObj(vm, &instance->obj, OBJ_INSTANCE);
instance->classObj = classObj; instance->classObj = classObj;
return instance; return instance;
} }
Value makeNull() Value newNull(VM* vm)
{ {
Obj* obj = malloc(sizeof(Obj)); Obj* obj = allocate(vm, sizeof(Obj));
initObj(obj, OBJ_NULL); initObj(vm, obj, OBJ_NULL);
return obj; return obj;
} }
ObjNum* makeNum(double number) ObjNum* newNum(VM* vm, double number)
{ {
ObjNum* num = malloc(sizeof(ObjNum)); ObjNum* num = allocate(vm, sizeof(ObjNum));
initObj(&num->obj, OBJ_NUM); initObj(vm, &num->obj, OBJ_NUM);
num->value = number; num->value = number;
return num; return num;
} }
ObjString* makeString(const char* text) ObjString* newString(VM* vm, const char* text, size_t length)
{ {
ObjString* string = malloc(sizeof(ObjString)); // Allocate before the string object in case this triggers a GC which would
initObj(&string->obj, OBJ_STRING); // free the string object.
string->value = text; char* heapText = allocate(vm, length + 1);
ObjString* string = allocate(vm, sizeof(ObjString));
initObj(vm, &string->obj, OBJ_STRING);
// Copy the string (if given one).
if (text != NULL)
{
strncpy(heapText, text, length);
heapText[length] = '\0';
}
string->value = heapText;
return string; return string;
} }
@ -116,6 +371,7 @@ void clearSymbolTable(SymbolTable* symbols)
int addSymbolUnchecked(SymbolTable* symbols, const char* name, size_t length) int addSymbolUnchecked(SymbolTable* symbols, const char* name, size_t length)
{ {
// TODO(bob): Get rid of explicit malloc here.
symbols->names[symbols->count] = malloc(length + 1); symbols->names[symbols->count] = malloc(length + 1);
strncpy(symbols->names[symbols->count], name, length); strncpy(symbols->names[symbols->count], name, length);
symbols->names[symbols->count][length] = '\0'; symbols->names[symbols->count][length] = '\0';
@ -166,8 +422,8 @@ Value findGlobal(VM* vm, const char* name)
return vm->globals[symbol]; return vm->globals[symbol];
} }
// TODO(bob): For debugging. Move to separate module.
/* /*
// TODO(bob): For debugging. Move to separate module.
void dumpCode(VM* vm, ObjFn* fn) void dumpCode(VM* vm, ObjFn* fn)
{ {
unsigned char* bytecode = fn->bytecode; unsigned char* bytecode = fn->bytecode;
@ -336,24 +592,29 @@ static ObjClass* getClass(VM* vm, Value object)
Value interpret(VM* vm, ObjFn* fn) Value interpret(VM* vm, ObjFn* fn)
{ {
// TODO(bob): Allocate fiber on heap. Fiber* fiber = vm->fiber;
Fiber fiber;
fiber.stackSize = 0;
fiber.numFrames = 0;
callFunction(&fiber, fn, 0); callFunction(fiber, fn, 0);
// These macros are designed to only be invoked within this function. // These macros are designed to only be invoked within this function.
// TODO(bob): Check for stack overflow. // TODO(bob): Check for stack overflow.
#define PUSH(value) (fiber.stack[fiber.stackSize++] = value) #define PUSH(value) (fiber->stack[fiber->stackSize++] = value)
#define POP() (fiber.stack[--fiber.stackSize]) #define POP() (fiber->stack[--fiber->stackSize])
#define PEEK() (fiber.stack[fiber.stackSize - 1]) #define PEEK() (fiber->stack[fiber->stackSize - 1])
#define READ_ARG() (frame->fn->bytecode[frame->ip++]) #define READ_ARG() (frame->fn->bytecode[frame->ip++])
Code lastOp;
for (;;) for (;;)
{ {
CallFrame* frame = &fiber.frames[fiber.numFrames - 1]; CallFrame* frame = &fiber->frames[fiber->numFrames - 1];
if (fiber->stackSize > 0 && PEEK() == NULL)
{
lastOp = frame->ip - 1;
printf("%d\n", lastOp);
}
switch (frame->fn->bytecode[frame->ip++]) switch (frame->fn->bytecode[frame->ip++])
{ {
@ -361,13 +622,13 @@ Value interpret(VM* vm, ObjFn* fn)
PUSH(frame->fn->constants[READ_ARG()]); PUSH(frame->fn->constants[READ_ARG()]);
break; break;
case CODE_NULL: PUSH(makeNull()); break; case CODE_NULL: PUSH(newNull(vm)); break;
case CODE_FALSE: PUSH(makeBool(0)); break; case CODE_FALSE: PUSH(newBool(vm, 0)); break;
case CODE_TRUE: PUSH(makeBool(1)); break; case CODE_TRUE: PUSH(newBool(vm, 1)); break;
case CODE_CLASS: case CODE_CLASS:
{ {
ObjClass* classObj = makeClass(); ObjClass* classObj = newClass(vm);
// Define a "new" method on the metaclass. // Define a "new" method on the metaclass.
// TODO(bob): Can this be inherited? // TODO(bob): Can this be inherited?
@ -402,14 +663,14 @@ Value interpret(VM* vm, ObjFn* fn)
case CODE_LOAD_LOCAL: case CODE_LOAD_LOCAL:
{ {
int local = READ_ARG(); int local = READ_ARG();
PUSH(fiber.stack[frame->stackStart + local]); PUSH(fiber->stack[frame->stackStart + local]);
break; break;
} }
case CODE_STORE_LOCAL: case CODE_STORE_LOCAL:
{ {
int local = READ_ARG(); int local = READ_ARG();
fiber.stack[frame->stackStart + local] = PEEK(); fiber->stack[frame->stackStart + local] = PEEK();
break; break;
} }
@ -446,7 +707,7 @@ Value interpret(VM* vm, ObjFn* fn)
int numArgs = frame->fn->bytecode[frame->ip - 1] - CODE_CALL_0 + 1; int numArgs = frame->fn->bytecode[frame->ip - 1] - CODE_CALL_0 + 1;
int symbol = READ_ARG(); int symbol = READ_ARG();
Value receiver = fiber.stack[fiber.stackSize - numArgs]; Value receiver = fiber->stack[fiber->stackSize - numArgs];
ObjClass* classObj = getClass(vm, receiver); ObjClass* classObj = getClass(vm, receiver);
Method* method = &classObj->methods[symbol]; Method* method = &classObj->methods[symbol];
@ -463,23 +724,23 @@ Value interpret(VM* vm, ObjFn* fn)
case METHOD_PRIMITIVE: case METHOD_PRIMITIVE:
{ {
Value* args = &fiber.stack[fiber.stackSize - numArgs]; Value* args = &fiber->stack[fiber->stackSize - numArgs];
Value result = method->primitive(vm, &fiber, args); Value result = method->primitive(vm, fiber, args);
// If the primitive pushed a call frame, it returns NULL. // If the primitive pushed a call frame, it returns NULL.
if (result != NULL) if (result != NULL)
{ {
fiber.stack[fiber.stackSize - numArgs] = result; fiber->stack[fiber->stackSize - numArgs] = result;
// Discard the stack slots for the arguments (but leave one for // Discard the stack slots for the arguments (but leave one for
// the result). // the result).
fiber.stackSize -= numArgs - 1; fiber->stackSize -= numArgs - 1;
} }
break; break;
} }
case METHOD_BLOCK: case METHOD_BLOCK:
callFunction(&fiber, method->fn, numArgs); callFunction(fiber, method->fn, numArgs);
break; break;
} }
break; break;
@ -507,25 +768,25 @@ Value interpret(VM* vm, ObjFn* fn)
// TODO(bob): What if classObj is not a class? // TODO(bob): What if classObj is not a class?
ObjClass* actual = getClass(vm, obj); ObjClass* actual = getClass(vm, obj);
PUSH(makeBool(actual == AS_CLASS(classObj))); PUSH(newBool(vm, actual == AS_CLASS(classObj)));
break; break;
} }
case CODE_END: case CODE_END:
{ {
Value result = POP(); Value result = POP();
fiber.numFrames--; fiber->numFrames--;
// If we are returning from the top-level block, just return the value. // If we are returning from the top-level block, just return the value.
if (fiber.numFrames == 0) return result; if (fiber->numFrames == 0) return result;
// Store the result of the block in the first slot, which is where the // Store the result of the block in the first slot, which is where the
// caller expects it. // caller expects it.
fiber.stack[frame->stackStart] = result; fiber->stack[frame->stackStart] = result;
// Discard the stack slots for the call frame (leaving one slot for the // Discard the stack slots for the call frame (leaving one slot for the
// result). // result).
fiber.stackSize = frame->stackStart + 1; fiber->stackSize = frame->stackStart + 1;
break; break;
} }
} }
@ -548,7 +809,7 @@ void printValue(Value value)
switch (value->type) switch (value->type)
{ {
case OBJ_CLASS: case OBJ_CLASS:
printf("[class]"); printf("[class %p]", value);
break; break;
case OBJ_FALSE: case OBJ_FALSE:
@ -556,11 +817,11 @@ void printValue(Value value)
break; break;
case OBJ_FN: case OBJ_FN:
printf("[fn]"); printf("[fn %p]", value);
break; break;
case OBJ_INSTANCE: case OBJ_INSTANCE:
printf("[instance]"); printf("[instance %p]", value);
break; break;
case OBJ_NULL: case OBJ_NULL:
@ -581,8 +842,21 @@ void printValue(Value value)
} }
} }
void pinObj(VM* vm, Value value)
{
ASSERT(vm->numPinned < MAX_PINNED - 1, "Too many pinned objects.");
vm->pinned[vm->numPinned++] = value;
}
void unpinObj(VM* vm, Value value)
{
ASSERT(vm->pinned[vm->numPinned - 1] == value,
"Unpinning object out of stack order.");
vm->numPinned--;
}
Value primitive_metaclass_new(VM* vm, Fiber* fiber, Value* args) Value primitive_metaclass_new(VM* vm, Fiber* fiber, Value* args)
{ {
// TODO(bob): Invoke initializer method. // TODO(bob): Invoke initializer method.
return (Value)makeInstance(AS_CLASS(args[0])); return (Value)newInstance(vm, AS_CLASS(args[0]));
} }

View File

@ -5,6 +5,7 @@
#define STACK_SIZE 1024 #define STACK_SIZE 1024
#define MAX_CALL_FRAMES 256 #define MAX_CALL_FRAMES 256
#define MAX_SYMBOLS 256 #define MAX_SYMBOLS 256
#define MAX_PINNED 16
// Get the class value of [obj] (0 or 1), which must be a boolean. // Get the class value of [obj] (0 or 1), which must be a boolean.
#define AS_CLASS(obj) ((ObjClass*)obj) #define AS_CLASS(obj) ((ObjClass*)obj)
@ -41,10 +42,13 @@ typedef enum
FLAG_MARKED = 0x01, FLAG_MARKED = 0x01,
} ObjFlags; } ObjFlags;
typedef struct typedef struct sObj
{ {
ObjType type; ObjType type;
ObjFlags flags; ObjFlags flags;
// The next object in the linked list of all currently allocated objects.
struct sObj* next;
} Obj; } Obj;
typedef Obj* Value; typedef Obj* Value;
@ -103,7 +107,7 @@ typedef struct
typedef struct typedef struct
{ {
Obj obj; Obj obj;
const char* value; char* value;
} ObjString; } ObjString;
typedef enum typedef enum
@ -200,6 +204,28 @@ struct sVM
SymbolTable globalSymbols; SymbolTable globalSymbols;
// TODO(bob): Using a fixed array is gross here. // TODO(bob): Using a fixed array is gross here.
Value globals[MAX_SYMBOLS]; Value globals[MAX_SYMBOLS];
// TODO(bob): Support more than one fiber.
Fiber* fiber;
// Memory management data:
// How many bytes of object data have been allocated so far.
size_t totalAllocated;
// The number of total allocated bytes that will trigger the next GC.
size_t nextGC;
// The first object in the linked list of all currently allocated objects.
Obj* first;
// How many pinned objects are in the stack.
int numPinned;
// The stack "pinned" objects. These are temporary explicit GC roots so that
// the collector can find them before they are reachable through a normal
// root.
Value pinned[MAX_PINNED];
}; };
typedef struct typedef struct
@ -231,26 +257,26 @@ void freeVM(VM* vm);
// Gets a bool object representing [value]. // Gets a bool object representing [value].
// TODO(bob): Inline or macro? // TODO(bob): Inline or macro?
Value makeBool(int value); Value newBool(VM* vm, int value);
// Creates a new function object. Assumes the compiler will fill it in with // Creates a new function object. Assumes the compiler will fill it in with
// bytecode, constants, etc. // bytecode, constants, etc.
ObjFn* makeFunction(); ObjFn* newFunction(VM* vm);
// Creates a new class object. // Creates a new class object.
ObjClass* makeClass(); ObjClass* newClass(VM* vm);
// Creates a new instance of the given [classObj]. // Creates a new instance of the given [classObj].
ObjInstance* makeInstance(ObjClass* classObj); ObjInstance* newInstance(VM* vm, ObjClass* classObj);
// Creates a new null object. // Creates a new null object.
Value makeNull(); Value newNull(VM* vm);
// Creates a new number object. // Creates a new number object.
ObjNum* makeNum(double number); ObjNum* newNum(VM* vm, double number);
// Creates a new string object. Does not copy text. // Creates a new string object and copies [text] into it.
ObjString* makeString(const char* text); ObjString* newString(VM* vm, const char* text, size_t length);
// Initializes the symbol table. // Initializes the symbol table.
void initSymbolTable(SymbolTable* symbols); void initSymbolTable(SymbolTable* symbols);
@ -284,4 +310,10 @@ void callFunction(Fiber* fiber, ObjFn* fn, int numArgs);
void printValue(Value value); void printValue(Value value);
// Mark [value] as a GC root so that it doesn't get collected.
void pinObj(VM* vm, Value value);
// Unmark [value] as a GC root so that it doesn't get collected.
void unpinObj(VM* vm, Value value);
#endif #endif

View File

@ -26,6 +26,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
296681E2183283A500C1407C /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = common.h; path = src/common.h; sourceTree = SOURCE_ROOT; };
29AB1F061816E3AD004B501E /* wren */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = wren; sourceTree = BUILT_PRODUCTS_DIR; }; 29AB1F061816E3AD004B501E /* wren */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = wren; sourceTree = BUILT_PRODUCTS_DIR; };
29AB1F201816E49C004B501E /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = src/main.c; sourceTree = SOURCE_ROOT; }; 29AB1F201816E49C004B501E /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = src/main.c; sourceTree = SOURCE_ROOT; };
29AB1F271816E49C004B501E /* wren.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = wren.1; path = src/wren.1; sourceTree = SOURCE_ROOT; }; 29AB1F271816E49C004B501E /* wren.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = wren.1; path = src/wren.1; sourceTree = SOURCE_ROOT; };
@ -67,6 +68,7 @@
29AB1F081816E3AD004B501E /* src */ = { 29AB1F081816E3AD004B501E /* src */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
296681E2183283A500C1407C /* common.h */,
29AB1F3018170104004B501E /* compiler.c */, 29AB1F3018170104004B501E /* compiler.c */,
29AB1F3118170104004B501E /* compiler.h */, 29AB1F3118170104004B501E /* compiler.h */,
29FA7297181D91020089013C /* primitives.c */, 29FA7297181D91020089013C /* primitives.c */,