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

Notes: More...

#include "serene/rt/impl/hashmap.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "serene/rt/context.h"
#include "serene/rt/engine.h"
#include "serene/utils.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 (const 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, const hmap_t *hmap, 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 (const 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 (const srn_context_t *ctx)
static hmap_node_thmap_singleton_node (const srn_context_t *ctx, hmap_hash_t hash, uint32_t shift, hmap_kv_entry_t *kv)
static hmap_data_thmap_copy_entries (const 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 (const 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, const hmap_t *hmap, 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.
static void * hmap_lookup_in_collision (const hmap_control_t *ctl, const hmap_t *hmap, 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, const hmap_t *hmap, 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 (const hmap_t *hmap, const hmap_key_t *a, const hmap_key_t *b)
static hmap_hash_t hmap_hash_internal (const hmap_t *hmap, const hmap_key_t *k)
hmap_t hmap_insert_ctl (const hmap_control_t *ctl, 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, 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 (const srn_context_t *ctx)
 Create, initialize and return a new hashmap pinned to ctx.
hmap_t hmap_insert (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 (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.
hmap_key_thmap_copy_key (const hmap_t *hmap, const hmap_key_t *k)
 The default copy_key hook.

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 48 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()

hmap_node_t * create_empty_node ( const srn_context_t * ctx)
inlinestatic

Definition at line 196 of file hashmap.c.

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

◆ hmap_at_max_depth()

bool hmap_at_max_depth ( uint32_t shift)
inlinestatic

Definition at line 181 of file hashmap.c.

181 {
182 return shift + HMAP_SHIFT >= HMAP_HASH_SIZE;
183}
#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()

hmap_bitmap_t hmap_bitpos ( hmap_hash_t hash,
uint8_t shift )
inlinestatic

Bit position corresponding to this fragment.

Definition at line 137 of file hashmap.c.

137 {
138 return (hmap_bitmap_t)1U << hmap_hash_fragment(hash, shift);
139}
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:129
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()

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

Copy entries array into a new one, returning pointer.

Definition at line 221 of file hashmap.c.

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

◆ hmap_copy_key()

hmap_key_t * hmap_copy_key ( const hmap_t * hmap,
const hmap_key_t * k )

The default copy_key hook.

Clone the key struct and its bytes into the map's map_ctx so the map never retains pointers into a caller's block.

Definition at line 678 of file hashmap.c.

678 {
679 PANIC_IF_NULL(hmap);
680 PANIC_IF_NULL(k);
681 PANIC_IF_NULL(hmap->map_ctx);
682
683 hmap_key_t *copy = ALLOC(hmap->map_ctx, hmap_key_t);
684 copy->len = k->len;
685 copy->data =
686 (k->len == 0) ? nullptr : srn_copy_bytes(hmap->map_ctx, k->data, k->len, alignof(char));
687 return copy;
688}
Note: For key equality we use the memcpy function.
Definition hashmap.h:66
void * data
len 0 -> data == nullptr
Definition hashmap.h:68
size_t len
Definition hashmap.h:69
const srn_context_t * map_ctx
The context that owns every allocation the map retains.
Definition hashmap.h:104
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
char * srn_copy_bytes(const srn_context_t *ctx, const char *src, size_t len, size_t alignment)
Definition utils.c:56
Here is the call graph for this function:

◆ hmap_empty()

hmap_t hmap_empty ( const srn_context_t * ctx)
nodiscard

Create, initialize and return a new hashmap pinned to ctx.

The pinned context owns every allocation the map (and all of its future versions) retains, so pick a context that lives at least as long as the map.

Definition at line 655 of file hashmap.c.

655 {
656 PANIC_IF_NULL(ctx);
657 hmap_t map = {.len = 0, .root = nullptr, .map_ctx = ctx};
658 return map;
659}
Here is the caller graph for this function:

◆ hmap_entry_index()

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 151 of file hashmap.c.

151 {
152 // Basically we're trying to count the set bits befor `before`
153 // and use it as the index in the packed entries
154 hmap_bitmap_t before = (node->datamap | node->nodemap) & (bitpos - 1U);
155 return hmap_popcount(before);
156}
static size_t hmap_popcount(hmap_bitmap_t bm)
Definition hashmap.c:141
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()

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 176 of file hashmap.c.

176 {
177 size_t idx = hmap_entry_index(node, bitpos);
178 return (hmap_node_t *)node->entries[idx];
179}
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:151
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()

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 170 of file hashmap.c.

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

◆ hmap_has_data_at()

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 159 of file hashmap.c.

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

◆ hmap_has_node_at()

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 164 of file hashmap.c.

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

◆ hmap_hash()

hmap_hash_t hmap_hash ( const 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 77 of file hashmap.c.

77 {
78 return srn_hash(ctx->engine, data, len);
79}
srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len)
Definition engine.c:166
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()

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 129 of file hashmap.c.

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

◆ hmap_hash_internal()

hmap_hash_t hmap_hash_internal ( const hmap_t * hmap,
const hmap_key_t * k )
static

Definition at line 589 of file hashmap.c.

589 {
590 PANIC_IF_NULL(k);
591 return hmap_hash(hmap->map_ctx, k->data, k->len);
592}
hmap_hash_t hmap_hash(const 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:77
Here is the call graph for this function:

◆ hmap_insert()

hmap_t hmap_insert ( 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. All retained allocations go through the map's map_ctx. The control's copy_key clones the key into it, so k may point at stack or transient memory. The value v is stored as is and must outlive the map.

Definition at line 661 of file hashmap.c.

661 {
662 return hmap_default_control.insert(&hmap_default_control, hmap, k, v);
663}
hmap_control_t hmap_default_control
Definition hashmap.c:690
Here is the caller graph for this function:

◆ hmap_insert_ctl()

hmap_t hmap_insert_ctl ( const hmap_control_t * ctl,
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 594 of file hashmap.c.

594 {
595 PANIC_IF_NULL(ctl);
596 PANIC_IF_NULL(hmap);
597 PANIC_IF_NULL(k);
599
600 // Everything the map retains has to live in the map's context, so the map
601 // survives the release of the inserting caller's context. A null map_ctx
602 // means the map was zero-initialized instead of created with hmap_empty.
603 const srn_context_t *map_ctx = hmap->map_ctx;
604 PANIC_IF_NULL(map_ctx);
605
606 hmap_hash_t hash = ctl->hash(hmap, k);
607 hmap_key_t *key = ctl->copy_key(hmap, k);
608 hmap_t new_map = *hmap;
609
610 HMAP_LOG("Trying to insert a key with the hash: %x", hash);
611
612 if (hmap->root == nullptr) {
613 // First insertion, just create a singleton node.
614 HMAP_LOG("First insertion on an empty map");
615 hmap_kv_entry_t *kv = hmap_new_kv_entry(map_ctx, hash, key, v);
616 hmap_node_t *root = hmap_singleton_node(map_ctx, hash, 0, kv);
617
618 new_map.root = root;
619 new_map.len = hmap->len + 1;
620 return new_map;
621 }
622 HMAP_LOG("Find the correct node to insert the key");
623 // Create the intermediate nodes recursively
624 bool added = false;
625 hmap_node_t *new_root = hmap_insert_node(ctl, hmap, hmap->root, hash, key, v, 0, &added);
626
627 new_map.root = new_root;
628 if (added) {
629 HMAP_LOG("The key was added successfully");
630 new_map.len = hmap->len + 1;
631 } else {
632 HMAP_LOG("The key existed, just updated the value");
633 new_map.len = hmap->len;
634 }
635 return new_map;
636}
static hmap_kv_entry_t * hmap_new_kv_entry(const srn_context_t *ctx, hmap_hash_t hash, hmap_key_t *k, void *v)
Abstract away the kv entry allocation.
Definition hashmap.c:187
static hmap_node_t * hmap_singleton_node(const srn_context_t *ctx, hmap_hash_t hash, uint32_t shift, hmap_kv_entry_t *kv)
Definition hashmap.c:205
#define HMAP_LOG(...)
Definition hashmap.c:48
static hmap_node_t * hmap_insert_node(const hmap_control_t *ctl, const hmap_t *hmap, 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:304
HMAP_HASH_TYPE hmap_hash_t
Definition hashmap.h:61
hmap_key_t *(* copy_key)(const hmap_t *hmap, const hmap_key_t *k)
Clone k into the map's map_ctx before the map retains it, so the map never holds a pointer into the i...
Definition hashmap.h:119
hmap_hash_t(* hash)(const hmap_t *hmap, const hmap_key_t *k)
Definition hashmap.h:111
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:
Here is the caller graph for this function:

◆ hmap_insert_node()

hmap_node_t * hmap_insert_node ( const hmap_control_t * ctl,
const hmap_t * hmap,
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 304 of file hashmap.c.

307 {
308 PANIC_IF_NULL(ctl);
309
310 const srn_context_t *ctx = hmap->map_ctx;
311 hmap_bitmap_t bit = hmap_bitpos(hash, shift);
312 bool has_data = hmap_has_data_at(node, bit);
313 bool has_child = hmap_has_node_at(node, bit);
314
315 HMAP_LOG(
316 "Recursive insert with: (bit %u) (shift %u) (has_data %s) (has_child %s)", bit, shift,
317 has_data ? "yes" : "no", has_child ? "yes" : "no"
318 );
319 PANIC_IF(shift >= HMAP_HASH_SIZE, "'shift' is not within the boundaries");
320 // Without the new entry
321 size_t count = hmap_number_of_entries(node);
322
323 //= Case A
324 if (!has_data && !has_child) {
325 HMAP_LOG("Case A");
326 // Neither data nor node. Insert a new kv entry here.
327 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
328
329 hmap_node_t *new_node = create_empty_node(ctx);
330 new_node->datamap = node->datamap | bit;
331 new_node->nodemap = node->nodemap;
332
333 new_node->entries = ALLOCN(ctx, hmap_data_t, count + 1);
334
335 size_t insert_idx = hmap_entry_index(new_node, bit);
336
337 // Copy old entries (pointer copy), inserting the new kv at insert_idx.
338 size_t src = 0;
339
340 for (size_t dst = 0; dst <= count; ++dst) {
341 if (dst == insert_idx) {
342 new_node->entries[dst] = new_kv;
343 } else {
344 new_node->entries[dst] = node->entries[src++];
345 }
346 }
347
348 *out_added = true;
349 return new_node;
350 }
351
352 //= Case B
353 if (has_data) {
354 HMAP_LOG("Case B");
355 // There is a data (kv or collision node) at this position.
356 size_t idx = hmap_entry_index(node, bit);
357 void *ptr = node->entries[idx];
358 PANIC_IF_NULL(ptr);
359
360 //== Case B.1 -- Collision node
361 if (is_collision_node(ptr)) {
362 HMAP_LOG("Case B: Hit a collision node");
363 // The pointer is taggend. So we have a collision node at hand. we need
364 // to insert the new entry in the collision node.
365 hmap_collision_t *head = untag_ptr(ptr);
366
367 // first we have to check whether the key is already in the collision node
368 // or not.
369 bool key_exist = is_key_in_collision_node(ctl, hmap, head, hash, k);
370
371 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
372 hmap_collision_t *new_head = nullptr;
373
374 if (key_exist) {
375 // Rebuild the list with the matching entry replaced. Prepending a
376 // shadowing entry instead would grow the list by one on every update
377 // of the same key, without bound.
378 hmap_collision_t **link = &new_head;
379 for (hmap_collision_t *n = head; n != nullptr; n = n->next) {
381 bool match = n->kv->hash == hash && (int)ctl->cmp(hmap, n->kv->k, k);
382 copy->kv = match ? new_kv : n->kv;
383 copy->next = nullptr;
384 *link = copy;
385 link = &copy->next;
386 }
387 } else {
388 // A genuinely new key is prepended in front of the shared old list.
389 new_head = ALLOC(ctx, hmap_collision_t);
390 new_head->next = head;
391 new_head->kv = new_kv;
392 }
393
394 hmap_node_t *new_node = create_empty_node(ctx);
395 new_node->datamap = node->datamap;
396 new_node->nodemap = node->nodemap;
397 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
398
399 // Put back the new collision node
400 new_node->entries[idx] = tag_ptr(new_head);
401 *out_added = ((!key_exist) != 0);
402 return new_node;
403 }
404
405 // LSB is 0 so we can just cast it
406 hmap_kv_entry_t *existing_kv = (hmap_kv_entry_t *)ptr;
407
408 //== Case B.2 -- Same key exists
409 if (existing_kv->hash == hash && (int)ctl->cmp(hmap, existing_kv->k, k)) {
410 HMAP_LOG("Case B: The key already exist");
411 // We need to replace the entry with the new value in the new node
412 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
413
414 hmap_node_t *new_node = create_empty_node(ctx);
415 new_node->datamap = node->datamap;
416 new_node->nodemap = node->nodemap;
417 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
418 new_node->entries[idx] = new_kv;
419
420 // The key existed, we just replaced it
421 *out_added = false;
422 return new_node;
423 }
424
425 hmap_kv_entry_t *new_kv = hmap_new_kv_entry(ctx, hash, k, v);
426
427 //== Case B.3 -- We're at the leaf nodes and have to create a collision
428 if (hmap_at_max_depth(shift)) {
429 // Eventhough we are not sure that we're fully colliding, since only two
430 // more bits left and we're at the max depth, we just treat it as a full
431 // collision
432 HMAP_LOG("Case B: Partial collision at maximum depth");
434 c1->next = nullptr;
435 c1->kv = existing_kv;
436
438 c2->next = c1;
439 c2->kv = new_kv;
440
441 hmap_node_t *new_node = create_empty_node(ctx);
442 // keeping it a data slot
443 new_node->datamap = node->datamap;
444 new_node->nodemap = node->nodemap;
445 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
446 new_node->entries[idx] = tag_ptr(c2);
447
448 *out_added = true;
449 return new_node;
450 }
451
452 //== Case B.4
453 HMAP_LOG("Case B: Partial collision");
454 // Collision on this position for the current node. Not a full-fledge
455 // collision though. We have to create a new node and move both entries
456 // to the new node.
457
458 hmap_node_t *sub_node = hmap_merge_two_kv(ctx, existing_kv, new_kv, shift + HMAP_SHIFT);
459
460 // Now build a new node where this bit in datamap is moved into nodemap.
461 hmap_node_t *new_node = ALLOC(ctx, hmap_node_t);
462
463 hmap_bitmap_t new_datamap = node->datamap & ~bit;
464 hmap_bitmap_t new_nodemap = node->nodemap | bit;
465
466 new_node->datamap = new_datamap;
467 new_node->nodemap = new_nodemap;
468
469 // We replace one data entry with one node, so count unchanged.
470 new_node->entries = ALLOCN(ctx, hmap_data_t, count);
471
472 for (size_t i = 0; i < count; ++i) {
473 new_node->entries[i] = node->entries[i];
474 }
475
476 new_node->entries[idx] = sub_node;
477
478 *out_added = true;
479 return new_node;
480 }
481
482 //= Case C
483 // There is a child node, so we have to keep chasing and creating new nodes
484 // as we move further down
485 size_t idx = hmap_entry_index(node, bit);
486 const hmap_node_t *child = (const hmap_node_t *)node->entries[idx];
487 HMAP_LOG("Case C");
488 bool child_added = false;
489 hmap_node_t *new_child =
490 hmap_insert_node(ctl, hmap, child, hash, k, v, shift + HMAP_SHIFT, &child_added);
491
492 // If nothing changed below, we can just reuse this node (like an update)
493 if (new_child == child) {
494 *out_added = false;
495 // Just reusing the old node as nothing happened
496 return (hmap_node_t *)node;
497 }
498
499 // Otherwise allocate a new node with updated child pointer.
500 hmap_node_t *new_node = create_empty_node(ctx);
501 new_node->datamap = node->datamap;
502 new_node->nodemap = node->nodemap;
503 new_node->entries = hmap_copy_entries(ctx, node->entries, count);
504 new_node->entries[idx] = new_child;
505
506 //*out_added = true;
507 *out_added = child_added;
508 return new_node;
509}
static hmap_node_t * hmap_merge_two_kv(const 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:233
static void * tag_ptr(void *ptr)
Definition hashmap.c:70
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:159
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:164
static bool is_collision_node(void *p)
Return true if the pointer's LSB is 1 indicating a collision.
Definition hashmap.c:61
static hmap_node_t * create_empty_node(const srn_context_t *ctx)
Definition hashmap.c:196
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:64
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:145
static hmap_data_t * hmap_copy_entries(const srn_context_t *ctx, hmap_data_t *entries, size_t count)
Copy entries array into a new one, returning pointer.
Definition hashmap.c:221
static bool is_key_in_collision_node(const hmap_control_t *ctl, const hmap_t *hmap, hmap_collision_t *head, hmap_hash_t hash, hmap_key_t *k)
Definition hashmap.c:109
static bool hmap_at_max_depth(uint32_t shift)
Definition hashmap.c:181
static hmap_bitmap_t hmap_bitpos(hmap_hash_t hash, uint8_t shift)
Bit position corresponding to this fragment.
Definition hashmap.c:137
A simple alist to manage collision nodes.
Definition hashmap.c:55
hmap_kv_entry_t * kv
Definition hashmap.c:57
struct hmap_collision_t * next
Definition hashmap.c:56
bool(* cmp)(const hmap_t *hmap, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.h:110
hmap_key_t * k
Definition hashmap.h:74
hmap_hash_t hash
Definition hashmap.h:73
#define PANIC_IF(cond, msg)
Definition utils.h:59
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_internal_cmp()

bool hmap_internal_cmp ( const hmap_t * hmap,
const hmap_key_t * a,
const hmap_key_t * b )
static

Definition at line 583 of file hashmap.c.

583 {
584 UNUSED(hmap);
585 return hmap_key_equal(a, b);
586}
static bool hmap_key_equal(const hmap_key_t *a, const hmap_key_t *b)
Compare key a with key b.
Definition hashmap.c:87
#define UNUSED(x)
Definition utils.h:45
Here is the call graph for this function:

◆ hmap_key_equal()

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

Compare key a with key b.

Definition at line 87 of file hashmap.c.

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

◆ hmap_lookup()

void * hmap_lookup ( 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 665 of file hashmap.c.

665 {
666 return hmap_default_control.lookup(&hmap_default_control, hmap, k, default_value);
667}
Here is the caller graph for this function:

◆ hmap_lookup_ctl()

void * hmap_lookup_ctl ( const hmap_control_t * ctl,
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 638 of file hashmap.c.

640 {
641 PANIC_IF_NULL(ctl);
642 PANIC_IF_NULL(hmap);
643 PANIC_IF_NULL(k);
644
645 PANIC_IF_NULL(hmap->map_ctx);
646
647 if (hmap->root == nullptr) {
648 return default_value;
649 }
650
651 hmap_hash_t hash = ctl->hash(hmap, k);
652 return hmap_lookup_in_node(ctl, hmap, hmap->root, hash, k, 0, default_value);
653}
static void * hmap_lookup_in_node(const hmap_control_t *ctl, const hmap_t *hmap, const hmap_node_t *node, hmap_hash_t hash, const hmap_key_t *k, uint32_t shift, void *default_value)
Definition hashmap.c:535
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_lookup_in_collision()

void * hmap_lookup_in_collision ( const hmap_control_t * ctl,
const hmap_t * hmap,
hmap_collision_t * cnode,
hmap_hash_t hash,
const hmap_key_t * k,
void * default_value )
static

Definition at line 511 of file hashmap.c.

514 {
515 assert(ctl != nullptr);
516 assert(hmap != nullptr);
517 assert(hmap->map_ctx != nullptr);
518
519 hmap_collision_t *n = cnode;
520 for (;;) {
521 if (n == nullptr) {
522 // Didn't found the key
523 return default_value;
524 }
525
526 PANIC_IF_NULL(n->kv);
527 if ((n->kv->hash == hash) && (int)ctl->cmp(hmap, n->kv->k, k)) {
528 return n->kv->v;
529 }
530 n = n->next;
531 }
532 return default_value;
533}
Here is the caller graph for this function:

◆ hmap_lookup_in_node()

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

Definition at line 535 of file hashmap.c.

538 {
539 assert(ctl != nullptr);
540 assert(hmap != nullptr);
541 assert(hmap->map_ctx != nullptr);
542
543 HMAP_LOG("Looking up in for haha %x at shift %u", hash, shift);
544 PANIC_IF(shift >= HMAP_HASH_SIZE, "'shift' is not within the boundaries");
545 if (node == nullptr) {
546 return default_value;
547 }
548
549 hmap_bitmap_t bit = hmap_bitpos(hash, shift);
550
551 // Check for a data (kv) entry at current bitpos.
552 if (hmap_has_data_at(node, bit)) {
553 void *ptr = hmap_get_entry_at(node, bit);
554 if (is_collision_node(ptr)) {
555 HMAP_LOG("Hit a collision node");
556 hmap_collision_t *cnode = untag_ptr(ptr);
557 return hmap_lookup_in_collision(ctl, hmap, cnode, hash, k, default_value);
558 }
559 hmap_kv_entry_t *kv = (hmap_kv_entry_t *)ptr;
560
561 if (kv->hash == hash && (int)ctl->cmp(hmap, kv->k, k)) {
562 return kv->v;
563 }
564
565 // If it's not a collision node, nor a match, then we have a key with the
566 // same hash that is not in the map, so treat it as any none existing key
567 return default_value;
568 }
569
570 // Otherwise, if there is a child node at this bit position, recurse.
571 if (hmap_has_node_at(node, bit)) {
572 PANIC_IF(hmap_at_max_depth(shift), "hmap should not have nodes at max level.");
573 const hmap_node_t *child = hmap_get_child_at(node, bit);
574 return hmap_lookup_in_node(ctl, hmap, child, hash, k, shift + HMAP_SHIFT, default_value);
575 }
576
577 // No data, no child, not found.
578 return default_value;
579}
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:170
static void * hmap_lookup_in_collision(const hmap_control_t *ctl, const hmap_t *hmap, hmap_collision_t *cnode, hmap_hash_t hash, const hmap_key_t *k, void *default_value)
Definition hashmap.c:511
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:176
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 669 of file hashmap.c.

669 {
670 PANIC_IF_NULL(ctx);
671 PANIC_IF_NULL(data);
672 hmap_key_t *key = ALLOC(ctx, hmap_key_t);
673 key->data = data;
674 key->len = len;
675 return key;
676}

◆ hmap_merge_two_kv()

hmap_node_t * hmap_merge_two_kv ( const 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 233 of file hashmap.c.

235 {
236 assert(kv1 != nullptr);
237 assert(kv2 != nullptr);
238
239 hmap_bitmap_t bit1 = hmap_bitpos(kv1->hash, shift);
240 hmap_bitmap_t bit2 = hmap_bitpos(kv2->hash, shift);
241
242 if (bit1 != bit2) {
243 // They diverge at this level; we can store both as data entries in one
244 // node. This branch also covers the deepest level, two keys whose
245 // hashes agree on every fragment except the last one are not a
246 // collision, and lookup probes each key's own bit position.
247 hmap_node_t *node = create_empty_node(ctx);
248 node->nodemap = 0;
249 node->datamap = bit1 | bit2;
250
251 size_t count = 2;
252 node->entries = ALLOCN(ctx, hmap_data_t, count);
253
254 // Determine ordering by bit value.
255 size_t idx1 = (bit1 < bit2) ? 0 : 1;
256 size_t idx2 = 1 - idx1;
257
258 node->entries[idx1] = kv1;
259 node->entries[idx2] = kv2;
260
261 return node;
262 }
263
264 if (shift + HMAP_SHIFT >= HMAP_HASH_SIZE) {
265 // Same final fragment and no hash bits left, a genuine full collision.
266 // Create an explicit collision node. Tag the pointer to indicate the
267 // collision and insert it in the datamap.
268 hmap_node_t *node = create_empty_node(ctx);
269
271 c1->next = nullptr;
272 c1->kv = kv1;
273
275 c2->next = c1;
276 c2->kv = kv2;
277
278 node->datamap = bit1;
279 node->entries = ALLOC(ctx, hmap_data_t);
280
281 // Tag the pointer to indicate a collision node and insert the collision
282 // node in the entries.
283 size_t idx = hmap_entry_index(node, bit1);
284 node->entries[idx] = tag_ptr(c2);
285 return node;
286 }
287
288 // Same fragment at this level; we need a deeper node.
289 hmap_node_t *child = hmap_merge_two_kv(ctx, kv1, kv2, shift + HMAP_SHIFT);
290
291 hmap_node_t *node = create_empty_node(ctx);
292 node->datamap = 0;
293 node->nodemap = bit1;
294 // Only one entry
295 node->entries = ALLOC(ctx, hmap_data_t);
296 node->entries[0] = child;
297 return node;
298}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hmap_new_kv_entry()

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

Abstract away the kv entry allocation.

Definition at line 187 of file hashmap.c.

187 {
188 assert(k != nullptr);
190 kv->hash = hash;
191 kv->k = k;
192 kv->v = v;
193 return kv;
194}
Here is the caller graph for this function:

◆ hmap_number_of_entries()

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 145 of file hashmap.c.

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

◆ hmap_popcount()

size_t hmap_popcount ( hmap_bitmap_t bm)
inlinestatic

Definition at line 141 of file hashmap.c.

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

◆ hmap_singleton_node()

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

Definition at line 205 of file hashmap.c.

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

◆ is_collision_node()

bool is_collision_node ( void * p)
inlinestatic

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

Definition at line 61 of file hashmap.c.

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

◆ is_key_in_collision_node()

bool is_key_in_collision_node ( const hmap_control_t * ctl,
const hmap_t * hmap,
hmap_collision_t * head,
hmap_hash_t hash,
hmap_key_t * k )
inlinestatic

Definition at line 109 of file hashmap.c.

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

◆ tag_ptr()

void * tag_ptr ( void * ptr)
inlinestatic

Definition at line 70 of file hashmap.c.

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

◆ untag_ptr()

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 64 of file hashmap.c.

64 {
65 // NOLINTBEGIN(performance-no-int-to-ptr)
66 return (hmap_collision_t *)(((uintptr_t)(ptr)) & ~(uintptr_t)1);
67 // NOLINTEND(performance-no-int-to-ptr)
68}
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,
.copy_key = &hmap_copy_key,
}
static bool hmap_internal_cmp(const hmap_t *hmap, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.c:583
void * hmap_lookup_ctl(const hmap_control_t *ctl, 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:638
hmap_t hmap_insert_ctl(const hmap_control_t *ctl, 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:594
static hmap_hash_t hmap_hash_internal(const hmap_t *hmap, const hmap_key_t *k)
Definition hashmap.c:589
hmap_key_t * hmap_copy_key(const hmap_t *hmap, const hmap_key_t *k)
The default copy_key hook.
Definition hashmap.c:678

Definition at line 690 of file hashmap.c.

690 {
691 .cmp = &hmap_internal_cmp,
692 .hash = &hmap_hash_internal,
693 .insert = &hmap_insert_ctl,
694 .lookup = &hmap_lookup_ctl,
695 .copy_key = &hmap_copy_key,
696};