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