void wrenMarkObj(WrenVM* vm, Obj* obj) { #if WREN_DEBUG_TRACE_MEMORY static int indent = 0; indent++; for (int i = 0; i < indent; i++) printf(" "); printf("mark "); wrenPrintValue(OBJ_VAL(obj)); printf(" @ %p\n", obj); #endif // Traverse the object's fields. switch (obj->type) { 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; case OBJ_RANGE: setMarkedFlag(obj); break; case OBJ_STRING: markString( vm, (ObjString*) obj); break; case OBJ_UPVALUE: markUpvalue( vm, (Upvalue*) obj); break; } #if WREN_DEBUG_TRACE_MEMORY indent--; #endif }
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjString* name) { // Create the metaclass. Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name)); wrenPushRoot(vm, AS_OBJ(metaclassName)); ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); metaclass->obj.classObj = vm->classClass; wrenPopRoot(vm); // Make sure the metaclass isn't collected when we allocate the class. wrenPushRoot(vm, (Obj*)metaclass); // Metaclasses always inherit Class and do not parallel the non-metaclass // hierarchy. wrenBindSuperclass(vm, metaclass, vm->classClass); ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); // Make sure the class isn't collected while the inherited methods are being // bound. wrenPushRoot(vm, (Obj*)classObj); classObj->obj.classObj = metaclass; wrenBindSuperclass(vm, classObj, superclass); wrenPopRoot(vm); wrenPopRoot(vm); return classObj; }
// Validates that the given argument in [args] is a function. Returns true if // it is. If not, reports an error and returns false. static bool validateFn(WrenVM* vm, Value* args, int index, const char* argName) { if (IS_FN(args[index]) || IS_CLOSURE(args[index])) return true; args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " must be a function.")); return false; }
// Validates that the given argument in [args] is a Num. Returns true if it is. // If not, reports an error and returns false. static bool validateNum(WrenVM* vm, Value* args, int index, const char* argName) { if (IS_NUM(args[index])) return true; args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " must be a number.")); return false; }
static void defineMethod(WrenVM* vm, const char* className, const char* methodName, int numParams, WrenForeignMethodFn methodFn, bool isStatic) { ASSERT(className != NULL, "Must provide class name."); int length = (int)strlen(methodName); ASSERT(methodName != NULL, "Must provide method name."); ASSERT(strlen(methodName) < MAX_METHOD_NAME, "Method name too long."); ASSERT(numParams >= 0, "numParams cannot be negative."); ASSERT(numParams <= MAX_PARAMETERS, "Too many parameters."); ASSERT(methodFn != NULL, "Must provide method function."); // Find or create the class to bind the method to. int classSymbol = wrenSymbolTableFind(&vm->globalNames, className, strlen(className)); ObjClass* classObj; if (classSymbol != -1) { // TODO: Handle name is not class. classObj = AS_CLASS(vm->globals.data[classSymbol]); } else { // The class doesn't already exist, so create it. size_t length = strlen(className); ObjString* nameString = AS_STRING(wrenNewString(vm, className, length)); WREN_PIN(vm, nameString); // TODO: Allow passing in name for superclass? classObj = wrenNewClass(vm, vm->objectClass, 0, nameString); wrenDefineGlobal(vm, className, length, OBJ_VAL(classObj)); WREN_UNPIN(vm); } // Create a name for the method, including its arity. char name[MAX_METHOD_SIGNATURE]; strncpy(name, methodName, length); for (int i = 0; i < numParams; i++) { name[length++] = ' '; } name[length] = '\0'; // Bind the method. int methodSymbol = wrenSymbolTableEnsure(vm, &vm->methodNames, name, length); Method method; method.type = METHOD_FOREIGN; method.fn.foreign = methodFn; if (isStatic) classObj = classObj->obj.classObj; wrenBindMethod(vm, classObj, methodSymbol, method); }
// Validates that [value] is an integer. Returns true if it is. If not, reports // an error and returns false. static bool validateIntValue(WrenVM* vm, Value* args, double value, const char* argName) { if (trunc(value) == value) return true; args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " must be an integer.")); return false; }
void metaCompile(WrenVM* vm) { // Evaluate the code in the module where the calling function was defined. // That's one stack frame back from the top since the top-most frame is the // helper eval() method in Meta itself. Value callingFn = OBJ_VAL(vm->fiber->frames[vm->fiber->numFrames - 2].fn); ObjModule* module = IS_FN(callingFn) ? AS_FN(callingFn)->module : AS_CLOSURE(callingFn)->fn->module; // Compile it. ObjFn* fn = wrenCompile(vm, module, wrenGetArgumentString(vm, 1), false); if (fn == NULL) return; // Return the result. We can't use the public API for this since we have a // bare ObjFn. *vm->foreignCallSlot = OBJ_VAL(fn); vm->foreignCallSlot = NULL; }
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) { ObjRange* range = allocate(vm, sizeof(ObjRange) + 16); initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); range->from = from; range->to = to; range->isInclusive = isInclusive; return OBJ_VAL(range); }
Value wrenNewUninitializedString(WrenVM* vm, size_t length) { // Allocate before the string object in case this triggers a GC which would // free the string object. char* heapText = allocate(vm, length + 1); ObjString* string = allocate(vm, sizeof(ObjString)); initObj(vm, &string->obj, OBJ_STRING, vm->stringClass); string->value = heapText; return OBJ_VAL(string); }
static ObjClass* defineClass(WrenVM* vm, const char* name) { size_t length = strlen(name); ObjString* nameString = AS_STRING(wrenNewString(vm, name, length)); WREN_PIN(vm, nameString); ObjClass* classObj = wrenNewClass(vm, vm->objectClass, 0, nameString); wrenDefineGlobal(vm, name, length, OBJ_VAL(classObj)); WREN_UNPIN(vm); return classObj; }
// Creates either the Object or Class class in the core module with [name]. static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) { ObjString* nameString = AS_STRING(wrenStringFormat(vm, "$", name)); wrenPushRoot(vm, (Obj*)nameString); ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj)); wrenPopRoot(vm); return classObj; }
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) { ObjInstance* instance = allocate(vm, sizeof(ObjInstance) + classObj->numFields * sizeof(Value)); initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); // Initialize fields to null. for (int i = 0; i < classObj->numFields; i++) { instance->fields[i] = NULL_VAL; } return OBJ_VAL(instance); }
void wrenFreeObj(WrenVM* vm, Obj* obj) { #if WREN_DEBUG_TRACE_MEMORY printf("free "); wrenPrintValue(OBJ_VAL(obj)); printf(" @ %p\n", obj); #endif switch (obj->type) { case OBJ_CLASS: wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); break; case OBJ_FIBER: { ObjFiber* fiber = ((ObjFiber*)obj); if (fiber->error != NULL) wrenReallocate(vm, fiber->error, 0, 0); break; } case OBJ_FN: { ObjFn* fn = (ObjFn*)obj; wrenReallocate(vm, fn->constants, 0, 0); wrenReallocate(vm, fn->bytecode, 0, 0); wrenReallocate(vm, fn->debug->name, 0, 0); wrenReallocate(vm, fn->debug->sourceLines, 0, 0); wrenReallocate(vm, fn->debug, 0, 0); break; } case OBJ_LIST: wrenReallocate(vm, ((ObjList*)obj)->elements, 0, 0); break; case OBJ_STRING: wrenReallocate(vm, ((ObjString*)obj)->value, 0, 0); break; case OBJ_CLOSURE: case OBJ_INSTANCE: case OBJ_RANGE: case OBJ_UPVALUE: break; } wrenReallocate(vm, obj, 0, 0); }
// Validates that [value] is an integer within `[0, count)`. Also allows // negative indices which map backwards from the end. Returns the valid positive // index value. If invalid, reports an error and returns -1. static int validateIndexValue(WrenVM* vm, Value* args, int count, double value, const char* argName) { if (!validateIntValue(vm, args, value, argName)) return -1; int index = (int)value; // Negative indices count from the end. if (index < 0) index = count + index; // Check bounds. if (index >= 0 && index < count) return index; args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " out of bounds.")); return -1; }
Value wrenNewString(WrenVM* vm, const char* text, size_t length) { // Allow NULL if the string is empty since byte buffers don't allocate any // characters for a zero-length string. ASSERT(length == 0 || text != NULL, "Unexpected NULL string."); // TODO: Don't allocate a heap string at all for zero-length strings. ObjString* string = AS_STRING(wrenNewUninitializedString(vm, length)); // Copy the string (if given one). if (length > 0) strncpy(string->value, text, length); string->value[length] = '\0'; return OBJ_VAL(string); }
Value wrenNewString(WrenVM* vm, const char* text, size_t length) { // Allocate before the string object in case this triggers a GC which would // free the string object. char* heapText = allocate(vm, length + 1); ObjString* string = allocate(vm, sizeof(ObjString)); initObj(vm, &string->obj, OBJ_STRING); string->value = heapText; // Copy the string (if given one). if (text != NULL) { strncpy(heapText, text, length); heapText[length] = '\0'; } return OBJ_VAL(string); }
// Puts [fiber] into a runtime failed state because of [error]. // // Returns the fiber that should receive the error or `NULL` if no fiber // caught it. static ObjFiber* runtimeError(WrenVM* vm, ObjFiber* fiber, ObjString* error) { ASSERT(fiber->error == NULL, "Can only fail once."); // Store the error in the fiber so it can be accessed later. fiber->error = error; // If the caller ran this fiber using "try", give it the error. if (fiber->callerIsTrying) { ObjFiber* caller = fiber->caller; // Make the caller's try method return the error message. *(caller->stackTop - 1) = OBJ_VAL(fiber->error); return caller; } // If we got here, nothing caught the error, so show the stack trace. wrenDebugPrintStackTrace(vm, fiber); return NULL; }
void wrenInitializeCore(WrenVM* vm) { ObjModule* coreModule = wrenNewModule(vm, NULL); wrenPushRoot(vm, (Obj*)coreModule); // The core module's key is null in the module map. wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule)); wrenPopRoot(vm); // coreModule. // Define the root Object class. This has to be done a little specially // because it has no superclass. vm->objectClass = defineClass(vm, coreModule, "Object"); PRIMITIVE(vm->objectClass, "!", object_not); PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); PRIMITIVE(vm->objectClass, "is(_)", object_is); PRIMITIVE(vm->objectClass, "toString", object_toString); PRIMITIVE(vm->objectClass, "type", object_type); // Now we can define Class, which is a subclass of Object. vm->classClass = defineClass(vm, coreModule, "Class"); wrenBindSuperclass(vm, vm->classClass, vm->objectClass); PRIMITIVE(vm->classClass, "name", class_name); PRIMITIVE(vm->classClass, "supertype", class_supertype); PRIMITIVE(vm->classClass, "toString", class_toString); // Finally, we can define Object's metaclass which is a subclass of Class. ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass"); // Wire up the metaclass relationships now that all three classes are built. vm->objectClass->obj.classObj = objectMetaclass; objectMetaclass->obj.classObj = vm->classClass; vm->classClass->obj.classObj = vm->classClass; // Do this after wiring up the metaclasses so objectMetaclass doesn't get // collected. wrenBindSuperclass(vm, objectMetaclass, vm->classClass); PRIMITIVE(objectMetaclass, "same(_,_)", object_same); // The core class diagram ends up looking like this, where single lines point // to a class's superclass, and double lines point to its metaclass: // // .------------------------------------. .====. // | .---------------. | # # // v | v | v # // .---------. .-------------------. .-------. # // | Object |==>| Object metaclass |==>| Class |==" // '---------' '-------------------' '-------' // ^ ^ ^ ^ ^ // | .--------------' # | # // | | # | # // .---------. .-------------------. # | # -. // | Base |==>| Base metaclass |======" | # | // '---------' '-------------------' | # | // ^ | # | // | .------------------' # | Example classes // | | # | // .---------. .-------------------. # | // | Derived |==>| Derived metaclass |==========" | // '---------' '-------------------' -' // The rest of the classes can now be defined normally. wrenInterpretInModule(vm, NULL, coreModuleSource); vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool")); PRIMITIVE(vm->boolClass, "toString", bool_toString); PRIMITIVE(vm->boolClass, "!", bool_not); vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber")); PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); PRIMITIVE(vm->fiberClass, "call()", fiber_call); PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); PRIMITIVE(vm->fiberClass, "error", fiber_error); PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError); PRIMITIVE(vm->fiberClass, "try()", fiber_try); vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); PRIMITIVE(vm->fnClass, "arity", fn_arity); fnCall(vm, "call()"); fnCall(vm, "call(_)"); fnCall(vm, "call(_,_)"); fnCall(vm, "call(_,_,_)"); fnCall(vm, "call(_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)"); fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)"); PRIMITIVE(vm->fnClass, "toString", fn_toString); vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null")); PRIMITIVE(vm->nullClass, "!", null_not); PRIMITIVE(vm->nullClass, "toString", null_toString); vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num")); PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString); PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi); PRIMITIVE(vm->numClass, "-(_)", num_minus); PRIMITIVE(vm->numClass, "+(_)", num_plus); PRIMITIVE(vm->numClass, "*(_)", num_multiply); PRIMITIVE(vm->numClass, "/(_)", num_divide); PRIMITIVE(vm->numClass, "<(_)", num_lt); PRIMITIVE(vm->numClass, ">(_)", num_gt); PRIMITIVE(vm->numClass, "<=(_)", num_lte); PRIMITIVE(vm->numClass, ">=(_)", num_gte); PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd); PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr); PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor); PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift); PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift); PRIMITIVE(vm->numClass, "abs", num_abs); PRIMITIVE(vm->numClass, "acos", num_acos); PRIMITIVE(vm->numClass, "asin", num_asin); PRIMITIVE(vm->numClass, "atan", num_atan); PRIMITIVE(vm->numClass, "ceil", num_ceil); PRIMITIVE(vm->numClass, "cos", num_cos); PRIMITIVE(vm->numClass, "floor", num_floor); PRIMITIVE(vm->numClass, "-", num_negate); PRIMITIVE(vm->numClass, "sin", num_sin); PRIMITIVE(vm->numClass, "sqrt", num_sqrt); PRIMITIVE(vm->numClass, "tan", num_tan); PRIMITIVE(vm->numClass, "%(_)", num_mod); PRIMITIVE(vm->numClass, "~", num_bitwiseNot); PRIMITIVE(vm->numClass, "..(_)", num_dotDot); PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot); PRIMITIVE(vm->numClass, "atan(_)", num_atan2); PRIMITIVE(vm->numClass, "fraction", num_fraction); PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity); PRIMITIVE(vm->numClass, "isInteger", num_isInteger); PRIMITIVE(vm->numClass, "isNan", num_isNan); PRIMITIVE(vm->numClass, "sign", num_sign); PRIMITIVE(vm->numClass, "toString", num_toString); PRIMITIVE(vm->numClass, "truncate", num_truncate); // These are defined just so that 0 and -0 are equal, which is specified by // IEEE 754 even though they have different bit representations. PRIMITIVE(vm->numClass, "==(_)", num_eqeq); PRIMITIVE(vm->numClass, "!=(_)", num_bangeq); vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String")); PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint); PRIMITIVE(vm->stringClass, "+(_)", string_plus); PRIMITIVE(vm->stringClass, "[_]", string_subscript); PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt); PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount); PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt); PRIMITIVE(vm->stringClass, "contains(_)", string_contains); PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith); PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf); PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate); PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte); PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue); PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith); PRIMITIVE(vm->stringClass, "toString", string_toString); vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List")); PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new); PRIMITIVE(vm->listClass, "[_]", list_subscript); PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter); PRIMITIVE(vm->listClass, "add(_)", list_add); PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore); PRIMITIVE(vm->listClass, "clear()", list_clear); PRIMITIVE(vm->listClass, "count", list_count); PRIMITIVE(vm->listClass, "insert(_,_)", list_insert); PRIMITIVE(vm->listClass, "iterate(_)", list_iterate); PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue); PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt); vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); PRIMITIVE(vm->mapClass, "[_]", map_subscript); PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter); PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore); PRIMITIVE(vm->mapClass, "clear()", map_clear); PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey); PRIMITIVE(vm->mapClass, "count", map_count); PRIMITIVE(vm->mapClass, "remove(_)", map_remove); PRIMITIVE(vm->mapClass, "iterate_(_)", map_iterate); PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue); PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue); vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range")); PRIMITIVE(vm->rangeClass, "from", range_from); PRIMITIVE(vm->rangeClass, "to", range_to); PRIMITIVE(vm->rangeClass, "min", range_min); PRIMITIVE(vm->rangeClass, "max", range_max); PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive); PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate); PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); PRIMITIVE(vm->rangeClass, "toString", range_toString); ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); // TODO: Do we want to give these magic names that can't be called from // regular code? PRIMITIVE(systemClass->obj.classObj, "getModuleVariable(_,_)", system_getModuleVariable); PRIMITIVE(systemClass->obj.classObj, "importModule(_)", system_importModule); PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString); // While bootstrapping the core types and running the core module, a number // of string objects have been created, many of which were instantiated // before stringClass was stored in the VM. Some of them *must* be created // first -- the ObjClass for string itself has a reference to the ObjString // for its name. // // These all currently have a NULL classObj pointer, so go back and assign // them now that the string class is known. for (Obj* obj = vm->first; obj != NULL; obj = obj->next) { if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass; } }
// The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. Returns `true` if the // fiber completed without error. static bool runInterpreter(WrenVM* vm) { // Hoist these into local variables. They are accessed frequently in the loop // but assigned less frequently. Keeping them in locals and updating them when // a call frame has been pushed or popped gives a large speed boost. register ObjFiber* fiber = vm->fiber; register CallFrame* frame; register Value* stackStart; register uint8_t* ip; register ObjFn* fn; // These macros are designed to only be invoked within this function. #define PUSH(value) (*fiber->stackTop++ = value) #define POP() (*(--fiber->stackTop)) #define DROP() (fiber->stackTop--) #define PEEK() (*(fiber->stackTop - 1)) #define PEEK2() (*(fiber->stackTop - 2)) #define READ_BYTE() (*ip++) #define READ_SHORT() (ip += 2, (ip[-2] << 8) | ip[-1]) // Use this before a CallFrame is pushed to store the local variables back // into the current one. #define STORE_FRAME() frame->ip = ip // Use this after a CallFrame has been pushed or popped to refresh the local // variables. #define LOAD_FRAME() \ frame = &fiber->frames[fiber->numFrames - 1]; \ stackStart = frame->stackStart; \ ip = frame->ip; \ if (frame->fn->type == OBJ_FN) \ { \ fn = (ObjFn*)frame->fn; \ } \ else \ { \ fn = ((ObjClosure*)frame->fn)->fn; \ } // Terminates the current fiber with error string [error]. If another calling // fiber is willing to catch the error, transfers control to it, otherwise // exits the interpreter. #define RUNTIME_ERROR(error) \ do { \ STORE_FRAME(); \ fiber = runtimeError(vm, fiber, error); \ if (fiber == NULL) return false; \ LOAD_FRAME(); \ DISPATCH(); \ } \ while (false) #if WREN_COMPUTED_GOTO // Note that the order of instructions here must exacly match the Code enum // in wren_vm.h or horrendously bad things happen. static void* dispatchTable[] = { &&code_CONSTANT, &&code_NULL, &&code_FALSE, &&code_TRUE, &&code_LOAD_LOCAL_0, &&code_LOAD_LOCAL_1, &&code_LOAD_LOCAL_2, &&code_LOAD_LOCAL_3, &&code_LOAD_LOCAL_4, &&code_LOAD_LOCAL_5, &&code_LOAD_LOCAL_6, &&code_LOAD_LOCAL_7, &&code_LOAD_LOCAL_8, &&code_LOAD_LOCAL, &&code_STORE_LOCAL, &&code_LOAD_UPVALUE, &&code_STORE_UPVALUE, &&code_LOAD_GLOBAL, &&code_STORE_GLOBAL, &&code_LOAD_FIELD_THIS, &&code_STORE_FIELD_THIS, &&code_LOAD_FIELD, &&code_STORE_FIELD, &&code_POP, &&code_CALL_0, &&code_CALL_1, &&code_CALL_2, &&code_CALL_3, &&code_CALL_4, &&code_CALL_5, &&code_CALL_6, &&code_CALL_7, &&code_CALL_8, &&code_CALL_9, &&code_CALL_10, &&code_CALL_11, &&code_CALL_12, &&code_CALL_13, &&code_CALL_14, &&code_CALL_15, &&code_CALL_16, &&code_SUPER_0, &&code_SUPER_1, &&code_SUPER_2, &&code_SUPER_3, &&code_SUPER_4, &&code_SUPER_5, &&code_SUPER_6, &&code_SUPER_7, &&code_SUPER_8, &&code_SUPER_9, &&code_SUPER_10, &&code_SUPER_11, &&code_SUPER_12, &&code_SUPER_13, &&code_SUPER_14, &&code_SUPER_15, &&code_SUPER_16, &&code_JUMP, &&code_LOOP, &&code_JUMP_IF, &&code_AND, &&code_OR, &&code_IS, &&code_CLOSE_UPVALUE, &&code_RETURN, &&code_LIST, &&code_CLOSURE, &&code_CLASS, &&code_METHOD_INSTANCE, &&code_METHOD_STATIC, &&code_END }; #define INTERPRET_LOOP DISPATCH(); #define CASE_CODE(name) code_##name #if WREN_DEBUG_TRACE_INSTRUCTIONS // Prints the stack and instruction before each instruction is executed. #define DISPATCH() \ { \ wrenDebugPrintStack(fiber); \ wrenDebugPrintInstruction(vm, fn, (int)(ip - fn->bytecode)); \ instruction = *ip++; \ goto *dispatchTable[instruction]; \ } #else #define DISPATCH() goto *dispatchTable[instruction = READ_BYTE()]; #endif #else #define INTERPRET_LOOP for (;;) switch (instruction = READ_BYTE()) #define CASE_CODE(name) case CODE_##name #define DISPATCH() break #endif LOAD_FRAME(); Code instruction; INTERPRET_LOOP { CASE_CODE(LOAD_LOCAL_0): CASE_CODE(LOAD_LOCAL_1): CASE_CODE(LOAD_LOCAL_2): CASE_CODE(LOAD_LOCAL_3): CASE_CODE(LOAD_LOCAL_4): CASE_CODE(LOAD_LOCAL_5): CASE_CODE(LOAD_LOCAL_6): CASE_CODE(LOAD_LOCAL_7): CASE_CODE(LOAD_LOCAL_8): PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); DISPATCH(); CASE_CODE(LOAD_LOCAL): PUSH(stackStart[READ_BYTE()]); DISPATCH(); CASE_CODE(LOAD_FIELD_THIS): { int field = READ_BYTE(); Value receiver = stackStart[0]; ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); PUSH(instance->fields[field]); DISPATCH(); } CASE_CODE(POP): DROP(); DISPATCH(); CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH(); CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH(); CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH(); CASE_CODE(CALL_0): CASE_CODE(CALL_1): CASE_CODE(CALL_2): CASE_CODE(CALL_3): CASE_CODE(CALL_4): CASE_CODE(CALL_5): CASE_CODE(CALL_6): CASE_CODE(CALL_7): CASE_CODE(CALL_8): CASE_CODE(CALL_9): CASE_CODE(CALL_10): CASE_CODE(CALL_11): CASE_CODE(CALL_12): CASE_CODE(CALL_13): CASE_CODE(CALL_14): CASE_CODE(CALL_15): CASE_CODE(CALL_16): { // Add one for the implicit receiver argument. int numArgs = instruction - CODE_CALL_0 + 1; int symbol = READ_SHORT(); Value receiver = *(fiber->stackTop - numArgs); ObjClass* classObj = wrenGetClassInline(vm, receiver); // If the class's method table doesn't include the symbol, bail. if (symbol >= classObj->methods.count) { RUNTIME_ERROR(methodNotFound(vm, classObj, symbol)); } Method* method = &classObj->methods.data[symbol]; switch (method->type) { case METHOD_PRIMITIVE: { Value* args = fiber->stackTop - numArgs; // After calling this, the result will be in the first arg slot. switch (method->fn.primitive(vm, fiber, args)) { case PRIM_VALUE: // The result is now in the first arg slot. Discard the other // stack slots. fiber->stackTop -= numArgs - 1; break; case PRIM_ERROR: RUNTIME_ERROR(AS_STRING(args[0])); case PRIM_CALL: STORE_FRAME(); callFunction(fiber, AS_OBJ(args[0]), numArgs); LOAD_FRAME(); break; case PRIM_RUN_FIBER: STORE_FRAME(); fiber = AS_FIBER(args[0]); LOAD_FRAME(); break; } break; } case METHOD_FOREIGN: callForeign(vm, fiber, method->fn.foreign, numArgs); break; case METHOD_BLOCK: STORE_FRAME(); callFunction(fiber, method->fn.obj, numArgs); LOAD_FRAME(); break; case METHOD_NONE: RUNTIME_ERROR(methodNotFound(vm, classObj, symbol)); break; } DISPATCH(); } CASE_CODE(STORE_LOCAL): stackStart[READ_BYTE()] = PEEK(); DISPATCH(); CASE_CODE(CONSTANT): PUSH(fn->constants[READ_SHORT()]); DISPATCH(); CASE_CODE(SUPER_0): CASE_CODE(SUPER_1): CASE_CODE(SUPER_2): CASE_CODE(SUPER_3): CASE_CODE(SUPER_4): CASE_CODE(SUPER_5): CASE_CODE(SUPER_6): CASE_CODE(SUPER_7): CASE_CODE(SUPER_8): CASE_CODE(SUPER_9): CASE_CODE(SUPER_10): CASE_CODE(SUPER_11): CASE_CODE(SUPER_12): CASE_CODE(SUPER_13): CASE_CODE(SUPER_14): CASE_CODE(SUPER_15): CASE_CODE(SUPER_16): { // TODO: Almost completely copied from CALL. Unify somehow. // Add one for the implicit receiver argument. int numArgs = instruction - CODE_SUPER_0 + 1; int symbol = READ_SHORT(); Value receiver = *(fiber->stackTop - numArgs); ObjClass* classObj = wrenGetClassInline(vm, receiver); // Ignore methods defined on the receiver's immediate class. classObj = classObj->superclass; // If the class's method table doesn't include the symbol, bail. if (symbol >= classObj->methods.count) { RUNTIME_ERROR(methodNotFound(vm, classObj, symbol)); } Method* method = &classObj->methods.data[symbol]; switch (method->type) { case METHOD_PRIMITIVE: { Value* args = fiber->stackTop - numArgs; // After calling this, the result will be in the first arg slot. switch (method->fn.primitive(vm, fiber, args)) { case PRIM_VALUE: // The result is now in the first arg slot. Discard the other // stack slots. fiber->stackTop -= numArgs - 1; break; case PRIM_ERROR: RUNTIME_ERROR(AS_STRING(args[0])); case PRIM_CALL: STORE_FRAME(); callFunction(fiber, AS_OBJ(args[0]), numArgs); LOAD_FRAME(); break; case PRIM_RUN_FIBER: STORE_FRAME(); fiber = AS_FIBER(args[0]); LOAD_FRAME(); break; } break; } case METHOD_FOREIGN: callForeign(vm, fiber, method->fn.foreign, numArgs); break; case METHOD_BLOCK: STORE_FRAME(); callFunction(fiber, method->fn.obj, numArgs); LOAD_FRAME(); break; case METHOD_NONE: RUNTIME_ERROR(methodNotFound(vm, classObj, symbol)); break; } DISPATCH(); } CASE_CODE(LOAD_UPVALUE): { Upvalue** upvalues = ((ObjClosure*)frame->fn)->upvalues; PUSH(*upvalues[READ_BYTE()]->value); DISPATCH(); } CASE_CODE(STORE_UPVALUE): { Upvalue** upvalues = ((ObjClosure*)frame->fn)->upvalues; *upvalues[READ_BYTE()]->value = PEEK(); DISPATCH(); } CASE_CODE(LOAD_GLOBAL): PUSH(vm->globals.data[READ_SHORT()]); DISPATCH(); CASE_CODE(STORE_GLOBAL): vm->globals.data[READ_SHORT()] = PEEK(); DISPATCH(); CASE_CODE(STORE_FIELD_THIS): { int field = READ_BYTE(); Value receiver = stackStart[0]; ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); instance->fields[field] = PEEK(); DISPATCH(); } CASE_CODE(LOAD_FIELD): { int field = READ_BYTE(); Value receiver = POP(); ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); PUSH(instance->fields[field]); DISPATCH(); } CASE_CODE(STORE_FIELD): { int field = READ_BYTE(); Value receiver = POP(); ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); instance->fields[field] = PEEK(); DISPATCH(); } CASE_CODE(JUMP): { int offset = READ_SHORT(); ip += offset; DISPATCH(); } CASE_CODE(LOOP): { // Jump back to the top of the loop. int offset = READ_SHORT(); ip -= offset; DISPATCH(); } CASE_CODE(JUMP_IF): { int offset = READ_SHORT(); Value condition = POP(); if (IS_FALSE(condition) || IS_NULL(condition)) ip += offset; DISPATCH(); } CASE_CODE(AND): { int offset = READ_SHORT(); Value condition = PEEK(); if (IS_FALSE(condition) || IS_NULL(condition)) { // Short-circuit the right hand side. ip += offset; } else { // Discard the condition and evaluate the right hand side. DROP(); } DISPATCH(); } CASE_CODE(OR): { int offset = READ_SHORT(); Value condition = PEEK(); if (IS_FALSE(condition) || IS_NULL(condition)) { // Discard the condition and evaluate the right hand side. DROP(); } else { // Short-circuit the right hand side. ip += offset; } DISPATCH(); } CASE_CODE(IS): { Value expected = POP(); if (!IS_CLASS(expected)) { const char* message = "Right operand must be a class."; RUNTIME_ERROR(AS_STRING(wrenNewString(vm, message, strlen(message)))); } ObjClass* actual = wrenGetClass(vm, POP()); bool isInstance = false; // Walk the superclass chain looking for the class. while (actual != NULL) { if (actual == AS_CLASS(expected)) { isInstance = true; break; } actual = actual->superclass; } PUSH(BOOL_VAL(isInstance)); DISPATCH(); } CASE_CODE(CLOSE_UPVALUE): closeUpvalue(fiber); DROP(); DISPATCH(); CASE_CODE(RETURN): { Value result = POP(); fiber->numFrames--; // Close any upvalues still in scope. Value* firstValue = stackStart; while (fiber->openUpvalues != NULL && fiber->openUpvalues->value >= firstValue) { closeUpvalue(fiber); } // If the fiber is complete, end it. if (fiber->numFrames == 0) { // If this is the main fiber, we're done. if (fiber->caller == NULL) return true; // We have a calling fiber to resume. fiber = fiber->caller; // Store the result in the resuming fiber. *(fiber->stackTop - 1) = result; } else { // Store the result of the block in the first slot, which is where the // caller expects it. stackStart[0] = result; // Discard the stack slots for the call frame (leaving one slot for the // result). fiber->stackTop = frame->stackStart + 1; } LOAD_FRAME(); DISPATCH(); } CASE_CODE(LIST): { int numElements = READ_BYTE(); ObjList* list = wrenNewList(vm, numElements); // TODO: Do a straight memcopy. for (int i = 0; i < numElements; i++) { list->elements[i] = *(fiber->stackTop - numElements + i); } // Discard the elements. fiber->stackTop -= numElements; PUSH(OBJ_VAL(list)); DISPATCH(); } CASE_CODE(CLOSURE): { ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]); ASSERT(prototype->numUpvalues > 0, "Should not create closure for functions that don't need it."); // Create the closure and push it on the stack before creating upvalues // so that it doesn't get collected. ObjClosure* closure = wrenNewClosure(vm, prototype); PUSH(OBJ_VAL(closure)); // Capture upvalues. for (int i = 0; i < prototype->numUpvalues; i++) { bool isLocal = READ_BYTE(); int index = READ_BYTE(); if (isLocal) { // Make an new upvalue to close over the parent's local variable. closure->upvalues[i] = captureUpvalue(vm, fiber, frame->stackStart + index); } else { // Use the same upvalue as the current call frame. closure->upvalues[i] = ((ObjClosure*)frame->fn)->upvalues[index]; } } DISPATCH(); } CASE_CODE(CLASS): { ObjString* name = AS_STRING(PEEK2()); ObjClass* superclass; if (IS_NULL(PEEK())) { // Implicit Object superclass. superclass = vm->objectClass; } else { // TODO: Handle the superclass not being a class object! superclass = AS_CLASS(PEEK()); } int numFields = READ_BYTE(); ObjClass* classObj = wrenNewClass(vm, superclass, numFields, name); // Don't pop the superclass and name off the stack until the subclass is // done being created, to make sure it doesn't get collected. DROP(); DROP(); // Now that we know the total number of fields, make sure we don't // overflow. if (superclass->numFields + numFields > MAX_FIELDS) { char message[70 + MAX_VARIABLE_NAME]; snprintf(message, 70 + MAX_VARIABLE_NAME, "Class '%s' may not have more than %d fields, including inherited " "ones.", name->value, MAX_FIELDS); RUNTIME_ERROR(AS_STRING(wrenNewString(vm, message, strlen(message)))); } PUSH(OBJ_VAL(classObj)); DISPATCH(); } CASE_CODE(METHOD_INSTANCE): CASE_CODE(METHOD_STATIC): { int type = instruction; int symbol = READ_SHORT(); ObjClass* classObj = AS_CLASS(PEEK()); Value method = PEEK2(); bindMethod(vm, type, symbol, classObj, method); DROP(); DROP(); DISPATCH(); } CASE_CODE(END): // A CODE_END should always be preceded by a CODE_RETURN. If we get here, // the compiler generated wrong code. UNREACHABLE(); } // We should only exit this function from an explicit return from CODE_RETURN // or a runtime error. UNREACHABLE(); return false; } WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath, const char* source) { // TODO: Check for freed VM. ObjFn* fn = wrenCompile(vm, sourcePath, source); if (fn == NULL) return WREN_RESULT_COMPILE_ERROR; WREN_PIN(vm, fn); vm->fiber = wrenNewFiber(vm, (Obj*)fn); WREN_UNPIN(vm); if (runInterpreter(vm)) { return WREN_RESULT_SUCCESS; } else { return WREN_RESULT_RUNTIME_ERROR; } }
static void string() { emit_constant(OBJ_VAL(copy_string(parser.previous.start + 1, parser.previous.length - 2))); }