Make fibers real objects.

This commit is contained in:
Bob Nystrom
2013-12-22 14:31:33 -08:00
parent 7625de7536
commit 034fec6eaa
7 changed files with 156 additions and 130 deletions

View File

@ -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"));

View File

@ -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++)

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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];

View File

@ -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].