Skip to content

Commit

Permalink
Add NaN Boxing optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
JarredAllen committed Aug 25, 2020
1 parent 4f0e488 commit 1ddf4ae
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 82 deletions.
4 changes: 2 additions & 2 deletions 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))

Expand Down
8 changes: 4 additions & 4 deletions 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) {
Expand Down
8 changes: 8 additions & 0 deletions 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;
Expand Down
2 changes: 1 addition & 1 deletion clox/chunk.c
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion clox/common.h
Expand Up @@ -5,11 +5,15 @@
#include <stddef.h>
#include <stdint.h>

// 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
4 changes: 2 additions & 2 deletions clox/debug.c
Expand Up @@ -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++];
Expand All @@ -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++];
Expand Down
Binary file modified clox/gmon.out
Binary file not shown.
2 changes: 1 addition & 1 deletion clox/memory.c
Expand Up @@ -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++) {
Expand Down
12 changes: 10 additions & 2 deletions clox/object.h
Expand Up @@ -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)
Expand All @@ -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*);
Expand Down
37 changes: 22 additions & 15 deletions clox/value.c
Expand Up @@ -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;
}
Expand All @@ -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
}
84 changes: 68 additions & 16 deletions clox/value.h
Expand Up @@ -4,9 +4,54 @@
#include "common.h"
#include "memory.h"

#ifdef NAN_BOXING
#include <string.h>
#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,
Expand All @@ -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;
Expand All @@ -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

0 comments on commit 1ddf4ae

Please sign in to comment.