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