1313#include " llvm/Support/Allocator.h"
1414#include " llvm/Support/Debug.h"
1515#include " llvm/Support/ThreadSafeAllocator.h"
16+ #include " llvm/Support/TrailingObjects.h"
1617#include " llvm/Support/raw_ostream.h"
1718#include < memory>
1819
@@ -54,9 +55,16 @@ static_assert(sizeof(TrieContent) ==
5455 ThreadSafeTrieRawHashMapBase::TrieContentBaseSize,
5556 " Check header assumption!" );
5657
57- class TrieSubtrie final : public TrieNode {
58+ class TrieSubtrie final
59+ : public TrieNode,
60+ private TrailingObjects<TrieSubtrie, LazyAtomicPointer<TrieNode>> {
5861public:
59- TrieNode *get (size_t I) const { return Slots[I].load (); }
62+ using Slot = LazyAtomicPointer<TrieNode>;
63+
64+ Slot &get (size_t I) { return getTrailingObjects<Slot>()[I]; }
65+ TrieNode *load (size_t I) { return get (I).load (); }
66+
67+ unsigned size () const { return Size; }
6068
6169 TrieSubtrie *
6270 sink (size_t I, TrieContent &Content, size_t NumSubtrieBits, size_t NewI,
@@ -68,6 +76,12 @@ class TrieSubtrie final : public TrieNode {
6876
6977 static bool classof (const TrieNode *TN) { return TN->IsSubtrie ; }
7078
79+ static constexpr size_t sizeToAlloc (unsigned NumBits) {
80+ assert (NumBits < 20 && " Tries should have fewer than ~1M slots" );
81+ size_t Count = 1u << NumBits;
82+ return totalSizeToAlloc<LazyAtomicPointer<TrieNode>>(Count);
83+ }
84+
7185private:
7286 // FIXME: Use a bitset to speed up access:
7387 //
@@ -91,38 +105,28 @@ class TrieSubtrie final : public TrieNode {
91105 // For debugging.
92106 unsigned StartBit = 0 ;
93107 unsigned NumBits = 0 ;
108+ unsigned Size = 0 ;
94109 friend class llvm ::ThreadSafeTrieRawHashMapBase;
110+ friend class TrailingObjects ;
95111
96112public:
97113 // / Linked list for ownership of tries. The pointer is owned by TrieSubtrie.
98114 std::atomic<TrieSubtrie *> Next;
99-
100- // / The (co-allocated) slots of the subtrie.
101- MutableArrayRef<LazyAtomicPointer<TrieNode>> Slots;
102115};
103116} // end namespace
104117
105- // Compute the trailing object size in the trie node. This is the size of \c
106- // Slots in TrieNodes that pointing to the children.
107- static size_t getTrieTailSize (size_t NumBits) {
108- return sizeof (TrieNode *) * (1u << NumBits);
109- }
110-
111118std::unique_ptr<TrieSubtrie> TrieSubtrie::create (size_t StartBit,
112119 size_t NumBits) {
113- size_t Size = sizeof (TrieSubtrie) + getTrieTailSize (NumBits);
114- void *Memory = ::malloc (Size);
120+ void *Memory = ::malloc (sizeToAlloc (NumBits));
115121 TrieSubtrie *S = ::new (Memory) TrieSubtrie (StartBit, NumBits);
116122 return std::unique_ptr<TrieSubtrie>(S);
117123}
118124
119125TrieSubtrie::TrieSubtrie (size_t StartBit, size_t NumBits)
120- : TrieNode(true ), StartBit(StartBit), NumBits(NumBits), Next(nullptr ),
121- Slots(reinterpret_cast <LazyAtomicPointer<TrieNode> *>(
122- reinterpret_cast <char *>(this ) + sizeof(TrieSubtrie)),
123- (1u << NumBits)) {
124- for (auto *I = Slots.begin (), *E = Slots.end (); I != E; ++I)
125- new (I) LazyAtomicPointer<TrieNode>(nullptr );
126+ : TrieNode(true ), StartBit(StartBit), NumBits(NumBits), Size(1u << NumBits),
127+ Next(nullptr ) {
128+ for (unsigned I = 0 ; I < Size; ++I)
129+ new (&get (I)) Slot (nullptr );
126130
127131 static_assert (
128132 std::is_trivially_destructible<LazyAtomicPointer<TrieNode>>::value,
@@ -140,24 +144,27 @@ TrieSubtrie *TrieSubtrie::sink(
140144 assert (NumSubtrieBits > 0 );
141145 std::unique_ptr<TrieSubtrie> S = create (StartBit + NumBits, NumSubtrieBits);
142146
143- assert (NewI < S-> Slots . size () );
144- S->Slots [ NewI] .store (&Content);
147+ assert (NewI < Size );
148+ S->get ( NewI) .store (&Content);
145149
146150 // Using compare_exchange to atomically add back the new sub-trie to the trie
147151 // in the place of the exsiting object.
148152 TrieNode *ExistingNode = &Content;
149- assert (I < Slots. size () );
150- if (Slots[I] .compare_exchange_strong (ExistingNode, S.get ()))
153+ assert (I < Size );
154+ if (get (I) .compare_exchange_strong (ExistingNode, S.get ()))
151155 return Saver (std::move (S));
152156
153157 // Another thread created a subtrie already. Return it and let "S" be
154158 // destructed.
155159 return cast<TrieSubtrie>(ExistingNode);
156160}
157161
158- struct ThreadSafeTrieRawHashMapBase ::ImplType {
162+ class ThreadSafeTrieRawHashMapBase ::ImplType final
163+ : private TrailingObjects<ThreadSafeTrieRawHashMapBase::ImplType,
164+ TrieSubtrie> {
165+ public:
159166 static std::unique_ptr<ImplType> create (size_t StartBit, size_t NumBits) {
160- size_t Size = sizeof (ImplType) + getTrieTailSize (NumBits);
167+ size_t Size = sizeof (ImplType) + TrieSubtrie::sizeToAlloc (NumBits);
161168 void *Memory = ::malloc (Size);
162169 ImplType *Impl = ::new (Memory) ImplType (StartBit, NumBits);
163170 return std::unique_ptr<ImplType>(Impl);
@@ -174,24 +181,30 @@ struct ThreadSafeTrieRawHashMapBase::ImplType {
174181 // Root.Next. This works by repeatedly setting S->Next to a candidate value
175182 // of Root.Next (initially nullptr), then setting Root.Next to S once the
176183 // candidate matches reality.
177- while (!Root. Next .compare_exchange_weak (CurrentHead, S.get ()))
184+ while (!getRoot ()-> Next .compare_exchange_weak (CurrentHead, S.get ()))
178185 S->Next .exchange (CurrentHead);
179186
180187 // Ownership transferred to subtrie successfully. Release the unique_ptr.
181188 return S.release ();
182189 }
183190
191+ // Get the root which is the trailing object.
192+ TrieSubtrie *getRoot () { return getTrailingObjects<TrieSubtrie>(); }
193+
184194 static void *operator new (size_t Size) { return ::malloc (Size); }
185195 void operator delete (void *Ptr) { ::free (Ptr); }
186196
187197 // / FIXME: This should take a function that allocates and constructs the
188198 // / content lazily (taking the hash as a separate parameter), in case of
189199 // / collision.
190200 ThreadSafeAllocator<BumpPtrAllocator> ContentAlloc;
191- TrieSubtrie Root; // Must be last! Tail-allocated.
192201
193202private:
194- ImplType (size_t StartBit, size_t NumBits) : Root(StartBit, NumBits) {}
203+ friend class TrailingObjects ;
204+
205+ ImplType (size_t StartBit, size_t NumBits) {
206+ ::new (getRoot ()) TrieSubtrie (StartBit, NumBits);
207+ }
195208};
196209
197210ThreadSafeTrieRawHashMapBase::ImplType &
@@ -221,7 +234,7 @@ ThreadSafeTrieRawHashMapBase::find(ArrayRef<uint8_t> Hash) const {
221234 if (!Impl)
222235 return PointerBase ();
223236
224- TrieSubtrie *S = & Impl->Root ;
237+ TrieSubtrie *S = Impl->getRoot () ;
225238 IndexGenerator IndexGen{NumRootBits, NumSubtrieBits, Hash};
226239 size_t Index = IndexGen.next ();
227240 while (Index != IndexGen.end ()) {
@@ -249,7 +262,7 @@ ThreadSafeTrieRawHashMapBase::PointerBase ThreadSafeTrieRawHashMapBase::insert(
249262 assert (!Hash.empty () && " Uninitialized hash" );
250263
251264 ImplType &Impl = getOrCreateImpl ();
252- TrieSubtrie *S = & Impl.Root ;
265+ TrieSubtrie *S = Impl.getRoot () ;
253266 IndexGenerator IndexGen{NumRootBits, NumSubtrieBits, Hash};
254267 size_t Index;
255268 if (Hint.isHint ()) {
@@ -263,7 +276,7 @@ ThreadSafeTrieRawHashMapBase::PointerBase ThreadSafeTrieRawHashMapBase::insert(
263276 // Load the node from the slot, allocating and calling the constructor if
264277 // the slot is empty.
265278 bool Generated = false ;
266- TrieNode &Existing = S->Slots [ Index] .loadOrGenerate ([&]() {
279+ TrieNode &Existing = S->get ( Index) .loadOrGenerate ([&]() {
267280 Generated = true ;
268281
269282 // Construct the value itself at the tail.
@@ -321,7 +334,15 @@ ThreadSafeTrieRawHashMapBase::ThreadSafeTrieRawHashMapBase(
321334 ContentOffset(ContentOffset),
322335 NumRootBits(NumRootBits ? *NumRootBits : DefaultNumRootBits),
323336 NumSubtrieBits(NumSubtrieBits ? *NumSubtrieBits : DefaultNumSubtrieBits),
324- ImplPtr(nullptr ) {}
337+ ImplPtr(nullptr ) {
338+ // Assertion checks for reasonable configuration. The settings below are not
339+ // hard limits on most platforms, but a reasonable configuration should fall
340+ // within those limits.
341+ assert ((!NumRootBits || *NumRootBits < 20 ) &&
342+ " Root should have fewer than ~1M slots" );
343+ assert ((!NumSubtrieBits || *NumSubtrieBits < 10 ) &&
344+ " Subtries should have fewer than ~1K slots" );
345+ }
325346
326347ThreadSafeTrieRawHashMapBase::ThreadSafeTrieRawHashMapBase (
327348 ThreadSafeTrieRawHashMapBase &&RHS)
@@ -349,14 +370,14 @@ void ThreadSafeTrieRawHashMapBase::destroyImpl(
349370 // FIXME: Once we have bitsets (see FIXME in TrieSubtrie class), use them
350371 // facilitate sparse iteration here.
351372 if (Destructor)
352- for (TrieSubtrie *Trie = & Impl->Root ; Trie; Trie = Trie->Next .load ())
353- for (auto &Slot : Trie->Slots )
354- if (auto *Content = dyn_cast_or_null<TrieContent>(Slot. load ()))
373+ for (TrieSubtrie *Trie = Impl->getRoot () ; Trie; Trie = Trie->Next .load ())
374+ for (unsigned I = 0 ; I < Trie->size (); ++I )
375+ if (auto *Content = dyn_cast_or_null<TrieContent>(Trie-> load (I )))
355376 Destructor (Content->getValuePointer ());
356377
357378 // Destroy the subtries. Incidentally, this destroys them in the reverse order
358379 // of saving.
359- TrieSubtrie *Trie = Impl->Root . Next ;
380+ TrieSubtrie *Trie = Impl->getRoot ()-> Next ;
360381 while (Trie) {
361382 TrieSubtrie *Next = Trie->Next .exchange (nullptr );
362383 delete Trie;
@@ -369,7 +390,7 @@ ThreadSafeTrieRawHashMapBase::getRoot() const {
369390 ImplType *Impl = ImplPtr.load ();
370391 if (!Impl)
371392 return PointerBase ();
372- return PointerBase (& Impl->Root );
393+ return PointerBase (Impl->getRoot () );
373394}
374395
375396unsigned ThreadSafeTrieRawHashMapBase::getStartBit (
@@ -401,8 +422,8 @@ unsigned ThreadSafeTrieRawHashMapBase::getNumSlotUsed(
401422 if (!S)
402423 return 0 ;
403424 unsigned Num = 0 ;
404- for (unsigned I = 0 , E = S->Slots . size (); I < E; ++I)
405- if (auto *E = S->Slots [I]. load ())
425+ for (unsigned I = 0 , E = S->size (); I < E; ++I)
426+ if (auto *E = S->load (I ))
406427 ++Num;
407428 return Num;
408429}
@@ -424,8 +445,8 @@ std::string ThreadSafeTrieRawHashMapBase::getTriePrefixAsString(
424445 while (Current) {
425446 TrieSubtrie *Next = nullptr ;
426447 // Find first used slot in the trie.
427- for (unsigned I = 0 , E = Current->Slots . size (); I < E; ++I) {
428- auto *S = Current->get (I);
448+ for (unsigned I = 0 , E = Current->size (); I < E; ++I) {
449+ auto *S = Current->load (I);
429450 if (!S)
430451 continue ;
431452
@@ -473,7 +494,7 @@ unsigned ThreadSafeTrieRawHashMapBase::getNumTries() const {
473494 if (!Impl)
474495 return 0 ;
475496 unsigned Num = 0 ;
476- for (TrieSubtrie *Trie = & Impl->Root ; Trie; Trie = Trie->Next .load ())
497+ for (TrieSubtrie *Trie = Impl->getRoot () ; Trie; Trie = Trie->Next .load ())
477498 ++Num;
478499 return Num;
479500}
0 commit comments