Bug 1161377 (part 1) - Add an initializing constructor and destructor to PLDHashTable. r=froydnj.

The destructor is "opt-in" -- there's a flag that makes it a no-op unless the
table was initialized with the initializing constructor. This will allow us to
incrementally convert existing tables from manual to automatic
initialization/finalization. This is important because some of the existing
uses are tricky (impossible?) to convert to the automatic style.
This commit is contained in:
Nicholas Nethercote 2015-05-04 22:59:02 -07:00
parent d671e16e9b
commit 9978627ce0
3 changed files with 73 additions and 21 deletions

View File

@ -406,10 +406,6 @@ nsTHashtable<EntryType>::nsTHashtable(nsTHashtable<EntryType>&& aOther)
// aOther shouldn't touch mTable after this, because we've stolen the table's
// pointers but not overwitten them.
MOZ_MAKE_MEM_UNDEFINED(&aOther.mTable, sizeof(aOther.mTable));
// Indicate that aOther is not initialized. This will make its destructor a
// nop, which is what we want.
aOther.mTable.SetOps(nullptr);
}
template<class EntryType>

View File

@ -218,6 +218,7 @@ MOZ_ALWAYS_INLINE void
PLDHashTable::Init(const PLDHashTableOps* aOps,
uint32_t aEntrySize, uint32_t aLength)
{
MOZ_ASSERT(!mAutoFinish);
MOZ_ASSERT(!IsInitialized());
// Check that the important fields have been set by the constructor.
@ -266,6 +267,19 @@ PL_DHashTableInit(PLDHashTable* aTable, const PLDHashTableOps* aOps,
aTable->Init(aOps, aEntrySize, aLength);
}
PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize,
uint32_t aLength)
: mOps(nullptr)
, mAutoFinish(0)
, mEntryStore(nullptr)
#ifdef DEBUG
, mRecursionLevel()
#endif
{
Init(aOps, aEntrySize, aLength);
mAutoFinish = 1;
}
PLDHashTable& PLDHashTable::operator=(PLDHashTable&& aOther)
{
if (this == &aOther) {
@ -273,6 +287,7 @@ PLDHashTable& PLDHashTable::operator=(PLDHashTable&& aOther)
}
// Destruct |this|.
mAutoFinish = 0;
Finish();
// Move pieces over.
@ -281,7 +296,9 @@ PLDHashTable& PLDHashTable::operator=(PLDHashTable&& aOther)
mEntrySize = Move(aOther.mEntrySize);
mEntryCount = Move(aOther.mEntryCount);
mRemovedCount = Move(aOther.mRemovedCount);
mGeneration = Move(aOther.mGeneration);
// We can't use Move() on bitfields. Fortunately, '=' suffices.
mGeneration = aOther.mGeneration;
mAutoFinish = aOther.mAutoFinish;
mEntryStore = Move(aOther.mEntryStore);
#ifdef PL_DHASHMETER
mStats = Move(aOther.mStats);
@ -340,6 +357,8 @@ PLDHashTable::EntryIsFree(PLDHashEntryHdr* aEntry)
MOZ_ALWAYS_INLINE void
PLDHashTable::Finish()
{
MOZ_ASSERT(!mAutoFinish);
if (!IsInitialized()) {
MOZ_ASSERT(!mEntryStore);
return;
@ -375,6 +394,20 @@ PL_DHashTableFinish(PLDHashTable* aTable)
aTable->Finish();
}
PLDHashTable::~PLDHashTable()
{
// If we used automatic initialization, then finalize the table here.
// Otherwise, Finish() should have already been called manually.
if (mAutoFinish) {
mAutoFinish = 0;
Finish();
} else {
// XXX: actually, we can't assert this here because some tables never get
// finalized.
//MOZ_ASSERT(!IsInitialized());
}
}
// If |IsAdd| is true, the return value is always non-null and it may be a
// previously-removed entry. If |IsAdd| is false, the return value is null on a
// miss, and will never be a previously-removed entry on a hit. This

View File

@ -145,12 +145,21 @@ typedef size_t (*PLDHashSizeOfEntryExcludingThisFun)(
PLDHashEntryHdr* aHdr, mozilla::MallocSizeOf aMallocSizeOf, void* aArg);
/*
* A PLDHashTable is currently 8 words (without the PL_DHASHMETER overhead)
* on most architectures, and may be allocated on the stack or within another
* structure or class (see below for the Init and Finish functions to use).
* A PLDHashTable may be allocated on the stack or within another structure or
* class. No entry storage is allocated until the first element is added. This
* means that empty hash tables are cheap, which is good because they are
* common.
*
* No entry storage is allocated until the first element is added. This means
* that empty hash tables are cheap, which is good because they are common.
* Due to historical reasons, there are two ways to manage the initialization
* and finalization of a PLDHashTable. There are assertions that will trigger
* if the two styles are mixed for a single table.
*
* - Automatic, C++ style: via the multi-arg constructor and the destructor.
* This is the preferred style.
*
* - Manual, C style: via the Init() and Finish() methods. If Init() is
* called on a table, then the Finish() must be called to finalize the
* table, and the destructor will be a no-op.
*
* There used to be a long, math-heavy comment here about the merits of
* double hashing vs. chaining; it was removed in bug 1058335. In short, double
@ -169,7 +178,8 @@ private:
uint32_t mEntrySize; /* number of bytes in an entry */
uint32_t mEntryCount; /* number of entries in table */
uint32_t mRemovedCount; /* removed entry sentinels in table */
uint32_t mGeneration; /* entry storage generation number */
uint32_t mGeneration:31; /* entry storage generation number */
uint32_t mAutoFinish:1; /* should the destructor call Finish()? */
char* mEntryStore; /* entry storage; allocated lazily */
#ifdef PL_DHASHMETER
struct PLDHashStats
@ -214,6 +224,7 @@ public:
, mEntryCount(0)
, mRemovedCount(0)
, mGeneration(0)
, mAutoFinish(0)
, mEntryStore(nullptr)
#ifdef PL_DHASHMETER
, mStats()
@ -223,8 +234,21 @@ public:
#endif
{}
// Initialize the table with aOps and aEntrySize. The table's initial
// capacity will be chosen such that |aLength| elements can be inserted
// without rehashing; if |aLength| is a power-of-two, this capacity will be
// |2*length|. However, because entry storage is allocated lazily, this
// initial capacity won't be relevant until the first element is added; prior
// to that the capacity will be zero.
//
// This function will crash if |aEntrySize| and/or |aLength| are too large.
//
PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize,
uint32_t aLength = PL_DHASH_DEFAULT_INITIAL_LENGTH);
PLDHashTable(PLDHashTable&& aOther)
: mOps(nullptr)
, mAutoFinish(0)
, mEntryStore(nullptr)
#ifdef DEBUG
, mRecursionLevel(0)
@ -235,6 +259,8 @@ public:
PLDHashTable& operator=(PLDHashTable&& aOther);
~PLDHashTable();
bool IsInitialized() const { return !!mOps; }
// These should be used rarely.
@ -466,23 +492,20 @@ PLDHashTable* PL_NewDHashTable(
void PL_DHashTableDestroy(PLDHashTable* aTable);
/*
* Initialize aTable with aOps and aEntrySize. The table's initial capacity
* will be chosen such that |aLength| elements can be inserted without
* rehashing; if |aLength| is a power-of-two, this capacity will be |2*length|.
* However, because entry storage is allocated lazily, this initial capacity
* won't be relevant until the first element is added; prior to that the
* capacity will be zero.
* This function works similarly to the multi-arg constructor.
*
* This function will crash if |aEntrySize| and/or |aLength| are too large.
* Any table initialized with this function must be finalized via
* PL_DHashTableFinish(). The alternative (and preferred) way to
* initialize a PLDHashTable is via the multi-arg constructor; any such table
* will be auto-finalized by the destructor.
*/
void PL_DHashTableInit(
PLDHashTable* aTable, const PLDHashTableOps* aOps,
uint32_t aEntrySize, uint32_t aLength = PL_DHASH_DEFAULT_INITIAL_LENGTH);
/*
* Free |aTable|'s entry storage (via aTable->mOps->freeTable). Use this
* function to destroy a PLDHashTable that is allocated on the stack or in
* static memory and was created via PL_DHashTableInit().
* Free |aTable|'s entry storage. Use this function to finalize a PLDHashTable
* that was initialized with PL_DHashTableInit().
*/
void PL_DHashTableFinish(PLDHashTable* aTable);