Serene Runtime 1.0.0
C runtime for the Serene programming language
Loading...
Searching...
No Matches
hashmap.c File Reference

Notes: More...

#include "serene/rt/impl/hashmap.h"
#include <stdint.h>
#include "serene/rt/context.h"
#include "serene/rt/engine.h"
#include "serene/utils.h"
#include <assert.h>
#include <string.h>
Include dependency graph for hashmap.c:

Go to the source code of this file.

Data Structures

struct  hmap_collision_t
 A simple alist to manage collision nodes. More...
 

Macros

#define HMAP_LOG(...)
 

Typedefs

typedef struct hmap_collision_t hmap_collision_t
 A simple alist to manage collision nodes.
 

Functions

static bool is_collision_node (void *p)
 Return true if the pointer's LSB is 1 indicating a collision.
 
static hmap_collision_tuntag_ptr (void *ptr)
 Since for non collision nodes the LSB is 0 then we don't need to untag it.
 
static void * tag_ptr (void *ptr)
 
hmap_hash_t hmap_hash (srn_context_t *ctx, const void *data, size_t len)
 This is a simple hack to mock the hash function during tests to force a high collision hashing function.
 
static bool hmap_key_equal (const hmap_key_t *a, const hmap_key_t *b)
 Compare key a with key b
 
static bool is_key_in_collision_node (const hmap_control_t *ctl, srn_context_t *ctx, hmap_collision_t *head, hmap_hash_t hash, hmap_key_t *k)
 
static uint32_t hmap_hash_fragment (hmap_hash_t hash, uint8_t shift)
 Extract a HMAP_MAK-bit chunk from the hash at the given shift.
 
static hmap_bitmap_t hmap_bitpos (hmap_hash_t hash, uint8_t shift)
 Bit position corresponding to this fragment.
 
static size_t hmap_popcount (hmap_bitmap_t bm)
 
static size_t hmap_number_of_entries (const hmap_node_t *node)
 Number of entries (data + node) in a node regardless of a data pointer or a node pointer.
 
static size_t hmap_entry_index (const hmap_node_t *node, hmap_bitmap_t bitpos)
 Index in the packed entries array for a given bitpos (either datamap or nodemap).
 
static bool hmap_has_data_at (const hmap_node_t *node, hmap_bitmap_t bitpos)
 Is this bitpos occupied by a data (kv) entry?
 
static bool hmap_has_node_at (const hmap_node_t *node, hmap_bitmap_t bitpos)
 Is this bitpos occupied by a child node?
 
static void * hmap_get_entry_at (const hmap_node_t *node, hmap_bitmap_t bitpos)
 Make sure the result with is_collision_node and case the result to either an entry or a collision node.
 
static hmap_node_thmap_get_child_at (const hmap_node_t *node, hmap_bitmap_t bitpos)
 Fetch the child node pointer at a given bitpos; we must ensure nodemap has this bit.
 
static bool hmap_at_max_depth (uint32_t shift)
 
static hmap_kv_entry_thmap_new_kv_entry (srn_context_t *ctx, hmap_hash_t hash, hmap_key_t *k, void *v)
 Abstract away the kv entry allocation.
 
static hmap_node_tcreate_empty_node (srn_context_t *ctx)
 
static hmap_node_thmap_singleton_node (srn_context_t *ctx, hmap_hash_t hash, uint32_t shift, hmap_kv_entry_t *kv)
 
static hmap_data_thmap_copy_entries (srn_context_t *ctx, hmap_data_t *entries, size_t count)
 Copy entries array into a new one, returning pointer.
 
static hmap_node_thmap_merge_two_kv (srn_context_t *ctx, hmap_kv_entry_t *kv1, hmap_kv_entry_t *kv2, uint32_t shift)
 Merge two distinct kv entries into a sub-trie.
 
static hmap_node_thmap_insert_node (const hmap_control_t *ctl, srn_context_t *ctx, const hmap_node_t *node, hmap_hash_t hash, hmap_key_t *k, void *v, uint32_t shift, bool *out_added)
 Recursively insert a new node containing the k and v.
 
void * hmap_lookup_in_collision (const hmap_control_t *ctl, srn_context_t *ctx, hmap_collision_t *cnode, hmap_hash_t hash, const hmap_key_t *k, void *default_value)
 
static void * hmap_lookup_in_node (const hmap_control_t *ctl, srn_context_t *ctx, const hmap_node_t *node, hmap_hash_t hash, const hmap_key_t *k, uint32_t shift, void *default_value)
 
static bool hmap_internal_cmp (srn_context_t *ctx, const hmap_key_t *a, const hmap_key_t *b)
 
static hmap_hash_t hmap_hash_internal (srn_context_t *ctx, const hmap_key_t *k)
 
hmap_t hmap_insert_ctl (const hmap_control_t *ctl, srn_context_t *ctx, const hmap_t *hmap, hmap_key_t *k, void *v)
 Just like the hmap_insert function but, it receives a hmap_control_t to customize the comparison and hashing function.
 
void * hmap_lookup_ctl (const hmap_control_t *ctl, srn_context_t *ctx, const hmap_t *hmap, const hmap_key_t *k, void *default_value)
 Just like the hmap_lookup function but, it receives a hmap_control_t to customize the comparison and hashing function.
 
hmap_t hmap_empty (srn_context_t *ctx)
 Create, initialize and return a new hashmap.
 
hmap_t hmap_insert (srn_context_t *ctx, const hmap_t *hmap, hmap_key_t *k, void *v)
 Insert the given key k with the value v in the given hash hmap and return the new map.
 
void * hmap_lookup (srn_context_t *ctx, const hmap_t *hmap, const hmap_key_t *k, void *default_value)
 Lookup the given k in the given hmap and return the value if it's been found.
 
hmap_key_thmap_make_key (srn_context_t *ctx, void *data, size_t len)
 Create a new key out of the given data, with the given len.
 

Variables

hmap_control_t hmap_default_control
 

Detailed Description

Notes:

  • Persistent: old nodes are never mutated.
  • hmap_node_t->entries is a single mixed array. Positions are determined by the ascending order of set bits in (datamap | nodemap). For a given bit:
    • if bit ∈ datamap -> entries[idx] is (hmap_kv_entry_t*) OR a tagged collision*
    • if bit ∈ nodemap -> entries[idx] is (hmap_node_t*)
  • Collision lists are stored in data positions (datamap bit set) by tagging the pointer's LSB = 1. Normal child nodes have LSB = 0.
    • Collisions are linked lists that form an associated list.

Safety: pointers returned by srn_allocate are naturally aligned. We only use LSB tagging for child pointers (never for kv pointers). Untag before deref.

Definition in file hashmap.c.

Macro Definition Documentation

◆ HMAP_LOG

#define HMAP_LOG ( ...)

Definition at line 49 of file hashmap.c.

Typedef Documentation

◆ hmap_collision_t

typedef struct hmap_collision_t hmap_collision_t

A simple alist to manage collision nodes.

Function Documentation

◆ create_empty_node()

static hmap_node_t * create_empty_node ( srn_context_t * ctx)
inlinestatic

Definition at line 195 of file hashmap.c.

195 {
196 assert(ctx != nullptr);
198 n->nodemap = 0;
199 n->datamap = 0;
200 n->entries = nullptr;
201 return n;
202}
int n
Definition acutest.h:538
#define ALLOC(ctx, T)
Definition context.h:82
Here is the caller graph for this function:

◆ hmap_at_max_depth()

static bool hmap_at_max_depth ( uint32_t shift)
inlinestatic

Definition at line 180 of file hashmap.c.

180 {
181 return shift + HMAP_SHIFT >= HMAP_HASH_SIZE;
182}
#define HMAP_HASH_SIZE
By default we use 32bit hashes.
Definition hashmap.h:57
#define HMAP_SHIFT
Definition hashmap.h:55
Here is the caller graph for this function:

◆ hmap_bitpos()

static hmap_bitmap_t hmap_bitpos ( hmap_hash_t hash,
uint8_t shift )
inlinestatic

Bit position corresponding to this fragment.

Definition at line 136 of file hashmap.c.

136 {
137 return (hmap_bitmap_t)1U << hmap_hash_fragment(hash, shift);
138}
static uint32_t hmap_hash_fragment(hmap_hash_t hash, uint8_t shift)
Extract a HMAP_MAK-bit chunk from the hash at the given shift.
Definition hashmap.c:128
HMAP_BITMAP_TYPE hmap_bitmap_t
Definition hashmap.h:62
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_copy_entries()

static hmap_data_t * hmap_copy_entries ( srn_context_t * ctx,
hmap_data_t * entries,
size_t count )
static

Copy entries array into a new one, returning pointer.

Definition at line 218 of file hashmap.c.

218 {
219 if (count == 0) {
220 return nullptr;
221 }
222 hmap_data_t *new_entries = ALLOCN(ctx, hmap_data_t, count);
223 for (size_t i = 0; i < count; ++i) {
224 new_entries[i] = entries[i];
225 }
226 return new_entries;
227}
#define ALLOCN(ctx, T, N)
Definition context.h:83
void * hmap_data_t
Definition hashmap.h:63
Here is the caller graph for this function:

◆ hmap_empty()

hmap_t hmap_empty ( srn_context_t * ctx)
nodiscard

Create, initialize and return a new hashmap.

Definition at line 612 of file hashmap.c.

612 {
613 UNUSED(ctx);
614 hmap_t map = {.len = 0, .root = nullptr};
615 return map;
616}
#define UNUSED(x)
Definition utils.h:43
Here is the caller graph for this function:

◆ hmap_entry_index()

static size_t hmap_entry_index ( const hmap_node_t * node,
hmap_bitmap_t bitpos )
inlinestatic

Index in the packed entries array for a given bitpos (either datamap or nodemap).

Definition at line 150 of file hashmap.c.

150 {
151 // Basically we're trying to count the set bits befor `before`
152 // and use it as the index in the packed entries
153 hmap_bitmap_t before = (node->datamap | node->nodemap) & (bitpos - 1U);
154 return hmap_popcount(before);
155}
static size_t hmap_popcount(hmap_bitmap_t bm)
Definition hashmap.c:140
hmap_bitmap_t datamap
Definition hashmap.h:79
hmap_bitmap_t nodemap
Definition hashmap.h:80
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_get_child_at()

static hmap_node_t * hmap_get_child_at ( const hmap_node_t * node,
hmap_bitmap_t bitpos )
inlinestatic

Fetch the child node pointer at a given bitpos; we must ensure nodemap has this bit.

Definition at line 175 of file hashmap.c.

175 {
176 size_t idx = hmap_entry_index(node, bitpos);
177 return (hmap_node_t *)node->entries[idx];
178}
static size_t hmap_entry_index(const hmap_node_t *node, hmap_bitmap_t bitpos)
Index in the packed entries array for a given bitpos (either datamap or nodemap).
Definition hashmap.c:150
hmap_data_t * entries
This is really a (void **) which each entry in the array will be either (mutually exclusive):
Definition hashmap.h:92
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_get_entry_at()

static void * hmap_get_entry_at ( const hmap_node_t * node,
hmap_bitmap_t bitpos )
inlinestatic

Make sure the result with is_collision_node and case the result to either an entry or a collision node.

Definition at line 169 of file hashmap.c.

169 {
170 return node->entries[hmap_entry_index(node, bitpos)];
171}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_has_data_at()

static bool hmap_has_data_at ( const hmap_node_t * node,
hmap_bitmap_t bitpos )
inlinestatic

Is this bitpos occupied by a data (kv) entry?

Definition at line 158 of file hashmap.c.

158 {
159 return (node->datamap & bitpos) != 0;
160}
Here is the caller graph for this function:

◆ hmap_has_node_at()

static bool hmap_has_node_at ( const hmap_node_t * node,
hmap_bitmap_t bitpos )
inlinestatic

Is this bitpos occupied by a child node?

Definition at line 163 of file hashmap.c.

163 {
164 return (node->nodemap & bitpos) != 0;
165}
Here is the caller graph for this function:

◆ hmap_hash()

hmap_hash_t hmap_hash ( srn_context_t * ctx,
const void * data,
size_t len )

This is a simple hack to mock the hash function during tests to force a high collision hashing function.

Outside of tests, this will be a wrapper around srn_hash.

Definition at line 78 of file hashmap.c.

78 {
79 return srn_hash(ctx->engine, data, len);
80}
srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len)
Definition engine.c:131
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_hash_fragment()

static uint32_t hmap_hash_fragment ( hmap_hash_t hash,
uint8_t shift )
inlinestatic

Extract a HMAP_MAK-bit chunk from the hash at the given shift.

Definition at line 128 of file hashmap.c.

128 {
129 /* if (shift > 32) { */
130 /* PANIC("'shift' should be less than 32\n"); */
131 /* } */
132 return (hash >> shift) & HMAP_MASK;
133}
#define HMAP_MASK
Definition hashmap.h:56
Here is the caller graph for this function:

◆ hmap_hash_internal()

static hmap_hash_t hmap_hash_internal ( srn_context_t * ctx,
const hmap_key_t * k )
static

Definition at line 554 of file hashmap.c.

554 {
555 PANIC_IF_NULL(k);
556 return hmap_hash(ctx, k->data, k->len);
557}
hmap_hash_t hmap_hash(srn_context_t *ctx, const void *data, size_t len)
This is a simple hack to mock the hash function during tests to force a high collision hashing functi...
Definition hashmap.c:78
void * data
len 0 -> data == nullptr
Definition hashmap.h:68
size_t len
Definition hashmap.h:69
#define PANIC_IF_NULL(ptr)
Definition utils.h:64
Here is the call graph for this function:

◆ hmap_insert()

hmap_t hmap_insert ( srn_context_t * ctx,
const hmap_t * hmap,
hmap_key_t * k,
void * v )
nodiscard

Insert the given key k with the value v in the given hash hmap and return the new map.

The new map will share parts of its structure with the old map and never mutate the old map.

Definition at line 618 of file hashmap.c.

618 {
619 return hmap_default_control.insert(&hmap_default_control, ctx, hmap, k, v);
620}
hmap_control_t hmap_default_control
Definition hashmap.c:636
Here is the caller graph for this function:

◆ hmap_insert_ctl()

hmap_t hmap_insert_ctl ( const hmap_control_t * ctl,
srn_context_t * ctx,
const hmap_t * hmap,
hmap_key_t * k,
void * v )
nodiscard

Just like the hmap_insert function but, it receives a hmap_control_t to customize the comparison and hashing function.

Definition at line 559 of file hashmap.c.

560 {
561 PANIC_IF_NULL(ctl);
562 PANIC_IF_NULL(ctx);
563 PANIC_IF_NULL(hmap);
564 PANIC_IF_NULL(k);
565
566 hmap_hash_t hash = ctl->hash(ctx, k);
567 hmap_t new_map = *hmap;
568
569 HMAP_LOG("Trying to insert a key with the hash: %x", hash);
570
571 if (hmap->root == nullptr) {
572 // First insertion: just create a singleton node.
573 HMAP_LOG("First insertion on an empty map");
574 hmap_kv_entry_t *kv = hmap_new_kv_entry(ctx, hash, k, v);
575 hmap_node_t *root = hmap_singleton_node(ctx, hash, 0, kv);
576
577 new_map.root = root;
578 new_map.len = hmap->len + 1;
579 return new_map;
580 }
581 HMAP_LOG("Find the correct node to insert the key");
582 // Create the intermediate nodes recursively
583 bool added = false;
584 hmap_node_t *new_root = hmap_insert_node(ctl, ctx, hmap->root, hash, k, v, 0, &added);
585
586 new_map.root = new_root;
587 if (added) {
588 HMAP_LOG("The key was added successfully");
589 new_map.len = hmap->len + 1;
590 } else {
591 HMAP_LOG("The key existed, just updated the value");
592 new_map.len = hmap->len;
593 }
594 return new_map;
595}
static hmap_kv_entry_t * hmap_new_kv_entry(srn_context_t *ctx, hmap_hash_t hash, hmap_key_t *k, void *v)
Abstract away the kv entry allocation.
Definition hashmap.c:185
#define HMAP_LOG(...)
Definition hashmap.c:49
static hmap_node_t * hmap_insert_node(const hmap_control_t *ctl, srn_context_t *ctx, const hmap_node_t *node, hmap_hash_t hash, hmap_key_t *k, void *v, uint32_t shift, bool *out_added)
Recursively insert a new node containing the k and v.
Definition hashmap.c:298
static hmap_node_t * hmap_singleton_node(srn_context_t *ctx, hmap_hash_t hash, uint32_t shift, hmap_kv_entry_t *kv)
Definition hashmap.c:204
HMAP_HASH_TYPE hmap_hash_t
Definition hashmap.h:61
hmap_hash_t(* hash)(srn_context_t *ctx, const hmap_key_t *k)
Definition hashmap.h:105
Definition hashmap.h:72
hmap_node_t * root
Definition hashmap.h:98
size_t len
Number of key/value pairs in the map.
Definition hashmap.h:97
Here is the call graph for this function:

◆ hmap_insert_node()

static hmap_node_t * hmap_insert_node ( const hmap_control_t * ctl,
srn_context_t * ctx,
const hmap_node_t * node,
hmap_hash_t hash,
hmap_key_t * k,
void * v,
uint32_t shift,
bool * out_added )
static

Recursively insert a new node containing the k and v.

out_addad is a output parameter. If set to true by the function. I indicates that there is a new node made by the function. Otherwise the returning node is the old node.

Definition at line 298 of file hashmap.c.

300 {
301 PANIC_IF_NULL(ctl);
302 hmap_bitmap_t bit = hmap_bitpos(hash, shift);
303 bool has_data = hmap_has_data_at(node, bit);
304 bool has_child = hmap_has_node_at(node, bit);
305
306 HMAP_LOG("Recursive insert with: (bit %u) (shift %u) (has_data %s) (has_child %s)", bit, shift,
307 has_data ? "yes" : "no", has_child ? "yes" : "no");
308 PANIC_IF(shift >= HMAP_HASH_SIZE, "'shift' is not within the boundaries");
309 // Without the new entry
310 size_t count = hmap_number_of_entries(node);
311
312 //= Case A
313 if (!has_data && !has_child) {
314 HMAP_LOG("Case A");
315 // Neither data nor node. Insert a new kv entry here.
316 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
317
318 hmap_node_t *new_node = create_empty_node(ctx);
319 new_node->datamap = node->datamap | bit;
320 new_node->nodemap = node->nodemap;
321
322 new_node->entries = ALLOCN(ctx, hmap_data_t, count + 1);
323
324 size_t insert_idx = hmap_entry_index(new_node, bit);
325
326 // Copy old entries (pointer copy), inserting the new kv at insert_idx.
327 size_t src = 0;
328
329 for (size_t dst = 0; dst <= count; ++dst) {
330 if (dst == insert_idx) {
331 new_node->entries[dst] = new_kv;
332 } else {
333 new_node->entries[dst] = node->entries[src++];
334 }
335 }
336
337 *out_added = true;
338 return new_node;
339 }
340
341 //= Case B
342 if (has_data) {
343 HMAP_LOG("Case B");
344 // There is a data (kv or collision node) at this position.
345 size_t idx = hmap_entry_index(node, bit);
346 void *ptr = node->entries[idx];
347 PANIC_IF_NULL(ptr);
348
349 //== Case B.1 -- Collision node
350 if (is_collision_node(ptr)) {
351 HMAP_LOG("Case B: Hit a collision node");
352 // The pointer is taggend. So we have a collision node at hand. we need
353 // to insert the new entry in the collision node.
354 hmap_collision_t *head = untag_ptr(ptr);
355
356 // first we have to check whether the key is already in the collision node
357 // or not.
358 bool key_exist = is_key_in_collision_node(ctl, ctx, head, hash, k);
359
360 // Since it's an alist, we just add the new value even if the key is
361 // already here, on look up we always fetch the newer value
362 hmap_collision_t *new_head = ALLOC(ctx, hmap_collision_t);
363 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
364 new_head->next = head;
365 new_head->kv = new_kv;
366
367 hmap_node_t *new_node = create_empty_node(ctx);
368 new_node->datamap = node->datamap;
369 new_node->nodemap = node->nodemap;
370 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
371
372 // Put back the new collision node
373 new_node->entries[idx] = tag_ptr(new_head);
374 *out_added = ((!key_exist) != 0);
375 return new_node;
376 }
377
378 // LSB is 0 so we can just cast it
379 hmap_kv_entry_t *existing_kv = (hmap_kv_entry_t *)ptr;
380
381 //== Case B.2 -- Same key exists
382 if (existing_kv->hash == hash && (int)ctl->cmp(ctx, existing_kv->k, k)) {
383 HMAP_LOG("Case B: The key already exist");
384 // We need to replace the entry with the new value in the new node
385 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
386
387 hmap_node_t *new_node = create_empty_node(ctx);
388 new_node->datamap = node->datamap;
389 new_node->nodemap = node->nodemap;
390 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
391 new_node->entries[idx] = new_kv;
392
393 // The key existed, we just replaced it
394 *out_added = false;
395 return new_node;
396 }
397
398 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
399
400 //== Case B.3 -- We're at the leaf nodes and have to create a collision
401 if (hmap_at_max_depth(shift)) {
402 // Eventhough we are not sure that we're fully colliding, since only two
403 // more bits left and we're at the max depth, we just treat it as a full
404 // collision
405 HMAP_LOG("Case B: Partial collision at maximum depth");
407 c1->next = nullptr;
408 c1->kv = existing_kv;
409
411 c2->next = c1;
412 c2->kv = new_kv;
413
414 hmap_node_t *new_node = create_empty_node(ctx);
415 // keeping it a data slot
416 new_node->datamap = node->datamap;
417 new_node->nodemap = node->nodemap;
418 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
419 new_node->entries[idx] = tag_ptr(c2);
420
421 *out_added = true;
422 return new_node;
423 }
424
425 //== Case B.4
426 HMAP_LOG("Case B: Partial collision");
427 // Collision on this position for the current node. Not a full-fledge
428 // collision though. We have to create a new node and move both entries
429 // to the new node.
430
431 hmap_node_t *sub_node = hmap_merge_two_kv(ctx, existing_kv, new_kv, shift + HMAP_SHIFT);
432
433 // Now build a new node where this bit in datamap is moved into nodemap.
434 hmap_node_t *new_node = ALLOC(ctx, hmap_node_t);
435
436 hmap_bitmap_t new_datamap = node->datamap & ~bit;
437 hmap_bitmap_t new_nodemap = node->nodemap | bit;
438
439 new_node->datamap = new_datamap;
440 new_node->nodemap = new_nodemap;
441
442 // We replace one data entry with one node, so count unchanged.
443 new_node->entries = ALLOCN(ctx, hmap_data_t, count);
444
445 for (size_t i = 0; i < count; ++i) {
446 new_node->entries[i] = node->entries[i];
447 }
448
449 new_node->entries[idx] = sub_node;
450
451 *out_added = true;
452 return new_node;
453 }
454
455 //= Case C
456 // There is a child node, so we have to keep chasing and creating new nodes
457 // as we move further down
458 size_t idx = hmap_entry_index(node, bit);
459 const hmap_node_t *child = (const hmap_node_t *)node->entries[idx];
460 HMAP_LOG("Case C");
461 bool child_added = false;
462 hmap_node_t *new_child =
463 hmap_insert_node(ctl, ctx, child, hash, k, v, shift + HMAP_SHIFT, &child_added);
464
465 // If nothing changed below, we can just reuse this node (like an update)
466 if (new_child == child) {
467 *out_added = false;
468 // Just reusing the old node as nothing happened
469 return (hmap_node_t *)node;
470 }
471
472 // Otherwise allocate a new node with updated child pointer.
473 hmap_node_t *new_node = create_empty_node(ctx);
474 new_node->datamap = node->datamap;
475 new_node->nodemap = node->nodemap;
476 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
477 new_node->entries[idx] = new_child;
478
479 //*out_added = true;
480 *out_added = child_added;
481 return new_node;
482}
static void * tag_ptr(void *ptr)
Definition hashmap.c:71
static bool hmap_has_data_at(const hmap_node_t *node, hmap_bitmap_t bitpos)
Is this bitpos occupied by a data (kv) entry?
Definition hashmap.c:158
static hmap_node_t * hmap_merge_two_kv(srn_context_t *ctx, hmap_kv_entry_t *kv1, hmap_kv_entry_t *kv2, uint32_t shift)
Merge two distinct kv entries into a sub-trie.
Definition hashmap.c:230
static hmap_node_t * create_empty_node(srn_context_t *ctx)
Definition hashmap.c:195
static bool hmap_has_node_at(const hmap_node_t *node, hmap_bitmap_t bitpos)
Is this bitpos occupied by a child node?
Definition hashmap.c:163
static hmap_data_t * hmap_copy_entries(srn_context_t *ctx, hmap_data_t *entries, size_t count)
Copy entries array into a new one, returning pointer.
Definition hashmap.c:218
static bool is_collision_node(void *p)
Return true if the pointer's LSB is 1 indicating a collision.
Definition hashmap.c:62
static bool is_key_in_collision_node(const hmap_control_t *ctl, srn_context_t *ctx, hmap_collision_t *head, hmap_hash_t hash, hmap_key_t *k)
Definition hashmap.c:110
static hmap_collision_t * untag_ptr(void *ptr)
Since for non collision nodes the LSB is 0 then we don't need to untag it.
Definition hashmap.c:65
static size_t hmap_number_of_entries(const hmap_node_t *node)
Number of entries (data + node) in a node regardless of a data pointer or a node pointer.
Definition hashmap.c:144
static bool hmap_at_max_depth(uint32_t shift)
Definition hashmap.c:180
static hmap_bitmap_t hmap_bitpos(hmap_hash_t hash, uint8_t shift)
Bit position corresponding to this fragment.
Definition hashmap.c:136
A simple alist to manage collision nodes.
Definition hashmap.c:56
hmap_kv_entry_t * kv
Definition hashmap.c:58
struct hmap_collision_t * next
Definition hashmap.c:57
bool(* cmp)(srn_context_t *ctx, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.h:104
hmap_key_t * k
Definition hashmap.h:74
hmap_hash_t hash
Definition hashmap.h:73
#define PANIC_IF(cond, msg)
Definition utils.h:57
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_internal_cmp()

static bool hmap_internal_cmp ( srn_context_t * ctx,
const hmap_key_t * a,
const hmap_key_t * b )
static

Definition at line 548 of file hashmap.c.

548 {
549 UNUSED(ctx);
550 return hmap_key_equal(a, b);
551}
static bool hmap_key_equal(const hmap_key_t *a, const hmap_key_t *b)
Compare key a with key b
Definition hashmap.c:88
Here is the call graph for this function:

◆ hmap_key_equal()

static bool hmap_key_equal ( const hmap_key_t * a,
const hmap_key_t * b )
inlinestatic

Compare key a with key b

Definition at line 88 of file hashmap.c.

88 {
89 if (a == b) {
90 // Same pointers are definitely the same object in memory regardless
91 // of what compiler might see
92 return true;
93 }
94
95 if (a == nullptr || b == nullptr || (a->len != b->len)) {
96 return false;
97 }
98
99 if (a->len == 0 && b->len == 0) {
100 return true;
101 }
102
103 if (a->len == 0) {
104 return false;
105 }
106
107 return memcmp(a->data, b->data, a->len) == 0;
108}
Here is the caller graph for this function:

◆ hmap_lookup()

void * hmap_lookup ( srn_context_t * ctx,
const hmap_t * hmap,
const hmap_key_t * k,
void * default_value )
nodiscard

Lookup the given k in the given hmap and return the value if it's been found.

Otherwise return the default_value value. Since a nullptr is a valid value for any keys, returning a nullptr is not a good option to indicate that the key does not exist, So it's safer to use explicit values to differenciate between a missing key and a legit nullptr as a value.

Definition at line 622 of file hashmap.c.

623 {
624 return hmap_default_control.lookup(&hmap_default_control, ctx, hmap, k, default_value);
625}
Here is the caller graph for this function:

◆ hmap_lookup_ctl()

void * hmap_lookup_ctl ( const hmap_control_t * ctl,
srn_context_t * ctx,
const hmap_t * hmap,
const hmap_key_t * k,
void * default_value )

Just like the hmap_lookup function but, it receives a hmap_control_t to customize the comparison and hashing function.

Definition at line 597 of file hashmap.c.

598 {
599 PANIC_IF_NULL(ctl);
600 PANIC_IF_NULL(ctx);
601 PANIC_IF_NULL(hmap);
602 PANIC_IF_NULL(k);
603
604 if (hmap->root == nullptr) {
605 return default_value;
606 }
607
608 hmap_hash_t hash = ctl->hash(ctx, k);
609 return hmap_lookup_in_node(ctl, ctx, hmap->root, hash, k, 0, default_value);
610}
static void * hmap_lookup_in_node(const hmap_control_t *ctl, srn_context_t *ctx, const hmap_node_t *node, hmap_hash_t hash, const hmap_key_t *k, uint32_t shift, void *default_value)
Definition hashmap.c:504
Here is the call graph for this function:

◆ hmap_lookup_in_collision()

void * hmap_lookup_in_collision ( const hmap_control_t * ctl,
srn_context_t * ctx,
hmap_collision_t * cnode,
hmap_hash_t hash,
const hmap_key_t * k,
void * default_value )

Definition at line 484 of file hashmap.c.

486 {
487 PANIC_IF_NULL(ctl);
488 hmap_collision_t *n = cnode;
489 for (;;) {
490 if (n == nullptr) {
491 // Didn't found the key
492 return default_value;
493 }
494
495 PANIC_IF_NULL(n->kv);
496 if ((n->kv->hash == hash) && (int)ctl->cmp(ctx, n->kv->k, k)) {
497 return n->kv->v;
498 }
499 n = n->next;
500 }
501 return default_value;
502}
Here is the caller graph for this function:

◆ hmap_lookup_in_node()

static void * hmap_lookup_in_node ( const hmap_control_t * ctl,
srn_context_t * ctx,
const hmap_node_t * node,
hmap_hash_t hash,
const hmap_key_t * k,
uint32_t shift,
void * default_value )
static

Definition at line 504 of file hashmap.c.

506 {
507 PANIC_IF_NULL(ctl);
508 HMAP_LOG("Looking up in for haha %x at shift %u", hash, shift);
509 PANIC_IF(shift >= HMAP_HASH_SIZE, "'shift' is not within the boundaries");
510 if (node == nullptr) {
511 return default_value;
512 }
513
514 hmap_bitmap_t bit = hmap_bitpos(hash, shift);
515
516 // Check for a data (kv) entry at current bitpos.
517 if (hmap_has_data_at(node, bit)) {
518 void *ptr = hmap_get_entry_at(node, bit);
519 if (is_collision_node(ptr)) {
520 HMAP_LOG("Hit a collision node");
521 hmap_collision_t *cnode = untag_ptr(ptr);
522 return hmap_lookup_in_collision(ctl, ctx, cnode, hash, k, default_value);
523 }
524 hmap_kv_entry_t *kv = (hmap_kv_entry_t *)ptr;
525
526 if (kv->hash == hash && (int)ctl->cmp(ctx, kv->k, k)) {
527 return kv->v;
528 }
529
530 // If it's not a collision node, nor a match, then we have a key with the
531 // same hash that is not in the map, so treat it as any none existing key
532 return default_value;
533 }
534
535 // Otherwise, if there is a child node at this bit position, recurse.
536 if (hmap_has_node_at(node, bit)) {
537 PANIC_IF(hmap_at_max_depth(shift), "hmap should not have nodes at max level.");
538 const hmap_node_t *child = hmap_get_child_at(node, bit);
539 return hmap_lookup_in_node(ctl, ctx, child, hash, k, shift + HMAP_SHIFT, default_value);
540 }
541
542 // No data, no child, not found.
543 return default_value;
544}
static void * hmap_get_entry_at(const hmap_node_t *node, hmap_bitmap_t bitpos)
Make sure the result with is_collision_node and case the result to either an entry or a collision nod...
Definition hashmap.c:169
static hmap_node_t * hmap_get_child_at(const hmap_node_t *node, hmap_bitmap_t bitpos)
Fetch the child node pointer at a given bitpos; we must ensure nodemap has this bit.
Definition hashmap.c:175
void * hmap_lookup_in_collision(const hmap_control_t *ctl, srn_context_t *ctx, hmap_collision_t *cnode, hmap_hash_t hash, const hmap_key_t *k, void *default_value)
Definition hashmap.c:484
void * v
Definition hashmap.h:75
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_make_key()

hmap_key_t * hmap_make_key ( srn_context_t * ctx,
void * data,
size_t len )

Create a new key out of the given data, with the given len.

Definition at line 627 of file hashmap.c.

627 {
628 PANIC_IF_NULL(ctx);
629 PANIC_IF_NULL(data);
630 hmap_key_t *key = ALLOC(ctx, hmap_key_t);
631 key->data = data;
632 key->len = len;
633 return key;
634}
Note: For key equality we use the memcpy function.
Definition hashmap.h:66
Here is the caller graph for this function:

◆ hmap_merge_two_kv()

static hmap_node_t * hmap_merge_two_kv ( srn_context_t * ctx,
hmap_kv_entry_t * kv1,
hmap_kv_entry_t * kv2,
uint32_t shift )
static

Merge two distinct kv entries into a sub-trie.

kv1 and kv2 assumed to share either the full hash or a prefix

Definition at line 230 of file hashmap.c.

231 {
232 PANIC_IF_NULL(kv1);
233 PANIC_IF_NULL(kv2);
234 if (shift + HMAP_SHIFT >= HMAP_HASH_SIZE) {
235 // We've exhausted the hash bits and still collide, we should
236 // create an explicit collision node. Tag the pointer to indicate the
237 // collision and insert it in the datamap.
238 hmap_node_t *node = create_empty_node(ctx);
239
241 c1->next = nullptr;
242 c1->kv = kv1;
243
245 c2->next = c1;
246 c2->kv = kv2;
247
248 hmap_bitmap_t bit = hmap_bitpos(kv1->hash, shift);
249 node->datamap = bit;
250 node->entries = ALLOC(ctx, hmap_data_t);
251
252 // Tag the pointer to indicate a collision node and dnsert the collision
253 // node in the entries.
254 size_t idx = hmap_entry_index(node, bit);
255 node->entries[idx] = tag_ptr(c2);
256 return node;
257 }
258
259 hmap_bitmap_t bit1 = hmap_bitpos(kv1->hash, shift);
260 hmap_bitmap_t bit2 = hmap_bitpos(kv2->hash, shift);
261
262 if (bit1 != bit2) {
263 // They diverge at this level; we can store both as data entries in one
264 // node.
265 hmap_node_t *node = create_empty_node(ctx);
266 node->nodemap = 0;
267 node->datamap = bit1 | bit2;
268
269 size_t count = 2;
270 node->entries = ALLOCN(ctx, hmap_data_t, count);
271
272 // Determine ordering by bit value.
273 size_t idx1 = (bit1 < bit2) ? 0 : 1;
274 size_t idx2 = 1 - idx1;
275
276 node->entries[idx1] = kv1;
277 node->entries[idx2] = kv2;
278
279 return node;
280 }
281
282 // Same fragment at this level; we need a deeper node.
283 hmap_node_t *child = hmap_merge_two_kv(ctx, kv1, kv2, shift + HMAP_SHIFT);
284
285 hmap_node_t *node = create_empty_node(ctx);
286 node->datamap = 0;
287 node->nodemap = bit1;
288 // Only one entry
289 node->entries = ALLOC(ctx, hmap_data_t);
290 node->entries[0] = child;
291 return node;
292}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_new_kv_entry()

static hmap_kv_entry_t * hmap_new_kv_entry ( srn_context_t * ctx,
hmap_hash_t hash,
hmap_key_t * k,
void * v )
inlinestatic

Abstract away the kv entry allocation.

Definition at line 185 of file hashmap.c.

186 {
187 PANIC_IF_NULL(k);
189 kv->hash = hash;
190 kv->k = k;
191 kv->v = v;
192 return kv;
193}
Here is the caller graph for this function:

◆ hmap_number_of_entries()

static size_t hmap_number_of_entries ( const hmap_node_t * node)
inlinestatic

Number of entries (data + node) in a node regardless of a data pointer or a node pointer.

Definition at line 144 of file hashmap.c.

144 {
145 return hmap_popcount(node->datamap | node->nodemap);
146}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_popcount()

static size_t hmap_popcount ( hmap_bitmap_t bm)
inlinestatic

Definition at line 140 of file hashmap.c.

140{ return srn_popcnt32(bm); }
static uint32_t srn_popcnt32(uint32_t x)
Definition utils.h:192
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_singleton_node()

static hmap_node_t * hmap_singleton_node ( srn_context_t * ctx,
hmap_hash_t hash,
uint32_t shift,
hmap_kv_entry_t * kv )
static

Definition at line 204 of file hashmap.c.

205 {
206 hmap_node_t *node = ALLOC(ctx, hmap_node_t);
207 hmap_bitmap_t bit = hmap_bitpos(hash, shift);
208
209 node->datamap = bit;
210 node->nodemap = 0U;
211 node->entries = ALLOCN(ctx, hmap_data_t, 1);
212 // No need to tag since LSB will be 0 anyway
213 node->entries[0] = kv;
214 return node;
215}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_collision_node()

static bool is_collision_node ( void * p)
inlinestatic

Return true if the pointer's LSB is 1 indicating a collision.

Definition at line 62 of file hashmap.c.

62{ return ((uintptr_t)p & (uintptr_t)1) != 0; }
Here is the caller graph for this function:

◆ is_key_in_collision_node()

static bool is_key_in_collision_node ( const hmap_control_t * ctl,
srn_context_t * ctx,
hmap_collision_t * head,
hmap_hash_t hash,
hmap_key_t * k )
inlinestatic

Definition at line 110 of file hashmap.c.

112 {
113 PANIC_IF_NULL(ctl);
114 PANIC_IF_NULL(ctx);
115
116 hmap_collision_t *n = head;
117 while (n != nullptr) {
118 hmap_kv_entry_t *existing_kv = n->kv;
119 if (existing_kv->hash == hash && (int)ctl->cmp(ctx, existing_kv->k, k)) {
120 return true;
121 }
122 n = n->next;
123 }
124 return false;
125}
Here is the caller graph for this function:

◆ tag_ptr()

static void * tag_ptr ( void * ptr)
inlinestatic

Definition at line 71 of file hashmap.c.

71 {
72 // NOLINTBEGIN(performance-no-int-to-ptr)
73 return (void *)(((uintptr_t)(ptr)) | (uintptr_t)1);
74 // NOLINTEND(performance-no-int-to-ptr)
75}
Here is the caller graph for this function:

◆ untag_ptr()

static hmap_collision_t * untag_ptr ( void * ptr)
inlinestatic

Since for non collision nodes the LSB is 0 then we don't need to untag it.

Definition at line 65 of file hashmap.c.

65 {
66 // NOLINTBEGIN(performance-no-int-to-ptr)
67 return (hmap_collision_t *)(((uintptr_t)(ptr)) & ~(uintptr_t)1);
68 // NOLINTEND(performance-no-int-to-ptr)
69}
Here is the caller graph for this function:

Variable Documentation

◆ hmap_default_control

hmap_control_t hmap_default_control
Initial value:
= {
.insert = &hmap_insert_ctl,
.lookup = &hmap_lookup_ctl,
}
static hmap_hash_t hmap_hash_internal(srn_context_t *ctx, const hmap_key_t *k)
Definition hashmap.c:554
void * hmap_lookup_ctl(const hmap_control_t *ctl, srn_context_t *ctx, const hmap_t *hmap, const hmap_key_t *k, void *default_value)
Just like the hmap_lookup function but, it receives a hmap_control_t to customize the comparison and ...
Definition hashmap.c:597
static bool hmap_internal_cmp(srn_context_t *ctx, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.c:548
hmap_t hmap_insert_ctl(const hmap_control_t *ctl, srn_context_t *ctx, const hmap_t *hmap, hmap_key_t *k, void *v)
Just like the hmap_insert function but, it receives a hmap_control_t to customize the comparison and ...
Definition hashmap.c:559

Definition at line 636 of file hashmap.c.

636 {
637 .cmp = &hmap_internal_cmp,
638 .hash = &hmap_hash_internal,
639 .insert = &hmap_insert_ctl,
640 .lookup = &hmap_lookup_ctl,
641};