src/util/hashmap.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <errno.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <string.h> | ||
| 4 | #include "hashmap.h" | ||
| 5 | #include "arith.h" | ||
| 6 | #include "bit.h" | ||
| 7 | #include "errorcode.h" | ||
| 8 | #include "hash.h" | ||
| 9 | #include "xstring.h" | ||
| 10 | |||
| 11 | enum { | ||
| 12 | MIN_CAPACITY = 8, | ||
| 13 | TOMBSTONE = 0xdead, | ||
| 14 | }; | ||
| 15 | |||
| 16 | WARN_UNUSED_RESULT | ||
| 17 | 508 | static SystemErrno hashmap_resize(HashMap *map, size_t capacity) | |
| 18 | { | ||
| 19 | 508 | BUG_ON(capacity < MIN_CAPACITY); | |
| 20 | 508 | BUG_ON(capacity <= map->count); | |
| 21 | 508 | BUG_ON(!IS_POWER_OF_2(capacity)); | |
| 22 | |||
| 23 | 508 | const size_t entrysize = sizeof(map->entries[0]); | |
| 24 |
1/2✓ Branch 8 → 9 taken 508 times.
✗ Branch 8 → 21 not taken.
|
508 | if (unlikely(calloc_args_have_ub_overflow(capacity, entrysize))) { |
| 25 | return EOVERFLOW; | ||
| 26 | } | ||
| 27 | |||
| 28 | 508 | HashMapEntry *newtab = calloc(capacity, entrysize); // NOLINT(*-unsafe-functions) | |
| 29 |
1/2✓ Branch 9 → 10 taken 508 times.
✗ Branch 9 → 21 not taken.
|
508 | if (unlikely(!newtab)) { |
| 30 | return ENOMEM; | ||
| 31 | } | ||
| 32 | |||
| 33 | 508 | HashMapEntry *oldtab = map->entries; | |
| 34 | 508 | size_t oldlen = map->mask + 1; | |
| 35 | 508 | map->entries = newtab; | |
| 36 | 508 | map->mask = capacity - 1; | |
| 37 | 508 | map->tombstones = 0; | |
| 38 | |||
| 39 |
2/2✓ Branch 10 → 11 taken 227 times.
✓ Branch 10 → 21 taken 281 times.
|
508 | if (!oldtab) { |
| 40 | return 0; | ||
| 41 | } | ||
| 42 | |||
| 43 | // Copy the entries to the new table | ||
| 44 |
2/2✓ Branch 19 → 12 taken 19304 times.
✓ Branch 19 → 20 taken 227 times.
|
19531 | for (const HashMapEntry *e = oldtab, *end = e + oldlen; e < end; e++) { |
| 45 |
2/2✓ Branch 12 → 13 taken 11006 times.
✓ Branch 12 → 14 taken 8298 times.
|
19304 | if (!e->key) { |
| 46 | 11006 | continue; | |
| 47 | } | ||
| 48 | 8298 | HashMapEntry *newe; | |
| 49 | 9926 | for (size_t i = e->hash, j = 1; ; i += j++) { | |
| 50 | 9926 | newe = newtab + (i & map->mask); | |
| 51 |
2/2✓ Branch 15 → 16 taken 1628 times.
✓ Branch 15 → 17 taken 8298 times.
|
9926 | if (!newe->key) { |
| 52 | break; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | 8298 | *newe = *e; | |
| 56 | } | ||
| 57 | |||
| 58 | 227 | free(oldtab); | |
| 59 | 227 | return 0; | |
| 60 | } | ||
| 61 | |||
| 62 | WARN_UNUSED_RESULT | ||
| 63 | 281 | static SystemErrno hashmap_do_init(HashMap *map, size_t capacity) | |
| 64 | { | ||
| 65 | // Accommodate the 75% load factor in the table size, to allow | ||
| 66 | // filling to the requested size without needing to resize() | ||
| 67 | 281 | capacity += capacity / 3; | |
| 68 | |||
| 69 |
2/2✓ Branch 2 → 3 taken 266 times.
✓ Branch 2 → 4 taken 15 times.
|
281 | if (unlikely(capacity < MIN_CAPACITY)) { |
| 70 | 266 | capacity = MIN_CAPACITY; | |
| 71 | } | ||
| 72 | |||
| 73 | // Round up the size to the next power of 2, to allow using simple | ||
| 74 | // bitwise ops (instead of modulo) to wrap the hash value and also | ||
| 75 | // to allow quadratic probing with triangular numbers (`i += j++` | ||
| 76 | // in the `for` loops below) | ||
| 77 | 281 | capacity = next_pow2(capacity); | |
| 78 |
1/2✓ Branch 4 → 5 taken 281 times.
✗ Branch 4 → 6 not taken.
|
281 | if (unlikely(capacity == 0)) { |
| 79 | return EOVERFLOW; | ||
| 80 | } | ||
| 81 | |||
| 82 | 281 | return hashmap_resize(map, capacity); | |
| 83 | } | ||
| 84 | |||
| 85 | 27 | HashMap hashmap_new(size_t capacity, HashMapFlags flags) | |
| 86 | { | ||
| 87 | 27 | HashMap map = {.flags = flags}; | |
| 88 | 27 | SystemErrno err = hashmap_do_init(&map, capacity); | |
| 89 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 27 times.
|
27 | FATAL_ERROR_ON(err, err); |
| 90 | 27 | return map; | |
| 91 | } | ||
| 92 | |||
| 93 | 42972 | HashMapEntry *hashmap_find(const HashMap *map, const char *key) | |
| 94 | { | ||
| 95 |
2/2✓ Branch 2 → 3 taken 42616 times.
✓ Branch 2 → 10 taken 356 times.
|
42972 | if (unlikely(!map->entries)) { |
| 96 | return NULL; | ||
| 97 | } | ||
| 98 | |||
| 99 | 42616 | size_t hash = fnv_1a_hash(key, strlen(key)); | |
| 100 | 42616 | HashMapEntry *e; | |
| 101 | 80311 | for (size_t i = hash, j = 1; ; i += j++) { | |
| 102 | 80311 | e = map->entries + (i & map->mask); | |
| 103 |
2/2✓ Branch 4 → 5 taken 31435 times.
✓ Branch 4 → 7 taken 48876 times.
|
80311 | if (!e->key) { |
| 104 |
2/2✓ Branch 5 → 6 taken 12182 times.
✓ Branch 5 → 10 taken 19253 times.
|
31435 | if (e->hash == TOMBSTONE) { |
| 105 | 12182 | continue; | |
| 106 | } | ||
| 107 | return NULL; | ||
| 108 | } | ||
| 109 |
3/4✓ Branch 7 → 8 taken 23363 times.
✓ Branch 7 → 9 taken 25513 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 23363 times.
|
48876 | if (e->hash == hash && streq(e->key, key)) { |
| 110 | return e; | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | BUG("unexpected loop break"); | ||
| 115 | } | ||
| 116 | |||
| 117 | 15865 | static void hashmap_free_key(const HashMap *map, char *key) | |
| 118 | { | ||
| 119 |
2/2✓ Branch 2 → 3 taken 15796 times.
✓ Branch 2 → 4 taken 69 times.
|
15865 | if (!(map->flags & HMAP_BORROWED_KEYS)) { |
| 120 | 15796 | free(key); | |
| 121 | } | ||
| 122 | 15865 | } | |
| 123 | |||
| 124 | 11562 | void *hashmap_remove(HashMap *map, const char *key) | |
| 125 | { | ||
| 126 | 11562 | HashMapEntry *e = hashmap_find(map, key); | |
| 127 |
2/2✓ Branch 3 → 4 taken 11543 times.
✓ Branch 3 → 6 taken 19 times.
|
11562 | if (!e) { |
| 128 | return NULL; | ||
| 129 | } | ||
| 130 | |||
| 131 | 11543 | hashmap_free_key(map, e->key); | |
| 132 | 11543 | e->key = NULL; | |
| 133 | 11543 | e->hash = TOMBSTONE; | |
| 134 | 11543 | map->count--; | |
| 135 | 11543 | map->tombstones++; | |
| 136 | 11543 | return e->value; | |
| 137 | } | ||
| 138 | |||
| 139 | WARN_UNUSED_RESULT | ||
| 140 | 15865 | static SystemErrno hashmap_do_insert(HashMap *map, char *key, void *value, void **old_value) | |
| 141 | { | ||
| 142 | 15865 | SystemErrno err = 0; | |
| 143 |
2/2✓ Branch 2 → 3 taken 254 times.
✓ Branch 2 → 5 taken 15611 times.
|
15865 | if (unlikely(!map->entries)) { |
| 144 | 254 | err = hashmap_do_init(map, 0); | |
| 145 |
1/2✓ Branch 4 → 5 taken 254 times.
✗ Branch 4 → 31 not taken.
|
254 | if (unlikely(err)) { |
| 146 | return err; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | 15865 | size_t hash = fnv_1a_hash(key, strlen(key)); | |
| 151 | 15865 | bool replacing_tombstone_or_existing_value = false; | |
| 152 | 15865 | HashMapEntry *e; | |
| 153 | 23561 | for (size_t i = hash, j = 1; ; i += j++) { | |
| 154 | 23561 | e = map->entries + (i & map->mask); | |
| 155 |
2/2✓ Branch 6 → 7 taken 15864 times.
✓ Branch 6 → 11 taken 7697 times.
|
23561 | if (!e->key) { |
| 156 |
2/2✓ Branch 7 → 8 taken 4770 times.
✓ Branch 7 → 20 taken 11094 times.
|
15864 | if (e->hash == TOMBSTONE) { |
| 157 | 4770 | replacing_tombstone_or_existing_value = true; | |
| 158 | 4770 | BUG_ON(map->tombstones == 0); | |
| 159 | 4770 | map->tombstones--; | |
| 160 | } | ||
| 161 | break; | ||
| 162 | } | ||
| 163 |
3/4✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 19 taken 7696 times.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 19 not taken.
|
7697 | if (unlikely(e->hash == hash && streq(e->key, key))) { |
| 164 | 1 | replacing_tombstone_or_existing_value = true; | |
| 165 | // When a caller passes NULL as the "old_value" return param, | ||
| 166 | // it implies there can be no existing entry with the same key | ||
| 167 | // as the one to be inserted. | ||
| 168 | 1 | BUG_ON(!old_value); | |
| 169 | 1 | BUG_ON(!e->value); | |
| 170 | 1 | *old_value = e->value; | |
| 171 | 1 | hashmap_free_key(map, key); | |
| 172 | 1 | key = e->key; | |
| 173 | 1 | map->count--; | |
| 174 | 1 | break; | |
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | 15865 | const size_t max_load = map->mask - (map->mask / 4); | |
| 179 | 15865 | e->key = key; | |
| 180 | 15865 | e->value = value; | |
| 181 | 15865 | e->hash = hash; | |
| 182 | 15865 | map->count++; | |
| 183 | |||
| 184 |
2/2✓ Branch 20 → 21 taken 227 times.
✓ Branch 20 → 31 taken 15638 times.
|
15865 | if (unlikely(map->count + map->tombstones > max_load)) { |
| 185 | 227 | BUG_ON(replacing_tombstone_or_existing_value); | |
| 186 | 227 | size_t new_size = map->mask + 1; | |
| 187 |
3/4✓ Branch 23 → 24 taken 6 times.
✓ Branch 23 → 25 taken 221 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 27 taken 6 times.
|
227 | if (map->count > map->tombstones || new_size <= 256) { |
| 188 | // Only increase the size of the table when the number of | ||
| 189 | // real entries is higher than the number of tombstones. | ||
| 190 | // If the number of real entries is lower, the table is | ||
| 191 | // most likely being filled with tombstones from repeated | ||
| 192 | // insert/remove churn; so we just rehash at the same size | ||
| 193 | // to clean out the tombstones. | ||
| 194 | 221 | new_size <<= 1; | |
| 195 |
1/2✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 221 times.
|
221 | if (unlikely(new_size == 0)) { |
| 196 | ✗ | err = EOVERFLOW; | |
| 197 | ✗ | goto error; | |
| 198 | } | ||
| 199 | } | ||
| 200 | 227 | err = hashmap_resize(map, new_size); | |
| 201 |
1/2✗ Branch 28 → 29 not taken.
✓ Branch 28 → 31 taken 227 times.
|
227 | if (unlikely(err)) { |
| 202 | ✗ | goto error; | |
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | return 0; | ||
| 207 | |||
| 208 | ✗ | error: | |
| 209 | ✗ | map->count--; | |
| 210 | ✗ | e->key = NULL; | |
| 211 | ✗ | e->hash = 0; | |
| 212 | ✗ | return err; | |
| 213 | } | ||
| 214 | |||
| 215 | 15561 | void *hashmap_insert(HashMap *map, char *key, void *value) | |
| 216 | { | ||
| 217 | 15561 | SystemErrno err = hashmap_do_insert(map, key, value, NULL); | |
| 218 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 15561 times.
|
15561 | FATAL_ERROR_ON(err, err); |
| 219 | 15561 | return value; | |
| 220 | } | ||
| 221 | |||
| 222 | 304 | void *hashmap_insert_or_replace(HashMap *map, char *key, void *value) | |
| 223 | { | ||
| 224 | 304 | void *replaced_value = NULL; | |
| 225 | 304 | SystemErrno err = hashmap_do_insert(map, key, value, &replaced_value); | |
| 226 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 304 times.
|
304 | FATAL_ERROR_ON(err, err); |
| 227 | 304 | return replaced_value; | |
| 228 | } | ||
| 229 | |||
| 230 | // Remove all entries without freeing the table | ||
| 231 | 595 | void hashmap_clear(HashMap *map, FreeFunction free_value) | |
| 232 | { | ||
| 233 |
2/2✓ Branch 2 → 3 taken 281 times.
✓ Branch 2 → 13 taken 314 times.
|
595 | if (unlikely(!map->entries)) { |
| 234 | return; | ||
| 235 | } | ||
| 236 | |||
| 237 | 281 | size_t count = 0; | |
| 238 |
2/2✓ Branch 9 → 4 taken 4321 times.
✓ Branch 9 → 10 taken 281 times.
|
4602 | for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); count++) { |
| 239 | 4321 | hashmap_free_key(map, it.entry->key); | |
| 240 |
2/2✓ Branch 5 → 6 taken 3859 times.
✓ Branch 5 → 7 taken 462 times.
|
4321 | if (free_value) { |
| 241 | 3859 | do_free_value(free_value, it.entry->value); | |
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | 281 | BUG_ON(count != map->count); | |
| 246 | 281 | size_t len = map->mask + 1; | |
| 247 | 281 | map->count = 0; | |
| 248 | 281 | memset(map->entries, 0, len * sizeof(*map->entries)); | |
| 249 | } | ||
| 250 | |||
| 251 | 577 | void hashmap_free(HashMap *map, FreeFunction free_value) | |
| 252 | { | ||
| 253 | 577 | hashmap_clear(map, free_value); | |
| 254 | 577 | free(map->entries); | |
| 255 | 577 | *map = (HashMap){.flags = map->flags}; | |
| 256 | 577 | } | |
| 257 |