📄 jsdhash.c
字号:
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- *//* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla 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/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or 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 the Initial Developer are Copyright (C) 1999-2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Brendan Eich <brendan@mozilla.org> (Original Author) * Chris Waterson <waterson@netscape.com> * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** *//* * Double hashing implementation. */#include <stdio.h>#include <stdlib.h>#include <string.h>#include "jsbit.h"#include "jsdhash.h"#include "jsutil.h" /* for JS_ASSERT */#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 RECURSION_LEVEL(table_) (*(uint32*)(table_->entryStore + \ JS_DHASH_TABLE_SIZE(table_) * \ table_->entrySize))#define ENTRY_STORE_EXTRA sizeof(uint32)#define INCREMENT_RECURSION_LEVEL(table_) (++RECURSION_LEVEL(table_))#define DECREMENT_RECURSION_LEVEL(table_) (--RECURSION_LEVEL(table_))#else#define ENTRY_STORE_EXTRA 0#define INCREMENT_RECURSION_LEVEL(table_) ((void)1)#define DECREMENT_RECURSION_LEVEL(table_) ((void)0)#endif /* defined(DEBUG) */JS_PUBLIC_API(void *)JS_DHashAllocTable(JSDHashTable *table, uint32 nbytes){ return malloc(nbytes);}JS_PUBLIC_API(void)JS_DHashFreeTable(JSDHashTable *table, void *ptr){ free(ptr);}JS_PUBLIC_API(JSDHashNumber)JS_DHashStringKey(JSDHashTable *table, const void *key){ JSDHashNumber h; const unsigned char *s; h = 0; for (s = key; *s != '\0'; s++) h = (h >> (JS_DHASH_BITS - 4)) ^ (h << 4) ^ *s; return h;}JS_PUBLIC_API(const void *)JS_DHashGetKeyStub(JSDHashTable *table, JSDHashEntryHdr *entry){ JSDHashEntryStub *stub = (JSDHashEntryStub *)entry; return stub->key;}JS_PUBLIC_API(JSDHashNumber)JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key){ return (JSDHashNumber)(unsigned long)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(stub->key, key) == 0);}JS_PUBLIC_API(void)JS_DHashMoveEntryStub(JSDHashTable *table, const JSDHashEntryHdr *from, JSDHashEntryHdr *to){ 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; 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_DHashGetKeyStub, 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 entrySize, uint32 capacity){ JSDHashTable *table; table = (JSDHashTable *) malloc(sizeof *table); if (!table) return NULL; if (!JS_DHashTableInit(table, ops, data, entrySize, capacity)) { free(table); return NULL; } return table;}JS_PUBLIC_API(void)JS_DHashTableDestroy(JSDHashTable *table){ JS_DHashTableFinish(table); free(table);}JS_PUBLIC_API(JSBool)JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data, uint32 entrySize, uint32 capacity){ int log2; uint32 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 = 0xC0; /* .75 */ table->minAlphaFrac = 0x40; /* .25 */ table->entrySize = entrySize; table->entryCount = table->removedCount = 0; table->generation = 0; nbytes = capacity * entrySize; table->entryStore = 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 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 - JS_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 - JS_MAX(size / 256, 1)) / (2 * size); } table->maxAlphaFrac = (uint8)(maxAlpha * 256); table->minAlphaFrac = (uint8)(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 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(table) == 0); /* Free entry storage last. */ table->ops->freeTable(table, table->entryStore);}static JSDHashEntryHdr * JS_DHASH_FASTCALLSearchTable(JSDHashTable *table, const void *key, JSDHashNumber keyHash, JSDHashOperator op){ JSDHashNumber hash1, hash2; int hashShift, sizeLog2; JSDHashEntryHdr *entry, *firstRemoved; JSDHashMatchEntry matchEntry; uint32 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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -