michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * PR hash table package. michael@0: */ michael@0: michael@0: #include "jshash.h" michael@0: michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "jstypes.h" michael@0: michael@0: #include "js/Utility.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::CeilingLog2Size; michael@0: using mozilla::RotateLeft; michael@0: michael@0: /* Compute the number of buckets in ht */ michael@0: #define NBUCKETS(ht) JS_BIT(JS_HASH_BITS - (ht)->shift) michael@0: michael@0: /* The smallest table has 16 buckets */ michael@0: #define MINBUCKETSLOG2 4 michael@0: #define MINBUCKETS JS_BIT(MINBUCKETSLOG2) michael@0: michael@0: /* Compute the maximum entries given n buckets that we will tolerate, ~90% */ michael@0: #define OVERLOADED(n) ((n) - ((n) >> 3)) michael@0: michael@0: /* Compute the number of entries below which we shrink the table by half */ michael@0: #define UNDERLOADED(n) (((n) > MINBUCKETS) ? ((n) >> 2) : 0) michael@0: michael@0: /* michael@0: ** Stubs for default hash allocator ops. michael@0: */ michael@0: static void * michael@0: DefaultAllocTable(void *pool, size_t size) michael@0: { michael@0: return js_malloc(size); michael@0: } michael@0: michael@0: static void michael@0: DefaultFreeTable(void *pool, void *item, size_t size) michael@0: { michael@0: js_free(item); michael@0: } michael@0: michael@0: static JSHashEntry * michael@0: DefaultAllocEntry(void *pool, const void *key) michael@0: { michael@0: return (JSHashEntry*) js_malloc(sizeof(JSHashEntry)); michael@0: } michael@0: michael@0: static void michael@0: DefaultFreeEntry(void *pool, JSHashEntry *he, unsigned flag) michael@0: { michael@0: if (flag == HT_FREE_ENTRY) michael@0: js_free(he); michael@0: } michael@0: michael@0: static const JSHashAllocOps defaultHashAllocOps = { michael@0: DefaultAllocTable, DefaultFreeTable, michael@0: DefaultAllocEntry, DefaultFreeEntry michael@0: }; michael@0: michael@0: JSHashTable * michael@0: JS_NewHashTable(uint32_t n, JSHashFunction keyHash, michael@0: JSHashComparator keyCompare, JSHashComparator valueCompare, michael@0: const JSHashAllocOps *allocOps, void *allocPriv) michael@0: { michael@0: JSHashTable *ht; michael@0: size_t nb; michael@0: michael@0: if (n <= MINBUCKETS) { michael@0: n = MINBUCKETSLOG2; michael@0: } else { michael@0: n = CeilingLog2Size(n); michael@0: if (int32_t(n) < 0) michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!allocOps) allocOps = &defaultHashAllocOps; michael@0: michael@0: ht = (JSHashTable*) allocOps->allocTable(allocPriv, sizeof *ht); michael@0: if (!ht) michael@0: return nullptr; michael@0: memset(ht, 0, sizeof *ht); michael@0: ht->shift = JS_HASH_BITS - n; michael@0: n = JS_BIT(n); michael@0: nb = n * sizeof(JSHashEntry *); michael@0: ht->buckets = (JSHashEntry**) allocOps->allocTable(allocPriv, nb); michael@0: if (!ht->buckets) { michael@0: allocOps->freeTable(allocPriv, ht, nb); michael@0: return nullptr; michael@0: } michael@0: memset(ht->buckets, 0, nb); michael@0: michael@0: ht->keyHash = keyHash; michael@0: ht->keyCompare = keyCompare; michael@0: ht->valueCompare = valueCompare; michael@0: ht->allocOps = allocOps; michael@0: ht->allocPriv = allocPriv; michael@0: return ht; michael@0: } michael@0: michael@0: void michael@0: JS_HashTableDestroy(JSHashTable *ht) michael@0: { michael@0: uint32_t i, n; michael@0: JSHashEntry *he, **hep; michael@0: const JSHashAllocOps *allocOps = ht->allocOps; michael@0: void *allocPriv = ht->allocPriv; michael@0: michael@0: n = NBUCKETS(ht); michael@0: for (i = 0; i < n; i++) { michael@0: hep = &ht->buckets[i]; michael@0: while ((he = *hep) != nullptr) { michael@0: *hep = he->next; michael@0: allocOps->freeEntry(allocPriv, he, HT_FREE_ENTRY); michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: memset(ht->buckets, 0xDB, n * sizeof ht->buckets[0]); michael@0: #endif michael@0: allocOps->freeTable(allocPriv, ht->buckets, n * sizeof ht->buckets[0]); michael@0: #ifdef DEBUG michael@0: memset(ht, 0xDB, sizeof *ht); michael@0: #endif michael@0: allocOps->freeTable(allocPriv, ht, sizeof *ht); michael@0: } michael@0: michael@0: /* michael@0: * Multiplicative hash, from Knuth 6.4. michael@0: */ michael@0: #define BUCKET_HEAD(ht, keyHash) \ michael@0: (&(ht)->buckets[((keyHash) * JS_GOLDEN_RATIO) >> (ht)->shift]) michael@0: michael@0: JSHashEntry ** michael@0: JS_HashTableRawLookup(JSHashTable *ht, JSHashNumber keyHash, const void *key) michael@0: { michael@0: JSHashEntry *he, **hep, **hep0; michael@0: michael@0: #ifdef JS_HASHMETER michael@0: ht->nlookups++; michael@0: #endif michael@0: hep = hep0 = BUCKET_HEAD(ht, keyHash); michael@0: while ((he = *hep) != nullptr) { michael@0: if (he->keyHash == keyHash && ht->keyCompare(key, he->key)) { michael@0: /* Move to front of chain if not already there */ michael@0: if (hep != hep0) { michael@0: *hep = he->next; michael@0: he->next = *hep0; michael@0: *hep0 = he; michael@0: } michael@0: return hep0; michael@0: } michael@0: hep = &he->next; michael@0: #ifdef JS_HASHMETER michael@0: ht->nsteps++; michael@0: #endif michael@0: } michael@0: return hep; michael@0: } michael@0: michael@0: static bool michael@0: Resize(JSHashTable *ht, uint32_t newshift) michael@0: { michael@0: size_t nb, nentries, i; michael@0: JSHashEntry **oldbuckets, *he, *next, **hep; michael@0: size_t nold = NBUCKETS(ht); michael@0: michael@0: MOZ_ASSERT(newshift < JS_HASH_BITS); michael@0: michael@0: nb = (size_t)1 << (JS_HASH_BITS - newshift); michael@0: michael@0: /* Integer overflow protection. */ michael@0: if (nb > (size_t)-1 / sizeof(JSHashEntry*)) michael@0: return false; michael@0: nb *= sizeof(JSHashEntry*); michael@0: michael@0: oldbuckets = ht->buckets; michael@0: ht->buckets = (JSHashEntry**)ht->allocOps->allocTable(ht->allocPriv, nb); michael@0: if (!ht->buckets) { michael@0: ht->buckets = oldbuckets; michael@0: return false; michael@0: } michael@0: memset(ht->buckets, 0, nb); michael@0: michael@0: ht->shift = newshift; michael@0: nentries = ht->nentries; michael@0: michael@0: for (i = 0; nentries != 0; i++) { michael@0: for (he = oldbuckets[i]; he; he = next) { michael@0: MOZ_ASSERT(nentries != 0); michael@0: --nentries; michael@0: next = he->next; michael@0: hep = BUCKET_HEAD(ht, he->keyHash); michael@0: michael@0: /* michael@0: * We do not require unique entries, instead appending he to the michael@0: * chain starting at hep. michael@0: */ michael@0: while (*hep) michael@0: hep = &(*hep)->next; michael@0: he->next = nullptr; michael@0: *hep = he; michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: memset(oldbuckets, 0xDB, nold * sizeof oldbuckets[0]); michael@0: #endif michael@0: ht->allocOps->freeTable(ht->allocPriv, oldbuckets, michael@0: nold * sizeof oldbuckets[0]); michael@0: return true; michael@0: } michael@0: michael@0: JSHashEntry * michael@0: JS_HashTableRawAdd(JSHashTable *ht, JSHashEntry **&hep, michael@0: JSHashNumber keyHash, const void *key, void *value) michael@0: { michael@0: uint32_t n; michael@0: JSHashEntry *he; michael@0: michael@0: /* Grow the table if it is overloaded */ michael@0: n = NBUCKETS(ht); michael@0: if (ht->nentries >= OVERLOADED(n)) { michael@0: if (!Resize(ht, ht->shift - 1)) michael@0: return nullptr; michael@0: #ifdef JS_HASHMETER michael@0: ht->ngrows++; michael@0: #endif michael@0: hep = JS_HashTableRawLookup(ht, keyHash, key); michael@0: } michael@0: michael@0: /* Make a new key value entry */ michael@0: he = ht->allocOps->allocEntry(ht->allocPriv, key); michael@0: if (!he) michael@0: return nullptr; michael@0: he->keyHash = keyHash; michael@0: he->key = key; michael@0: he->value = value; michael@0: he->next = *hep; michael@0: *hep = he; michael@0: ht->nentries++; michael@0: return he; michael@0: } michael@0: michael@0: JSHashEntry * michael@0: JS_HashTableAdd(JSHashTable *ht, const void *key, void *value) michael@0: { michael@0: JSHashNumber keyHash; michael@0: JSHashEntry *he, **hep; michael@0: michael@0: keyHash = ht->keyHash(key); michael@0: hep = JS_HashTableRawLookup(ht, keyHash, key); michael@0: if ((he = *hep) != nullptr) { michael@0: /* Hit; see if values match */ michael@0: if (ht->valueCompare(he->value, value)) { michael@0: /* key,value pair is already present in table */ michael@0: return he; michael@0: } michael@0: if (he->value) michael@0: ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_VALUE); michael@0: he->value = value; michael@0: return he; michael@0: } michael@0: return JS_HashTableRawAdd(ht, hep, keyHash, key, value); michael@0: } michael@0: michael@0: void michael@0: JS_HashTableRawRemove(JSHashTable *ht, JSHashEntry **hep, JSHashEntry *he) michael@0: { michael@0: uint32_t n; michael@0: michael@0: *hep = he->next; michael@0: ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_ENTRY); michael@0: michael@0: /* Shrink table if it's underloaded */ michael@0: n = NBUCKETS(ht); michael@0: if (--ht->nentries < UNDERLOADED(n)) { michael@0: Resize(ht, ht->shift + 1); michael@0: #ifdef JS_HASHMETER michael@0: ht->nshrinks++; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JS_HashTableRemove(JSHashTable *ht, const void *key) michael@0: { michael@0: JSHashNumber keyHash; michael@0: JSHashEntry *he, **hep; michael@0: michael@0: keyHash = ht->keyHash(key); michael@0: hep = JS_HashTableRawLookup(ht, keyHash, key); michael@0: if ((he = *hep) == nullptr) michael@0: return false; michael@0: michael@0: /* Hit; remove element */ michael@0: JS_HashTableRawRemove(ht, hep, he); michael@0: return true; michael@0: } michael@0: michael@0: void * michael@0: JS_HashTableLookup(JSHashTable *ht, const void *key) michael@0: { michael@0: JSHashNumber keyHash; michael@0: JSHashEntry *he, **hep; michael@0: michael@0: keyHash = ht->keyHash(key); michael@0: hep = JS_HashTableRawLookup(ht, keyHash, key); michael@0: if ((he = *hep) != nullptr) { michael@0: return he->value; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: ** Iterate over the entries in the hash table calling func for each michael@0: ** entry found. Stop if "f" says to (return value & JS_ENUMERATE_STOP). michael@0: ** Return a count of the number of elements scanned. michael@0: */ michael@0: int michael@0: JS_HashTableEnumerateEntries(JSHashTable *ht, JSHashEnumerator f, void *arg) michael@0: { michael@0: JSHashEntry *he, **hep, **bucket; michael@0: uint32_t nlimit, n, nbuckets, newlog2; michael@0: int rv; michael@0: michael@0: nlimit = ht->nentries; michael@0: n = 0; michael@0: for (bucket = ht->buckets; n != nlimit; ++bucket) { michael@0: hep = bucket; michael@0: while ((he = *hep) != nullptr) { michael@0: MOZ_ASSERT(n < nlimit); michael@0: rv = f(he, n, arg); michael@0: n++; michael@0: if (rv & HT_ENUMERATE_REMOVE) { michael@0: *hep = he->next; michael@0: ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_ENTRY); michael@0: --ht->nentries; michael@0: } else { michael@0: hep = &he->next; michael@0: } michael@0: if (rv & HT_ENUMERATE_STOP) { michael@0: goto out; michael@0: } michael@0: } michael@0: } michael@0: michael@0: out: michael@0: /* Shrink table if removal of entries made it underloaded */ michael@0: if (ht->nentries != nlimit) { michael@0: MOZ_ASSERT(ht->nentries < nlimit); michael@0: nbuckets = NBUCKETS(ht); michael@0: if (MINBUCKETS < nbuckets && ht->nentries < UNDERLOADED(nbuckets)) { michael@0: newlog2 = CeilingLog2Size(ht->nentries); michael@0: if (newlog2 < MINBUCKETSLOG2) michael@0: newlog2 = MINBUCKETSLOG2; michael@0: michael@0: /* Check that we really shrink the table. */ michael@0: MOZ_ASSERT(JS_HASH_BITS - ht->shift > newlog2); michael@0: Resize(ht, JS_HASH_BITS - newlog2); michael@0: } michael@0: } michael@0: return (int)n; michael@0: } michael@0: michael@0: #ifdef JS_HASHMETER michael@0: #include michael@0: michael@0: void michael@0: JS_HashTableDumpMeter(JSHashTable *ht, JSHashEnumerator dump, FILE *fp) michael@0: { michael@0: double sqsum, mean, sigma; michael@0: uint32_t nchains, nbuckets; michael@0: uint32_t i, n, maxChain, maxChainLen; michael@0: JSHashEntry *he; michael@0: michael@0: sqsum = 0; michael@0: nchains = 0; michael@0: maxChain = maxChainLen = 0; michael@0: nbuckets = NBUCKETS(ht); michael@0: for (i = 0; i < nbuckets; i++) { michael@0: he = ht->buckets[i]; michael@0: if (!he) michael@0: continue; michael@0: nchains++; michael@0: for (n = 0; he; he = he->next) michael@0: n++; michael@0: sqsum += n * n; michael@0: if (n > maxChainLen) { michael@0: maxChainLen = n; michael@0: maxChain = i; michael@0: } michael@0: } michael@0: michael@0: mean = JS_MeanAndStdDev(nchains, ht->nentries, sqsum, &sigma); michael@0: michael@0: fprintf(fp, "\nHash table statistics:\n"); michael@0: fprintf(fp, " number of lookups: %u\n", ht->nlookups); michael@0: fprintf(fp, " number of entries: %u\n", ht->nentries); michael@0: fprintf(fp, " number of grows: %u\n", ht->ngrows); michael@0: fprintf(fp, " number of shrinks: %u\n", ht->nshrinks); michael@0: fprintf(fp, " mean steps per hash: %g\n", (double)ht->nsteps michael@0: / ht->nlookups); michael@0: fprintf(fp, "mean hash chain length: %g\n", mean); michael@0: fprintf(fp, " standard deviation: %g\n", sigma); michael@0: fprintf(fp, " max hash chain length: %u\n", maxChainLen); michael@0: fprintf(fp, " max hash chain: [%u]\n", maxChain); michael@0: michael@0: for (he = ht->buckets[maxChain], i = 0; he; he = he->next, i++) michael@0: if (dump(he, i, fp) != HT_ENUMERATE_NEXT) michael@0: break; michael@0: } michael@0: #endif /* JS_HASHMETER */ michael@0: michael@0: int michael@0: JS_HashTableDump(JSHashTable *ht, JSHashEnumerator dump, FILE *fp) michael@0: { michael@0: int count; michael@0: michael@0: count = JS_HashTableEnumerateEntries(ht, dump, fp); michael@0: #ifdef JS_HASHMETER michael@0: JS_HashTableDumpMeter(ht, dump, fp); michael@0: #endif michael@0: return count; michael@0: } michael@0: michael@0: JSHashNumber michael@0: JS_HashString(const void *key) michael@0: { michael@0: JSHashNumber h; michael@0: const unsigned char *s; michael@0: michael@0: h = 0; michael@0: for (s = (const unsigned char *)key; *s; s++) michael@0: h = RotateLeft(h, 4) ^ *s; michael@0: return h; michael@0: } michael@0: michael@0: int michael@0: JS_CompareValues(const void *v1, const void *v2) michael@0: { michael@0: return v1 == v2; michael@0: }