/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla JavaScript code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1999,2000 Netscape Communications Corporation. * All Rights Reserved. * * Original Contributor: * Brendan Eich * * Contributor(s): * * Alternatively, the contents of this file may be used under the * terms of the GNU Public License (the "GPL"), in which case the * provisions of the GPL are applicable instead of those above. * If you wish to allow use of your version of this file only * under the terms of the GPL and not to allow others to use your * version of this file under the NPL, indicate your decision by * deleting the provisions above and replace them with the notice * and other provisions required by the GPL. If you do not delete * the provisions above, a recipient may use your version of this * file under either the NPL or the GPL. */ /* * Double hashing implementation. * GENERATED BY js/src/plify_jsdhash.sed -- DO NOT EDIT!!! */ #include #include #include #include "prbit.h" #include "pldhash.h" #include "prlog.h" /* for PR_ASSERT */ #ifdef PL_DHASHMETER # define METER(x) x #else # define METER(x) /* nothing */ #endif PR_IMPLEMENT(void *) PL_DHashAllocTable(PLDHashTable *table, PRUint32 nbytes) { return malloc(nbytes); } PR_IMPLEMENT(void) PL_DHashFreeTable(PLDHashTable *table, void *ptr) { free(ptr); } PR_IMPLEMENT(PLDHashNumber) PL_DHashStringKey(PLDHashTable *table, const void *key) { PLDHashNumber h; const unsigned char *s; h = 0; for (s = key; *s != '\0'; s++) h = (h >> (PL_DHASH_BITS - 4)) ^ (h << 4) ^ *s; return h; } PR_IMPLEMENT(const void *) PL_DHashGetKeyStub(PLDHashTable *table, PLDHashEntryHdr *entry) { PLDHashEntryStub *stub = (PLDHashEntryStub *)entry; return stub->key; } PR_IMPLEMENT(PLDHashNumber) PL_DHashVoidPtrKeyStub(PLDHashTable *table, const void *key) { return (PLDHashNumber)key >> 2; } PR_IMPLEMENT(PRBool) PL_DHashMatchEntryStub(PLDHashTable *table, const PLDHashEntryHdr *entry, const void *key) { PLDHashEntryStub *stub = (PLDHashEntryStub *)entry; return stub->key == key; } PR_IMPLEMENT(void) PL_DHashMoveEntryStub(PLDHashTable *table, const PLDHashEntryHdr *from, PLDHashEntryHdr *to) { memcpy(to, from, table->entrySize); } PR_IMPLEMENT(void) PL_DHashClearEntryStub(PLDHashTable *table, PLDHashEntryHdr *entry) { memset(entry, 0, table->entrySize); } PR_IMPLEMENT(void) PL_DHashFinalizeStub(PLDHashTable *table) { } static PLDHashTableOps stub_ops = { PL_DHashAllocTable, PL_DHashFreeTable, PL_DHashGetKeyStub, PL_DHashVoidPtrKeyStub, PL_DHashMatchEntryStub, PL_DHashMoveEntryStub, PL_DHashClearEntryStub, PL_DHashFinalizeStub }; PR_IMPLEMENT(PLDHashTableOps *) PL_DHashGetStubOps(void) { return &stub_ops; } PR_IMPLEMENT(PLDHashTable *) PR_NewDHashTable(PLDHashTableOps *ops, void *data, PRUint32 entrySize, PRUint32 capacity) { PLDHashTable *table; table = (PLDHashTable *) malloc(sizeof *table); if (!table) return NULL; if (!PL_DHashTableInit(table, ops, data, entrySize, capacity)) { free(table); return NULL; } return table; } PR_IMPLEMENT(void) PL_DHashTableDestroy(PLDHashTable *table) { PL_DHashTableFinish(table); free(table); } PR_IMPLEMENT(PRBool) PL_DHashTableInit(PLDHashTable *table, PLDHashTableOps *ops, void *data, PRUint32 entrySize, PRUint32 capacity) { int log2; PRUint32 nbytes; #ifdef DEBUG if (entrySize > 6 * sizeof(void *)) { fprintf(stderr, "pldhash: for the table at address 0x%p, the given entrySize" " of %lu %s favors chaining over double hashing.\n", table, (unsigned long) entrySize, (entrySize > 16 * sizeof(void*)) ? "definitely" : "probably"); } #endif table->ops = ops; table->data = data; if (capacity < PL_DHASH_MIN_SIZE) capacity = PL_DHASH_MIN_SIZE; log2 = PR_CeilingLog2(capacity); capacity = PR_BIT(log2); table->hashShift = PL_DHASH_BITS - log2; table->sizeLog2 = log2; table->sizeMask = PR_BITMASK(log2); table->entrySize = entrySize; table->entryCount = table->removedCount = 0; nbytes = capacity * entrySize; table->entryStore = ops->allocTable(table, nbytes); if (!table->entryStore) return PR_FALSE; memset(table->entryStore, 0, nbytes); METER(memset(&table->stats, 0, sizeof table->stats)); return PR_TRUE; } PR_IMPLEMENT(void) PL_DHashTableFinish(PLDHashTable *table) { table->ops->finalize(table); table->ops->freeTable(table, table->entryStore); } /* * Double hashing needs the second hash code to be relatively prime to table * size, so we simply make hash2 odd. */ #define HASH1(hash0, shift) ((hash0) >> (shift)) #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) /* Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. */ #define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0) #define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1) #define ENTRY_IS_LIVE(entry) ((entry)->keyHash >= 2) #define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0 /* Compute the address of the indexed entry in table. */ #define ADDRESS_ENTRY(table, index) \ ((PLDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize)) static PLDHashEntryHdr * SearchTable(PLDHashTable *table, const void *key, PLDHashNumber keyHash) { PLDHashNumber hash1, hash2; int hashShift; PLDHashEntryHdr *entry; PLDHashMatchEntry matchEntry; METER(table->stats.searches++); /* Compute the primary hash address. */ hashShift = table->hashShift; hash1 = HASH1(keyHash, hashShift); entry = ADDRESS_ENTRY(table, hash1); /* Miss: return space for a new entry. */ if (PL_DHASH_ENTRY_IS_FREE(entry)) { METER(table->stats.misses++); return entry; } /* Hit: return entry. */ matchEntry = table->ops->matchEntry; if (entry->keyHash == keyHash && matchEntry(table, entry, key)) { METER(table->stats.hits++); return entry; } /* Collision: double hash. */ hash2 = HASH2(keyHash, table->sizeLog2, hashShift); do { METER(table->stats.steps++); hash1 -= hash2; hash1 &= table->sizeMask; entry = ADDRESS_ENTRY(table, hash1); if (PL_DHASH_ENTRY_IS_FREE(entry)) { METER(table->stats.misses++); return entry; } } while (entry->keyHash != keyHash || !matchEntry(table, entry, key)); METER(table->stats.hits++); return entry; } static PRBool ChangeTable(PLDHashTable *table, int deltaLog2, PLDHashEntryHdr *skipEntry) { int oldLog2, newLog2; PRUint32 oldCapacity, newCapacity; char *newEntryStore, *oldEntryStore, *oldEntryAddr; PRUint32 entrySize, i, nbytes; PLDHashEntryHdr *oldEntry, *newEntry; PLDHashGetKey getKey; PLDHashMoveEntry moveEntry; /* Look, but don't touch, until we succeed in getting new entry store. */ oldLog2 = table->sizeLog2; newLog2 = oldLog2 + deltaLog2; oldCapacity = PR_BIT(oldLog2); newCapacity = PR_BIT(newLog2); entrySize = table->entrySize; nbytes = newCapacity * entrySize; newEntryStore = table->ops->allocTable(table, nbytes); if (!newEntryStore) return PR_FALSE; table->hashShift = PL_DHASH_BITS - newLog2; table->sizeLog2 = newLog2; table->sizeMask = PR_BITMASK(newLog2); table->removedCount = 0; memset(newEntryStore, 0, nbytes); oldEntryAddr = oldEntryStore = table->entryStore; table->entryStore = newEntryStore; getKey = table->ops->getKey; moveEntry = table->ops->moveEntry; /* Copy only live entries, leaving removed ones (and skipEntry) behind. */ for (i = 0; i < oldCapacity; i++) { oldEntry = (PLDHashEntryHdr *)oldEntryAddr; if (oldEntry != skipEntry && ENTRY_IS_LIVE(oldEntry)) { newEntry = SearchTable(table, getKey(table, oldEntry), oldEntry->keyHash); PR_ASSERT(PL_DHASH_ENTRY_IS_FREE(newEntry)); moveEntry(table, oldEntry, newEntry); newEntry->keyHash = oldEntry->keyHash; } oldEntryAddr += entrySize; } table->ops->freeTable(table, oldEntryStore); return PR_TRUE; } PR_IMPLEMENT(PLDHashEntryHdr *) PL_DHashTableOperate(PLDHashTable *table, const void *key, PLDHashOperator op) { int biasedDeltaLog2; PLDHashNumber keyHash; PLDHashEntryHdr *entry; PRUint32 size; /* * Usually we don't grow or shrink the table, so optimize for test-not-zero * by biasing the deltaLog2 of -1 (shrink), 0 (compress), or 1 (grow) so that * the biased no-change value is 0. */ #define DELTA_LOG2_BIAS 2 biasedDeltaLog2 = 0; /* Avoid 0 and 1 hash codes, they indicate free and removed entries. */ keyHash = table->ops->hashKey(table, key); ENSURE_LIVE_KEYHASH(keyHash); keyHash *= PL_DHASH_GOLDEN_RATIO; entry = SearchTable(table, key, keyHash); switch (op) { case PL_DHASH_LOOKUP: METER(table->stats.lookups++); break; case PL_DHASH_ADD: if (PL_DHASH_ENTRY_IS_FREE(entry)) { /* Initialize the entry, indicating that it's no longer free. */ METER(table->stats.addMisses++); entry->keyHash = keyHash; table->entryCount++; /* If alpha is >= .75, set biasedDeltaLog2 to trigger growth. */ size = PR_BIT(table->sizeLog2); if (table->entryCount + table->removedCount >= size - (size >> 2)) { if (table->removedCount >= size >> 2) { METER(table->stats.compresses++); biasedDeltaLog2 = 0 + DELTA_LOG2_BIAS; } else { METER(table->stats.grows++); biasedDeltaLog2 = 1 + DELTA_LOG2_BIAS; } } } METER(else table->stats.addHits++); break; case PL_DHASH_REMOVE: if (PL_DHASH_ENTRY_IS_BUSY(entry)) { /* Clear this entry and mark it as "removed". */ METER(table->stats.removeHits++); PL_DHashTableRawRemove(table, entry); /* Shrink if alpha is <= .25 and table isn't too small already. */ size = PR_BIT(table->sizeLog2); if (size > PL_DHASH_MIN_SIZE && table->entryCount <= size >> 2) { METER(table->stats.shrinks++); biasedDeltaLog2 = -1 + DELTA_LOG2_BIAS; } } METER(else table->stats.removeMisses++); entry = NULL; break; default: PR_ASSERT(0); } if (biasedDeltaLog2) { if (!ChangeTable(table, biasedDeltaLog2 - DELTA_LOG2_BIAS, entry)) { /* If we just grabbed the last free entry, undo and fail hard. */ if (op == PL_DHASH_ADD && table->entryCount + table->removedCount == size) { METER(table->stats.addFailures++); MARK_ENTRY_FREE(entry); table->entryCount--; entry = NULL; } } else { if (op == PL_DHASH_ADD) { /* If the table grew, add the new (skipped) entry. */ entry = SearchTable(table, key, keyHash); PR_ASSERT(PL_DHASH_ENTRY_IS_FREE(entry)); entry->keyHash = keyHash; } } } #undef DELTA_LOG2_BIAS return entry; } PR_IMPLEMENT(void) PL_DHashTableRawRemove(PLDHashTable *table, PLDHashEntryHdr *entry) { table->ops->clearEntry(table, entry); MARK_ENTRY_REMOVED(entry); table->removedCount++; table->entryCount--; } PR_IMPLEMENT(PRUint32) PL_DHashTableEnumerate(PLDHashTable *table, PLDHashEnumerator etor, void *arg) { char *entryAddr; PRUint32 i, j, capacity, entrySize; PLDHashEntryHdr *entry; PLDHashOperator op; entryAddr = table->entryStore; entrySize = table->entrySize; capacity = PR_BIT(table->sizeLog2); for (i = j = 0; i < capacity; i++) { entry = (PLDHashEntryHdr *)entryAddr; if (ENTRY_IS_LIVE(entry)) { op = etor(table, entry, j++, arg); if (op & PL_DHASH_REMOVE) { METER(table->stats.removeEnums++); PL_DHashTableRawRemove(table, entry); } if (op & PL_DHASH_STOP) break; } entryAddr += entrySize; } /* Shrink or compress if enough entries were removed that alpha < .5. */ if (table->removedCount >= capacity >> 2) { METER(table->stats.enumShrinks++); capacity = table->entryCount; capacity += capacity >> 1; if (capacity < PL_DHASH_MIN_SIZE) capacity = PL_DHASH_MIN_SIZE; (void) ChangeTable(table, PR_CeilingLog2(capacity) - table->sizeLog2, NULL); } return j; } #ifdef PL_DHASHMETER #include PR_IMPLEMENT(void) PL_DHashTableDumpMeter(PLDHashTable *table, PLDHashEnumerator dump, FILE *fp) { char *entryAddr; PRUint32 entrySize, entryCount; PRUint32 i, tableSize, chainLen, maxChainLen, chainCount; PLDHashNumber hash1, hash2, saveHash1, maxChainHash1, maxChainHash2; double sqsum, mean, variance, sigma; PLDHashEntryHdr *entry, *probe; entryAddr = table->entryStore; entrySize = table->entrySize; tableSize = PR_BIT(table->sizeLog2); chainCount = maxChainLen = 0; hash2 = 0; sqsum = 0; for (i = 0; i < tableSize; i++) { entry = (PLDHashEntryHdr *)entryAddr; entryAddr += entrySize; if (!ENTRY_IS_LIVE(entry)) continue; hash1 = saveHash1 = HASH1(entry->keyHash, table->hashShift); probe = ADDRESS_ENTRY(table, hash1); chainLen = 1; if (probe == entry) { /* Start of a (possibly unit-length) chain. */ chainCount++; } else { hash2 = HASH2(entry->keyHash, table->sizeLog2, table->hashShift); do { chainLen++; hash1 -= hash2; hash1 &= table->sizeMask; probe = ADDRESS_ENTRY(table, hash1); } while (probe != entry); } sqsum += chainLen * chainLen; if (chainLen > maxChainLen) { maxChainLen = chainLen; maxChainHash1 = saveHash1; maxChainHash2 = hash2; } } entryCount = table->entryCount; mean = (double)entryCount / chainCount; variance = chainCount * sqsum - entryCount * entryCount; if (variance < 0 || chainCount == 1) variance = 0; else variance /= chainCount * (chainCount - 1); sigma = sqrt(variance); fprintf(fp, "Double hashing statistics:\n"); fprintf(fp, " table size (in entries): %u\n", tableSize); fprintf(fp, " number of entries: %u\n", table->entryCount); fprintf(fp, " number of removed entries: %u\n", table->removedCount); fprintf(fp, " number of searches: %u\n", table->stats.searches); fprintf(fp, " number of hits: %u\n", table->stats.hits); fprintf(fp, " number of misses: %u\n", table->stats.misses); fprintf(fp, " mean steps per search: %g\n", (double)table->stats.steps / table->stats.searches); fprintf(fp, " mean hash chain length: %g\n", mean); fprintf(fp, " standard deviation: %g\n", sigma); fprintf(fp, " maximum hash chain length: %u\n", maxChainLen); fprintf(fp, " number of lookups: %u\n", table->stats.lookups); fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses); fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits); fprintf(fp, " add failures: %u\n", table->stats.addFailures); fprintf(fp, " useful removes: %u\n", table->stats.removeHits); fprintf(fp, " useless removes: %u\n", table->stats.removeMisses); fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums); fprintf(fp, " number of grows: %u\n", table->stats.grows); fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks); fprintf(fp, " number of compresses: %u\n", table->stats.compresses); fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks); if (maxChainLen && hash2) { fputs("Maximum hash chain:\n", fp); hash1 = maxChainHash1; hash2 = maxChainHash2; entry = ADDRESS_ENTRY(table, hash1); i = 0; do { if (dump(table, entry, i++, fp) != PL_DHASH_NEXT) break; hash1 -= hash2; hash1 &= table->sizeMask; entry = ADDRESS_ENTRY(table, hash1); } while (PL_DHASH_ENTRY_IS_BUSY(entry)); } } #endif /* PL_DHASHMETER */