tor-browser

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

SparseBitSet.h (5729B)


      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 jit_SparseBitSet_h
      8 #define jit_SparseBitSet_h
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/MathAlgorithms.h"
     12 
     13 #include <stddef.h>
     14 #include <stdint.h>
     15 
     16 #include "ds/InlineTable.h"
     17 #include "ds/LifoAlloc.h"
     18 
     19 namespace js::jit {
     20 
     21 // jit::SparseBitSet is very similar to jit::BitSet, but optimized for sets that
     22 // can be very large but are often very sparse.
     23 //
     24 // It uses an InlineMap mapping from word-index to 32-bit words storing bits.
     25 //
     26 // For example, consider the following bitmap of 192 bits:
     27 //
     28 //   0xff001100 0x00001000  0x00000000 0x00000000 0x00000000 0x11110000
     29 //   0^         1^          2^         3^         4^         5^
     30 //
     31 // Words 2-4 don't have any bits set, so a SparseBitSet only stores the other
     32 // words:
     33 //
     34 //   0 => 0xff001100
     35 //   1 => 0x00001000
     36 //   5 => 0x11110000
     37 //
     38 // SparseBitSet ensures words in the map are never 0.
     39 template <typename AllocPolicy, typename Owner>
     40 class SparseBitSet {
     41  // Note: use uint32_t (instead of uintptr_t or uint64_t) to not waste space in
     42  // InlineMap's array of inline entries. It uses a struct for each key/value
     43  // pair.
     44  using WordType = uint32_t;
     45  static constexpr size_t BitsPerWord = 8 * sizeof(WordType);
     46 
     47  // Note: 8 inline entries is sufficient for the majority of bit sets.
     48  // Compiling a large PhotoShop Wasm module with Ion, 94.5% of SparseBitSets
     49  // had <= 8 map entries. For OpenOffice this was more than 98.5%.
     50  static constexpr size_t NumEntries = 8;
     51  using Map = InlineMap<uint32_t, WordType, NumEntries, DefaultHasher<uint32_t>,
     52                        AllocPolicy>;
     53  using Range = typename Map::Range;
     54  Map map_;
     55 
     56  static_assert(mozilla::IsPowerOfTwo(BitsPerWord),
     57                "Must be power-of-two for fast division/modulo");
     58  static_assert((sizeof(uint32_t) + sizeof(WordType)) * NumEntries ==
     59                    Map::SizeOfInlineEntries,
     60                "Array of inline entries must not have unused padding bytes");
     61 
     62  static WordType bitMask(size_t bit) {
     63    return WordType(1) << (bit % BitsPerWord);
     64  }
     65 
     66 public:
     67  class Iterator;
     68 
     69  bool contains(size_t bit) {
     70    uint32_t word = bit / BitsPerWord;
     71    if (auto p = map_.lookup(word)) {
     72      return p->value() & bitMask(bit);
     73    }
     74    return false;
     75  }
     76  void remove(size_t bit) {
     77    uint32_t word = bit / BitsPerWord;
     78    if (auto p = map_.lookup(word)) {
     79      WordType value = p->value() & ~bitMask(bit);
     80      if (value != 0) {
     81        p->value() = value;
     82      } else {
     83        // The iterator and empty() method rely on the map not containing
     84        // entries without any bits set.
     85        map_.remove(p);
     86      }
     87    }
     88  }
     89  [[nodiscard]] bool insert(size_t bit) {
     90    uint32_t word = bit / BitsPerWord;
     91    WordType mask = bitMask(bit);
     92    auto p = map_.lookupForAdd(word);
     93    if (p) {
     94      p->value() |= mask;
     95      return true;
     96    }
     97    return map_.add(p, word, mask);
     98  }
     99 
    100  bool empty() const { return map_.empty(); }
    101 
    102  [[nodiscard]] bool insertAll(const SparseBitSet& other) {
    103    for (Range r(other.map_.all()); !r.empty(); r.popFront()) {
    104      auto index = r.front().key();
    105      WordType bits = r.front().value();
    106      MOZ_ASSERT(bits);
    107      auto p = map_.lookupForAdd(index);
    108      if (p) {
    109        p->value() |= bits;
    110      } else {
    111        if (!map_.add(p, index, bits)) {
    112          return false;
    113        }
    114      }
    115    }
    116    return true;
    117  }
    118 };
    119 
    120 // Iterates over the set bits in a SparseBitSet. For example:
    121 //
    122 //   using Set = SparseBitSet<AllocPolicy>;
    123 //   Set set;
    124 //   ...
    125 //   for (Set::Iterator iter(set); iter; ++iter) {
    126 //     MOZ_ASSERT(set.contains(*iter));
    127 //   }
    128 template <typename AllocPolicy, typename Owner>
    129 class SparseBitSet<AllocPolicy, Owner>::Iterator {
    130 #ifdef DEBUG
    131  SparseBitSet& bitSet_;
    132 #endif
    133  SparseBitSet::Range range_;
    134  WordType currentWord_ = 0;
    135  // Index of a 1-bit in the SparseBitSet. This is the value returned by
    136  // |*iter|.
    137  size_t index_ = 0;
    138 
    139  bool done() const { return range_.empty(); }
    140 
    141  void skipZeroBits() {
    142    MOZ_ASSERT(!done());
    143    MOZ_ASSERT(currentWord_ != 0);
    144    auto numZeroes = mozilla::CountTrailingZeroes32(currentWord_);
    145    index_ += numZeroes;
    146    currentWord_ >>= numZeroes;
    147  }
    148 
    149 public:
    150  explicit Iterator(SparseBitSet& bitSet)
    151      :
    152 #ifdef DEBUG
    153        bitSet_(bitSet),
    154 #endif
    155        range_(bitSet.map_.all()) {
    156    if (!range_.empty()) {
    157      index_ = range_.front().key() * BitsPerWord;
    158      currentWord_ = range_.front().value();
    159      skipZeroBits();
    160    }
    161  }
    162 
    163  size_t operator*() const {
    164    MOZ_ASSERT(!done());
    165    MOZ_ASSERT(bitSet_.contains(index_));
    166    return index_;
    167  }
    168 
    169  explicit operator bool() const { return !done(); }
    170 
    171  void operator++() {
    172    MOZ_ASSERT(!done());
    173    currentWord_ >>= 1;
    174    if (currentWord_ == 0) {
    175      range_.popFront();
    176      if (range_.empty()) {
    177        // Done iterating.
    178        return;
    179      }
    180      index_ = range_.front().key() * BitsPerWord;
    181      currentWord_ = range_.front().value();
    182    } else {
    183      index_++;
    184    }
    185    skipZeroBits();
    186  }
    187 };
    188 
    189 }  // namespace js::jit
    190 
    191 namespace js {
    192 
    193 // A SparseBitSet may be LifoAllocated only if it is owned by a stack-allocated
    194 // class, since that will cause it to be destroyed when the owner's scope ends.
    195 // Otherwise, this could leak memory.
    196 template <typename T, typename Owner>
    197 struct CanLifoAlloc<js::jit::SparseBitSet<T, Owner>> : Owner::IsStackAllocated {
    198 };
    199 
    200 }  // namespace js
    201 
    202 #endif /* jit_SparseBitSet_h */