From efaf5c8c0708dac544f7745aa221908c2e4a5f30 Mon Sep 17 00:00:00 2001 From: "igor%mir2.org" Date: Thu, 23 Feb 2006 22:20:27 +0000 Subject: [PATCH] Bug 328294: After enumeration that removes hashtable entries, the hashtable is shrinked only once. r=brendan git-svn-id: svn://10.0.0.236/trunk@191075 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/js/src/jshash.c | 158 ++++++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 72 deletions(-) diff --git a/mozilla/js/src/jshash.c b/mozilla/js/src/jshash.c index 960e80ead9b..8e25517d5c0 100644 --- a/mozilla/js/src/jshash.c +++ b/mozilla/js/src/jshash.c @@ -161,20 +161,20 @@ JS_HashTableDestroy(JSHashTable *ht) } /* -** Multiplicative hash, from Knuth 6.4. -*/ + * Multiplicative hash, from Knuth 6.4. + */ +#define BUCKET_HEAD(ht, keyHash) \ + (&(ht)->buckets[((keyHash) * JS_GOLDEN_RATIO) >> (ht)->shift]) + JS_PUBLIC_API(JSHashEntry **) JS_HashTableRawLookup(JSHashTable *ht, JSHashNumber keyHash, const void *key) { JSHashEntry *he, **hep, **hep0; - JSHashNumber h; #ifdef HASHMETER ht->nlookups++; #endif - h = keyHash * JS_GOLDEN_RATIO; - h >>= ht->shift; - hep = hep0 = &ht->buckets[h]; + hep = hep0 = BUCKET_HEAD(ht, keyHash); while ((he = *hep) != NULL) { if (he->keyHash == keyHash && ht->keyCompare(key, he->key)) { /* Move to front of chain if not already there */ @@ -193,44 +193,72 @@ JS_HashTableRawLookup(JSHashTable *ht, JSHashNumber keyHash, const void *key) return hep; } +static JSBool +Resize(JSHashTable *ht, uint32 newshift) +{ + size_t nb, nentries, i; + JSHashEntry **oldbuckets, *he, *next, **hep; +#ifdef DEBUG + size_t nold = NBUCKETS(ht); +#endif + + JS_ASSERT(newshift < JS_HASH_BITS); + + nb = (size_t)1 << (JS_HASH_BITS - newshift); + + /* Integer overflow protection. */ + if (nb > (size_t)-1 / sizeof(JSHashEntry*)) + return JS_FALSE; + nb *= sizeof(JSHashEntry*); + + oldbuckets = ht->buckets; + ht->buckets = (JSHashEntry**)ht->allocOps->allocTable(ht->allocPriv, nb); + if (!ht->buckets) { + ht->buckets = oldbuckets; + return JS_FALSE; + } + memset(ht->buckets, 0, nb); + + ht->shift = newshift; + nentries = ht->nentries; + + for (i = 0; nentries != 0; i++) { + for (he = oldbuckets[i]; he; he = next) { + JS_ASSERT(nentries != 0); + --nentries; + next = he->next; + hep = BUCKET_HEAD(ht, he->keyHash); + + /* + * Since he comes from the old table, it must be unique and we + * simply add it to the head of bucket chain without chain lookup. + */ + he->next = *hep; + *hep = he; + } + } +#ifdef DEBUG + memset(oldbuckets, 0xDB, nold * sizeof oldbuckets[0]); +#endif + ht->allocOps->freeTable(ht->allocPriv, oldbuckets); + return JS_TRUE; +} + JS_PUBLIC_API(JSHashEntry *) JS_HashTableRawAdd(JSHashTable *ht, JSHashEntry **hep, JSHashNumber keyHash, const void *key, void *value) { - uint32 i, n; - JSHashEntry *he, *next, **oldbuckets; - size_t nb; + uint32 n; + JSHashEntry *he; /* Grow the table if it is overloaded */ n = NBUCKETS(ht); if (ht->nentries >= OVERLOADED(n)) { - oldbuckets = ht->buckets; - nb = 2 * n * sizeof(JSHashEntry *); - ht->buckets = (JSHashEntry**) - ht->allocOps->allocTable(ht->allocPriv, nb); - if (!ht->buckets) { - ht->buckets = oldbuckets; + if (!Resize(ht, ht->shift - 1)) return NULL; - } - memset(ht->buckets, 0, nb); #ifdef HASHMETER ht->ngrows++; #endif - ht->shift--; - - for (i = 0; i < n; i++) { - for (he = oldbuckets[i]; he; he = next) { - next = he->next; - hep = JS_HashTableRawLookup(ht, he->keyHash, he->key); - JS_ASSERT(*hep == NULL); - he->next = NULL; - *hep = he; - } - } -#ifdef DEBUG - memset(oldbuckets, 0xDB, n * sizeof oldbuckets[0]); -#endif - ht->allocOps->freeTable(ht->allocPriv, oldbuckets); hep = JS_HashTableRawLookup(ht, keyHash, key); } @@ -272,9 +300,7 @@ JS_HashTableAdd(JSHashTable *ht, const void *key, void *value) JS_PUBLIC_API(void) JS_HashTableRawRemove(JSHashTable *ht, JSHashEntry **hep, JSHashEntry *he) { - uint32 i, n; - JSHashEntry *next, **oldbuckets; - size_t nb; + uint32 n; *hep = he->next; ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_ENTRY); @@ -282,33 +308,10 @@ JS_HashTableRawRemove(JSHashTable *ht, JSHashEntry **hep, JSHashEntry *he) /* Shrink table if it's underloaded */ n = NBUCKETS(ht); if (--ht->nentries < UNDERLOADED(n)) { - oldbuckets = ht->buckets; - nb = n * sizeof(JSHashEntry*) / 2; - ht->buckets = (JSHashEntry**) - ht->allocOps->allocTable(ht->allocPriv, nb); - if (!ht->buckets) { - ht->buckets = oldbuckets; - return; - } - memset(ht->buckets, 0, nb); + Resize(ht, ht->shift + 1); #ifdef HASHMETER ht->nshrinks++; #endif - ht->shift++; - - for (i = 0; i < n; i++) { - for (he = oldbuckets[i]; he; he = next) { - next = he->next; - hep = JS_HashTableRawLookup(ht, he->keyHash, he->key); - JS_ASSERT(*hep == NULL); - he->next = NULL; - *hep = he; - } - } -#ifdef DEBUG - memset(oldbuckets, 0xDB, n * sizeof oldbuckets[0]); -#endif - ht->allocOps->freeTable(ht->allocPriv, oldbuckets); } } @@ -350,21 +353,22 @@ JS_HashTableLookup(JSHashTable *ht, const void *key) JS_PUBLIC_API(int) JS_HashTableEnumerateEntries(JSHashTable *ht, JSHashEnumerator f, void *arg) { - JSHashEntry *he, **hep; - uint32 i, nbuckets; - int rv, n = 0; - JSHashEntry *todo = NULL; + JSHashEntry *he, **hep, **bucket; + uint32 nlimit, n, nbuckets, newlog2; + int rv; - nbuckets = NBUCKETS(ht); - for (i = 0; i < nbuckets; i++) { - hep = &ht->buckets[i]; + nlimit = ht->nentries; + n = 0; + for (bucket = ht->buckets; n != nlimit; ++bucket) { + hep = bucket; while ((he = *hep) != NULL) { + JS_ASSERT(n < nlimit); rv = f(he, n, arg); n++; if (rv & HT_ENUMERATE_REMOVE) { *hep = he->next; - he->next = todo; - todo = he; + ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_ENTRY); + --ht->nentries; } else { hep = &he->next; } @@ -375,11 +379,21 @@ JS_HashTableEnumerateEntries(JSHashTable *ht, JSHashEnumerator f, void *arg) } out: - hep = &todo; - while ((he = *hep) != NULL) { - JS_HashTableRawRemove(ht, hep, he); + /* Shrink table if removal of entries made it underloaded */ + if (ht->nentries != nlimit) { + JS_ASSERT(ht->nentries < nlimit); + nbuckets = NBUCKETS(ht); + if (MINBUCKETS < nbuckets && ht->nentries < UNDERLOADED(nbuckets)) { + newlog2 = JS_CeilingLog2(ht->nentries); + if (newlog2 < MINBUCKETSLOG2) + newlog2 = MINBUCKETSLOG2; + + /* Check that we really shrink the table. */ + JS_ASSERT(JS_HASH_BITS - ht->shift > newlog2); + Resize(ht, JS_HASH_BITS - newlog2); + } } - return n; + return (int)n; } #ifdef HASHMETER