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