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