Skip to content

Commit

Permalink
Added new Map type that can use most types as a key
Browse files Browse the repository at this point in the history
It is implemented as a sparse hashmap and will probably
replace the old Tree type in the future
  • Loading branch information
Melchizedek6809 committed Apr 25, 2024
1 parent 83c8eee commit 0112f07
Show file tree
Hide file tree
Showing 17 changed files with 12,711 additions and 12,177 deletions.
2 changes: 1 addition & 1 deletion benchmark/recfib/fib.nuj
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
((= 1 n) 1)
(#t (+ (fib (- n 1)) (fib (- n 2))))))

(pfmtln "fib(40) is: {}\nGC Runs: {}" (fib 40) (garbage-collection-runs))
(pfmtln "fib(30) is: {}\nGC Runs: {}" (fib 30) (garbage-collection-runs))
4 changes: 2 additions & 2 deletions benchmark/recfib/fib.scm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
((= 1 n) 1)
(#t (+ (fib (- n 1)) (fib (- n 2))))))

(display "fib(40) = ")
(display (fib 40))
(display "fib(30) = ")
(display (fib 30))
(display "\n")
24,372 changes: 12,270 additions & 12,102 deletions bootstrap/image.c

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions lib/core-operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,15 @@ void lInit(){
lOperationsCore();
lOperationsArray();
lOperationsTree();
lOperationsMap();
lOperationsBytecode();
lOperationsString();
}

static lVal lnfSymbolTable(){
return lValMap(lSymbolTable);
}

void lOperationsCore(){
lAddNativeFuncV("quote", "(v)", "Return v as is without evaluating", lnfQuote, NFUNC_PURE);
lAddNativeFuncV("read", "(str)", "Read and Parses STR as an S-Expression", lnfRead, NFUNC_PURE);
Expand All @@ -231,9 +236,11 @@ void lOperationsCore(){

lAddNativeFuncR("array/new", "args", "Create a new array from ...ARGS", lnfArrNew, 0);
lAddNativeFuncR("tree/new", "plist", "Return a new tree", lnfTreeNew, 0);
lAddNativeFuncR("map/new", "plist", "Return a new map", lnfMapNew, 0);

lAddNativeFuncV("image/serialize", "(val)", "Serializes val into a binary representation that can be stored", lnfSerialize, 0);
lAddNativeFuncV("image/deserialize", "(buf)", "Deserializes buf into a value", lnfDeserialize, 0);
lAddNativeFunc("symbol-table", "()", "Returns the global symbol table", lnfSymbolTable, 0);

lAddNativeMethodV(&lClassList[ltNil], lSymLTString, "(self)", lnfNilToString, NFUNC_PURE);
lAddNativeMethodV(&lClassList[ltInt], lSymLTString, "(self)", lnfIntToString, NFUNC_PURE);
Expand Down
23 changes: 23 additions & 0 deletions lib/garbage-collection.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ T##MarkMap[ci] = 1
static void lValGCMark (lVal v);
static void lBufferGCMark (const lBuffer *v);
static void lBufferViewGCMark (const lBufferView *v);
static void lMapGCMark (const lMap *v);
static void lTreeGCMark (const lTree *v);
static void lTreeRootGCMark (const lTreeRoot *v);
static void lClosureGCMark (const lClosure *c);
Expand Down Expand Up @@ -92,6 +93,15 @@ static void lTreeFree(lTree *t){
lTreeMarkMap[t - lTreeList] = 2;
}

static void lMapFree(lMap *t){
free(t->entries);
t->entries = NULL;
t->nextFree = lMapFFree;
lMapFFree = t;
lMapActive--;
lMapMarkMap[t - lMapList] = 2;
}

static void lTreeRootFree(lTreeRoot *t){
t->nextFree = lTreeRootFFree;
lTreeRootFFree = t;
Expand Down Expand Up @@ -165,6 +175,9 @@ static void lValGCMark(lVal v){
case ltSymbol:
lSymbolGCMark(v.vSymbol);
break;
case ltMap:
lMapGCMark(v.vMap);
break;
case ltTree:
lTreeRootGCMark(v.vTree);
break;
Expand All @@ -183,6 +196,15 @@ static void lValGCMark(lVal v){
}
}

static void lMapGCMark(const lMap *v){
markerPrefix(lMap);
for(int i=0;i<v->size;i++){
if(v->entries[i].key.type == ltNil){continue;}
lValGCMark(v->entries[i].key);
lValGCMark(v->entries[i].val);
}
}

static void lTreeGCMark(const lTree *v){
markerPrefix(lTree);

Expand Down Expand Up @@ -233,6 +255,7 @@ static void lRootsMark(){
for(uint i=0;i<lNFuncMax;i++){
lNFuncGCMark(&lNFuncList[i]);
}
lMapGCMark(lSymbolTable);
}

lSymbol *lRootsSymbolPush(lSymbol *v){
Expand Down
4 changes: 4 additions & 0 deletions lib/generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ static lVal lBufferViewRef(lVal car, size_t i){

lVal lGenericRef(lVal col, lVal key){
switch(col.type){
case ltMap:
return lMapRef(col.vMap, key);
case ltPair:
reqNaturalInt(key);
for(int i=0;i<key.vInt;i++){
Expand Down Expand Up @@ -160,6 +162,8 @@ static lVal lBufferViewSet(lVal car, size_t i, lVal v){

lVal lGenericSet(lVal col, lVal key, lVal v){
switch(col.type){
case ltMap:
return lMapSet(col.vMap, key, v);
case ltBytecodeArr: {
const lBytecodeArray *arr = col.vBytecodeArr;
reqNaturalInt(key);
Expand Down
242 changes: 242 additions & 0 deletions lib/map.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/* Nujel - Copyright (C) 2020-2022 - Benjamin Vincent Schulenburg
* This project uses the MIT license, a copy should be included under /LICENSE */
#include "nujel.h"
#ifndef NUJEL_AMALGAMATION
#include "nujel-private.h"
#endif

#include "../third-party/fasthash/fasthash.h"

static const u64 hashSeed = 0x5b0a159d9eac0381ULL;

u32 lHashString(const char *str, i32 len){
return fasthash64(str, len, hashSeed);
}

static inline u32 lHashVal(lVal v){
switch(v.type){
case ltType:
case ltFileHandle:
case ltBytecodeArr:
case ltBufferView:
case ltEnvironment:
case ltNativeFunc:
case ltMacro:
case ltLambda:
case ltMap:
case ltTree:
case ltArray:
case ltPair:
#if INTPTR_MAX == INT32_MAX
return fasthash32v((u32)v.vPointer);
#else
return fasthash64v((u64)v.vPointer);
#endif

case ltString:
return lHashString(v.vString->data, v.vString->length);
case ltBool:
return fasthash64v((u64)v.vBool);
case ltKeyword:
case ltSymbol:
return v.vSymbol->hash;
case ltInt:
return fasthash64v(v.vInt);
case ltFloat:
return fasthash64v(v.vFloat);
default: // Anything else can't be used as keys in maps
return 0xdeadbeef;
}
}

static bool lMapSetSimple(lMap *map, lVal key, lVal val){
const u32 size = map->size;
const u32 mask = size-1;
u32 off = lHashVal(key) & mask;
if(unlikely(map->entries == NULL)){
return false;
}
for(u32 i=0; i < size; i++){
if(map->entries[off].key.type == ltNil){
map->entries[off].key = key;
map->entries[off].val = val;
return true;
}
if(lValEqual(key, map->entries[off].key)){
map->entries[off].key = key;
map->entries[off].val = val;
return false;
}
off = (off + 1) & mask;
}
// This can only occur if the hashmap is full, which must be checked outside ot lMapSetSimple
exit(176);
}

static lVal lMapRefSimple(lMap *map, lVal key){
const u32 size = map->size;
const u32 mask = size-1;
u32 off = lHashVal(key) & mask;
for(u32 i=0; i < size; i++){
if(map->entries[off].key.type == ltNil){ return NIL; }
if(lValEqual(key, map->entries[off].key)){
return map->entries[off].val;
}
off = (off + 1) & mask;
}
return NIL;
}

static bool lMapHasSimple(lMap *map, lVal key){
const u32 size = map->size;
const u32 mask = size-1;
u32 off = lHashVal(key) & mask;
for(u32 i=0; i < size; i++){
if(map->entries[off].key.type == ltNil){ return false; }
if(lValEqual(key, map->entries[off].key)){
return true;
}
off = (off + 1) & mask;
}
return false;
}

static void lMapResize(lMap *map, u32 size){
if(unlikely(size <= map->length)){ return; }
const lMapEntry *oldEntries = map->entries;
const u32 oldSize = map->size;
map->entries = calloc(size, sizeof(lMapEntry));
map->size = size;
if(oldEntries != NULL){
for(uint i=0; i < oldSize; i++){
if(oldEntries[i].key.type == ltNil){ continue;}
lMapSetSimple(map, oldEntries[i].key, oldEntries[i].val);
}
free((void *)oldEntries);
}
}

lVal lnfMapNew(lVal v) {
lMap *m = lMapAllocRaw();
m->entries = NULL;
lVal e = v;
while(e.type == ltPair){
lVal key = lCar(e);
if(unlikely((key.type == ltNil) || (key.type == ltFloat))){
return lValException(lSymTypeError, "Can't use Nil or Float values as keys", key);
}
lVal val = lCadr(e);
e = lCddr(e);
lMapSet(m, key, val);
}
return lValAlloc(ltMap, m);;
}

lVal lMapSet(lMap *map, lVal key, lVal val) {
if(unlikely((key.type == ltNil) || (key.type == ltFloat))){
return lValException(lSymTypeError, "Can't use Nil or Float values as keys", key);
}
const u32 resizeSize = (map->size - (uint)(map->size >> 2));
if(unlikely(map->length >= resizeSize)){
lMapResize(map, MAX(4, map->size) * 2);
}
if(lMapSetSimple(map, key, val)){
map->length++;
}
return val;
}

lVal lMapRef(lMap *map, lVal key) {
if(unlikely((key.type == ltNil) || (key.type == ltFloat))){
return lValException(lSymTypeError, "Can't use Nil or Float values as keys", key);
}
return lMapRefSimple(map, key);
}

lVal lMapRefString(lMap *map, const char *str) {
const u32 size = map->size;
const u32 mask = size-1;
i32 len = strlen(str);
u32 off = lHashString(str, len) & mask;
for(u32 i=0; i < size; i++){
if(map->entries[off].key.type == ltNil){ return NIL; }
if(map->entries[off].key.type != ltString){ continue; }
const i32 eLen = map->entries[off].key.vString->length;
if ((len == eLen) && (memcmp(str, map->entries[off].key.vString->data, len) == 0)){
return map->entries[off].val;
}
off = (off + 1) & mask;
}
return NIL;
}

static lVal lnmMapLength(lVal self) {
return lValInt(self.vMap->length);
}

static lVal lnmMapSize(lVal self) {
return lValInt(self.vMap->size);
}

static lVal lnmMapHas(lVal self, lVal key) {
return lValBool(lMapHasSimple(self.vMap, key));
}

static lVal lnmMapKey(lVal self, lVal off) {
reqNaturalInt(off);
if(unlikely(off.vInt >= self.vMap->size)){
return NIL;
}
return self.vMap->entries[off.vInt].key;
}

static lVal lnmMapVal(lVal self, lVal off) {
reqNaturalInt(off);
if(unlikely(off.vInt >= self.vMap->size)){
return NIL;
}
return self.vMap->entries[off.vInt].val;
}

static lVal lnmMapValues(lVal self) {
lVal ret = NIL;
const u32 size = self.vMap->size;
for(uint i=0; i < size; i++){
if(self.vMap->entries[i].key.type == ltNil){ continue; }
ret = lCons(self.vMap->entries[i].val, ret);
}
return ret;
}

static lVal lnmMapKeys(lVal self) {
lVal ret = NIL;
const u32 size = self.vMap->size;
for(uint i=0; i < size; i++){
if(self.vMap->entries[i].key.type == ltNil){ continue; }
ret = lCons(self.vMap->entries[i].key, ret);
}
return ret;
}

static lVal lnmMapClone(lVal self) {
lMap *m = lMapAllocRaw();
m->length = self.vMap->length;
m->flags = self.vMap->flags;
m->size = self.vMap->size;
m->entries = calloc(sizeof(lMapEntry), m->size);
memcpy(m->entries, self.vMap->entries, sizeof(lMapEntry) * m->size);
return lValAlloc(ltMap, m);
}

void lOperationsMap() {
lClass *Map = &lClassList[ltMap];
lAddNativeMethodV (Map, lSymS("length"), "(self)", lnmMapLength, NFUNC_PURE);
lAddNativeMethodV (Map, lSymS("clone"), "(self)", lnmMapClone, NFUNC_PURE);
lAddNativeMethodVV (Map, lSymS("has?"), "(self key)", lnmMapHas, NFUNC_PURE);
lAddNativeMethodV (Map, lSymS("values"), "(self)", lnmMapValues, NFUNC_PURE);
lAddNativeMethodV (Map, lSymS("keys"), "(self)", lnmMapKeys, NFUNC_PURE);

lAddNativeMethodV (Map, lSymS("size*"), "(self)", lnmMapSize, NFUNC_PURE);
lAddNativeMethodVV (Map, lSymS("key*"), "(self off)", lnmMapKey, NFUNC_PURE);
lAddNativeMethodVV (Map, lSymS("value*"), "(self off)", lnmMapVal, NFUNC_PURE);
}
Loading

0 comments on commit 0112f07

Please sign in to comment.