tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

InlineTable.h (17121B)


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