Add BitSetRangeIterator
Add a new iterator for Dawn BitSet. The iterator defines "range".
"range" means continuous set bits and represents with
[offset, size] pair.
The new iterator iterate between ranges and could help immediate
data states tracker to do state tracking.
Bug:366291600
Change-Id: Iffbac0a2d1393eab22b2c289d84c966915cc8b18
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/231754
Commit-Queue: Shaobo Yan <shaoboyan@microsoft.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn
index 357c9a2..39decd6 100644
--- a/src/dawn/common/BUILD.gn
+++ b/src/dawn/common/BUILD.gn
@@ -264,6 +264,7 @@
"Alloc.h",
"Assert.cpp",
"Assert.h",
+ "BitSetRangeIterator.h",
"Compiler.h",
"Constants.h",
"ContentLessObjectCache.h",
diff --git a/src/dawn/common/BitSetRangeIterator.h b/src/dawn/common/BitSetRangeIterator.h
new file mode 100644
index 0000000..6447751
--- /dev/null
+++ b/src/dawn/common/BitSetRangeIterator.h
@@ -0,0 +1,176 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_COMMON_BITSETRANGEITERATOR_H_
+#define SRC_DAWN_COMMON_BITSETRANGEITERATOR_H_
+
+#include <bitset>
+#include <limits>
+#include <utility>
+
+#include "dawn/common/Assert.h"
+#include "dawn/common/Math.h"
+#include "dawn/common/UnderlyingType.h"
+
+namespace dawn {
+
+// Similar to BitSetIterator but returns ranges of consecutive bits as (offset, size) pairs
+// TODO(crbug.com/366291600): // Specialization for bitset size fits in uint64_t to skip
+// loops for bits across words boundary.
+template <size_t N, typename T>
+class BitSetRangeIterator final {
+ public:
+ explicit BitSetRangeIterator(const std::bitset<N>& bitset);
+ BitSetRangeIterator(const BitSetRangeIterator& other);
+ BitSetRangeIterator& operator=(const BitSetRangeIterator& other);
+
+ class Iterator final {
+ public:
+ explicit Iterator(const std::bitset<N>& bits, uint32_t offset = 0, uint32_t size = 0);
+ Iterator& operator++();
+
+ bool operator==(const Iterator& other) const;
+ bool operator!=(const Iterator& other) const;
+
+ // Returns a pair of offset and size of the current range
+ std::pair<T, size_t> operator*() const {
+ using U = UnderlyingType<T>;
+ DAWN_ASSERT(static_cast<U>(mOffset) <= std::numeric_limits<U>::max());
+ DAWN_ASSERT(static_cast<size_t>(mSize) <= std::numeric_limits<size_t>::max());
+ return std::make_pair(static_cast<T>(static_cast<U>(mOffset)),
+ static_cast<size_t>(mSize));
+ }
+
+ private:
+ void Advance();
+ size_t ScanForwardAndShiftBits();
+
+ static constexpr size_t kBitsPerWord = sizeof(uint64_t) * 8;
+ std::bitset<N> mBits;
+ uint32_t mOffset{0};
+ uint32_t mSize{0};
+ };
+
+ Iterator begin() const { return Iterator(mBits); }
+ Iterator end() const { return Iterator(std::bitset<N>(0), N, 0); }
+
+ private:
+ const std::bitset<N> mBits;
+};
+
+template <size_t N, typename T>
+BitSetRangeIterator<N, T>::BitSetRangeIterator(const std::bitset<N>& bitset) : mBits(bitset) {}
+
+template <size_t N, typename T>
+BitSetRangeIterator<N, T>::BitSetRangeIterator(const BitSetRangeIterator& other)
+ : mBits(other.mBits) {}
+
+template <size_t N, typename T>
+BitSetRangeIterator<N, T>& BitSetRangeIterator<N, T>::operator=(const BitSetRangeIterator& other) {
+ mBits = other.mBits;
+ return *this;
+}
+
+template <size_t N, typename T>
+BitSetRangeIterator<N, T>::Iterator::Iterator(const std::bitset<N>& bits,
+ uint32_t offset,
+ uint32_t size)
+ : mBits(bits), mOffset(offset), mSize(size) {
+ Advance();
+}
+
+template <size_t N, typename T>
+typename BitSetRangeIterator<N, T>::Iterator& BitSetRangeIterator<N, T>::Iterator::operator++() {
+ Advance();
+ return *this;
+}
+
+template <size_t N, typename T>
+bool BitSetRangeIterator<N, T>::Iterator::operator==(const Iterator& other) const {
+ return mOffset == other.mOffset && mSize == other.mSize && mBits == other.mBits;
+}
+
+template <size_t N, typename T>
+bool BitSetRangeIterator<N, T>::Iterator::operator!=(const Iterator& other) const {
+ return !(*this == other);
+}
+
+template <size_t N, typename T>
+size_t BitSetRangeIterator<N, T>::Iterator::ScanForwardAndShiftBits() {
+ if (mBits.none()) {
+ return N; // Or some other indicator that there are no bits.
+ }
+
+ constexpr std::bitset<N> wordMask(std::numeric_limits<uint64_t>::max());
+ size_t offset = 0;
+ while ((mBits & wordMask).to_ullong() == 0) {
+ offset += kBitsPerWord;
+ mBits >>= kBitsPerWord;
+ }
+
+ size_t nextBit = ScanForward(static_cast<uint64_t>((mBits & wordMask).to_ullong()));
+ mBits >>= nextBit;
+ return offset + nextBit;
+}
+
+template <size_t N, typename T>
+void BitSetRangeIterator<N, T>::Iterator::Advance() {
+ // Bits are currently shifted to mOffset + mSize.
+ mOffset += mSize;
+
+ size_t rangeStart = ScanForwardAndShiftBits();
+ if (rangeStart == N) {
+ // Reached the end, no more ranges.
+ mOffset = N;
+ mSize = 0;
+ return;
+ }
+
+ mOffset += rangeStart;
+ mBits = ~mBits;
+
+ size_t rangeCount = ScanForwardAndShiftBits();
+ if (rangeCount == N) {
+ // All bits until the end of the set are set.
+ rangeCount = N - mOffset;
+ }
+
+ mSize = rangeCount;
+ mBits = ~mBits;
+ mBits <<= mOffset;
+ mBits >>= mOffset;
+}
+
+// Helper to avoid needing to specify the template parameter size
+template <size_t N>
+BitSetRangeIterator<N, uint32_t> IterateBitSetRanges(const std::bitset<N>& bitset) {
+ return BitSetRangeIterator<N, uint32_t>(bitset);
+}
+
+} // namespace dawn
+
+#endif // SRC_DAWN_COMMON_BITSETRANGEITERATOR_H_
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt
index 56e4aa12..3965ffc 100644
--- a/src/dawn/common/CMakeLists.txt
+++ b/src/dawn/common/CMakeLists.txt
@@ -48,6 +48,7 @@
"AlignedAlloc.h"
"Alloc.h"
"Assert.h"
+ "BitSetRangeIterator.h"
"Compiler.h"
"Constants.h"
"ContentLessObjectCache.h"
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index fbee698..9cde65f 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -319,6 +319,7 @@
"DawnNativeTest.cpp",
"DawnNativeTest.h",
"unittests/AsyncTaskTests.cpp",
+ "unittests/BitSetRangeIteratorTests.cpp",
"unittests/BuddyAllocatorTests.cpp",
"unittests/BuddyMemoryAllocatorTests.cpp",
"unittests/ChainUtilsTests.cpp",
diff --git a/src/dawn/tests/unittests/BitSetRangeIteratorTests.cpp b/src/dawn/tests/unittests/BitSetRangeIteratorTests.cpp
new file mode 100644
index 0000000..b64cf68
--- /dev/null
+++ b/src/dawn/tests/unittests/BitSetRangeIteratorTests.cpp
@@ -0,0 +1,221 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "dawn/common/BitSetRangeIterator.h"
+#include "gtest/gtest.h"
+
+namespace dawn {
+namespace {
+
+class BitSetRangeIteratorTest : public testing::Test {
+ protected:
+ template <size_t N>
+ void RunSingleBitRangesTests() {
+ std::bitset<N> stateBits;
+ uint32_t bitSize = stateBits.size();
+
+ std::vector<std::pair<uint32_t, size_t>> expectedRanges;
+ expectedRanges.push_back({bitSize / 4u * 3u, 1});
+
+ for (const auto& range : expectedRanges) {
+ stateBits.set(range.first);
+ }
+
+ std::vector<std::pair<uint32_t, size_t>> foundRanges;
+ for (auto range : IterateBitSetRanges(stateBits)) {
+ foundRanges.push_back(range);
+ }
+
+ EXPECT_EQ(expectedRanges.size(), foundRanges.size());
+ for (size_t i = 0; i < expectedRanges.size(); ++i) {
+ EXPECT_EQ(expectedRanges[i].first, foundRanges[i].first);
+ EXPECT_EQ(expectedRanges[i].second, foundRanges[i].second);
+ }
+ }
+
+ template <size_t N>
+ void RunConsecutiveBitRangesTests() {
+ std::bitset<N> stateBits;
+ uint32_t bitSize = stateBits.size();
+ // Ensure range no overlapping
+ DAWN_ASSERT(bitSize >= 14);
+
+ std::vector<std::pair<uint32_t, size_t>> expectedRanges;
+ expectedRanges.push_back({2, 3});
+ expectedRanges.push_back({bitSize / 2, 2});
+ expectedRanges.push_back({bitSize / 2 + 3, 4});
+
+ for (const auto& range : expectedRanges) {
+ for (uint32_t i = 0; i < range.second; ++i) {
+ stateBits.set(range.first + i);
+ }
+ }
+
+ std::vector<std::pair<uint32_t, size_t>> foundRanges;
+ for (auto range : IterateBitSetRanges(stateBits)) {
+ foundRanges.push_back(range);
+ }
+
+ EXPECT_EQ(expectedRanges.size(), foundRanges.size());
+ for (size_t i = 0; i < expectedRanges.size(); ++i) {
+ EXPECT_EQ(expectedRanges[i].first, foundRanges[i].first);
+ EXPECT_EQ(expectedRanges[i].second, foundRanges[i].second);
+ }
+ }
+
+ template <size_t N>
+ void RunNonLValueBitset() {
+ std::bitset<N> stateBits;
+ std::bitset<N> otherBits;
+
+ uint32_t halfBitSize = stateBits.size() / 2;
+ DAWN_ASSERT(halfBitSize >= 3);
+
+ std::vector<std::pair<uint32_t, size_t>> expectedRanges;
+ expectedRanges.push_back({halfBitSize - 2, 2});
+ expectedRanges.push_back({halfBitSize + 1, 1});
+
+ // Set up consecutive bits
+ stateBits.set(halfBitSize - 2);
+ stateBits.set(halfBitSize - 1);
+ stateBits.set(halfBitSize);
+ stateBits.set(halfBitSize + 1);
+
+ // Set up second bitset with overlapping and non-overlapping bits
+ otherBits.set(halfBitSize - 3);
+ otherBits.set(halfBitSize - 2);
+ otherBits.set(halfBitSize - 1);
+ otherBits.set(halfBitSize + 1);
+ otherBits.set(halfBitSize + 2);
+
+ std::vector<std::pair<uint32_t, size_t>> foundRanges;
+ for (auto range : IterateBitSetRanges(stateBits & otherBits)) {
+ foundRanges.push_back(range);
+
+ // Verify all bits in the range are set in both bitsets
+ for (uint32_t i = 0; i < range.second; ++i) {
+ EXPECT_TRUE(stateBits[range.first + i]);
+ EXPECT_TRUE(otherBits[range.first + i]);
+ }
+ }
+
+ EXPECT_EQ(expectedRanges.size(), foundRanges.size());
+ for (size_t i = 0; i < expectedRanges.size(); ++i) {
+ EXPECT_EQ(expectedRanges[i].first, foundRanges[i].first);
+ EXPECT_EQ(expectedRanges[i].second, foundRanges[i].second);
+ }
+ }
+
+ // Same value as BitSetRangeIterator.
+ static constexpr size_t kBitsPerWord = sizeof(uint64_t) * 8;
+};
+
+// Test basic range iteration with single bits (each range has size 1)
+TEST_F(BitSetRangeIteratorTest, SingleBitRanges) {
+ // Smaller than 1 word
+ {
+ RunSingleBitRangesTests<kBitsPerWord - 1>();
+ }
+
+ // Equal to 1 word
+ {
+ RunSingleBitRangesTests<kBitsPerWord>();
+ }
+
+ // Larger than 1 word
+ {
+ RunSingleBitRangesTests<kBitsPerWord * 2 - 1>();
+ }
+}
+
+// Test ranges with consecutive bits
+TEST_F(BitSetRangeIteratorTest, ConsecutiveBitRanges) {
+ // Smaller than 1 word
+ {
+ RunConsecutiveBitRangesTests<kBitsPerWord - 1>();
+ }
+
+ // Equal to 1 word
+ {
+ RunConsecutiveBitRangesTests<kBitsPerWord>();
+ }
+
+ // Larger than 1 word
+ {
+ RunConsecutiveBitRangesTests<kBitsPerWord * 2 - 1>();
+ }
+}
+
+// Test an empty iterator
+TEST_F(BitSetRangeIteratorTest, EmptySet) {
+ std::bitset<kBitsPerWord> stateBits;
+ auto iterator = IterateBitSetRanges(stateBits);
+ EXPECT_EQ(iterator.end(), iterator.begin());
+}
+
+// Test iterating a result of combining two bitsets
+TEST_F(BitSetRangeIteratorTest, NonLValueBitset) {
+ // Smaller than 1 word
+ {
+ RunNonLValueBitset<kBitsPerWord - 1>();
+ }
+
+ // Equal to 1 word
+ {
+ RunNonLValueBitset<kBitsPerWord>();
+ }
+
+ // Larger than 1 word
+ {
+ RunNonLValueBitset<kBitsPerWord * 2 - 1>();
+ }
+}
+
+// Test ranges that cross word boundaries
+TEST_F(BitSetRangeIteratorTest, CrossWordBoundaryRanges) {
+ std::bitset<kBitsPerWord * 2> stateBits;
+ // Set a range that crosses bit word boundary
+ for (uint32_t i = kBitsPerWord - 2; i <= kBitsPerWord + 1; ++i) {
+ stateBits.set(i);
+ }
+
+ std::vector<std::pair<uint32_t, size_t>> foundRanges;
+ for (auto range : IterateBitSetRanges(stateBits)) {
+ foundRanges.push_back(range);
+ }
+
+ EXPECT_EQ(1u, foundRanges.size());
+ EXPECT_EQ(kBitsPerWord - 2, foundRanges[0].first);
+ EXPECT_EQ(4u, foundRanges[0].second);
+}
+
+} // anonymous namespace
+} // namespace dawn
diff --git a/src/dawn/tests/unittests/MathTests.cpp b/src/dawn/tests/unittests/MathTests.cpp
index a3cb746..063b93b 100644
--- a/src/dawn/tests/unittests/MathTests.cpp
+++ b/src/dawn/tests/unittests/MathTests.cpp
@@ -432,20 +432,20 @@
// Tests for RoundUp
TEST(Math, RoundUp) {
- ASSERT_EQ(RoundUp(2, 2), 2u);
- ASSERT_EQ(RoundUp(2, 4), 4u);
- ASSERT_EQ(RoundUp(6, 2), 6u);
- ASSERT_EQ(RoundUp(8, 4), 8u);
- ASSERT_EQ(RoundUp(12, 6), 12u);
+ ASSERT_EQ(RoundUp(2u, 2u), 2u);
+ ASSERT_EQ(RoundUp(2u, 4u), 4u);
+ ASSERT_EQ(RoundUp(6u, 2u), 6u);
+ ASSERT_EQ(RoundUp(8u, 4u), 8u);
+ ASSERT_EQ(RoundUp(12u, 6u), 12u);
- ASSERT_EQ(RoundUp(3, 3), 3u);
- ASSERT_EQ(RoundUp(3, 5), 5u);
- ASSERT_EQ(RoundUp(5, 3), 6u);
- ASSERT_EQ(RoundUp(9, 5), 10u);
+ ASSERT_EQ(RoundUp(3u, 3u), 3u);
+ ASSERT_EQ(RoundUp(3u, 5u), 5u);
+ ASSERT_EQ(RoundUp(5u, 3u), 6u);
+ ASSERT_EQ(RoundUp(9u, 5u), 10u);
// Test extrema
ASSERT_EQ(RoundUp(0x7FFFFFFFFFFFFFFFull, 0x8000000000000000ull), 0x8000000000000000ull);
- ASSERT_EQ(RoundUp(1, 1), 1u);
+ ASSERT_EQ(RoundUp(1u, 1u), 1u);
}
// Tests for IsSubset