blob: 355941daaad8cac2cac6139a6d1ad62bdfa7c5f2 [file] [edit]
/*
* Copyright (C) 2026 Apple Inc. All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
*/
#pragma once
#include <algorithm>
#include <wtf/BitSet.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
namespace WTF {
// A bit vector that only stores the chunks ("elements") that have set bits, so its memory is
// proportional to the number of set bits rather than to the largest index. This is the right
// representation when the universe of indices is large but only a small, clustered subset is set
// at any time -- e.g. per-basic-block liveness sets over a function with many virtual registers,
// where a dense BitVector would cost numBlocks x numIndices bits.
template<unsigned elementBits = 128>
class SparseBitVector {
public:
SparseBitVector() = default;
void ensureSize(unsigned) { }
void clear()
{
m_elements.clear();
m_hint = 0;
}
bool isEmpty() const
{
ASSERT(allElementsNonEmpty());
return m_elements.isEmpty();
}
bool set(unsigned bit)
{
Element& element = findOrInsert(bit / elementBits);
return !element.bits.testAndSet(bit % elementBits);
}
// Matches WTF::BitVector::add(): returns whether the bit transitioned from unset to set. This
// lets callers template uniformly over BitVector and SparseBitVector.
bool add(unsigned bit)
{
return set(bit);
}
bool reset(unsigned bit)
{
unsigned elementIndex = bit / elementBits;
unsigned position = (m_hint < m_elements.size() && m_elements[m_hint].index == elementIndex) ? m_hint : lowerBound(elementIndex);
if (position >= m_elements.size() || m_elements[position].index != elementIndex)
return false;
bool wasSet = m_elements[position].bits.testAndClear(bit % elementBits);
if (wasSet && m_elements[position].bits.isEmpty()) {
m_elements.removeAt(position);
m_hint = 0; // Positions shifted; invalidate the hint.
} else
m_hint = position;
return wasSet;
}
bool contains(unsigned bit) const
{
const Element* element = find(bit / elementBits);
return element && element->bits.get(bit % elementBits);
}
template<typename Func>
void forEachSetBit(const Func& func) const
{
for (const Element& element : m_elements) {
unsigned base = element.index * elementBits;
element.bits.forEachSetBit([&] (size_t bitInElement) {
func(base + static_cast<unsigned>(bitInElement));
});
}
}
class iterator {
public:
iterator() = default;
iterator(const SparseBitVector& owner, bool isEnd)
: m_owner(&owner)
{
if (isEnd || owner.m_elements.isEmpty()) {
m_elementIndex = owner.m_elements.size();
return;
}
m_bitIter = owner.m_elements[0].bits.begin();
}
unsigned operator*() const
{
const Element& element = m_owner->m_elements[m_elementIndex];
return element.index * elementBits + static_cast<unsigned>(*m_bitIter);
}
iterator& operator++()
{
++m_bitIter;
if (m_bitIter == m_owner->m_elements[m_elementIndex].bits.end()) {
++m_elementIndex;
if (m_elementIndex < m_owner->m_elements.size())
m_bitIter = m_owner->m_elements[m_elementIndex].bits.begin();
else
m_bitIter = { };
}
return *this;
}
bool operator==(const iterator& other) const
{
if (m_elementIndex != other.m_elementIndex)
return false;
if (!m_owner || m_elementIndex == m_owner->m_elements.size())
return true;
return m_bitIter == other.m_bitIter;
}
private:
const SparseBitVector* m_owner { nullptr };
size_t m_elementIndex { 0 };
typename BitSet<elementBits>::iterator m_bitIter;
};
iterator begin() const LIFETIME_BOUND { return iterator(*this, false); }
iterator end() const LIFETIME_BOUND { return iterator(*this, true); }
private:
struct Element {
unsigned index { 0 };
BitSet<elementBits> bits;
};
#if ASSERT_ENABLED
bool allElementsNonEmpty() const
{
for (const Element& element : m_elements) {
if (element.bits.isEmpty())
return false;
}
return true;
}
#endif
const Element* find(unsigned elementIndex) const
{
if (m_hint < m_elements.size() && m_elements[m_hint].index == elementIndex)
return &m_elements[m_hint];
unsigned position = lowerBound(elementIndex);
if (position < m_elements.size() && m_elements[position].index == elementIndex) {
m_hint = position;
return &m_elements[position];
}
return nullptr;
}
Element& findOrInsert(unsigned elementIndex)
{
if (m_hint < m_elements.size() && m_elements[m_hint].index == elementIndex)
return m_elements[m_hint];
unsigned position = lowerBound(elementIndex);
if (position < m_elements.size() && m_elements[position].index == elementIndex) {
m_hint = position;
return m_elements[position];
}
m_elements.insert(position, Element { elementIndex, { } });
m_hint = position;
return m_elements[position];
}
unsigned lowerBound(unsigned elementIndex) const
{
auto iterator = std::ranges::lower_bound(m_elements, elementIndex, { }, &Element::index);
return iterator - m_elements.begin();
}
Vector<Element> m_elements;
mutable unsigned m_hint { 0 };
};
} // namespace WTF
using WTF::SparseBitVector;