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 : /*
42 : * Double hashing implementation.
43 : *
44 : * Try to keep this file in sync with xpcom/glue/pldhash.cpp.
45 : */
46 : #include <stdio.h>
47 : #include <stdlib.h>
48 : #include <string.h>
49 : #include "jsdhash.h"
50 : #include "jsutil.h"
51 :
52 : using namespace js;
53 :
54 : #ifdef JS_DHASHMETER
55 : # if defined MOZILLA_CLIENT && defined DEBUG_XXXbrendan
56 : # include "nsTraceMalloc.h"
57 : # endif
58 : # define METER(x) x
59 : #else
60 : # define METER(x) /* nothing */
61 : #endif
62 :
63 : /*
64 : * The following DEBUG-only code is used to assert that calls to one of
65 : * table->ops or to an enumerator do not cause re-entry into a call that
66 : * can mutate the table. The recursion level is stored in additional
67 : * space allocated at the end of the entry store to avoid changing
68 : * JSDHashTable, which could cause issues when mixing DEBUG and
69 : * non-DEBUG components.
70 : */
71 : #ifdef DEBUG
72 :
73 : #define JSDHASH_ONELINE_ASSERT JS_ASSERT
74 : #define RECURSION_LEVEL(table_) (*(uint32_t*)(table_->entryStore + \
75 : JS_DHASH_TABLE_SIZE(table_) * \
76 : table_->entrySize))
77 : /*
78 : * Most callers that assert about the recursion level don't care about
79 : * this magical value because they are asserting that mutation is
80 : * allowed (and therefore the level is 0 or 1, depending on whether they
81 : * incremented it).
82 : *
83 : * Only PL_DHashTableFinish needs to allow this special value.
84 : */
85 : #define IMMUTABLE_RECURSION_LEVEL UINT32_MAX
86 :
87 : #define RECURSION_LEVEL_SAFE_TO_FINISH(table_) \
88 : (RECURSION_LEVEL(table_) == 0 || \
89 : RECURSION_LEVEL(table_) == IMMUTABLE_RECURSION_LEVEL)
90 :
91 : #define ENTRY_STORE_EXTRA sizeof(uint32_t)
92 : #define INCREMENT_RECURSION_LEVEL(table_) \
93 : JS_BEGIN_MACRO \
94 : if (RECURSION_LEVEL(table_) != IMMUTABLE_RECURSION_LEVEL) \
95 : ++RECURSION_LEVEL(table_); \
96 : JS_END_MACRO
97 : #define DECREMENT_RECURSION_LEVEL(table_) \
98 : JS_BEGIN_MACRO \
99 : if (RECURSION_LEVEL(table_) != IMMUTABLE_RECURSION_LEVEL) { \
100 : JS_ASSERT(RECURSION_LEVEL(table_) > 0); \
101 : --RECURSION_LEVEL(table_); \
102 : } \
103 : JS_END_MACRO
104 :
105 : #else
106 :
107 : #define ENTRY_STORE_EXTRA 0
108 : #define INCREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
109 : #define DECREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
110 :
111 : #endif /* defined(DEBUG) */
112 :
113 : JS_PUBLIC_API(void *)
114 144158 : JS_DHashAllocTable(JSDHashTable *table, uint32_t nbytes)
115 : {
116 144158 : return OffTheBooks::malloc_(nbytes);
117 : }
118 :
119 : JS_PUBLIC_API(void)
120 144135 : JS_DHashFreeTable(JSDHashTable *table, void *ptr)
121 : {
122 144135 : UnwantedForeground::free_(ptr);
123 144135 : }
124 :
125 : JS_PUBLIC_API(JSDHashNumber)
126 0 : JS_DHashStringKey(JSDHashTable *table, const void *key)
127 : {
128 : JSDHashNumber h;
129 : const unsigned char *s;
130 :
131 0 : h = 0;
132 0 : for (s = (const unsigned char *) key; *s != '\0'; s++)
133 0 : h = JS_ROTATE_LEFT32(h, 4) ^ *s;
134 0 : return h;
135 : }
136 :
137 : JS_PUBLIC_API(JSDHashNumber)
138 8286606 : JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key)
139 : {
140 8286606 : return (JSDHashNumber)(uintptr_t)key >> 2;
141 : }
142 :
143 : JS_PUBLIC_API(JSBool)
144 4223493 : JS_DHashMatchEntryStub(JSDHashTable *table,
145 : const JSDHashEntryHdr *entry,
146 : const void *key)
147 : {
148 4223493 : const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
149 :
150 4223493 : return stub->key == key;
151 : }
152 :
153 : JS_PUBLIC_API(JSBool)
154 0 : JS_DHashMatchStringKey(JSDHashTable *table,
155 : const JSDHashEntryHdr *entry,
156 : const void *key)
157 : {
158 0 : const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
159 :
160 : /* XXX tolerate null keys on account of sloppy Mozilla callers. */
161 : return stub->key == key ||
162 : (stub->key && key &&
163 0 : strcmp((const char *) stub->key, (const char *) key) == 0);
164 : }
165 :
166 : JS_PUBLIC_API(void)
167 3307662 : JS_DHashMoveEntryStub(JSDHashTable *table,
168 : const JSDHashEntryHdr *from,
169 : JSDHashEntryHdr *to)
170 : {
171 3307662 : js_memcpy(to, from, table->entrySize);
172 3307662 : }
173 :
174 : JS_PUBLIC_API(void)
175 2338863 : JS_DHashClearEntryStub(JSDHashTable *table, JSDHashEntryHdr *entry)
176 : {
177 2338863 : memset(entry, 0, table->entrySize);
178 2338863 : }
179 :
180 : JS_PUBLIC_API(void)
181 0 : JS_DHashFreeStringKey(JSDHashTable *table, JSDHashEntryHdr *entry)
182 : {
183 0 : const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
184 :
185 0 : UnwantedForeground::free_((void *) stub->key);
186 0 : memset(entry, 0, table->entrySize);
187 0 : }
188 :
189 : JS_PUBLIC_API(void)
190 61873 : JS_DHashFinalizeStub(JSDHashTable *table)
191 : {
192 61873 : }
193 :
194 : static const JSDHashTableOps stub_ops = {
195 : JS_DHashAllocTable,
196 : JS_DHashFreeTable,
197 : JS_DHashVoidPtrKeyStub,
198 : JS_DHashMatchEntryStub,
199 : JS_DHashMoveEntryStub,
200 : JS_DHashClearEntryStub,
201 : JS_DHashFinalizeStub,
202 : NULL
203 : };
204 :
205 : JS_PUBLIC_API(const JSDHashTableOps *)
206 54876 : JS_DHashGetStubOps(void)
207 : {
208 54876 : return &stub_ops;
209 : }
210 :
211 : JS_PUBLIC_API(JSDHashTable *)
212 60465 : JS_NewDHashTable(const JSDHashTableOps *ops, void *data, uint32_t entrySize,
213 : uint32_t capacity)
214 : {
215 : JSDHashTable *table;
216 :
217 60465 : table = (JSDHashTable *) OffTheBooks::malloc_(sizeof *table);
218 60465 : if (!table)
219 0 : return NULL;
220 60465 : if (!JS_DHashTableInit(table, ops, data, entrySize, capacity)) {
221 0 : Foreground::free_(table);
222 0 : return NULL;
223 : }
224 60465 : return table;
225 : }
226 :
227 : JS_PUBLIC_API(void)
228 60443 : JS_DHashTableDestroy(JSDHashTable *table)
229 : {
230 60443 : JS_DHashTableFinish(table);
231 60443 : UnwantedForeground::free_(table);
232 60443 : }
233 :
234 : JS_PUBLIC_API(JSBool)
235 61896 : JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data,
236 : uint32_t entrySize, uint32_t capacity)
237 : {
238 : int log2;
239 : uint32_t nbytes;
240 :
241 : #ifdef DEBUG
242 61896 : if (entrySize > 10 * sizeof(void *)) {
243 : fprintf(stderr,
244 : "jsdhash: for the table at address %p, the given entrySize"
245 : " of %lu %s favors chaining over double hashing.\n",
246 : (void *) table,
247 : (unsigned long) entrySize,
248 0 : (entrySize > 16 * sizeof(void*)) ? "definitely" : "probably");
249 : }
250 : #endif
251 :
252 61896 : table->ops = ops;
253 61896 : table->data = data;
254 61896 : if (capacity < JS_DHASH_MIN_SIZE)
255 1404 : capacity = JS_DHASH_MIN_SIZE;
256 :
257 61896 : JS_CEILING_LOG2(log2, capacity);
258 :
259 61896 : capacity = JS_BIT(log2);
260 61896 : if (capacity >= JS_DHASH_SIZE_LIMIT)
261 0 : return JS_FALSE;
262 61896 : table->hashShift = JS_DHASH_BITS - log2;
263 61896 : table->maxAlphaFrac = (uint8_t)(0x100 * JS_DHASH_DEFAULT_MAX_ALPHA);
264 61896 : table->minAlphaFrac = (uint8_t)(0x100 * JS_DHASH_DEFAULT_MIN_ALPHA);
265 61896 : table->entrySize = entrySize;
266 61896 : table->entryCount = table->removedCount = 0;
267 61896 : table->generation = 0;
268 61896 : nbytes = capacity * entrySize;
269 :
270 : table->entryStore = (char *) ops->allocTable(table,
271 61896 : nbytes + ENTRY_STORE_EXTRA);
272 61896 : if (!table->entryStore)
273 0 : return JS_FALSE;
274 61896 : memset(table->entryStore, 0, nbytes);
275 : METER(memset(&table->stats, 0, sizeof table->stats));
276 :
277 : #ifdef DEBUG
278 61896 : RECURSION_LEVEL(table) = 0;
279 : #endif
280 :
281 61896 : return JS_TRUE;
282 : }
283 :
284 : /*
285 : * Compute max and min load numbers (entry counts) from table params.
286 : */
287 : #define MAX_LOAD(table, size) (((table)->maxAlphaFrac * (size)) >> 8)
288 : #define MIN_LOAD(table, size) (((table)->minAlphaFrac * (size)) >> 8)
289 :
290 : JS_PUBLIC_API(void)
291 0 : JS_DHashTableSetAlphaBounds(JSDHashTable *table,
292 : float maxAlpha,
293 : float minAlpha)
294 : {
295 : uint32_t size;
296 :
297 : /*
298 : * Reject obviously insane bounds, rather than trying to guess what the
299 : * buggy caller intended.
300 : */
301 0 : JS_ASSERT(0.5 <= maxAlpha && maxAlpha < 1 && 0 <= minAlpha);
302 0 : if (maxAlpha < 0.5 || 1 <= maxAlpha || minAlpha < 0)
303 0 : return;
304 :
305 : /*
306 : * Ensure that at least one entry will always be free. If maxAlpha at
307 : * minimum size leaves no entries free, reduce maxAlpha based on minimum
308 : * size and the precision limit of maxAlphaFrac's fixed point format.
309 : */
310 0 : JS_ASSERT(JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) >= 1);
311 0 : if (JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) < 1) {
312 : maxAlpha = (float)
313 : (JS_DHASH_MIN_SIZE - JS_MAX(JS_DHASH_MIN_SIZE / 256, 1))
314 0 : / JS_DHASH_MIN_SIZE;
315 : }
316 :
317 : /*
318 : * Ensure that minAlpha is strictly less than half maxAlpha. Take care
319 : * not to truncate an entry's worth of alpha when storing in minAlphaFrac
320 : * (8-bit fixed point format).
321 : */
322 0 : JS_ASSERT(minAlpha < maxAlpha / 2);
323 0 : if (minAlpha >= maxAlpha / 2) {
324 0 : size = JS_DHASH_TABLE_SIZE(table);
325 0 : minAlpha = (size * maxAlpha - JS_MAX(size / 256, 1)) / (2 * size);
326 : }
327 :
328 0 : table->maxAlphaFrac = (uint8_t)(maxAlpha * 256);
329 0 : table->minAlphaFrac = (uint8_t)(minAlpha * 256);
330 : }
331 :
332 : /*
333 : * Double hashing needs the second hash code to be relatively prime to table
334 : * size, so we simply make hash2 odd.
335 : */
336 : #define HASH1(hash0, shift) ((hash0) >> (shift))
337 : #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
338 :
339 : /*
340 : * Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. Note
341 : * that a removed-entry sentinel need be stored only if the removed entry had
342 : * a colliding entry added after it. Therefore we can use 1 as the collision
343 : * flag in addition to the removed-entry sentinel value. Multiplicative hash
344 : * uses the high order bits of keyHash, so this least-significant reservation
345 : * should not hurt the hash function's effectiveness much.
346 : *
347 : * If you change any of these magic numbers, also update JS_DHASH_ENTRY_IS_LIVE
348 : * in jsdhash.h. It used to be private to jsdhash.c, but then became public to
349 : * assist iterator writers who inspect table->entryStore directly.
350 : */
351 : #define COLLISION_FLAG ((JSDHashNumber) 1)
352 : #define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0)
353 : #define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1)
354 : #define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1)
355 : #define ENTRY_IS_LIVE(entry) JS_DHASH_ENTRY_IS_LIVE(entry)
356 : #define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0
357 :
358 : /* Match an entry's keyHash against an unstored one computed from a key. */
359 : #define MATCH_ENTRY_KEYHASH(entry,hash0) \
360 : (((entry)->keyHash & ~COLLISION_FLAG) == (hash0))
361 :
362 : /* Compute the address of the indexed entry in table. */
363 : #define ADDRESS_ENTRY(table, index) \
364 : ((JSDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize))
365 :
366 : JS_PUBLIC_API(void)
367 61873 : JS_DHashTableFinish(JSDHashTable *table)
368 : {
369 : char *entryAddr, *entryLimit;
370 : uint32_t entrySize;
371 : JSDHashEntryHdr *entry;
372 :
373 : #ifdef DEBUG_XXXbrendan
374 : static FILE *dumpfp = NULL;
375 : if (!dumpfp) dumpfp = fopen("/tmp/jsdhash.bigdump", "w");
376 : if (dumpfp) {
377 : #ifdef MOZILLA_CLIENT
378 : NS_TraceStack(1, dumpfp);
379 : #endif
380 : JS_DHashTableDumpMeter(table, NULL, dumpfp);
381 : fputc('\n', dumpfp);
382 : }
383 : #endif
384 :
385 61873 : INCREMENT_RECURSION_LEVEL(table);
386 :
387 : /* Call finalize before clearing entries, so it can enumerate them. */
388 61873 : table->ops->finalize(table);
389 :
390 : /* Clear any remaining live entries. */
391 61873 : entryAddr = table->entryStore;
392 61873 : entrySize = table->entrySize;
393 61873 : entryLimit = entryAddr + JS_DHASH_TABLE_SIZE(table) * entrySize;
394 2236050 : while (entryAddr < entryLimit) {
395 2112304 : entry = (JSDHashEntryHdr *)entryAddr;
396 2112304 : if (ENTRY_IS_LIVE(entry)) {
397 : METER(table->stats.removeEnums++);
398 170462 : table->ops->clearEntry(table, entry);
399 : }
400 2112304 : entryAddr += entrySize;
401 : }
402 :
403 61873 : DECREMENT_RECURSION_LEVEL(table);
404 61873 : JS_ASSERT(RECURSION_LEVEL_SAFE_TO_FINISH(table));
405 :
406 : /* Free entry storage last. */
407 61873 : table->ops->freeTable(table, table->entryStore);
408 61873 : }
409 :
410 : static JSDHashEntryHdr * JS_DHASH_FASTCALL
411 12734916 : SearchTable(JSDHashTable *table, const void *key, JSDHashNumber keyHash,
412 : JSDHashOperator op)
413 : {
414 : JSDHashNumber hash1, hash2;
415 : int hashShift, sizeLog2;
416 : JSDHashEntryHdr *entry, *firstRemoved;
417 : JSDHashMatchEntry matchEntry;
418 : uint32_t sizeMask;
419 :
420 : METER(table->stats.searches++);
421 12734916 : JS_ASSERT(!(keyHash & COLLISION_FLAG));
422 :
423 : /* Compute the primary hash address. */
424 12734916 : hashShift = table->hashShift;
425 12734916 : hash1 = HASH1(keyHash, hashShift);
426 12734916 : entry = ADDRESS_ENTRY(table, hash1);
427 :
428 : /* Miss: return space for a new entry. */
429 12734916 : if (JS_DHASH_ENTRY_IS_FREE(entry)) {
430 : METER(table->stats.misses++);
431 2590636 : return entry;
432 : }
433 :
434 : /* Hit: return entry. */
435 10144280 : matchEntry = table->ops->matchEntry;
436 10144280 : if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) {
437 : METER(table->stats.hits++);
438 5418228 : return entry;
439 : }
440 :
441 : /* Collision: double hash. */
442 4726052 : sizeLog2 = JS_DHASH_BITS - table->hashShift;
443 4726052 : hash2 = HASH2(keyHash, sizeLog2, hashShift);
444 4726052 : sizeMask = JS_BITMASK(sizeLog2);
445 :
446 : /* Save the first removed entry pointer so JS_DHASH_ADD can recycle it. */
447 4726052 : firstRemoved = NULL;
448 :
449 4922653 : for (;;) {
450 9648705 : if (JS_UNLIKELY(ENTRY_IS_REMOVED(entry))) {
451 798018 : if (!firstRemoved)
452 600248 : firstRemoved = entry;
453 : } else {
454 8850687 : if (op == JS_DHASH_ADD)
455 2541318 : entry->keyHash |= COLLISION_FLAG;
456 : }
457 :
458 : METER(table->stats.steps++);
459 9648705 : hash1 -= hash2;
460 9648705 : hash1 &= sizeMask;
461 :
462 9648705 : entry = ADDRESS_ENTRY(table, hash1);
463 9648705 : if (JS_DHASH_ENTRY_IS_FREE(entry)) {
464 : METER(table->stats.misses++);
465 2087511 : return (firstRemoved && op == JS_DHASH_ADD) ? firstRemoved : entry;
466 : }
467 :
468 10201430 : if (MATCH_ENTRY_KEYHASH(entry, keyHash) &&
469 2640236 : matchEntry(table, entry, key)) {
470 : METER(table->stats.hits++);
471 2638541 : return entry;
472 : }
473 : }
474 :
475 : /* NOTREACHED */
476 : return NULL;
477 : }
478 :
479 : /*
480 : * This is a copy of SearchTable, used by ChangeTable, hardcoded to
481 : * 1. assume |op == PL_DHASH_ADD|,
482 : * 2. assume that |key| will never match an existing entry, and
483 : * 3. assume that no entries have been removed from the current table
484 : * structure.
485 : * Avoiding the need for |key| means we can avoid needing a way to map
486 : * entries to keys, which means callers can use complex key types more
487 : * easily.
488 : */
489 : static JSDHashEntryHdr * JS_DHASH_FASTCALL
490 3307662 : FindFreeEntry(JSDHashTable *table, JSDHashNumber keyHash)
491 : {
492 : JSDHashNumber hash1, hash2;
493 : int hashShift, sizeLog2;
494 : JSDHashEntryHdr *entry;
495 : uint32_t sizeMask;
496 :
497 : METER(table->stats.searches++);
498 3307662 : JS_ASSERT(!(keyHash & COLLISION_FLAG));
499 :
500 : /* Compute the primary hash address. */
501 3307662 : hashShift = table->hashShift;
502 3307662 : hash1 = HASH1(keyHash, hashShift);
503 3307662 : entry = ADDRESS_ENTRY(table, hash1);
504 :
505 : /* Miss: return space for a new entry. */
506 3307662 : if (JS_DHASH_ENTRY_IS_FREE(entry)) {
507 : METER(table->stats.misses++);
508 2649995 : return entry;
509 : }
510 :
511 : /* Collision: double hash. */
512 657667 : sizeLog2 = JS_DHASH_BITS - table->hashShift;
513 657667 : hash2 = HASH2(keyHash, sizeLog2, hashShift);
514 657667 : sizeMask = JS_BITMASK(sizeLog2);
515 :
516 251740 : for (;;) {
517 909407 : JS_ASSERT(!ENTRY_IS_REMOVED(entry));
518 909407 : entry->keyHash |= COLLISION_FLAG;
519 :
520 : METER(table->stats.steps++);
521 909407 : hash1 -= hash2;
522 909407 : hash1 &= sizeMask;
523 :
524 909407 : entry = ADDRESS_ENTRY(table, hash1);
525 909407 : if (JS_DHASH_ENTRY_IS_FREE(entry)) {
526 : METER(table->stats.misses++);
527 657667 : return entry;
528 : }
529 : }
530 :
531 : /* NOTREACHED */
532 : return NULL;
533 : }
534 :
535 : static JSBool
536 82262 : ChangeTable(JSDHashTable *table, int deltaLog2)
537 : {
538 : int oldLog2, newLog2;
539 : uint32_t oldCapacity, newCapacity;
540 : char *newEntryStore, *oldEntryStore, *oldEntryAddr;
541 : uint32_t entrySize, i, nbytes;
542 : JSDHashEntryHdr *oldEntry, *newEntry;
543 : JSDHashMoveEntry moveEntry;
544 : #ifdef DEBUG
545 : uint32_t recursionLevel;
546 : #endif
547 :
548 : /* Look, but don't touch, until we succeed in getting new entry store. */
549 82262 : oldLog2 = JS_DHASH_BITS - table->hashShift;
550 82262 : newLog2 = oldLog2 + deltaLog2;
551 82262 : oldCapacity = JS_BIT(oldLog2);
552 82262 : newCapacity = JS_BIT(newLog2);
553 82262 : if (newCapacity >= JS_DHASH_SIZE_LIMIT)
554 0 : return JS_FALSE;
555 82262 : entrySize = table->entrySize;
556 82262 : nbytes = newCapacity * entrySize;
557 :
558 : newEntryStore = (char *) table->ops->allocTable(table,
559 82262 : nbytes + ENTRY_STORE_EXTRA);
560 82262 : if (!newEntryStore)
561 0 : return JS_FALSE;
562 :
563 : /* We can't fail from here on, so update table parameters. */
564 : #ifdef DEBUG
565 82262 : recursionLevel = RECURSION_LEVEL(table);
566 : #endif
567 82262 : table->hashShift = JS_DHASH_BITS - newLog2;
568 82262 : table->removedCount = 0;
569 82262 : table->generation++;
570 :
571 : /* Assign the new entry store to table. */
572 82262 : memset(newEntryStore, 0, nbytes);
573 82262 : oldEntryAddr = oldEntryStore = table->entryStore;
574 82262 : table->entryStore = newEntryStore;
575 82262 : moveEntry = table->ops->moveEntry;
576 : #ifdef DEBUG
577 82262 : RECURSION_LEVEL(table) = recursionLevel;
578 : #endif
579 :
580 : /* Copy only live entries, leaving removed ones behind. */
581 9175350 : for (i = 0; i < oldCapacity; i++) {
582 9093088 : oldEntry = (JSDHashEntryHdr *)oldEntryAddr;
583 9093088 : if (ENTRY_IS_LIVE(oldEntry)) {
584 3307662 : oldEntry->keyHash &= ~COLLISION_FLAG;
585 3307662 : newEntry = FindFreeEntry(table, oldEntry->keyHash);
586 3307662 : JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(newEntry));
587 3307662 : moveEntry(table, oldEntry, newEntry);
588 3307662 : newEntry->keyHash = oldEntry->keyHash;
589 : }
590 9093088 : oldEntryAddr += entrySize;
591 : }
592 :
593 82262 : table->ops->freeTable(table, oldEntryStore);
594 82262 : return JS_TRUE;
595 : }
596 :
597 : JS_PUBLIC_API(JSDHashEntryHdr *) JS_DHASH_FASTCALL
598 12734916 : JS_DHashTableOperate(JSDHashTable *table, const void *key, JSDHashOperator op)
599 : {
600 : JSDHashNumber keyHash;
601 : JSDHashEntryHdr *entry;
602 : uint32_t size;
603 : int deltaLog2;
604 :
605 12734916 : JS_ASSERT(op == JS_DHASH_LOOKUP || RECURSION_LEVEL(table) == 0);
606 12734916 : INCREMENT_RECURSION_LEVEL(table);
607 :
608 12734916 : keyHash = table->ops->hashKey(table, key);
609 12734916 : keyHash *= JS_DHASH_GOLDEN_RATIO;
610 :
611 : /* Avoid 0 and 1 hash codes, they indicate free and removed entries. */
612 12734916 : ENSURE_LIVE_KEYHASH(keyHash);
613 12734916 : keyHash &= ~COLLISION_FLAG;
614 :
615 12734916 : switch (op) {
616 : case JS_DHASH_LOOKUP:
617 : METER(table->stats.lookups++);
618 7996520 : entry = SearchTable(table, key, keyHash, op);
619 7996520 : break;
620 :
621 : case JS_DHASH_ADD:
622 : /*
623 : * If alpha is >= .75, grow or compress the table. If key is already
624 : * in the table, we may grow once more than necessary, but only if we
625 : * are on the edge of being overloaded.
626 : */
627 2705567 : size = JS_DHASH_TABLE_SIZE(table);
628 2705567 : if (table->entryCount + table->removedCount >= MAX_LOAD(table, size)) {
629 : /* Compress if a quarter or more of all entries are removed. */
630 21414 : if (table->removedCount >= size >> 2) {
631 : METER(table->stats.compresses++);
632 138 : deltaLog2 = 0;
633 : } else {
634 : METER(table->stats.grows++);
635 21276 : deltaLog2 = 1;
636 : }
637 :
638 : /*
639 : * Grow or compress table, returning null if ChangeTable fails and
640 : * falling through might claim the last free entry.
641 : */
642 21414 : if (!ChangeTable(table, deltaLog2) &&
643 : table->entryCount + table->removedCount == size - 1) {
644 : METER(table->stats.addFailures++);
645 0 : entry = NULL;
646 0 : break;
647 : }
648 : }
649 :
650 : /*
651 : * Look for entry after possibly growing, so we don't have to add it,
652 : * then skip it while growing the table and re-add it after.
653 : */
654 2705567 : entry = SearchTable(table, key, keyHash, op);
655 2705567 : if (!ENTRY_IS_LIVE(entry)) {
656 : /* Initialize the entry, indicating that it's no longer free. */
657 : METER(table->stats.addMisses++);
658 2339278 : if (ENTRY_IS_REMOVED(entry)) {
659 : METER(table->stats.addOverRemoved++);
660 130498 : table->removedCount--;
661 130498 : keyHash |= COLLISION_FLAG;
662 : }
663 2339278 : if (table->ops->initEntry &&
664 0 : !table->ops->initEntry(table, entry, key)) {
665 : /* We haven't claimed entry yet; fail with null return. */
666 0 : memset(entry + 1, 0, table->entrySize - sizeof *entry);
667 0 : entry = NULL;
668 0 : break;
669 : }
670 2339278 : entry->keyHash = keyHash;
671 2339278 : table->entryCount++;
672 : }
673 : METER(else table->stats.addHits++);
674 2705567 : break;
675 :
676 : case JS_DHASH_REMOVE:
677 2032829 : entry = SearchTable(table, key, keyHash, op);
678 2032829 : if (ENTRY_IS_LIVE(entry)) {
679 : /* Clear this entry and mark it as "removed". */
680 : METER(table->stats.removeHits++);
681 1680755 : JS_DHashTableRawRemove(table, entry);
682 :
683 : /* Shrink if alpha is <= .25 and table isn't too small already. */
684 1680755 : size = JS_DHASH_TABLE_SIZE(table);
685 1680755 : if (size > JS_DHASH_MIN_SIZE &&
686 : table->entryCount <= MIN_LOAD(table, size)) {
687 : METER(table->stats.shrinks++);
688 46218 : (void) ChangeTable(table, -1);
689 : }
690 : }
691 : METER(else table->stats.removeMisses++);
692 2032829 : entry = NULL;
693 2032829 : break;
694 :
695 : default:
696 0 : JS_ASSERT(0);
697 0 : entry = NULL;
698 : }
699 :
700 12734916 : DECREMENT_RECURSION_LEVEL(table);
701 :
702 12734916 : return entry;
703 : }
704 :
705 : JS_PUBLIC_API(void)
706 2168706 : JS_DHashTableRawRemove(JSDHashTable *table, JSDHashEntryHdr *entry)
707 : {
708 : JSDHashNumber keyHash; /* load first in case clearEntry goofs it */
709 :
710 2168706 : JS_ASSERT(RECURSION_LEVEL(table) != IMMUTABLE_RECURSION_LEVEL);
711 :
712 2168706 : JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(entry));
713 2168706 : keyHash = entry->keyHash;
714 2168706 : table->ops->clearEntry(table, entry);
715 2168706 : if (keyHash & COLLISION_FLAG) {
716 667156 : MARK_ENTRY_REMOVED(entry);
717 667156 : table->removedCount++;
718 : } else {
719 : METER(table->stats.removeFrees++);
720 1501550 : MARK_ENTRY_FREE(entry);
721 : }
722 2168706 : table->entryCount--;
723 2168706 : }
724 :
725 : JS_PUBLIC_API(uint32_t)
726 1329799 : JS_DHashTableEnumerate(JSDHashTable *table, JSDHashEnumerator etor, void *arg)
727 : {
728 : char *entryAddr, *entryLimit;
729 : uint32_t i, capacity, entrySize, ceiling;
730 : JSBool didRemove;
731 : JSDHashEntryHdr *entry;
732 : JSDHashOperator op;
733 :
734 1329799 : INCREMENT_RECURSION_LEVEL(table);
735 :
736 1329799 : entryAddr = table->entryStore;
737 1329799 : entrySize = table->entrySize;
738 1329799 : capacity = JS_DHASH_TABLE_SIZE(table);
739 1329799 : entryLimit = entryAddr + capacity * entrySize;
740 1329799 : i = 0;
741 1329799 : didRemove = JS_FALSE;
742 63789950 : while (entryAddr < entryLimit) {
743 61130352 : entry = (JSDHashEntryHdr *)entryAddr;
744 61130352 : if (ENTRY_IS_LIVE(entry)) {
745 14236898 : op = etor(table, entry, i++, arg);
746 14236898 : if (op & JS_DHASH_REMOVE) {
747 : METER(table->stats.removeEnums++);
748 487951 : JS_DHashTableRawRemove(table, entry);
749 487951 : didRemove = JS_TRUE;
750 : }
751 14236898 : if (op & JS_DHASH_STOP)
752 0 : break;
753 : }
754 61130352 : entryAddr += entrySize;
755 : }
756 :
757 1329799 : JS_ASSERT(!didRemove || RECURSION_LEVEL(table) == 1);
758 :
759 : /*
760 : * Shrink or compress if a quarter or more of all entries are removed, or
761 : * if the table is underloaded according to the configured minimum alpha,
762 : * and is not minimal-size already. Do this only if we removed above, so
763 : * non-removing enumerations can count on stable table->entryStore until
764 : * the next non-lookup-Operate or removing-Enumerate.
765 : */
766 1329799 : if (didRemove &&
767 : (table->removedCount >= capacity >> 2 ||
768 : (capacity > JS_DHASH_MIN_SIZE &&
769 : table->entryCount <= MIN_LOAD(table, capacity)))) {
770 : METER(table->stats.enumShrinks++);
771 14630 : capacity = table->entryCount;
772 14630 : capacity += capacity >> 1;
773 14630 : if (capacity < JS_DHASH_MIN_SIZE)
774 11300 : capacity = JS_DHASH_MIN_SIZE;
775 :
776 14630 : JS_CEILING_LOG2(ceiling, capacity);
777 14630 : ceiling -= JS_DHASH_BITS - table->hashShift;
778 :
779 14630 : (void) ChangeTable(table, ceiling);
780 : }
781 :
782 1329799 : DECREMENT_RECURSION_LEVEL(table);
783 :
784 1329799 : return i;
785 : }
786 :
787 : struct SizeOfEntryExcludingThisArg
788 : {
789 : size_t total;
790 : JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis;
791 : JSMallocSizeOfFun mallocSizeOf;
792 : void *arg; // the arg passed by the user
793 : };
794 :
795 : static JSDHashOperator
796 3002 : SizeOfEntryExcludingThisEnumerator(JSDHashTable *table, JSDHashEntryHdr *hdr,
797 : uint32_t number, void *arg)
798 : {
799 3002 : SizeOfEntryExcludingThisArg *e = (SizeOfEntryExcludingThisArg *)arg;
800 3002 : e->total += e->sizeOfEntryExcludingThis(hdr, e->mallocSizeOf, e->arg);
801 3002 : return JS_DHASH_NEXT;
802 : }
803 :
804 : extern JS_PUBLIC_API(size_t)
805 192 : JS_DHashTableSizeOfExcludingThis(const JSDHashTable *table,
806 : JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
807 : JSMallocSizeOfFun mallocSizeOf,
808 : void *arg /* = NULL */)
809 : {
810 192 : size_t n = 0;
811 192 : n += mallocSizeOf(table->entryStore);
812 192 : if (sizeOfEntryExcludingThis) {
813 186 : SizeOfEntryExcludingThisArg arg2 = { 0, sizeOfEntryExcludingThis, mallocSizeOf, arg };
814 : JS_DHashTableEnumerate(const_cast<JSDHashTable *>(table),
815 186 : SizeOfEntryExcludingThisEnumerator, &arg2);
816 186 : n += arg2.total;
817 : }
818 192 : return n;
819 : }
820 :
821 : extern JS_PUBLIC_API(size_t)
822 189 : JS_DHashTableSizeOfIncludingThis(const JSDHashTable *table,
823 : JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
824 : JSMallocSizeOfFun mallocSizeOf,
825 : void *arg /* = NULL */)
826 : {
827 189 : return mallocSizeOf(table) +
828 : JS_DHashTableSizeOfExcludingThis(table, sizeOfEntryExcludingThis,
829 189 : mallocSizeOf, arg);
830 : }
831 :
832 : #ifdef DEBUG
833 : JS_PUBLIC_API(void)
834 0 : JS_DHashMarkTableImmutable(JSDHashTable *table)
835 : {
836 0 : RECURSION_LEVEL(table) = IMMUTABLE_RECURSION_LEVEL;
837 0 : }
838 : #endif
839 :
840 : #ifdef JS_DHASHMETER
841 : #include <math.h>
842 :
843 : JS_PUBLIC_API(void)
844 : JS_DHashTableDumpMeter(JSDHashTable *table, JSDHashEnumerator dump, FILE *fp)
845 : {
846 : char *entryAddr;
847 : uint32_t entrySize, entryCount;
848 : int hashShift, sizeLog2;
849 : uint32_t i, tableSize, sizeMask, chainLen, maxChainLen, chainCount;
850 : JSDHashNumber hash1, hash2, saveHash1, maxChainHash1, maxChainHash2;
851 : double sqsum, mean, variance, sigma;
852 : JSDHashEntryHdr *entry, *probe;
853 :
854 : entryAddr = table->entryStore;
855 : entrySize = table->entrySize;
856 : hashShift = table->hashShift;
857 : sizeLog2 = JS_DHASH_BITS - hashShift;
858 : tableSize = JS_DHASH_TABLE_SIZE(table);
859 : sizeMask = JS_BITMASK(sizeLog2);
860 : chainCount = maxChainLen = 0;
861 : hash2 = 0;
862 : sqsum = 0;
863 :
864 : for (i = 0; i < tableSize; i++) {
865 : entry = (JSDHashEntryHdr *)entryAddr;
866 : entryAddr += entrySize;
867 : if (!ENTRY_IS_LIVE(entry))
868 : continue;
869 : hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift);
870 : saveHash1 = hash1;
871 : probe = ADDRESS_ENTRY(table, hash1);
872 : chainLen = 1;
873 : if (probe == entry) {
874 : /* Start of a (possibly unit-length) chain. */
875 : chainCount++;
876 : } else {
877 : hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2,
878 : hashShift);
879 : do {
880 : chainLen++;
881 : hash1 -= hash2;
882 : hash1 &= sizeMask;
883 : probe = ADDRESS_ENTRY(table, hash1);
884 : } while (probe != entry);
885 : }
886 : sqsum += chainLen * chainLen;
887 : if (chainLen > maxChainLen) {
888 : maxChainLen = chainLen;
889 : maxChainHash1 = saveHash1;
890 : maxChainHash2 = hash2;
891 : }
892 : }
893 :
894 : entryCount = table->entryCount;
895 : if (entryCount && chainCount) {
896 : mean = (double)entryCount / chainCount;
897 : variance = chainCount * sqsum - entryCount * entryCount;
898 : if (variance < 0 || chainCount == 1)
899 : variance = 0;
900 : else
901 : variance /= chainCount * (chainCount - 1);
902 : sigma = sqrt(variance);
903 : } else {
904 : mean = sigma = 0;
905 : }
906 :
907 : fprintf(fp, "Double hashing statistics:\n");
908 : fprintf(fp, " table size (in entries): %u\n", tableSize);
909 : fprintf(fp, " number of entries: %u\n", table->entryCount);
910 : fprintf(fp, " number of removed entries: %u\n", table->removedCount);
911 : fprintf(fp, " number of searches: %u\n", table->stats.searches);
912 : fprintf(fp, " number of hits: %u\n", table->stats.hits);
913 : fprintf(fp, " number of misses: %u\n", table->stats.misses);
914 : fprintf(fp, " mean steps per search: %g\n", table->stats.searches ?
915 : (double)table->stats.steps
916 : / table->stats.searches :
917 : 0.);
918 : fprintf(fp, " mean hash chain length: %g\n", mean);
919 : fprintf(fp, " standard deviation: %g\n", sigma);
920 : fprintf(fp, " maximum hash chain length: %u\n", maxChainLen);
921 : fprintf(fp, " number of lookups: %u\n", table->stats.lookups);
922 : fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses);
923 : fprintf(fp, "adds that recycled removeds: %u\n", table->stats.addOverRemoved);
924 : fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits);
925 : fprintf(fp, " add failures: %u\n", table->stats.addFailures);
926 : fprintf(fp, " useful removes: %u\n", table->stats.removeHits);
927 : fprintf(fp, " useless removes: %u\n", table->stats.removeMisses);
928 : fprintf(fp, "removes that freed an entry: %u\n", table->stats.removeFrees);
929 : fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums);
930 : fprintf(fp, " number of grows: %u\n", table->stats.grows);
931 : fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks);
932 : fprintf(fp, " number of compresses: %u\n", table->stats.compresses);
933 : fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks);
934 :
935 : if (dump && maxChainLen && hash2) {
936 : fputs("Maximum hash chain:\n", fp);
937 : hash1 = maxChainHash1;
938 : hash2 = maxChainHash2;
939 : entry = ADDRESS_ENTRY(table, hash1);
940 : i = 0;
941 : do {
942 : if (dump(table, entry, i++, fp) != JS_DHASH_NEXT)
943 : break;
944 : hash1 -= hash2;
945 : hash1 &= sizeMask;
946 : entry = ADDRESS_ENTRY(table, hash1);
947 : } while (JS_DHASH_ENTRY_IS_BUSY(entry));
948 : }
949 : }
950 : #endif /* JS_DHASHMETER */
|