|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 * PR hash table package. |
|
9 */ |
|
10 |
|
11 #include "jshash.h" |
|
12 |
|
13 #include "mozilla/MathAlgorithms.h" |
|
14 |
|
15 #include <stdlib.h> |
|
16 #include <string.h> |
|
17 |
|
18 #include "jstypes.h" |
|
19 |
|
20 #include "js/Utility.h" |
|
21 |
|
22 using namespace js; |
|
23 |
|
24 using mozilla::CeilingLog2Size; |
|
25 using mozilla::RotateLeft; |
|
26 |
|
27 /* Compute the number of buckets in ht */ |
|
28 #define NBUCKETS(ht) JS_BIT(JS_HASH_BITS - (ht)->shift) |
|
29 |
|
30 /* The smallest table has 16 buckets */ |
|
31 #define MINBUCKETSLOG2 4 |
|
32 #define MINBUCKETS JS_BIT(MINBUCKETSLOG2) |
|
33 |
|
34 /* Compute the maximum entries given n buckets that we will tolerate, ~90% */ |
|
35 #define OVERLOADED(n) ((n) - ((n) >> 3)) |
|
36 |
|
37 /* Compute the number of entries below which we shrink the table by half */ |
|
38 #define UNDERLOADED(n) (((n) > MINBUCKETS) ? ((n) >> 2) : 0) |
|
39 |
|
40 /* |
|
41 ** Stubs for default hash allocator ops. |
|
42 */ |
|
43 static void * |
|
44 DefaultAllocTable(void *pool, size_t size) |
|
45 { |
|
46 return js_malloc(size); |
|
47 } |
|
48 |
|
49 static void |
|
50 DefaultFreeTable(void *pool, void *item, size_t size) |
|
51 { |
|
52 js_free(item); |
|
53 } |
|
54 |
|
55 static JSHashEntry * |
|
56 DefaultAllocEntry(void *pool, const void *key) |
|
57 { |
|
58 return (JSHashEntry*) js_malloc(sizeof(JSHashEntry)); |
|
59 } |
|
60 |
|
61 static void |
|
62 DefaultFreeEntry(void *pool, JSHashEntry *he, unsigned flag) |
|
63 { |
|
64 if (flag == HT_FREE_ENTRY) |
|
65 js_free(he); |
|
66 } |
|
67 |
|
68 static const JSHashAllocOps defaultHashAllocOps = { |
|
69 DefaultAllocTable, DefaultFreeTable, |
|
70 DefaultAllocEntry, DefaultFreeEntry |
|
71 }; |
|
72 |
|
73 JSHashTable * |
|
74 JS_NewHashTable(uint32_t n, JSHashFunction keyHash, |
|
75 JSHashComparator keyCompare, JSHashComparator valueCompare, |
|
76 const JSHashAllocOps *allocOps, void *allocPriv) |
|
77 { |
|
78 JSHashTable *ht; |
|
79 size_t nb; |
|
80 |
|
81 if (n <= MINBUCKETS) { |
|
82 n = MINBUCKETSLOG2; |
|
83 } else { |
|
84 n = CeilingLog2Size(n); |
|
85 if (int32_t(n) < 0) |
|
86 return nullptr; |
|
87 } |
|
88 |
|
89 if (!allocOps) allocOps = &defaultHashAllocOps; |
|
90 |
|
91 ht = (JSHashTable*) allocOps->allocTable(allocPriv, sizeof *ht); |
|
92 if (!ht) |
|
93 return nullptr; |
|
94 memset(ht, 0, sizeof *ht); |
|
95 ht->shift = JS_HASH_BITS - n; |
|
96 n = JS_BIT(n); |
|
97 nb = n * sizeof(JSHashEntry *); |
|
98 ht->buckets = (JSHashEntry**) allocOps->allocTable(allocPriv, nb); |
|
99 if (!ht->buckets) { |
|
100 allocOps->freeTable(allocPriv, ht, nb); |
|
101 return nullptr; |
|
102 } |
|
103 memset(ht->buckets, 0, nb); |
|
104 |
|
105 ht->keyHash = keyHash; |
|
106 ht->keyCompare = keyCompare; |
|
107 ht->valueCompare = valueCompare; |
|
108 ht->allocOps = allocOps; |
|
109 ht->allocPriv = allocPriv; |
|
110 return ht; |
|
111 } |
|
112 |
|
113 void |
|
114 JS_HashTableDestroy(JSHashTable *ht) |
|
115 { |
|
116 uint32_t i, n; |
|
117 JSHashEntry *he, **hep; |
|
118 const JSHashAllocOps *allocOps = ht->allocOps; |
|
119 void *allocPriv = ht->allocPriv; |
|
120 |
|
121 n = NBUCKETS(ht); |
|
122 for (i = 0; i < n; i++) { |
|
123 hep = &ht->buckets[i]; |
|
124 while ((he = *hep) != nullptr) { |
|
125 *hep = he->next; |
|
126 allocOps->freeEntry(allocPriv, he, HT_FREE_ENTRY); |
|
127 } |
|
128 } |
|
129 #ifdef DEBUG |
|
130 memset(ht->buckets, 0xDB, n * sizeof ht->buckets[0]); |
|
131 #endif |
|
132 allocOps->freeTable(allocPriv, ht->buckets, n * sizeof ht->buckets[0]); |
|
133 #ifdef DEBUG |
|
134 memset(ht, 0xDB, sizeof *ht); |
|
135 #endif |
|
136 allocOps->freeTable(allocPriv, ht, sizeof *ht); |
|
137 } |
|
138 |
|
139 /* |
|
140 * Multiplicative hash, from Knuth 6.4. |
|
141 */ |
|
142 #define BUCKET_HEAD(ht, keyHash) \ |
|
143 (&(ht)->buckets[((keyHash) * JS_GOLDEN_RATIO) >> (ht)->shift]) |
|
144 |
|
145 JSHashEntry ** |
|
146 JS_HashTableRawLookup(JSHashTable *ht, JSHashNumber keyHash, const void *key) |
|
147 { |
|
148 JSHashEntry *he, **hep, **hep0; |
|
149 |
|
150 #ifdef JS_HASHMETER |
|
151 ht->nlookups++; |
|
152 #endif |
|
153 hep = hep0 = BUCKET_HEAD(ht, keyHash); |
|
154 while ((he = *hep) != nullptr) { |
|
155 if (he->keyHash == keyHash && ht->keyCompare(key, he->key)) { |
|
156 /* Move to front of chain if not already there */ |
|
157 if (hep != hep0) { |
|
158 *hep = he->next; |
|
159 he->next = *hep0; |
|
160 *hep0 = he; |
|
161 } |
|
162 return hep0; |
|
163 } |
|
164 hep = &he->next; |
|
165 #ifdef JS_HASHMETER |
|
166 ht->nsteps++; |
|
167 #endif |
|
168 } |
|
169 return hep; |
|
170 } |
|
171 |
|
172 static bool |
|
173 Resize(JSHashTable *ht, uint32_t newshift) |
|
174 { |
|
175 size_t nb, nentries, i; |
|
176 JSHashEntry **oldbuckets, *he, *next, **hep; |
|
177 size_t nold = NBUCKETS(ht); |
|
178 |
|
179 MOZ_ASSERT(newshift < JS_HASH_BITS); |
|
180 |
|
181 nb = (size_t)1 << (JS_HASH_BITS - newshift); |
|
182 |
|
183 /* Integer overflow protection. */ |
|
184 if (nb > (size_t)-1 / sizeof(JSHashEntry*)) |
|
185 return false; |
|
186 nb *= sizeof(JSHashEntry*); |
|
187 |
|
188 oldbuckets = ht->buckets; |
|
189 ht->buckets = (JSHashEntry**)ht->allocOps->allocTable(ht->allocPriv, nb); |
|
190 if (!ht->buckets) { |
|
191 ht->buckets = oldbuckets; |
|
192 return false; |
|
193 } |
|
194 memset(ht->buckets, 0, nb); |
|
195 |
|
196 ht->shift = newshift; |
|
197 nentries = ht->nentries; |
|
198 |
|
199 for (i = 0; nentries != 0; i++) { |
|
200 for (he = oldbuckets[i]; he; he = next) { |
|
201 MOZ_ASSERT(nentries != 0); |
|
202 --nentries; |
|
203 next = he->next; |
|
204 hep = BUCKET_HEAD(ht, he->keyHash); |
|
205 |
|
206 /* |
|
207 * We do not require unique entries, instead appending he to the |
|
208 * chain starting at hep. |
|
209 */ |
|
210 while (*hep) |
|
211 hep = &(*hep)->next; |
|
212 he->next = nullptr; |
|
213 *hep = he; |
|
214 } |
|
215 } |
|
216 #ifdef DEBUG |
|
217 memset(oldbuckets, 0xDB, nold * sizeof oldbuckets[0]); |
|
218 #endif |
|
219 ht->allocOps->freeTable(ht->allocPriv, oldbuckets, |
|
220 nold * sizeof oldbuckets[0]); |
|
221 return true; |
|
222 } |
|
223 |
|
224 JSHashEntry * |
|
225 JS_HashTableRawAdd(JSHashTable *ht, JSHashEntry **&hep, |
|
226 JSHashNumber keyHash, const void *key, void *value) |
|
227 { |
|
228 uint32_t n; |
|
229 JSHashEntry *he; |
|
230 |
|
231 /* Grow the table if it is overloaded */ |
|
232 n = NBUCKETS(ht); |
|
233 if (ht->nentries >= OVERLOADED(n)) { |
|
234 if (!Resize(ht, ht->shift - 1)) |
|
235 return nullptr; |
|
236 #ifdef JS_HASHMETER |
|
237 ht->ngrows++; |
|
238 #endif |
|
239 hep = JS_HashTableRawLookup(ht, keyHash, key); |
|
240 } |
|
241 |
|
242 /* Make a new key value entry */ |
|
243 he = ht->allocOps->allocEntry(ht->allocPriv, key); |
|
244 if (!he) |
|
245 return nullptr; |
|
246 he->keyHash = keyHash; |
|
247 he->key = key; |
|
248 he->value = value; |
|
249 he->next = *hep; |
|
250 *hep = he; |
|
251 ht->nentries++; |
|
252 return he; |
|
253 } |
|
254 |
|
255 JSHashEntry * |
|
256 JS_HashTableAdd(JSHashTable *ht, const void *key, void *value) |
|
257 { |
|
258 JSHashNumber keyHash; |
|
259 JSHashEntry *he, **hep; |
|
260 |
|
261 keyHash = ht->keyHash(key); |
|
262 hep = JS_HashTableRawLookup(ht, keyHash, key); |
|
263 if ((he = *hep) != nullptr) { |
|
264 /* Hit; see if values match */ |
|
265 if (ht->valueCompare(he->value, value)) { |
|
266 /* key,value pair is already present in table */ |
|
267 return he; |
|
268 } |
|
269 if (he->value) |
|
270 ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_VALUE); |
|
271 he->value = value; |
|
272 return he; |
|
273 } |
|
274 return JS_HashTableRawAdd(ht, hep, keyHash, key, value); |
|
275 } |
|
276 |
|
277 void |
|
278 JS_HashTableRawRemove(JSHashTable *ht, JSHashEntry **hep, JSHashEntry *he) |
|
279 { |
|
280 uint32_t n; |
|
281 |
|
282 *hep = he->next; |
|
283 ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_ENTRY); |
|
284 |
|
285 /* Shrink table if it's underloaded */ |
|
286 n = NBUCKETS(ht); |
|
287 if (--ht->nentries < UNDERLOADED(n)) { |
|
288 Resize(ht, ht->shift + 1); |
|
289 #ifdef JS_HASHMETER |
|
290 ht->nshrinks++; |
|
291 #endif |
|
292 } |
|
293 } |
|
294 |
|
295 bool |
|
296 JS_HashTableRemove(JSHashTable *ht, const void *key) |
|
297 { |
|
298 JSHashNumber keyHash; |
|
299 JSHashEntry *he, **hep; |
|
300 |
|
301 keyHash = ht->keyHash(key); |
|
302 hep = JS_HashTableRawLookup(ht, keyHash, key); |
|
303 if ((he = *hep) == nullptr) |
|
304 return false; |
|
305 |
|
306 /* Hit; remove element */ |
|
307 JS_HashTableRawRemove(ht, hep, he); |
|
308 return true; |
|
309 } |
|
310 |
|
311 void * |
|
312 JS_HashTableLookup(JSHashTable *ht, const void *key) |
|
313 { |
|
314 JSHashNumber keyHash; |
|
315 JSHashEntry *he, **hep; |
|
316 |
|
317 keyHash = ht->keyHash(key); |
|
318 hep = JS_HashTableRawLookup(ht, keyHash, key); |
|
319 if ((he = *hep) != nullptr) { |
|
320 return he->value; |
|
321 } |
|
322 return nullptr; |
|
323 } |
|
324 |
|
325 /* |
|
326 ** Iterate over the entries in the hash table calling func for each |
|
327 ** entry found. Stop if "f" says to (return value & JS_ENUMERATE_STOP). |
|
328 ** Return a count of the number of elements scanned. |
|
329 */ |
|
330 int |
|
331 JS_HashTableEnumerateEntries(JSHashTable *ht, JSHashEnumerator f, void *arg) |
|
332 { |
|
333 JSHashEntry *he, **hep, **bucket; |
|
334 uint32_t nlimit, n, nbuckets, newlog2; |
|
335 int rv; |
|
336 |
|
337 nlimit = ht->nentries; |
|
338 n = 0; |
|
339 for (bucket = ht->buckets; n != nlimit; ++bucket) { |
|
340 hep = bucket; |
|
341 while ((he = *hep) != nullptr) { |
|
342 MOZ_ASSERT(n < nlimit); |
|
343 rv = f(he, n, arg); |
|
344 n++; |
|
345 if (rv & HT_ENUMERATE_REMOVE) { |
|
346 *hep = he->next; |
|
347 ht->allocOps->freeEntry(ht->allocPriv, he, HT_FREE_ENTRY); |
|
348 --ht->nentries; |
|
349 } else { |
|
350 hep = &he->next; |
|
351 } |
|
352 if (rv & HT_ENUMERATE_STOP) { |
|
353 goto out; |
|
354 } |
|
355 } |
|
356 } |
|
357 |
|
358 out: |
|
359 /* Shrink table if removal of entries made it underloaded */ |
|
360 if (ht->nentries != nlimit) { |
|
361 MOZ_ASSERT(ht->nentries < nlimit); |
|
362 nbuckets = NBUCKETS(ht); |
|
363 if (MINBUCKETS < nbuckets && ht->nentries < UNDERLOADED(nbuckets)) { |
|
364 newlog2 = CeilingLog2Size(ht->nentries); |
|
365 if (newlog2 < MINBUCKETSLOG2) |
|
366 newlog2 = MINBUCKETSLOG2; |
|
367 |
|
368 /* Check that we really shrink the table. */ |
|
369 MOZ_ASSERT(JS_HASH_BITS - ht->shift > newlog2); |
|
370 Resize(ht, JS_HASH_BITS - newlog2); |
|
371 } |
|
372 } |
|
373 return (int)n; |
|
374 } |
|
375 |
|
376 #ifdef JS_HASHMETER |
|
377 #include <stdio.h> |
|
378 |
|
379 void |
|
380 JS_HashTableDumpMeter(JSHashTable *ht, JSHashEnumerator dump, FILE *fp) |
|
381 { |
|
382 double sqsum, mean, sigma; |
|
383 uint32_t nchains, nbuckets; |
|
384 uint32_t i, n, maxChain, maxChainLen; |
|
385 JSHashEntry *he; |
|
386 |
|
387 sqsum = 0; |
|
388 nchains = 0; |
|
389 maxChain = maxChainLen = 0; |
|
390 nbuckets = NBUCKETS(ht); |
|
391 for (i = 0; i < nbuckets; i++) { |
|
392 he = ht->buckets[i]; |
|
393 if (!he) |
|
394 continue; |
|
395 nchains++; |
|
396 for (n = 0; he; he = he->next) |
|
397 n++; |
|
398 sqsum += n * n; |
|
399 if (n > maxChainLen) { |
|
400 maxChainLen = n; |
|
401 maxChain = i; |
|
402 } |
|
403 } |
|
404 |
|
405 mean = JS_MeanAndStdDev(nchains, ht->nentries, sqsum, &sigma); |
|
406 |
|
407 fprintf(fp, "\nHash table statistics:\n"); |
|
408 fprintf(fp, " number of lookups: %u\n", ht->nlookups); |
|
409 fprintf(fp, " number of entries: %u\n", ht->nentries); |
|
410 fprintf(fp, " number of grows: %u\n", ht->ngrows); |
|
411 fprintf(fp, " number of shrinks: %u\n", ht->nshrinks); |
|
412 fprintf(fp, " mean steps per hash: %g\n", (double)ht->nsteps |
|
413 / ht->nlookups); |
|
414 fprintf(fp, "mean hash chain length: %g\n", mean); |
|
415 fprintf(fp, " standard deviation: %g\n", sigma); |
|
416 fprintf(fp, " max hash chain length: %u\n", maxChainLen); |
|
417 fprintf(fp, " max hash chain: [%u]\n", maxChain); |
|
418 |
|
419 for (he = ht->buckets[maxChain], i = 0; he; he = he->next, i++) |
|
420 if (dump(he, i, fp) != HT_ENUMERATE_NEXT) |
|
421 break; |
|
422 } |
|
423 #endif /* JS_HASHMETER */ |
|
424 |
|
425 int |
|
426 JS_HashTableDump(JSHashTable *ht, JSHashEnumerator dump, FILE *fp) |
|
427 { |
|
428 int count; |
|
429 |
|
430 count = JS_HashTableEnumerateEntries(ht, dump, fp); |
|
431 #ifdef JS_HASHMETER |
|
432 JS_HashTableDumpMeter(ht, dump, fp); |
|
433 #endif |
|
434 return count; |
|
435 } |
|
436 |
|
437 JSHashNumber |
|
438 JS_HashString(const void *key) |
|
439 { |
|
440 JSHashNumber h; |
|
441 const unsigned char *s; |
|
442 |
|
443 h = 0; |
|
444 for (s = (const unsigned char *)key; *s; s++) |
|
445 h = RotateLeft(h, 4) ^ *s; |
|
446 return h; |
|
447 } |
|
448 |
|
449 int |
|
450 JS_CompareValues(const void *v1, const void *v2) |
|
451 { |
|
452 return v1 == v2; |
|
453 } |