From b936827cc6c25d98df4158cab177e3466f14603e Mon Sep 17 00:00:00 2001 From: CPunch Date: Fri, 4 Dec 2020 00:04:14 -0600 Subject: [PATCH] added support for NaN boxing (8 byte CValues!) --- src/cdebug.c | 2 +- src/cmem.c | 2 +- src/cobj.h | 12 +++---- src/cosmo.h | 6 ++++ src/ctable.c | 14 ++++---- src/cvalue.c | 29 ++++++++-------- src/cvalue.h | 98 +++++++++++++++++++++++++++++++++++++++++----------- src/cvm.c | 68 ++++++++++++++++++------------------ 8 files changed, 148 insertions(+), 83 deletions(-) diff --git a/src/cdebug.c b/src/cdebug.c index 957e9c0..9211057 100644 --- a/src/cdebug.c +++ b/src/cdebug.c @@ -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); diff --git a/src/cmem.c b/src/cmem.c index fd20547..3d2c124 100644 --- a/src/cmem.c +++ b/src/cmem.c @@ -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); } } diff --git a/src/cobj.h b/src/cobj.h index ecbd500..a3c365c 100644 --- a/src/cobj.h +++ b/src/cobj.h @@ -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; diff --git a/src/cosmo.h b/src/cosmo.h index 249abab..fad59bc 100644 --- a/src/cosmo.h +++ b/src/cosmo.h @@ -7,10 +7,16 @@ #include #include +//#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; diff --git a/src/ctable.c b/src/ctable.c index 24fa032..d2e1574 100644 --- a/src/ctable.c +++ b/src/ctable.c @@ -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 diff --git a/src/cvalue.c b/src/cvalue.c index 7f851ee..46d2b42 100644 --- a/src/cvalue.c +++ b/src/cvalue.c @@ -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 ""; case COSMO_TBOOLEAN: return ""; case COSMO_TNUMBER: return ""; - case COSMO_TOBJ: return cosmoO_typeStr(val.val.obj); + case COSMO_TOBJ: return cosmoO_typeStr(cosmoV_readObj(val)); default: return ""; @@ -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: diff --git a/src/cvalue.h b/src/cvalue.h index f14e48e..8274e24 100644 --- a/src/cvalue.h +++ b/src/cvalue.h @@ -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 \ No newline at end of file diff --git a/src/cvm.c b/src/cvm.c index 2051b08..a935654 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -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)); }