diff --git a/clox/Makefile b/clox/Makefile index fb72616..67a3eaf 100644 --- a/clox/Makefile +++ b/clox/Makefile @@ -1,9 +1,9 @@ -CC=gcc +CC=clang CC_FLAGS=-Wall -Wextra -Wpedantic -g -O2 ODIR=build -HEADERS=common.h chunk.h compiler.h debug.h lexer.h memory.h table.h object.h value.h vm.h +HEADERS=common.h chunk.h compiler.h debug.h lexer.h memory.h table.h object.h value.h vm.h Makefile _OBJ=chunk.o compiler.o debug.o lexer.o main.o memory.o table.o object.o value.o vm.o OBJ=$(patsubst %,$(ODIR)/%,$(_OBJ)) diff --git a/clox/benches/fibonacci-metric.lox b/clox/benches/fibonacci-metric.lox index 41d99a1..26726c2 100644 --- a/clox/benches/fibonacci-metric.lox +++ b/clox/benches/fibonacci-metric.lox @@ -1,12 +1,12 @@ // This script mostly measures how efficient our function calls are. // The first three lines should look like: -// fibonacci(32): -// 2.17831e+06 +// fibonacci(35): +// 9.22746e+06 // Time taken (seconds): -// The last line indicates how long it took to run (typically about 0.81 seconds -// if compiled with -Og, or 0.43 seconds if compiled with -O2). +// The last line indicates how long it took to run (typically about 1.6-1.7 +// seconds if compiled with -O2 or -O3). fun fibonacci(n) { if (n < 2) { diff --git a/clox/benches/zoo.lox b/clox/benches/zoo.lox index 9616dd3..02cc977 100644 --- a/clox/benches/zoo.lox +++ b/clox/benches/zoo.lox @@ -1,3 +1,11 @@ +// This bench tests the calling of methods and accessing of hashtables. + +// The first line outputs the amount of time it takes to run, usually +// about 5 seconds if compiled with -O2 or -O3 (with -O2 a little +// faster) +// The second line should be: +// 1e+08 + class Zoo { init() { this.aardvark = 1; diff --git a/clox/chunk.c b/clox/chunk.c index 248422e..073eeee 100644 --- a/clox/chunk.c +++ b/clox/chunk.c @@ -44,7 +44,7 @@ uint64_t addConstant(Chunk*chunk, Value value) { if (IS_STRING(value)) { Value index; if (tableGet(&chunk->stringConstants, AS_STRING(value), &index)) { - return (uint64_t) index.as.integer; + return (uint64_t)AS_INTEGER(index); } else { writeValueArray(&chunk->constants, value); uint64_t index = chunk->constants.length - 1; diff --git a/clox/common.h b/clox/common.h index 2fd2635..85d3626 100644 --- a/clox/common.h +++ b/clox/common.h @@ -5,11 +5,15 @@ #include #include +// Various debug printouts that may be useful // #define DEBUG_PRINT_CODE // #define DEBUG_TRACE_EXECUTION // #define DEBUG_STRING_INTERNING -#define DEBUG_RUNTIME_CHECKS // #define DEBUG_STRESS_GC // #define DEBUG_LOG_GC +// Changing runtime behavior +#define DEBUG_RUNTIME_CHECKS +#define NAN_BOXING + #endif diff --git a/clox/debug.c b/clox/debug.c index 8229af4..a7179f3 100644 --- a/clox/debug.c +++ b/clox/debug.c @@ -52,7 +52,7 @@ static int closureInstruction(Chunk* chunk, int offset) { printf("%-21s 0x%04x '", "OP_CLOSURE", constant); printValue(chunk->constants.values[constant]); printf("'\n"); - ObjFunction* function = (ObjFunction*)chunk->constants.values[constant].as.obj; + ObjFunction* function = AS_FUNCTION(chunk->constants.values[constant]); for (int j=0; j < function->upvalueCount; j++) { int isLocal = chunk->code[offset++]; int index = chunk->code[offset++]; @@ -65,7 +65,7 @@ static int longClosureInstruction(Chunk* chunk, int offset) { printf("%-21s 0x%04x '", "OP_CLOSURE_LONG", constant); printValue(chunk->constants.values[constant]); printf("'\n"); - ObjFunction* function = (ObjFunction*)chunk->constants.values[constant].as.obj; + ObjFunction* function = AS_FUNCTION(chunk->constants.values[constant]); for (int j=0; j < function->upvalueCount; j++) { int isLocal = chunk->code[offset++]; int index = chunk->code[offset++]; diff --git a/clox/gmon.out b/clox/gmon.out index e0d9af0..43b6f1a 100644 Binary files a/clox/gmon.out and b/clox/gmon.out differ diff --git a/clox/memory.c b/clox/memory.c index a5c5aa9..c69c56c 100644 --- a/clox/memory.c +++ b/clox/memory.c @@ -101,7 +101,7 @@ static void markValue(Value value) { if (!IS_OBJ(value)) { return; } - markObject(value.as.obj); + markObject(AS_OBJECT(value)); } static void markTable(Table* table) { for (uint i=0; i < table->capacity; i++) { diff --git a/clox/object.h b/clox/object.h index 0a813d6..bacbf62 100644 --- a/clox/object.h +++ b/clox/object.h @@ -86,7 +86,7 @@ ObjClass* newClass(ObjString* name); ObjInstance* newInstance(ObjClass* class); ObjBoundMethod* newBoundMethod(Value receiver, ObjClosure* method); -#define OBJ_TYPE(value) (value.as.obj->type) +#define OBJ_TYPE(value) (AS_OBJECT(value)->type) #define IS_STRING(value) isObjType(value, OBJ_STRING) #define IS_FUNCTION(value) isObjType(value, OBJ_FUNCTION) #define IS_NATIVE(value) isObjType(value, OBJ_NATIVE) @@ -96,9 +96,17 @@ ObjBoundMethod* newBoundMethod(Value receiver, ObjClosure* method); #define IS_INSTANCE(value) isObjType(value, OBJ_INSTANCE) #define IS_BOUND_METHOD(value) isObjType(value, OBJ_BOUND_METHOD) static inline bool isObjType(Value value, ObjType type) { - return IS_OBJ(value) && value.as.obj->type == type; + return IS_OBJ(value) && OBJ_TYPE(value) == type; } +#define AS_FUNCTION(value) ((ObjFunction*)AS_OBJECT(value)) +#define AS_NATIVE(value) ((ObjNative*)AS_OBJECT(value)) +#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJECT(value)) +#define AS_UPVALUE(value) ((ObjUpvalue*)AS_OBJECT(value)) +#define AS_CLASS(value) ((ObjClass*)AS_OBJECT(value)) +#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJECT(value)) +#define AS_BOUND_METHOD(value) ((ObjBoundMethod*)AS_OBJECT(value)) + void printObject(Obj*); bool objectsEqual(Obj*, Obj*); diff --git a/clox/value.c b/clox/value.c index 5ac2954..3e5aa23 100644 --- a/clox/value.c +++ b/clox/value.c @@ -26,28 +26,35 @@ void freeValueArray(ValueArray* values) { } void printValue(Value value) { - switch (value.type) { - case VAL_NUMBER: printf("%g", value.as.number); break; - case VAL_INTEGER: printf("%ld", value.as.integer); break; - case VAL_BOOL: printf(value.as.boolean ? "true" : "false"); break; - case VAL_NIL: printf("nil"); break; - case VAL_OBJ: printObject(value.as.obj); break; + if (IS_NUMBER(value)) { + printf("%g", AS_NUMBER(value)); + } else if (IS_INTEGER(value)) { + printf("%d", AS_INTEGER(value)); + } else if (IS_BOOL(value)) { + printf(AS_BOOL(value) ? "true" : "false"); + } else if (IS_NIL(value)) { + printf("nil"); + } else if (IS_OBJ(value)) { + printObject(AS_OBJECT(value)); } } bool truthy(Value value) { - switch (value.type) { - case VAL_NIL: return false; - case VAL_BOOL: return value.as.boolean; - case VAL_NUMBER: - case VAL_INTEGER: - case VAL_OBJ: - return true; + if (IS_NIL(value)) { + return false; + } else if (IS_BOOL(value)) { + return AS_BOOL(value); + } else { + return true; } - return false; } bool valuesEqual(Value a, Value b) { +#ifdef NAN_BOXING + // Note: this says NaN == NaN is true, which goes against IEE 754 + // But the performance savings are worth it + return a == b; +#else if (a.type != b.type) { return false; } @@ -58,5 +65,5 @@ bool valuesEqual(Value a, Value b) { case VAL_INTEGER: return a.as.integer == b.as.integer; case VAL_OBJ: return a.as.obj == b.as.obj; } - return false; +#endif } diff --git a/clox/value.h b/clox/value.h index dd6b96a..9f68f1e 100644 --- a/clox/value.h +++ b/clox/value.h @@ -4,9 +4,54 @@ #include "common.h" #include "memory.h" +#ifdef NAN_BOXING +#include +#endif + typedef struct sObj Obj; typedef struct sObjString ObjString; +#ifdef NAN_BOXING + +#define QNAN ((uint64_t)0x7ffc000000000000) +#define SIGN_BIT ((uint64_t)0x8000000000000000) +#define TAG_INTEGER 1 +#define TAG_NIL 0 +#define TAG_TRUE 2 +#define TAG_FALSE 3 + +typedef uint64_t Value; + +static inline Value numToValue(double num) { + Value value; + memcpy(&value, &num, sizeof(double)); + return value; +} +#define NUMBER_VAL(num) numToValue(num) +#define NIL_VAL() ((Value)(uint64_t)(QNAN | TAG_NIL)) +#define FALSE_VAL() ((Value)(uint64_t)(QNAN | TAG_FALSE)) +#define TRUE_VAL() ((Value)(uint64_t)(QNAN | TAG_TRUE)) +#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL() : FALSE_VAL()) +#define INTEGER_VAL(integer) ((Value)(uint64_t)(QNAN | TAG_INTEGER | ((uint64_t)integer << 2))) +#define OBJ_VAL(obj) ((Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj))) + +#define IS_NUMBER(value) (((value) & QNAN) != QNAN) +#define IS_NIL(value) ((value) == NIL_VAL()) +#define IS_BOOL(value) (((value) | 1) == FALSE_VAL()) +#define IS_INTEGER(value) (((value) & (QNAN | TAG_INTEGER)) == (QNAN | TAG_INTEGER)) +#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT)) + +static inline double valueToNum(Value value) { + double num; + memcpy(&num, &value, sizeof(Value)); + return num; +} +#define AS_NUMBER(value) valueToNum(value) +#define AS_BOOL(value) ((value) == TRUE_VAL()) +#define AS_INTEGER(value) ((uint32_t)(((value) & ~(QNAN | TAG_INTEGER)) >> 2)) +#define AS_OBJECT(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN))) + +#else typedef enum { VAL_NIL, VAL_BOOL, @@ -20,11 +65,33 @@ typedef struct { union { bool boolean; double number; - int64_t integer; + int32_t integer; Obj* obj; } as; } Value; +#define BOOL_VAL(value) ((Value){ VAL_BOOL, { .boolean = value } }) +#define NIL_VAL() ((Value){ VAL_NIL, { .number = 0 } }) +#define NUMBER_VAL(value) ((Value){ VAL_NUMBER, { .number = value } }) +#define INTEGER_VAL(value) ((Value){ VAL_NUMBER, { .integer = value } }) +#define OBJ_VAL(object) ((Value){ VAL_OBJ, { .obj = (Obj*)(object) } }) + +#define IS_BOOL(value) ((value).type == VAL_BOOL) +#define IS_NIL(value) ((value).type == VAL_NIL) +#define IS_NUMBER(value) ((value).type == VAL_NUMBER) +#define IS_INTEGER(value) ((value).type == VAL_INTEGER) +#define IS_OBJ(value) ((value).type == VAL_OBJ) + +#define AS_BOOL(value) ((value).as.boolean) +#define AS_NUMBER(value) ((value).as.number) +#define AS_INTEGER(value) ((value).as.integer) +#define AS_OBJECT(value) ((value).as.obj) + +#endif + +#define AS_STRING(value) ((ObjString*)AS_OBJECT(value)) +#define AS_CSTRING(value) (AS_STRING(value)->chars) + typedef struct { Value* values; int length; @@ -41,19 +108,4 @@ bool valuesEqual(Value, Value); void printValue(Value value); -#define BOOL_VAL(value) ((Value){ VAL_BOOL, { .boolean = value } }) -#define NIL_VAL() ((Value){ VAL_NIL, { .number = 0 } }) -#define NUMBER_VAL(value) ((Value){ VAL_NUMBER, { .number = value } }) -#define INTEGER_VAL(value) ((Value){ VAL_NUMBER, { .integer = value } }) -#define OBJ_VAL(object) ((Value){ VAL_OBJ, { .obj = (Obj*)(object) } }) - -#define IS_BOOL(value) ((value).type == VAL_BOOL) -#define IS_NIL(value) ((value).type == VAL_NIL) -#define IS_NUMBER(value) ((value).type == VAL_NUMBER) -#define IS_INTEGER(value) ((value).type == VAL_INTEGER) -#define IS_OBJ(value) ((value).type == VAL_OBJ) - -#define AS_STRING(value) ((ObjString*)value.as.obj) -#define AS_CSTRING(value) (((ObjString*)value.as.obj)->chars) - #endif diff --git a/clox/vm.c b/clox/vm.c index 4c7159b..8a4a0aa 100644 --- a/clox/vm.c +++ b/clox/vm.c @@ -78,7 +78,7 @@ static void defineNative(const char* name, NativeFn function) { // using them stackPush(OBJ_VAL(copyString(name, (int)strlen(name)))); stackPush(OBJ_VAL(newNative(function))); - tableSet(&vm.globals, (ObjString*)vm.stack[0].as.obj, vm.stack[1]); + tableSet(&vm.globals, AS_STRING(vm.stack[0]), vm.stack[1]); stackPop(); stackPop(); } @@ -100,20 +100,20 @@ static bool call(ObjClosure* closure, int argCount) { static bool callValue(Value callee, int argCount) { if (IS_OBJ(callee)) { switch (OBJ_TYPE(callee)) { - case OBJ_CLOSURE: return call((ObjClosure*)callee.as.obj, argCount); + case OBJ_CLOSURE: return call(AS_CLOSURE(callee), argCount); case OBJ_NATIVE: { - NativeFn native = ((ObjNative*)callee.as.obj)->function; + NativeFn native = AS_NATIVE(callee)->function; Value result = native(argCount, vm.stackTop - argCount); vm.stackTop -= argCount + 1; stackPush(result); return true; } case OBJ_CLASS: { - ObjClass* class = (ObjClass*)callee.as.obj; + ObjClass* class = AS_CLASS(callee); vm.stackTop[-argCount-1] = OBJ_VAL(newInstance(class)); Value initializer; if (tableGet(&class->methods, vm.initString, &initializer)) { - return call((ObjClosure*)initializer.as.obj, argCount); + return call(AS_CLOSURE(initializer), argCount); } else if (argCount != 0) { runtimeError("Expected 0 arguments but got %d.", argCount); return false; @@ -122,7 +122,7 @@ static bool callValue(Value callee, int argCount) { } } case OBJ_BOUND_METHOD: { - ObjBoundMethod* bound = (ObjBoundMethod*)callee.as.obj; + ObjBoundMethod* bound = AS_BOUND_METHOD(callee); vm.stackTop[-argCount-1] = bound->receiver; return call(bound->method, argCount); } @@ -203,7 +203,7 @@ static void closeUpvalues(Value* last) { static void defineMethod(ObjString* name) { Value method = stackPeek(0); - ObjClass* class = (ObjClass*)stackPeek(1).as.obj; + ObjClass* class = AS_CLASS(stackPeek(1)); tableSet(&class->methods, name, method); stackPop(); } @@ -213,7 +213,7 @@ static bool bindMethod(ObjClass* class, ObjString* name) { if (!tableGet(&class->methods, name, &method)) { return false; } - ObjBoundMethod* bound = newBoundMethod(stackPeek(0), (ObjClosure*)method.as.obj); + ObjBoundMethod* bound = newBoundMethod(stackPeek(0), AS_CLOSURE(method)); stackPop(); stackPush(OBJ_VAL(bound)); return true; @@ -225,7 +225,7 @@ static bool invokeFromClass(ObjClass* class, ObjString* name, int argCount) { runtimeError("Undefined property '%s'.", name->chars); return false; } - return call((ObjClosure*)method.as.obj, argCount); + return call(AS_CLOSURE(method), argCount); } static bool invoke(ObjString* name, int argCount) { @@ -234,7 +234,7 @@ static bool invoke(ObjString* name, int argCount) { runtimeError("Only instances have methods."); return false; } - ObjInstance* instance = (ObjInstance*)receiver.as.obj; + ObjInstance* instance = AS_INSTANCE(receiver); Value value; if (tableGet(&instance->fields, name, &value)) { vm.stackTop[-argCount-1] = value; @@ -254,8 +254,8 @@ static bool invoke(ObjString* name, int argCount) { runtimeError("Operands must be numbers."); \ return INTERPRET_RUNTIME_ERROR; \ } \ - double b = stackPop().as.number; \ - double a = stackPop().as.number; \ + double b = AS_NUMBER(stackPop()); \ + double a = AS_NUMBER(stackPop()); \ stackPush(valueType(a op b)); \ } while(false) static InterpretResult run() { @@ -311,7 +311,7 @@ static InterpretResult run() { runtimeError("Unary negation operand must be a number"); return INTERPRET_RUNTIME_ERROR; } - stackPush(NUMBER_VAL(-stackPop().as.number)); + stackPush(NUMBER_VAL(-AS_NUMBER(stackPop()))); break; } case OP_NOT: { @@ -334,7 +334,7 @@ static InterpretResult run() { } else if (IS_NUMBER(stackPeek(0)) && IS_NUMBER(stackPeek(1))) { Value b = stackPop(); Value a = stackPop(); - stackPush(NUMBER_VAL(a.as.number + b.as.number)); + stackPush(NUMBER_VAL(AS_NUMBER(a) + AS_NUMBER(b))); } else { runtimeError("Addition operands must be either two strings or two numbers"); return INTERPRET_RUNTIME_ERROR; @@ -452,7 +452,7 @@ static InterpretResult run() { } case OP_NOP: break; case OP_CLOSURE: { - ObjFunction* function = (ObjFunction*)READ_CONSTANT().as.obj; + ObjFunction* function = AS_FUNCTION(READ_CONSTANT()); ObjClosure* closure = newClosure(function); stackPush(OBJ_VAL(closure)); for (int i=0; iupvalueCount; i++) { @@ -482,11 +482,11 @@ static InterpretResult run() { break; } case OP_CLASS: { - stackPush(OBJ_VAL(newClass((ObjString*)READ_CONSTANT().as.obj))); + stackPush(OBJ_VAL(newClass(AS_STRING(READ_CONSTANT())))); break; } case OP_CLASS_LONG: { - stackPush(OBJ_VAL(newClass((ObjString*)READ_CONSTANT_LONG().as.obj))); + stackPush(OBJ_VAL(newClass(AS_STRING(READ_CONSTANT_LONG())))); break; } case OP_GET_PROPERTY: { @@ -494,8 +494,8 @@ static InterpretResult run() { runtimeError("Only instances have properties"); return INTERPRET_RUNTIME_ERROR; } - ObjInstance* instance = (ObjInstance*)stackPeek(0).as.obj; - ObjString* name = (ObjString*)READ_CONSTANT().as.obj; + ObjInstance* instance = AS_INSTANCE(stackPeek(0)); + ObjString* name = AS_STRING(READ_CONSTANT()); Value value; if (tableGet(&instance->fields, name, &value)) { stackPop(); @@ -512,8 +512,8 @@ static InterpretResult run() { runtimeError("Only instances have properties"); return INTERPRET_RUNTIME_ERROR; } - ObjInstance* instance = (ObjInstance*)stackPeek(0).as.obj; - ObjString* name = (ObjString*)READ_CONSTANT_LONG().as.obj; + ObjInstance* instance = AS_INSTANCE(stackPeek(0)); + ObjString* name = AS_STRING(READ_CONSTANT_LONG()); Value value; if (tableGet(&instance->fields, name, &value)) { stackPop(); @@ -529,8 +529,8 @@ static InterpretResult run() { runtimeError("Only instances have properties"); return INTERPRET_RUNTIME_ERROR; } - ObjInstance* instance = (ObjInstance*)stackPeek(1).as.obj; - ObjString* name = (ObjString*)READ_CONSTANT().as.obj; + ObjInstance* instance = AS_INSTANCE(stackPeek(1)); + ObjString* name = AS_STRING(READ_CONSTANT()); Value value = stackPop(); tableSet(&instance->fields, name, value); stackPop(); @@ -542,8 +542,8 @@ static InterpretResult run() { runtimeError("Only instances have properties"); return INTERPRET_RUNTIME_ERROR; } - ObjInstance* instance = (ObjInstance*)stackPeek(1).as.obj; - ObjString* name = (ObjString*)READ_CONSTANT_LONG().as.obj; + ObjInstance* instance = AS_INSTANCE(stackPeek(1)); + ObjString* name = AS_STRING(READ_CONSTANT_LONG()); Value value = stackPop(); tableSet(&instance->fields, name, value); stackPop(); @@ -551,15 +551,15 @@ static InterpretResult run() { break; } case OP_METHOD: { - defineMethod((ObjString*)READ_CONSTANT().as.obj); + defineMethod(AS_STRING(READ_CONSTANT())); break; } case OP_METHOD_LONG: { - defineMethod((ObjString*)READ_CONSTANT_LONG().as.obj); + defineMethod(AS_STRING(READ_CONSTANT_LONG())); break; } case OP_INVOKE: { - ObjString* method = (ObjString*)READ_CONSTANT().as.obj; + ObjString* method = AS_STRING(READ_CONSTANT()); int argCount = READ_BYTE(); if (!invoke(method, argCount)) { return INTERPRET_RUNTIME_ERROR; @@ -573,31 +573,31 @@ static InterpretResult run() { runtimeError("Superclass must be a class"); return INTERPRET_RUNTIME_ERROR; } - ObjClass* subclass = (ObjClass*)stackPeek(0).as.obj; - copyTable(&((ObjClass*)superclass.as.obj)->methods, &subclass->methods); + ObjClass* subclass = AS_CLASS(stackPeek(0)); + copyTable(&AS_CLASS(superclass)->methods, &subclass->methods); stackPop(); break; } case OP_GET_SUPER: { - ObjString* name = (ObjString*)READ_CONSTANT().as.obj; - ObjClass* superclass = (ObjClass*)stackPop().as.obj; + ObjString* name = AS_STRING(READ_CONSTANT()); + ObjClass* superclass = AS_CLASS(stackPop()); if (!bindMethod(superclass, name)) { return INTERPRET_RUNTIME_ERROR; } break; } case OP_GET_SUPER_LONG: { - ObjString* name = (ObjString*)READ_CONSTANT_LONG().as.obj; - ObjClass* superclass = (ObjClass*)stackPop().as.obj; + ObjString* name = AS_STRING(READ_CONSTANT_LONG()); + ObjClass* superclass = AS_CLASS(stackPop()); if (!bindMethod(superclass, name)) { return INTERPRET_RUNTIME_ERROR; } break; } case OP_SUPER_INVOKE: { - ObjString* method = (ObjString*)READ_CONSTANT().as.obj; + ObjString* method = AS_STRING(READ_CONSTANT()); int argCount = READ_BYTE(); - ObjClass* superclass = (ObjClass*)stackPop().as.obj; + ObjClass* superclass = AS_CLASS(stackPop()); if (!invokeFromClass(superclass, method, argCount)) { return INTERPRET_RUNTIME_ERROR; } @@ -605,9 +605,9 @@ static InterpretResult run() { break; } case OP_SUPER_INVOKE_LONG: { - ObjString* method = (ObjString*)READ_CONSTANT_LONG().as.obj; + ObjString* method = AS_STRING(READ_CONSTANT_LONG()); int argCount = READ_BYTE(); - ObjClass* superclass = (ObjClass*)stackPop().as.obj; + ObjClass* superclass = AS_CLASS(stackPop()); if (!invokeFromClass(superclass, method, argCount)) { return INTERPRET_RUNTIME_ERROR; }