// // Copyright 2020 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this // file was obtained (LICENSE.txt). A copy of the license may also be // found online at https://opensource.org/licenses/MIT. // #include "core/nng_impl.h" #include "zthash.h" struct zt_hash_entry { uint64_t key; void * val; uint32_t skips; }; int zt_hash_init(zt_hash **hp) { zt_hash *h; if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { return (NNG_ENOMEM); } h->ih_entries = NULL; h->ih_count = 0; h->ih_load = 0; h->ih_cap = 0; h->ih_maxload = 0; h->ih_minload = 0; // never shrink below this h->ih_minval = 0; h->ih_maxval = 0xffffffff; h->ih_dynval = 0; *hp = h; return (0); } void zt_hash_fini(zt_hash *h) { if (h != NULL) { if (h->ih_entries != NULL) { NNI_FREE_STRUCTS(h->ih_entries, h->ih_cap); h->ih_entries = NULL; h->ih_cap = h->ih_count = 0; h->ih_load = h->ih_minload = h->ih_maxload = 0; } NNI_FREE_STRUCT(h); } } void zt_hash_limits(zt_hash *h, uint64_t minval, uint64_t maxval, uint64_t start) { if (start < minval) { start = minval; } if (start > maxval) { start = maxval; } h->ih_minval = minval; h->ih_maxval = maxval; h->ih_dynval = start; NNI_ASSERT(minval < maxval); NNI_ASSERT(start >= minval); NNI_ASSERT(start <= maxval); } // Inspired by Python dict implementation. This probe will visit every // cell. We always hash consecutively assigned IDs. #define ZT_HASH_NEXT(h, j) ((((j) *5) + 1) & (h->ih_cap - 1)) #define ZT_HASH_INDEX(h, j) ((j) & (h->ih_cap - 1)) static size_t zt_hash_find_index(zt_hash *h, uint64_t id) { size_t index; size_t start; if (h->ih_count == 0) { return ((size_t) -1); } index = ZT_HASH_INDEX(h, id); start = index; for (;;) { // The value of ihe_key is only valid if ihe_val is not NULL. if ((h->ih_entries[index].key == id) && (h->ih_entries[index].val != NULL)) { return (index); } if (h->ih_entries[index].skips == 0) { return ((size_t) -1); } index = ZT_HASH_NEXT(h, index); if (index == start) { break; } } return ((size_t) -1); } int zt_hash_find(zt_hash *h, uint64_t id, void **vp) { size_t index; if ((index = zt_hash_find_index(h, id)) == (size_t) -1) { return (NNG_ENOENT); } *vp = h->ih_entries[index].val; return (0); } static int zt_hash_resize(zt_hash *h) { size_t newsize; size_t oldsize; zt_hash_entry *newents; zt_hash_entry *oldents; uint32_t i; if ((h->ih_load < h->ih_maxload) && (h->ih_load >= h->ih_minload)) { // No resize needed. return (0); } oldsize = h->ih_cap; newsize = 8; while (newsize < (h->ih_count * 2)) { newsize *= 2; } if (newsize == oldsize) { // Same size. return (0); } oldents = h->ih_entries; newents = NNI_ALLOC_STRUCTS(newents, newsize); if (newents == NULL) { return (NNG_ENOMEM); } h->ih_entries = newents; h->ih_cap = newsize; h->ih_load = 0; if (newsize > 8) { h->ih_minload = newsize / 8; h->ih_maxload = newsize * 2 / 3; } else { h->ih_minload = 0; h->ih_maxload = 5; } for (i = 0; i < oldsize; i++) { size_t index; if (oldents[i].val == NULL) { continue; } index = oldents[i].key & (newsize - 1); for (;;) { // Increment the load unconditionally. It counts // once for every item stored, plus once for each // hashing operation we use to store the item (i.e. // one for the item, plus once for each rehash.) h->ih_load++; if (newents[index].val == NULL) { // As we are hitting this entry for the first // time, it won't have any skips. NNI_ASSERT(newents[index].skips == 0); newents[index].val = oldents[i].val; newents[index].key = oldents[i].key; break; } newents[index].skips++; index = ZT_HASH_NEXT(h, index); } } if (oldsize != 0) { NNI_FREE_STRUCTS(oldents, oldsize); } return (0); } int zt_hash_remove(zt_hash *h, uint64_t id) { size_t index; size_t probe; if ((index = zt_hash_find_index(h, id)) == (size_t) -1) { return (NNG_ENOENT); } // Now we have found the index where the object exists. We are going // to restart the search, until the index matches, to decrement the // skips counter. probe = (int) ZT_HASH_INDEX(h, id); for (;;) { zt_hash_entry *entry; // The load was increased once each hashing operation we used // to place the the item. Decrement it accordingly. h->ih_load--; entry = &h->ih_entries[probe]; if (probe == index) { entry->val = NULL; entry->key = 0; break; } NNI_ASSERT(entry->skips > 0); entry->skips--; probe = ZT_HASH_NEXT(h, probe); } h->ih_count--; // Shrink -- but it's ok if we can't. (void) zt_hash_resize(h); return (0); } int zt_hash_insert(zt_hash *h, uint64_t id, void *val) { size_t index; zt_hash_entry *ent; // Try to resize -- if we don't need to, this will be a no-op. if (zt_hash_resize(h) != 0) { return (NNG_ENOMEM); } // If it already exists, just overwrite the old value. if ((index = zt_hash_find_index(h, id)) != (size_t) -1) { ent = &h->ih_entries[index]; ent->val = val; return (0); } index = ZT_HASH_INDEX(h, id); for (;;) { ent = &h->ih_entries[index]; // Increment the load count. We do this each time time we // rehash. This may over-count items that collide on the // same rehashing, but this should just cause a table to // grow sooner, which is probably a good thing. h->ih_load++; if (ent->val == NULL) { h->ih_count++; ent->key = id; ent->val = val; return (0); } // Record the skip count. This being non-zero informs // that a rehash will be necessary. Without this we // would need to scan the entire hash for the match. ent->skips++; index = ZT_HASH_NEXT(h, index); } } int zt_hash_alloc(zt_hash *h, uint64_t *idp, void *val) { uint64_t id; int rv; NNI_ASSERT(val != NULL); if (h->ih_count > (h->ih_maxval - h->ih_minval)) { // Really more like ENOSPC.. the table is filled to max. return (NNG_ENOMEM); } for (;;) { id = h->ih_dynval; h->ih_dynval++; if (h->ih_dynval > h->ih_maxval) { h->ih_dynval = h->ih_minval; } if (zt_hash_find_index(h, id) == (size_t) -1) { break; } } rv = zt_hash_insert(h, id, val); if (rv == 0) { *idp = id; } return (rv); }