added support for NaN boxing (8 byte CValues!)

This commit is contained in:
CPunch 2020-12-04 00:04:14 -06:00
parent 495f1d7272
commit b936827cc6
8 changed files with 148 additions and 83 deletions

View File

@ -98,7 +98,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
int index = readu16Chunk(chunk, offset + 1);
printf("%-16s [%05d] - ", "OP_CLOSURE", index);
CValue val = chunk->constants.values[index];
CObjFunction *cobjFunc = (CObjFunction*)val.val.obj;
CObjFunction *cobjFunc = (CObjFunction*)cosmoV_readObj(val);
offset += 3; // we consumed the opcode + u16
printValue(val);

View File

@ -71,7 +71,7 @@ void tableRemoveWhite(CState *state, CTable *tbl) {
for (int i = 0; i < tbl->capacity; i++) {
CTableEntry *entry = &tbl->table[i];
if (IS_OBJ(entry->key) && !(entry->key.val.obj)->isMarked) { // if the key is a object and it's white (unmarked), remove it from the table
if (IS_OBJ(entry->key) && !(cosmoV_readObj(entry->key))->isMarked) { // if the key is a object and it's white (unmarked), remove it from the table
cosmoT_remove(state, tbl, entry->key);
}
}

View File

@ -90,12 +90,12 @@ typedef struct CObjUpval {
#define IS_METHOD(x) isObjType(x, COBJ_METHOD)
#define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE)
#define cosmoV_readString(x) ((CObjString*)cosmoV_readObj((x)))
#define cosmoV_readObject(x) ((CObjObject*)cosmoV_readObj((x)))
#define cosmoV_readFunction(x) ((CObjFunction*)cosmoV_readObj((x)))
#define cosmoV_readCFunction(x) (((CObjCFunction*)cosmoV_readObj((x)))->cfunc)
#define cosmoV_readMethod(x) ((CObjMethod*)cosmoV_readObj((x)))
#define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj((x)))
#define cosmoV_readString(x) ((CObjString*)cosmoV_readObj(x))
#define cosmoV_readObject(x) ((CObjObject*)cosmoV_readObj(x))
#define cosmoV_readFunction(x) ((CObjFunction*)cosmoV_readObj(x))
#define cosmoV_readCFunction(x) (((CObjCFunction*)cosmoV_readObj(x))->cfunc)
#define cosmoV_readMethod(x) ((CObjMethod*)cosmoV_readObj(x))
#define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj(x))
static inline bool isObjType(CValue val, CObjType type) {
return IS_OBJ(val) && cosmoV_readObj(val)->type == type;

View File

@ -7,10 +7,16 @@
#include <stdbool.h>
#include <stdio.h>
//#define NAN_BOXXED
// forward declare *most* stuff so our headers are cleaner
typedef struct CState CState;
typedef struct CChunk CChunk;
#ifdef NAN_BOXXED
typedef union CValue CValue;
#else
typedef struct CValue CValue;
#endif
// objs
typedef struct CObj CObj;

View File

@ -62,18 +62,20 @@ uint32_t getObjectHash(CObj *obj) {
}
uint32_t getValueHash(CValue *val) {
switch (val->type) {
switch (GET_TYPE(*val)) {
case COSMO_TOBJ:
return getObjectHash(val->val.obj);
return getObjectHash(cosmoV_readObj(*val));
case COSMO_TNUMBER: {
uint32_t buf[sizeof(cosmo_Number)/sizeof(uint32_t)];
if (val->val.num == 0)
cosmo_Number num = cosmoV_readNumber(*val);
if (num == 0)
return 0;
memcpy(buf, &val->val.num, sizeof(buf));
memcpy(buf, &num, sizeof(buf));
for (int i = 0; i < sizeof(cosmo_Number)/sizeof(uint32_t); i++) buf[0] += buf[i];
return buf[0];
}
// TODO: add support for other types
default:
return 0;
@ -227,7 +229,7 @@ CObjString *cosmoT_lookupString(CTable *tbl, const char *str, size_t length, uin
return NULL;
} else if (IS_STRING(entry->key) && cosmoV_readString(entry->key)->length == length && memcmp(cosmoV_readString(entry->key)->str, str, length) == 0) {
// it's a match!
return (CObjString*)entry->key.val.obj;
return (CObjString*)cosmoV_readObj(entry->key);
}
indx = (indx + 1) & (tbl->capacity - 1); // fast mod here too

View File

@ -1,3 +1,4 @@
#include "cosmo.h"
#include "cmem.h"
#include "cvalue.h"
#include "cobj.h"
@ -19,14 +20,14 @@ void appendValArray(CState *state, CValueArray *array, CValue val) {
}
bool cosmoV_equal(CValue valA, CValue valB) {
if (valA.type != valB.type) // are they the same type?
if (GET_TYPE(valA) != GET_TYPE(valB)) // are they the same type?
return false;
// compare
switch (valA.type) {
case COSMO_TBOOLEAN: return valA.val.b == valB.val.b;
case COSMO_TNUMBER: return valA.val.num == valB.val.num;
case COSMO_TOBJ: return cosmoO_equal(valA.val.obj, valB.val.obj);
switch (GET_TYPE(valA)) {
case COSMO_TBOOLEAN: return cosmoV_readBoolean(valA) == cosmoV_readBoolean(valB);
case COSMO_TNUMBER: return cosmoV_readNumber(valA) == cosmoV_readNumber(valB);
case COSMO_TOBJ: return cosmoO_equal(cosmoV_readObj(valA), cosmoV_readObj(valB));
case COSMO_TNIL: return true;
default:
return false;
@ -34,17 +35,17 @@ bool cosmoV_equal(CValue valA, CValue valB) {
}
CObjString *cosmoV_toString(CState *state, CValue val) {
switch (val.type) {
switch (GET_TYPE(val)) {
case COSMO_TNUMBER: {
char buf[32];
int size = snprintf((char*)&buf, 32, "%.14g", val.val.num);
int size = snprintf((char*)&buf, 32, "%.14g", cosmoV_readNumber(val));
return cosmoO_copyString(state, (char*)&buf, size);
}
case COSMO_TBOOLEAN: {
return val.val.b ? cosmoO_copyString(state, "true", 4) : cosmoO_copyString(state, "false", 5);
return cosmoV_readBoolean(val) ? cosmoO_copyString(state, "true", 4) : cosmoO_copyString(state, "false", 5);
}
case COSMO_TOBJ: {
return cosmoO_toString(state, val.val.obj);
return cosmoO_toString(state, cosmoV_readObj(val));
}
case COSMO_TNIL: {
return cosmoO_copyString(state, "nil", 3);
@ -55,11 +56,11 @@ CObjString *cosmoV_toString(CState *state, CValue val) {
}
const char *cosmoV_typeStr(CValue val) {
switch (val.type) {
switch (GET_TYPE(val)) {
case COSMO_TNIL: return "<nil>";
case COSMO_TBOOLEAN: return "<bool>";
case COSMO_TNUMBER: return "<number>";
case COSMO_TOBJ: return cosmoO_typeStr(val.val.obj);
case COSMO_TOBJ: return cosmoO_typeStr(cosmoV_readObj(val));
default:
return "<unkn val>";
@ -67,15 +68,15 @@ const char *cosmoV_typeStr(CValue val) {
}
void printValue(CValue val) {
switch (val.type) {
switch (GET_TYPE(val)) {
case COSMO_TNUMBER:
printf("%g", val.val.num);
printf("%g", cosmoV_readNumber(val));
break;
case COSMO_TBOOLEAN:
printf(cosmoV_readBoolean(val) ? "true" : "false");
break;
case COSMO_TOBJ: {
printObject(val.val.obj);
printObject(cosmoV_readObj(val));
break;
}
case COSMO_TNIL:

View File

@ -4,10 +4,10 @@
#include "cosmo.h"
typedef enum {
COSMO_TNIL,
COSMO_TNUMBER, // number has to be 0 because NaN box
COSMO_TBOOLEAN,
COSMO_TNUMBER,
COSMO_TOBJ
COSMO_TOBJ,
COSMO_TNIL,
} CosmoType;
typedef double cosmo_Number;
@ -15,6 +15,57 @@ typedef double cosmo_Number;
/*
holds primitive cosmo types
*/
#ifdef NAN_BOXXED
/*
NaN box, this is great for performance on x86_64 or ARM64 architectures. If you don't know how this works please reference these
two articles:
https://leonardschuetz.ch/blog/nan-boxing/ and https://piotrduperas.com/posts/nan-boxing/
both are great resources :)
TL;DR: we can store payloads in the NaN value in the IEEE 754 standard.
*/
typedef union CValue {
uint64_t data;
cosmo_Number num;
} CValue;
#define MASK_SIGN_BIT ((uint64_t)1 << 63)
#define MASK_TYPE ((uint64_t)0xf)
#define MASK_PAYLOAD ((uint64_t)0xfffffffffff0)
#define MAKE_PAYLOAD(x) ((uint64_t)(x) << 4)
// The bits that must be set to indicate a quiet NaN.
#define MASK_QUIETNAN ((uint64_t)0x7ffc000000000000)
// sadly this requires a bit more than a simple macro :(
static inline CosmoType GET_TYPE(CValue val) {
// it's not not a number (its a number)
if ((((val.data) & MASK_QUIETNAN) != MASK_QUIETNAN))
return COSMO_TNUMBER;
if ((((val.data) & (MASK_QUIETNAN | MASK_SIGN_BIT)) == (MASK_QUIETNAN | MASK_SIGN_BIT)))
return COSMO_TOBJ;
return (val.data & MASK_TYPE);
}
#define cosmoV_newNumber(x) ((CValue){.num = x})
#define cosmoV_newObj(x) ((CValue){.data = MASK_SIGN_BIT | MASK_QUIETNAN | (uint64_t)(uintptr_t)(x)})
#define cosmoV_newBoolean(x) ((CValue){.data = MASK_QUIETNAN | MAKE_PAYLOAD(x) | COSMO_TBOOLEAN})
#define cosmoV_newNil() ((CValue){.data = MASK_QUIETNAN | COSMO_TNIL})
#define cosmoV_readNumber(x) ((x).num)
#define cosmoV_readBoolean(x) ((bool)((x).data & MASK_PAYLOAD))
#define cosmoV_readObj(x) (((CObj*)(uintptr_t)(((x).data) & ~(MASK_SIGN_BIT | MASK_QUIETNAN))))
#else
/*
Tagged union, this is the best platform independent solution
*/
typedef struct CValue {
CosmoType type;
union {
@ -23,6 +74,29 @@ typedef struct CValue {
CObj *obj;
} val;
} CValue;
#define GET_TYPE(x) ((x).type)
// create CValues
#define cosmoV_newNumber(x) ((CValue){COSMO_TNUMBER, {.num = (x)}})
#define cosmoV_newBoolean(x) ((CValue){COSMO_TBOOLEAN, {.b = (x)}})
#define cosmoV_newObj(x) ((CValue){COSMO_TOBJ, {.obj = (CObj*)(x)}})
#define cosmoV_newNil() ((CValue){COSMO_TNIL, {.num = 0}})
// read CValues
#define cosmoV_readNumber(x) ((cosmo_Number)(x).val.num)
#define cosmoV_readBoolean(x) ((bool)(x).val.b)
#define cosmoV_readObj(x) ((CObj*)(x).val.obj)
#endif
#define IS_NUMBER(x) (GET_TYPE(x) == COSMO_TNUMBER)
#define IS_BOOLEAN(x) (GET_TYPE(x) == COSMO_TBOOLEAN)
#define IS_NIL(x) (GET_TYPE(x) == COSMO_TNIL)
#define IS_OBJ(x) (GET_TYPE(x) == COSMO_TOBJ)
typedef CValue* StkPtr;
typedef struct CValueArray {
@ -40,22 +114,4 @@ COSMO_API bool cosmoV_equal(CValue valA, CValue valB);
COSMO_API CObjString *cosmoV_toString(CState *state, CValue val);
COSMO_API const char *cosmoV_typeStr(CValue val); // return constant char array for corresponding type
#define IS_NUMBER(x) (x.type == COSMO_TNUMBER)
#define IS_BOOLEAN(x) (x.type == COSMO_TBOOLEAN)
#define IS_NIL(x) (x.type == COSMO_TNIL)
#define IS_OBJ(x) (x.type == COSMO_TOBJ)
// create CValues
#define cosmoV_newNumber(x) ((CValue){COSMO_TNUMBER, {.num = x}})
#define cosmoV_newBoolean(x) ((CValue){COSMO_TBOOLEAN, {.b = x}})
#define cosmoV_newObj(x) ((CValue){COSMO_TOBJ, {.obj = (CObj*)x}})
#define cosmoV_newNil() ((CValue){COSMO_TNIL, {.num = 0}})
// read CValues
#define cosmoV_readNumber(x) ((cosmo_Number)x.val.num)
#define cosmoV_readBoolean(x) ((bool)x.val.b)
#define cosmoV_readObj(x) ((CObj*)x.val.obj)
#endif

View File

@ -164,26 +164,26 @@ bool invokeMethod(CState* state, CObjObject *obj, CValue func, int args) {
COSMOVMRESULT cosmoV_call(CState *state, int args) {
StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args
if (val->type != COSMO_TOBJ) {
if (GET_TYPE(*val) != COSMO_TOBJ) {
cosmoV_error(state, "Cannot call non-function type %s!", cosmoV_typeStr(*val));
return COSMOVM_RUNTIME_ERR;
}
switch (val->val.obj->type) {
switch (cosmoV_readObj(*val)->type) {
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)(val->val.obj);
CObjClosure *closure = (CObjClosure*)cosmoV_readObj(*val);
if (!call(state, closure, args, 0)) {
return COSMOVM_RUNTIME_ERR;
}
break;
}
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod*)val->val.obj;
CObjMethod *method = (CObjMethod*)cosmoV_readObj(*val);
invokeMethod(state, method->obj, method->func, args);
break;
}
case COBJ_OBJECT: {
CObjObject *protoObj = (CObjObject*)val->val.obj;
CObjObject *protoObj = (CObjObject*)cosmoV_readObj(*val);
CObjObject *newObj = cosmoO_newObject(state);
newObj->proto = protoObj;
CValue ret;
@ -205,7 +205,7 @@ COSMOVMRESULT cosmoV_call(CState *state, int args) {
}
case COBJ_CFUNCTION: {
// it's a C function, so call it
CosmoCFunction cfunc = ((CObjCFunction*)(val->val.obj))->cfunc;
CosmoCFunction cfunc = ((CObjCFunction*)cosmoV_readObj(*val))->cfunc;
callCFunction(state, cfunc, args, 0);
break;
}
@ -218,7 +218,7 @@ COSMOVMRESULT cosmoV_call(CState *state, int args) {
}
static inline bool isFalsey(StkPtr val) {
return val->type == COSMO_TNIL || (val->type == COSMO_TBOOLEAN && !val->val.b);
return IS_NIL(*val) || (IS_BOOLEAN(*val) && !cosmoV_readBoolean(*val));
}
COSMO_API void cosmoV_pushObject(CState *state, int pairs) {
@ -242,12 +242,12 @@ COSMO_API void cosmoV_pushObject(CState *state, int pairs) {
COSMO_API bool cosmoV_getObject(CState *state, CObjObject *object, CValue key, CValue *val) {
if (cosmoO_getObject(state, object, key, val)) {
if (val->type == COSMO_TOBJ ) {
if (val->val.obj->type == COBJ_CLOSURE) { // is it a function? if so, make it a method to the current object
CObjMethod *method = cosmoO_newMethod(state, (CObjClosure*)val->val.obj, object);
if (IS_OBJ(*val)) {
if (cosmoV_readObj(*val)->type == COBJ_CLOSURE) { // is it a function? if so, make it a method to the current object
CObjMethod *method = cosmoO_newMethod(state, (CObjClosure*)cosmoV_readObj(*val), object);
*val = cosmoV_newObj(method);
} else if (val->val.obj->type == COBJ_CFUNCTION) {
CObjMethod *method = cosmoO_newCMethod(state, (CObjCFunction*)val->val.obj, object);
} else if (cosmoV_readObj(*val)->type == COBJ_CFUNCTION) {
CObjMethod *method = cosmoO_newCMethod(state, (CObjCFunction*)cosmoV_readObj(*val), object);
*val = cosmoV_newObj(method);
}
}
@ -261,9 +261,9 @@ COSMO_API bool cosmoV_getObject(CState *state, CObjObject *object, CValue key, C
#define NUMBEROP(typeConst, op) \
StkPtr valA = cosmoV_getTop(state, 1); \
StkPtr valB = cosmoV_getTop(state, 0); \
if (valA->type == COSMO_TNUMBER && valB->type == COSMO_TNUMBER) { \
if (IS_NUMBER(*valA) && IS_NUMBER(*valB)) { \
cosmoV_setTop(state, 2); /* pop the 2 values */ \
cosmoV_pushValue(state, typeConst((valA->val.num) op (valB->val.num))); \
cosmoV_pushValue(state, typeConst(cosmoV_readNumber(*valA) op cosmoV_readNumber(*valB))); \
} else { \
cosmoV_error(state, "Expected numbers, got %s and %s!", cosmoV_typeStr(*valA), cosmoV_typeStr(*valB)); \
} \
@ -393,12 +393,12 @@ bool cosmoV_execute(CState *state) {
StkPtr temp = cosmoV_getTop(state, 1); // after that should be the object
// sanity check
if (temp->type != COSMO_TOBJ || temp->val.obj->type != COBJ_OBJECT) {
if (!IS_OBJ(*temp) || cosmoV_readObj(*temp)->type != COBJ_OBJECT) {
cosmoV_error(state, "Couldn't get from type %s!", cosmoV_typeStr(*temp));
break;
}
CObjObject *object = (CObjObject*)temp->val.obj;
CObjObject *object = (CObjObject*)cosmoV_readObj(*temp);
CValue val; // to hold our value
cosmoV_getObject(state, object, *key, &val);
@ -412,12 +412,12 @@ bool cosmoV_execute(CState *state) {
StkPtr temp = cosmoV_getTop(state, 2); // object is after the key
// sanity check
if (temp->type != COSMO_TOBJ || temp->val.obj->type != COBJ_OBJECT) {
if (!IS_OBJ(*temp) || cosmoV_readObj(*temp)->type != COBJ_OBJECT) {
cosmoV_error(state, "Couldn't set a field on type %s!", cosmoV_typeStr(*temp));
break;
}
CObjObject *object = (CObjObject*)temp->val.obj;
CObjObject *object = (CObjObject*)cosmoV_readObj(*temp);
cosmoO_setObject(state, object, *key, *value);
// pop everything off the stack
@ -430,12 +430,12 @@ bool cosmoV_execute(CState *state) {
StkPtr temp = cosmoV_getTop(state, args+1); // grabs object from stack
// sanity check
if (temp->type != COSMO_TOBJ || temp->val.obj->type != COBJ_OBJECT) {
if (!IS_OBJ(*temp) || cosmoV_readObj(*temp)->type != COBJ_OBJECT) {
cosmoV_error(state, "Couldn't get from non-object type %s!", cosmoV_typeStr(*temp));
break;
}
CObjObject *object = (CObjObject*)temp->val.obj;
CObjObject *object = (CObjObject*)cosmoV_readObj(*temp);
CValue val; // to hold our value
cosmoO_getObject(state, object, *key, &val); // we use cosmoO_getObject instead of the cosmoV_getObject wrapper so we get the raw value from the object instead of the CObjMethod wrapper
@ -471,9 +471,9 @@ bool cosmoV_execute(CState *state) {
case OP_NEGATE: { // pop 1 value off the stack & try to negate
StkPtr val = cosmoV_getTop(state, 0);
if (val->type == COSMO_TNUMBER) {
if (IS_NUMBER(*val)) {
cosmoV_pop(state);
cosmoV_pushNumber(state, -(val->val.num));
cosmoV_pushNumber(state, -(cosmoV_readNumber(*val)));
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
@ -482,12 +482,12 @@ bool cosmoV_execute(CState *state) {
case OP_COUNT: { // pop 1 value off the stack & if it's an object return the ammount of active entries it has
StkPtr temp = cosmoV_getTop(state, 0);
if (temp->type != COSMO_TOBJ || ((CObj*)temp->val.obj)->type != COBJ_OBJECT) {
if (!IS_OBJ(*temp) || cosmoV_readObj(*temp)->type != COBJ_OBJECT) {
cosmoV_error(state, "Expected object, got %s!", cosmoV_typeStr(*temp));
break;
}
CObjObject *obj = (CObjObject*)temp->val.obj;
CObjObject *obj = (CObjObject*)cosmoV_readObj(*temp);
cosmoV_pop(state);
cosmoV_pushNumber(state, cosmoT_count(&obj->tbl)); // pushes the count onto the stack
break;
@ -517,9 +517,9 @@ bool cosmoV_execute(CState *state) {
StkPtr val = &frame->base[indx];
// check that it's a number value
if (val->type == COSMO_TNUMBER) {
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(val->val.num + inc);
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
@ -533,9 +533,9 @@ bool cosmoV_execute(CState *state) {
CValue *val = cosmoT_insert(state, &state->globals, ident);
// check that it's a number value
if (val->type == COSMO_TNUMBER) {
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(val->val.num + inc);
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
@ -548,9 +548,9 @@ bool cosmoV_execute(CState *state) {
CValue *val = frame->closure->upvalues[indx]->val;
// check that it's a number value
if (val->type == COSMO_TNUMBER) {
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(val->val.num + inc);
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
@ -564,21 +564,21 @@ bool cosmoV_execute(CState *state) {
CValue ident = constants[indx]; // grabs identifier
// sanity check
if (temp->type != COSMO_TOBJ || temp->val.obj->type != COBJ_OBJECT) {
if (!IS_OBJ(*temp) || cosmoV_readObj(*temp)->type != COBJ_OBJECT) {
cosmoV_error(state, "Couldn't set a field on non-object type %s!", cosmoV_typeStr(*temp));
break;
}
CObjObject *object = (CObjObject*)temp->val.obj;
CObjObject *object = (CObjObject*)cosmoV_readObj(*temp);
CValue *val = cosmoT_insert(state, &object->tbl, ident);
// pop the object off the stack
cosmoV_pop(state);
// check that it's a number value
if (val->type == COSMO_TNUMBER) {
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(val->val.num + inc);
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}