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 | 455 | static int hashmap_resize(HashMap *map, size_t size) | |
16 | { | ||
17 | 455 | BUG_ON(size < MIN_SIZE); | |
18 | 455 | BUG_ON(size <= map->count); | |
19 | 455 | BUG_ON(!IS_POWER_OF_2(size)); | |
20 | |||
21 | 455 | HashMapEntry *newtab = calloc(size, sizeof(*newtab)); | |
22 |
1/2✓ Branch 0 taken 455 times.
✗ Branch 1 not taken.
|
455 | if (unlikely(!newtab)) { |
23 | return ENOMEM; | ||
24 | } | ||
25 | |||
26 | 455 | HashMapEntry *oldtab = map->entries; | |
27 | 455 | size_t oldlen = map->mask + 1; | |
28 | 455 | map->entries = newtab; | |
29 | 455 | map->mask = size - 1; | |
30 | 455 | map->tombstones = 0; | |
31 | |||
32 |
2/2✓ Branch 0 taken 208 times.
✓ Branch 1 taken 247 times.
|
455 | if (!oldtab) { |
33 | return 0; | ||
34 | } | ||
35 | |||
36 | // Copy the entries to the new table | ||
37 |
2/2✓ Branch 0 taken 19016 times.
✓ Branch 1 taken 208 times.
|
19224 | for (const HashMapEntry *e = oldtab, *end = e + oldlen; e < end; e++) { |
38 |
2/2✓ Branch 0 taken 10953 times.
✓ Branch 1 taken 8063 times.
|
19016 | if (!e->key) { |
39 | 10953 | continue; | |
40 | } | ||
41 | 8063 | HashMapEntry *newe; | |
42 | 9580 | for (size_t i = e->hash, j = 1; ; i += j++) { | |
43 | 9580 | newe = newtab + (i & map->mask); | |
44 |
2/2✓ Branch 0 taken 1517 times.
✓ Branch 1 taken 8063 times.
|
9580 | if (!newe->key) { |
45 | break; | ||
46 | } | ||
47 | } | ||
48 | 8063 | *newe = *e; | |
49 | } | ||
50 | |||
51 | 208 | free(oldtab); | |
52 | 208 | return 0; | |
53 | } | ||
54 | |||
55 | WARN_UNUSED_RESULT | ||
56 | 247 | 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 | 247 | size += size / 3; | |
61 | |||
62 |
2/2✓ Branch 0 taken 220 times.
✓ Branch 1 taken 27 times.
|
247 | if (unlikely(size < MIN_SIZE)) { |
63 | 220 | 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 | ||
69 | 247 | size = round_size_to_next_power_of_2(size); | |
70 |
1/2✓ Branch 0 taken 247 times.
✗ Branch 1 not taken.
|
247 | if (unlikely(size == 0)) { |
71 | return EOVERFLOW; | ||
72 | } | ||
73 | |||
74 | 247 | *map = (HashMap)HASHMAP_INIT; | |
75 | 247 | return hashmap_resize(map, size); | |
76 | } | ||
77 | |||
78 | 28 | void hashmap_init(HashMap *map, size_t capacity) | |
79 | { | ||
80 | 28 | int err = hashmap_do_init(map, capacity); | |
81 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
|
28 | if (unlikely(err)) { |
82 | − | fatal_error(__func__, err); | |
83 | } | ||
84 | 28 | } | |
85 | |||
86 | 41860 | HashMapEntry *hashmap_find(const HashMap *map, const char *key) | |
87 | { | ||
88 |
2/2✓ Branch 0 taken 41543 times.
✓ Branch 1 taken 317 times.
|
41860 | if (unlikely(!map->entries)) { |
89 | return NULL; | ||
90 | } | ||
91 | |||
92 | 41543 | size_t hash = fnv_1a_hash(key, strlen(key)); | |
93 | 41543 | HashMapEntry *e; | |
94 | 76626 | for (size_t i = hash, j = 1; ; i += j++) { | |
95 | 76626 | e = map->entries + (i & map->mask); | |
96 |
2/2✓ Branch 0 taken 30939 times.
✓ Branch 1 taken 45687 times.
|
76626 | if (!e->key) { |
97 |
2/2✓ Branch 0 taken 12182 times.
✓ Branch 1 taken 18757 times.
|
30939 | if (e->hash == TOMBSTONE) { |
98 | 12182 | continue; | |
99 | } | ||
100 | return NULL; | ||
101 | } | ||
102 |
3/4✓ Branch 0 taken 22786 times.
✓ Branch 1 taken 22901 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22786 times.
|
45687 | if (e->hash == hash && streq(e->key, key)) { |
103 | return e; | ||
104 | } | ||
105 | } | ||
106 | |||
107 | BUG("unexpected loop break"); | ||
108 | } | ||
109 | |||
110 | 11560 | void *hashmap_remove(HashMap *map, const char *key) | |
111 | { | ||
112 | 11560 | HashMapEntry *e = hashmap_find(map, key); | |
113 |
2/2✓ Branch 0 taken 11541 times.
✓ Branch 1 taken 19 times.
|
11560 | if (!e) { |
114 | return NULL; | ||
115 | } | ||
116 | |||
117 | 11541 | free(e->key); | |
118 | 11541 | e->key = NULL; | |
119 | 11541 | e->hash = TOMBSTONE; | |
120 | 11541 | map->count--; | |
121 | 11541 | map->tombstones++; | |
122 | 11541 | return e->value; | |
123 | } | ||
124 | |||
125 | WARN_UNUSED_RESULT | ||
126 | 15474 | static int hashmap_do_insert(HashMap *map, char *key, void *value, void **old_value) | |
127 | { | ||
128 | 15474 | int err = 0; | |
129 |
2/2✓ Branch 0 taken 219 times.
✓ Branch 1 taken 15255 times.
|
15474 | if (unlikely(!map->entries)) { |
130 | 219 | err = hashmap_do_init(map, 0); | |
131 |
1/2✓ Branch 0 taken 219 times.
✗ Branch 1 not taken.
|
219 | if (unlikely(err)) { |
132 | return err; | ||
133 | } | ||
134 | } | ||
135 | |||
136 | 15474 | size_t hash = fnv_1a_hash(key, strlen(key)); | |
137 | 15474 | bool replacing_tombstone_or_existing_value = false; | |
138 | 15474 | HashMapEntry *e; | |
139 | 22754 | for (size_t i = hash, j = 1; ; i += j++) { | |
140 | 22754 | e = map->entries + (i & map->mask); | |
141 |
2/2✓ Branch 0 taken 15473 times.
✓ Branch 1 taken 7281 times.
|
22754 | if (!e->key) { |
142 |
2/2✓ Branch 0 taken 4770 times.
✓ Branch 1 taken 10703 times.
|
15473 | if (e->hash == TOMBSTONE) { |
143 | 4770 | replacing_tombstone_or_existing_value = true; | |
144 | 4770 | BUG_ON(map->tombstones == 0); | |
145 | 4770 | map->tombstones--; | |
146 | } | ||
147 | break; | ||
148 | } | ||
149 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7280 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
7281 | if (unlikely(e->hash == hash && streq(e->key, key))) { |
150 | 1 | replacing_tombstone_or_existing_value = true; | |
151 | // When a caller passes NULL as the "old_value" return param, | ||
152 | // it implies there can be no existing entry with the same key | ||
153 | // as the one to be inserted. | ||
154 | 1 | BUG_ON(!old_value); | |
155 | 1 | BUG_ON(!e->value); | |
156 | 1 | *old_value = e->value; | |
157 | 1 | free(key); | |
158 | 1 | key = e->key; | |
159 | 1 | map->count--; | |
160 | 1 | break; | |
161 | } | ||
162 | } | ||
163 | |||
164 | 15474 | const size_t max_load = map->mask - (map->mask / 4); | |
165 | 15474 | e->key = key; | |
166 | 15474 | e->value = value; | |
167 | 15474 | e->hash = hash; | |
168 | 15474 | map->count++; | |
169 | |||
170 |
2/2✓ Branch 0 taken 208 times.
✓ Branch 1 taken 15266 times.
|
15474 | if (unlikely(map->count + map->tombstones > max_load)) { |
171 | 208 | BUG_ON(replacing_tombstone_or_existing_value); | |
172 | 208 | size_t new_size = map->mask + 1; | |
173 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 202 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
208 | if (map->count > map->tombstones || new_size <= 256) { |
174 | // Only increase the size of the table when the number of | ||
175 | // real entries is higher than the number of tombstones. | ||
176 | // If the number of real entries is lower, the table is | ||
177 | // most likely being filled with tombstones from repeated | ||
178 | // insert/remove churn; so we just rehash at the same size | ||
179 | // to clean out the tombstones. | ||
180 | 202 | new_size <<= 1; | |
181 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 202 times.
|
202 | if (unlikely(new_size == 0)) { |
182 | ✗ | err = EOVERFLOW; | |
183 | ✗ | goto error; | |
184 | } | ||
185 | } | ||
186 | 208 | err = hashmap_resize(map, new_size); | |
187 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 208 times.
|
208 | if (unlikely(err)) { |
188 | ✗ | goto error; | |
189 | } | ||
190 | } | ||
191 | |||
192 | return 0; | ||
193 | |||
194 | ✗ | error: | |
195 | ✗ | map->count--; | |
196 | ✗ | e->key = NULL; | |
197 | ✗ | e->hash = 0; | |
198 | ✗ | return err; | |
199 | } | ||
200 | |||
201 | 15259 | void *hashmap_insert(HashMap *map, char *key, void *value) | |
202 | { | ||
203 | 15259 | int err = hashmap_do_insert(map, key, value, NULL); | |
204 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15259 times.
|
15259 | if (unlikely(err)) { |
205 | − | fatal_error(__func__, err); | |
206 | } | ||
207 | 15259 | return value; | |
208 | } | ||
209 | |||
210 | 215 | void *hashmap_insert_or_replace(HashMap *map, char *key, void *value) | |
211 | { | ||
212 | 215 | void *replaced_value = NULL; | |
213 | 215 | int err = hashmap_do_insert(map, key, value, &replaced_value); | |
214 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 215 times.
|
215 | if (unlikely(err)) { |
215 | − | fatal_error(__func__, err); | |
216 | } | ||
217 | 215 | return replaced_value; | |
218 | } | ||
219 | |||
220 | // Call a FreeFunction declared with an arbitrary pointer parameter type | ||
221 | // without -fsanitize=function pedantry | ||
222 | NO_SANITIZE("undefined") | ||
223 | 3478 | static void do_free_value(FreeFunction free_value, void *value) | |
224 | { | ||
225 | 3478 | free_value(value); | |
226 | 3478 | } | |
227 | |||
228 | // Remove all entries without freeing the table | ||
229 | 484 | void hashmap_clear(HashMap *map, FreeFunction free_value) | |
230 | { | ||
231 |
2/2✓ Branch 0 taken 247 times.
✓ Branch 1 taken 237 times.
|
484 | if (unlikely(!map->entries)) { |
232 | return; | ||
233 | } | ||
234 | |||
235 | 247 | size_t count = 0; | |
236 |
2/2✓ Branch 0 taken 3932 times.
✓ Branch 1 taken 247 times.
|
4179 | for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); count++) { |
237 | 3932 | free(it.entry->key); | |
238 |
2/2✓ Branch 0 taken 3478 times.
✓ Branch 1 taken 454 times.
|
3932 | if (free_value) { |
239 | 3478 | do_free_value(free_value, it.entry->value); | |
240 | } | ||
241 | } | ||
242 | |||
243 | 247 | BUG_ON(count != map->count); | |
244 | 247 | size_t len = map->mask + 1; | |
245 | 247 | map->count = 0; | |
246 | 247 | memset(map->entries, 0, len * sizeof(*map->entries)); | |
247 | } | ||
248 | |||
249 | 472 | void hashmap_free(HashMap *map, FreeFunction free_value) | |
250 | { | ||
251 | 472 | hashmap_clear(map, free_value); | |
252 | 472 | free(map->entries); | |
253 | 472 | *map = (HashMap)HASHMAP_INIT; | |
254 | 472 | } | |
255 |