1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef ds_InlineTable_h
8 #define ds_InlineTable_h
9 
10 #include "mozilla/Move.h"
11 
12 #include "jsalloc.h"
13 
14 #include "js/HashTable.h"
15 
16 namespace js {
17 
18 namespace detail {
19 
20 template <typename InlineEntry,
21           typename Entry,
22           typename Table,
23           typename HashPolicy,
24           typename AllocPolicy,
25           size_t InlineEntries>
26 class InlineTable
27 {
28   private:
29     using TablePtr    = typename Table::Ptr;
30     using TableAddPtr = typename Table::AddPtr;
31     using TableRange  = typename Table::Range;
32     using Lookup      = typename HashPolicy::Lookup;
33 
34     size_t      inlNext_;
35     size_t      inlCount_;
36     InlineEntry inl_[InlineEntries];
37     Table       table_;
38 
39 #ifdef DEBUG
40     template <typename Key>
keyNonZero(const Key & key)41     static bool keyNonZero(const Key& key) {
42         // Zero as tombstone means zero keys are invalid.
43         return !!key;
44     }
45 #endif
46 
inlineStart()47     InlineEntry* inlineStart() {
48         MOZ_ASSERT(!usingTable());
49         return inl_;
50     }
51 
inlineStart()52     const InlineEntry* inlineStart() const {
53         MOZ_ASSERT(!usingTable());
54         return inl_;
55     }
56 
inlineEnd()57     InlineEntry* inlineEnd() {
58         MOZ_ASSERT(!usingTable());
59         return inl_ + inlNext_;
60     }
61 
inlineEnd()62     const InlineEntry* inlineEnd() const {
63         MOZ_ASSERT(!usingTable());
64         return inl_ + inlNext_;
65     }
66 
usingTable()67     bool usingTable() const {
68         return inlNext_ > InlineEntries;
69     }
70 
switchToTable()71     MOZ_MUST_USE bool switchToTable() {
72         MOZ_ASSERT(inlNext_ == InlineEntries);
73 
74         if (table_.initialized()) {
75             table_.clear();
76         } else {
77             if (!table_.init(count()))
78                 return false;
79             MOZ_ASSERT(table_.initialized());
80         }
81 
82         InlineEntry* end = inlineEnd();
83         for (InlineEntry* it = inlineStart(); it != end; ++it) {
84             if (it->key && !it->moveTo(table_))
85                 return false;
86         }
87 
88         inlNext_ = InlineEntries + 1;
89         MOZ_ASSERT(table_.count() == inlCount_);
90         MOZ_ASSERT(usingTable());
91         return true;
92     }
93 
94     MOZ_NEVER_INLINE
switchAndAdd(const InlineEntry & entry)95     MOZ_MUST_USE bool switchAndAdd(const InlineEntry& entry) {
96         if (!switchToTable())
97             return false;
98 
99         return entry.putNew(table_);
100     }
101 
102   public:
103     static const size_t SizeOfInlineEntries = sizeof(InlineEntry) * InlineEntries;
104 
105     explicit InlineTable(AllocPolicy a = AllocPolicy())
106       : inlNext_(0),
107         inlCount_(0),
108         table_(a)
109     { }
110 
111     class Ptr
112     {
113         friend class InlineTable;
114 
115       protected:
116         Entry        entry_;
117         TablePtr     tablePtr_;
118         InlineEntry* inlPtr_;
119         bool         isInlinePtr_;
120 
Ptr(TablePtr p)121         explicit Ptr(TablePtr p)
122           : entry_(p.found() ? &*p : nullptr),
123             tablePtr_(p),
124             isInlinePtr_(false)
125         { }
126 
Ptr(InlineEntry * inlineEntry)127         explicit Ptr(InlineEntry* inlineEntry)
128           : entry_(inlineEntry),
129             inlPtr_(inlineEntry),
130             isInlinePtr_(true)
131         { }
132 
133         void operator==(const Ptr& other);
134 
135       public:
136         // Leaves Ptr uninitialized.
Ptr()137         Ptr() {
138 #ifdef DEBUG
139             inlPtr_ = (InlineEntry*) 0xbad;
140             isInlinePtr_ = true;
141 #endif
142         }
143 
144         // Default copy constructor works for this structure.
145 
found()146         bool found() const {
147             return isInlinePtr_ ? bool(inlPtr_) : tablePtr_.found();
148         }
149 
150         explicit operator bool() const {
151             return found();
152         }
153 
154         bool operator==(const Ptr& other) const {
155             MOZ_ASSERT(found() && other.found());
156             if (isInlinePtr_ != other.isInlinePtr_)
157                 return false;
158             if (isInlinePtr_)
159                 return inlPtr_ == other.inlPtr_;
160             return tablePtr_ == other.tablePtr_;
161         }
162 
163         bool operator!=(const Ptr& other) const {
164             return !(*this == other);
165         }
166 
167         Entry& operator*() {
168             MOZ_ASSERT(found());
169             return entry_;
170         }
171 
172         Entry* operator->() {
173             MOZ_ASSERT(found());
174             return &entry_;
175         }
176     };
177 
178     class AddPtr
179     {
180         friend class InlineTable;
181 
182       protected:
183         Entry        entry_;
184         TableAddPtr  tableAddPtr_;
185         InlineEntry* inlAddPtr_;
186         bool         isInlinePtr_;
187         // Indicates whether inlAddPtr is a found result or an add pointer.
188         bool         inlPtrFound_;
189 
AddPtr(InlineEntry * ptr,bool found)190         AddPtr(InlineEntry* ptr, bool found)
191           : entry_(ptr),
192             inlAddPtr_(ptr),
193             isInlinePtr_(true),
194             inlPtrFound_(found)
195         {}
196 
AddPtr(const TableAddPtr & p)197         explicit AddPtr(const TableAddPtr& p)
198           : entry_(p.found() ? &*p : nullptr),
199             tableAddPtr_(p),
200             isInlinePtr_(false)
201         { }
202 
203       public:
AddPtr()204         AddPtr() {}
205 
found()206         bool found() const {
207             return isInlinePtr_ ? inlPtrFound_ : tableAddPtr_.found();
208         }
209 
210         explicit operator bool() const {
211             return found();
212         }
213 
214         bool operator==(const AddPtr& other) const {
215             MOZ_ASSERT(found() && other.found());
216             if (isInlinePtr_ != other.isInlinePtr_)
217                 return false;
218             if (isInlinePtr_)
219                 return inlAddPtr_ == other.inlAddPtr_;
220             return tableAddPtr_ == other.tableAddPtr_;
221         }
222 
223         bool operator!=(const AddPtr& other) const {
224             return !(*this == other);
225         }
226 
227         Entry& operator*() {
228             MOZ_ASSERT(found());
229             return entry_;
230         }
231 
232         Entry* operator->() {
233             MOZ_ASSERT(found());
234             return &entry_;
235         }
236     };
237 
count()238     size_t count() const {
239         return usingTable() ? table_.count() : inlCount_;
240     }
241 
empty()242     bool empty() const {
243         return usingTable() ? table_.empty() : !inlCount_;
244     }
245 
clear()246     void clear() {
247         inlNext_ = 0;
248         inlCount_ = 0;
249     }
250 
251     MOZ_ALWAYS_INLINE
lookup(const Lookup & l)252     Ptr lookup(const Lookup& l) {
253         MOZ_ASSERT(keyNonZero(l));
254 
255         if (usingTable())
256             return Ptr(table_.lookup(l));
257 
258         InlineEntry* end = inlineEnd();
259         for (InlineEntry* it = inlineStart(); it != end; ++it) {
260             if (it->key && HashPolicy::match(it->key, l))
261                 return Ptr(it);
262         }
263 
264         return Ptr(nullptr);
265     }
266 
267     MOZ_ALWAYS_INLINE
lookupForAdd(const Lookup & l)268     AddPtr lookupForAdd(const Lookup& l) {
269         MOZ_ASSERT(keyNonZero(l));
270 
271         if (usingTable())
272             return AddPtr(table_.lookupForAdd(l));
273 
274         InlineEntry* end = inlineEnd();
275         for (InlineEntry* it = inlineStart(); it != end; ++it) {
276             if (it->key && HashPolicy::match(it->key, l))
277                 return AddPtr(it, true);
278         }
279 
280         // The add pointer that's returned here may indicate the limit entry of
281         // the linear space, in which case the |add| operation will initialize
282         // the table if necessary and add the entry there.
283         return AddPtr(inlineEnd(), false);
284     }
285 
286     template <typename KeyInput,
287               typename... Args>
288     MOZ_ALWAYS_INLINE
add(AddPtr & p,KeyInput && key,Args &&...args)289     MOZ_MUST_USE bool add(AddPtr& p, KeyInput&& key, Args&&... args) {
290         MOZ_ASSERT(!p);
291         MOZ_ASSERT(keyNonZero(key));
292 
293         if (p.isInlinePtr_) {
294             InlineEntry* addPtr = p.inlAddPtr_;
295             MOZ_ASSERT(addPtr == inlineEnd());
296 
297             // Switching to table mode before we add this pointer.
298             if (addPtr == inlineStart() + InlineEntries) {
299                 if (!switchToTable())
300                     return false;
301                 return table_.putNew(mozilla::Forward<KeyInput>(key),
302                                      mozilla::Forward<Args>(args)...);
303             }
304 
305             MOZ_ASSERT(!p.found());
306             MOZ_ASSERT(uintptr_t(inlineEnd()) == uintptr_t(p.inlAddPtr_));
307             addPtr->update(mozilla::Forward<KeyInput>(key),
308                            mozilla::Forward<Args>(args)...);
309             ++inlCount_;
310             ++inlNext_;
311             return true;
312         }
313 
314         return table_.add(p.tableAddPtr_,
315                           mozilla::Forward<KeyInput>(key),
316                           mozilla::Forward<Args>(args)...);
317     }
318 
remove(Ptr & p)319     void remove(Ptr& p) {
320         MOZ_ASSERT(p);
321         if (p.isInlinePtr_) {
322             MOZ_ASSERT(inlCount_ > 0);
323             MOZ_ASSERT(p.inlPtr_->key != nullptr);
324             p.inlPtr_->key = nullptr;
325             --inlCount_;
326             return;
327         }
328         MOZ_ASSERT(table_.initialized() && usingTable());
329         table_.remove(p.tablePtr_);
330     }
331 
remove(const Lookup & l)332     void remove(const Lookup& l) {
333         if (Ptr p = lookup(l))
334             remove(p);
335     }
336 
337     class Range
338     {
339         friend class InlineTable;
340 
341       protected:
342         TableRange   tableRange_;
343         InlineEntry* cur_;
344         InlineEntry* end_;
345         bool         isInline_;
346 
Range(TableRange r)347         explicit Range(TableRange r)
348           : cur_(nullptr),
349             end_(nullptr),
350             isInline_(false)
351         {
352             tableRange_ = r;
353             MOZ_ASSERT(!isInlineRange());
354         }
355 
Range(const InlineEntry * begin,const InlineEntry * end)356         Range(const InlineEntry* begin, const InlineEntry* end)
357           : cur_(const_cast<InlineEntry*>(begin)),
358             end_(const_cast<InlineEntry*>(end)),
359             isInline_(true)
360         {
361             advancePastNulls(cur_);
362             MOZ_ASSERT(isInlineRange());
363         }
364 
assertInlineRangeInvariants()365         bool assertInlineRangeInvariants() const {
366             MOZ_ASSERT(uintptr_t(cur_) <= uintptr_t(end_));
367             MOZ_ASSERT_IF(cur_ != end_, cur_->key != nullptr);
368             return true;
369         }
370 
isInlineRange()371         bool isInlineRange() const {
372             MOZ_ASSERT_IF(isInline_, assertInlineRangeInvariants());
373             return isInline_;
374         }
375 
advancePastNulls(InlineEntry * begin)376         void advancePastNulls(InlineEntry* begin) {
377             InlineEntry* newCur = begin;
378             while (newCur < end_ && nullptr == newCur->key)
379                 ++newCur;
380             MOZ_ASSERT(uintptr_t(newCur) <= uintptr_t(end_));
381             cur_ = newCur;
382         }
383 
bumpCurPtr()384         void bumpCurPtr() {
385             MOZ_ASSERT(isInlineRange());
386             advancePastNulls(cur_ + 1);
387         }
388 
389       public:
empty()390         bool empty() const {
391             return isInlineRange() ? cur_ == end_ : tableRange_.empty();
392         }
393 
front()394         Entry front() {
395             MOZ_ASSERT(!empty());
396             if (isInlineRange())
397                 return Entry(cur_);
398             return Entry(&tableRange_.front());
399         }
400 
popFront()401         void popFront() {
402             MOZ_ASSERT(!empty());
403             if (isInlineRange())
404                 bumpCurPtr();
405             else
406                 tableRange_.popFront();
407         }
408     };
409 
all()410     Range all() const {
411         return usingTable() ? Range(table_.all()) : Range(inlineStart(), inlineEnd());
412     }
413 };
414 
415 } // namespace detail
416 
417 // A map with InlineEntries number of entries kept inline in an array.
418 //
419 // The Key type must be zeroable as zeros are used as tombstone keys.
420 // The Value type must have a default constructor.
421 //
422 // The API is very much like HashMap's.
423 template <typename Key,
424           typename Value,
425           size_t InlineEntries,
426           typename HashPolicy = DefaultHasher<Key>,
427           typename AllocPolicy = TempAllocPolicy>
428 class InlineMap
429 {
430     using Map = HashMap<Key, Value, HashPolicy, AllocPolicy>;
431 
432     struct InlineEntry
433     {
434         Key   key;
435         Value value;
436 
437         template <typename KeyInput, typename ValueInput>
updateInlineEntry438         void update(KeyInput&& key, ValueInput&& value) {
439             this->key = mozilla::Forward<KeyInput>(key);
440             this->value = mozilla::Forward<ValueInput>(value);
441         }
442 
moveToInlineEntry443         MOZ_MUST_USE bool moveTo(Map& map) {
444             return map.putNew(mozilla::Move(key), mozilla::Move(value));
445         }
446     };
447 
448     class Entry
449     {
450         using MapEntry = typename Map::Entry;
451 
452         MapEntry*    mapEntry_;
453         InlineEntry* inlineEntry_;
454 
455       public:
456         Entry() = default;
457 
Entry(MapEntry * mapEntry)458         explicit Entry(MapEntry* mapEntry)
459           : mapEntry_(mapEntry),
460             inlineEntry_(nullptr)
461         { }
462 
Entry(InlineEntry * inlineEntry)463         explicit Entry(InlineEntry* inlineEntry)
464           : mapEntry_(nullptr),
465             inlineEntry_(inlineEntry)
466         { }
467 
key()468         const Key& key() const {
469             MOZ_ASSERT(!!mapEntry_ != !!inlineEntry_);
470             if (mapEntry_)
471                 return mapEntry_->key();
472             return inlineEntry_->key;
473         }
474 
value()475         Value& value() {
476             MOZ_ASSERT(!!mapEntry_ != !!inlineEntry_);
477             if (mapEntry_)
478                 return mapEntry_->value();
479             return inlineEntry_->value;
480         }
481     };
482 
483     using Impl = detail::InlineTable<InlineEntry, Entry,
484                                      Map, HashPolicy, AllocPolicy,
485                                      InlineEntries>;
486 
487     Impl impl_;
488 
489   public:
490     using Table  = Map;
491     using Ptr    = typename Impl::Ptr;
492     using AddPtr = typename Impl::AddPtr;
493     using Range  = typename Impl::Range;
494     using Lookup = typename HashPolicy::Lookup;
495 
496     static const size_t SizeOfInlineEntries = Impl::SizeOfInlineEntries;
497 
498     explicit InlineMap(AllocPolicy a = AllocPolicy())
impl_(a)499       : impl_(a)
500     { }
501 
count()502     size_t count() const {
503         return impl_.count();
504     }
505 
empty()506     bool empty() const {
507         return impl_.empty();
508     }
509 
clear()510     void clear() {
511         impl_.clear();
512     }
513 
all()514     Range all() const {
515         return impl_.all();
516     }
517 
518     MOZ_ALWAYS_INLINE
lookup(const Lookup & l)519     Ptr lookup(const Lookup& l) {
520         return impl_.lookup(l);
521     }
522 
523     MOZ_ALWAYS_INLINE
has(const Lookup & l)524     bool has(const Lookup& l) const {
525         return const_cast<InlineMap*>(this)->lookup(l).found();
526     }
527 
528     MOZ_ALWAYS_INLINE
lookupForAdd(const Lookup & l)529     AddPtr lookupForAdd(const Lookup& l) {
530         return impl_.lookupForAdd(l);
531     }
532 
533     template <typename KeyInput, typename ValueInput>
534     MOZ_ALWAYS_INLINE
add(AddPtr & p,KeyInput && key,ValueInput && value)535     MOZ_MUST_USE bool add(AddPtr& p, KeyInput&& key, ValueInput&& value) {
536         return impl_.add(p, mozilla::Forward<KeyInput>(key), mozilla::Forward<ValueInput>(value));
537     }
538 
539     template <typename KeyInput, typename ValueInput>
put(KeyInput && key,ValueInput && value)540     MOZ_MUST_USE bool put(KeyInput&& key, ValueInput&& value) {
541         AddPtr p = lookupForAdd(key);
542         if (p) {
543             p->value() = mozilla::Forward<ValueInput>(value);
544             return true;
545         }
546         return add(p, mozilla::Forward<KeyInput>(key), mozilla::Forward<ValueInput>(value));
547     }
548 
remove(Ptr & p)549     void remove(Ptr& p) {
550         impl_.remove(p);
551     }
552 
remove(const Lookup & l)553     void remove(const Lookup& l) {
554         impl_.remove(l);
555     }
556 };
557 
558 // A set with InlineEntries number of entries kept inline in an array.
559 //
560 // The T type must be zeroable as zeros are used as tombstone keys.
561 // The T type must have a default constructor.
562 //
563 // The API is very much like HashMap's.
564 template <typename T,
565           size_t InlineEntries,
566           typename HashPolicy = DefaultHasher<T>,
567           typename AllocPolicy = TempAllocPolicy>
568 class InlineSet
569 {
570     using Set = HashSet<T, HashPolicy, AllocPolicy>;
571 
572     struct InlineEntry
573     {
574         T key;
575 
576         template <typename TInput>
updateInlineEntry577         void update(TInput&& key) {
578             this->key = mozilla::Forward<TInput>(key);
579         }
580 
moveToInlineEntry581         MOZ_MUST_USE bool moveTo(Set& set) {
582             return set.putNew(mozilla::Move(key));
583         }
584     };
585 
586     class Entry
587     {
588         using SetEntry = typename Set::Entry;
589 
590         SetEntry*    setEntry_;
591         InlineEntry* inlineEntry_;
592 
593       public:
594         Entry() = default;
595 
Entry(const SetEntry * setEntry)596         explicit Entry(const SetEntry* setEntry)
597           : setEntry_(const_cast<SetEntry*>(setEntry)),
598             inlineEntry_(nullptr)
599         { }
600 
Entry(InlineEntry * inlineEntry)601         explicit Entry(InlineEntry* inlineEntry)
602           : setEntry_(nullptr),
603             inlineEntry_(inlineEntry)
604         { }
605 
T()606         operator T() const {
607             MOZ_ASSERT(!!setEntry_ != !!inlineEntry_);
608             if (setEntry_)
609                 return *setEntry_;
610             return inlineEntry_->key;
611         }
612     };
613 
614     using Impl = detail::InlineTable<InlineEntry, Entry,
615                                      Set, HashPolicy, AllocPolicy,
616                                      InlineEntries>;
617 
618     Impl impl_;
619 
620   public:
621     using Table  = Set;
622     using Ptr    = typename Impl::Ptr;
623     using AddPtr = typename Impl::AddPtr;
624     using Range  = typename Impl::Range;
625     using Lookup = typename HashPolicy::Lookup;
626 
627     static const size_t SizeOfInlineEntries = Impl::SizeOfInlineEntries;
628 
629     explicit InlineSet(AllocPolicy a = AllocPolicy())
impl_(a)630       : impl_(a)
631     { }
632 
count()633     size_t count() const {
634         return impl_.count();
635     }
636 
empty()637     bool empty() const {
638         return impl_.empty();
639     }
640 
clear()641     void clear() {
642         impl_.clear();
643     }
644 
all()645     Range all() const {
646         return impl_.all();
647     }
648 
649     MOZ_ALWAYS_INLINE
lookup(const Lookup & l)650     Ptr lookup(const Lookup& l) {
651         return impl_.lookup(l);
652     }
653 
654     MOZ_ALWAYS_INLINE
has(const Lookup & l)655     bool has(const Lookup& l) const {
656         return const_cast<InlineSet*>(this)->lookup(l).found();
657     }
658 
659     MOZ_ALWAYS_INLINE
lookupForAdd(const Lookup & l)660     AddPtr lookupForAdd(const Lookup& l) {
661         return impl_.lookupForAdd(l);
662     }
663 
664     template <typename TInput>
665     MOZ_ALWAYS_INLINE
add(AddPtr & p,TInput && key)666     MOZ_MUST_USE bool add(AddPtr& p, TInput&& key) {
667         return impl_.add(p, mozilla::Forward<TInput>(key));
668     }
669 
670     template <typename TInput>
put(TInput && key)671     MOZ_MUST_USE bool put(TInput&& key) {
672         AddPtr p = lookupForAdd(key);
673         return p ? true : add(p, mozilla::Forward<TInput>(key));
674     }
675 
remove(Ptr & p)676     void remove(Ptr& p) {
677         impl_.remove(p);
678     }
679 
remove(const Lookup & l)680     void remove(const Lookup& l) {
681         impl_.remove(l);
682     }
683 };
684 
685 } // namespace js
686 
687 #endif // ds_InlineTable_h
688