| // Copyright 2024 Google LLC |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef HIGHWAY_HWY_BIT_SET_H_ |
| #define HIGHWAY_HWY_BIT_SET_H_ |
| |
| // Various BitSet for 64, up to 4096, or any number of bits. |
| |
| #include <stddef.h> |
| |
| #include <atomic> |
| |
| #include "third_party/highway/hwy/base.h" |
| |
| namespace hwy { |
| |
| // 64-bit specialization of `std::bitset`, which lacks `Foreach`. |
| class BitSet64 { |
| public: |
| constexpr size_t MaxSize() const { return 64; } |
| |
| // No harm if `i` is already set. |
| void Set(size_t i) { |
| HWY_DASSERT(i < 64); |
| bits_ |= (1ULL << i); |
| HWY_DASSERT(Get(i)); |
| } |
| |
| // Equivalent to Set(i) for i in [0, 64) where (bits >> i) & 1. This does |
| // not clear any existing bits. |
| void SetNonzeroBitsFrom64(uint64_t bits) { bits_ |= bits; } |
| |
| void Clear(size_t i) { |
| HWY_DASSERT(i < 64); |
| bits_ &= ~(1ULL << i); |
| } |
| |
| bool Get(size_t i) const { |
| HWY_DASSERT(i < 64); |
| return (bits_ & (1ULL << i)) != 0; |
| } |
| |
| // Returns true if Get(i) would return true for any i in [0, 64). |
| bool Any() const { return bits_ != 0; } |
| |
| // Returns true if Get(i) would return true for all i in [0, 64). |
| bool All() const { return bits_ == ~uint64_t{0}; } |
| |
| // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`! |
| size_t First() const { |
| HWY_DASSERT(Any()); |
| return Num0BitsBelowLS1Bit_Nonzero64(bits_); |
| } |
| |
| // Returns lowest i such that `!Get(i)`. Caller must first ensure `!All()`! |
| size_t First0() const { |
| HWY_DASSERT(!All()); |
| return Num0BitsBelowLS1Bit_Nonzero64(~bits_); |
| } |
| |
| // Returns uint64_t(Get(i)) << i for i in [0, 64). |
| uint64_t Get64() const { return bits_; } |
| |
| // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify |
| // the set, but the current Foreach call is unaffected. |
| template <class Func> |
| void Foreach(const Func& func) const { |
| uint64_t remaining_bits = bits_; |
| while (remaining_bits != 0) { |
| const size_t i = Num0BitsBelowLS1Bit_Nonzero64(remaining_bits); |
| remaining_bits &= remaining_bits - 1; // clear LSB |
| func(i); |
| } |
| } |
| |
| size_t Count() const { return PopCount(bits_); } |
| |
| private: |
| uint64_t bits_ = 0; |
| }; |
| |
| // Any number of bits, flat array. |
| template <size_t kMaxSize> |
| class BitSet { |
| static_assert(kMaxSize != 0, "BitSet requires non-zero size"); |
| |
| public: |
| constexpr size_t MaxSize() const { return kMaxSize; } |
| |
| // No harm if `i` is already set. |
| void Set(size_t i) { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| bits_[idx].Set(mod); |
| } |
| |
| void Clear(size_t i) { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| bits_[idx].Clear(mod); |
| HWY_DASSERT(!Get(i)); |
| } |
| |
| bool Get(size_t i) const { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| return bits_[idx].Get(mod); |
| } |
| |
| // Returns true if Get(i) would return true for any i in [0, kMaxSize). |
| bool Any() const { |
| for (const BitSet64& bits : bits_) { |
| if (bits.Any()) return true; |
| } |
| return false; |
| } |
| |
| // Returns true if Get(i) would return true for all i in [0, kMaxSize). |
| bool All() const { |
| for (size_t idx = 0; idx < kNum64 - 1; ++idx) { |
| if (!bits_[idx].All()) return false; |
| } |
| |
| constexpr size_t kRemainder = kMaxSize % 64; |
| if (kRemainder == 0) { |
| return bits_[kNum64 - 1].All(); |
| } |
| return bits_[kNum64 - 1].Count() == kRemainder; |
| } |
| |
| // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`! |
| size_t First() const { |
| HWY_DASSERT(Any()); |
| for (size_t idx = 0;; ++idx) { |
| HWY_DASSERT(idx < kNum64); |
| if (bits_[idx].Any()) return idx * 64 + bits_[idx].First(); |
| } |
| } |
| |
| // Returns lowest i such that `!Get(i)`. Caller must first ensure `All()`! |
| size_t First0() const { |
| HWY_DASSERT(!All()); |
| for (size_t idx = 0;; ++idx) { |
| HWY_DASSERT(idx < kNum64); |
| if (!bits_[idx].All()) { |
| const size_t first0 = idx * 64 + bits_[idx].First0(); |
| HWY_DASSERT(first0 < kMaxSize); |
| return first0; |
| } |
| } |
| } |
| |
| // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify |
| // the set, but the current Foreach call is only affected if changing one of |
| // the not yet visited BitSet64. |
| template <class Func> |
| void Foreach(const Func& func) const { |
| for (size_t idx = 0; idx < kNum64; ++idx) { |
| bits_[idx].Foreach([idx, &func](size_t mod) { func(idx * 64 + mod); }); |
| } |
| } |
| |
| size_t Count() const { |
| size_t total = 0; |
| for (const BitSet64& bits : bits_) { |
| total += bits.Count(); |
| } |
| return total; |
| } |
| |
| private: |
| static constexpr size_t kNum64 = DivCeil(kMaxSize, size_t{64}); |
| BitSet64 bits_[kNum64]; |
| }; |
| |
| // Any number of bits, flat array, atomic updates to the u64. |
| template <size_t kMaxSize> |
| class AtomicBitSet { |
| static_assert(kMaxSize != 0, "AtomicBitSet requires non-zero size"); |
| |
| // Bits may signal something to other threads, hence relaxed is insufficient. |
| // Acq/Rel ensures a happens-before relationship. |
| static constexpr auto kAcq = std::memory_order_acquire; |
| static constexpr auto kRel = std::memory_order_release; |
| |
| public: |
| constexpr size_t MaxSize() const { return kMaxSize; } |
| |
| // No harm if `i` is already set. |
| void Set(size_t i) { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| bits_[idx].fetch_or(1ULL << mod, kRel); |
| } |
| |
| void Clear(size_t i) { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| bits_[idx].fetch_and(~(1ULL << mod), kRel); |
| HWY_DASSERT(!Get(i)); |
| } |
| |
| bool Get(size_t i) const { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| return ((bits_[idx].load(kAcq) & (1ULL << mod))) != 0; |
| } |
| |
| // Returns true if Get(i) would return true for any i in [0, kMaxSize). |
| bool Any() const { |
| for (const std::atomic<uint64_t>& bits : bits_) { |
| if (bits.load(kAcq)) return true; |
| } |
| return false; |
| } |
| |
| // Returns true if Get(i) would return true for all i in [0, kMaxSize). |
| bool All() const { |
| for (size_t idx = 0; idx < kNum64 - 1; ++idx) { |
| if (bits_[idx].load(kAcq) != ~uint64_t{0}) return false; |
| } |
| |
| constexpr size_t kRemainder = kMaxSize % 64; |
| const uint64_t last_bits = bits_[kNum64 - 1].load(kAcq); |
| if (kRemainder == 0) { |
| return last_bits == ~uint64_t{0}; |
| } |
| return PopCount(last_bits) == kRemainder; |
| } |
| |
| // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`! |
| size_t First() const { |
| HWY_DASSERT(Any()); |
| for (size_t idx = 0;; ++idx) { |
| HWY_DASSERT(idx < kNum64); |
| const uint64_t bits = bits_[idx].load(kAcq); |
| if (bits != 0) { |
| return idx * 64 + Num0BitsBelowLS1Bit_Nonzero64(bits); |
| } |
| } |
| } |
| |
| // Returns lowest i such that `!Get(i)`. Caller must first ensure `!All()`! |
| size_t First0() const { |
| HWY_DASSERT(!All()); |
| for (size_t idx = 0;; ++idx) { |
| HWY_DASSERT(idx < kNum64); |
| const uint64_t inv_bits = ~bits_[idx].load(kAcq); |
| if (inv_bits != 0) { |
| const size_t first0 = |
| idx * 64 + Num0BitsBelowLS1Bit_Nonzero64(inv_bits); |
| HWY_DASSERT(first0 < kMaxSize); |
| return first0; |
| } |
| } |
| } |
| |
| // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify |
| // the set, but the current Foreach call is only affected if changing one of |
| // the not yet visited uint64_t. |
| template <class Func> |
| void Foreach(const Func& func) const { |
| for (size_t idx = 0; idx < kNum64; ++idx) { |
| uint64_t remaining_bits = bits_[idx].load(kAcq); |
| while (remaining_bits != 0) { |
| const size_t i = Num0BitsBelowLS1Bit_Nonzero64(remaining_bits); |
| remaining_bits &= remaining_bits - 1; // clear LSB |
| func(idx * 64 + i); |
| } |
| } |
| } |
| |
| size_t Count() const { |
| size_t total = 0; |
| for (const std::atomic<uint64_t>& bits : bits_) { |
| total += PopCount(bits.load(kAcq)); |
| } |
| return total; |
| } |
| |
| private: |
| static constexpr size_t kNum64 = DivCeil(kMaxSize, size_t{64}); |
| std::atomic<uint64_t> bits_[kNum64] = {}; |
| }; |
| |
| // Two-level bitset for up to `kMaxSize` <= 4096 values. The iterators |
| // (`Any/First/Foreach/Count`) are more efficient than `BitSet` for sparse sets. |
| // This comes at the cost of slightly slower mutators (`Set/Clear`). |
| template <size_t kMaxSize = 4096> |
| class BitSet4096 { |
| static_assert(kMaxSize != 0, "BitSet4096 requires non-zero size"); |
| |
| public: |
| constexpr size_t MaxSize() const { return kMaxSize; } |
| |
| // No harm if `i` is already set. |
| void Set(size_t i) { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| bits_[idx].Set(mod); |
| nonzero_.Set(idx); |
| HWY_DASSERT(Get(i)); |
| } |
| |
| // Equivalent to Set(i) for i in [0, 64) where (bits >> i) & 1. This does |
| // not clear any existing bits. |
| void SetNonzeroBitsFrom64(uint64_t bits) { |
| bits_[0].SetNonzeroBitsFrom64(bits); |
| if (bits) nonzero_.Set(0); |
| } |
| |
| void Clear(size_t i) { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| bits_[idx].Clear(mod); |
| if (!bits_[idx].Any()) { |
| nonzero_.Clear(idx); |
| } |
| HWY_DASSERT(!Get(i)); |
| } |
| |
| bool Get(size_t i) const { |
| HWY_DASSERT(i < kMaxSize); |
| const size_t idx = i / 64; |
| const size_t mod = i % 64; |
| return bits_[idx].Get(mod); |
| } |
| |
| // Returns true if `Get(i)` would return true for any i in [0, kMaxSize). |
| bool Any() const { return nonzero_.Any(); } |
| |
| // Returns true if `Get(i)` would return true for all i in [0, kMaxSize). |
| bool All() const { |
| // Do not check `nonzero_.All()` - that only works if `kMaxSize` is 4096. |
| if (nonzero_.Count() != kNum64) return false; |
| return Count() == kMaxSize; |
| } |
| |
| // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`! |
| size_t First() const { |
| HWY_DASSERT(Any()); |
| const size_t idx = nonzero_.First(); |
| return idx * 64 + bits_[idx].First(); |
| } |
| |
| // Returns lowest i such that `!Get(i)`. Caller must first ensure `!All()`! |
| size_t First0() const { |
| HWY_DASSERT(!All()); |
| // It is likely not worthwhile to have a separate `BitSet64` for `not_all_`, |
| // hence iterate over all u64. |
| for (size_t idx = 0;; ++idx) { |
| HWY_DASSERT(idx < kNum64); |
| if (!bits_[idx].All()) { |
| const size_t first0 = idx * 64 + bits_[idx].First0(); |
| HWY_DASSERT(first0 < kMaxSize); |
| return first0; |
| } |
| } |
| } |
| |
| // Returns uint64_t(Get(i)) << i for i in [0, 64). |
| uint64_t Get64() const { return bits_[0].Get64(); } |
| |
| // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify |
| // the set, but the current Foreach call is only affected if changing one of |
| // the not yet visited BitSet64 for which Any() is true. |
| template <class Func> |
| void Foreach(const Func& func) const { |
| nonzero_.Foreach([&func, this](size_t idx) { |
| bits_[idx].Foreach([idx, &func](size_t mod) { func(idx * 64 + mod); }); |
| }); |
| } |
| |
| size_t Count() const { |
| size_t total = 0; |
| nonzero_.Foreach( |
| [&total, this](size_t idx) { total += bits_[idx].Count(); }); |
| return total; |
| } |
| |
| private: |
| static_assert(kMaxSize <= 64 * 64, "One BitSet64 insufficient"); |
| static constexpr size_t kNum64 = DivCeil(kMaxSize, size_t{64}); |
| BitSet64 nonzero_; |
| BitSet64 bits_[kNum64]; |
| }; |
| |
| } // namespace hwy |
| |
| #endif // HIGHWAY_HWY_BIT_SET_H_ |