Minor table refactor, added cosmoV_compileString and loadstring() to baselib

cosmoV_compileString is recommended since it'll push the result (<error> or <closure>) onto the stack.

also, fixed some GC-related bugs, so yay!
This commit is contained in:
CPunch 2021-01-10 14:38:53 -06:00
parent 8dc8cef7dc
commit c510c9aebf
9 changed files with 122 additions and 59 deletions

View File

@ -57,6 +57,24 @@ int cosmoB_pcall(CState *state, int nargs, CValue *args) {
return 2; 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()", "<string>", "%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; // <boolean>, <closure> or <error>
}
// ================================================================ [STRING.*] ================================================================ // ================================================================ [STRING.*] ================================================================
// string.sub // string.sub
@ -103,23 +121,32 @@ int cosmoB_sSub(CState *state, int nargs, CValue *args) {
} }
void cosmoB_loadLibrary(CState *state) { void cosmoB_loadLibrary(CState *state) {
// print const char *identifiers[] = {
cosmoV_pushString(state, "print"); "print",
cosmoV_pushCFunction(state, cosmoB_print); "assert",
"type",
"pcall",
"loadstring"
};
// assert (for unit testing) CosmoCFunction baseLib[] = {
cosmoV_pushString(state, "assert"); cosmoB_print,
cosmoV_pushCFunction(state, cosmoB_assert); cosmoB_assert,
cosmoB_type,
cosmoB_pcall,
cosmoB_loadstring
};
// type int i;
cosmoV_pushString(state, "type"); for (i = 0; i < sizeof(identifiers)/sizeof(identifiers[0]); i++) {
cosmoV_pushCFunction(state, cosmoB_type); cosmoV_pushString(state, identifiers[i]);
cosmoV_pushCFunction(state, baseLib[i]);
}
// pcall // register all the pushed c functions and the strings as globals
cosmoV_pushString(state, "pcall"); cosmoV_register(state, i);
cosmoV_pushCFunction(state, cosmoB_pcall);
// string. // string.*
cosmoV_pushString(state, "string"); cosmoV_pushString(state, "string");
// sub // sub
@ -127,19 +154,19 @@ void cosmoB_loadLibrary(CState *state) {
cosmoV_pushCFunction(state, cosmoB_sSub); cosmoV_pushCFunction(state, cosmoB_sSub);
cosmoV_makeTable(state, 1); cosmoV_makeTable(state, 1);
// string. // string.*
// register these all to the global table // register "string" to that table
cosmoV_register(state, 5); cosmoV_register(state, 1);
// make string object for CObjStrings // make string object for CObjStrings
// sub // sub
cosmoV_pushString(state, "sub"); cosmoV_pushString(state, "sub");
cosmoV_pushCFunction(state, cosmoB_sSub); cosmoV_pushCFunction(state, cosmoB_sSub);
cosmoV_makeObject(state, 1); cosmoV_makeObject(state, 1);
// grab the object from the stack and set the base protoObject
StkPtr obj = cosmoV_pop(state); StkPtr obj = cosmoV_pop(state);
state->protoObjects[COBJ_STRING] = cosmoV_readObject(*obj); state->protoObjects[COBJ_STRING] = cosmoV_readObject(*obj);
} }

View File

@ -55,7 +55,8 @@ void markTable(CState *state, CTable *tbl) {
if (tbl->table == NULL) // table is still being initialized if (tbl->table == NULL) // table is still being initialized
return; 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]; CTableEntry *entry = &tbl->table[i];
markValue(state, entry->key); markValue(state, entry->key);
markValue(state, entry->val); markValue(state, entry->val);
@ -67,7 +68,8 @@ void tableRemoveWhite(CState *state, CTable *tbl) {
if (tbl->table == NULL) // table is still being initialized if (tbl->table == NULL) // table is still being initialized
return; 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]; 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 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); cosmoT_remove(state, tbl, entry->key);

View File

@ -1587,20 +1587,19 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source, const char
endCompiler(&parser); endCompiler(&parser);
freeParseState(&parser); freeParseState(&parser);
// the VM still expects a result on the stack
cosmoV_pushValue(state, cosmoV_newNil());
cosmoM_unfreezeGC(state); cosmoM_unfreezeGC(state);
return NULL; return NULL;
} }
CObjFunction* resFunc = compiler.function; 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 // finally free out parser states
endCompiler(&parser); endCompiler(&parser);
freeParseState(&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); cosmoM_unfreezeGC(state);
cosmoV_pop(state);
return resFunc; return resFunc;
} }

View File

@ -33,14 +33,18 @@ CState *cosmoV_newState() {
state->openUpvalues = NULL; state->openUpvalues = NULL;
state->error = NULL; state->error = NULL;
cosmoT_initTable(state, &state->strings, 8); // init string table // set default proto objects
cosmoT_initTable(state, &state->globals, 8); // init global table 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 // first, set all strings to NULL so our GC doesn't read garbage data
for (int i = 0; i < ISTRING_MAX; i++) for (int i = 0; i < ISTRING_MAX; i++)
state->iStrings[i] = NULL; 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 // setup all strings used by the VM
state->iStrings[ISTRING_INIT] = cosmoO_copyString(state, "__init", 6); state->iStrings[ISTRING_INIT] = cosmoO_copyString(state, "__init", 6);
state->iStrings[ISTRING_TOSTRING] = cosmoO_copyString(state, "__tostring", 10); state->iStrings[ISTRING_TOSTRING] = cosmoO_copyString(state, "__tostring", 10);
@ -61,10 +65,6 @@ CState *cosmoV_newState() {
// set the IString flags // set the IString flags
for (int i = 0; i < ISTRING_MAX; i++) for (int i = 0; i < ISTRING_MAX; i++)
state->iStrings[i]->isIString = true; state->iStrings[i]->isIString = true;
// set default proto objects
for (int i = 0; i < COBJ_MAX; i++)
state->protoObjects[i] = NULL;
return state; return state;
} }

View File

@ -24,21 +24,24 @@ unsigned int nextPow2(unsigned int x) {
} }
void cosmoT_initTable(CState *state, CTable *tbl, int startCap) { 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->count = 0;
tbl->tombstones = 0; tbl->tombstones = 0;
tbl->table = NULL; // to let out GC know we're initalizing 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 // 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].key = cosmoV_newNil();
tbl->table[i].val = cosmoV_newNil(); tbl->table[i].val = cosmoV_newNil();
} }
} }
void cosmoT_addTable(CState *state, CTable *from, CTable *to) { 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]; CTableEntry *entry = &from->table[i];
if (!(IS_NIL(entry->key))) { 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) { 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) { uint32_t getObjectHash(CObj *obj) {
@ -57,7 +60,7 @@ uint32_t getObjectHash(CObj *obj) {
case COBJ_STRING: case COBJ_STRING:
return ((CObjString*)obj)->hash; return ((CObjString*)obj)->hash;
default: 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; size_t size = sizeof(CTableEntry) * newCapacity;
int cachedCount = tbl->count; int cachedCount = tbl->count;
int newCount, oldCap;
cosmoM_checkGarbage(state, size); // if this allocation would cause a GC, run the GC 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! if (tbl->count < cachedCount) // the GC removed some objects from this table and resized it, ignore our resize event!
return; return;
CTableEntry *entries = cosmoM_xmalloc(state, size); CTableEntry *entries = cosmoM_xmalloc(state, size);
int newCount = 0; oldCap = tbl->capacityMask + 1;
newCount = 0;
// set all nodes as NIL : NIL // set all nodes as NIL : NIL
for (int i = 0; i < newCapacity; i++) { 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 // 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]; CTableEntry *oldEntry = &tbl->table[i];
if (IS_NIL(oldEntry->key)) if (IS_NIL(oldEntry->key))
continue; // skip empty keys continue; // skip empty keys
@ -143,10 +149,10 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
} }
// free the old table // free the old table
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity); cosmoM_freearray(state, CTableEntry, tbl->table, oldCap);
tbl->table = entries; tbl->table = entries;
tbl->capacity = newCapacity; tbl->capacityMask = newCapacity - 1;
tbl->count = newCount; tbl->count = newCount;
tbl->tombstones = 0; tbl->tombstones = 0;
} }
@ -164,14 +170,15 @@ bool cosmoT_checkShrink(CState *state, CTable *tbl) {
// returns a pointer to the allocated value // returns a pointer to the allocated value
COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) { COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) {
// make sure we have enough space allocated // 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 // grow table
int newCap = tbl->capacity * GROW_FACTOR; int newCap = cap * GROW_FACTOR;
resizeTbl(state, tbl, newCap, true); resizeTbl(state, tbl, newCap, true);
} }
// insert into the table // 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->key)) {
if (IS_NIL(entry->val)) // is it empty? if (IS_NIL(entry->val)) // is it empty?
@ -191,7 +198,7 @@ bool cosmoT_get(CTable *tbl, CValue key, CValue *val) {
return false; return false;
} }
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key);
*val = entry->val; *val = entry->val;
// return if get was successful // 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) { bool cosmoT_remove(CState* state, CTable *tbl, CValue key) {
if (tbl->count == 0) return 0; // sanity check 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 if (IS_NIL(entry->key)) // sanity check
return false; 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) { CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash) {
if (tbl->count == 0) return 0; // sanity check 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 // keep looking for an open slot in the entries array
while (true) { while (true) {
@ -234,14 +241,15 @@ CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32
return (CObjString*)cosmoV_readObj(entry->key); 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 // for debugging purposes
void cosmoT_printTable(CTable *tbl, const char *name) { void cosmoT_printTable(CTable *tbl, const char *name) {
printf("==== [[%s]] ====\n", 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]; CTableEntry *entry = &tbl->table[i];
if (!(IS_NIL(entry->key))) { if (!(IS_NIL(entry->key))) {
printValue(entry->key); printValue(entry->key);

View File

@ -13,20 +13,19 @@ typedef struct CTableEntry {
typedef struct CTable { typedef struct CTable {
int count; int count;
int capacity; int capacityMask; // +1 to get the capacity
int tombstones; int tombstones;
CTableEntry *table; CTableEntry *table;
} CTable; } CTable;
COSMO_API void cosmoT_initTable(CState *state, CTable *tbl, int startCap); COSMO_API void cosmoT_initTable(CState *state, CTable *tbl, int startCap);
COSMO_API void cosmoT_clearTable(CState *state, CTable *tbl); 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); COSMO_API int cosmoT_count(CTable *tbl);
bool cosmoT_checkShrink(CState *state, CTable *tbl); bool cosmoT_checkShrink(CState *state, CTable *tbl);
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash); 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_get(CTable *tbl, CValue key, CValue *val);
bool cosmoT_remove(CState *state, CTable *tbl, CValue key); bool cosmoT_remove(CState *state, CTable *tbl, CValue key);

View File

@ -2,6 +2,7 @@
#include "cstate.h" #include "cstate.h"
#include "cdebug.h" #include "cdebug.h"
#include "cmem.h" #include "cmem.h"
#include "cparse.h"
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
@ -27,7 +28,26 @@ COSMO_API void cosmo_insert(CState *state, int indx, CValue val) {
state->top++; 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 // print stack trace
for (int i = 0; i < err->frameCount; i++) { for (int i = 0; i < err->frameCount; i++) {
CCallFrame *frame = &err->frames[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); CObjTable *table = (CObjTable*)cosmoV_readObj(val);
// while the entry is invalid, go to the next entry // while the entry is invalid, go to the next entry
int cap = table->tbl.capacityMask + 1;
CTableEntry *entry; CTableEntry *entry;
do { do {
entry = &table->tbl.table[index++]; 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 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 if (!IS_NIL(entry->key)) { // if the entry is valid, return it's key and value pair

View File

@ -24,6 +24,15 @@ COSMO_API CObjError* cosmoV_throw(CState *state);
COSMO_API void cosmoV_error(CState *state, const char *format, ...); COSMO_API void cosmoV_error(CState *state, const char *format, ...);
COSMO_API void cosmo_insert(CState *state, int indx, CValue val); COSMO_API void cosmo_insert(CState *state, int indx, CValue val);
/*
compiles string into a <closure>, if successful, <closure> will be pushed onto the stack otherwise the <error> will be pushed.
returns:
false : <error> is at the top of the stack
true : <closure> 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_get(CState *state, CObj *obj, CValue key, CValue *val);
COSMO_API bool cosmoV_set(CState *state, CObj *obj, CValue key, CValue val); COSMO_API bool cosmoV_set(CState *state, CObj *obj, CValue key, CValue val);

View File

@ -32,16 +32,14 @@ int cosmoB_input(CState *state, int nargs, CValue *args) {
} }
static void interpret(CState *state, const char *script, const char *mod) { static void interpret(CState *state, const char *script, const char *mod) {
// cosmoP_compileString pushes the result onto the stack (NIL or COBJ_CLOSURE) // cosmoV_compileString pushes the result onto the stack (COBJ_ERROR or COBJ_CLOSURE)
CObjFunction* func = cosmoP_compileString(state, script, mod); if (cosmoV_compileString(state, script, mod)) {
if (func != NULL) {
disasmChunk(&func->chunk, func->name != NULL ? func->name->str : "_main", 0);
COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected
if (res == COSMOVM_RUNTIME_ERR) if (res == COSMOVM_RUNTIME_ERR)
cosmoV_printError(state, state->error); cosmoV_printError(state, state->error);
} else { } else {
cosmoV_pop(state); // pop the error off the stack
cosmoV_printError(state, state->error); cosmoV_printError(state, state->error);
} }