Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <errno.h> | ||
2 | #include <stdlib.h> | ||
3 | #include <string.h> | ||
4 | #include "intmap.h" | ||
5 | #include "bit.h" | ||
6 | #include "debug.h" | ||
7 | #include "hash.h" | ||
8 | |||
9 | const char tombstone[16] = "TOMBSTONE"; | ||
10 | |||
11 | enum { | ||
12 | MIN_SIZE = 8, | ||
13 | }; | ||
14 | |||
15 | 1405 | static size_t hash_key(uint32_t k) | |
16 | { | ||
17 | 1405 | k = ((k >> 16) ^ k) * 0x45d9f3b; | |
18 | 1405 | k = ((k >> 16) ^ k) * 0x45d9f3b; | |
19 | 1405 | k = (k >> 16) ^ k; | |
20 | 1405 | return k; | |
21 | } | ||
22 | |||
23 | WARN_UNUSED_RESULT | ||
24 | 29 | static int intmap_resize(IntMap *map, size_t size) | |
25 | { | ||
26 | 29 | BUG_ON(size < MIN_SIZE); | |
27 | 29 | BUG_ON(size <= map->count); | |
28 | 29 | BUG_ON(!IS_POWER_OF_2(size)); | |
29 | |||
30 | 29 | IntMapEntry *newtab = calloc(size, sizeof(*newtab)); | |
31 |
1/2✓ Branch 0 (8→9) taken 29 times.
✗ Branch 1 (8→21) not taken.
|
29 | if (unlikely(!newtab)) { |
32 | return ENOMEM; | ||
33 | } | ||
34 | |||
35 | 29 | IntMapEntry *oldtab = map->entries; | |
36 | 29 | size_t oldlen = map->mask + 1; | |
37 | 29 | map->entries = newtab; | |
38 | 29 | map->mask = size - 1; | |
39 | 29 | map->tombstones = 0; | |
40 | |||
41 |
2/2✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→21) taken 28 times.
|
29 | if (!oldtab) { |
42 | return 0; | ||
43 | } | ||
44 | |||
45 | // Copy the entries to the new table | ||
46 |
2/2✓ Branch 0 (19→11) taken 8 times.
✓ Branch 1 (19→20) taken 1 times.
|
9 | for (const IntMapEntry *e = oldtab, *end = e + oldlen; e < end; e++) { |
47 |
3/4✓ Branch 0 (11→12) taken 8 times.
✗ Branch 1 (11→13) not taken.
✓ Branch 2 (12→13) taken 1 times.
✓ Branch 3 (12→14) taken 7 times.
|
8 | if (!e->value || e->value == tombstone) { |
48 | 1 | continue; | |
49 | } | ||
50 | 7 | IntMapEntry *newe; | |
51 | 9 | for (size_t i = hash_key(e->key), j = 1; ; i += j++) { | |
52 | 9 | newe = newtab + (i & map->mask); | |
53 |
2/2✓ Branch 0 (15→16) taken 2 times.
✓ Branch 1 (15→17) taken 7 times.
|
9 | if (!newe->key) { |
54 | break; | ||
55 | } | ||
56 | } | ||
57 | 7 | *newe = *e; | |
58 | } | ||
59 | |||
60 | 1 | free(oldtab); | |
61 | 1 | return 0; | |
62 | } | ||
63 | |||
64 | WARN_UNUSED_RESULT | ||
65 | 28 | static int intmap_do_init(IntMap *map, size_t size) | |
66 | { | ||
67 | // Accommodate the 75% load factor in the table size, to allow | ||
68 | // filling to the requested size without needing to resize() | ||
69 | 28 | size += size / 3; | |
70 | |||
71 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 27 times.
|
28 | if (unlikely(size < MIN_SIZE)) { |
72 | 1 | size = MIN_SIZE; | |
73 | } | ||
74 | |||
75 | 28 | size = next_pow2(size); | |
76 |
1/2✓ Branch 0 (5→6) taken 28 times.
✗ Branch 1 (5→7) not taken.
|
28 | if (unlikely(size == 0)) { |
77 | return EOVERFLOW; | ||
78 | } | ||
79 | |||
80 | 28 | *map = (IntMap)INTMAP_INIT; | |
81 | 28 | return intmap_resize(map, size); | |
82 | } | ||
83 | |||
84 | 27 | void intmap_init(IntMap *map, size_t capacity) | |
85 | { | ||
86 | 27 | int err = intmap_do_init(map, capacity); | |
87 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 27 times.
|
27 | if (unlikely(err)) { |
88 | − | fatal_error(__func__, err); | |
89 | } | ||
90 | 27 | } | |
91 | |||
92 | 17 | IntMapEntry *intmap_find(const IntMap *map, uint32_t key) | |
93 | { | ||
94 |
2/2✓ Branch 0 (2→3) taken 15 times.
✓ Branch 1 (2→9) taken 2 times.
|
17 | if (unlikely(!map->entries)) { |
95 | return NULL; | ||
96 | } | ||
97 | |||
98 | 15 | size_t hash = hash_key(key); | |
99 | 15 | IntMapEntry *e; | |
100 | 27 | for (size_t i = hash, j = 1; ; i += j++) { | |
101 | 27 | e = map->entries + (i & map->mask); | |
102 |
2/2✓ Branch 0 (4→5) taken 24 times.
✓ Branch 1 (4→9) taken 3 times.
|
27 | if (!e->value) { |
103 | return NULL; | ||
104 | } | ||
105 |
2/2✓ Branch 0 (5→6) taken 2 times.
✓ Branch 1 (5→7) taken 22 times.
|
24 | if (e->value == tombstone) { |
106 | 2 | continue; | |
107 | } | ||
108 |
2/2✓ Branch 0 (7→8) taken 10 times.
✓ Branch 1 (7→9) taken 12 times.
|
22 | if (e->key == key) { |
109 | return e; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | BUG("unexpected loop break"); | ||
114 | } | ||
115 | |||
116 | 2 | void *intmap_remove(IntMap *map, uint32_t key) | |
117 | { | ||
118 | 2 | IntMapEntry *e = intmap_find(map, key); | |
119 |
1/2✓ Branch 0 (3→4) taken 2 times.
✗ Branch 1 (3→5) not taken.
|
2 | if (!e) { |
120 | return NULL; | ||
121 | } | ||
122 | |||
123 | 2 | void *value = e->value; | |
124 | 2 | e->key = 0; | |
125 | 2 | e->value = (char*)tombstone; | |
126 | 2 | map->count--; | |
127 | 2 | map->tombstones++; | |
128 | 2 | return value; | |
129 | } | ||
130 | |||
131 | WARN_UNUSED_RESULT | ||
132 | 1383 | static int intmap_do_insert(IntMap *map, uint32_t key, void *value, void **old_value) | |
133 | { | ||
134 | 1383 | int err = 0; | |
135 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→5) taken 1382 times.
|
1383 | if (unlikely(!map->entries)) { |
136 | 1 | err = intmap_do_init(map, 0); | |
137 |
1/2✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→27) not taken.
|
1 | if (unlikely(err)) { |
138 | return err; | ||
139 | } | ||
140 | } | ||
141 | |||
142 | 1383 | size_t hash = hash_key(key); | |
143 | 1383 | bool replacing_tombstone_or_existing_value = false; | |
144 | 1383 | IntMapEntry *e; | |
145 | 2097 | for (size_t i = hash, j = 1; ; i += j++) { | |
146 | 2097 | e = map->entries + (i & map->mask); | |
147 |
2/2✓ Branch 0 (6→7) taken 715 times.
✓ Branch 1 (6→16) taken 1382 times.
|
2097 | if (!e->value) { |
148 | break; | ||
149 | } | ||
150 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→11) taken 714 times.
|
715 | if (e->value == tombstone) { |
151 | 1 | replacing_tombstone_or_existing_value = true; | |
152 | 1 | BUG_ON(map->tombstones == 0); | |
153 | 1 | map->tombstones--; | |
154 | } | ||
155 |
2/2✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→15) taken 714 times.
|
715 | if (unlikely(e->key == key)) { |
156 | 1 | replacing_tombstone_or_existing_value = true; | |
157 | 1 | BUG_ON(!e->value); | |
158 | 1 | *old_value = e->value; | |
159 | 1 | key = e->key; | |
160 | 1 | map->count--; | |
161 | 1 | break; | |
162 | } | ||
163 | } | ||
164 | |||
165 | 1383 | const size_t max_load = map->mask - (map->mask / 4); | |
166 | 1383 | e->key = key; | |
167 | 1383 | e->value = value; | |
168 | 1383 | map->count++; | |
169 | |||
170 |
2/2✓ Branch 0 (16→17) taken 1 times.
✓ Branch 1 (16→27) taken 1382 times.
|
1383 | if (unlikely(map->count + map->tombstones > max_load)) { |
171 | 1 | BUG_ON(replacing_tombstone_or_existing_value); | |
172 | 1 | size_t new_size = map->mask + 1; | |
173 |
1/4✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→21) taken 1 times.
✗ Branch 2 (20→21) not taken.
✗ Branch 3 (20→23) not taken.
|
1 | 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 | 1 | new_size <<= 1; | |
177 |
1/2✗ Branch 0 (21→22) not taken.
✓ Branch 1 (21→23) taken 1 times.
|
1 | if (unlikely(new_size == 0)) { |
178 | ✗ | err = EOVERFLOW; | |
179 | ✗ | goto error; | |
180 | } | ||
181 | } | ||
182 | 1 | err = intmap_resize(map, new_size); | |
183 |
1/2✗ Branch 0 (24→25) not taken.
✓ Branch 1 (24→27) taken 1 times.
|
1 | if (unlikely(err)) { |
184 | ✗ | goto error; | |
185 | } | ||
186 | } | ||
187 | |||
188 | return 0; | ||
189 | |||
190 | ✗ | error: | |
191 | ✗ | map->count--; | |
192 | ✗ | e->key = 0; | |
193 | ✗ | e->value = NULL; | |
194 | ✗ | return err; | |
195 | } | ||
196 | |||
197 | 1383 | void *intmap_insert_or_replace(IntMap *map, uint32_t key, void *value) | |
198 | { | ||
199 | 1383 | void *replaced_value = NULL; | |
200 | 1383 | int err = intmap_do_insert(map, key, value, &replaced_value); | |
201 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 1383 times.
|
1383 | if (unlikely(err)) { |
202 | − | fatal_error(__func__, err); | |
203 | } | ||
204 | 1383 | return replaced_value; | |
205 | } | ||
206 | |||
207 | // Call a FreeFunction declared with an arbitrary pointer parameter type | ||
208 | // without -fsanitize=function pedantry | ||
209 | NO_SANITIZE("undefined") | ||
210 | 1380 | static void do_free_value(FreeFunction free_value, void *value) | |
211 | { | ||
212 | 1380 | free_value(value); | |
213 | 1380 | } | |
214 | |||
215 | // Remove all entries without freeing the table | ||
216 | 30 | static void intmap_clear(IntMap *map, FreeFunction free_value) | |
217 | { | ||
218 |
2/2✓ Branch 0 (2→3) taken 28 times.
✓ Branch 1 (2→12) taken 2 times.
|
30 | if (unlikely(!map->entries)) { |
219 | return; | ||
220 | } | ||
221 | |||
222 |
1/2✓ Branch 0 (3→4) taken 28 times.
✗ Branch 1 (3→11) not taken.
|
28 | if (free_value) { |
223 | 28 | size_t count = 0; | |
224 |
2/2✓ Branch 0 (8→5) taken 1380 times.
✓ Branch 1 (8→9) taken 28 times.
|
1408 | for (IntMapIter it = intmap_iter(map); intmap_next(&it); count++) { |
225 | 1380 | do_free_value(free_value, it.entry->value); | |
226 | } | ||
227 | 28 | BUG_ON(count != map->count); | |
228 | } | ||
229 | |||
230 | 28 | size_t len = map->mask + 1; | |
231 | 28 | map->count = 0; | |
232 | 28 | memset(map->entries, 0, len * sizeof(*map->entries)); | |
233 | } | ||
234 | |||
235 | 30 | void intmap_free(IntMap *map, FreeFunction free_value) | |
236 | { | ||
237 | 30 | intmap_clear(map, free_value); | |
238 | 30 | free(map->entries); | |
239 | 30 | *map = (IntMap)INTMAP_INIT; | |
240 | 30 | } | |
241 |