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