Bug 1927417 - Part 1: Add mozilla::BitSet methods to efficiently find the next and previous bits set r=glandium

Differential Revision: https://phabricator.services.mozilla.com/D227039
This commit is contained in:
Jon Coppeard 2024-11-06 08:21:48 +00:00
parent 72924760ab
commit aeab420bef
3 changed files with 128 additions and 20 deletions

View File

@ -26,6 +26,7 @@ template <size_t N, typename Word = size_t>
class BitSet {
static_assert(std::is_unsigned_v<Word>,
"The Word type must be an unsigned integral type");
static_assert(N != 0);
private:
static constexpr size_t kBitsPerWord = 8 * sizeof(Word);
@ -167,6 +168,55 @@ class BitSet {
ResetPaddingBits();
}
// Return the position of the first bit set, or SIZE_MAX if none.
size_t FindFirst() const { return FindNext(0); }
// Return the position of the next bit set starting from |aFromPos| inclusive,
// or SIZE_MAX if none.
size_t FindNext(size_t aFromPos) const {
MOZ_ASSERT(aFromPos < N);
size_t wordIndex = aFromPos / kBitsPerWord;
size_t bitIndex = aFromPos % kBitsPerWord;
Word word = mStorage[wordIndex];
// Mask word containing |aFromPos|.
word &= (Word(-1) << bitIndex);
while (word == 0) {
wordIndex++;
if (wordIndex == kNumWords) {
return SIZE_MAX;
}
word = mStorage[wordIndex];
}
uint_fast8_t pos = CountTrailingZeroes(word);
return wordIndex * kBitsPerWord + pos;
}
size_t FindLast() const { return FindPrev(Size() - 1); }
// Return the position of the previous bit set starting from |aFromPos|
// inclusive, or SIZE_MAX if none.
size_t FindPrev(size_t aFromPos) const {
MOZ_ASSERT(aFromPos < N);
size_t wordIndex = aFromPos / kBitsPerWord;
size_t bitIndex = aFromPos % kBitsPerWord;
Word word = mStorage[wordIndex];
// Mask word containing |aFromPos|.
word &= Word(-1) >> (kBitsPerWord - 1 - bitIndex);
while (word == 0) {
if (wordIndex == 0) {
return SIZE_MAX;
}
wordIndex--;
word = mStorage[wordIndex];
}
uint_fast8_t pos = FindMostSignificantBit(word);
return wordIndex * kBitsPerWord + pos;
}
Span<Word> Storage() { return mStorage; }
Span<const Word> Storage() const { return mStorage; }

View File

@ -318,28 +318,24 @@ inline uint_fast8_t CeilingLog2Size(size_t aValue) {
return CeilingLog2(aValue);
}
namespace detail {
template <typename T, size_t Size = sizeof(T)>
class FloorLog2;
/**
* Compute the bit position of the most significant bit set in
* |aValue|. Requires that |aValue| is non-zero.
*/
template <typename T>
class FloorLog2<T, 4> {
public:
static uint_fast8_t compute(const T aValue) {
return 31u - CountLeadingZeroes32(aValue | 1);
inline uint_fast8_t FindMostSignificantBit(T aValue) {
static_assert(sizeof(T) <= 8);
static_assert(std::is_integral_v<T>);
MOZ_ASSERT(aValue != 0);
// This casts to 32-bits
if constexpr (sizeof(T) <= 4) {
return 31u - CountLeadingZeroes32(aValue);
}
};
template <typename T>
class FloorLog2<T, 8> {
public:
static uint_fast8_t compute(const T aValue) {
return 63u - CountLeadingZeroes64(aValue | 1);
// This doesn't
if constexpr (sizeof(T) == 8) {
return 63u - CountLeadingZeroes64(aValue);
}
};
} // namespace detail
}
/**
* Compute the log of the greatest power of 2 less than or equal to |aValue|.
@ -351,7 +347,7 @@ class FloorLog2<T, 8> {
*/
template <typename T>
inline constexpr uint_fast8_t FloorLog2(const T aValue) {
return detail::FloorLog2<T>::compute(aValue);
return FindMostSignificantBit(aValue | 1);
}
/** A FloorLog2 variant that accepts only size_t. */

View File

@ -101,10 +101,72 @@ class BitSetSuite {
}
}
void testFindBits() {
TestBitSet<kBitsPerWord * 5 + 2> bitset;
size_t size = bitset.Size();
MOZ_RELEASE_ASSERT(bitset.IsEmpty());
MOZ_RELEASE_ASSERT(bitset.FindFirst() == SIZE_MAX);
MOZ_RELEASE_ASSERT(bitset.FindLast() == SIZE_MAX);
MOZ_RELEASE_ASSERT(bitset.FindNext(0) == SIZE_MAX);
MOZ_RELEASE_ASSERT(bitset.FindNext(size - 1) == SIZE_MAX);
MOZ_RELEASE_ASSERT(bitset.FindPrev(0) == SIZE_MAX);
MOZ_RELEASE_ASSERT(bitset.FindPrev(size - 1) == SIZE_MAX);
// Test with single bit set.
for (size_t i = 0; i < size; i += 5) {
bitset[i] = true;
MOZ_RELEASE_ASSERT(bitset.FindFirst() == i);
MOZ_RELEASE_ASSERT(bitset.FindLast() == i);
MOZ_RELEASE_ASSERT(bitset.FindNext(i) == i);
MOZ_RELEASE_ASSERT(bitset.FindPrev(i) == i);
MOZ_RELEASE_ASSERT(bitset.FindNext(0) == i);
MOZ_RELEASE_ASSERT(bitset.FindPrev(size - 1) == i);
if (i != 0) {
MOZ_RELEASE_ASSERT(bitset.FindNext(i - 1) == i);
MOZ_RELEASE_ASSERT(bitset.FindPrev(i - 1) == SIZE_MAX);
}
if (i != size - 1) {
MOZ_RELEASE_ASSERT(bitset.FindNext(i + 1) == SIZE_MAX);
MOZ_RELEASE_ASSERT(bitset.FindPrev(i + 1) == i);
}
bitset[i] = false;
}
// Test with multiple bits set.
//
// This creates bits pattern with every |i|th bit set and checks the result
// of calling FindNext/FindPrev at and around each set bit.
for (size_t i = 3; i < size; i += 5) {
bitset.ResetAll();
for (size_t j = 0; j < size; j += i) {
bitset[j] = true;
}
for (size_t j = 0; j < size; j += i) {
// Test FindNext/FindPrev on the current bit.
MOZ_RELEASE_ASSERT(bitset[j]);
MOZ_RELEASE_ASSERT(bitset.FindNext(j) == j);
MOZ_RELEASE_ASSERT(bitset.FindPrev(j) == j);
// Test FindNext/FindPrev on the previous bit.
if (j != 0) {
MOZ_RELEASE_ASSERT(bitset[j - i]);
MOZ_RELEASE_ASSERT(bitset.FindNext(j - 1) == j);
MOZ_RELEASE_ASSERT(bitset.FindPrev(j - 1) == j - i);
}
// Test FindNext/FindPrev on the next bit.
if (j + i < size) {
MOZ_RELEASE_ASSERT(bitset[j + i]);
MOZ_RELEASE_ASSERT(bitset.FindNext(j + 1) == j + i);
MOZ_RELEASE_ASSERT(bitset.FindPrev(j + 1) == j);
}
}
}
}
void runTests() {
testLength();
testConstruct();
testSetBit();
testFindBits();
}
};