From 034fec6eaa002565607ecbf6321cbcbc1cd75b73 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 22 Dec 2013 14:31:33 -0800 Subject: [PATCH] Make fibers real objects. --- src/wren_core.c | 30 +++++++------- src/wren_debug.c | 2 +- src/wren_debug.h | 2 +- src/wren_value.c | 27 +++++++++++-- src/wren_value.h | 77 +++++++++++++++++++++++++---------- src/wren_vm.c | 102 ++++++++++++++++++++++++++--------------------- src/wren_vm.h | 46 +-------------------- 7 files changed, 156 insertions(+), 130 deletions(-) diff --git a/src/wren_core.c b/src/wren_core.c index b31f4285..e23b182e 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -35,7 +35,7 @@ // Defines a fiber native method whose C function name is [native]. #define DEF_FIBER_NATIVE(native) \ - static void native_##native(WrenVM* vm, Fiber* fiber, Value* args) + static void native_##native(WrenVM* vm, ObjFiber* fiber, Value* args) // TODO: Tune these. // The initial (and minimum) capacity of a non-empty list object. @@ -163,6 +163,7 @@ DEF_NATIVE(list_clear) { ObjList* list = AS_LIST(args[0]); wrenReallocate(vm, list->elements, 0, 0); + list->elements = NULL; list->capacity = 0; list->count = 0; return NULL_VAL; @@ -276,56 +277,57 @@ DEF_NATIVE(num_negate) DEF_NATIVE(num_minus) { - if (!IS_NUM(args[1])) return vm->unsupported; + // TODO: Handle unsupported operand types better. + if (!IS_NUM(args[1])) return NULL_VAL; return NUM_VAL(AS_NUM(args[0]) - AS_NUM(args[1])); } DEF_NATIVE(num_plus) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; // TODO: Handle coercion to string if RHS is a string. return NUM_VAL(AS_NUM(args[0]) + AS_NUM(args[1])); } DEF_NATIVE(num_multiply) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return NUM_VAL(AS_NUM(args[0]) * AS_NUM(args[1])); } DEF_NATIVE(num_divide) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return NUM_VAL(AS_NUM(args[0]) / AS_NUM(args[1])); } DEF_NATIVE(num_mod) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return NUM_VAL(fmod(AS_NUM(args[0]), AS_NUM(args[1]))); } DEF_NATIVE(num_lt) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return BOOL_VAL(AS_NUM(args[0]) < AS_NUM(args[1])); } DEF_NATIVE(num_gt) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return BOOL_VAL(AS_NUM(args[0]) > AS_NUM(args[1])); } DEF_NATIVE(num_lte) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return BOOL_VAL(AS_NUM(args[0]) <= AS_NUM(args[1])); } DEF_NATIVE(num_gte) { - if (!IS_NUM(args[1])) return vm->unsupported; + if (!IS_NUM(args[1])) return NULL_VAL; return BOOL_VAL(AS_NUM(args[0]) >= AS_NUM(args[1])); } @@ -400,7 +402,7 @@ DEF_NATIVE(string_toString) DEF_NATIVE(string_plus) { - if (!IS_STRING(args[1])) return vm->unsupported; + if (!IS_STRING(args[1])) return NULL_VAL; // TODO: Handle coercion to string of RHS. const char* left = AS_CSTRING(args[0]); @@ -536,6 +538,8 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->boolClass, "toString", bool_toString); NATIVE(vm->boolClass, "!", bool_not); + vm->fiberClass = defineClass(vm, "Fiber"); + vm->fnClass = defineClass(vm, "Function"); FIBER_NATIVE(vm->fnClass, "call", fn_call0); FIBER_NATIVE(vm->fnClass, "call ", fn_call1); @@ -590,10 +594,6 @@ void wrenInitializeCore(WrenVM* vm) ObjClass* osClass = defineClass(vm, "OS"); NATIVE(osClass->metaclass, "clock", os_clock); - // TODO: Make this a distinct object type. - ObjClass* unsupportedClass = wrenNewClass(vm, vm->objectClass, 0); - vm->unsupported = (Value)wrenNewInstance(vm, unsupportedClass); - wrenInterpret(vm, coreLibSource); vm->listClass = AS_CLASS(findGlobal(vm, "List")); diff --git a/src/wren_debug.c b/src/wren_debug.c index 6f7e3925..f6a2adb5 100644 --- a/src/wren_debug.c +++ b/src/wren_debug.c @@ -299,7 +299,7 @@ void wrenDebugDumpCode(WrenVM* vm, ObjFn* fn) } } -void wrenDebugDumpStack(Fiber* fiber) +void wrenDebugDumpStack(ObjFiber* fiber) { printf(":: "); for (int i = 0; i < fiber->stackSize; i++) diff --git a/src/wren_debug.h b/src/wren_debug.h index f1bec87f..114d33ad 100644 --- a/src/wren_debug.h +++ b/src/wren_debug.h @@ -14,6 +14,6 @@ int wrenDebugDumpInstruction(WrenVM* vm, ObjFn* fn, int i); void wrenDebugDumpCode(WrenVM* vm, ObjFn* fn); -void wrenDebugDumpStack(Fiber* fiber); +void wrenDebugDumpStack(ObjFiber* fiber); #endif diff --git a/src/wren_value.c b/src/wren_value.c index 114e6d2a..1d7d1faa 100644 --- a/src/wren_value.c +++ b/src/wren_value.c @@ -57,6 +57,10 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields) ObjClass* metaclass = wrenNewSingleClass(vm, 0); metaclass->metaclass = vm->classClass; + // Make sure the metaclass isn't collected when we allocate the class. + PinnedObj pinned; + pinObj(vm, (Obj*)metaclass, &pinned); + // The metaclass inheritance chain mirrors the class's inheritance chain // except that when the latter bottoms out at "Object", the metaclass one // bottoms out at "Class". @@ -69,10 +73,6 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields) wrenBindSuperclass(metaclass, superclass->metaclass); } - // Make sure it isn't collected when we allocate the metaclass. - PinnedObj pinned; - pinObj(vm, (Obj*)metaclass, &pinned); - ObjClass* classObj = wrenNewSingleClass(vm, numFields); classObj->metaclass = metaclass; wrenBindSuperclass(classObj, superclass); @@ -97,6 +97,18 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn) return closure; } +ObjFiber* wrenNewFiber(WrenVM* vm) +{ + ObjFiber* fiber = allocate(vm, sizeof(ObjFiber)); + initObj(vm, &fiber->obj, OBJ_FIBER); + + fiber->stackSize = 0; + fiber->numFrames = 0; + fiber->openUpvalues = NULL; + + return fiber; +} + ObjFn* wrenNewFunction(WrenVM* vm) { // Allocate these before the function in case they trigger a GC which would @@ -190,6 +202,7 @@ static ObjClass* getObjectClass(WrenVM* vm, Obj* obj) return classObj->metaclass; } case OBJ_CLOSURE: return vm->fnClass; + case OBJ_FIBER: return vm->fiberClass; case OBJ_FN: return vm->fnClass; case OBJ_INSTANCE: return ((ObjInstance*)obj)->classObj; case OBJ_LIST: return vm->listClass; @@ -260,6 +273,7 @@ static void printObject(Obj* obj) { case OBJ_CLASS: printf("[class %p]", obj); break; case OBJ_CLOSURE: printf("[closure %p]", obj); break; + case OBJ_FIBER: printf("[fiber %p]", obj); break; case OBJ_FN: printf("[fn %p]", obj); break; case OBJ_INSTANCE: printf("[instance %p]", obj); break; case OBJ_LIST: printList((ObjList*)obj); break; @@ -320,6 +334,11 @@ bool wrenIsClosure(Value value) return IS_OBJ(value) && AS_OBJ(value)->type == OBJ_CLOSURE; } +bool wrenIsFiber(Value value) +{ + return IS_OBJ(value) && AS_OBJ(value)->type == OBJ_FIBER; +} + bool wrenIsFn(Value value) { return IS_OBJ(value) && AS_OBJ(value)->type == OBJ_FN; diff --git a/src/wren_value.h b/src/wren_value.h index b3d701ee..b17e0113 100644 --- a/src/wren_value.h +++ b/src/wren_value.h @@ -37,6 +37,10 @@ // and have growable symbol tables.) #define MAX_SYMBOLS 256 +// TODO: Make these externally controllable. +#define STACK_SIZE 1024 +#define MAX_CALL_FRAMES 256 + typedef enum { VAL_FALSE, @@ -49,6 +53,7 @@ typedef enum typedef enum { OBJ_CLASS, OBJ_CLOSURE, + OBJ_FIBER, OBJ_FN, OBJ_INSTANCE, OBJ_LIST, @@ -90,26 +95,6 @@ typedef struct #endif -typedef struct sFiber Fiber; - -typedef Value (*Primitive)(WrenVM* vm, Value* args); -typedef void (*FiberPrimitive)(WrenVM* vm, Fiber* fiber, Value* args); - -// A first-class function object. A raw ObjFn can be used and invoked directly -// if it has no upvalues (i.e. [numUpvalues] is zero). If it does use upvalues, -// it must be wrapped in an [ObjClosure] first. The compiler is responsible for -// emitting code to ensure that that happens. -typedef struct -{ - Obj obj; - int numConstants; - int numUpvalues; - unsigned char* bytecode; - - // TODO: Flexible array? - Value* constants; -} ObjFn; - // The dynamically allocated data structure for a variable that has been used // by a closure. Whenever a function accesses a variable declared in an // enclosing function, it will get to it through this. @@ -141,6 +126,54 @@ typedef struct sUpvalue struct sUpvalue* next; } Upvalue; +typedef struct +{ + // Pointer to the current (really next-to-be-executed) instruction in the + // function's bytecode. + unsigned char* ip; + + // The function or closure being executed. + Value fn; + + // Index of the first stack slot used by this call frame. This will contain + // the receiver, followed by the function's parameters, then local variables + // and temporaries. + int stackStart; +} CallFrame; + +typedef struct +{ + Obj obj; + Value stack[STACK_SIZE]; + int stackSize; + + CallFrame frames[MAX_CALL_FRAMES]; + int numFrames; + + // Pointer to the first node in the linked list of open upvalues that are + // pointing to values still on the stack. The head of the list will be the + // upvalue closest to the top of the stack, and then the list works downwards. + Upvalue* openUpvalues; +} ObjFiber; + +typedef Value (*Primitive)(WrenVM* vm, Value* args); +typedef void (*FiberPrimitive)(WrenVM* vm, ObjFiber* fiber, Value* args); + +// A first-class function object. A raw ObjFn can be used and invoked directly +// if it has no upvalues (i.e. [numUpvalues] is zero). If it does use upvalues, +// it must be wrapped in an [ObjClosure] first. The compiler is responsible for +// emitting code to ensure that that happens. +typedef struct +{ + Obj obj; + int numConstants; + int numUpvalues; + unsigned char* bytecode; + + // TODO: Flexible array? + Value* constants; +} ObjFn; + // An instance of a first-class function and the environment it has closed over. // Unlike [ObjFn], this has captured the upvalues that the function accesses. typedef struct @@ -425,6 +458,9 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields); // upvalues, but assumes outside code will populate it. ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn); +// Creates a new fiber object. +ObjFiber* wrenNewFiber(WrenVM* vm); + // Creates a new function object. Assumes the compiler will fill it in with // bytecode, constants, etc. ObjFn* wrenNewFunction(WrenVM* vm); @@ -453,6 +489,7 @@ void wrenPrintValue(Value value); bool wrenIsBool(Value value); bool wrenIsClosure(Value value); +bool wrenIsFiber(Value value); bool wrenIsFn(Value value); bool wrenIsInstance(Value value); bool wrenIsString(Value value); diff --git a/src/wren_vm.c b/src/wren_vm.c index bc5f3882..cbd797ee 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -25,14 +25,12 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration) } WrenVM* vm = reallocate(NULL, 0, sizeof(WrenVM)); + + vm->reallocate = reallocate; + initSymbolTable(&vm->methods); initSymbolTable(&vm->globalSymbols); - vm->fiber = reallocate(NULL, 0, sizeof(Fiber)); - vm->fiber->stackSize = 0; - vm->fiber->numFrames = 0; - vm->fiber->openUpvalues = NULL; - vm->bytesAllocated = 0; vm->nextGC = 1024 * 1024 * 10; @@ -66,10 +64,7 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration) vm->globals[i] = NULL_VAL; } - vm->reallocate = reallocate; - wrenInitializeCore(vm); - return vm; } @@ -80,16 +75,6 @@ void wrenFreeVM(WrenVM* vm) free(vm); } -int wrenInterpret(WrenVM* vm, const char* source) -{ - ObjFn* fn = wrenCompile(vm, source); - if (fn == NULL) return 1; - - // TODO: Return error code on runtime errors. - interpret(vm, OBJ_VAL(fn)); - return 0; -} - static void collectGarbage(WrenVM* vm); void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) @@ -234,6 +219,33 @@ static void markUpvalue(WrenVM* vm, Upvalue* upvalue) vm->bytesAllocated += sizeof(Upvalue); } +static void markFiber(WrenVM* vm, ObjFiber* fiber) +{ + // Don't recurse if already marked. Avoids getting stuck in a loop on cycles. + if (fiber->obj.flags & FLAG_MARKED) return; + fiber->obj.flags |= FLAG_MARKED; + + // Stack functions. + for (int k = 0; k < fiber->numFrames; k++) + { + markValue(vm, fiber->frames[k].fn); + } + + // Stack variables. + for (int l = 0; l < fiber->stackSize; l++) + { + markValue(vm, fiber->stack[l]); + } + + // Open upvalues. + Upvalue* upvalue = fiber->openUpvalues; + while (upvalue != NULL) + { + markUpvalue(vm, upvalue); + upvalue = upvalue->next; + } +} + static void markClosure(WrenVM* vm, ObjClosure* closure) { // Don't recurse if already marked. Avoids getting stuck in a loop on cycles. @@ -284,6 +296,7 @@ static void markObj(WrenVM* vm, Obj* obj) { case OBJ_CLASS: markClass( vm, (ObjClass*) obj); break; case OBJ_CLOSURE: markClosure( vm, (ObjClosure*) obj); break; + case OBJ_FIBER: markFiber( vm, (ObjFiber*) obj); break; case OBJ_FN: markFn( vm, (ObjFn*) obj); break; case OBJ_INSTANCE: markInstance(vm, (ObjInstance*)obj); break; case OBJ_LIST: markList( vm, (ObjList*) obj); break; @@ -376,26 +389,6 @@ static void collectGarbage(WrenVM* vm) pinned = pinned->previous; } - // Stack functions. - for (int k = 0; k < vm->fiber->numFrames; k++) - { - markValue(vm, vm->fiber->frames[k].fn); - } - - // Stack variables. - for (int l = 0; l < vm->fiber->stackSize; l++) - { - markValue(vm, vm->fiber->stack[l]); - } - - // Open upvalues. - Upvalue* upvalue = vm->fiber->openUpvalues; - while (upvalue != NULL) - { - markUpvalue(vm, upvalue); - upvalue = upvalue->next; - } - // Collect any unmarked objects. Obj** obj = &vm->first; while (*obj != NULL) @@ -498,7 +491,7 @@ Value findGlobal(WrenVM* vm, const char* name) // ensure that multiple closures closing over the same variable actually see // the same variable.) Otherwise, it will create a new open upvalue and add it // the fiber's list of upvalues. -static Upvalue* captureUpvalue(WrenVM* vm, Fiber* fiber, int slot) +static Upvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, int slot) { Value* local = &fiber->stack[slot]; @@ -541,7 +534,7 @@ static Upvalue* captureUpvalue(WrenVM* vm, Fiber* fiber, int slot) return createdUpvalue; } -static void closeUpvalue(Fiber* fiber) +static void closeUpvalue(ObjFiber* fiber) { Upvalue* upvalue = fiber->openUpvalues; @@ -584,11 +577,8 @@ static void bindMethod(int methodType, int symbol, ObjClass* classObj, // The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. -Value interpret(WrenVM* vm, Value function) +static Value interpret(WrenVM* vm, ObjFiber* fiber) { - Fiber* fiber = vm->fiber; - wrenCallFunction(fiber, function, 0); - // These macros are designed to only be invoked within this function. // TODO: Check for stack overflow. #define PUSH(value) (fiber->stack[fiber->stackSize++] = value) @@ -1152,9 +1142,31 @@ Value interpret(WrenVM* vm, Value function) // the compiler generated wrong code. ASSERT(0, "Should not execute past end of bytecode."); } + + ASSERT(0, "Should not reach end of interpret."); } -void wrenCallFunction(Fiber* fiber, Value function, int numArgs) +int wrenInterpret(WrenVM* vm, const char* source) +{ + ObjFiber* fiber = wrenNewFiber(vm); + PinnedObj pinned; + pinObj(vm, (Obj*)fiber, &pinned); + + int result = 1; + ObjFn* fn = wrenCompile(vm, source); + if (fn != NULL) + { + wrenCallFunction(fiber, OBJ_VAL(fn), 0); + // TODO: Return error code on runtime errors. + interpret(vm, fiber); + result = 0; + } + + unpinObj(vm); + return result; +} + +void wrenCallFunction(ObjFiber* fiber, Value function, int numArgs) { // TODO: Check for stack overflow. CallFrame* frame = &fiber->frames[fiber->numFrames]; diff --git a/src/wren_vm.h b/src/wren_vm.h index 5e833560..0030abe4 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -4,10 +4,6 @@ #include "wren_common.h" #include "wren_value.h" -// TODO: Make these externally controllable. -#define STACK_SIZE 1024 -#define MAX_CALL_FRAMES 256 - typedef enum { // Load the constant at index [arg]. @@ -192,6 +188,7 @@ struct WrenVM ObjClass* boolClass; ObjClass* classClass; + ObjClass* fiberClass; ObjClass* fnClass; ObjClass* listClass; ObjClass* nullClass; @@ -199,17 +196,11 @@ struct WrenVM ObjClass* objectClass; ObjClass* stringClass; - // The singleton values. - Value unsupported; - SymbolTable globalSymbols; // TODO: Using a fixed array is gross here. Value globals[MAX_SYMBOLS]; - // TODO: Support more than one fiber. - Fiber* fiber; - // Memory management data: // TODO: Temp. @@ -241,37 +232,6 @@ struct WrenVM WrenReallocateFn reallocate; }; -// TODO: Move into wren_vm.c. -typedef struct -{ - // Pointer to the current (really next-to-be-executed) instruction in the - // function's bytecode. - unsigned char* ip; - - // The function or closure being executed. - Value fn; - - // Index of the first stack slot used by this call frame. This will contain - // the receiver, followed by the function's parameters, then local variables - // and temporaries. - int stackStart; -} CallFrame; - -// TODO: Move into wren_vm.c. -struct sFiber -{ - Value stack[STACK_SIZE]; - int stackSize; - - CallFrame frames[MAX_CALL_FRAMES]; - int numFrames; - - // Pointer to the first node in the linked list of open upvalues that are - // pointing to values still on the stack. The head of the list will be the - // upvalue closest to the top of the stack, and then the list works downwards. - Upvalue* openUpvalues; -}; - void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); // TODO: Make these static or prefix their names. @@ -303,12 +263,10 @@ const char* getSymbolName(SymbolTable* symbols, int symbol); // Returns the global variable named [name]. Value findGlobal(WrenVM* vm, const char* name); -Value interpret(WrenVM* vm, Value function); - // Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs] // arguments (including the receiver) to be on the top of the stack already. // [function] can be an `ObjFn` or `ObjClosure`. -void wrenCallFunction(Fiber* fiber, Value function, int numArgs); +void wrenCallFunction(ObjFiber* fiber, Value function, int numArgs); // Mark [obj] as a GC root so that it doesn't get collected. Initializes // [pinned], which must be then passed to [unpinObj].