From 541a5443b6b77cc23ea0d732bbec4b719422619d Mon Sep 17 00:00:00 2001 From: Miguel Lechon Date: Tue, 16 Mar 2021 19:05:48 +0100 Subject: [PATCH] Test and avoid MD_StringMap collisions. --- source/md.h | 1 + source/md_impl.c | 153 ++++++++++++++++++++++++++++--------------- tests/sanity_tests.c | 55 ++++++++++++++++ 3 files changed, 157 insertions(+), 52 deletions(-) diff --git a/source/md.h b/source/md.h index e22e292..5bcd25f 100644 --- a/source/md.h +++ b/source/md.h @@ -398,6 +398,7 @@ struct MD_MapSlot { MD_MapSlot *next; MD_u64 hash; + void *key; void *value; }; diff --git a/source/md_impl.c b/source/md_impl.c index 6bf55d7..e8c77d6 100644 --- a/source/md_impl.c +++ b/source/md_impl.c @@ -959,12 +959,109 @@ _MD_Map_Initialize(MD_Map *map) } } +///////////////////////////////////////////// +//~ NOTE(mal): MD_StringMap MD_FUNCTION_IMPL MD_MapSlot * -_MD_Map_Lookup(MD_Map *map, MD_u64 hash) +MD_StringMap_Lookup(MD_StringMap *map, MD_String8 string) // NOTE(mal): Or MD_PtrFromString { _MD_Map_Initialize(map); MD_MapSlot *slot = 0; + MD_u64 hash = MD_HashString(string); + MD_u64 index = hash % map->table_size; + for(MD_MapSlot *candidate = map->table[index]; candidate; candidate = candidate->next) + { + if(candidate->hash == hash && MD_StringMatch(*((MD_String8 *)candidate->key), string, 0)) + { + slot = candidate; + break; + } + } + + return slot; +} + +MD_FUNCTION_IMPL MD_b32 +MD_StringMap_Insert(MD_StringMap *map, MD_MapCollisionRule collision_rule, MD_String8 string, void *value) +{ + _MD_Map_Initialize(map); + + MD_MapSlot *slot = 0; + MD_u64 hash = MD_HashString(string); + MD_u64 index = hash % map->table_size; + + for(MD_MapSlot *candidate = map->table[index]; candidate; candidate = candidate->next) + { + if(candidate->hash == hash && MD_StringMatch(*((MD_String8 *)candidate->key), string, 0)) + { + slot = candidate; + break; + } + } + + if(slot == 0 || (slot != 0 && collision_rule == MD_MapCollisionRule_Chain)) + { + slot = _MD_PushArray(_MD_GetCtx(), MD_MapSlot, 1); + if(slot) + { + slot->next = 0; + if(map->table[index]) + { + for(MD_MapSlot *old_slot = map->table[index]; old_slot; old_slot = old_slot->next) + { + if(old_slot->next == 0) + { + old_slot->next = slot; + break; + } + } + } + else + { + map->table[index] = slot; + } + } + } + + if(slot) + { + slot->value = value; + slot->hash = hash; + MD_String8 *string_copy = _MD_PushArray(_MD_GetCtx(), MD_String8, 1); + *string_copy = MD_PushStringCopy(string); + slot->key = string_copy; + } + + return !!slot; +} + +// NOTE(mal): Original MD_NodeTable interface +#define MD_NodeTable_Lookup(map, string) MD_StringMap_Lookup(map, string) +#define MD_NodeTable_Insert(map, collision_rule, string, node) MD_StringMap_Insert(map, collision_rule, string, (void *) node) + +///////////////////////////////////////////// +//~ NOTE(mal): MD_PtrMap + +// NOTE(mal): Generic 64-bit hash function (https://nullprogram.com/blog/2018/07/31/) +// Reversible, so no collisions. Assumes all bits of the pointer matter. +MD_FUNCTION_IMPL MD_u64 +MD_HashPointer(void *p) +{ + MD_u64 h = (MD_u64)p; + h = (h ^ (h >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + h = (h ^ (h >> 27)) * UINT64_C(0x94d049bb133111eb); + h = h ^ (h >> 31); + return h; +} + +MD_FUNCTION_IMPL MD_MapSlot * +MD_PtrMap_Lookup(MD_PtrMap *map, void *key) // NOTE(mal): Or MD_PtrFromPtr +{ + _MD_Map_Initialize(map); + + MD_MapSlot *slot = 0; + + MD_u64 hash = MD_HashPointer(key); MD_u64 index = hash % map->table_size; for(MD_MapSlot *candidate = map->table[index]; candidate; candidate = candidate->next) { @@ -974,15 +1071,17 @@ _MD_Map_Lookup(MD_Map *map, MD_u64 hash) break; } } + return slot; } MD_FUNCTION_IMPL MD_b32 -_MD_Map_Insert(MD_StringMap *map, MD_MapCollisionRule collision_rule, MD_u64 hash, void *value) +MD_PtrMap_Insert(MD_PtrMap *map, MD_MapCollisionRule collision_rule, void *key, void *value) { _MD_Map_Initialize(map); MD_MapSlot *slot = 0; + MD_u64 hash = MD_HashPointer(key); MD_u64 index = hash % map->table_size; for(MD_MapSlot *candidate = map->table[index]; candidate; candidate = candidate->next) @@ -1027,56 +1126,6 @@ _MD_Map_Insert(MD_StringMap *map, MD_MapCollisionRule collision_rule, MD_u64 has return !!slot; } -///////////////////////////////////////////// -//~ NOTE(mal): MD_StringMap -MD_FUNCTION_IMPL MD_MapSlot * -MD_StringMap_Lookup(MD_StringMap *map, MD_String8 string) // NOTE(mal): Or MD_PtrFromString -{ - MD_MapSlot *slot = _MD_Map_Lookup(map, MD_HashString(string)); - return slot; -} - -MD_FUNCTION_IMPL MD_b32 -MD_StringMap_Insert(MD_StringMap *map, MD_MapCollisionRule collision_rule, MD_String8 string, void *value) -{ - MD_b32 result = _MD_Map_Insert(map, collision_rule, MD_HashString(string), value); - return result; -} - -// NOTE(mal): Original MD_NodeTable interface -#define MD_NodeTable_Lookup(map, string) MD_StringMap_Lookup(map, string) -#define MD_NodeTable_Insert(map, collision_rule, string, node) MD_StringMap_Insert(map, collision_rule, string, (void *) node) - -///////////////////////////////////////////// -//~ NOTE(mal): MD_PtrMap - -// NOTE(mal): Generic 64-bit hash function (https://nullprogram.com/blog/2018/07/31/) -// Assumes all bits of the pointer matter and that there's a b -MD_FUNCTION_IMPL MD_u64 -MD_HashPointer(void *p) -{ - MD_u64 h = (MD_u64)p; - h = (h ^ (h >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); - h = (h ^ (h >> 27)) * UINT64_C(0x94d049bb133111eb); - h = h ^ (h >> 31); - return h; -} - -MD_FUNCTION_IMPL MD_MapSlot * -MD_PtrMap_Lookup(MD_PtrMap *map, void *key) // NOTE(mal): Or MD_PtrFromPtr -{ - MD_MapSlot *slot = _MD_Map_Lookup(map, MD_HashPointer(key)); - return slot; -} - -// TODO(mal): Remove collision_rule parameter and assume MD_MapCollisionRule_Overwrite? -MD_FUNCTION_IMPL MD_b32 -MD_PtrMap_Insert(MD_PtrMap *map, MD_MapCollisionRule collision_rule, void *key, void *value) -{ - MD_b32 result = _MD_Map_Insert(map, collision_rule, MD_HashPointer(key), value); - return result; -} - MD_FUNCTION_IMPL MD_b32 MD_TokenKindIsWhitespace(MD_TokenKind kind) { diff --git a/tests/sanity_tests.c b/tests/sanity_tests.c index b8435fd..ca6151d 100644 --- a/tests/sanity_tests.c +++ b/tests/sanity_tests.c @@ -488,5 +488,60 @@ int main(void) } } + Test("Hash maps") + { + MD_String8 keys[] = + { + MD_S8Lit("\xed\x80\x73\x71\x78\xba\xff\xd6\x87\x83\xcd\x20\x28\xf7\x1c\xc1\x5f\xca\x98\x9c\x5a\xab\x0c\xae\x9a\x60\x57\x03\xeb\x1f\xde\x99"), + MD_S8Lit("\x4c\x80\xb7\x8b\xbf\x65\x5a\x4b\xc1\x2a\xc3\x5f\xe1\x66\xfb\x0d\x72\x83\x1c\x63\xba\xb5\x97\x02\x3f\x6a\xe0\x2a\x1b\x82\x07\x76"), + MD_S8Lit("\xd8\xfd\x11\x4b\x04\xdf\xe5\x20\x5b\xd6\x4f\x87\x00\x70\x6a\xc8\xde\xed\xc7\x79\xdb\x87\x24\x36\xa8\x7a\x31\x41\x00\x57\xbd\x8d"), + }; + + // NOTE(mal): True for MD_HashString is djb2 + { + TestResult(MD_HashString(keys[0]) == 1 && MD_HashString(keys[1]) == 1 && MD_HashString(keys[2]) == 3); + } + + { + MD_MapCollisionRule rules[] = { MD_MapCollisionRule_Chain, MD_MapCollisionRule_Overwrite }; + for(int i_rule = 0; i_rule < MD_ArrayCount(rules); ++i_rule) + { + MD_StringMap map = {0}; + MD_StringMap_Insert(&map, rules[i_rule], keys[0], (void *)0); + MD_StringMap_Insert(&map, rules[i_rule], keys[1], (void *)1); + MD_StringMap_Insert(&map, rules[i_rule], keys[2], (void *)2); + + MD_MapSlot *slot0 = MD_StringMap_Lookup(&map, keys[0]); + MD_MapSlot *slot1 = MD_StringMap_Lookup(&map, keys[1]); + MD_MapSlot *slot2 = MD_StringMap_Lookup(&map, keys[2]); + + TestResult(slot0 && slot0->value == (void *)0 && + slot1 && slot1->value == (void *)1 && + slot2 && slot2->value == (void *)2); + } + } + + { + MD_MapCollisionRule rules[] = { MD_MapCollisionRule_Chain, MD_MapCollisionRule_Overwrite }; + for(int i_rule = 0; i_rule < MD_ArrayCount(rules); ++i_rule) + { + MD_PtrMap map = {0}; + MD_PtrMap_Insert(&map, rules[i_rule], (void *)0, (void *)0); + MD_PtrMap_Insert(&map, rules[i_rule], (void *)1, (void *)1); + MD_PtrMap_Insert(&map, rules[i_rule], (void *)2, (void *)2); + + MD_MapSlot *slot0 = MD_PtrMap_Lookup(&map, (void *)0); + MD_MapSlot *slot1 = MD_PtrMap_Lookup(&map, (void *)1); + MD_MapSlot *slot2 = MD_PtrMap_Lookup(&map, (void *)2); + + TestResult(slot0 && slot0->value == (void *) 0 && + slot1 && slot1->value == (void *) 1 && + slot2 && slot2->value == (void *) 2); + } + } + + + } + return 0; } \ No newline at end of file