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