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