/src/S2OPC/src/Common/helpers/sopc_dict.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Licensed to Systerel under one or more contributor license |
3 | | * agreements. See the NOTICE file distributed with this work |
4 | | * for additional information regarding copyright ownership. |
5 | | * Systerel licenses this file to you under the Apache |
6 | | * License, Version 2.0 (the "License"); you may not use this |
7 | | * file except in compliance with the License. You may obtain |
8 | | * a copy of the License at |
9 | | * |
10 | | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | | * |
12 | | * Unless required by applicable law or agreed to in writing, |
13 | | * software distributed under the License is distributed on an |
14 | | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | | * KIND, either express or implied. See the License for the |
16 | | * specific language governing permissions and limitations |
17 | | * under the License. |
18 | | */ |
19 | | |
20 | | #include "sopc_dict.h" |
21 | | |
22 | | #include "sopc_assert.h" |
23 | | #include "sopc_mem_alloc.h" |
24 | | |
25 | 0 | #define HASH_I(hash, i) (hash + (i / 2) + (i * i / 2)) |
26 | | |
27 | | /* This is a dictionary implemented using quadratic probing (see |
28 | | * https://en.wikipedia.org/wiki/Linear_probing) for resolving key conflicts. |
29 | | * |
30 | | * We distinguish between empty and non-empty buckets using a special value for |
31 | | * the key. Removal of items can be implemented by stealing a second value from |
32 | | * the keyspace and using it as a tombstone. |
33 | | */ |
34 | | |
35 | | typedef struct _SOPC_DictBucket |
36 | | { |
37 | | uintptr_t key; |
38 | | uintptr_t value; |
39 | | } SOPC_DictBucket; |
40 | | |
41 | | struct _SOPC_Dict |
42 | | { |
43 | | SOPC_DictBucket* buckets; |
44 | | size_t size; // Total number of buckets, always a power of two |
45 | | size_t sizemask; // sizemask == (size - 1), used to replace (hash % size) by (hash & sizemask) |
46 | | size_t n_items; // Number of buckets holding a "real" value (not empty, not tombstone) |
47 | | size_t n_busy; // Number of buckets where the key is not the empty key |
48 | | uintptr_t empty_key; |
49 | | uintptr_t tombstone_key; |
50 | | SOPC_Dict_KeyHash_Fct* hash_func; |
51 | | SOPC_Dict_KeyEqual_Fct* equal_func; |
52 | | SOPC_Dict_Free_Fct* key_free; |
53 | | SOPC_Dict_Free_Fct* value_free; |
54 | | }; |
55 | | |
56 | | static const size_t DICT_INITIAL_SIZE = 16; |
57 | | |
58 | | // Shrink the table if the number of items is below SHRINK_FACTOR * (number of buckets) |
59 | | // We never shrink below DICT_INITIAL_SIZE. |
60 | | static const double SHRINK_FACTOR = 0.4; |
61 | | |
62 | | static void set_empty_keys(SOPC_DictBucket* buckets, size_t n_buckets, uintptr_t empty_key) |
63 | 0 | { |
64 | 0 | for (size_t i = 0; i < n_buckets; ++i) |
65 | 0 | { |
66 | 0 | buckets[i].key = empty_key; |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | static void free_bucket(SOPC_DictBucket* b, SOPC_Dict_Free_Fct* key_free, SOPC_Dict_Free_Fct* value_free) |
71 | 0 | { |
72 | 0 | if (key_free != NULL) |
73 | 0 | { |
74 | 0 | key_free(b->key); |
75 | 0 | } |
76 | |
|
77 | 0 | if (value_free != NULL) |
78 | 0 | { |
79 | 0 | value_free(b->value); |
80 | 0 | } |
81 | 0 | } |
82 | | |
83 | | static bool insert_item(SOPC_Dict* d, uint64_t hash, uintptr_t key, uintptr_t value, bool overwrite) |
84 | 0 | { |
85 | 0 | for (size_t i = 0; i < d->size; ++i) |
86 | 0 | { |
87 | 0 | size_t idx = (size_t) HASH_I(hash, i) & d->sizemask; |
88 | 0 | SOPC_DictBucket* b = &d->buckets[idx]; |
89 | | |
90 | | // Normal insert |
91 | 0 | if (b->key == d->empty_key || b->key == d->tombstone_key) |
92 | 0 | { |
93 | 0 | b->key = key; |
94 | 0 | b->value = value; |
95 | 0 | d->n_items++; |
96 | 0 | d->n_busy++; |
97 | 0 | return true; |
98 | 0 | } |
99 | | |
100 | | // Overwriting of existing value |
101 | 0 | if (overwrite && d->equal_func(key, b->key)) |
102 | 0 | { |
103 | 0 | free_bucket(b, d->key_free, d->value_free); |
104 | |
|
105 | 0 | b->key = key; |
106 | 0 | b->value = value; |
107 | |
|
108 | 0 | return true; |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | 0 | SOPC_ASSERT(false && "Cannot find a free bucket?!"); |
113 | 0 | return false; |
114 | 0 | } |
115 | | |
116 | | static bool dict_resize(SOPC_Dict* d, size_t size) |
117 | 0 | { |
118 | 0 | size_t sizemask = size - 1; |
119 | 0 | SOPC_ASSERT((size & sizemask) == 0); // Ensure we have a power of two |
120 | | |
121 | 0 | SOPC_DictBucket* buckets = SOPC_Calloc(size, sizeof(SOPC_DictBucket)); |
122 | |
|
123 | 0 | if (buckets == NULL) |
124 | 0 | { |
125 | 0 | return false; |
126 | 0 | } |
127 | | |
128 | 0 | if (d->empty_key != 0) |
129 | 0 | { |
130 | 0 | set_empty_keys(buckets, size, d->empty_key); |
131 | 0 | } |
132 | |
|
133 | 0 | SOPC_Dict dict_backup = *d; |
134 | |
|
135 | 0 | d->n_busy = 0; |
136 | 0 | d->n_items = 0; |
137 | 0 | d->buckets = buckets; |
138 | 0 | d->size = size; |
139 | 0 | d->sizemask = sizemask; |
140 | |
|
141 | 0 | bool ok = true; |
142 | |
|
143 | 0 | for (size_t i = 0; i < dict_backup.size; ++i) |
144 | 0 | { |
145 | 0 | SOPC_DictBucket* b = &dict_backup.buckets[i]; |
146 | |
|
147 | 0 | if ((b->key == d->empty_key) || (b->key == d->tombstone_key)) |
148 | 0 | { |
149 | 0 | continue; |
150 | 0 | } |
151 | | |
152 | 0 | uint64_t hash = d->hash_func(b->key); |
153 | |
|
154 | 0 | if (!insert_item(d, hash, b->key, b->value, false)) |
155 | 0 | { |
156 | 0 | ok = false; |
157 | 0 | break; |
158 | 0 | } |
159 | 0 | } |
160 | |
|
161 | 0 | if (ok) |
162 | 0 | { |
163 | 0 | SOPC_Free(dict_backup.buckets); |
164 | 0 | } |
165 | 0 | else |
166 | 0 | { |
167 | 0 | *d = dict_backup; |
168 | 0 | } |
169 | |
|
170 | 0 | return ok; |
171 | 0 | } |
172 | | |
173 | | SOPC_Dict* SOPC_Dict_Create(uintptr_t empty_key, |
174 | | SOPC_Dict_KeyHash_Fct* key_hash, |
175 | | SOPC_Dict_KeyEqual_Fct* key_equal, |
176 | | SOPC_Dict_Free_Fct* key_free, |
177 | | SOPC_Dict_Free_Fct* value_free) |
178 | 0 | { |
179 | 0 | SOPC_Dict* d = SOPC_Calloc(1, sizeof(SOPC_Dict)); |
180 | |
|
181 | 0 | if (d == NULL) |
182 | 0 | { |
183 | 0 | return NULL; |
184 | 0 | } |
185 | | |
186 | 0 | d->size = DICT_INITIAL_SIZE; |
187 | 0 | d->sizemask = d->size - 1; |
188 | |
|
189 | 0 | d->buckets = SOPC_Calloc(d->size, sizeof(SOPC_DictBucket)); |
190 | |
|
191 | 0 | bool ok = d->buckets != NULL; |
192 | |
|
193 | 0 | if (!ok) |
194 | 0 | { |
195 | 0 | SOPC_Dict_Delete(d); |
196 | 0 | return NULL; |
197 | 0 | } |
198 | | |
199 | 0 | d->empty_key = empty_key; |
200 | 0 | d->tombstone_key = empty_key; |
201 | 0 | d->hash_func = key_hash; |
202 | 0 | d->equal_func = key_equal; |
203 | 0 | d->key_free = key_free; |
204 | 0 | d->value_free = value_free; |
205 | | |
206 | | // We only go touch the memory if the empty key is not 0, since the memory |
207 | | // for our buckets is always alloced with calloc. |
208 | 0 | if (d->empty_key != 0) |
209 | 0 | { |
210 | 0 | set_empty_keys(d->buckets, d->size, d->empty_key); |
211 | 0 | } |
212 | |
|
213 | 0 | return d; |
214 | 0 | } |
215 | | |
216 | | void SOPC_Dict_Delete(SOPC_Dict* d) |
217 | 0 | { |
218 | 0 | if (d != NULL) |
219 | 0 | { |
220 | | // buckets can be NULL if called from SOPC_Dict_Create |
221 | 0 | if (d->buckets != NULL) |
222 | 0 | { |
223 | 0 | for (size_t i = 0; i < d->size; ++i) |
224 | 0 | { |
225 | 0 | SOPC_DictBucket* bucket = &d->buckets[i]; |
226 | |
|
227 | 0 | if ((bucket->key != d->empty_key) && (bucket->key != d->tombstone_key)) |
228 | 0 | { |
229 | 0 | free_bucket(bucket, d->key_free, d->value_free); |
230 | 0 | } |
231 | 0 | } |
232 | |
|
233 | 0 | SOPC_Free(d->buckets); |
234 | 0 | } |
235 | |
|
236 | 0 | SOPC_Free(d); |
237 | 0 | } |
238 | 0 | } |
239 | | |
240 | | // Compute the minimum dictionary size (a power of two) given an initial size |
241 | | // and a number of items to store. |
242 | | // The computed value is such that dictionary occupation stays under 50%. |
243 | | static size_t minimum_dict_size(size_t start_size, size_t n_items) |
244 | 0 | { |
245 | 0 | SOPC_ASSERT((start_size & (start_size - 1)) == 0); |
246 | | |
247 | 0 | size_t size = start_size; |
248 | |
|
249 | 0 | while (size < (2 * n_items)) |
250 | 0 | { |
251 | 0 | size *= 2; |
252 | 0 | } |
253 | |
|
254 | 0 | return size; |
255 | 0 | } |
256 | | |
257 | | bool SOPC_Dict_Reserve(SOPC_Dict* d, size_t n_items) |
258 | 0 | { |
259 | 0 | SOPC_ASSERT(d != NULL); |
260 | 0 | return dict_resize(d, minimum_dict_size(d->size, n_items)); |
261 | 0 | } |
262 | | |
263 | | void SOPC_Dict_SetTombstoneKey(SOPC_Dict* d, uintptr_t tombstone_key) |
264 | 0 | { |
265 | 0 | SOPC_ASSERT(d != NULL); |
266 | 0 | SOPC_ASSERT(d->empty_key != tombstone_key); |
267 | 0 | SOPC_ASSERT(d->n_busy == 0); |
268 | 0 | d->tombstone_key = tombstone_key; |
269 | 0 | } |
270 | | |
271 | | // Delta is 1 when adding, 0 when removing |
272 | | static bool maybe_resize(SOPC_Dict* d, uint8_t delta) |
273 | 0 | { |
274 | 0 | const size_t shrink_limit = (size_t)(SHRINK_FACTOR * ((double) d->size)); |
275 | 0 | size_t target_size = d->size; |
276 | |
|
277 | 0 | if (((delta > 0) && ((d->n_busy + delta) > (d->size / 2))) || ((delta == 0) && (d->n_items < shrink_limit))) |
278 | 0 | { |
279 | | // One of the two cases: |
280 | | // - Overpopulation when adding items |
281 | | // - Underpopulation while removing items |
282 | | // |
283 | | // Compute the required number of buckets after eliminating tombstones |
284 | | // and resize. |
285 | | |
286 | | // Ensure the occupation will be under 50% |
287 | 0 | target_size = minimum_dict_size(DICT_INITIAL_SIZE, d->n_items + delta); |
288 | 0 | } |
289 | |
|
290 | 0 | return (d->size == target_size) ? true : dict_resize(d, target_size); |
291 | 0 | } |
292 | | |
293 | | bool SOPC_Dict_Insert(SOPC_Dict* d, uintptr_t key, uintptr_t value) |
294 | 0 | { |
295 | 0 | SOPC_ASSERT(d != NULL); |
296 | 0 | if (key == d->empty_key || key == d->tombstone_key) |
297 | 0 | { |
298 | 0 | return false; |
299 | 0 | } |
300 | | |
301 | 0 | if (!maybe_resize(d, 1)) |
302 | 0 | { |
303 | 0 | return false; |
304 | 0 | } |
305 | | |
306 | 0 | uint64_t hash = d->hash_func(key); |
307 | |
|
308 | 0 | return insert_item(d, hash, key, value, true); |
309 | 0 | } |
310 | | |
311 | | static SOPC_DictBucket* get_internal(const SOPC_Dict* d, const uintptr_t key) |
312 | 0 | { |
313 | 0 | if (key == d->empty_key || key == d->tombstone_key) |
314 | 0 | { |
315 | 0 | return NULL; |
316 | 0 | } |
317 | | |
318 | 0 | uint64_t hash = d->hash_func(key); |
319 | |
|
320 | 0 | for (size_t i = 0; i < d->size; ++i) |
321 | 0 | { |
322 | 0 | uint64_t idx = HASH_I(hash, i) & d->sizemask; |
323 | 0 | const uintptr_t bucket_key = d->buckets[idx].key; |
324 | |
|
325 | 0 | if (bucket_key == d->empty_key) |
326 | 0 | { |
327 | 0 | break; |
328 | 0 | } |
329 | | |
330 | | // If removals are not supported, we have empty_key == tombstone_key, so |
331 | | // this if never matches. |
332 | 0 | if (bucket_key == d->tombstone_key) |
333 | 0 | { |
334 | 0 | continue; |
335 | 0 | } |
336 | | |
337 | 0 | if (d->equal_func(key, bucket_key)) |
338 | 0 | { |
339 | 0 | return &d->buckets[idx]; |
340 | 0 | } |
341 | 0 | } |
342 | | |
343 | 0 | return NULL; |
344 | 0 | } |
345 | | |
346 | | uintptr_t SOPC_Dict_Get(const SOPC_Dict* d, const uintptr_t key, bool* found) |
347 | 0 | { |
348 | 0 | SOPC_ASSERT(d != NULL); |
349 | 0 | SOPC_DictBucket* bucket = get_internal(d, key); |
350 | |
|
351 | 0 | if (found != NULL) |
352 | 0 | { |
353 | 0 | *found = (bucket != NULL); |
354 | 0 | } |
355 | |
|
356 | 0 | return (bucket != NULL) ? bucket->value : 0; |
357 | 0 | } |
358 | | |
359 | | uintptr_t SOPC_Dict_GetKey(const SOPC_Dict* d, const uintptr_t key, bool* found) |
360 | 0 | { |
361 | 0 | SOPC_ASSERT(d != NULL); |
362 | 0 | SOPC_DictBucket* bucket = get_internal(d, key); |
363 | |
|
364 | 0 | if (found != NULL) |
365 | 0 | { |
366 | 0 | *found = (bucket != NULL); |
367 | 0 | } |
368 | |
|
369 | 0 | return (bucket != NULL) ? bucket->key : 0; |
370 | 0 | } |
371 | | |
372 | | void SOPC_Dict_Remove(SOPC_Dict* d, const uintptr_t key) |
373 | 0 | { |
374 | 0 | SOPC_ASSERT(d != NULL); |
375 | | |
376 | | // Check that a tombstone key has been defined |
377 | 0 | SOPC_ASSERT(d->empty_key != d->tombstone_key); |
378 | | |
379 | 0 | SOPC_DictBucket* bucket = get_internal(d, key); |
380 | |
|
381 | 0 | if (bucket == NULL) |
382 | 0 | { |
383 | 0 | return; |
384 | 0 | } |
385 | | |
386 | 0 | free_bucket(bucket, d->key_free, d->value_free); |
387 | 0 | bucket->key = d->tombstone_key; |
388 | 0 | bucket->value = 0; |
389 | 0 | --d->n_items; |
390 | | |
391 | | // We can ignore failures here, worst case we fail to compact and will try |
392 | | // later. |
393 | 0 | maybe_resize(d, 0); |
394 | 0 | } |
395 | | |
396 | | SOPC_Dict_Free_Fct* SOPC_Dict_GetKeyFreeFunc(const SOPC_Dict* d) |
397 | 0 | { |
398 | 0 | SOPC_ASSERT(d != NULL); |
399 | 0 | return d->key_free; |
400 | 0 | } |
401 | | |
402 | | void SOPC_Dict_SetKeyFreeFunc(SOPC_Dict* d, SOPC_Dict_Free_Fct* func) |
403 | 0 | { |
404 | 0 | SOPC_ASSERT(d != NULL); |
405 | 0 | d->key_free = func; |
406 | 0 | } |
407 | | |
408 | | SOPC_Dict_Free_Fct* SOPC_Dict_GetValueFreeFunc(const SOPC_Dict* d) |
409 | 0 | { |
410 | 0 | SOPC_ASSERT(d != NULL); |
411 | 0 | return d->value_free; |
412 | 0 | } |
413 | | |
414 | | void SOPC_Dict_SetValueFreeFunc(SOPC_Dict* d, SOPC_Dict_Free_Fct* func) |
415 | 0 | { |
416 | 0 | SOPC_ASSERT(d != NULL); |
417 | 0 | d->value_free = func; |
418 | 0 | } |
419 | | |
420 | | size_t SOPC_Dict_Size(const SOPC_Dict* d) |
421 | 0 | { |
422 | 0 | return d->n_items; |
423 | 0 | } |
424 | | |
425 | | size_t SOPC_Dict_Capacity(const SOPC_Dict* d) |
426 | 0 | { |
427 | 0 | return d->size / 2; |
428 | 0 | } |
429 | | |
430 | | void SOPC_Dict_ForEach(SOPC_Dict* d, SOPC_Dict_ForEach_Fct* func, uintptr_t user_data) |
431 | 0 | { |
432 | 0 | SOPC_ASSERT(NULL != func && NULL != d); |
433 | | |
434 | 0 | for (size_t i = 0; i < d->size; ++i) |
435 | 0 | { |
436 | 0 | if (d->buckets[i].key != d->empty_key) |
437 | 0 | { |
438 | 0 | func(d->buckets[i].key, d->buckets[i].value, user_data); |
439 | 0 | } |
440 | 0 | } |
441 | 0 | } |