LCOV - code coverage report
Current view: directory - xpcom/glue - pldhash.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 273 172 63.0 %
Date: 2012-06-02 Functions: 26 12 46.2 %

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

Generated by: LCOV version 1.7