Serene Runtime 1.0.0-dev
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 <assert.h>
38#include <stdint.h>
39#include <string.h>
40
41#include "serene/rt/context.h"
42#include "serene/rt/engine.h" // for HMAP_HASH_FN
43#include "serene/utils.h"
44
45#if defined(HMAP_DEBUG)
46# define HMAP_LOG(...) DBG("HMAP", __VA_ARGS__)
47#else
48# define HMAP_LOG(...)
49#endif
50
51// -----------------------------------------------------------------------------
52// Collisions
53// -----------------------------------------------------------------------------
54/// A simple alist to manage collision nodes
59
60/// Return true if the pointer's LSB is 1 indicating a collision.
61static inline bool is_collision_node(void *p) { return ((uintptr_t)p & (uintptr_t)1) != 0; }
62
63/// Since for non collision nodes the LSB is 0 then we don't need to untag it.
64static inline hmap_collision_t *untag_ptr(void *ptr) {
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}
69
70static inline void *tag_ptr(void *ptr) {
71 // NOLINTBEGIN(performance-no-int-to-ptr)
72 return (void *)(((uintptr_t)(ptr)) | (uintptr_t)1);
73 // NOLINTEND(performance-no-int-to-ptr)
74}
75
76#if !defined(SERENE_TESTS)
77hmap_hash_t hmap_hash(const srn_context_t *ctx, const void *data, size_t len) {
78 return srn_hash(ctx->engine, data, len);
79}
80#endif
81
82// -----------------------------------------------------------------------------
83// helpers
84// -----------------------------------------------------------------------------
85
86/// Compare key `a` with key `b`
87static inline bool hmap_key_equal(const hmap_key_t *a, const hmap_key_t *b) {
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}
108
109static inline bool is_key_in_collision_node(
110 const hmap_control_t *ctl, const hmap_t *hmap, hmap_collision_t *head, hmap_hash_t hash,
111 hmap_key_t *k
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}
127
128/// Extract a `HMAP_MAK`-bit chunk from the hash at the given shift.
129static inline uint32_t hmap_hash_fragment(hmap_hash_t hash, uint8_t shift) {
130 /* if (shift > 32) { */
131 /* PANIC("'shift' should be less than 32\n"); */
132 /* } */
133 return (hash >> shift) & HMAP_MASK;
134}
135
136/// Bit position corresponding to this fragment.
137static inline hmap_bitmap_t hmap_bitpos(hmap_hash_t hash, uint8_t shift) {
138 return (hmap_bitmap_t)1U << hmap_hash_fragment(hash, shift);
139}
140
141static inline size_t hmap_popcount(hmap_bitmap_t bm) { return srn_popcnt32(bm); }
142
143/// Number of entries (data + node) in a node regardless of a data pointer
144/// or a node pointer
145static inline size_t hmap_number_of_entries(const hmap_node_t *node) {
146 return hmap_popcount(node->datamap | node->nodemap);
147}
148
149/// Index in the packed `entries` array for a given bitpos (either datamap or
150/// nodemap).
151static inline size_t hmap_entry_index(const hmap_node_t *node, hmap_bitmap_t bitpos) {
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}
157
158/// Is this bitpos occupied by a data (kv) entry?
159static inline bool hmap_has_data_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
160 return (node->datamap & bitpos) != 0;
161}
162
163/// Is this bitpos occupied by a child node?
164static inline bool hmap_has_node_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
165 return (node->nodemap & bitpos) != 0;
166}
167
168/// Make sure the result with `is_collision_node` and case the
169/// result to either an entry or a collision node
170static inline void *hmap_get_entry_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
171 return node->entries[hmap_entry_index(node, bitpos)];
172}
173
174/// Fetch the child node pointer at a given bitpos;
175/// we must ensure nodemap has this bit.
176static inline hmap_node_t *hmap_get_child_at(const hmap_node_t *node, hmap_bitmap_t bitpos) {
177 size_t idx = hmap_entry_index(node, bitpos);
178 return (hmap_node_t *)node->entries[idx];
179}
180
181static inline bool hmap_at_max_depth(uint32_t shift) {
182 return shift + HMAP_SHIFT >= HMAP_HASH_SIZE;
183}
184
185/// Abstract away the kv entry allocation.
186static inline hmap_kv_entry_t *
188 assert(k != nullptr);
190 kv->hash = hash;
191 kv->k = k;
192 kv->v = v;
193 return kv;
194}
195
196static inline hmap_node_t *create_empty_node(const srn_context_t *ctx) {
197 assert(ctx != nullptr);
199 n->nodemap = 0;
200 n->datamap = 0;
201 n->entries = nullptr;
202 return n;
203}
204
206 const srn_context_t *ctx, hmap_hash_t hash, uint32_t shift, hmap_kv_entry_t *kv
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}
218
219/// Copy entries array into a new one, returning pointer.
220static hmap_data_t *
221hmap_copy_entries(const srn_context_t *ctx, hmap_data_t *entries, size_t count) {
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}
231/// Merge two distinct kv entries into a sub-trie. `kv1` and `kv2` assumed to
232/// share either the full hash or a prefix
234 const srn_context_t *ctx, hmap_kv_entry_t *kv1, hmap_kv_entry_t *kv2, uint32_t shift
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}
299
300/// Recursively insert a new node containing the `k` and `v`.
301/// `out_addad` is a output parameter. If set to true by the function. I
302/// indicates that there is a new node made by the function. Otherwise
303/// the returning node is the old node.
305 const hmap_control_t *ctl, const hmap_t *hmap, const hmap_node_t *node, hmap_hash_t hash,
306 hmap_key_t *k, void *v, uint32_t shift, bool *out_added
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}
510
512 const hmap_control_t *ctl, const hmap_t *hmap, hmap_collision_t *cnode, hmap_hash_t hash,
513 const hmap_key_t *k, void *default_value
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}
534
536 const hmap_control_t *ctl, const hmap_t *hmap, const hmap_node_t *node, hmap_hash_t hash,
537 const hmap_key_t *k, uint32_t shift, void *default_value
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 }
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}
580// -----------------------------------------------------------------------------
581// Public API
582// -----------------------------------------------------------------------------
583static bool hmap_internal_cmp(const hmap_t *hmap, const hmap_key_t *a, const hmap_key_t *b) {
584 UNUSED(hmap);
585 return hmap_key_equal(a, b);
586}
587
588[[gnu::nonnull(1)]]
589static hmap_hash_t hmap_hash_internal(const hmap_t *hmap, const hmap_key_t *k) {
590 PANIC_IF_NULL(k);
591 return hmap_hash(hmap->map_ctx, k->data, k->len);
592}
593
594hmap_t hmap_insert_ctl(const hmap_control_t *ctl, const hmap_t *hmap, hmap_key_t *k, void *v) {
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}
637
639 const hmap_control_t *ctl, const hmap_t *hmap, const hmap_key_t *k, void *default_value
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}
654
656 PANIC_IF_NULL(ctx);
657 hmap_t map = {.len = 0, .root = nullptr, .map_ctx = ctx};
658 return map;
659}
660
661hmap_t hmap_insert(const hmap_t *hmap, hmap_key_t *k, void *v) {
662 return hmap_default_control.insert(&hmap_default_control, hmap, k, v);
663}
664
665void *hmap_lookup(const hmap_t *hmap, const hmap_key_t *k, void *default_value) {
666 return hmap_default_control.lookup(&hmap_default_control, hmap, k, default_value);
667}
668
669hmap_key_t *hmap_make_key(srn_context_t *ctx, void *data, size_t len) {
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}
677
678hmap_key_t *hmap_copy_key(const hmap_t *hmap, const hmap_key_t *k) {
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}
689
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};
int n
Definition acutest.h:525
#define ALLOCN(ctx, T, N)
Definition context.h:85
#define ALLOC(ctx, T)
Definition context.h:84
srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len)
Definition engine.c:166
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_key_equal(const hmap_key_t *a, const hmap_key_t *b)
Compare key a with key b.
Definition hashmap.c:87
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_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.
Definition hashmap.c:661
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
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 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_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 bool hmap_internal_cmp(const hmap_t *hmap, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.c:583
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_popcount(hmap_bitmap_t bm)
Definition hashmap.c:141
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
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
hmap_t hmap_empty(const srn_context_t *ctx)
Create, initialize and return a new hashmap pinned to ctx.
Definition hashmap.c:655
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
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
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:669
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
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
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 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
#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
static bool hmap_at_max_depth(uint32_t shift)
Definition hashmap.c:181
static hmap_hash_t hmap_hash_internal(const hmap_t *hmap, const hmap_key_t *k)
Definition hashmap.c:589
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
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
static hmap_bitmap_t hmap_bitpos(hmap_hash_t hash, uint8_t shift)
Bit position corresponding to this fragment.
Definition hashmap.c:137
hmap_key_t * hmap_copy_key(const hmap_t *hmap, const hmap_key_t *k)
The default copy_key hook.
Definition hashmap.c:678
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.
Definition hashmap.c:665
hmap_control_t hmap_default_control
Definition hashmap.c:690
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:55
hmap_kv_entry_t * kv
Definition hashmap.c:57
struct hmap_collision_t * next
Definition hashmap.c:56
If we ever want to modify some of these behaviours for a new instance of hashmap, we should use this ...
Definition hashmap.h:109
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
bool(* cmp)(const hmap_t *hmap, const hmap_key_t *a, const hmap_key_t *b)
Definition hashmap.h:110
hmap_hash_t(* hash)(const hmap_t *hmap, const hmap_key_t *k)
Definition hashmap.h:111
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
const srn_context_t * map_ctx
The context that owns every allocation the map retains.
Definition hashmap.h:104
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:66
#define PANIC_IF(cond, msg)
Definition utils.h:59
char * srn_copy_bytes(const srn_context_t *ctx, const char *src, size_t len, size_t alignment)
Definition utils.c:56
#define UNUSED(x)
Definition utils.h:45
static uint32_t srn_popcnt32(uint32_t x)
Definition utils.h:194