/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef jit_SparseBitSet_h
#define jit_SparseBitSet_h
#include "mozilla/Assertions.h"
#include <bit>
#include <stddef.h>
#include <stdint.h>
#include "ds/InlineTable.h"
#include "ds/LifoAlloc.h"
namespace js::jit {
// jit::SparseBitSet is very similar to jit::BitSet, but optimized for sets that
// can be very large but are often very sparse.
//
// It uses an InlineMap mapping from word-index to 32-bit words storing bits.
//
// For example, consider the following bitmap of 192 bits:
//
// 0xff001100 0x00001000 0x00000000 0x00000000 0x00000000 0x11110000
// 0^ 1^ 2^ 3^ 4^ 5^
//
// Words 2-4 don't have any bits set, so a SparseBitSet only stores the other
// words:
//
// 0 => 0xff001100
// 1 => 0x00001000
// 5 => 0x11110000
//
// SparseBitSet ensures words in the map are never 0.
template <typename AllocPolicy, typename Owner>
class SparseBitSet {
// Note: use uint32_t (instead of uintptr_t or uint64_t) to not waste space in
// InlineMap's array of inline entries. It uses a struct for each key/value
// pair.
using WordType = uint32_t;
static constexpr size_t BitsPerWord =
8 *
sizeof(WordType);
// Note: 8 inline entries is sufficient for the majority of bit sets.
// Compiling a large PhotoShop Wasm module with Ion, 94.5% of SparseBitSets
// had <= 8 map entries. For OpenOffice this was more than 98.5%.
static constexpr size_t NumEntries =
8;
using Map = InlineMap<uint32_t, WordType, NumEntries, DefaultHasher<uint32_t>,
AllocPolicy>;
Map map_;
static_assert(std::has_single_bit(BitsPerWord),
"Must be power-of-two for fast division/modulo");
static_assert((
sizeof(uint32_t) +
sizeof(WordType)) * NumEntries ==
Map::SizeOfInlineEntries,
"Array of inline entries must not have unused padding bytes");
static WordType bitMask(size_t bit) {
return WordType(
1) << (bit % BitsPerWord);
}
public:
class Iterator;
bool contains(size_t bit) {
uint32_t word = bit / BitsPerWord;
if (
auto p = map_.lookup(word)) {
return p->value() & bitMask(bit);
}
return false;
}
void remove(size_t bit) {
uint32_t word = bit / BitsPerWord;
if (
auto p = map_.lookup(word)) {
WordType value = p->value() & ~bitMask(bit);
if (value !=
0) {
p->value() = value;
}
else {
// The iterator and empty() method rely on the map not containing
// entries without any bits set.
map_.remove(p);
}
}
}
[[nodiscard]]
bool insert(size_t bit) {
uint32_t word = bit / BitsPerWord;
WordType mask = bitMask(bit);
auto p = map_.lookupForAdd(word);
if (p) {
p->value() |= mask;
return true;
}
return map_.add(p, word, mask);
}
bool empty()
const {
return map_.empty(); }
[[nodiscard]]
bool insertAll(
const SparseBitSet& other) {
for (
auto iter = other.map_.iter(); !iter.done(); iter.next()) {
auto index = iter.get().key();
WordType bits = iter.get().value();
MOZ_ASSERT(bits);
auto p = map_.lookupForAdd(index);
if (p) {
p->value() |= bits;
}
else {
if (!map_.add(p, index, bits)) {
return false;
}
}
}
return true;
}
};
// Iterates over the set bits in a SparseBitSet. For example:
//
// using Set = SparseBitSet<AllocPolicy>;
// Set set;
// ...
// for (Set::Iterator iter(set); iter; ++iter) {
// MOZ_ASSERT(set.contains(*iter));
// }
template <typename AllocPolicy, typename Owner>
class SparseBitSet<AllocPolicy, Owner>::Iterator {
#ifdef DEBUG
SparseBitSet& bitSet_;
#endif
typename SparseBitSet::Map::Iterator iter_;
WordType currentWord_ =
0;
// Index of a 1-bit in the SparseBitSet. This is the value returned by
// |*iter|.
size_t index_ =
0;
bool done()
const {
return iter_.done(); }
void skipZeroBits() {
MOZ_ASSERT(!done());
MOZ_ASSERT(currentWord_ !=
0);
auto numZeroes = std::countr_zero(currentWord_);
index_ += numZeroes;
currentWord_ >>= numZeroes;
}
public:
explicit Iterator(SparseBitSet& bitSet)
:
#ifdef DEBUG
bitSet_(bitSet),
#endif
iter_(bitSet.map_.iter()) {
if (!iter_.done()) {
index_ = iter_.get().key() * BitsPerWord;
currentWord_ = iter_.get().value();
skipZeroBits();
}
}
size_t
operator*()
const {
MOZ_ASSERT(!done());
MOZ_ASSERT(bitSet_.contains(index_));
return index_;
}
explicit operator bool()
const {
return !done(); }
void operator++() {
MOZ_ASSERT(!done());
currentWord_ >>=
1;
if (currentWord_ ==
0) {
iter_.next();
if (iter_.done()) {
// Done iterating.
return;
}
index_ = iter_.get().key() * BitsPerWord;
currentWord_ = iter_.get().value();
}
else {
index_++;
}
skipZeroBits();
}
};
}
// namespace js::jit
namespace js {
// A SparseBitSet may be LifoAllocated only if it is owned by a stack-allocated
// class, since that will cause it to be destroyed when the owner's scope ends.
// Otherwise, this could leak memory.
template <typename T, typename Owner>
struct CanLifoAlloc<js::jit::SparseBitSet<T, Owner>> : Owner::IsStackAllocated {
};
}
// namespace js
#endif /* jit_SparseBitSet_h */