/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/js/jsdhash.cpp

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs · C++ · 876 lines · 625 code · 111 blank · 140 comment · 94 complexity · a4ea72d4299baa5adae07d29773b5184 MD5 · raw file

  1. /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * The Original Code is Mozilla JavaScript code.
  16. *
  17. * The Initial Developer of the Original Code is
  18. * Netscape Communications Corporation.
  19. * Portions created by the Initial Developer are Copyright (C) 1999-2001
  20. * the Initial Developer. All Rights Reserved.
  21. *
  22. * Contributor(s):
  23. * Brendan Eich <brendan@mozilla.org> (Original Author)
  24. * Chris Waterson <waterson@netscape.com>
  25. * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation
  26. *
  27. * Alternatively, the contents of this file may be used under the terms of
  28. * either of the GNU General Public License Version 2 or later (the "GPL"),
  29. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30. * in which case the provisions of the GPL or the LGPL are applicable instead
  31. * of those above. If you wish to allow use of your version of this file only
  32. * under the terms of either the GPL or the LGPL, and not to allow others to
  33. * use your version of this file under the terms of the MPL, indicate your
  34. * decision by deleting the provisions above and replace them with the notice
  35. * and other provisions required by the GPL or the LGPL. If you do not delete
  36. * the provisions above, a recipient may use your version of this file under
  37. * the terms of any one of the MPL, the GPL or the LGPL.
  38. *
  39. * ***** END LICENSE BLOCK ***** */
  40. /*
  41. * Double hashing implementation.
  42. */
  43. #include <stdio.h>
  44. #include <stdlib.h>
  45. #include <string.h>
  46. #include "jsbit.h"
  47. #include "jsdhash.h"
  48. #include "jsutil.h" /* for JS_ASSERT */
  49. #ifdef JS_DHASHMETER
  50. # if defined MOZILLA_CLIENT && defined DEBUG_XXXbrendan
  51. # include "nsTraceMalloc.h"
  52. # endif
  53. # define METER(x) x
  54. #else
  55. # define METER(x) /* nothing */
  56. #endif
  57. /*
  58. * The following DEBUG-only code is used to assert that calls to one of
  59. * table->ops or to an enumerator do not cause re-entry into a call that
  60. * can mutate the table. The recursion level is stored in additional
  61. * space allocated at the end of the entry store to avoid changing
  62. * JSDHashTable, which could cause issues when mixing DEBUG and
  63. * non-DEBUG components.
  64. */
  65. #ifdef DEBUG
  66. #define JSDHASH_ONELINE_ASSERT JS_ASSERT
  67. #define RECURSION_LEVEL(table_) (*(uint32*)(table_->entryStore + \
  68. JS_DHASH_TABLE_SIZE(table_) * \
  69. table_->entrySize))
  70. #define ENTRY_STORE_EXTRA sizeof(uint32)
  71. #define INCREMENT_RECURSION_LEVEL(table_) \
  72. JS_BEGIN_MACRO \
  73. ++RECURSION_LEVEL(table_); \
  74. JS_END_MACRO
  75. #define DECREMENT_RECURSION_LEVEL(table_) \
  76. JS_BEGIN_MACRO \
  77. JSDHASH_ONELINE_ASSERT(RECURSION_LEVEL(table_) > 0); \
  78. --RECURSION_LEVEL(table_); \
  79. JS_END_MACRO
  80. #else
  81. #define ENTRY_STORE_EXTRA 0
  82. #define INCREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
  83. #define DECREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
  84. #endif /* defined(DEBUG) */
  85. JS_PUBLIC_API(void *)
  86. JS_DHashAllocTable(JSDHashTable *table, uint32 nbytes)
  87. {
  88. return malloc(nbytes);
  89. }
  90. JS_PUBLIC_API(void)
  91. JS_DHashFreeTable(JSDHashTable *table, void *ptr)
  92. {
  93. free(ptr);
  94. }
  95. JS_PUBLIC_API(JSDHashNumber)
  96. JS_DHashStringKey(JSDHashTable *table, const void *key)
  97. {
  98. JSDHashNumber h;
  99. const unsigned char *s;
  100. h = 0;
  101. for (s = (const unsigned char *) key; *s != '\0'; s++)
  102. h = JS_ROTATE_LEFT32(h, 4) ^ *s;
  103. return h;
  104. }
  105. JS_PUBLIC_API(JSDHashNumber)
  106. JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key)
  107. {
  108. return (JSDHashNumber)(unsigned long)key >> 2;
  109. }
  110. JS_PUBLIC_API(JSBool)
  111. JS_DHashMatchEntryStub(JSDHashTable *table,
  112. const JSDHashEntryHdr *entry,
  113. const void *key)
  114. {
  115. const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
  116. return stub->key == key;
  117. }
  118. JS_PUBLIC_API(JSBool)
  119. JS_DHashMatchStringKey(JSDHashTable *table,
  120. const JSDHashEntryHdr *entry,
  121. const void *key)
  122. {
  123. const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
  124. /* XXX tolerate null keys on account of sloppy Mozilla callers. */
  125. return stub->key == key ||
  126. (stub->key && key &&
  127. strcmp((const char *) stub->key, (const char *) key) == 0);
  128. }
  129. JS_PUBLIC_API(void)
  130. JS_DHashMoveEntryStub(JSDHashTable *table,
  131. const JSDHashEntryHdr *from,
  132. JSDHashEntryHdr *to)
  133. {
  134. memcpy(to, from, table->entrySize);
  135. }
  136. JS_PUBLIC_API(void)
  137. JS_DHashClearEntryStub(JSDHashTable *table, JSDHashEntryHdr *entry)
  138. {
  139. memset(entry, 0, table->entrySize);
  140. }
  141. JS_PUBLIC_API(void)
  142. JS_DHashFreeStringKey(JSDHashTable *table, JSDHashEntryHdr *entry)
  143. {
  144. const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
  145. free((void *) stub->key);
  146. memset(entry, 0, table->entrySize);
  147. }
  148. JS_PUBLIC_API(void)
  149. JS_DHashFinalizeStub(JSDHashTable *table)
  150. {
  151. }
  152. static const JSDHashTableOps stub_ops = {
  153. JS_DHashAllocTable,
  154. JS_DHashFreeTable,
  155. JS_DHashVoidPtrKeyStub,
  156. JS_DHashMatchEntryStub,
  157. JS_DHashMoveEntryStub,
  158. JS_DHashClearEntryStub,
  159. JS_DHashFinalizeStub,
  160. NULL
  161. };
  162. JS_PUBLIC_API(const JSDHashTableOps *)
  163. JS_DHashGetStubOps(void)
  164. {
  165. return &stub_ops;
  166. }
  167. JS_PUBLIC_API(JSDHashTable *)
  168. JS_NewDHashTable(const JSDHashTableOps *ops, void *data, uint32 entrySize,
  169. uint32 capacity)
  170. {
  171. JSDHashTable *table;
  172. table = (JSDHashTable *) malloc(sizeof *table);
  173. if (!table)
  174. return NULL;
  175. if (!JS_DHashTableInit(table, ops, data, entrySize, capacity)) {
  176. free(table);
  177. return NULL;
  178. }
  179. return table;
  180. }
  181. JS_PUBLIC_API(void)
  182. JS_DHashTableDestroy(JSDHashTable *table)
  183. {
  184. JS_DHashTableFinish(table);
  185. free(table);
  186. }
  187. JS_PUBLIC_API(JSBool)
  188. JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data,
  189. uint32 entrySize, uint32 capacity)
  190. {
  191. int log2;
  192. uint32 nbytes;
  193. #ifdef DEBUG
  194. if (entrySize > 10 * sizeof(void *)) {
  195. fprintf(stderr,
  196. "jsdhash: for the table at address %p, the given entrySize"
  197. " of %lu %s favors chaining over double hashing.\n",
  198. (void *) table,
  199. (unsigned long) entrySize,
  200. (entrySize > 16 * sizeof(void*)) ? "definitely" : "probably");
  201. }
  202. #endif
  203. table->ops = ops;
  204. table->data = data;
  205. if (capacity < JS_DHASH_MIN_SIZE)
  206. capacity = JS_DHASH_MIN_SIZE;
  207. JS_CEILING_LOG2(log2, capacity);
  208. capacity = JS_BIT(log2);
  209. if (capacity >= JS_DHASH_SIZE_LIMIT)
  210. return JS_FALSE;
  211. table->hashShift = JS_DHASH_BITS - log2;
  212. table->maxAlphaFrac = (uint8)(0x100 * JS_DHASH_DEFAULT_MAX_ALPHA);
  213. table->minAlphaFrac = (uint8)(0x100 * JS_DHASH_DEFAULT_MIN_ALPHA);
  214. table->entrySize = entrySize;
  215. table->entryCount = table->removedCount = 0;
  216. table->generation = 0;
  217. nbytes = capacity * entrySize;
  218. table->entryStore = (char *) ops->allocTable(table,
  219. nbytes + ENTRY_STORE_EXTRA);
  220. if (!table->entryStore)
  221. return JS_FALSE;
  222. memset(table->entryStore, 0, nbytes);
  223. METER(memset(&table->stats, 0, sizeof table->stats));
  224. #ifdef DEBUG
  225. RECURSION_LEVEL(table) = 0;
  226. #endif
  227. return JS_TRUE;
  228. }
  229. /*
  230. * Compute max and min load numbers (entry counts) from table params.
  231. */
  232. #define MAX_LOAD(table, size) (((table)->maxAlphaFrac * (size)) >> 8)
  233. #define MIN_LOAD(table, size) (((table)->minAlphaFrac * (size)) >> 8)
  234. JS_PUBLIC_API(void)
  235. JS_DHashTableSetAlphaBounds(JSDHashTable *table,
  236. float maxAlpha,
  237. float minAlpha)
  238. {
  239. uint32 size;
  240. /*
  241. * Reject obviously insane bounds, rather than trying to guess what the
  242. * buggy caller intended.
  243. */
  244. JS_ASSERT(0.5 <= maxAlpha && maxAlpha < 1 && 0 <= minAlpha);
  245. if (maxAlpha < 0.5 || 1 <= maxAlpha || minAlpha < 0)
  246. return;
  247. /*
  248. * Ensure that at least one entry will always be free. If maxAlpha at
  249. * minimum size leaves no entries free, reduce maxAlpha based on minimum
  250. * size and the precision limit of maxAlphaFrac's fixed point format.
  251. */
  252. JS_ASSERT(JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) >= 1);
  253. if (JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) < 1) {
  254. maxAlpha = (float)
  255. (JS_DHASH_MIN_SIZE - JS_MAX(JS_DHASH_MIN_SIZE / 256, 1))
  256. / JS_DHASH_MIN_SIZE;
  257. }
  258. /*
  259. * Ensure that minAlpha is strictly less than half maxAlpha. Take care
  260. * not to truncate an entry's worth of alpha when storing in minAlphaFrac
  261. * (8-bit fixed point format).
  262. */
  263. JS_ASSERT(minAlpha < maxAlpha / 2);
  264. if (minAlpha >= maxAlpha / 2) {
  265. size = JS_DHASH_TABLE_SIZE(table);
  266. minAlpha = (size * maxAlpha - JS_MAX(size / 256, 1)) / (2 * size);
  267. }
  268. table->maxAlphaFrac = (uint8)(maxAlpha * 256);
  269. table->minAlphaFrac = (uint8)(minAlpha * 256);
  270. }
  271. /*
  272. * Double hashing needs the second hash code to be relatively prime to table
  273. * size, so we simply make hash2 odd.
  274. */
  275. #define HASH1(hash0, shift) ((hash0) >> (shift))
  276. #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
  277. /*
  278. * Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. Note
  279. * that a removed-entry sentinel need be stored only if the removed entry had
  280. * a colliding entry added after it. Therefore we can use 1 as the collision
  281. * flag in addition to the removed-entry sentinel value. Multiplicative hash
  282. * uses the high order bits of keyHash, so this least-significant reservation
  283. * should not hurt the hash function's effectiveness much.
  284. *
  285. * If you change any of these magic numbers, also update JS_DHASH_ENTRY_IS_LIVE
  286. * in jsdhash.h. It used to be private to jsdhash.c, but then became public to
  287. * assist iterator writers who inspect table->entryStore directly.
  288. */
  289. #define COLLISION_FLAG ((JSDHashNumber) 1)
  290. #define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0)
  291. #define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1)
  292. #define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1)
  293. #define ENTRY_IS_LIVE(entry) JS_DHASH_ENTRY_IS_LIVE(entry)
  294. #define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0
  295. /* Match an entry's keyHash against an unstored one computed from a key. */
  296. #define MATCH_ENTRY_KEYHASH(entry,hash0) \
  297. (((entry)->keyHash & ~COLLISION_FLAG) == (hash0))
  298. /* Compute the address of the indexed entry in table. */
  299. #define ADDRESS_ENTRY(table, index) \
  300. ((JSDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize))
  301. JS_PUBLIC_API(void)
  302. JS_DHashTableFinish(JSDHashTable *table)
  303. {
  304. char *entryAddr, *entryLimit;
  305. uint32 entrySize;
  306. JSDHashEntryHdr *entry;
  307. #ifdef DEBUG_XXXbrendan
  308. static FILE *dumpfp = NULL;
  309. if (!dumpfp) dumpfp = fopen("/tmp/jsdhash.bigdump", "w");
  310. if (dumpfp) {
  311. #ifdef MOZILLA_CLIENT
  312. NS_TraceStack(1, dumpfp);
  313. #endif
  314. JS_DHashTableDumpMeter(table, NULL, dumpfp);
  315. fputc('\n', dumpfp);
  316. }
  317. #endif
  318. INCREMENT_RECURSION_LEVEL(table);
  319. /* Call finalize before clearing entries, so it can enumerate them. */
  320. table->ops->finalize(table);
  321. /* Clear any remaining live entries. */
  322. entryAddr = table->entryStore;
  323. entrySize = table->entrySize;
  324. entryLimit = entryAddr + JS_DHASH_TABLE_SIZE(table) * entrySize;
  325. while (entryAddr < entryLimit) {
  326. entry = (JSDHashEntryHdr *)entryAddr;
  327. if (ENTRY_IS_LIVE(entry)) {
  328. METER(table->stats.removeEnums++);
  329. table->ops->clearEntry(table, entry);
  330. }
  331. entryAddr += entrySize;
  332. }
  333. DECREMENT_RECURSION_LEVEL(table);
  334. JS_ASSERT(RECURSION_LEVEL(table) == 0);
  335. /* Free entry storage last. */
  336. table->ops->freeTable(table, table->entryStore);
  337. }
  338. static JSDHashEntryHdr * JS_DHASH_FASTCALL
  339. SearchTable(JSDHashTable *table, const void *key, JSDHashNumber keyHash,
  340. JSDHashOperator op)
  341. {
  342. JSDHashNumber hash1, hash2;
  343. int hashShift, sizeLog2;
  344. JSDHashEntryHdr *entry, *firstRemoved;
  345. JSDHashMatchEntry matchEntry;
  346. uint32 sizeMask;
  347. METER(table->stats.searches++);
  348. JS_ASSERT(!(keyHash & COLLISION_FLAG));
  349. /* Compute the primary hash address. */
  350. hashShift = table->hashShift;
  351. hash1 = HASH1(keyHash, hashShift);
  352. entry = ADDRESS_ENTRY(table, hash1);
  353. /* Miss: return space for a new entry. */
  354. if (JS_DHASH_ENTRY_IS_FREE(entry)) {
  355. METER(table->stats.misses++);
  356. return entry;
  357. }
  358. /* Hit: return entry. */
  359. matchEntry = table->ops->matchEntry;
  360. if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) {
  361. METER(table->stats.hits++);
  362. return entry;
  363. }
  364. /* Collision: double hash. */
  365. sizeLog2 = JS_DHASH_BITS - table->hashShift;
  366. hash2 = HASH2(keyHash, sizeLog2, hashShift);
  367. sizeMask = JS_BITMASK(sizeLog2);
  368. /* Save the first removed entry pointer so JS_DHASH_ADD can recycle it. */
  369. firstRemoved = NULL;
  370. for (;;) {
  371. if (JS_UNLIKELY(ENTRY_IS_REMOVED(entry))) {
  372. if (!firstRemoved)
  373. firstRemoved = entry;
  374. } else {
  375. if (op == JS_DHASH_ADD)
  376. entry->keyHash |= COLLISION_FLAG;
  377. }
  378. METER(table->stats.steps++);
  379. hash1 -= hash2;
  380. hash1 &= sizeMask;
  381. entry = ADDRESS_ENTRY(table, hash1);
  382. if (JS_DHASH_ENTRY_IS_FREE(entry)) {
  383. METER(table->stats.misses++);
  384. return (firstRemoved && op == JS_DHASH_ADD) ? firstRemoved : entry;
  385. }
  386. if (MATCH_ENTRY_KEYHASH(entry, keyHash) &&
  387. matchEntry(table, entry, key)) {
  388. METER(table->stats.hits++);
  389. return entry;
  390. }
  391. }
  392. /* NOTREACHED */
  393. return NULL;
  394. }
  395. /*
  396. * This is a copy of SearchTable, used by ChangeTable, hardcoded to
  397. * 1. assume |op == PL_DHASH_ADD|,
  398. * 2. assume that |key| will never match an existing entry, and
  399. * 3. assume that no entries have been removed from the current table
  400. * structure.
  401. * Avoiding the need for |key| means we can avoid needing a way to map
  402. * entries to keys, which means callers can use complex key types more
  403. * easily.
  404. */
  405. static JSDHashEntryHdr * JS_DHASH_FASTCALL
  406. FindFreeEntry(JSDHashTable *table, JSDHashNumber keyHash)
  407. {
  408. JSDHashNumber hash1, hash2;
  409. int hashShift, sizeLog2;
  410. JSDHashEntryHdr *entry;
  411. uint32 sizeMask;
  412. METER(table->stats.searches++);
  413. JS_ASSERT(!(keyHash & COLLISION_FLAG));
  414. /* Compute the primary hash address. */
  415. hashShift = table->hashShift;
  416. hash1 = HASH1(keyHash, hashShift);
  417. entry = ADDRESS_ENTRY(table, hash1);
  418. /* Miss: return space for a new entry. */
  419. if (JS_DHASH_ENTRY_IS_FREE(entry)) {
  420. METER(table->stats.misses++);
  421. return entry;
  422. }
  423. /* Collision: double hash. */
  424. sizeLog2 = JS_DHASH_BITS - table->hashShift;
  425. hash2 = HASH2(keyHash, sizeLog2, hashShift);
  426. sizeMask = JS_BITMASK(sizeLog2);
  427. for (;;) {
  428. JS_ASSERT(!ENTRY_IS_REMOVED(entry));
  429. entry->keyHash |= COLLISION_FLAG;
  430. METER(table->stats.steps++);
  431. hash1 -= hash2;
  432. hash1 &= sizeMask;
  433. entry = ADDRESS_ENTRY(table, hash1);
  434. if (JS_DHASH_ENTRY_IS_FREE(entry)) {
  435. METER(table->stats.misses++);
  436. return entry;
  437. }
  438. }
  439. /* NOTREACHED */
  440. return NULL;
  441. }
  442. static JSBool
  443. ChangeTable(JSDHashTable *table, int deltaLog2)
  444. {
  445. int oldLog2, newLog2;
  446. uint32 oldCapacity, newCapacity;
  447. char *newEntryStore, *oldEntryStore, *oldEntryAddr;
  448. uint32 entrySize, i, nbytes;
  449. JSDHashEntryHdr *oldEntry, *newEntry;
  450. JSDHashMoveEntry moveEntry;
  451. #ifdef DEBUG
  452. uint32 recursionLevel;
  453. #endif
  454. /* Look, but don't touch, until we succeed in getting new entry store. */
  455. oldLog2 = JS_DHASH_BITS - table->hashShift;
  456. newLog2 = oldLog2 + deltaLog2;
  457. oldCapacity = JS_BIT(oldLog2);
  458. newCapacity = JS_BIT(newLog2);
  459. if (newCapacity >= JS_DHASH_SIZE_LIMIT)
  460. return JS_FALSE;
  461. entrySize = table->entrySize;
  462. nbytes = newCapacity * entrySize;
  463. newEntryStore = (char *) table->ops->allocTable(table,
  464. nbytes + ENTRY_STORE_EXTRA);
  465. if (!newEntryStore)
  466. return JS_FALSE;
  467. /* We can't fail from here on, so update table parameters. */
  468. #ifdef DEBUG
  469. recursionLevel = RECURSION_LEVEL(table);
  470. #endif
  471. table->hashShift = JS_DHASH_BITS - newLog2;
  472. table->removedCount = 0;
  473. table->generation++;
  474. /* Assign the new entry store to table. */
  475. memset(newEntryStore, 0, nbytes);
  476. oldEntryAddr = oldEntryStore = table->entryStore;
  477. table->entryStore = newEntryStore;
  478. moveEntry = table->ops->moveEntry;
  479. #ifdef DEBUG
  480. RECURSION_LEVEL(table) = recursionLevel;
  481. #endif
  482. /* Copy only live entries, leaving removed ones behind. */
  483. for (i = 0; i < oldCapacity; i++) {
  484. oldEntry = (JSDHashEntryHdr *)oldEntryAddr;
  485. if (ENTRY_IS_LIVE(oldEntry)) {
  486. oldEntry->keyHash &= ~COLLISION_FLAG;
  487. newEntry = FindFreeEntry(table, oldEntry->keyHash);
  488. JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(newEntry));
  489. moveEntry(table, oldEntry, newEntry);
  490. newEntry->keyHash = oldEntry->keyHash;
  491. }
  492. oldEntryAddr += entrySize;
  493. }
  494. table->ops->freeTable(table, oldEntryStore);
  495. return JS_TRUE;
  496. }
  497. JS_PUBLIC_API(JSDHashEntryHdr *) JS_DHASH_FASTCALL
  498. JS_DHashTableOperate(JSDHashTable *table, const void *key, JSDHashOperator op)
  499. {
  500. JSDHashNumber keyHash;
  501. JSDHashEntryHdr *entry;
  502. uint32 size;
  503. int deltaLog2;
  504. JS_ASSERT(op == JS_DHASH_LOOKUP || RECURSION_LEVEL(table) == 0);
  505. INCREMENT_RECURSION_LEVEL(table);
  506. keyHash = table->ops->hashKey(table, key);
  507. keyHash *= JS_DHASH_GOLDEN_RATIO;
  508. /* Avoid 0 and 1 hash codes, they indicate free and removed entries. */
  509. ENSURE_LIVE_KEYHASH(keyHash);
  510. keyHash &= ~COLLISION_FLAG;
  511. switch (op) {
  512. case JS_DHASH_LOOKUP:
  513. METER(table->stats.lookups++);
  514. entry = SearchTable(table, key, keyHash, op);
  515. break;
  516. case JS_DHASH_ADD:
  517. /*
  518. * If alpha is >= .75, grow or compress the table. If key is already
  519. * in the table, we may grow once more than necessary, but only if we
  520. * are on the edge of being overloaded.
  521. */
  522. size = JS_DHASH_TABLE_SIZE(table);
  523. if (table->entryCount + table->removedCount >= MAX_LOAD(table, size)) {
  524. /* Compress if a quarter or more of all entries are removed. */
  525. if (table->removedCount >= size >> 2) {
  526. METER(table->stats.compresses++);
  527. deltaLog2 = 0;
  528. } else {
  529. METER(table->stats.grows++);
  530. deltaLog2 = 1;
  531. }
  532. /*
  533. * Grow or compress table, returning null if ChangeTable fails and
  534. * falling through might claim the last free entry.
  535. */
  536. if (!ChangeTable(table, deltaLog2) &&
  537. table->entryCount + table->removedCount == size - 1) {
  538. METER(table->stats.addFailures++);
  539. entry = NULL;
  540. break;
  541. }
  542. }
  543. /*
  544. * Look for entry after possibly growing, so we don't have to add it,
  545. * then skip it while growing the table and re-add it after.
  546. */
  547. entry = SearchTable(table, key, keyHash, op);
  548. if (!ENTRY_IS_LIVE(entry)) {
  549. /* Initialize the entry, indicating that it's no longer free. */
  550. METER(table->stats.addMisses++);
  551. if (ENTRY_IS_REMOVED(entry)) {
  552. METER(table->stats.addOverRemoved++);
  553. table->removedCount--;
  554. keyHash |= COLLISION_FLAG;
  555. }
  556. if (table->ops->initEntry &&
  557. !table->ops->initEntry(table, entry, key)) {
  558. /* We haven't claimed entry yet; fail with null return. */
  559. memset(entry + 1, 0, table->entrySize - sizeof *entry);
  560. entry = NULL;
  561. break;
  562. }
  563. entry->keyHash = keyHash;
  564. table->entryCount++;
  565. }
  566. METER(else table->stats.addHits++);
  567. break;
  568. case JS_DHASH_REMOVE:
  569. entry = SearchTable(table, key, keyHash, op);
  570. if (ENTRY_IS_LIVE(entry)) {
  571. /* Clear this entry and mark it as "removed". */
  572. METER(table->stats.removeHits++);
  573. JS_DHashTableRawRemove(table, entry);
  574. /* Shrink if alpha is <= .25 and table isn't too small already. */
  575. size = JS_DHASH_TABLE_SIZE(table);
  576. if (size > JS_DHASH_MIN_SIZE &&
  577. table->entryCount <= MIN_LOAD(table, size)) {
  578. METER(table->stats.shrinks++);
  579. (void) ChangeTable(table, -1);
  580. }
  581. }
  582. METER(else table->stats.removeMisses++);
  583. entry = NULL;
  584. break;
  585. default:
  586. JS_ASSERT(0);
  587. entry = NULL;
  588. }
  589. DECREMENT_RECURSION_LEVEL(table);
  590. return entry;
  591. }
  592. JS_PUBLIC_API(void)
  593. JS_DHashTableRawRemove(JSDHashTable *table, JSDHashEntryHdr *entry)
  594. {
  595. JSDHashNumber keyHash; /* load first in case clearEntry goofs it */
  596. JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(entry));
  597. keyHash = entry->keyHash;
  598. table->ops->clearEntry(table, entry);
  599. if (keyHash & COLLISION_FLAG) {
  600. MARK_ENTRY_REMOVED(entry);
  601. table->removedCount++;
  602. } else {
  603. METER(table->stats.removeFrees++);
  604. MARK_ENTRY_FREE(entry);
  605. }
  606. table->entryCount--;
  607. }
  608. JS_PUBLIC_API(uint32)
  609. JS_DHashTableEnumerate(JSDHashTable *table, JSDHashEnumerator etor, void *arg)
  610. {
  611. char *entryAddr, *entryLimit;
  612. uint32 i, capacity, entrySize, ceiling;
  613. JSBool didRemove;
  614. JSDHashEntryHdr *entry;
  615. JSDHashOperator op;
  616. INCREMENT_RECURSION_LEVEL(table);
  617. entryAddr = table->entryStore;
  618. entrySize = table->entrySize;
  619. capacity = JS_DHASH_TABLE_SIZE(table);
  620. entryLimit = entryAddr + capacity * entrySize;
  621. i = 0;
  622. didRemove = JS_FALSE;
  623. while (entryAddr < entryLimit) {
  624. entry = (JSDHashEntryHdr *)entryAddr;
  625. if (ENTRY_IS_LIVE(entry)) {
  626. op = etor(table, entry, i++, arg);
  627. if (op & JS_DHASH_REMOVE) {
  628. METER(table->stats.removeEnums++);
  629. JS_DHashTableRawRemove(table, entry);
  630. didRemove = JS_TRUE;
  631. }
  632. if (op & JS_DHASH_STOP)
  633. break;
  634. }
  635. entryAddr += entrySize;
  636. }
  637. JS_ASSERT(!didRemove || RECURSION_LEVEL(table) == 1);
  638. /*
  639. * Shrink or compress if a quarter or more of all entries are removed, or
  640. * if the table is underloaded according to the configured minimum alpha,
  641. * and is not minimal-size already. Do this only if we removed above, so
  642. * non-removing enumerations can count on stable table->entryStore until
  643. * the next non-lookup-Operate or removing-Enumerate.
  644. */
  645. if (didRemove &&
  646. (table->removedCount >= capacity >> 2 ||
  647. (capacity > JS_DHASH_MIN_SIZE &&
  648. table->entryCount <= MIN_LOAD(table, capacity)))) {
  649. METER(table->stats.enumShrinks++);
  650. capacity = table->entryCount;
  651. capacity += capacity >> 1;
  652. if (capacity < JS_DHASH_MIN_SIZE)
  653. capacity = JS_DHASH_MIN_SIZE;
  654. JS_CEILING_LOG2(ceiling, capacity);
  655. ceiling -= JS_DHASH_BITS - table->hashShift;
  656. (void) ChangeTable(table, ceiling);
  657. }
  658. DECREMENT_RECURSION_LEVEL(table);
  659. return i;
  660. }
  661. #ifdef JS_DHASHMETER
  662. #include <math.h>
  663. JS_PUBLIC_API(void)
  664. JS_DHashTableDumpMeter(JSDHashTable *table, JSDHashEnumerator dump, FILE *fp)
  665. {
  666. char *entryAddr;
  667. uint32 entrySize, entryCount;
  668. int hashShift, sizeLog2;
  669. uint32 i, tableSize, sizeMask, chainLen, maxChainLen, chainCount;
  670. JSDHashNumber hash1, hash2, saveHash1, maxChainHash1, maxChainHash2;
  671. double sqsum, mean, variance, sigma;
  672. JSDHashEntryHdr *entry, *probe;
  673. entryAddr = table->entryStore;
  674. entrySize = table->entrySize;
  675. hashShift = table->hashShift;
  676. sizeLog2 = JS_DHASH_BITS - hashShift;
  677. tableSize = JS_DHASH_TABLE_SIZE(table);
  678. sizeMask = JS_BITMASK(sizeLog2);
  679. chainCount = maxChainLen = 0;
  680. hash2 = 0;
  681. sqsum = 0;
  682. for (i = 0; i < tableSize; i++) {
  683. entry = (JSDHashEntryHdr *)entryAddr;
  684. entryAddr += entrySize;
  685. if (!ENTRY_IS_LIVE(entry))
  686. continue;
  687. hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift);
  688. saveHash1 = hash1;
  689. probe = ADDRESS_ENTRY(table, hash1);
  690. chainLen = 1;
  691. if (probe == entry) {
  692. /* Start of a (possibly unit-length) chain. */
  693. chainCount++;
  694. } else {
  695. hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2,
  696. hashShift);
  697. do {
  698. chainLen++;
  699. hash1 -= hash2;
  700. hash1 &= sizeMask;
  701. probe = ADDRESS_ENTRY(table, hash1);
  702. } while (probe != entry);
  703. }
  704. sqsum += chainLen * chainLen;
  705. if (chainLen > maxChainLen) {
  706. maxChainLen = chainLen;
  707. maxChainHash1 = saveHash1;
  708. maxChainHash2 = hash2;
  709. }
  710. }
  711. entryCount = table->entryCount;
  712. if (entryCount && chainCount) {
  713. mean = (double)entryCount / chainCount;
  714. variance = chainCount * sqsum - entryCount * entryCount;
  715. if (variance < 0 || chainCount == 1)
  716. variance = 0;
  717. else
  718. variance /= chainCount * (chainCount - 1);
  719. sigma = sqrt(variance);
  720. } else {
  721. mean = sigma = 0;
  722. }
  723. fprintf(fp, "Double hashing statistics:\n");
  724. fprintf(fp, " table size (in entries): %u\n", tableSize);
  725. fprintf(fp, " number of entries: %u\n", table->entryCount);
  726. fprintf(fp, " number of removed entries: %u\n", table->removedCount);
  727. fprintf(fp, " number of searches: %u\n", table->stats.searches);
  728. fprintf(fp, " number of hits: %u\n", table->stats.hits);
  729. fprintf(fp, " number of misses: %u\n", table->stats.misses);
  730. fprintf(fp, " mean steps per search: %g\n", table->stats.searches ?
  731. (double)table->stats.steps
  732. / table->stats.searches :
  733. 0.);
  734. fprintf(fp, " mean hash chain length: %g\n", mean);
  735. fprintf(fp, " standard deviation: %g\n", sigma);
  736. fprintf(fp, " maximum hash chain length: %u\n", maxChainLen);
  737. fprintf(fp, " number of lookups: %u\n", table->stats.lookups);
  738. fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses);
  739. fprintf(fp, "adds that recycled removeds: %u\n", table->stats.addOverRemoved);
  740. fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits);
  741. fprintf(fp, " add failures: %u\n", table->stats.addFailures);
  742. fprintf(fp, " useful removes: %u\n", table->stats.removeHits);
  743. fprintf(fp, " useless removes: %u\n", table->stats.removeMisses);
  744. fprintf(fp, "removes that freed an entry: %u\n", table->stats.removeFrees);
  745. fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums);
  746. fprintf(fp, " number of grows: %u\n", table->stats.grows);
  747. fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks);
  748. fprintf(fp, " number of compresses: %u\n", table->stats.compresses);
  749. fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks);
  750. if (dump && maxChainLen && hash2) {
  751. fputs("Maximum hash chain:\n", fp);
  752. hash1 = maxChainHash1;
  753. hash2 = maxChainHash2;
  754. entry = ADDRESS_ENTRY(table, hash1);
  755. i = 0;
  756. do {
  757. if (dump(table, entry, i++, fp) != JS_DHASH_NEXT)
  758. break;
  759. hash1 -= hash2;
  760. hash1 &= sizeMask;
  761. entry = ADDRESS_ENTRY(table, hash1);
  762. } while (JS_DHASH_ENTRY_IS_BUSY(entry));
  763. }
  764. }
  765. #endif /* JS_DHASHMETER */