Skip to content

Commit 6543a55

Browse files
committed
Bug 1681469 - Allow nsBaseHashtable to work with a non-default-constructible/non-movable DataType; r=nika
nsBaseHashtable now supports non-default-constructible DataType and UserDataType, however not all methods can be instantiated. All methods which can't be instantiated with non-default-constructible DataType or UserDataType are now described as such in method definitions. The public API of PLDHashTable and nsBaseHashtable/nsDataHashtable was changed: - A new method PLDHashTable::WithEntryHandle has been added. It allows to use a custom function for entry initialization (instead of the global hook). - A new method nsBaseHashtable::MaybeGet has been added. - A new overload nsBaseHashtable::Remove has been added. - The nsDataHashtable::GetAndRemove method has been pulled up to nsBaseHashtable. In addition, the following implementation details have changed: PLDHashTable: - The code from the Add method has been split into MakeEntryHandle and a helper object called EntryHandle. The Add method is now implemented on top of that. nsTHashtable: - A new (non-public) API for WithEntryHandle has been added. - The InitEntry hook is no longer used. Instead of using the hook, PutEntry methods now use nsTHashtable::WithEntryHandle instead of PLDHashTable::Add. This change allows to do custom initialization in derived classes. nsBaseHashtable: - A new (non-public) API for WithEntryHandle has been added. - Put methods no longer use nsTHashtable::PutEntry, they now use the new method nsBaseHashtable::WithEntryHandle. Differential Revision: https://phabricator.services.mozilla.com/D99428
1 parent 3cc69b8 commit 6543a55

File tree

7 files changed

+1248
-150
lines changed

7 files changed

+1248
-150
lines changed

xpcom/ds/PLDHashTable.cpp

Lines changed: 134 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -500,86 +500,24 @@ PLDHashEntryHdr* PLDHashTable::Search(const void* aKey) const {
500500
}
501501

502502
PLDHashEntryHdr* PLDHashTable::Add(const void* aKey,
503-
const mozilla::fallible_t&) {
504-
#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
505-
AutoWriteOp op(mChecker);
506-
#endif
507-
508-
// Allocate the entry storage if it hasn't already been allocated.
509-
if (!mEntryStore.IsAllocated()) {
510-
uint32_t nbytes;
511-
// We already checked this in the constructor, so it must still be true.
512-
MOZ_RELEASE_ASSERT(
513-
SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes));
514-
mEntryStore.Set((char*)calloc(1, nbytes), &mGeneration);
515-
if (!mEntryStore.IsAllocated()) {
516-
return nullptr;
517-
}
518-
}
519-
520-
// If alpha is >= .75, grow or compress the table. If aKey is already in the
521-
// table, we may grow once more than necessary, but only if we are on the
522-
// edge of being overloaded.
523-
uint32_t capacity = Capacity();
524-
if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) {
525-
// Compress if a quarter or more of all entries are removed.
526-
int deltaLog2;
527-
if (mRemovedCount >= capacity >> 2) {
528-
deltaLog2 = 0;
529-
} else {
530-
deltaLog2 = 1;
531-
}
532-
533-
// Grow or compress the table. If ChangeTable() fails, allow overloading up
534-
// to the secondary max. Once we hit the secondary max, return null.
535-
if (!ChangeTable(deltaLog2) &&
536-
mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) {
537-
return nullptr;
538-
}
503+
const mozilla::fallible_t& aFallible) {
504+
auto maybeEntryHandle = MakeEntryHandle(aKey, aFallible);
505+
if (!maybeEntryHandle) {
506+
return nullptr;
539507
}
540-
541-
// Look for entry after possibly growing, so we don't have to add it,
542-
// then skip it while growing the table and re-add it after.
543-
PLDHashNumber keyHash = ComputeKeyHash(aKey);
544-
Slot slot = SearchTable<ForAdd>(
545-
aKey, keyHash, [&](Slot& found) -> Slot { return found; },
546-
[&]() -> Slot {
547-
MOZ_CRASH("Nope");
548-
return Slot(nullptr, nullptr);
549-
});
550-
if (!slot.IsLive()) {
551-
// Initialize the slot, indicating that it's no longer free.
552-
if (slot.IsRemoved()) {
553-
mRemovedCount--;
554-
keyHash |= kCollisionFlag;
555-
}
508+
return maybeEntryHandle->OrInsert([&aKey, this](PLDHashEntryHdr* entry) {
556509
if (mOps->initEntry) {
557-
mOps->initEntry(slot.ToEntry(), aKey);
510+
mOps->initEntry(entry, aKey);
558511
}
559-
slot.SetKeyHash(keyHash);
560-
mEntryCount++;
561-
}
562-
563-
return slot.ToEntry();
512+
});
564513
}
565514

566515
PLDHashEntryHdr* PLDHashTable::Add(const void* aKey) {
567-
PLDHashEntryHdr* entry = Add(aKey, fallible);
568-
if (!entry) {
569-
if (!mEntryStore.IsAllocated()) {
570-
// We OOM'd while allocating the initial entry storage.
571-
uint32_t nbytes;
572-
(void)SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes);
573-
NS_ABORT_OOM(nbytes);
574-
} else {
575-
// We failed to resize the existing entry storage, either due to OOM or
576-
// because we exceeded the maximum table capacity or size; report it as
577-
// an OOM. The multiplication by 2 gets us the size we tried to allocate,
578-
// which is double the current size.
579-
NS_ABORT_OOM(2 * EntrySize() * EntryCount());
516+
return MakeEntryHandle(aKey).OrInsert([&aKey, this](PLDHashEntryHdr* entry) {
517+
if (mOps->initEntry) {
518+
mOps->initEntry(entry, aKey);
580519
}
581-
}
582-
return entry;
520+
});
583521
}
584522

585523
void PLDHashTable::Remove(const void* aKey) {
@@ -671,6 +609,129 @@ size_t PLDHashTable::ShallowSizeOfIncludingThis(
671609
return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
672610
}
673611

612+
mozilla::Maybe<PLDHashTable::EntryHandle> PLDHashTable::MakeEntryHandle(
613+
const void* aKey, const mozilla::fallible_t&) {
614+
#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
615+
mChecker.StartWriteOp();
616+
auto endWriteOp = MakeScopeExit([&] { mChecker.EndWriteOp(); });
617+
#endif
618+
619+
// Allocate the entry storage if it hasn't already been allocated.
620+
if (!mEntryStore.IsAllocated()) {
621+
uint32_t nbytes;
622+
// We already checked this in the constructor, so it must still be true.
623+
MOZ_RELEASE_ASSERT(
624+
SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes));
625+
mEntryStore.Set((char*)calloc(1, nbytes), &mGeneration);
626+
if (!mEntryStore.IsAllocated()) {
627+
return Nothing();
628+
}
629+
}
630+
631+
// If alpha is >= .75, grow or compress the table. If aKey is already in the
632+
// table, we may grow once more than necessary, but only if we are on the
633+
// edge of being overloaded.
634+
uint32_t capacity = Capacity();
635+
if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) {
636+
// Compress if a quarter or more of all entries are removed.
637+
int deltaLog2 = 1;
638+
if (mRemovedCount >= capacity >> 2) {
639+
deltaLog2 = 0;
640+
}
641+
642+
// Grow or compress the table. If ChangeTable() fails, allow overloading up
643+
// to the secondary max. Once we hit the secondary max, return null.
644+
if (!ChangeTable(deltaLog2) &&
645+
mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) {
646+
return Nothing();
647+
}
648+
}
649+
650+
// Look for entry after possibly growing, so we don't have to add it,
651+
// then skip it while growing the table and re-add it after.
652+
PLDHashNumber keyHash = ComputeKeyHash(aKey);
653+
Slot slot = SearchTable<ForAdd>(
654+
aKey, keyHash, [](Slot& found) -> Slot { return found; },
655+
[]() -> Slot {
656+
MOZ_CRASH("Nope");
657+
return Slot(nullptr, nullptr);
658+
});
659+
660+
// The `EntryHandle` will handle ending the write op when it is destroyed.
661+
#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
662+
endWriteOp.release();
663+
#endif
664+
665+
return Some(EntryHandle{this, keyHash, slot});
666+
}
667+
668+
PLDHashTable::EntryHandle PLDHashTable::MakeEntryHandle(const void* aKey) {
669+
auto res = MakeEntryHandle(aKey, fallible);
670+
if (!res) {
671+
if (!mEntryStore.IsAllocated()) {
672+
// We OOM'd while allocating the initial entry storage.
673+
uint32_t nbytes;
674+
(void)SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes);
675+
NS_ABORT_OOM(nbytes);
676+
} else {
677+
// We failed to resize the existing entry storage, either due to OOM or
678+
// because we exceeded the maximum table capacity or size; report it as
679+
// an OOM. The multiplication by 2 gets us the size we tried to allocate,
680+
// which is double the current size.
681+
NS_ABORT_OOM(2 * EntrySize() * EntryCount());
682+
}
683+
}
684+
return res.extract();
685+
}
686+
687+
PLDHashTable::EntryHandle::EntryHandle(PLDHashTable* aTable,
688+
PLDHashNumber aKeyHash, Slot aSlot)
689+
: mTable(aTable), mKeyHash(aKeyHash), mSlot(aSlot) {}
690+
691+
PLDHashTable::EntryHandle::EntryHandle(EntryHandle&& aOther) noexcept
692+
: mTable(std::exchange(aOther.mTable, nullptr)),
693+
mKeyHash(aOther.mKeyHash),
694+
mSlot(aOther.mSlot) {}
695+
696+
PLDHashTable::EntryHandle::~EntryHandle() {
697+
if (!mTable) {
698+
return;
699+
}
700+
701+
// If our slot is empty when this `EntryHandle` is destroyed, we may want to
702+
// resize our table, as we just removed an entry.
703+
if (!HasEntry()) {
704+
mTable->ShrinkIfAppropriate();
705+
}
706+
#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED
707+
mTable->mChecker.EndWriteOp();
708+
#endif
709+
}
710+
711+
void PLDHashTable::EntryHandle::Remove() {
712+
MOZ_ASSERT(HasEntry());
713+
714+
mTable->RawRemove(mSlot);
715+
}
716+
717+
void PLDHashTable::EntryHandle::OrRemove() {
718+
if (HasEntry()) {
719+
Remove();
720+
}
721+
}
722+
723+
void PLDHashTable::EntryHandle::OccupySlot() {
724+
MOZ_ASSERT(!HasEntry());
725+
726+
PLDHashNumber keyHash = mKeyHash;
727+
if (mSlot.IsRemoved()) {
728+
mTable->mRemovedCount--;
729+
keyHash |= kCollisionFlag;
730+
}
731+
mSlot.SetKeyHash(keyHash);
732+
mTable->mEntryCount++;
733+
}
734+
674735
PLDHashTable::Iterator::Iterator(Iterator&& aOther)
675736
: mTable(aOther.mTable),
676737
mCurrent(aOther.mCurrent),

xpcom/ds/PLDHashTable.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "mozilla/Assertions.h"
1616
#include "mozilla/Atomics.h"
1717
#include "mozilla/HashFunctions.h"
18+
#include "mozilla/Maybe.h"
1819
#include "mozilla/MemoryReporting.h"
1920
#include "mozilla/fallible.h"
2021
#include "nscore.h"
@@ -528,6 +529,74 @@ class PLDHashTable {
528529
static PLDHashNumber HashStringKey(const void* aKey);
529530
static bool MatchStringKey(const PLDHashEntryHdr* aEntry, const void* aKey);
530531

532+
class EntryHandle {
533+
public:
534+
EntryHandle(EntryHandle&& aOther) noexcept;
535+
~EntryHandle();
536+
537+
EntryHandle(const EntryHandle&) = delete;
538+
EntryHandle& operator=(const EntryHandle&) = delete;
539+
EntryHandle& operator=(EntryHandle&& aOther) = delete;
540+
541+
// Is this slot currently occupied?
542+
bool HasEntry() const { return mSlot.IsLive(); }
543+
544+
explicit operator bool() const { return HasEntry(); }
545+
546+
// Get the entry stored in this slot. May not be called unless the slot is
547+
// currently occupied.
548+
PLDHashEntryHdr* Entry() {
549+
MOZ_ASSERT(HasEntry());
550+
return mSlot.ToEntry();
551+
}
552+
553+
template <class F>
554+
void Insert(F&& aInitEntry) {
555+
MOZ_ASSERT(!HasEntry());
556+
OccupySlot();
557+
std::forward<F>(aInitEntry)(Entry());
558+
}
559+
560+
// If the slot is currently vacant, the slot is occupied and `initEntry` is
561+
// invoked to initialize the entry. Returns the entry stored in now-occupied
562+
// slot.
563+
template <class F>
564+
PLDHashEntryHdr* OrInsert(F&& aInitEntry) {
565+
if (!HasEntry()) {
566+
Insert(std::forward<F>(aInitEntry));
567+
}
568+
return Entry();
569+
}
570+
571+
void Remove();
572+
573+
void OrRemove();
574+
575+
private:
576+
friend class PLDHashTable;
577+
578+
EntryHandle(PLDHashTable* aTable, PLDHashNumber aKeyHash, Slot aSlot);
579+
580+
void OccupySlot();
581+
582+
PLDHashTable* mTable;
583+
PLDHashNumber mKeyHash;
584+
Slot mSlot;
585+
};
586+
587+
template <class F>
588+
auto WithEntryHandle(const void* aKey, F&& aFunc)
589+
-> std::invoke_result_t<F, EntryHandle&&> {
590+
return std::forward<F>(aFunc)(MakeEntryHandle(aKey));
591+
}
592+
593+
template <class F>
594+
auto WithEntryHandle(const void* aKey, const mozilla::fallible_t& aFallible,
595+
F&& aFunc)
596+
-> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> {
597+
return std::forward<F>(aFunc)(MakeEntryHandle(aKey, aFallible));
598+
}
599+
531600
// This is an iterator for PLDHashtable. Assertions will detect some, but not
532601
// all, mid-iteration table modifications that might invalidate (e.g.
533602
// reallocate) the entry storage.
@@ -650,6 +719,11 @@ class PLDHashTable {
650719
void RawRemove(Slot& aSlot);
651720
void ShrinkIfAppropriate();
652721

722+
mozilla::Maybe<EntryHandle> MakeEntryHandle(const void* aKey,
723+
const mozilla::fallible_t&);
724+
725+
EntryHandle MakeEntryHandle(const void* aKey);
726+
653727
PLDHashTable(const PLDHashTable& aOther) = delete;
654728
PLDHashTable& operator=(const PLDHashTable& aOther) = delete;
655729
};

0 commit comments

Comments
 (0)