Coverage Report

Created: 2025-08-26 06:59

/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
}