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