Serene Runtime 1.0.0
C runtime for the Serene programming language
Loading...
Searching...
No Matches
hashmap.c
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/// @file
20/// Notes:
21/// - Persistent: old nodes are never mutated.
22/// - hmap_node_t->entries is a single mixed array. Positions are determined by
23/// the ascending order of set bits in (datamap | nodemap). For a given bit:
24/// + if bit ∈ datamap -> entries[idx] is (hmap_kv_entry_t*) OR a tagged
25/// collision*
26/// + if bit ∈ nodemap -> entries[idx] is (hmap_node_t*)
27/// - Collision lists are stored in data positions (datamap bit set) by
28/// tagging the pointer's LSB = 1. Normal child nodes have LSB = 0.
29///
30/// + Collisions are linked lists that form an associated list.
31///
32/// Safety: pointers returned by srn_allocate are naturally aligned. We only use
33/// LSB tagging for child pointers (never for kv pointers). Untag before deref.
34
36
37#include <stdint.h>
38
39#include "serene/rt/context.h"
40#include "serene/rt/engine.h" // for HMAP_HASH_FN
41#include "serene/utils.h"
42
43#include <assert.h>
44#include <string.h>
45
46#if defined(HMAP_DEBUG)
47#define HMAP_LOG(...) DBG("HMAP", __VA_ARGS__)
48#else
49#define HMAP_LOG(...)
50#endif
51
52// -----------------------------------------------------------------------------
53// Collisions
54// -----------------------------------------------------------------------------
55/// A simple alist to manage collision nodes
60
61/// Return true if the pointer's LSB is 1 indicating a collision.
62static inline bool is_collision_node(void *p) { return ((uintptr_t)p & (uintptr_t)1) != 0; }
63
64/// Since for non collision nodes the LSB is 0 then we don't need to untag it.
65static inline hmap_collision_t *untag_ptr(void *ptr) {
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}
70
71static inline void *tag_ptr(void *ptr) {
72 // NOLINTBEGIN(performance-no-int-to-ptr)
73 return (void *)(((uintptr_t)(ptr)) | (uintptr_t)1);
74 // NOLINTEND(performance-no-int-to-ptr)
75}
76
77#if !defined(SERENE_TESTS)
78hmap_hash_t hmap_hash(srn_context_t *ctx, const void *data, size_t len) {
79 return srn_hash(ctx->engine, data, len);
80}
81#endif
82
83// -----------------------------------------------------------------------------
84// helpers
85// -----------------------------------------------------------------------------
86
87/// Compare key `a` with key `b`
88static inline bool hmap_key_equal(const hmap_key_t *a, const hmap_key_t *b) {
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}
109
110static inline bool is_key_in_collision_node(const hmap_control_t *ctl, srn_context_t *ctx,
111 hmap_collision_t *head, hmap_hash_t hash,
112 hmap_key_t *k) {
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}
126
127/// Extract a `HMAP_MAK`-bit chunk from the hash at the given shift.
128static inline uint32_t hmap_hash_fragment(hmap_hash_t hash, uint8_t shift) {
129 /* if (shift > 32) { */
130 /* PANIC("'shift' should be less than 32\n"); */
131 /* } */
132 return (hash >> shift) & HMAP_MASK;
133}
134
135/// Bit position corresponding to this fragment.
136static inline hmap_bitmap_t hmap_bitpos(hmap_hash_t hash, uint8_t shift) {
137 return (hmap_bitmap_t)1U << hmap_hash_fragment(hash, shift);
138}
139
140static inline size_t hmap_popcount(hmap_bitmap_t bm) { return srn_popcnt32(bm); }
141
142/// Number of entries (data + node) in a node regardless of a data pointer
143/// or a node pointer
144static inline size_t hmap_number_of_entries(const hmap_node_t *node) {
145 return hmap_popcount(node->datamap | node->nodemap);
146}
147
148/// Index in the packed `entries` array for a given bitpos (either datamap or
149/// nodemap).
150static inline size_t hmap_entry_index(const hmap_node_t *node, hmap_bitmap_t bitpos) {
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}
156
157/// Is this bitpos occupied by a data (kv) entry?
158static inline bool hmap_has_data_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
159 return (node->datamap & bitpos) != 0;
160}
161
162/// Is this bitpos occupied by a child node?
163static inline bool hmap_has_node_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
164 return (node->nodemap & bitpos) != 0;
165}
166
167/// Make sure the result with `is_collision_node` and case the
168/// result to either an entry or a collision node
169static inline void *hmap_get_entry_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
170 return node->entries[hmap_entry_index(node, bitpos)];
171}
172
173/// Fetch the child node pointer at a given bitpos;
174/// we must ensure nodemap has this bit.
175static inline hmap_node_t *hmap_get_child_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
176 size_t idx = hmap_entry_index(node, bitpos);
177 return (hmap_node_t *)node->entries[idx];
178}
179
180static inline bool hmap_at_max_depth(uint32_t shift) {
181 return shift + HMAP_SHIFT >= HMAP_HASH_SIZE;
182}
183
184/// Abstract away the kv entry allocation.
186 hmap_key_t *k, void *v) {
187 PANIC_IF_NULL(k);
189 kv->hash = hash;
190 kv->k = k;
191 kv->v = v;
192 return kv;
193}
194
196 assert(ctx != nullptr);
198 n->nodemap = 0;
199 n->datamap = 0;
200 n->entries = nullptr;
201 return n;
202}
203
204static hmap_node_t *hmap_singleton_node(srn_context_t *ctx, hmap_hash_t hash, uint32_t shift,
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}
216
217/// Copy entries array into a new one, returning pointer.
218static hmap_data_t *hmap_copy_entries(srn_context_t *ctx, hmap_data_t *entries, size_t count) {
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}
228/// Merge two distinct kv entries into a sub-trie. `kv1` and `kv2` assumed to
229/// share either the full hash or a prefix
231 hmap_kv_entry_t *kv2, uint32_t shift) {
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}
293
294/// Recursively insert a new node containing the `k` and `v`.
295/// `out_addad` is a output parameter. If set to true by the function. I
296/// indicates that there is a new node made by the function. Otherwise
297/// the returning node is the old node.
299 const hmap_node_t *node, hmap_hash_t hash, hmap_key_t *k,
300 void *v, uint32_t shift, bool *out_added) {
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}
483
485 hmap_collision_t *cnode, hmap_hash_t hash, const hmap_key_t *k,
486 void *default_value) {
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}
503
505 const hmap_node_t *node, hmap_hash_t hash, const hmap_key_t *k,
506 uint32_t shift, void *default_value) {
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 }
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}
545// -----------------------------------------------------------------------------
546// Public API
547// -----------------------------------------------------------------------------
548static bool hmap_internal_cmp(srn_context_t *ctx, const hmap_key_t *a, const hmap_key_t *b) {
549 UNUSED(ctx);
550 return hmap_key_equal(a, b);
551}
552
553[[gnu::nonnull(1)]]
555 PANIC_IF_NULL(k);
556 return hmap_hash(ctx, k->data, k->len);
557}
558
560 hmap_key_t *k, void *v) {
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}
596
597void *hmap_lookup_ctl(const hmap_control_t *ctl, srn_context_t *ctx, const hmap_t *hmap,
598 const hmap_key_t *k, void *default_value) {
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}
611
613 UNUSED(ctx);
614 hmap_t map = {.len = 0, .root = nullptr};
615 return map;
616}
617
618hmap_t hmap_insert(srn_context_t *ctx, const hmap_t *hmap, hmap_key_t *k, void *v) {
619 return hmap_default_control.insert(&hmap_default_control, ctx, hmap, k, v);
620}
621
622void *hmap_lookup(srn_context_t *ctx, const hmap_t *hmap, const hmap_key_t *k,
623 void *default_value) {
624 return hmap_default_control.lookup(&hmap_default_control, ctx, hmap, k, default_value);
625}
626
627hmap_key_t *hmap_make_key(srn_context_t *ctx, void *data, size_t len) {
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}
635
637 .cmp = &hmap_internal_cmp,
638 .hash = &hmap_hash_internal,
639 .insert = &hmap_insert_ctl,
640 .lookup = &hmap_lookup_ctl,
641};
int n
Definition acutest.h:538
#define ALLOCN(ctx, T, N)
Definition context.h:83
#define ALLOC(ctx, T)
Definition context.h:82
srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len)
Definition engine.c:131
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 bool hmap_key_equal(const hmap_key_t *a, const hmap_key_t *b)
Compare key a with key b
Definition hashmap.c:88
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
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
hmap_t hmap_empty(srn_context_t *ctx)
Create, initialize and return a new hashmap.
Definition hashmap.c:612
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 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 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
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.
Definition hashmap.c:622
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 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
static size_t hmap_popcount(hmap_bitmap_t bm)
Definition hashmap.c:140
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
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.
Definition hashmap.c:618
static hmap_hash_t hmap_hash_internal(srn_context_t *ctx, const hmap_key_t *k)
Definition hashmap.c:554
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
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
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
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 hashmap.c:627
#define HMAP_LOG(...)
Definition hashmap.c:49
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
static bool hmap_at_max_depth(uint32_t shift)
Definition hashmap.c:180
static bool hmap_internal_cmp(srn_context_t *ctx, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.c:548
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 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
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
static hmap_bitmap_t hmap_bitpos(hmap_hash_t hash, uint8_t shift)
Bit position corresponding to this fragment.
Definition hashmap.c:136
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
hmap_control_t hmap_default_control
Definition hashmap.c:636
This is an implementation of Compressed Hash-Array Mapped Prefix-tree, which is a bit-partitioned,...
void * hmap_data_t
Definition hashmap.h:63
#define HMAP_HASH_SIZE
By default we use 32bit hashes.
Definition hashmap.h:57
#define HMAP_MASK
Definition hashmap.h:56
HMAP_BITMAP_TYPE hmap_bitmap_t
Definition hashmap.h:62
#define HMAP_SHIFT
Definition hashmap.h:55
HMAP_HASH_TYPE hmap_hash_t
Definition hashmap.h:61
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
If we ever want to modify some of these behaviours for a new instance of hashmap, we should use this ...
Definition hashmap.h:103
hmap_hash_t(* hash)(srn_context_t *ctx, const hmap_key_t *k)
Definition hashmap.h:105
bool(* cmp)(srn_context_t *ctx, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.h:104
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
Definition hashmap.h:72
hmap_key_t * k
Definition hashmap.h:74
void * v
Definition hashmap.h:75
hmap_hash_t hash
Definition hashmap.h:73
hmap_data_t * entries
This is really a (void **) which each entry in the array will be either (mutually exclusive):
Definition hashmap.h:92
hmap_bitmap_t datamap
Definition hashmap.h:79
hmap_bitmap_t nodemap
Definition hashmap.h:80
hmap_node_t * root
Definition hashmap.h:98
size_t len
Number of key/value pairs in the map.
Definition hashmap.h:97
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
#define PANIC_IF_NULL(ptr)
Definition utils.h:64
#define PANIC_IF(cond, msg)
Definition utils.h:57
#define UNUSED(x)
Definition utils.h:43
static uint32_t srn_popcnt32(uint32_t x)
Definition utils.h:192