Git fork
at reftables-rust 352 lines 8.6 kB view raw
1/* 2 * Generic implementation of hash-based key value mappings. 3 */ 4#include "git-compat-util.h" 5#include "hashmap.h" 6 7#define FNV32_BASE ((unsigned int) 0x811c9dc5) 8#define FNV32_PRIME ((unsigned int) 0x01000193) 9 10unsigned int strhash(const char *str) 11{ 12 unsigned int c, hash = FNV32_BASE; 13 while ((c = (unsigned char) *str++)) 14 hash = (hash * FNV32_PRIME) ^ c; 15 return hash; 16} 17 18unsigned int strihash(const char *str) 19{ 20 unsigned int c, hash = FNV32_BASE; 21 while ((c = (unsigned char) *str++)) { 22 if (c >= 'a' && c <= 'z') 23 c -= 'a' - 'A'; 24 hash = (hash * FNV32_PRIME) ^ c; 25 } 26 return hash; 27} 28 29unsigned int memhash(const void *buf, size_t len) 30{ 31 unsigned int hash = FNV32_BASE; 32 unsigned char *ucbuf = (unsigned char *) buf; 33 while (len--) { 34 unsigned int c = *ucbuf++; 35 hash = (hash * FNV32_PRIME) ^ c; 36 } 37 return hash; 38} 39 40unsigned int memihash(const void *buf, size_t len) 41{ 42 unsigned int hash = FNV32_BASE; 43 unsigned char *ucbuf = (unsigned char *) buf; 44 while (len--) { 45 unsigned int c = *ucbuf++; 46 if (c >= 'a' && c <= 'z') 47 c -= 'a' - 'A'; 48 hash = (hash * FNV32_PRIME) ^ c; 49 } 50 return hash; 51} 52 53/* 54 * Incorporate another chunk of data into a memihash 55 * computation. 56 */ 57unsigned int memihash_cont(unsigned int hash_seed, const void *buf, size_t len) 58{ 59 unsigned int hash = hash_seed; 60 unsigned char *ucbuf = (unsigned char *) buf; 61 while (len--) { 62 unsigned int c = *ucbuf++; 63 if (c >= 'a' && c <= 'z') 64 c -= 'a' - 'A'; 65 hash = (hash * FNV32_PRIME) ^ c; 66 } 67 return hash; 68} 69 70#define HASHMAP_INITIAL_SIZE 64 71/* grow / shrink by 2^2 */ 72#define HASHMAP_RESIZE_BITS 2 73/* load factor in percent */ 74#define HASHMAP_LOAD_FACTOR 80 75 76static void alloc_table(struct hashmap *map, unsigned int size) 77{ 78 map->tablesize = size; 79 CALLOC_ARRAY(map->table, size); 80 81 /* calculate resize thresholds for new size */ 82 map->grow_at = (unsigned int) ((uint64_t) size * HASHMAP_LOAD_FACTOR / 100); 83 if (size <= HASHMAP_INITIAL_SIZE) 84 map->shrink_at = 0; 85 else 86 /* 87 * The shrink-threshold must be slightly smaller than 88 * (grow-threshold / resize-factor) to prevent erratic resizing, 89 * thus we divide by (resize-factor + 1). 90 */ 91 map->shrink_at = map->grow_at / ((1 << HASHMAP_RESIZE_BITS) + 1); 92} 93 94static inline int entry_equals(const struct hashmap *map, 95 const struct hashmap_entry *e1, 96 const struct hashmap_entry *e2, 97 const void *keydata) 98{ 99 return (e1 == e2) || 100 (e1->hash == e2->hash && 101 !map->cmpfn(map->cmpfn_data, e1, e2, keydata)); 102} 103 104static inline unsigned int bucket(const struct hashmap *map, 105 const struct hashmap_entry *key) 106{ 107 return key->hash & (map->tablesize - 1); 108} 109 110int hashmap_bucket(const struct hashmap *map, unsigned int hash) 111{ 112 return hash & (map->tablesize - 1); 113} 114 115static void rehash(struct hashmap *map, unsigned int newsize) 116{ 117 /* map->table MUST NOT be NULL when this function is called */ 118 unsigned int i, oldsize = map->tablesize; 119 struct hashmap_entry **oldtable = map->table; 120 121 alloc_table(map, newsize); 122 for (i = 0; i < oldsize; i++) { 123 struct hashmap_entry *e = oldtable[i]; 124 while (e) { 125 struct hashmap_entry *next = e->next; 126 unsigned int b = bucket(map, e); 127 e->next = map->table[b]; 128 map->table[b] = e; 129 e = next; 130 } 131 } 132 free(oldtable); 133} 134 135static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map, 136 const struct hashmap_entry *key, const void *keydata) 137{ 138 /* map->table MUST NOT be NULL when this function is called */ 139 struct hashmap_entry **e = &map->table[bucket(map, key)]; 140 while (*e && !entry_equals(map, *e, key, keydata)) 141 e = &(*e)->next; 142 return e; 143} 144 145static int always_equal(const void *cmp_data UNUSED, 146 const struct hashmap_entry *entry1 UNUSED, 147 const struct hashmap_entry *entry2 UNUSED, 148 const void *keydata UNUSED) 149{ 150 return 0; 151} 152 153void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, 154 const void *cmpfn_data, size_t initial_size) 155{ 156 unsigned int size = HASHMAP_INITIAL_SIZE; 157 158 memset(map, 0, sizeof(*map)); 159 160 map->cmpfn = equals_function ? equals_function : always_equal; 161 map->cmpfn_data = cmpfn_data; 162 163 /* calculate initial table size and allocate the table */ 164 initial_size = (unsigned int) ((uint64_t) initial_size * 100 165 / HASHMAP_LOAD_FACTOR); 166 while (initial_size > size) 167 size <<= HASHMAP_RESIZE_BITS; 168 alloc_table(map, size); 169 170 /* 171 * Keep track of the number of items in the map and 172 * allow the map to automatically grow as necessary. 173 */ 174 map->do_count_items = 1; 175} 176 177static void free_individual_entries(struct hashmap *map, ssize_t entry_offset) 178{ 179 struct hashmap_iter iter; 180 struct hashmap_entry *e; 181 182 hashmap_iter_init(map, &iter); 183 while ((e = hashmap_iter_next(&iter))) 184 /* 185 * like container_of, but using caller-calculated 186 * offset (caller being hashmap_clear_and_free) 187 */ 188 free((char *)e - entry_offset); 189} 190 191void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset) 192{ 193 if (!map || !map->table) 194 return; 195 if (entry_offset >= 0) /* called by hashmap_clear_entries */ 196 free_individual_entries(map, entry_offset); 197 memset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *)); 198 map->shrink_at = 0; 199 map->private_size = 0; 200} 201 202void hashmap_clear_(struct hashmap *map, ssize_t entry_offset) 203{ 204 if (!map || !map->table) 205 return; 206 if (entry_offset >= 0) /* called by hashmap_clear_and_free */ 207 free_individual_entries(map, entry_offset); 208 FREE_AND_NULL(map->table); 209 map->tablesize = 0; 210 map->private_size = 0; 211} 212 213struct hashmap_entry *hashmap_get(const struct hashmap *map, 214 const struct hashmap_entry *key, 215 const void *keydata) 216{ 217 if (!map->table) 218 return NULL; 219 return *find_entry_ptr(map, key, keydata); 220} 221 222struct hashmap_entry *hashmap_get_next(const struct hashmap *map, 223 const struct hashmap_entry *entry) 224{ 225 struct hashmap_entry *e = entry->next; 226 for (; e; e = e->next) 227 if (entry_equals(map, entry, e, NULL)) 228 return e; 229 return NULL; 230} 231 232void hashmap_add(struct hashmap *map, struct hashmap_entry *entry) 233{ 234 unsigned int b; 235 236 if (!map->table) 237 alloc_table(map, HASHMAP_INITIAL_SIZE); 238 239 b = bucket(map, entry); 240 /* add entry */ 241 entry->next = map->table[b]; 242 map->table[b] = entry; 243 244 /* fix size and rehash if appropriate */ 245 if (map->do_count_items) { 246 map->private_size++; 247 if (map->private_size > map->grow_at) 248 rehash(map, map->tablesize << HASHMAP_RESIZE_BITS); 249 } 250} 251 252struct hashmap_entry *hashmap_remove(struct hashmap *map, 253 const struct hashmap_entry *key, 254 const void *keydata) 255{ 256 struct hashmap_entry *old; 257 struct hashmap_entry **e; 258 259 if (!map->table) 260 return NULL; 261 e = find_entry_ptr(map, key, keydata); 262 if (!*e) 263 return NULL; 264 265 /* remove existing entry */ 266 old = *e; 267 *e = old->next; 268 old->next = NULL; 269 270 /* fix size and rehash if appropriate */ 271 if (map->do_count_items) { 272 map->private_size--; 273 if (map->private_size < map->shrink_at) 274 rehash(map, map->tablesize >> HASHMAP_RESIZE_BITS); 275 } 276 277 return old; 278} 279 280struct hashmap_entry *hashmap_put(struct hashmap *map, 281 struct hashmap_entry *entry) 282{ 283 struct hashmap_entry *old = hashmap_remove(map, entry, NULL); 284 hashmap_add(map, entry); 285 return old; 286} 287 288void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter) 289{ 290 iter->map = map; 291 iter->tablepos = 0; 292 iter->next = NULL; 293} 294 295struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter) 296{ 297 struct hashmap_entry *current = iter->next; 298 for (;;) { 299 if (current) { 300 iter->next = current->next; 301 return current; 302 } 303 304 if (iter->tablepos >= iter->map->tablesize) 305 return NULL; 306 307 current = iter->map->table[iter->tablepos++]; 308 } 309} 310 311struct pool_entry { 312 struct hashmap_entry ent; 313 size_t len; 314 unsigned char data[FLEX_ARRAY]; 315}; 316 317static int pool_entry_cmp(const void *cmp_data UNUSED, 318 const struct hashmap_entry *eptr, 319 const struct hashmap_entry *entry_or_key, 320 const void *keydata) 321{ 322 const struct pool_entry *e1, *e2; 323 324 e1 = container_of(eptr, const struct pool_entry, ent); 325 e2 = container_of(entry_or_key, const struct pool_entry, ent); 326 327 return e1->data != keydata && 328 (e1->len != e2->len || memcmp(e1->data, keydata, e1->len)); 329} 330 331const void *memintern(const void *data, size_t len) 332{ 333 static struct hashmap map; 334 struct pool_entry key, *e; 335 336 /* initialize string pool hashmap */ 337 if (!map.tablesize) 338 hashmap_init(&map, pool_entry_cmp, NULL, 0); 339 340 /* lookup interned string in pool */ 341 hashmap_entry_init(&key.ent, memhash(data, len)); 342 key.len = len; 343 e = hashmap_get_entry(&map, &key, ent, data); 344 if (!e) { 345 /* not found: create it */ 346 FLEX_ALLOC_MEM(e, data, data, len); 347 hashmap_entry_init(&e->ent, key.ent.hash); 348 e->len = len; 349 hashmap_add(&map, &e->ent); 350 } 351 return e->data; 352}