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 ) {
2020-12-04 06:04:14 +00:00
switch ( GET_TYPE ( * val ) ) {
2020-10-28 05:16:30 +00:00
case COSMO_TOBJ :
2020-12-04 06:04:14 +00:00
return getObjectHash ( cosmoV_readObj ( * val ) ) ;
2020-11-05 03:37:45 +00:00
case COSMO_TNUMBER : {
uint32_t buf [ sizeof ( cosmo_Number ) / sizeof ( uint32_t ) ] ;
2020-12-04 06:04:14 +00:00
cosmo_Number num = cosmoV_readNumber ( * val ) ;
if ( num = = 0 )
2020-11-05 03:37:45 +00:00
return 0 ;
2020-12-04 06:04:14 +00:00
memcpy ( buf , & num , sizeof ( buf ) ) ;
2021-01-02 05:06:24 +00:00
for ( size_t i = 0 ; i < sizeof ( cosmo_Number ) / sizeof ( uint32_t ) ; i + + ) buf [ 0 ] + = buf [ i ] ;
2020-11-05 03:37:45 +00:00
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-28 01:34:54 +00:00
static void resizeTbl ( CState * state , CTable * tbl , int newCapacity , bool canShrink ) {
if ( canShrink & & cosmoT_checkShrink ( state , tbl ) )
2020-11-24 21:16:37 +00:00
return ;
2020-11-17 20:32:20 +00:00
size_t size = sizeof ( CTableEntry ) * newCapacity ;
2020-11-19 20:41:21 +00:00
int cachedCount = tbl - > count ;
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
2020-11-19 20:41:21 +00:00
if ( tbl - > count < cachedCount ) // the GC removed some objects from this table and resized it, ignore our resize event!
2020-11-17 09:10:55 +00:00
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
}
2020-11-19 20:41:21 +00:00
bool cosmoT_checkShrink ( CState * state , CTable * tbl ) {
// if count > 8 and active entries < tombstones
2020-11-28 01:42:00 +00:00
if ( tbl - > count > MIN_TABLE_CAPACITY & & ( tbl - > count - tbl - > tombstones < tbl - > tombstones | | tbl - > tombstones > 50 ) ) { // TODO: 50 should be a threshhold
2020-11-28 01:34:54 +00:00
resizeTbl ( state , tbl , nextPow2 ( tbl - > count - tbl - > tombstones ) * GROW_FACTOR , false ) ; // shrink based on active entries to the next pow of 2
2020-11-19 20:41:21 +00:00
return true ;
}
return false ;
}
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-28 01:34:54 +00:00
resizeTbl ( state , tbl , newCap , true ) ;
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
2020-12-19 19:32:43 +00:00
entry - > val = cosmoV_newBoolean ( false ) ; // doesn't really 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 ;
}
2020-11-30 18:32:04 +00:00
// returns the active entry count
COSMO_API int cosmoT_count ( CTable * tbl ) {
return tbl - > count - tbl - > tombstones ;
}
2021-01-02 05:06:24 +00:00
CObjString * cosmoT_lookupString ( CTable * tbl , const char * str , int length , uint32_t hash ) {
2020-10-28 05:16:30 +00:00
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!
2020-12-04 06:04:14 +00:00
return ( CObjString * ) cosmoV_readObj ( entry - > key ) ;
2020-10-28 05:16:30 +00:00
}
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 " ) ;
}
}
2021-01-02 05:06:24 +00:00
}