Cosmo/src/ctable.c

236 lines
7.6 KiB
C
Raw Normal View History

2020-10-28 05:16:30 +00:00
#include "ctable.h"
#include "cmem.h"
#include "cvalue.h"
#include "cobj.h"
#include <string.h>
#define MAX_TABLE_FILL 0.75
2020-11-17 09:10:55 +00:00
// at 30% capacity with capacity > ARRAY_START, shrink the array
#define MIN_TABLE_CAPACITY ARRAY_START
// bit-twiddling hacks, gets the next power of 2
unsigned int nextPow2(unsigned int x) {
if (x <= ARRAY_START - 1) return ARRAY_START; // sanity check
x--;
int power = 2;
while (x >>= 1) power <<= 1;
if (power < ARRAY_START)
return ARRAY_START;
return power;
}
2020-10-28 05:16:30 +00:00
void cosmoT_initTable(CState *state, CTable *tbl, int startCap) {
2020-11-04 04:10:51 +00:00
tbl->capacity = startCap != 0 ? startCap : ARRAY_START; // sanity check :P
2020-10-28 05:16:30 +00:00
tbl->count = 0;
2020-11-17 09:10:55 +00:00
tbl->tombstones = 0;
2020-10-28 05:16:30 +00:00
tbl->table = NULL; // to let out GC know we're initalizing
2020-11-04 04:10:51 +00:00
tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * tbl->capacity);
2020-10-28 05:16:30 +00:00
// init everything to NIL
2020-11-04 04:10:51 +00:00
for (int i = 0; i < tbl->capacity; i++) {
2020-10-28 05:16:30 +00:00
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++) {
CTableEntry *entry = &from->table[i];
if (!(IS_NIL(entry->key))) {
CValue *newVal = cosmoT_insert(state, to, entry->key);
*newVal = entry->val;
}
}
}
void cosmoT_clearTable(CState *state, CTable *tbl) {
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity);
}
uint32_t getObjectHash(CObj *obj) {
switch(obj->type) {
case COBJ_STRING:
return ((CObjString*)obj)->hash;
default:
return 0;
}
}
uint32_t getValueHash(CValue *val) {
switch (val->type) {
case COSMO_TOBJ:
return getObjectHash(val->val.obj);
2020-11-05 03:37:45 +00:00
case COSMO_TNUMBER: {
uint32_t buf[sizeof(cosmo_Number)/sizeof(uint32_t)];
if (val->val.num == 0)
return 0;
memcpy(buf, &val->val.num, sizeof(buf));
for (int i = 0; i < sizeof(cosmo_Number)/sizeof(uint32_t); i++) buf[0] += buf[i];
return buf[0];
}
2020-10-28 05:16:30 +00:00
// TODO: add support for other types
default:
return 0;
}
}
// mask should always be (capacity - 1)
static CTableEntry *findEntry(CTableEntry *entries, int mask, CValue key) {
uint32_t hash = getValueHash(&key);
uint32_t indx = hash & mask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
CTableEntry *tomb = NULL;
// keep looking for an open slot in the entries array
while (true) {
CTableEntry *entry = &entries[indx];
if (IS_NIL(entry->key)) {
// check if it's an empty bucket or a tombstone
if (IS_NIL(entry->val)) {
// it's empty! if we found a tombstone, return that so it'll be reused
return tomb != NULL ? tomb : entry;
} else {
// its a tombstone!
tomb = entry;
}
} else if (cosmoV_equal(entry->key, key)) {
return entry;
}
indx = (indx + 1) & mask; // fast mod here too
}
}
2020-11-17 09:10:55 +00:00
static void resizeTbl(CState *state, CTable *tbl, size_t newCapacity) {
2020-11-17 20:32:20 +00:00
size_t size = sizeof(CTableEntry) * newCapacity;
2020-11-17 20:39:02 +00:00
cosmoM_checkGarbage(state, size); // if this allocation would cause a GC, run the GC
2020-11-17 09:10:55 +00:00
// if count > 8 and active entries < tombstones
if (tbl->count > MIN_TABLE_CAPACITY && tbl->count - tbl->tombstones < tbl->tombstones) {
int tombs = tbl->tombstones;
2020-11-17 20:32:20 +00:00
tbl->tombstones = 0; // set this to 0 so in our recursive call to resizeTbl() this branch isn't run again
2020-11-17 20:39:02 +00:00
resizeTbl(state, tbl, nextPow2((tbl->count - tombs) * GROW_FACTOR)); // shrink based on active entries to the next pow of 2
2020-11-17 09:10:55 +00:00
cosmoM_updateThreshhold(state); // force a threshhold update since this *could* be such a huge memory difference
return;
}
2020-11-17 20:32:20 +00:00
CTableEntry *entries = cosmoM_xmalloc(state, size);
2020-11-06 01:53:55 +00:00
int newCount = 0;
2020-10-28 05:16:30 +00:00
// set all nodes as NIL : NIL
for (int i = 0; i < newCapacity; i++) {
entries[i].key = cosmoV_newNil();
entries[i].val = cosmoV_newNil();
}
// move over old values to the new buffer
for (int i = 0; i < tbl->capacity; i++) {
CTableEntry *oldEntry = &tbl->table[i];
if (IS_NIL(oldEntry->key))
continue; // skip empty keys
// get new entry location & update the node
CTableEntry *newEntry = findEntry(entries, newCapacity - 1, oldEntry->key);
newEntry->key = oldEntry->key;
newEntry->val = oldEntry->val;
newCount++; // inc count
}
// free the old table
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity);
tbl->table = entries;
tbl->capacity = newCapacity;
tbl->count = newCount;
2020-11-17 20:32:20 +00:00
tbl->tombstones = 0;
2020-10-28 05:16:30 +00:00
}
// 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
2020-11-06 01:53:55 +00:00
if (tbl->count + 1 > (int)(tbl->capacity * MAX_TABLE_FILL)) {
2020-11-17 09:10:55 +00:00
// grow table
2020-10-28 05:16:30 +00:00
int newCap = tbl->capacity * GROW_FACTOR;
2020-11-17 09:10:55 +00:00
resizeTbl(state, tbl, newCap);
2020-10-28 05:16:30 +00:00
}
// insert into the table
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); // -1 for our capacity mask
2020-11-17 09:38:00 +00:00
if (IS_NIL(entry->key)) {
2020-11-17 09:36:56 +00:00
if (IS_NIL(entry->val)) // is it empty?
tbl->count++;
else // it's a tombstone, mark it alive!
tbl->tombstones--;
2020-11-17 09:38:00 +00:00
}
2020-10-28 05:16:30 +00:00
entry->key = key;
return &entry->val;
}
bool cosmoT_get(CTable *tbl, CValue key, CValue *val) {
if (tbl->count == 0) {
*val = cosmoV_newNil();
return false; // sanity check
}
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key);
*val = entry->val;
return !(IS_NIL(entry->key));
}
2020-11-17 09:10:55 +00:00
bool cosmoT_remove(CState* state, CTable *tbl, CValue key) {
2020-10-28 05:16:30 +00:00
if (tbl->count == 0) return 0; // sanity check
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key);
if (IS_NIL(entry->key)) // sanity check
return false;
// crafts tombstone
entry->key = cosmoV_newNil(); // this has to be nil
entry->val = cosmoV_newBoolean(false); // doesn't reall matter what this is, as long as it isn't nil
2020-11-17 09:10:55 +00:00
tbl->tombstones++;
2020-10-28 05:16:30 +00:00
return true;
}
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, size_t 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
// keep looking for an open slot in the entries array
while (true) {
CTableEntry *entry = &tbl->table[indx];
// check if it's an empty slot (meaning we dont have it in the table)
if (IS_NIL(entry->key) && IS_NIL(entry->val)) {
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;
}
indx = (indx + 1) & (tbl->capacity - 1); // 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++) {
CTableEntry *entry = &tbl->table[i];
if (!(IS_NIL(entry->key))) {
printValue(entry->key);
printf(" - ");
printValue(entry->val);
printf("\n");
}
}
}