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