gecko/js/src/jsdhash.cpp
2012-08-02 20:50:51 -07:00

916 lines
30 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Double hashing implementation.
*
* Try to keep this file in sync with xpcom/glue/pldhash.cpp.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jsdhash.h"
#include "jsutil.h"
using namespace js;
#ifdef JS_DHASHMETER
# if defined MOZILLA_CLIENT && defined DEBUG_XXXbrendan
# include "nsTraceMalloc.h"
# endif
# define METER(x) x
#else
# define METER(x) /* nothing */
#endif
/*
* The following DEBUG-only code is used to assert that calls to one of
* table->ops or to an enumerator do not cause re-entry into a call that
* can mutate the table. The recursion level is stored in additional
* space allocated at the end of the entry store to avoid changing
* JSDHashTable, which could cause issues when mixing DEBUG and
* non-DEBUG components.
*/
#ifdef DEBUG
#define JSDHASH_ONELINE_ASSERT JS_ASSERT
#define RECURSION_LEVEL(table_) (*(uint32_t*)(table_->entryStore + \
JS_DHASH_TABLE_SIZE(table_) * \
table_->entrySize))
/*
* Most callers that assert about the recursion level don't care about
* this magical value because they are asserting that mutation is
* allowed (and therefore the level is 0 or 1, depending on whether they
* incremented it).
*
* Only PL_DHashTableFinish needs to allow this special value.
*/
#define IMMUTABLE_RECURSION_LEVEL UINT32_MAX
#define RECURSION_LEVEL_SAFE_TO_FINISH(table_) \
(RECURSION_LEVEL(table_) == 0 || \
RECURSION_LEVEL(table_) == IMMUTABLE_RECURSION_LEVEL)
#define ENTRY_STORE_EXTRA sizeof(uint32_t)
#define INCREMENT_RECURSION_LEVEL(table_) \
JS_BEGIN_MACRO \
if (RECURSION_LEVEL(table_) != IMMUTABLE_RECURSION_LEVEL) \
++RECURSION_LEVEL(table_); \
JS_END_MACRO
#define DECREMENT_RECURSION_LEVEL(table_) \
JS_BEGIN_MACRO \
if (RECURSION_LEVEL(table_) != IMMUTABLE_RECURSION_LEVEL) { \
JS_ASSERT(RECURSION_LEVEL(table_) > 0); \
--RECURSION_LEVEL(table_); \
} \
JS_END_MACRO
#else
#define ENTRY_STORE_EXTRA 0
#define INCREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
#define DECREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
#endif /* defined(DEBUG) */
JS_PUBLIC_API(void *)
JS_DHashAllocTable(JSDHashTable *table, uint32_t nbytes)
{
return OffTheBooks::malloc_(nbytes);
}
JS_PUBLIC_API(void)
JS_DHashFreeTable(JSDHashTable *table, void *ptr)
{
UnwantedForeground::free_(ptr);
}
JS_PUBLIC_API(JSDHashNumber)
JS_DHashStringKey(JSDHashTable *table, const void *key)
{
JSDHashNumber h;
const unsigned char *s;
h = 0;
for (s = (const unsigned char *) key; *s != '\0'; s++)
h = JS_ROTATE_LEFT32(h, 4) ^ *s;
return h;
}
JS_PUBLIC_API(JSDHashNumber)
JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key)
{
return (JSDHashNumber)(uintptr_t)key >> 2;
}
JS_PUBLIC_API(JSBool)
JS_DHashMatchEntryStub(JSDHashTable *table,
const JSDHashEntryHdr *entry,
const void *key)
{
const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
return stub->key == key;
}
JS_PUBLIC_API(JSBool)
JS_DHashMatchStringKey(JSDHashTable *table,
const JSDHashEntryHdr *entry,
const void *key)
{
const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
/* XXX tolerate null keys on account of sloppy Mozilla callers. */
return stub->key == key ||
(stub->key && key &&
strcmp((const char *) stub->key, (const char *) key) == 0);
}
JS_PUBLIC_API(void)
JS_DHashMoveEntryStub(JSDHashTable *table,
const JSDHashEntryHdr *from,
JSDHashEntryHdr *to)
{
js_memcpy(to, from, table->entrySize);
}
JS_PUBLIC_API(void)
JS_DHashClearEntryStub(JSDHashTable *table, JSDHashEntryHdr *entry)
{
memset(entry, 0, table->entrySize);
}
JS_PUBLIC_API(void)
JS_DHashFreeStringKey(JSDHashTable *table, JSDHashEntryHdr *entry)
{
const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
UnwantedForeground::free_((void *) stub->key);
memset(entry, 0, table->entrySize);
}
JS_PUBLIC_API(void)
JS_DHashFinalizeStub(JSDHashTable *table)
{
}
static const JSDHashTableOps stub_ops = {
JS_DHashAllocTable,
JS_DHashFreeTable,
JS_DHashVoidPtrKeyStub,
JS_DHashMatchEntryStub,
JS_DHashMoveEntryStub,
JS_DHashClearEntryStub,
JS_DHashFinalizeStub,
NULL
};
JS_PUBLIC_API(const JSDHashTableOps *)
JS_DHashGetStubOps(void)
{
return &stub_ops;
}
JS_PUBLIC_API(JSDHashTable *)
JS_NewDHashTable(const JSDHashTableOps *ops, void *data, uint32_t entrySize,
uint32_t capacity)
{
JSDHashTable *table;
table = (JSDHashTable *) OffTheBooks::malloc_(sizeof *table);
if (!table)
return NULL;
if (!JS_DHashTableInit(table, ops, data, entrySize, capacity)) {
Foreground::free_(table);
return NULL;
}
return table;
}
JS_PUBLIC_API(void)
JS_DHashTableDestroy(JSDHashTable *table)
{
JS_DHashTableFinish(table);
UnwantedForeground::free_(table);
}
JS_PUBLIC_API(JSBool)
JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data,
uint32_t entrySize, uint32_t capacity)
{
int log2;
uint32_t nbytes;
#ifdef DEBUG
if (entrySize > 10 * sizeof(void *)) {
fprintf(stderr,
"jsdhash: for the table at address %p, the given entrySize"
" of %lu %s favors chaining over double hashing.\n",
(void *) table,
(unsigned long) entrySize,
(entrySize > 16 * sizeof(void*)) ? "definitely" : "probably");
}
#endif
table->ops = ops;
table->data = data;
if (capacity < JS_DHASH_MIN_SIZE)
capacity = JS_DHASH_MIN_SIZE;
JS_CEILING_LOG2(log2, capacity);
capacity = JS_BIT(log2);
if (capacity >= JS_DHASH_SIZE_LIMIT)
return JS_FALSE;
table->hashShift = JS_DHASH_BITS - log2;
table->maxAlphaFrac = (uint8_t)(0x100 * JS_DHASH_DEFAULT_MAX_ALPHA);
table->minAlphaFrac = (uint8_t)(0x100 * JS_DHASH_DEFAULT_MIN_ALPHA);
table->entrySize = entrySize;
table->entryCount = table->removedCount = 0;
table->generation = 0;
nbytes = capacity * entrySize;
table->entryStore = (char *) ops->allocTable(table,
nbytes + ENTRY_STORE_EXTRA);
if (!table->entryStore)
return JS_FALSE;
memset(table->entryStore, 0, nbytes);
METER(memset(&table->stats, 0, sizeof table->stats));
#ifdef DEBUG
RECURSION_LEVEL(table) = 0;
#endif
return JS_TRUE;
}
/*
* Compute max and min load numbers (entry counts) from table params.
*/
#define MAX_LOAD(table, size) (((table)->maxAlphaFrac * (size)) >> 8)
#define MIN_LOAD(table, size) (((table)->minAlphaFrac * (size)) >> 8)
JS_PUBLIC_API(void)
JS_DHashTableSetAlphaBounds(JSDHashTable *table,
float maxAlpha,
float minAlpha)
{
uint32_t size;
/*
* Reject obviously insane bounds, rather than trying to guess what the
* buggy caller intended.
*/
JS_ASSERT(0.5 <= maxAlpha && maxAlpha < 1 && 0 <= minAlpha);
if (maxAlpha < 0.5 || 1 <= maxAlpha || minAlpha < 0)
return;
/*
* Ensure that at least one entry will always be free. If maxAlpha at
* minimum size leaves no entries free, reduce maxAlpha based on minimum
* size and the precision limit of maxAlphaFrac's fixed point format.
*/
JS_ASSERT(JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) >= 1);
if (JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) < 1) {
maxAlpha = (float)
(JS_DHASH_MIN_SIZE - Max(JS_DHASH_MIN_SIZE / 256, 1))
/ JS_DHASH_MIN_SIZE;
}
/*
* Ensure that minAlpha is strictly less than half maxAlpha. Take care
* not to truncate an entry's worth of alpha when storing in minAlphaFrac
* (8-bit fixed point format).
*/
JS_ASSERT(minAlpha < maxAlpha / 2);
if (minAlpha >= maxAlpha / 2) {
size = JS_DHASH_TABLE_SIZE(table);
minAlpha = (size * maxAlpha - Max(size / 256, 1U)) / (2 * size);
}
table->maxAlphaFrac = (uint8_t)(maxAlpha * 256);
table->minAlphaFrac = (uint8_t)(minAlpha * 256);
}
/*
* 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. Note
* that a removed-entry sentinel need be stored only if the removed entry had
* a colliding entry added after it. Therefore we can use 1 as the collision
* flag in addition to the removed-entry sentinel value. Multiplicative hash
* uses the high order bits of keyHash, so this least-significant reservation
* should not hurt the hash function's effectiveness much.
*
* If you change any of these magic numbers, also update JS_DHASH_ENTRY_IS_LIVE
* in jsdhash.h. It used to be private to jsdhash.c, but then became public to
* assist iterator writers who inspect table->entryStore directly.
*/
#define COLLISION_FLAG ((JSDHashNumber) 1)
#define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0)
#define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1)
#define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1)
#define ENTRY_IS_LIVE(entry) JS_DHASH_ENTRY_IS_LIVE(entry)
#define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0
/* Match an entry's keyHash against an unstored one computed from a key. */
#define MATCH_ENTRY_KEYHASH(entry,hash0) \
(((entry)->keyHash & ~COLLISION_FLAG) == (hash0))
/* Compute the address of the indexed entry in table. */
#define ADDRESS_ENTRY(table, index) \
((JSDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize))
JS_PUBLIC_API(void)
JS_DHashTableFinish(JSDHashTable *table)
{
char *entryAddr, *entryLimit;
uint32_t entrySize;
JSDHashEntryHdr *entry;
#ifdef DEBUG_XXXbrendan
static FILE *dumpfp = NULL;
if (!dumpfp) dumpfp = fopen("/tmp/jsdhash.bigdump", "w");
if (dumpfp) {
#ifdef MOZILLA_CLIENT
NS_TraceStack(1, dumpfp);
#endif
JS_DHashTableDumpMeter(table, NULL, dumpfp);
fputc('\n', dumpfp);
}
#endif
INCREMENT_RECURSION_LEVEL(table);
/* Call finalize before clearing entries, so it can enumerate them. */
table->ops->finalize(table);
/* Clear any remaining live entries. */
entryAddr = table->entryStore;
entrySize = table->entrySize;
entryLimit = entryAddr + JS_DHASH_TABLE_SIZE(table) * entrySize;
while (entryAddr < entryLimit) {
entry = (JSDHashEntryHdr *)entryAddr;
if (ENTRY_IS_LIVE(entry)) {
METER(table->stats.removeEnums++);
table->ops->clearEntry(table, entry);
}
entryAddr += entrySize;
}
DECREMENT_RECURSION_LEVEL(table);
JS_ASSERT(RECURSION_LEVEL_SAFE_TO_FINISH(table));
/* Free entry storage last. */
table->ops->freeTable(table, table->entryStore);
}
static JSDHashEntryHdr * JS_DHASH_FASTCALL
SearchTable(JSDHashTable *table, const void *key, JSDHashNumber keyHash,
JSDHashOperator op)
{
JSDHashNumber hash1, hash2;
int hashShift, sizeLog2;
JSDHashEntryHdr *entry, *firstRemoved;
JSDHashMatchEntry matchEntry;
uint32_t sizeMask;
METER(table->stats.searches++);
JS_ASSERT(!(keyHash & COLLISION_FLAG));
/* 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 (JS_DHASH_ENTRY_IS_FREE(entry)) {
METER(table->stats.misses++);
return entry;
}
/* Hit: return entry. */
matchEntry = table->ops->matchEntry;
if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) {
METER(table->stats.hits++);
return entry;
}
/* Collision: double hash. */
sizeLog2 = JS_DHASH_BITS - table->hashShift;
hash2 = HASH2(keyHash, sizeLog2, hashShift);
sizeMask = JS_BITMASK(sizeLog2);
/* Save the first removed entry pointer so JS_DHASH_ADD can recycle it. */
firstRemoved = NULL;
for (;;) {
if (JS_UNLIKELY(ENTRY_IS_REMOVED(entry))) {
if (!firstRemoved)
firstRemoved = entry;
} else {
if (op == JS_DHASH_ADD)
entry->keyHash |= COLLISION_FLAG;
}
METER(table->stats.steps++);
hash1 -= hash2;
hash1 &= sizeMask;
entry = ADDRESS_ENTRY(table, hash1);
if (JS_DHASH_ENTRY_IS_FREE(entry)) {
METER(table->stats.misses++);
return (firstRemoved && op == JS_DHASH_ADD) ? firstRemoved : entry;
}
if (MATCH_ENTRY_KEYHASH(entry, keyHash) &&
matchEntry(table, entry, key)) {
METER(table->stats.hits++);
return entry;
}
}
/* NOTREACHED */
return NULL;
}
/*
* This is a copy of SearchTable, used by ChangeTable, hardcoded to
* 1. assume |op == PL_DHASH_ADD|,
* 2. assume that |key| will never match an existing entry, and
* 3. assume that no entries have been removed from the current table
* structure.
* Avoiding the need for |key| means we can avoid needing a way to map
* entries to keys, which means callers can use complex key types more
* easily.
*/
static JSDHashEntryHdr * JS_DHASH_FASTCALL
FindFreeEntry(JSDHashTable *table, JSDHashNumber keyHash)
{
JSDHashNumber hash1, hash2;
int hashShift, sizeLog2;
JSDHashEntryHdr *entry;
uint32_t sizeMask;
METER(table->stats.searches++);
JS_ASSERT(!(keyHash & COLLISION_FLAG));
/* 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 (JS_DHASH_ENTRY_IS_FREE(entry)) {
METER(table->stats.misses++);
return entry;
}
/* Collision: double hash. */
sizeLog2 = JS_DHASH_BITS - table->hashShift;
hash2 = HASH2(keyHash, sizeLog2, hashShift);
sizeMask = JS_BITMASK(sizeLog2);
for (;;) {
JS_ASSERT(!ENTRY_IS_REMOVED(entry));
entry->keyHash |= COLLISION_FLAG;
METER(table->stats.steps++);
hash1 -= hash2;
hash1 &= sizeMask;
entry = ADDRESS_ENTRY(table, hash1);
if (JS_DHASH_ENTRY_IS_FREE(entry)) {
METER(table->stats.misses++);
return entry;
}
}
/* NOTREACHED */
return NULL;
}
static JSBool
ChangeTable(JSDHashTable *table, int deltaLog2)
{
int oldLog2, newLog2;
uint32_t oldCapacity, newCapacity;
char *newEntryStore, *oldEntryStore, *oldEntryAddr;
uint32_t entrySize, i, nbytes;
JSDHashEntryHdr *oldEntry, *newEntry;
JSDHashMoveEntry moveEntry;
#ifdef DEBUG
uint32_t recursionLevel;
#endif
/* Look, but don't touch, until we succeed in getting new entry store. */
oldLog2 = JS_DHASH_BITS - table->hashShift;
newLog2 = oldLog2 + deltaLog2;
oldCapacity = JS_BIT(oldLog2);
newCapacity = JS_BIT(newLog2);
if (newCapacity >= JS_DHASH_SIZE_LIMIT)
return JS_FALSE;
entrySize = table->entrySize;
nbytes = newCapacity * entrySize;
newEntryStore = (char *) table->ops->allocTable(table,
nbytes + ENTRY_STORE_EXTRA);
if (!newEntryStore)
return JS_FALSE;
/* We can't fail from here on, so update table parameters. */
#ifdef DEBUG
recursionLevel = RECURSION_LEVEL(table);
#endif
table->hashShift = JS_DHASH_BITS - newLog2;
table->removedCount = 0;
table->generation++;
/* Assign the new entry store to table. */
memset(newEntryStore, 0, nbytes);
oldEntryAddr = oldEntryStore = table->entryStore;
table->entryStore = newEntryStore;
moveEntry = table->ops->moveEntry;
#ifdef DEBUG
RECURSION_LEVEL(table) = recursionLevel;
#endif
/* Copy only live entries, leaving removed ones behind. */
for (i = 0; i < oldCapacity; i++) {
oldEntry = (JSDHashEntryHdr *)oldEntryAddr;
if (ENTRY_IS_LIVE(oldEntry)) {
oldEntry->keyHash &= ~COLLISION_FLAG;
newEntry = FindFreeEntry(table, oldEntry->keyHash);
JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(newEntry));
moveEntry(table, oldEntry, newEntry);
newEntry->keyHash = oldEntry->keyHash;
}
oldEntryAddr += entrySize;
}
table->ops->freeTable(table, oldEntryStore);
return JS_TRUE;
}
JS_PUBLIC_API(JSDHashEntryHdr *) JS_DHASH_FASTCALL
JS_DHashTableOperate(JSDHashTable *table, const void *key, JSDHashOperator op)
{
JSDHashNumber keyHash;
JSDHashEntryHdr *entry;
uint32_t size;
int deltaLog2;
JS_ASSERT(op == JS_DHASH_LOOKUP || RECURSION_LEVEL(table) == 0);
INCREMENT_RECURSION_LEVEL(table);
keyHash = table->ops->hashKey(table, key);
keyHash *= JS_DHASH_GOLDEN_RATIO;
/* Avoid 0 and 1 hash codes, they indicate free and removed entries. */
ENSURE_LIVE_KEYHASH(keyHash);
keyHash &= ~COLLISION_FLAG;
switch (op) {
case JS_DHASH_LOOKUP:
METER(table->stats.lookups++);
entry = SearchTable(table, key, keyHash, op);
break;
case JS_DHASH_ADD:
/*
* If alpha is >= .75, grow or compress the table. If key is already
* in the table, we may grow once more than necessary, but only if we
* are on the edge of being overloaded.
*/
size = JS_DHASH_TABLE_SIZE(table);
if (table->entryCount + table->removedCount >= MAX_LOAD(table, size)) {
/* Compress if a quarter or more of all entries are removed. */
if (table->removedCount >= size >> 2) {
METER(table->stats.compresses++);
deltaLog2 = 0;
} else {
METER(table->stats.grows++);
deltaLog2 = 1;
}
/*
* Grow or compress table, returning null if ChangeTable fails and
* falling through might claim the last free entry.
*/
if (!ChangeTable(table, deltaLog2) &&
table->entryCount + table->removedCount == size - 1) {
METER(table->stats.addFailures++);
entry = NULL;
break;
}
}
/*
* Look for entry after possibly growing, so we don't have to add it,
* then skip it while growing the table and re-add it after.
*/
entry = SearchTable(table, key, keyHash, op);
if (!ENTRY_IS_LIVE(entry)) {
/* Initialize the entry, indicating that it's no longer free. */
METER(table->stats.addMisses++);
if (ENTRY_IS_REMOVED(entry)) {
METER(table->stats.addOverRemoved++);
table->removedCount--;
keyHash |= COLLISION_FLAG;
}
if (table->ops->initEntry &&
!table->ops->initEntry(table, entry, key)) {
/* We haven't claimed entry yet; fail with null return. */
memset(entry + 1, 0, table->entrySize - sizeof *entry);
entry = NULL;
break;
}
entry->keyHash = keyHash;
table->entryCount++;
}
METER(else table->stats.addHits++);
break;
case JS_DHASH_REMOVE:
entry = SearchTable(table, key, keyHash, op);
if (ENTRY_IS_LIVE(entry)) {
/* Clear this entry and mark it as "removed". */
METER(table->stats.removeHits++);
JS_DHashTableRawRemove(table, entry);
/* Shrink if alpha is <= .25 and table isn't too small already. */
size = JS_DHASH_TABLE_SIZE(table);
if (size > JS_DHASH_MIN_SIZE &&
table->entryCount <= MIN_LOAD(table, size)) {
METER(table->stats.shrinks++);
(void) ChangeTable(table, -1);
}
}
METER(else table->stats.removeMisses++);
entry = NULL;
break;
default:
JS_ASSERT(0);
entry = NULL;
}
DECREMENT_RECURSION_LEVEL(table);
return entry;
}
JS_PUBLIC_API(void)
JS_DHashTableRawRemove(JSDHashTable *table, JSDHashEntryHdr *entry)
{
JSDHashNumber keyHash; /* load first in case clearEntry goofs it */
JS_ASSERT(RECURSION_LEVEL(table) != IMMUTABLE_RECURSION_LEVEL);
JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(entry));
keyHash = entry->keyHash;
table->ops->clearEntry(table, entry);
if (keyHash & COLLISION_FLAG) {
MARK_ENTRY_REMOVED(entry);
table->removedCount++;
} else {
METER(table->stats.removeFrees++);
MARK_ENTRY_FREE(entry);
}
table->entryCount--;
}
JS_PUBLIC_API(uint32_t)
JS_DHashTableEnumerate(JSDHashTable *table, JSDHashEnumerator etor, void *arg)
{
char *entryAddr, *entryLimit;
uint32_t i, capacity, entrySize, ceiling;
JSBool didRemove;
JSDHashEntryHdr *entry;
JSDHashOperator op;
INCREMENT_RECURSION_LEVEL(table);
entryAddr = table->entryStore;
entrySize = table->entrySize;
capacity = JS_DHASH_TABLE_SIZE(table);
entryLimit = entryAddr + capacity * entrySize;
i = 0;
didRemove = JS_FALSE;
while (entryAddr < entryLimit) {
entry = (JSDHashEntryHdr *)entryAddr;
if (ENTRY_IS_LIVE(entry)) {
op = etor(table, entry, i++, arg);
if (op & JS_DHASH_REMOVE) {
METER(table->stats.removeEnums++);
JS_DHashTableRawRemove(table, entry);
didRemove = JS_TRUE;
}
if (op & JS_DHASH_STOP)
break;
}
entryAddr += entrySize;
}
JS_ASSERT(!didRemove || RECURSION_LEVEL(table) == 1);
/*
* Shrink or compress if a quarter or more of all entries are removed, or
* if the table is underloaded according to the configured minimum alpha,
* and is not minimal-size already. Do this only if we removed above, so
* non-removing enumerations can count on stable table->entryStore until
* the next non-lookup-Operate or removing-Enumerate.
*/
if (didRemove &&
(table->removedCount >= capacity >> 2 ||
(capacity > JS_DHASH_MIN_SIZE &&
table->entryCount <= MIN_LOAD(table, capacity)))) {
METER(table->stats.enumShrinks++);
capacity = table->entryCount;
capacity += capacity >> 1;
if (capacity < JS_DHASH_MIN_SIZE)
capacity = JS_DHASH_MIN_SIZE;
JS_CEILING_LOG2(ceiling, capacity);
ceiling -= JS_DHASH_BITS - table->hashShift;
(void) ChangeTable(table, ceiling);
}
DECREMENT_RECURSION_LEVEL(table);
return i;
}
struct SizeOfEntryExcludingThisArg
{
size_t total;
JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis;
JSMallocSizeOfFun mallocSizeOf;
void *arg; // the arg passed by the user
};
static JSDHashOperator
SizeOfEntryExcludingThisEnumerator(JSDHashTable *table, JSDHashEntryHdr *hdr,
uint32_t number, void *arg)
{
SizeOfEntryExcludingThisArg *e = (SizeOfEntryExcludingThisArg *)arg;
e->total += e->sizeOfEntryExcludingThis(hdr, e->mallocSizeOf, e->arg);
return JS_DHASH_NEXT;
}
extern JS_PUBLIC_API(size_t)
JS_DHashTableSizeOfExcludingThis(const JSDHashTable *table,
JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
JSMallocSizeOfFun mallocSizeOf,
void *arg /* = NULL */)
{
size_t n = 0;
n += mallocSizeOf(table->entryStore);
if (sizeOfEntryExcludingThis) {
SizeOfEntryExcludingThisArg arg2 = { 0, sizeOfEntryExcludingThis, mallocSizeOf, arg };
JS_DHashTableEnumerate(const_cast<JSDHashTable *>(table),
SizeOfEntryExcludingThisEnumerator, &arg2);
n += arg2.total;
}
return n;
}
extern JS_PUBLIC_API(size_t)
JS_DHashTableSizeOfIncludingThis(const JSDHashTable *table,
JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
JSMallocSizeOfFun mallocSizeOf,
void *arg /* = NULL */)
{
return mallocSizeOf(table) +
JS_DHashTableSizeOfExcludingThis(table, sizeOfEntryExcludingThis,
mallocSizeOf, arg);
}
#ifdef DEBUG
JS_PUBLIC_API(void)
JS_DHashMarkTableImmutable(JSDHashTable *table)
{
RECURSION_LEVEL(table) = IMMUTABLE_RECURSION_LEVEL;
}
#endif
#ifdef JS_DHASHMETER
#include <math.h>
JS_PUBLIC_API(void)
JS_DHashTableDumpMeter(JSDHashTable *table, JSDHashEnumerator dump, FILE *fp)
{
char *entryAddr;
uint32_t entrySize, entryCount;
int hashShift, sizeLog2;
uint32_t i, tableSize, sizeMask, chainLen, maxChainLen, chainCount;
JSDHashNumber hash1, hash2, saveHash1, maxChainHash1, maxChainHash2;
double sqsum, mean, variance, sigma;
JSDHashEntryHdr *entry, *probe;
entryAddr = table->entryStore;
entrySize = table->entrySize;
hashShift = table->hashShift;
sizeLog2 = JS_DHASH_BITS - hashShift;
tableSize = JS_DHASH_TABLE_SIZE(table);
sizeMask = JS_BITMASK(sizeLog2);
chainCount = maxChainLen = 0;
hash2 = 0;
sqsum = 0;
for (i = 0; i < tableSize; i++) {
entry = (JSDHashEntryHdr *)entryAddr;
entryAddr += entrySize;
if (!ENTRY_IS_LIVE(entry))
continue;
hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift);
saveHash1 = hash1;
probe = ADDRESS_ENTRY(table, hash1);
chainLen = 1;
if (probe == entry) {
/* Start of a (possibly unit-length) chain. */
chainCount++;
} else {
hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2,
hashShift);
do {
chainLen++;
hash1 -= hash2;
hash1 &= sizeMask;
probe = ADDRESS_ENTRY(table, hash1);
} while (probe != entry);
}
sqsum += chainLen * chainLen;
if (chainLen > maxChainLen) {
maxChainLen = chainLen;
maxChainHash1 = saveHash1;
maxChainHash2 = hash2;
}
}
entryCount = table->entryCount;
if (entryCount && chainCount) {
mean = (double)entryCount / chainCount;
variance = chainCount * sqsum - entryCount * entryCount;
if (variance < 0 || chainCount == 1)
variance = 0;
else
variance /= chainCount * (chainCount - 1);
sigma = sqrt(variance);
} else {
mean = sigma = 0;
}
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", table->stats.searches ?
(double)table->stats.steps
/ table->stats.searches :
0.);
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 recycled removeds: %u\n", table->stats.addOverRemoved);
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 that freed an entry: %u\n", table->stats.removeFrees);
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 (dump && 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) != JS_DHASH_NEXT)
break;
hash1 -= hash2;
hash1 &= sizeMask;
entry = ADDRESS_ENTRY(table, hash1);
} while (JS_DHASH_ENTRY_IS_BUSY(entry));
}
}
#endif /* JS_DHASHMETER */