From c510c9aebfe5be411837e3b71797a65ab1546ace Mon Sep 17 00:00:00 2001 From: CPunch Date: Sun, 10 Jan 2021 14:38:53 -0600 Subject: [PATCH] Minor table refactor, added cosmoV_compileString and loadstring() to baselib cosmoV_compileString is recommended since it'll push the result ( or ) onto the stack. also, fixed some GC-related bugs, so yay! --- src/cbaselib.c | 61 ++++++++++++++++++++++++++++++++++++-------------- src/cmem.c | 6 +++-- src/cparse.c | 9 ++++---- src/cstate.c | 14 ++++++------ src/ctable.c | 44 +++++++++++++++++++++--------------- src/ctable.h | 5 ++--- src/cvm.c | 25 +++++++++++++++++++-- src/cvm.h | 9 ++++++++ src/main.c | 8 +++---- 9 files changed, 122 insertions(+), 59 deletions(-) diff --git a/src/cbaselib.c b/src/cbaselib.c index 02ff896..54b8a5f 100644 --- a/src/cbaselib.c +++ b/src/cbaselib.c @@ -57,6 +57,24 @@ int cosmoB_pcall(CState *state, int nargs, CValue *args) { return 2; } +int cosmoB_loadstring(CState *state, int nargs, CValue *args) { + if (nargs < 1) { + cosmoV_error(state, "loadstring() expected 1 argument, got %d!", nargs); + return 0; + } + + if (!IS_STRING(args[0])) { + cosmoV_typeError(state, "loadstring()", "", "%s", cosmoV_typeStr(args[0])); + return 0; + } + + CObjString *str = cosmoV_readString(args[0]); + bool res = cosmoV_compileString(state, str->str, ""); + + cosmo_insert(state, 0, cosmoV_newBoolean(res)); + return 2; // , or +} + // ================================================================ [STRING.*] ================================================================ // string.sub @@ -103,23 +121,32 @@ int cosmoB_sSub(CState *state, int nargs, CValue *args) { } void cosmoB_loadLibrary(CState *state) { - // print - cosmoV_pushString(state, "print"); - cosmoV_pushCFunction(state, cosmoB_print); + const char *identifiers[] = { + "print", + "assert", + "type", + "pcall", + "loadstring" + }; - // assert (for unit testing) - cosmoV_pushString(state, "assert"); - cosmoV_pushCFunction(state, cosmoB_assert); + CosmoCFunction baseLib[] = { + cosmoB_print, + cosmoB_assert, + cosmoB_type, + cosmoB_pcall, + cosmoB_loadstring + }; - // type - cosmoV_pushString(state, "type"); - cosmoV_pushCFunction(state, cosmoB_type); + int i; + for (i = 0; i < sizeof(identifiers)/sizeof(identifiers[0]); i++) { + cosmoV_pushString(state, identifiers[i]); + cosmoV_pushCFunction(state, baseLib[i]); + } - // pcall - cosmoV_pushString(state, "pcall"); - cosmoV_pushCFunction(state, cosmoB_pcall); + // register all the pushed c functions and the strings as globals + cosmoV_register(state, i); - // string. + // string.* cosmoV_pushString(state, "string"); // sub @@ -127,19 +154,19 @@ void cosmoB_loadLibrary(CState *state) { cosmoV_pushCFunction(state, cosmoB_sSub); cosmoV_makeTable(state, 1); - // string. + // string.* - // register these all to the global table - cosmoV_register(state, 5); + // register "string" to that table + cosmoV_register(state, 1); // make string object for CObjStrings - // sub cosmoV_pushString(state, "sub"); cosmoV_pushCFunction(state, cosmoB_sSub); cosmoV_makeObject(state, 1); + // grab the object from the stack and set the base protoObject StkPtr obj = cosmoV_pop(state); state->protoObjects[COBJ_STRING] = cosmoV_readObject(*obj); } diff --git a/src/cmem.c b/src/cmem.c index 271d012..0ed1141 100644 --- a/src/cmem.c +++ b/src/cmem.c @@ -55,7 +55,8 @@ void markTable(CState *state, CTable *tbl) { if (tbl->table == NULL) // table is still being initialized return; - for (int i = 0; i < tbl->capacity; i++) { + int cap = tbl->capacityMask + 1; + for (int i = 0; i < cap; i++) { CTableEntry *entry = &tbl->table[i]; markValue(state, entry->key); markValue(state, entry->val); @@ -67,7 +68,8 @@ void tableRemoveWhite(CState *state, CTable *tbl) { if (tbl->table == NULL) // table is still being initialized return; - for (int i = 0; i < tbl->capacity; i++) { + int cap = tbl->capacityMask + 1; + for (int i = 0; i < cap; i++) { CTableEntry *entry = &tbl->table[i]; 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/cparse.c b/src/cparse.c index fc05295..1a937e9 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -1587,20 +1587,19 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source, const char endCompiler(&parser); freeParseState(&parser); - // the VM still expects a result on the stack - cosmoV_pushValue(state, cosmoV_newNil()); cosmoM_unfreezeGC(state); return NULL; } CObjFunction* resFunc = compiler.function; - // VM expects the closure on the stack :P (we do this before ending the compiler so our GC doesn't free it) - cosmoV_pushValue(state, cosmoV_newObj((CObj*)cosmoO_newClosure(state, resFunc))); - // finally free out parser states endCompiler(&parser); freeParseState(&parser); + + // push the funciton onto the stack so if we cause an GC event, it won't be free'd + cosmoV_pushValue(state, cosmoV_newObj(resFunc)); cosmoM_unfreezeGC(state); + cosmoV_pop(state); return resFunc; } diff --git a/src/cstate.c b/src/cstate.c index 4824218..3f32e33 100644 --- a/src/cstate.c +++ b/src/cstate.c @@ -33,14 +33,18 @@ CState *cosmoV_newState() { state->openUpvalues = NULL; state->error = NULL; - - cosmoT_initTable(state, &state->strings, 8); // init string table - cosmoT_initTable(state, &state->globals, 8); // init global table + + // set default proto objects + for (int i = 0; i < COBJ_MAX; i++) + state->protoObjects[i] = NULL; // first, set all strings to NULL so our GC doesn't read garbage data for (int i = 0; i < ISTRING_MAX; i++) state->iStrings[i] = NULL; + cosmoT_initTable(state, &state->strings, 8); // init string table + cosmoT_initTable(state, &state->globals, 8); // init global table + // setup all strings used by the VM state->iStrings[ISTRING_INIT] = cosmoO_copyString(state, "__init", 6); state->iStrings[ISTRING_TOSTRING] = cosmoO_copyString(state, "__tostring", 10); @@ -61,10 +65,6 @@ CState *cosmoV_newState() { // set the IString flags for (int i = 0; i < ISTRING_MAX; i++) state->iStrings[i]->isIString = true; - - // set default proto objects - for (int i = 0; i < COBJ_MAX; i++) - state->protoObjects[i] = NULL; return state; } diff --git a/src/ctable.c b/src/ctable.c index 867432a..bf6432c 100644 --- a/src/ctable.c +++ b/src/ctable.c @@ -24,21 +24,24 @@ unsigned int nextPow2(unsigned int x) { } void cosmoT_initTable(CState *state, CTable *tbl, int startCap) { - tbl->capacity = startCap != 0 ? startCap : ARRAY_START; // sanity check :P + startCap = startCap != 0 ? startCap : ARRAY_START; // sanity check :P + + tbl->capacityMask = startCap - 1; tbl->count = 0; tbl->tombstones = 0; tbl->table = NULL; // to let out GC know we're initalizing - tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * tbl->capacity); + tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * startCap); // init everything to NIL - for (int i = 0; i < tbl->capacity; i++) { + for (int i = 0; i < startCap; i++) { tbl->table[i].key = cosmoV_newNil(); tbl->table[i].val = cosmoV_newNil(); } } void cosmoT_addTable(CState *state, CTable *from, CTable *to) { - for (int i = 0; i < from->capacity; i++) { + int cap = from->capacityMask + 1; + for (int i = 0; i < cap; i++) { CTableEntry *entry = &from->table[i]; if (!(IS_NIL(entry->key))) { @@ -49,7 +52,7 @@ void cosmoT_addTable(CState *state, CTable *from, CTable *to) { } void cosmoT_clearTable(CState *state, CTable *tbl) { - cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity); + cosmoM_freearray(state, CTableEntry, tbl->table, (tbl->capacityMask + 1)); } uint32_t getObjectHash(CObj *obj) { @@ -57,7 +60,7 @@ uint32_t getObjectHash(CObj *obj) { case COBJ_STRING: return ((CObjString*)obj)->hash; default: - return 0; + return (uint32_t)obj; // just "hash" the pointer } } @@ -115,13 +118,16 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin size_t size = sizeof(CTableEntry) * newCapacity; int cachedCount = tbl->count; + int newCount, oldCap; + cosmoM_checkGarbage(state, size); // if this allocation would cause a GC, run the GC if (tbl->count < cachedCount) // the GC removed some objects from this table and resized it, ignore our resize event! return; CTableEntry *entries = cosmoM_xmalloc(state, size); - int newCount = 0; + oldCap = tbl->capacityMask + 1; + newCount = 0; // set all nodes as NIL : NIL for (int i = 0; i < newCapacity; i++) { @@ -130,7 +136,7 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin } // move over old values to the new buffer - for (int i = 0; i < tbl->capacity; i++) { + for (int i = 0; i < oldCap; i++) { CTableEntry *oldEntry = &tbl->table[i]; if (IS_NIL(oldEntry->key)) continue; // skip empty keys @@ -143,10 +149,10 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin } // free the old table - cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity); + cosmoM_freearray(state, CTableEntry, tbl->table, oldCap); tbl->table = entries; - tbl->capacity = newCapacity; + tbl->capacityMask = newCapacity - 1; tbl->count = newCount; tbl->tombstones = 0; } @@ -164,14 +170,15 @@ bool cosmoT_checkShrink(CState *state, CTable *tbl) { // returns a pointer to the allocated value COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) { // make sure we have enough space allocated - if (tbl->count + 1 > (int)(tbl->capacity * MAX_TABLE_FILL)) { + int cap = tbl->capacityMask + 1; + if (tbl->count + 1 > (int)(cap * MAX_TABLE_FILL)) { // grow table - int newCap = tbl->capacity * GROW_FACTOR; + int newCap = cap * GROW_FACTOR; resizeTbl(state, tbl, newCap, true); } // insert into the table - CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); // -1 for our capacity mask + CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key); // -1 for our capacity mask if (IS_NIL(entry->key)) { if (IS_NIL(entry->val)) // is it empty? @@ -191,7 +198,7 @@ bool cosmoT_get(CTable *tbl, CValue key, CValue *val) { return false; } - CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); + CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key); *val = entry->val; // return if get was successful @@ -201,7 +208,7 @@ bool cosmoT_get(CTable *tbl, CValue key, CValue *val) { bool cosmoT_remove(CState* state, CTable *tbl, CValue key) { if (tbl->count == 0) return 0; // sanity check - CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); + CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key); if (IS_NIL(entry->key)) // sanity check return false; @@ -220,7 +227,7 @@ COSMO_API int cosmoT_count(CTable *tbl) { CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash) { if (tbl->count == 0) return 0; // sanity check - uint32_t indx = hash & (tbl->capacity - 1); // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation + uint32_t indx = hash & tbl->capacityMask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation // keep looking for an open slot in the entries array while (true) { @@ -234,14 +241,15 @@ CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32 return (CObjString*)cosmoV_readObj(entry->key); } - indx = (indx + 1) & (tbl->capacity - 1); // fast mod here too + indx = (indx + 1) & tbl->capacityMask; // fast mod here too } } // for debugging purposes void cosmoT_printTable(CTable *tbl, const char *name) { printf("==== [[%s]] ====\n", name); - for (int i = 0; i < tbl->capacity; i++) { + int cap = tbl->capacityMask + 1; + for (int i = 0; i < cap; i++) { CTableEntry *entry = &tbl->table[i]; if (!(IS_NIL(entry->key))) { printValue(entry->key); diff --git a/src/ctable.h b/src/ctable.h index 7ebe413..31dd125 100644 --- a/src/ctable.h +++ b/src/ctable.h @@ -13,20 +13,19 @@ typedef struct CTableEntry { typedef struct CTable { int count; - int capacity; + int capacityMask; // +1 to get the capacity int tombstones; CTableEntry *table; } CTable; COSMO_API void cosmoT_initTable(CState *state, CTable *tbl, int startCap); COSMO_API void cosmoT_clearTable(CState *state, CTable *tbl); -COSMO_API void cosmoT_addTable(CState *state, CTable *from, CTable *to); COSMO_API int cosmoT_count(CTable *tbl); bool cosmoT_checkShrink(CState *state, CTable *tbl); CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash); -COSMO_API CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key); +CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key); bool cosmoT_get(CTable *tbl, CValue key, CValue *val); bool cosmoT_remove(CState *state, CTable *tbl, CValue key); diff --git a/src/cvm.c b/src/cvm.c index f2c51b1..645641b 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -2,6 +2,7 @@ #include "cstate.h" #include "cdebug.h" #include "cmem.h" +#include "cparse.h" #include #include @@ -27,7 +28,26 @@ COSMO_API void cosmo_insert(CState *state, int indx, CValue val) { state->top++; } -void cosmoV_printError(CState *state, CObjError *err) { +COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name) { + CObjFunction *func; + + if ((func = cosmoP_compileString(state, src, name)) != NULL) { + // success + disasmChunk(&func->chunk, func->module->str, 0); + + // push function onto the stack so it doesn't it cleaned up by the GC, at the same stack location put our closure + cosmoV_pushValue(state, cosmoV_newObj(func)); + *(cosmoV_getTop(state, 0)) = cosmoV_newObj(cosmoO_newClosure(state, func)); + return true; + } + + // fail + state->panic = false; + cosmoV_pushValue(state, cosmoV_newObj(state->error)); + return false; +} + +COSMO_API void cosmoV_printError(CState *state, CObjError *err) { // print stack trace for (int i = 0; i < err->frameCount; i++) { CCallFrame *frame = &err->frames[i]; @@ -473,10 +493,11 @@ int _tbl__next(CState *state, int nargs, CValue *args) { CObjTable *table = (CObjTable*)cosmoV_readObj(val); // while the entry is invalid, go to the next entry + int cap = table->tbl.capacityMask + 1; CTableEntry *entry; do { entry = &table->tbl.table[index++]; - } while (IS_NIL(entry->key) && index < table->tbl.capacity); + } while (IS_NIL(entry->key) && index < cap); cosmoO_setUserI(state, obj, index); // update the userdata if (!IS_NIL(entry->key)) { // if the entry is valid, return it's key and value pair diff --git a/src/cvm.h b/src/cvm.h index 4b8051b..c824fbc 100644 --- a/src/cvm.h +++ b/src/cvm.h @@ -24,6 +24,15 @@ COSMO_API CObjError* cosmoV_throw(CState *state); COSMO_API void cosmoV_error(CState *state, const char *format, ...); COSMO_API void cosmo_insert(CState *state, int indx, CValue val); +/* + compiles string into a , if successful, will be pushed onto the stack otherwise the will be pushed. + + returns: + false : is at the top of the stack + true : is at the top of the stack +*/ +COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name); + COSMO_API bool cosmoV_get(CState *state, CObj *obj, CValue key, CValue *val); COSMO_API bool cosmoV_set(CState *state, CObj *obj, CValue key, CValue val); diff --git a/src/main.c b/src/main.c index 1f650e9..c7d5586 100644 --- a/src/main.c +++ b/src/main.c @@ -32,16 +32,14 @@ int cosmoB_input(CState *state, int nargs, CValue *args) { } static void interpret(CState *state, const char *script, const char *mod) { - // cosmoP_compileString pushes the result onto the stack (NIL or COBJ_CLOSURE) - CObjFunction* func = cosmoP_compileString(state, script, mod); - if (func != NULL) { - disasmChunk(&func->chunk, func->name != NULL ? func->name->str : "_main", 0); - + // cosmoV_compileString pushes the result onto the stack (COBJ_ERROR or COBJ_CLOSURE) + if (cosmoV_compileString(state, script, mod)) { COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected if (res == COSMOVM_RUNTIME_ERR) cosmoV_printError(state, state->error); } else { + cosmoV_pop(state); // pop the error off the stack cosmoV_printError(state, state->error); }