xpcom/glue/pldhash.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 /*
michael@0 7 * Double hashing implementation.
michael@0 8 */
michael@0 9 #include <stdio.h>
michael@0 10 #include <stdlib.h>
michael@0 11 #include <string.h>
michael@0 12 #include "pldhash.h"
michael@0 13 #include "mozilla/HashFunctions.h"
michael@0 14 #include "mozilla/MathAlgorithms.h"
michael@0 15 #include "nsDebug.h" /* for PR_ASSERT */
michael@0 16 #include "nsAlgorithm.h"
michael@0 17 #include "mozilla/Likely.h"
michael@0 18 #include "mozilla/MemoryReporting.h"
michael@0 19 #include "mozilla/ChaosMode.h"
michael@0 20
michael@0 21 #ifdef PL_DHASHMETER
michael@0 22 # define METER(x) x
michael@0 23 #else
michael@0 24 # define METER(x) /* nothing */
michael@0 25 #endif
michael@0 26
michael@0 27 /*
michael@0 28 * The following DEBUG-only code is used to assert that calls to one of
michael@0 29 * table->ops or to an enumerator do not cause re-entry into a call that
michael@0 30 * can mutate the table.
michael@0 31 */
michael@0 32 #ifdef DEBUG
michael@0 33
michael@0 34 /*
michael@0 35 * Most callers that assert about the recursion level don't care about
michael@0 36 * this magical value because they are asserting that mutation is
michael@0 37 * allowed (and therefore the level is 0 or 1, depending on whether they
michael@0 38 * incremented it).
michael@0 39 *
michael@0 40 * Only PL_DHashTableFinish needs to allow this special value.
michael@0 41 */
michael@0 42 #define IMMUTABLE_RECURSION_LEVEL ((uint16_t)-1)
michael@0 43
michael@0 44 #define RECURSION_LEVEL_SAFE_TO_FINISH(table_) \
michael@0 45 (table_->recursionLevel == 0 || \
michael@0 46 table_->recursionLevel == IMMUTABLE_RECURSION_LEVEL)
michael@0 47
michael@0 48 #define INCREMENT_RECURSION_LEVEL(table_) \
michael@0 49 do { \
michael@0 50 if (table_->recursionLevel != IMMUTABLE_RECURSION_LEVEL) \
michael@0 51 ++table_->recursionLevel; \
michael@0 52 } while(0)
michael@0 53 #define DECREMENT_RECURSION_LEVEL(table_) \
michael@0 54 do { \
michael@0 55 if (table->recursionLevel != IMMUTABLE_RECURSION_LEVEL) { \
michael@0 56 MOZ_ASSERT(table->recursionLevel > 0); \
michael@0 57 --table->recursionLevel; \
michael@0 58 } \
michael@0 59 } while(0)
michael@0 60
michael@0 61 #else
michael@0 62
michael@0 63 #define INCREMENT_RECURSION_LEVEL(table_) do { } while(0)
michael@0 64 #define DECREMENT_RECURSION_LEVEL(table_) do { } while(0)
michael@0 65
michael@0 66 #endif /* defined(DEBUG) */
michael@0 67
michael@0 68 using namespace mozilla;
michael@0 69
michael@0 70 void *
michael@0 71 PL_DHashAllocTable(PLDHashTable *table, uint32_t nbytes)
michael@0 72 {
michael@0 73 return malloc(nbytes);
michael@0 74 }
michael@0 75
michael@0 76 void
michael@0 77 PL_DHashFreeTable(PLDHashTable *table, void *ptr)
michael@0 78 {
michael@0 79 free(ptr);
michael@0 80 }
michael@0 81
michael@0 82 PLDHashNumber
michael@0 83 PL_DHashStringKey(PLDHashTable *table, const void *key)
michael@0 84 {
michael@0 85 return HashString(static_cast<const char*>(key));
michael@0 86 }
michael@0 87
michael@0 88 PLDHashNumber
michael@0 89 PL_DHashVoidPtrKeyStub(PLDHashTable *table, const void *key)
michael@0 90 {
michael@0 91 return (PLDHashNumber)(ptrdiff_t)key >> 2;
michael@0 92 }
michael@0 93
michael@0 94 bool
michael@0 95 PL_DHashMatchEntryStub(PLDHashTable *table,
michael@0 96 const PLDHashEntryHdr *entry,
michael@0 97 const void *key)
michael@0 98 {
michael@0 99 const PLDHashEntryStub *stub = (const PLDHashEntryStub *)entry;
michael@0 100
michael@0 101 return stub->key == key;
michael@0 102 }
michael@0 103
michael@0 104 bool
michael@0 105 PL_DHashMatchStringKey(PLDHashTable *table,
michael@0 106 const PLDHashEntryHdr *entry,
michael@0 107 const void *key)
michael@0 108 {
michael@0 109 const PLDHashEntryStub *stub = (const PLDHashEntryStub *)entry;
michael@0 110
michael@0 111 /* XXX tolerate null keys on account of sloppy Mozilla callers. */
michael@0 112 return stub->key == key ||
michael@0 113 (stub->key && key &&
michael@0 114 strcmp((const char *) stub->key, (const char *) key) == 0);
michael@0 115 }
michael@0 116
michael@0 117 void
michael@0 118 PL_DHashMoveEntryStub(PLDHashTable *table,
michael@0 119 const PLDHashEntryHdr *from,
michael@0 120 PLDHashEntryHdr *to)
michael@0 121 {
michael@0 122 memcpy(to, from, table->entrySize);
michael@0 123 }
michael@0 124
michael@0 125 void
michael@0 126 PL_DHashClearEntryStub(PLDHashTable *table, PLDHashEntryHdr *entry)
michael@0 127 {
michael@0 128 memset(entry, 0, table->entrySize);
michael@0 129 }
michael@0 130
michael@0 131 void
michael@0 132 PL_DHashFreeStringKey(PLDHashTable *table, PLDHashEntryHdr *entry)
michael@0 133 {
michael@0 134 const PLDHashEntryStub *stub = (const PLDHashEntryStub *)entry;
michael@0 135
michael@0 136 free((void *) stub->key);
michael@0 137 memset(entry, 0, table->entrySize);
michael@0 138 }
michael@0 139
michael@0 140 void
michael@0 141 PL_DHashFinalizeStub(PLDHashTable *table)
michael@0 142 {
michael@0 143 }
michael@0 144
michael@0 145 static const PLDHashTableOps stub_ops = {
michael@0 146 PL_DHashAllocTable,
michael@0 147 PL_DHashFreeTable,
michael@0 148 PL_DHashVoidPtrKeyStub,
michael@0 149 PL_DHashMatchEntryStub,
michael@0 150 PL_DHashMoveEntryStub,
michael@0 151 PL_DHashClearEntryStub,
michael@0 152 PL_DHashFinalizeStub,
michael@0 153 nullptr
michael@0 154 };
michael@0 155
michael@0 156 const PLDHashTableOps *
michael@0 157 PL_DHashGetStubOps(void)
michael@0 158 {
michael@0 159 return &stub_ops;
michael@0 160 }
michael@0 161
michael@0 162 static bool
michael@0 163 SizeOfEntryStore(uint32_t capacity, uint32_t entrySize, uint32_t *nbytes)
michael@0 164 {
michael@0 165 uint64_t nbytes64 = uint64_t(capacity) * uint64_t(entrySize);
michael@0 166 *nbytes = capacity * entrySize;
michael@0 167 return uint64_t(*nbytes) == nbytes64; // returns false on overflow
michael@0 168 }
michael@0 169
michael@0 170 PLDHashTable *
michael@0 171 PL_NewDHashTable(const PLDHashTableOps *ops, void *data, uint32_t entrySize,
michael@0 172 uint32_t capacity)
michael@0 173 {
michael@0 174 PLDHashTable *table = (PLDHashTable *) malloc(sizeof *table);
michael@0 175 if (!table)
michael@0 176 return nullptr;
michael@0 177 if (!PL_DHashTableInit(table, ops, data, entrySize, capacity, fallible_t())) {
michael@0 178 free(table);
michael@0 179 return nullptr;
michael@0 180 }
michael@0 181 return table;
michael@0 182 }
michael@0 183
michael@0 184 void
michael@0 185 PL_DHashTableDestroy(PLDHashTable *table)
michael@0 186 {
michael@0 187 PL_DHashTableFinish(table);
michael@0 188 free(table);
michael@0 189 }
michael@0 190
michael@0 191 bool
michael@0 192 PL_DHashTableInit(PLDHashTable *table, const PLDHashTableOps *ops,
michael@0 193 void *data, uint32_t entrySize, uint32_t capacity,
michael@0 194 const fallible_t& )
michael@0 195 {
michael@0 196 #ifdef DEBUG
michael@0 197 if (entrySize > 16 * sizeof(void *)) {
michael@0 198 printf_stderr(
michael@0 199 "pldhash: for the table at address %p, the given entrySize"
michael@0 200 " of %lu definitely favors chaining over double hashing.\n",
michael@0 201 (void *) table,
michael@0 202 (unsigned long) entrySize);
michael@0 203 }
michael@0 204 #endif
michael@0 205
michael@0 206 table->ops = ops;
michael@0 207 table->data = data;
michael@0 208 if (capacity < PL_DHASH_MIN_SIZE)
michael@0 209 capacity = PL_DHASH_MIN_SIZE;
michael@0 210
michael@0 211 int log2 = CeilingLog2(capacity);
michael@0 212
michael@0 213 capacity = 1u << log2;
michael@0 214 if (capacity > PL_DHASH_MAX_SIZE)
michael@0 215 return false;
michael@0 216 table->hashShift = PL_DHASH_BITS - log2;
michael@0 217 table->entrySize = entrySize;
michael@0 218 table->entryCount = table->removedCount = 0;
michael@0 219 table->generation = 0;
michael@0 220 uint32_t nbytes;
michael@0 221 if (!SizeOfEntryStore(capacity, entrySize, &nbytes))
michael@0 222 return false; // overflowed
michael@0 223
michael@0 224 table->entryStore = (char *) ops->allocTable(table, nbytes);
michael@0 225 if (!table->entryStore)
michael@0 226 return false;
michael@0 227 memset(table->entryStore, 0, nbytes);
michael@0 228 METER(memset(&table->stats, 0, sizeof table->stats));
michael@0 229
michael@0 230 #ifdef DEBUG
michael@0 231 table->recursionLevel = 0;
michael@0 232 #endif
michael@0 233
michael@0 234 return true;
michael@0 235 }
michael@0 236
michael@0 237 void
michael@0 238 PL_DHashTableInit(PLDHashTable *table, const PLDHashTableOps *ops, void *data,
michael@0 239 uint32_t entrySize, uint32_t capacity)
michael@0 240 {
michael@0 241 if (!PL_DHashTableInit(table, ops, data, entrySize, capacity, fallible_t())) {
michael@0 242 if (capacity > PL_DHASH_MAX_SIZE) {
michael@0 243 MOZ_CRASH();
michael@0 244 }
michael@0 245 uint32_t nbytes;
michael@0 246 if (!SizeOfEntryStore(capacity, entrySize, &nbytes)) {
michael@0 247 MOZ_CRASH();
michael@0 248 }
michael@0 249 NS_ABORT_OOM(nbytes);
michael@0 250 }
michael@0 251 }
michael@0 252
michael@0 253 /*
michael@0 254 * Compute max and min load numbers (entry counts). We have a secondary max
michael@0 255 * that allows us to overload a table reasonably if it cannot be grown further
michael@0 256 * (i.e. if ChangeTable() fails). The table slows down drastically if the
michael@0 257 * secondary max is too close to 1, but 0.96875 gives only a slight slowdown
michael@0 258 * while allowing 1.3x more elements.
michael@0 259 */
michael@0 260 static inline uint32_t MaxLoad(uint32_t size) {
michael@0 261 return size - (size >> 2); // == size * 0.75
michael@0 262 }
michael@0 263 static inline uint32_t MaxLoadOnGrowthFailure(uint32_t size) {
michael@0 264 return size - (size >> 5); // == size * 0.96875
michael@0 265 }
michael@0 266 static inline uint32_t MinLoad(uint32_t size) {
michael@0 267 return size >> 2; // == size * 0.25
michael@0 268 }
michael@0 269
michael@0 270 /*
michael@0 271 * Double hashing needs the second hash code to be relatively prime to table
michael@0 272 * size, so we simply make hash2 odd.
michael@0 273 */
michael@0 274 #define HASH1(hash0, shift) ((hash0) >> (shift))
michael@0 275 #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
michael@0 276
michael@0 277 /*
michael@0 278 * Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. Note
michael@0 279 * that a removed-entry sentinel need be stored only if the removed entry had
michael@0 280 * a colliding entry added after it. Therefore we can use 1 as the collision
michael@0 281 * flag in addition to the removed-entry sentinel value. Multiplicative hash
michael@0 282 * uses the high order bits of keyHash, so this least-significant reservation
michael@0 283 * should not hurt the hash function's effectiveness much.
michael@0 284 *
michael@0 285 * If you change any of these magic numbers, also update PL_DHASH_ENTRY_IS_LIVE
michael@0 286 * in pldhash.h. It used to be private to pldhash.c, but then became public to
michael@0 287 * assist iterator writers who inspect table->entryStore directly.
michael@0 288 */
michael@0 289 #define COLLISION_FLAG ((PLDHashNumber) 1)
michael@0 290 #define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0)
michael@0 291 #define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1)
michael@0 292 #define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1)
michael@0 293 #define ENTRY_IS_LIVE(entry) PL_DHASH_ENTRY_IS_LIVE(entry)
michael@0 294 #define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0
michael@0 295
michael@0 296 /* Match an entry's keyHash against an unstored one computed from a key. */
michael@0 297 #define MATCH_ENTRY_KEYHASH(entry,hash0) \
michael@0 298 (((entry)->keyHash & ~COLLISION_FLAG) == (hash0))
michael@0 299
michael@0 300 /* Compute the address of the indexed entry in table. */
michael@0 301 #define ADDRESS_ENTRY(table, index) \
michael@0 302 ((PLDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize))
michael@0 303
michael@0 304 void
michael@0 305 PL_DHashTableFinish(PLDHashTable *table)
michael@0 306 {
michael@0 307 INCREMENT_RECURSION_LEVEL(table);
michael@0 308
michael@0 309 /* Call finalize before clearing entries, so it can enumerate them. */
michael@0 310 table->ops->finalize(table);
michael@0 311
michael@0 312 /* Clear any remaining live entries. */
michael@0 313 char *entryAddr = table->entryStore;
michael@0 314 uint32_t entrySize = table->entrySize;
michael@0 315 char *entryLimit = entryAddr + PL_DHASH_TABLE_SIZE(table) * entrySize;
michael@0 316 while (entryAddr < entryLimit) {
michael@0 317 PLDHashEntryHdr *entry = (PLDHashEntryHdr *)entryAddr;
michael@0 318 if (ENTRY_IS_LIVE(entry)) {
michael@0 319 METER(table->stats.removeEnums++);
michael@0 320 table->ops->clearEntry(table, entry);
michael@0 321 }
michael@0 322 entryAddr += entrySize;
michael@0 323 }
michael@0 324
michael@0 325 DECREMENT_RECURSION_LEVEL(table);
michael@0 326 MOZ_ASSERT(RECURSION_LEVEL_SAFE_TO_FINISH(table));
michael@0 327
michael@0 328 /* Free entry storage last. */
michael@0 329 table->ops->freeTable(table, table->entryStore);
michael@0 330 }
michael@0 331
michael@0 332 static PLDHashEntryHdr * PL_DHASH_FASTCALL
michael@0 333 SearchTable(PLDHashTable *table, const void *key, PLDHashNumber keyHash,
michael@0 334 PLDHashOperator op)
michael@0 335 {
michael@0 336 METER(table->stats.searches++);
michael@0 337 NS_ASSERTION(!(keyHash & COLLISION_FLAG),
michael@0 338 "!(keyHash & COLLISION_FLAG)");
michael@0 339
michael@0 340 /* Compute the primary hash address. */
michael@0 341 int hashShift = table->hashShift;
michael@0 342 PLDHashNumber hash1 = HASH1(keyHash, hashShift);
michael@0 343 PLDHashEntryHdr *entry = ADDRESS_ENTRY(table, hash1);
michael@0 344
michael@0 345 /* Miss: return space for a new entry. */
michael@0 346 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
michael@0 347 METER(table->stats.misses++);
michael@0 348 return entry;
michael@0 349 }
michael@0 350
michael@0 351 /* Hit: return entry. */
michael@0 352 PLDHashMatchEntry matchEntry = table->ops->matchEntry;
michael@0 353 if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) {
michael@0 354 METER(table->stats.hits++);
michael@0 355 return entry;
michael@0 356 }
michael@0 357
michael@0 358 /* Collision: double hash. */
michael@0 359 int sizeLog2 = PL_DHASH_BITS - table->hashShift;
michael@0 360 PLDHashNumber hash2 = HASH2(keyHash, sizeLog2, hashShift);
michael@0 361 uint32_t sizeMask = (1u << sizeLog2) - 1;
michael@0 362
michael@0 363 /* Save the first removed entry pointer so PL_DHASH_ADD can recycle it. */
michael@0 364 PLDHashEntryHdr *firstRemoved = nullptr;
michael@0 365
michael@0 366 for (;;) {
michael@0 367 if (MOZ_UNLIKELY(ENTRY_IS_REMOVED(entry))) {
michael@0 368 if (!firstRemoved)
michael@0 369 firstRemoved = entry;
michael@0 370 } else {
michael@0 371 if (op == PL_DHASH_ADD)
michael@0 372 entry->keyHash |= COLLISION_FLAG;
michael@0 373 }
michael@0 374
michael@0 375 METER(table->stats.steps++);
michael@0 376 hash1 -= hash2;
michael@0 377 hash1 &= sizeMask;
michael@0 378
michael@0 379 entry = ADDRESS_ENTRY(table, hash1);
michael@0 380 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
michael@0 381 METER(table->stats.misses++);
michael@0 382 return (firstRemoved && op == PL_DHASH_ADD) ? firstRemoved : entry;
michael@0 383 }
michael@0 384
michael@0 385 if (MATCH_ENTRY_KEYHASH(entry, keyHash) &&
michael@0 386 matchEntry(table, entry, key)) {
michael@0 387 METER(table->stats.hits++);
michael@0 388 return entry;
michael@0 389 }
michael@0 390 }
michael@0 391
michael@0 392 /* NOTREACHED */
michael@0 393 return nullptr;
michael@0 394 }
michael@0 395
michael@0 396 /*
michael@0 397 * This is a copy of SearchTable, used by ChangeTable, hardcoded to
michael@0 398 * 1. assume |op == PL_DHASH_ADD|,
michael@0 399 * 2. assume that |key| will never match an existing entry, and
michael@0 400 * 3. assume that no entries have been removed from the current table
michael@0 401 * structure.
michael@0 402 * Avoiding the need for |key| means we can avoid needing a way to map
michael@0 403 * entries to keys, which means callers can use complex key types more
michael@0 404 * easily.
michael@0 405 */
michael@0 406 static PLDHashEntryHdr * PL_DHASH_FASTCALL
michael@0 407 FindFreeEntry(PLDHashTable *table, PLDHashNumber keyHash)
michael@0 408 {
michael@0 409 METER(table->stats.searches++);
michael@0 410 NS_ASSERTION(!(keyHash & COLLISION_FLAG),
michael@0 411 "!(keyHash & COLLISION_FLAG)");
michael@0 412
michael@0 413 /* Compute the primary hash address. */
michael@0 414 int hashShift = table->hashShift;
michael@0 415 PLDHashNumber hash1 = HASH1(keyHash, hashShift);
michael@0 416 PLDHashEntryHdr *entry = ADDRESS_ENTRY(table, hash1);
michael@0 417
michael@0 418 /* Miss: return space for a new entry. */
michael@0 419 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
michael@0 420 METER(table->stats.misses++);
michael@0 421 return entry;
michael@0 422 }
michael@0 423
michael@0 424 /* Collision: double hash. */
michael@0 425 int sizeLog2 = PL_DHASH_BITS - table->hashShift;
michael@0 426 PLDHashNumber hash2 = HASH2(keyHash, sizeLog2, hashShift);
michael@0 427 uint32_t sizeMask = (1u << sizeLog2) - 1;
michael@0 428
michael@0 429 for (;;) {
michael@0 430 NS_ASSERTION(!ENTRY_IS_REMOVED(entry),
michael@0 431 "!ENTRY_IS_REMOVED(entry)");
michael@0 432 entry->keyHash |= COLLISION_FLAG;
michael@0 433
michael@0 434 METER(table->stats.steps++);
michael@0 435 hash1 -= hash2;
michael@0 436 hash1 &= sizeMask;
michael@0 437
michael@0 438 entry = ADDRESS_ENTRY(table, hash1);
michael@0 439 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
michael@0 440 METER(table->stats.misses++);
michael@0 441 return entry;
michael@0 442 }
michael@0 443 }
michael@0 444
michael@0 445 /* NOTREACHED */
michael@0 446 return nullptr;
michael@0 447 }
michael@0 448
michael@0 449 static bool
michael@0 450 ChangeTable(PLDHashTable *table, int deltaLog2)
michael@0 451 {
michael@0 452 /* Look, but don't touch, until we succeed in getting new entry store. */
michael@0 453 int oldLog2 = PL_DHASH_BITS - table->hashShift;
michael@0 454 int newLog2 = oldLog2 + deltaLog2;
michael@0 455 uint32_t newCapacity = 1u << newLog2;
michael@0 456 if (newCapacity > PL_DHASH_MAX_SIZE)
michael@0 457 return false;
michael@0 458
michael@0 459 uint32_t entrySize = table->entrySize;
michael@0 460 uint32_t nbytes;
michael@0 461 if (!SizeOfEntryStore(newCapacity, entrySize, &nbytes))
michael@0 462 return false; // overflowed
michael@0 463
michael@0 464 char *newEntryStore = (char *) table->ops->allocTable(table, nbytes);
michael@0 465 if (!newEntryStore)
michael@0 466 return false;
michael@0 467
michael@0 468 /* We can't fail from here on, so update table parameters. */
michael@0 469 #ifdef DEBUG
michael@0 470 uint32_t recursionLevel = table->recursionLevel;
michael@0 471 #endif
michael@0 472 table->hashShift = PL_DHASH_BITS - newLog2;
michael@0 473 table->removedCount = 0;
michael@0 474 table->generation++;
michael@0 475
michael@0 476 /* Assign the new entry store to table. */
michael@0 477 memset(newEntryStore, 0, nbytes);
michael@0 478 char *oldEntryStore, *oldEntryAddr;
michael@0 479 oldEntryAddr = oldEntryStore = table->entryStore;
michael@0 480 table->entryStore = newEntryStore;
michael@0 481 PLDHashMoveEntry moveEntry = table->ops->moveEntry;
michael@0 482 #ifdef DEBUG
michael@0 483 table->recursionLevel = recursionLevel;
michael@0 484 #endif
michael@0 485
michael@0 486 /* Copy only live entries, leaving removed ones behind. */
michael@0 487 uint32_t oldCapacity = 1u << oldLog2;
michael@0 488 for (uint32_t i = 0; i < oldCapacity; i++) {
michael@0 489 PLDHashEntryHdr *oldEntry = (PLDHashEntryHdr *)oldEntryAddr;
michael@0 490 if (ENTRY_IS_LIVE(oldEntry)) {
michael@0 491 oldEntry->keyHash &= ~COLLISION_FLAG;
michael@0 492 PLDHashEntryHdr *newEntry = FindFreeEntry(table, oldEntry->keyHash);
michael@0 493 NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(newEntry),
michael@0 494 "PL_DHASH_ENTRY_IS_FREE(newEntry)");
michael@0 495 moveEntry(table, oldEntry, newEntry);
michael@0 496 newEntry->keyHash = oldEntry->keyHash;
michael@0 497 }
michael@0 498 oldEntryAddr += entrySize;
michael@0 499 }
michael@0 500
michael@0 501 table->ops->freeTable(table, oldEntryStore);
michael@0 502 return true;
michael@0 503 }
michael@0 504
michael@0 505 PLDHashEntryHdr * PL_DHASH_FASTCALL
michael@0 506 PL_DHashTableOperate(PLDHashTable *table, const void *key, PLDHashOperator op)
michael@0 507 {
michael@0 508 PLDHashEntryHdr *entry;
michael@0 509
michael@0 510 MOZ_ASSERT(op == PL_DHASH_LOOKUP || table->recursionLevel == 0);
michael@0 511 INCREMENT_RECURSION_LEVEL(table);
michael@0 512
michael@0 513 PLDHashNumber keyHash = table->ops->hashKey(table, key);
michael@0 514 keyHash *= PL_DHASH_GOLDEN_RATIO;
michael@0 515
michael@0 516 /* Avoid 0 and 1 hash codes, they indicate free and removed entries. */
michael@0 517 ENSURE_LIVE_KEYHASH(keyHash);
michael@0 518 keyHash &= ~COLLISION_FLAG;
michael@0 519
michael@0 520 switch (op) {
michael@0 521 case PL_DHASH_LOOKUP:
michael@0 522 METER(table->stats.lookups++);
michael@0 523 entry = SearchTable(table, key, keyHash, op);
michael@0 524 break;
michael@0 525
michael@0 526 case PL_DHASH_ADD: {
michael@0 527 /*
michael@0 528 * If alpha is >= .75, grow or compress the table. If key is already
michael@0 529 * in the table, we may grow once more than necessary, but only if we
michael@0 530 * are on the edge of being overloaded.
michael@0 531 */
michael@0 532 uint32_t size = PL_DHASH_TABLE_SIZE(table);
michael@0 533 if (table->entryCount + table->removedCount >= MaxLoad(size)) {
michael@0 534 /* Compress if a quarter or more of all entries are removed. */
michael@0 535 int deltaLog2;
michael@0 536 if (table->removedCount >= size >> 2) {
michael@0 537 METER(table->stats.compresses++);
michael@0 538 deltaLog2 = 0;
michael@0 539 } else {
michael@0 540 METER(table->stats.grows++);
michael@0 541 deltaLog2 = 1;
michael@0 542 }
michael@0 543
michael@0 544 /*
michael@0 545 * Grow or compress table. If ChangeTable() fails, allow
michael@0 546 * overloading up to the secondary max. Once we hit the secondary
michael@0 547 * max, return null.
michael@0 548 */
michael@0 549 if (!ChangeTable(table, deltaLog2) &&
michael@0 550 table->entryCount + table->removedCount >=
michael@0 551 MaxLoadOnGrowthFailure(size))
michael@0 552 {
michael@0 553 METER(table->stats.addFailures++);
michael@0 554 entry = nullptr;
michael@0 555 break;
michael@0 556 }
michael@0 557 }
michael@0 558
michael@0 559 /*
michael@0 560 * Look for entry after possibly growing, so we don't have to add it,
michael@0 561 * then skip it while growing the table and re-add it after.
michael@0 562 */
michael@0 563 entry = SearchTable(table, key, keyHash, op);
michael@0 564 if (!ENTRY_IS_LIVE(entry)) {
michael@0 565 /* Initialize the entry, indicating that it's no longer free. */
michael@0 566 METER(table->stats.addMisses++);
michael@0 567 if (ENTRY_IS_REMOVED(entry)) {
michael@0 568 METER(table->stats.addOverRemoved++);
michael@0 569 table->removedCount--;
michael@0 570 keyHash |= COLLISION_FLAG;
michael@0 571 }
michael@0 572 if (table->ops->initEntry &&
michael@0 573 !table->ops->initEntry(table, entry, key)) {
michael@0 574 /* We haven't claimed entry yet; fail with null return. */
michael@0 575 memset(entry + 1, 0, table->entrySize - sizeof *entry);
michael@0 576 entry = nullptr;
michael@0 577 break;
michael@0 578 }
michael@0 579 entry->keyHash = keyHash;
michael@0 580 table->entryCount++;
michael@0 581 }
michael@0 582 METER(else table->stats.addHits++);
michael@0 583 break;
michael@0 584 }
michael@0 585
michael@0 586 case PL_DHASH_REMOVE:
michael@0 587 entry = SearchTable(table, key, keyHash, op);
michael@0 588 if (ENTRY_IS_LIVE(entry)) {
michael@0 589 /* Clear this entry and mark it as "removed". */
michael@0 590 METER(table->stats.removeHits++);
michael@0 591 PL_DHashTableRawRemove(table, entry);
michael@0 592
michael@0 593 /* Shrink if alpha is <= .25 and table isn't too small already. */
michael@0 594 uint32_t size = PL_DHASH_TABLE_SIZE(table);
michael@0 595 if (size > PL_DHASH_MIN_SIZE &&
michael@0 596 table->entryCount <= MinLoad(size)) {
michael@0 597 METER(table->stats.shrinks++);
michael@0 598 (void) ChangeTable(table, -1);
michael@0 599 }
michael@0 600 }
michael@0 601 METER(else table->stats.removeMisses++);
michael@0 602 entry = nullptr;
michael@0 603 break;
michael@0 604
michael@0 605 default:
michael@0 606 NS_NOTREACHED("0");
michael@0 607 entry = nullptr;
michael@0 608 }
michael@0 609
michael@0 610 DECREMENT_RECURSION_LEVEL(table);
michael@0 611
michael@0 612 return entry;
michael@0 613 }
michael@0 614
michael@0 615 void
michael@0 616 PL_DHashTableRawRemove(PLDHashTable *table, PLDHashEntryHdr *entry)
michael@0 617 {
michael@0 618 MOZ_ASSERT(table->recursionLevel != IMMUTABLE_RECURSION_LEVEL);
michael@0 619
michael@0 620 NS_ASSERTION(PL_DHASH_ENTRY_IS_LIVE(entry),
michael@0 621 "PL_DHASH_ENTRY_IS_LIVE(entry)");
michael@0 622
michael@0 623 /* Load keyHash first in case clearEntry() goofs it. */
michael@0 624 PLDHashNumber keyHash = entry->keyHash;
michael@0 625 table->ops->clearEntry(table, entry);
michael@0 626 if (keyHash & COLLISION_FLAG) {
michael@0 627 MARK_ENTRY_REMOVED(entry);
michael@0 628 table->removedCount++;
michael@0 629 } else {
michael@0 630 METER(table->stats.removeFrees++);
michael@0 631 MARK_ENTRY_FREE(entry);
michael@0 632 }
michael@0 633 table->entryCount--;
michael@0 634 }
michael@0 635
michael@0 636 uint32_t
michael@0 637 PL_DHashTableEnumerate(PLDHashTable *table, PLDHashEnumerator etor, void *arg)
michael@0 638 {
michael@0 639 INCREMENT_RECURSION_LEVEL(table);
michael@0 640
michael@0 641 char *entryAddr = table->entryStore;
michael@0 642 uint32_t entrySize = table->entrySize;
michael@0 643 uint32_t capacity = PL_DHASH_TABLE_SIZE(table);
michael@0 644 uint32_t tableSize = capacity * entrySize;
michael@0 645 char *entryLimit = entryAddr + tableSize;
michael@0 646 uint32_t i = 0;
michael@0 647 bool didRemove = false;
michael@0 648
michael@0 649 if (ChaosMode::isActive()) {
michael@0 650 // Start iterating at a random point in the hashtable. It would be
michael@0 651 // even more chaotic to iterate in fully random order, but that's a lot
michael@0 652 // more work.
michael@0 653 entryAddr += ChaosMode::randomUint32LessThan(capacity) * entrySize;
michael@0 654 if (entryAddr >= entryLimit) {
michael@0 655 entryAddr -= tableSize;
michael@0 656 }
michael@0 657 }
michael@0 658
michael@0 659 for (uint32_t e = 0; e < capacity; ++e) {
michael@0 660 PLDHashEntryHdr *entry = (PLDHashEntryHdr *)entryAddr;
michael@0 661 if (ENTRY_IS_LIVE(entry)) {
michael@0 662 PLDHashOperator op = etor(table, entry, i++, arg);
michael@0 663 if (op & PL_DHASH_REMOVE) {
michael@0 664 METER(table->stats.removeEnums++);
michael@0 665 PL_DHashTableRawRemove(table, entry);
michael@0 666 didRemove = true;
michael@0 667 }
michael@0 668 if (op & PL_DHASH_STOP)
michael@0 669 break;
michael@0 670 }
michael@0 671 entryAddr += entrySize;
michael@0 672 if (entryAddr >= entryLimit) {
michael@0 673 entryAddr -= tableSize;
michael@0 674 }
michael@0 675 }
michael@0 676
michael@0 677 MOZ_ASSERT(!didRemove || table->recursionLevel == 1);
michael@0 678
michael@0 679 /*
michael@0 680 * Shrink or compress if a quarter or more of all entries are removed, or
michael@0 681 * if the table is underloaded according to the minimum alpha, and is not
michael@0 682 * minimal-size already. Do this only if we removed above, so non-removing
michael@0 683 * enumerations can count on stable table->entryStore until the next
michael@0 684 * non-lookup-Operate or removing-Enumerate.
michael@0 685 */
michael@0 686 if (didRemove &&
michael@0 687 (table->removedCount >= capacity >> 2 ||
michael@0 688 (capacity > PL_DHASH_MIN_SIZE &&
michael@0 689 table->entryCount <= MinLoad(capacity)))) {
michael@0 690 METER(table->stats.enumShrinks++);
michael@0 691 capacity = table->entryCount;
michael@0 692 capacity += capacity >> 1;
michael@0 693 if (capacity < PL_DHASH_MIN_SIZE)
michael@0 694 capacity = PL_DHASH_MIN_SIZE;
michael@0 695
michael@0 696 uint32_t ceiling = CeilingLog2(capacity);
michael@0 697 ceiling -= PL_DHASH_BITS - table->hashShift;
michael@0 698
michael@0 699 (void) ChangeTable(table, ceiling);
michael@0 700 }
michael@0 701
michael@0 702 DECREMENT_RECURSION_LEVEL(table);
michael@0 703
michael@0 704 return i;
michael@0 705 }
michael@0 706
michael@0 707 struct SizeOfEntryExcludingThisArg
michael@0 708 {
michael@0 709 size_t total;
michael@0 710 PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis;
michael@0 711 MallocSizeOf mallocSizeOf;
michael@0 712 void *arg; // the arg passed by the user
michael@0 713 };
michael@0 714
michael@0 715 static PLDHashOperator
michael@0 716 SizeOfEntryExcludingThisEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr,
michael@0 717 uint32_t number, void *arg)
michael@0 718 {
michael@0 719 SizeOfEntryExcludingThisArg *e = (SizeOfEntryExcludingThisArg *)arg;
michael@0 720 e->total += e->sizeOfEntryExcludingThis(hdr, e->mallocSizeOf, e->arg);
michael@0 721 return PL_DHASH_NEXT;
michael@0 722 }
michael@0 723
michael@0 724 size_t
michael@0 725 PL_DHashTableSizeOfExcludingThis(const PLDHashTable *table,
michael@0 726 PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
michael@0 727 MallocSizeOf mallocSizeOf,
michael@0 728 void *arg /* = nullptr */)
michael@0 729 {
michael@0 730 size_t n = 0;
michael@0 731 n += mallocSizeOf(table->entryStore);
michael@0 732 if (sizeOfEntryExcludingThis) {
michael@0 733 SizeOfEntryExcludingThisArg arg2 = { 0, sizeOfEntryExcludingThis, mallocSizeOf, arg };
michael@0 734 PL_DHashTableEnumerate(const_cast<PLDHashTable *>(table),
michael@0 735 SizeOfEntryExcludingThisEnumerator, &arg2);
michael@0 736 n += arg2.total;
michael@0 737 }
michael@0 738 return n;
michael@0 739 }
michael@0 740
michael@0 741 size_t
michael@0 742 PL_DHashTableSizeOfIncludingThis(const PLDHashTable *table,
michael@0 743 PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
michael@0 744 MallocSizeOf mallocSizeOf,
michael@0 745 void *arg /* = nullptr */)
michael@0 746 {
michael@0 747 return mallocSizeOf(table) +
michael@0 748 PL_DHashTableSizeOfExcludingThis(table, sizeOfEntryExcludingThis,
michael@0 749 mallocSizeOf, arg);
michael@0 750 }
michael@0 751
michael@0 752 #ifdef DEBUG
michael@0 753 void
michael@0 754 PL_DHashMarkTableImmutable(PLDHashTable *table)
michael@0 755 {
michael@0 756 table->recursionLevel = IMMUTABLE_RECURSION_LEVEL;
michael@0 757 }
michael@0 758 #endif
michael@0 759
michael@0 760 #ifdef PL_DHASHMETER
michael@0 761 #include <math.h>
michael@0 762
michael@0 763 void
michael@0 764 PL_DHashTableDumpMeter(PLDHashTable *table, PLDHashEnumerator dump, FILE *fp)
michael@0 765 {
michael@0 766 PLDHashNumber hash1, hash2, maxChainHash1, maxChainHash2;
michael@0 767 double sqsum, mean, variance, sigma;
michael@0 768 PLDHashEntryHdr *entry;
michael@0 769
michael@0 770 char *entryAddr = table->entryStore;
michael@0 771 uint32_t entrySize = table->entrySize;
michael@0 772 int hashShift = table->hashShift;
michael@0 773 int sizeLog2 = PL_DHASH_BITS - hashShift;
michael@0 774 uint32_t tableSize = PL_DHASH_TABLE_SIZE(table);
michael@0 775 uint32_t sizeMask = (1u << sizeLog2) - 1;
michael@0 776 uint32_t chainCount = 0, maxChainLen = 0;
michael@0 777 hash2 = 0;
michael@0 778 sqsum = 0;
michael@0 779
michael@0 780 for (uint32_t i = 0; i < tableSize; i++) {
michael@0 781 entry = (PLDHashEntryHdr *)entryAddr;
michael@0 782 entryAddr += entrySize;
michael@0 783 if (!ENTRY_IS_LIVE(entry))
michael@0 784 continue;
michael@0 785 hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift);
michael@0 786 PLDHashNumber saveHash1 = hash1;
michael@0 787 PLDHashEntryHdr *probe = ADDRESS_ENTRY(table, hash1);
michael@0 788 uint32_t chainLen = 1;
michael@0 789 if (probe == entry) {
michael@0 790 /* Start of a (possibly unit-length) chain. */
michael@0 791 chainCount++;
michael@0 792 } else {
michael@0 793 hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2,
michael@0 794 hashShift);
michael@0 795 do {
michael@0 796 chainLen++;
michael@0 797 hash1 -= hash2;
michael@0 798 hash1 &= sizeMask;
michael@0 799 probe = ADDRESS_ENTRY(table, hash1);
michael@0 800 } while (probe != entry);
michael@0 801 }
michael@0 802 sqsum += chainLen * chainLen;
michael@0 803 if (chainLen > maxChainLen) {
michael@0 804 maxChainLen = chainLen;
michael@0 805 maxChainHash1 = saveHash1;
michael@0 806 maxChainHash2 = hash2;
michael@0 807 }
michael@0 808 }
michael@0 809
michael@0 810 uint32_t entryCount = table->entryCount;
michael@0 811 if (entryCount && chainCount) {
michael@0 812 mean = (double)entryCount / chainCount;
michael@0 813 variance = chainCount * sqsum - entryCount * entryCount;
michael@0 814 if (variance < 0 || chainCount == 1)
michael@0 815 variance = 0;
michael@0 816 else
michael@0 817 variance /= chainCount * (chainCount - 1);
michael@0 818 sigma = sqrt(variance);
michael@0 819 } else {
michael@0 820 mean = sigma = 0;
michael@0 821 }
michael@0 822
michael@0 823 fprintf(fp, "Double hashing statistics:\n");
michael@0 824 fprintf(fp, " table size (in entries): %u\n", tableSize);
michael@0 825 fprintf(fp, " number of entries: %u\n", table->entryCount);
michael@0 826 fprintf(fp, " number of removed entries: %u\n", table->removedCount);
michael@0 827 fprintf(fp, " number of searches: %u\n", table->stats.searches);
michael@0 828 fprintf(fp, " number of hits: %u\n", table->stats.hits);
michael@0 829 fprintf(fp, " number of misses: %u\n", table->stats.misses);
michael@0 830 fprintf(fp, " mean steps per search: %g\n", table->stats.searches ?
michael@0 831 (double)table->stats.steps
michael@0 832 / table->stats.searches :
michael@0 833 0.);
michael@0 834 fprintf(fp, " mean hash chain length: %g\n", mean);
michael@0 835 fprintf(fp, " standard deviation: %g\n", sigma);
michael@0 836 fprintf(fp, " maximum hash chain length: %u\n", maxChainLen);
michael@0 837 fprintf(fp, " number of lookups: %u\n", table->stats.lookups);
michael@0 838 fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses);
michael@0 839 fprintf(fp, "adds that recycled removeds: %u\n", table->stats.addOverRemoved);
michael@0 840 fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits);
michael@0 841 fprintf(fp, " add failures: %u\n", table->stats.addFailures);
michael@0 842 fprintf(fp, " useful removes: %u\n", table->stats.removeHits);
michael@0 843 fprintf(fp, " useless removes: %u\n", table->stats.removeMisses);
michael@0 844 fprintf(fp, "removes that freed an entry: %u\n", table->stats.removeFrees);
michael@0 845 fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums);
michael@0 846 fprintf(fp, " number of grows: %u\n", table->stats.grows);
michael@0 847 fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks);
michael@0 848 fprintf(fp, " number of compresses: %u\n", table->stats.compresses);
michael@0 849 fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks);
michael@0 850
michael@0 851 if (dump && maxChainLen && hash2) {
michael@0 852 fputs("Maximum hash chain:\n", fp);
michael@0 853 hash1 = maxChainHash1;
michael@0 854 hash2 = maxChainHash2;
michael@0 855 entry = ADDRESS_ENTRY(table, hash1);
michael@0 856 uint32_t i = 0;
michael@0 857 do {
michael@0 858 if (dump(table, entry, i++, fp) != PL_DHASH_NEXT)
michael@0 859 break;
michael@0 860 hash1 -= hash2;
michael@0 861 hash1 &= sizeMask;
michael@0 862 entry = ADDRESS_ENTRY(table, hash1);
michael@0 863 } while (PL_DHASH_ENTRY_IS_BUSY(entry));
michael@0 864 }
michael@0 865 }
michael@0 866 #endif /* PL_DHASHMETER */

mercurial