Bug 1508873 - part 2 - convert HashTable to work primarily in terms of slots; r=luke

HashTableEntry's data layout currently wastes a fair amount of space due
to ABI-mandated padding.  For instance, HashTableEntry<T*> on a 64-bit
platform looks like:

class HashTableEntry {
  HashNumber mKeyHash;
  // Four bytes of wasted space here to pad mValueData to the correct place.
  unsigned char mValueData[sizeof(T*)];
};

This wasted space means that sets of pointers backed by
mozilla::HashTable waste a quarter of their entry storage space.  Maps
of pointers to pointers waste a sixth of their entry storage space.
We'd like to fix this by packing all the cached hashes together,
followed by all the hash table entries.

As a first step to laying out the hash table storage differently, we
have to make HashTable not access entries directly, but go through an
abstraction that represents the key and the entry.  We call this
abstraction "slots".  This commit is similar to the change done for
PLDHashTable previously.

Parts of HashTable still work in terms of Entry; the creation and
destruction of tables was not worth changing here.  We'll address that
in the next commit.
This commit is contained in:
Nathan Froyd 2018-12-12 14:57:21 -05:00
Родитель 05c72d126b
Коммит 27902c8e43
1 изменённых файлов: 170 добавлений и 96 удалений

Просмотреть файл

@ -80,6 +80,7 @@
#include "mozilla/Casting.h" #include "mozilla/Casting.h"
#include "mozilla/HashFunctions.h" #include "mozilla/HashFunctions.h"
#include "mozilla/MathAlgorithms.h" #include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryChecking.h" #include "mozilla/MemoryChecking.h"
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
#include "mozilla/Move.h" #include "mozilla/Move.h"
@ -1078,6 +1079,71 @@ class HashTableEntry {
} }
}; };
// A slot represents a cached hash value and its associated entry stored
// in the hash table. While these two things currently belong to the same
// object, HashTableEntry, above, they do not necessarily need to be
// contiguous in memory and this abstraction helps enforce the separation
// between the two.
template <class T>
class EntrySlot {
using NonConstT = typename RemoveConst<T>::Type;
using Entry = HashTableEntry<T>;
Entry* mEntry;
public:
EntrySlot(Entry* aEntry) : mEntry(aEntry) {}
EntrySlot(const EntrySlot&) = default;
EntrySlot(EntrySlot&& aOther) = default;
EntrySlot& operator=(const EntrySlot&) = default;
EntrySlot& operator=(EntrySlot&&) = default;
bool operator==(const EntrySlot& aRhs) const { return mEntry == aRhs.mEntry; }
bool operator<(const EntrySlot& aRhs) const { return mEntry < aRhs.mEntry; }
EntrySlot& operator++() {
++mEntry;
return *this;
}
void destroy() { mEntry->destroy(); }
void swap(EntrySlot& aOther) { mEntry->swap(aOther.mEntry); }
T& get() const { return mEntry->get(); }
NonConstT& getMutable() { return mEntry->getMutable(); }
bool isFree() const { return mEntry->isFree(); }
void clearLive() { mEntry->clearLive(); }
void clear() { mEntry->clear(); }
bool isRemoved() const { return mEntry->isRemoved(); }
void removeLive() { mEntry->removeLive(); }
bool isLive() const { return mEntry->isLive(); }
void setCollision() { mEntry->setCollision(); }
void unsetCollision() { mEntry->unsetCollision(); }
bool hasCollision() const { return mEntry->hasCollision(); }
bool matchHash(HashNumber hn) { return mEntry->matchHash(hn); }
HashNumber getKeyHash() const { return mEntry->getKeyHash(); }
template <typename... Args>
void setLive(HashNumber aHashNumber, Args&&... aArgs) {
mEntry->setLive(aHashNumber, std::forward<Args>(aArgs)...);
}
Entry* toEntry() const { return mEntry; }
};
template <class T, class HashPolicy, class AllocPolicy> template <class T, class HashPolicy, class AllocPolicy>
class HashTable : private AllocPolicy { class HashTable : private AllocPolicy {
friend class mozilla::ReentrancyGuard; friend class mozilla::ReentrancyGuard;
@ -1088,6 +1154,16 @@ class HashTable : private AllocPolicy {
public: public:
using Entry = HashTableEntry<T>; using Entry = HashTableEntry<T>;
using Slot = EntrySlot<T>;
template <typename F>
static void forEachSlot(Entry* aEntries, uint32_t aCapacity, F&& f) {
Entry* end = aEntries + aCapacity;
for (Entry* start = aEntries; start < end; ++start) {
Slot slot(start);
f(slot);
}
}
// A nullable pointer to a hash table element. A Ptr |p| can be tested // A nullable pointer to a hash table element. A Ptr |p| can be tested
// either explicitly |if (p.found()) p->...| or using boolean conversion // either explicitly |if (p.found()) p->...| or using boolean conversion
@ -1096,15 +1172,15 @@ class HashTable : private AllocPolicy {
class Ptr { class Ptr {
friend class HashTable; friend class HashTable;
Entry* mEntry; Slot mSlot;
#ifdef DEBUG #ifdef DEBUG
const HashTable* mTable; const HashTable* mTable;
Generation mGeneration; Generation mGeneration;
#endif #endif
protected: protected:
Ptr(Entry& aEntry, const HashTable& aTable) Ptr(Slot aSlot, const HashTable& aTable)
: mEntry(&aEntry) : mSlot(aSlot)
#ifdef DEBUG #ifdef DEBUG
, ,
mTable(&aTable), mTable(&aTable),
@ -1115,7 +1191,7 @@ class HashTable : private AllocPolicy {
// This constructor is used only by AddPtr() within lookupForAdd(). // This constructor is used only by AddPtr() within lookupForAdd().
explicit Ptr(const HashTable& aTable) explicit Ptr(const HashTable& aTable)
: mEntry(nullptr) : mSlot(nullptr)
#ifdef DEBUG #ifdef DEBUG
, ,
mTable(&aTable), mTable(&aTable),
@ -1124,11 +1200,11 @@ class HashTable : private AllocPolicy {
{ {
} }
bool isValid() const { return !!mEntry; } bool isValid() const { return !!mSlot.toEntry(); }
public: public:
Ptr() Ptr()
: mEntry(nullptr) : mSlot(nullptr)
#ifdef DEBUG #ifdef DEBUG
, ,
mTable(nullptr), mTable(nullptr),
@ -1144,14 +1220,14 @@ class HashTable : private AllocPolicy {
#ifdef DEBUG #ifdef DEBUG
MOZ_ASSERT(mGeneration == mTable->generation()); MOZ_ASSERT(mGeneration == mTable->generation());
#endif #endif
return mEntry->isLive(); return mSlot.isLive();
} }
explicit operator bool() const { return found(); } explicit operator bool() const { return found(); }
bool operator==(const Ptr& aRhs) const { bool operator==(const Ptr& aRhs) const {
MOZ_ASSERT(found() && aRhs.found()); MOZ_ASSERT(found() && aRhs.found());
return mEntry == aRhs.mEntry; return mSlot == aRhs.mSlot;
} }
bool operator!=(const Ptr& aRhs) const { bool operator!=(const Ptr& aRhs) const {
@ -1166,7 +1242,7 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(found()); MOZ_ASSERT(found());
MOZ_ASSERT(mGeneration == mTable->generation()); MOZ_ASSERT(mGeneration == mTable->generation());
#endif #endif
return mEntry->get(); return mSlot.get();
} }
T* operator->() const { T* operator->() const {
@ -1174,7 +1250,7 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(found()); MOZ_ASSERT(found());
MOZ_ASSERT(mGeneration == mTable->generation()); MOZ_ASSERT(mGeneration == mTable->generation());
#endif #endif
return &mEntry->get(); return &mSlot.get();
} }
}; };
@ -1187,8 +1263,8 @@ class HashTable : private AllocPolicy {
uint64_t mMutationCount; uint64_t mMutationCount;
#endif #endif
AddPtr(Entry& aEntry, const HashTable& aTable, HashNumber aHashNumber) AddPtr(Slot aSlot, const HashTable& aTable, HashNumber aHashNumber)
: Ptr(aEntry, aTable), : Ptr(aSlot, aTable),
mKeyHash(aHashNumber) mKeyHash(aHashNumber)
#ifdef DEBUG #ifdef DEBUG
, ,
@ -1198,7 +1274,7 @@ class HashTable : private AllocPolicy {
} }
// This constructor is used when lookupForAdd() is performed on a table // This constructor is used when lookupForAdd() is performed on a table
// lacking entry storage; it leaves mEntry null but initializes everything // lacking entry storage; it leaves mSlot null but initializes everything
// else. // else.
AddPtr(const HashTable& aTable, HashNumber aHashNumber) AddPtr(const HashTable& aTable, HashNumber aHashNumber)
: Ptr(aTable), : Ptr(aTable),
@ -1225,8 +1301,8 @@ class HashTable : private AllocPolicy {
friend class HashTable; friend class HashTable;
explicit Iterator(const HashTable& aTable) explicit Iterator(const HashTable& aTable)
: mCur(aTable.mTable), : mCur(aTable.slotForIndex(0)),
mEnd(aTable.mTable + aTable.capacity()) mEnd(aTable.slotForIndex(aTable.capacity()))
#ifdef DEBUG #ifdef DEBUG
, ,
mTable(aTable), mTable(aTable),
@ -1235,13 +1311,13 @@ class HashTable : private AllocPolicy {
mValidEntry(true) mValidEntry(true)
#endif #endif
{ {
while (mCur < mEnd && !mCur->isLive()) { while (mCur < mEnd && !mCur.isLive()) {
++mCur; ++mCur;
} }
} }
Entry* mCur; Slot mCur;
Entry* mEnd; Slot mEnd;
#ifdef DEBUG #ifdef DEBUG
const HashTable& mTable; const HashTable& mTable;
uint64_t mMutationCount; uint64_t mMutationCount;
@ -1265,7 +1341,7 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(mGeneration == mTable.generation()); MOZ_ASSERT(mGeneration == mTable.generation());
MOZ_ASSERT(mMutationCount == mTable.mMutationCount); MOZ_ASSERT(mMutationCount == mTable.mMutationCount);
#endif #endif
return mCur->get(); return mCur.get();
} }
void next() { void next() {
@ -1274,7 +1350,7 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(mGeneration == mTable.generation()); MOZ_ASSERT(mGeneration == mTable.generation());
MOZ_ASSERT(mMutationCount == mTable.mMutationCount); MOZ_ASSERT(mMutationCount == mTable.mMutationCount);
#endif #endif
while (++mCur < mEnd && !mCur->isLive()) { while (++mCur < mEnd && !mCur.isLive()) {
continue; continue;
} }
#ifdef DEBUG #ifdef DEBUG
@ -1316,7 +1392,7 @@ class HashTable : private AllocPolicy {
// Removes the current element from the table, leaving |get()| // Removes the current element from the table, leaving |get()|
// invalid until the next call to |next()|. // invalid until the next call to |next()|.
void remove() { void remove() {
mTable.remove(*this->mCur); mTable.remove(this->mCur);
mRemoved = true; mRemoved = true;
#ifdef DEBUG #ifdef DEBUG
this->mValidEntry = false; this->mValidEntry = false;
@ -1331,7 +1407,7 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(this->mGeneration == this->Iterator::mTable.generation()); MOZ_ASSERT(this->mGeneration == this->Iterator::mTable.generation());
MOZ_ASSERT(this->mMutationCount == this->Iterator::mTable.mMutationCount); MOZ_ASSERT(this->mMutationCount == this->Iterator::mTable.mMutationCount);
#endif #endif
return this->mCur->getMutable(); return this->mCur.getMutable();
} }
// Removes the current element and re-inserts it into the table with // Removes the current element and re-inserts it into the table with
@ -1339,7 +1415,7 @@ class HashTable : private AllocPolicy {
// this operation until the next call to |next()|. // this operation until the next call to |next()|.
void rekey(const Lookup& l, const Key& k) { void rekey(const Lookup& l, const Key& k) {
MOZ_ASSERT(&k != &HashPolicy::getKey(this->mCur->get())); MOZ_ASSERT(&k != &HashPolicy::getKey(this->mCur->get()));
Ptr p(*this->mCur, mTable); Ptr p(this->mCur, mTable);
mTable.rekeyWithoutRehash(p, l, k); mTable.rekeyWithoutRehash(p, l, k);
mRekeyed = true; mRekeyed = true;
#ifdef DEBUG #ifdef DEBUG
@ -1514,19 +1590,17 @@ class HashTable : private AllocPolicy {
? aAllocPolicy.template pod_malloc<Entry>(aCapacity) ? aAllocPolicy.template pod_malloc<Entry>(aCapacity)
: aAllocPolicy.template maybe_pod_malloc<Entry>(aCapacity); : aAllocPolicy.template maybe_pod_malloc<Entry>(aCapacity);
if (table) { if (table) {
for (uint32_t i = 0; i < aCapacity; i++) { forEachSlot(table, aCapacity, [&](const Slot& slot) {
new (KnownNotNull, &table[i]) Entry(); new (KnownNotNull, slot.toEntry()) Entry();
} });
} }
return table; return table;
} }
static void destroyTable(AllocPolicy& aAllocPolicy, Entry* aOldTable, static void destroyTable(AllocPolicy& aAllocPolicy, Entry* aOldTable,
uint32_t aCapacity) { uint32_t aCapacity) {
Entry* end = aOldTable + aCapacity; forEachSlot(aOldTable, aCapacity,
for (Entry* e = aOldTable; e < end; ++e) { [&](const Slot& slot) { slot.toEntry()->~Entry(); });
e->~Entry();
}
aAllocPolicy.free_(aOldTable, aCapacity); aAllocPolicy.free_(aOldTable, aCapacity);
} }
@ -1575,59 +1649,61 @@ class HashTable : private AllocPolicy {
return (aHash1 - aDoubleHash.mHash2) & aDoubleHash.mSizeMask; return (aHash1 - aDoubleHash.mHash2) & aDoubleHash.mSizeMask;
} }
static MOZ_ALWAYS_INLINE bool match(Entry& aEntry, const Lookup& aLookup) { static MOZ_ALWAYS_INLINE bool match(T& aEntry, const Lookup& aLookup) {
return HashPolicy::match(HashPolicy::getKey(aEntry.get()), aLookup); return HashPolicy::match(HashPolicy::getKey(aEntry), aLookup);
} }
enum LookupReason { ForNonAdd, ForAdd }; enum LookupReason { ForNonAdd, ForAdd };
Slot slotForIndex(HashNumber aIndex) const { return Slot(&mTable[aIndex]); }
// Warning: in order for readonlyThreadsafeLookup() to be safe this // Warning: in order for readonlyThreadsafeLookup() to be safe this
// function must not modify the table in any way when Reason==ForNonAdd. // function must not modify the table in any way when Reason==ForNonAdd.
template <LookupReason Reason> template <LookupReason Reason>
MOZ_ALWAYS_INLINE Entry& lookup(const Lookup& aLookup, MOZ_ALWAYS_INLINE Slot lookup(const Lookup& aLookup,
HashNumber aKeyHash) const { HashNumber aKeyHash) const {
MOZ_ASSERT(isLiveHash(aKeyHash)); MOZ_ASSERT(isLiveHash(aKeyHash));
MOZ_ASSERT(!(aKeyHash & sCollisionBit)); MOZ_ASSERT(!(aKeyHash & sCollisionBit));
MOZ_ASSERT(mTable); MOZ_ASSERT(mTable);
// Compute the primary hash address. // Compute the primary hash address.
HashNumber h1 = hash1(aKeyHash); HashNumber h1 = hash1(aKeyHash);
Entry* entry = &mTable[h1]; Slot slot = slotForIndex(h1);
// Miss: return space for a new entry. // Miss: return space for a new entry.
if (entry->isFree()) { if (slot.isFree()) {
return *entry; return slot;
} }
// Hit: return entry. // Hit: return entry.
if (entry->matchHash(aKeyHash) && match(*entry, aLookup)) { if (slot.matchHash(aKeyHash) && match(slot.get(), aLookup)) {
return *entry; return slot;
} }
// Collision: double hash. // Collision: double hash.
DoubleHash dh = hash2(aKeyHash); DoubleHash dh = hash2(aKeyHash);
// Save the first removed entry pointer so we can recycle later. // Save the first removed entry pointer so we can recycle later.
Entry* firstRemoved = nullptr; Maybe<Slot> firstRemoved;
while (true) { while (true) {
if (Reason == ForAdd && !firstRemoved) { if (Reason == ForAdd && !firstRemoved) {
if (MOZ_UNLIKELY(entry->isRemoved())) { if (MOZ_UNLIKELY(slot.isRemoved())) {
firstRemoved = entry; firstRemoved.emplace(slot);
} else { } else {
entry->setCollision(); slot.setCollision();
} }
} }
h1 = applyDoubleHash(h1, dh); h1 = applyDoubleHash(h1, dh);
entry = &mTable[h1]; slot = slotForIndex(h1);
if (entry->isFree()) { if (slot.isFree()) {
return firstRemoved ? *firstRemoved : *entry; return firstRemoved.refOr(slot);
} }
if (entry->matchHash(aKeyHash) && match(*entry, aLookup)) { if (slot.matchHash(aKeyHash) && match(slot.get(), aLookup)) {
return *entry; return slot;
} }
} }
} }
@ -1635,7 +1711,7 @@ class HashTable : private AllocPolicy {
// This is a copy of lookup() hardcoded to the assumptions: // This is a copy of lookup() hardcoded to the assumptions:
// 1. the lookup is for an add; // 1. the lookup is for an add;
// 2. the key, whose |keyHash| has been passed, is not in the table. // 2. the key, whose |keyHash| has been passed, is not in the table.
Entry& findNonLiveEntry(HashNumber aKeyHash) { Slot findNonLiveSlot(HashNumber aKeyHash) {
MOZ_ASSERT(!(aKeyHash & sCollisionBit)); MOZ_ASSERT(!(aKeyHash & sCollisionBit));
MOZ_ASSERT(mTable); MOZ_ASSERT(mTable);
@ -1643,24 +1719,24 @@ class HashTable : private AllocPolicy {
// Compute the primary hash address. // Compute the primary hash address.
HashNumber h1 = hash1(aKeyHash); HashNumber h1 = hash1(aKeyHash);
Entry* entry = &mTable[h1]; Slot slot = slotForIndex(h1);
// Miss: return space for a new entry. // Miss: return space for a new entry.
if (!entry->isLive()) { if (!slot.isLive()) {
return *entry; return slot;
} }
// Collision: double hash. // Collision: double hash.
DoubleHash dh = hash2(aKeyHash); DoubleHash dh = hash2(aKeyHash);
while (true) { while (true) {
entry->setCollision(); slot.setCollision();
h1 = applyDoubleHash(h1, dh); h1 = applyDoubleHash(h1, dh);
entry = &mTable[h1]; slot = slotForIndex(h1);
if (!entry->isLive()) { if (!slot.isLive()) {
return *entry; return slot;
} }
} }
} }
@ -1696,16 +1772,15 @@ class HashTable : private AllocPolicy {
mTable = newTable; mTable = newTable;
// Copy only live entries, leaving removed ones behind. // Copy only live entries, leaving removed ones behind.
Entry* end = oldTable + oldCapacity; forEachSlot(oldTable, oldCapacity, [&](Slot& slot) {
for (Entry* src = oldTable; src < end; ++src) { if (slot.isLive()) {
if (src->isLive()) { HashNumber hn = slot.getKeyHash();
HashNumber hn = src->getKeyHash(); findNonLiveSlot(hn).setLive(
findNonLiveEntry(hn).setLive( hn, std::move(const_cast<typename Entry::NonConstT&>(slot.get())));
hn, std::move(const_cast<typename Entry::NonConstT&>(src->get())));
} }
src->~Entry(); slot.clear();
} });
// All entries have been destroyed, no need to destroyTable. // All entries have been destroyed, no need to destroyTable.
this->free_(oldTable, oldCapacity); this->free_(oldTable, oldCapacity);
@ -1741,14 +1816,14 @@ class HashTable : private AllocPolicy {
} }
} }
void remove(Entry& aEntry) { void remove(Slot& aSlot) {
MOZ_ASSERT(mTable); MOZ_ASSERT(mTable);
if (aEntry.hasCollision()) { if (aSlot.hasCollision()) {
aEntry.removeLive(); aSlot.removeLive();
mRemovedCount++; mRemovedCount++;
} else { } else {
aEntry.clearLive(); aSlot.clearLive();
} }
mEntryCount--; mEntryCount--;
#ifdef DEBUG #ifdef DEBUG
@ -1756,6 +1831,11 @@ class HashTable : private AllocPolicy {
#endif #endif
} }
void remove(Entry& aEntry) {
Slot slot(&aEntry);
remove(slot);
}
void shrinkIfUnderloaded() { void shrinkIfUnderloaded() {
static_assert(sMaxCapacity <= UINT32_MAX / sMinAlphaNumerator, static_assert(sMaxCapacity <= UINT32_MAX / sMinAlphaNumerator,
"multiplication below could overflow"); "multiplication below could overflow");
@ -1776,30 +1856,28 @@ class HashTable : private AllocPolicy {
void rehashTableInPlace() { void rehashTableInPlace() {
mRemovedCount = 0; mRemovedCount = 0;
mGen++; mGen++;
for (uint32_t i = 0; i < capacity(); ++i) { forEachSlot(mTable, capacity(), [&](Slot& slot) { slot.unsetCollision(); });
mTable[i].unsetCollision();
}
for (uint32_t i = 0; i < capacity();) { for (uint32_t i = 0; i < capacity();) {
Entry* src = &mTable[i]; Slot src = slotForIndex(i);
if (!src->isLive() || src->hasCollision()) { if (!src.isLive() || src.hasCollision()) {
++i; ++i;
continue; continue;
} }
HashNumber keyHash = src->getKeyHash(); HashNumber keyHash = src.getKeyHash();
HashNumber h1 = hash1(keyHash); HashNumber h1 = hash1(keyHash);
DoubleHash dh = hash2(keyHash); DoubleHash dh = hash2(keyHash);
Entry* tgt = &mTable[h1]; Slot tgt = slotForIndex(h1);
while (true) { while (true) {
if (!tgt->hasCollision()) { if (!tgt.hasCollision()) {
src->swap(tgt); src.swap(tgt);
tgt->setCollision(); tgt.setCollision();
break; break;
} }
h1 = applyDoubleHash(h1, dh); h1 = applyDoubleHash(h1, dh);
tgt = &mTable[h1]; tgt = slotForIndex(h1);
} }
} }
@ -1820,15 +1898,14 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(mTable); MOZ_ASSERT(mTable);
HashNumber keyHash = prepareHash(aLookup); HashNumber keyHash = prepareHash(aLookup);
Entry* entry = &findNonLiveEntry(keyHash); Slot slot = findNonLiveSlot(keyHash);
MOZ_ASSERT(entry);
if (entry->isRemoved()) { if (slot.isRemoved()) {
mRemovedCount--; mRemovedCount--;
keyHash |= sCollisionBit; keyHash |= sCollisionBit;
} }
entry->setLive(keyHash, std::forward<Args>(aArgs)...); slot.setLive(keyHash, std::forward<Args>(aArgs)...);
mEntryCount++; mEntryCount++;
#ifdef DEBUG #ifdef DEBUG
mMutationCount++; mMutationCount++;
@ -1837,10 +1914,7 @@ class HashTable : private AllocPolicy {
public: public:
void clear() { void clear() {
Entry* end = mTable + capacity(); forEachSlot(mTable, capacity(), [&](Slot& slot) { slot.clear(); });
for (Entry* e = mTable; e < end; ++e) {
e->clear();
}
mRemovedCount = 0; mRemovedCount = 0;
mEntryCount = 0; mEntryCount = 0;
#ifdef DEBUG #ifdef DEBUG
@ -1971,9 +2045,9 @@ class HashTable : private AllocPolicy {
if (status == RehashFailed) { if (status == RehashFailed) {
return false; return false;
} }
aPtr.mEntry = &findNonLiveEntry(aPtr.mKeyHash); aPtr.mSlot = findNonLiveSlot(aPtr.mKeyHash);
} else if (aPtr.mEntry->isRemoved()) { } else if (aPtr.mSlot.isRemoved()) {
// Changing an entry from removed to live does not affect whether we are // Changing an entry from removed to live does not affect whether we are
// overloaded and can be handled separately. // overloaded and can be handled separately.
if (!this->checkSimulatedOOM()) { if (!this->checkSimulatedOOM()) {
@ -1983,7 +2057,7 @@ class HashTable : private AllocPolicy {
aPtr.mKeyHash |= sCollisionBit; aPtr.mKeyHash |= sCollisionBit;
} else { } else {
// Preserve the validity of |aPtr.mEntry|. // Preserve the validity of |aPtr.mSlot|.
RebuildStatus status = rehashIfOverloaded(); RebuildStatus status = rehashIfOverloaded();
if (status == RehashFailed) { if (status == RehashFailed) {
return false; return false;
@ -1992,11 +2066,11 @@ class HashTable : private AllocPolicy {
return false; return false;
} }
if (status == Rehashed) { if (status == Rehashed) {
aPtr.mEntry = &findNonLiveEntry(aPtr.mKeyHash); aPtr.mSlot = findNonLiveSlot(aPtr.mKeyHash);
} }
} }
aPtr.mEntry->setLive(aPtr.mKeyHash, std::forward<Args>(aArgs)...); aPtr.mSlot.setLive(aPtr.mKeyHash, std::forward<Args>(aArgs)...);
mEntryCount++; mEntryCount++;
#ifdef DEBUG #ifdef DEBUG
mMutationCount++; mMutationCount++;
@ -2049,14 +2123,14 @@ class HashTable : private AllocPolicy {
ReentrancyGuard g(*this); ReentrancyGuard g(*this);
// Check that aLookup has not been destroyed. // Check that aLookup has not been destroyed.
MOZ_ASSERT(prepareHash(aLookup) == aPtr.mKeyHash); MOZ_ASSERT(prepareHash(aLookup) == aPtr.mKeyHash);
aPtr.mEntry = &lookup<ForAdd>(aLookup, aPtr.mKeyHash); aPtr.mSlot = lookup<ForAdd>(aLookup, aPtr.mKeyHash);
if (aPtr.found()) { if (aPtr.found()) {
return true; return true;
} }
} else { } else {
// Clear aPtr so it's invalid; add() will allocate storage and redo the // Clear aPtr so it's invalid; add() will allocate storage and redo the
// lookup. // lookup.
aPtr.mEntry = nullptr; aPtr.mSlot = Slot(nullptr);
} }
return add(aPtr, std::forward<Args>(aArgs)...); return add(aPtr, std::forward<Args>(aArgs)...);
} }
@ -2066,7 +2140,7 @@ class HashTable : private AllocPolicy {
ReentrancyGuard g(*this); ReentrancyGuard g(*this);
MOZ_ASSERT(aPtr.found()); MOZ_ASSERT(aPtr.found());
MOZ_ASSERT(aPtr.mGeneration == generation()); MOZ_ASSERT(aPtr.mGeneration == generation());
remove(*aPtr.mEntry); remove(aPtr.mSlot);
shrinkIfUnderloaded(); shrinkIfUnderloaded();
} }
@ -2077,7 +2151,7 @@ class HashTable : private AllocPolicy {
MOZ_ASSERT(aPtr.mGeneration == generation()); MOZ_ASSERT(aPtr.mGeneration == generation());
typename HashTableEntry<T>::NonConstT t(std::move(*aPtr)); typename HashTableEntry<T>::NonConstT t(std::move(*aPtr));
HashPolicy::setKey(t, const_cast<Key&>(aKey)); HashPolicy::setKey(t, const_cast<Key&>(aKey));
remove(*aPtr.mEntry); remove(aPtr.mSlot);
putNewInfallibleInternal(aLookup, std::move(t)); putNewInfallibleInternal(aLookup, std::move(t));
} }