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 */