/// @file dpf/bit_array.hpp /// @brief Packed bit arrays, static and dynamic, with bit proxies and iterators. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. #ifndef LIBDPF_INCLUDE_DPF_BIT_ARRAY_HPP__ #define LIBDPF_INCLUDE_DPF_BIT_ARRAY_HPP__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hedley/hedley.h" #include "portable-snippets/exact-int/exact-int.h" #include "portable-snippets/builtin/builtin.h" #include "portable-snippets/endian/endian.h" #include "dpf/utils.hpp" #include "dpf/bit.hpp" #include "dpf/aligned_allocator.hpp" namespace dpf { namespace detail { template HEDLEY_NO_THROW constexpr void check_one_bit(Word mask) noexcept { (void)mask; #if defined(__GNUC__) || defined(__clang__) if (__builtin_is_constant_evaluated()) return; #endif assert(mask != Word{0} && (mask & static_cast(mask - Word{1})) == Word{0}); } } // namespace detail template class setbit_index_iterable; // forward declaration template class bit_iterator_base; // forward reference template class bit_iterator; // forward reference template class const_bit_iterator; // forward reference /// @brief a base class for classes representing a sequence of bits /// @details A `bit_array` represents a sequence of bits. The underlying /// storage is an array of integers of type `dpf::bit_array::word_type`. /// @tparam ConcreteBitArrayT concrete bit array type /// @tparam WordT word used to pack bits template class bit_array_base { private: HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr auto derived_from_this() noexcept { return static_cast(this); } HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr auto derived_from_this() const noexcept { return static_cast(this); } public: class bit_reference; // forward reference /// @brief `dpf::bit` using value_type = dpf::bit; /// @brief proxy class representing a reference to a single bit. using reference = bit_array_base::bit_reference; /// @brief `dpf::bit` using const_reference = dpf::bit; /// @brief a random access iterator to `value_type`. /// @note convertible to `const_bit_iterator` using iterator = bit_iterator; /// @brief a random access iterator to `const value_type` using const_iterator = const_bit_iterator; /// @brief a type that simulates pointer-to-`value_type` behavior, /// identical to `iterator`. using pointer = iterator; /// @brief a type that simulates pointer-to-`const value_type` behavior, /// identical to `const_iterator`. using const_pointer = const_iterator; /// @brief a signed integral type, identical to /// `std::iterator_traits::difference_type`. using difference_type = std::ptrdiff_t; static_assert(std::is_integral_v && std::is_signed_v); /// @brief an unsigned integral type that can represent any non-negative /// value of `difference_type`. using size_type = std::size_t; static_assert(std::is_integral_v && std::is_unsigned_v); /// @brief an unsigned integral type used for the internal representation /// of bits. using word_type = WordT; static_assert(std::is_integral_v && std::is_unsigned_v); /// @brief pointer to `word_type` using word_pointer = std::add_pointer_t; /// @brief const pointer to `word_type` using const_word_pointer = std::add_pointer_t>; /// @brief the number of `dpf::bit`s represented by each `word_type`. /// @note guaranteed (via `static_assert`) to at most `64` bits using child_type = ConcreteBitArrayT; static constexpr size_type bits_per_word = utils::bitlength_of(); static constexpr size_type lg_bits_per_word = std::log2(bits_per_word); static_assert(bits_per_word <= 64, "bits_per_word greater than 64"); /// @brief default copy constructor inline constexpr bit_array_base(const bit_array_base &) = default; /// @brief default move constructor HEDLEY_NO_THROW inline constexpr bit_array_base(bit_array_base &&) noexcept = default; /// @brief default destructor ~bit_array_base() = default; /// @brief default copy assignment /// @return `*this` inline constexpr bit_array_base & operator=(const bit_array_base &) = default; /// @brief defaulted move assignment /// @return `*this` HEDLEY_NO_THROW inline constexpr bit_array_base & operator=(bit_array_base &&) noexcept = default; /// @brief direct access to the underlying data array /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr const_word_pointer data() const noexcept { return derived_from_this()->data(); } /// @brief direct access into the underlying data array (w/o bounds /// checking) /// @param pos the array element to access /// @note Does not perform bounds checking; behaviour is undefined if /// `pos` is out of bounds /// @return `data()[pos]` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr word_type data(size_type pos) const noexcept { return data()[pos]; } /// @brief direct access to the underlying data array /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr word_pointer data() noexcept { return derived_from_this()->data(); } /// @brief direct access into the underlying data array (w/o bounds /// checking) /// @param pos the array element to access /// @note Does not perform bounds checking; behaviour is undefined if /// `pos` is out of bounds /// @return `data()[pos]` HEDLEY_ALWAYS_INLINE constexpr word_type & data(size_type pos) noexcept { return data()[pos]; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr size_type data_length() const noexcept { return derived_from_this()->data_length(); } /// @brief access specified bit (w/o bounds checking) /// @{ /// @details accesses the bit at position `pos` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) /// @note Unlike `test` and `at`, does not throw exceptions: the behavior /// is undefined if `pos` is out of bounds /// @return an object of type `dpf::bit_array_base::reference`, which /// allows writing to the requested bit /// @complexity `O(1)` HEDLEY_NO_THROW inline constexpr reference operator[](size_type pos) noexcept { return reference{&data(pos / bits_per_word), static_cast(word_type(1) << (pos % bits_per_word))}; } /// @details accesses the bit at position `pos` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) /// @return the value of the requested bit /// @note Unlike `test` and `at`, does not throw exceptions: the behavior /// is undefined if `pos` is out of bounds /// @complexity `O(1)` HEDLEY_NO_THROW inline constexpr const_reference operator[](size_type pos) const noexcept { const word_type tmp = data(pos / bits_per_word); const word_type mask = static_cast(word_type(1) << (pos % bits_per_word)); return (tmp & mask) ? dpf::bit::one : dpf::bit::zero; } /// @} /// @brief access specified bit with bounds checking /// @{ /// @details accesses the bit at position `pos` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` /// @return an object of type `dpf::bit_array_base::reference`, which /// allows writing to the requested bit /// @complexity `O(1)` constexpr reference at(size_type pos) { utils::constexpr_maybe_throw( !(pos < size()), "pos is out of range"); return this->operator[](pos); } /// @details accesses the bit at position `pos` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) /// @return the value of the requested bit /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` /// @complexity `O(1)` constexpr const_reference at(size_type pos) const { utils::constexpr_maybe_throw( !(pos < size()), "pos is out of range"); return this->operator[](pos); } /// @} /// @brief returns an iterator to the first bit /// @{ /// @return iterator to the first element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr iterator begin() noexcept { auto *p = data(); if (p == nullptr) return iterator{}; return iterator{p, word_type(1)}; } /// @return iterator to the first element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator begin() const noexcept { auto *p = data(); if (p == nullptr) return const_iterator{}; return const_iterator{p, word_type(1)}; } /// @return iterator to the first element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator cbegin() const noexcept { return begin(); } /// @} /// @brief returns an iterator to the end (one past the last bit) /// @{ /// @return iterator to the element following the last element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr iterator end() noexcept { auto *p = data(); if (p == nullptr) return iterator{}; return iterator{p + (size() >> lg_bits_per_word), static_cast(word_type(1) << (size() % bits_per_word))}; } /// @return iterator to the element following the last element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator end() const noexcept { auto *p = data(); if (p == nullptr) return const_iterator{}; return const_iterator{p + (size() >> lg_bits_per_word), static_cast(word_type(1) << (size() % bits_per_word))}; } /// @return iterator to the element following the last element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator cend() const noexcept { return end(); } /// @} /// @brief checks if the specified bit is set to `true` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) /// @return `true` if the requested bit is set, `false` otherwise /// @complexity `O(1)` bool test(size_type pos) const { return at(pos); } /// @brief checks if all, any or none of the bits are set to `true` /// @{ /// @details checks if all bits are set to `true` /// @return `true` if all of the bits are set to `true`, otherwise `false` /// @complexity `O(size())` HEDLEY_NO_THROW bool all() const noexcept { if (size() == 0) return true; const size_type n = data_length(); const size_type rem = size() % bits_per_word; const word_type *p = data(); const size_type full = (rem == 0) ? n : n - 1; const word_type ones = static_cast(~word_type{0}); for (size_type i = 0; i < full; ++i) if (p[i] != ones) return false; if (rem != 0) { const word_type m = low_bits_mask(rem); if (static_cast(p[n - 1] & m) != m) return false; } return true; } /// @details checks if all bits in a range are set to `true` /// @tparam Iterator an iterator type /// @param first,last the range of elements under consideration /// @return `true` if all of the bits in the given range are set to `true`, otherwise `false` /// @complexity `O(last-first)` template HEDLEY_NO_THROW bool all(Iterator first, Iterator last) const noexcept { bool ok = true; for_each_span(first, last, [&](word_type bits, word_type relevant) { if (bits != relevant) ok = false; return ok; }); return ok; } /// @details checks if any bits are set to `true` /// @return `true` if any of the bits are set to `true`, otherwise `false` /// @complexity `O(size())` HEDLEY_NO_THROW bool any() const noexcept { const size_type n = data_length(); if (n == 0 || size() == 0) return false; const size_type rem = size() % bits_per_word; const word_type *p = data(); const size_type full = (rem == 0) ? n : n - 1; for (size_type i = 0; i < full; ++i) if (p[i] != word_type{0}) return true; if (rem != 0) return static_cast(p[n - 1] & low_bits_mask(rem)) != word_type{0}; return false; } /// @details checks if any bits in a range are set to `true` /// @tparam Iterator an iterator type /// @param first,last the range of elements under consideration /// @return `true` if any of the bits in the given range are set to `true`, otherwise `false` /// @complexity `O(last-first)` template HEDLEY_NO_THROW bool any(Iterator first, Iterator last) const noexcept { bool found = false; for_each_span(first, last, [&](word_type bits, word_type) { if (bits != word_type{0}) found = true; return !found; }); return found; } /// @details checks if none of the bits are set to `true` /// @return `true` if none of the bits are set to `true`, otherwise `false` /// @complexity `O(size())` HEDLEY_NO_THROW bool none() const noexcept { return !any(); } /// @details checks if none bits in a range are set to `true` /// @tparam Iterator an iterator type /// @param first,last the range of elements under consideration /// @return `true` if none of the bits in the given range are set to `true`, otherwise `false` /// @complexity `O(last-first)` template HEDLEY_NO_THROW bool none(Iterator first, Iterator last) const noexcept { return !any(first, last); } /// @} /// @brief returns the number of bits set to `true` /// @{ /// @details counts the number of bits that are set to `true` /// @return the number of bits set to `true` /// @complexity `O(size())` HEDLEY_NO_THROW size_type count() const noexcept { const size_type n = data_length(); if (n == 0 || size() == 0) return 0; const size_type rem = size() % bits_per_word; const word_type *p = data(); const size_type full = (rem == 0) ? n : n - 1; size_type sum = 0; for (size_type i = 0; i < full; ++i) sum += pop(p[i]); if (rem != 0) sum += pop(static_cast(p[n - 1] & low_bits_mask(rem))); return sum; } /// @details counts the number of bits in a range that are set to `true` /// @tparam Iterator an iterator type /// @param first,last the range of elements under consideration /// @return the number of bits in the given range that are set to `true` /// @complexity `O(last-first)` template HEDLEY_NO_THROW size_type count(Iterator first, Iterator last) const noexcept { size_type sum = 0; for_each_span(first, last, [&](word_type bits, word_type) { sum += pop(bits); return true; }); return sum; } /// @} /// @brief returns the parity of all stored bits /// @{ /// @details counts the parity of all stored bits /// @return the parity of all stored bits /// @complexity `O(size())` HEDLEY_NO_THROW size_type parity() const noexcept { const size_type n = data_length(); if (n == 0 || size() == 0) return 0; const size_type rem = size() % bits_per_word; const word_type *p = data(); const size_type full = (rem == 0) ? n : n - 1; word_type x = word_type{0}; for (size_type i = 0; i < full; ++i) x ^= p[i]; if (rem != 0) x ^= static_cast(p[n - 1] & low_bits_mask(rem)); return utils::parity(x); } /// @details counts the parity of bits in a range /// @tparam Iterator an iterator type /// @param first,last the range of elements under consideration /// @return the parity of all bits in the given range /// @complexity `O(last-first)` template HEDLEY_NO_THROW size_type parity(Iterator first, Iterator last) const noexcept { word_type x = word_type{0}; for_each_span(first, last, [&](word_type bits, word_type) { x ^= bits; return true; }); return utils::parity(x); } /// @} /// @brief returns the number of bits /// @return number of bits that the `bit_array_base` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE HEDLEY_ALWAYS_INLINE constexpr size_type size() const noexcept { return derived_from_this()->size(); } /// @brief sets bits to `true` or given value /// @{ /// @brief sets all bits to `true` /// @complexity `O(size())` HEDLEY_NO_THROW constexpr void set() noexcept { const size_type n = data_length(); if (n == 0) return; const size_type rem = size() % bits_per_word; word_type *p = data(); const word_type ones = static_cast(~word_type{0}); if (rem == 0) { for (size_type i = 0; i < n; ++i) p[i] = ones; return; } for (size_type i = 0; i + 1 < n; ++i) p[i] = ones; p[n - 1] = low_bits_mask(rem); } /// @brief sets the bit at position `pos` to the value `value` /// @param pos the 0-based position of the bit to set (least significant /// to most significant) /// @param value the value to set the bit to /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` /// @complexity `O(1)` constexpr void set(size_type pos, bool value = true) { at(pos) = value; } /// @brief sets the bit at position `pos` to the value `value` /// @param pos the 0-based position of the bit to set (least significant /// to most significant) /// @param value the value to set the bit to /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` /// @complexity `O(1)` constexpr void unchecked_set(size_type pos, bool value = true) { this->operator[](pos) = value; } /// @} /// @brief sets bits to `false` /// @{ /// @brief sets all bits to `false' /// @complexity `O(size())` HEDLEY_NO_THROW constexpr void unset() noexcept { word_type *p = data(); const size_type n = data_length(); for (size_type i = 0; i < n; ++i) p[i] = word_type{0}; } /// @brief sets the bit at position `pos` to `false` /// @param pos the 0-based position of the bit to unset (least /// significant to most significant) /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` /// @complexity `O(1)` constexpr void unset(size_type pos) { at(pos) = dpf::bit::zero; } /// @} constexpr void unchecked_unset(size_type pos) { this->operator[](pos) = dpf::bit::zero; } /// @brief toggles the values of bits /// @{ /// @brief flips all bits (like `operator~`, but in-place) /// @complexity `O(size())` HEDLEY_NO_THROW constexpr void flip() noexcept { const size_type n = data_length(); if (n == 0) return; word_type *p = data(); for (size_type i = 0; i < n; ++i) p[i] = static_cast(~p[i]); const size_type rem = size() % bits_per_word; if (rem != 0) p[n - 1] &= low_bits_mask(rem); } /// @brief flips the bit at the position `pos` /// @param pos the 0-based position of the bit to flip (least /// significant to most significant) /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` /// @complexity `O(1)` constexpr void flip(size_type pos) { at(pos).flip(); } /// @} /// @brief returns a string representation of the data /// @details converts the contents of the `bit_array_base` to a string. Uses /// `zero` to represent bits with value `false` and `one` to /// represent bits with value `true`. The resulting string /// contains `size()` characters with the first character /// corresponding to the last `(size()-1th)` bit and the last /// character corresponding tot he first `(0th)` bit. /// @tparam CharT character type /// @tparam Traits character traits /// @tparam Allocator allocator type /// @param zero character to use to represent `false`/`0` (default: ``CharT('0')``) /// @param one character to use to represent `true`/`1` (default: ``CharT('1')``) /// @return the converted string /// @throws May throw `std::bad_alloc` from the `std::string` constructor. /// @complexity `O(size())` template , typename Allocator = std::allocator> std::basic_string to_string( CharT zero = CharT('0'), CharT one = CharT('1')) const { const size_type n = this->size(); std::basic_string s(n, zero); for (size_type i = 0; i < n; ++i) { if (this->operator[](n - 1 - i)) s[i] = one; } return s; } /// @brief proxy class representing a reference to a bit /// @details This class is used as a proxy object to allow users to /// interact with individual bits of a ``bit_array_base`, since /// standard C++ types have insufficient precision to specify /// individual bits. /// @note The primary use of ``bit_array_base::bit_reference`` is to /// provide an l-value that can be returned from /// `bit_array_base::operator[]`. class bit_reference { public: using word_type = bit_array_base::word_type; using word_pointer = bit_array_base::word_pointer; /// @brief (deleted) default c'tor inline bit_reference() = delete; /// @brief copy c'tor HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr bit_reference(const bit_reference &) noexcept = default; /// @brief move c'tor HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr bit_reference(bit_reference &&) noexcept = default; HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference & operator=(bool b) noexcept { this->assign(b); return *this; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference & operator=(const bit_reference & b) noexcept { this->assign(static_cast(b)); return *this; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference & operator=(bit_reference && b) noexcept { this->assign(static_cast(b)); return *this; } ~bit_reference() = default; HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr operator bool() const noexcept { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); return !(!(*word_ptr_ & mask_)); } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr operator int() const noexcept { return int{static_cast(*this)}; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr operator dpf::bit() const noexcept { return dpf::bit{static_cast(*this)}; } /// @brief XOR. Exact match so `bit_reference - bit_reference` is not /// ambiguous with integer subtraction of the proxy. /// @param lhs the left-hand operand /// @param rhs the right-hand operand /// @return XOR friend constexpr dpf::bit operator-(bit_reference lhs, bit_reference rhs) noexcept { return static_cast(static_cast(lhs) ^ static_cast(rhs)); } HEDLEY_NO_THROW friend constexpr dpf::bit operator+(bit_reference lhs, bit_reference rhs) noexcept { return static_cast(static_cast(lhs) ^ static_cast(rhs)); } /// @brief performs binary AND, OR, XOR and NOT /// @{ /// @details sets `*this` to the result of binary AND on `*this` and `b` /// @param b the other bit /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference & operator&=(bool b) noexcept { if (b == false) *word_ptr_ &= ~this->mask_; return *this; } /// @details sets `*this` to the result of binary OR on `*this` and `b` /// @param b the other bit /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference & operator|=(bool b) noexcept { if (b == true) *word_ptr_ |= this->mask_; return *this; } /// @details sets `*this` to the result of binary XOR on `*this` and `b` /// @param b the other bit /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference & operator^=(bool b) noexcept { if (b == true) *word_ptr_ ^= this->mask_; return *this; } /// @details returns a temporary copy of `*this` with its value flipped (binary NOT) /// @return the flipped bit /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr dpf::bit operator~() const noexcept { return dpf::bit{!static_cast(*this)}; } /// @} /// @brief sets to the referenced bit to 1 /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference& set() noexcept { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); *word_ptr_ |= mask_; return *this; } /// @brief unsets the referenced bit to 0 /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference& unset() noexcept { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); *word_ptr_ &= static_cast(~mask_); return *this; } /// @brief assigns `b ? 1 : 0` to the referenced bit /// @param b the `b` /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference& assign(bool b) noexcept { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); *word_ptr_ = b ? (*word_ptr_ | mask_) : (*word_ptr_ & static_cast(~mask_)); return *this; } /// @brief flips the referenced bit /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bit_reference& flip() noexcept { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); *word_ptr_ ^= mask_; return *this; } /// @brief Exchange the bits named by two proxies, including temporaries /// returned from `operator[]` and `operator*`. /// @param a the `a` /// @param b the `b` HEDLEY_NO_THROW friend constexpr void swap(bit_reference a, bit_reference b) noexcept { const bool tmp = static_cast(a); a = static_cast(b); b = tmp; } private: word_pointer word_ptr_; //< pointer to word holding the bit. word_type mask_; //< bitmask for referenced bit within `*word_ptr`. /// @brief private c'tor. /// @param word_ptr pointer to the word holding the actual bit. /// @param mask bitmask indicating referenced bit of `*word_ptr`. /// @note `word_ptr` must not be `nullptr`. /// @note `mask` must be `1ull << b` for some `0 <= b < 64`. /// @note accessible only to `dpf::bit_array_base` and its iterators. HEDLEY_NO_THROW HEDLEY_NON_NULL() HEDLEY_ALWAYS_INLINE constexpr bit_reference(word_pointer word_ptr, word_type mask) noexcept : word_ptr_{word_ptr}, mask_{mask} { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); } template friend std::basic_ostream & operator<<(std::basic_ostream & os, bit_reference bit) { return os << dpf::to_string(bit); } friend class bit_array_base; //< access to c'tor friend class bit_iterator_base; //< access to c'tor friend class bit_iterator; //< access to c'tor friend class const_bit_iterator; //< access to c'tor }; // class bit_array_base::bit_reference protected: /// @brief constructs a `bit_array_base` inline constexpr bit_array_base() = default; /// @brief an all-`1`s sentinel word marking the end of the data /// @note `sentinel` exists to assist `setbit_index_iterator` in deciding /// if it has hit the end of the data array static constexpr word_type sentinel = ~word_type(0); /// @brief Low `n` bits set. `n == 0` yields 0. `n >= bits_per_word` yields all ones. /// @param n the `n` /// @return Low `n` bits set HEDLEY_NO_THROW static constexpr word_type low_bits_mask(size_type n) noexcept { if (n == 0) return word_type{0}; if (n >= bits_per_word) return static_cast(~word_type{0}); return static_cast( static_cast(~word_type{0}) >> (bits_per_word - n)); } HEDLEY_NO_THROW static constexpr size_type pop(word_type w) noexcept { return static_cast(utils::popcount(w)); } /// @brief Bits strictly below the single set bit in `mask`. /// @param mask the bit mask /// @return Bits strictly below the single set bit in `mask` HEDLEY_NO_THROW static constexpr word_type bits_below(word_type mask) noexcept { return static_cast(mask - word_type{1}); } /// @brief Bits at and above the single set bit in `mask`. /// @param mask the bit mask /// @return Bits at and above the single set bit in `mask` HEDLEY_NO_THROW static constexpr word_type bits_at_and_above(word_type mask) noexcept { return static_cast(~bits_below(mask)); } /// @brief Invoke `fn(masked_bits, relevant_mask)` for each limb touched by /// `[first, last)`. Does not dereference a one-past-the-end word. /// `fn` returns false to stop early. /// @tparam Iterator iterator type /// @tparam Fn fn /// @param first the first element of the range /// @param last the past-the-end element of the range /// @param fn the `fn` template void for_each_span(Iterator first, Iterator last, Fn fn) const { if (first == last) return; const word_type *base = data(); const word_type *limit = base + data_length(); const word_type *fw = first.word_ptr_; const word_type *lw = last.word_ptr_; const word_type fm = first.mask_; const word_type lm = last.mask_; auto contained = [base, limit](const word_type *p) { return p >= base && p < limit; }; auto emit = [&](const word_type *p, word_type relevant) -> bool { if (!contained(p) || relevant == word_type{0}) return true; return fn(static_cast(*p & relevant), relevant); }; if (fw == lw) { const word_type span = static_cast( bits_below(lm) & bits_at_and_above(fm)); emit(fw, span); return; } if (!emit(fw, bits_at_and_above(fm))) return; const word_type *mid = fw + 1; const word_type *mid_end = (lw < limit) ? lw : limit; if (mid < base) mid = base; const word_type ones = static_cast(~word_type{0}); for (const word_type *p = mid; p < mid_end; ++p) { if (!fn(*p, ones)) return; } emit(lw, bits_below(lm)); } }; // class bit_array_base /// @brief a base class provided to simplify the definition of /// `bit_iterator` and `const_bit_iterator` /// @tparam ConcreteBitArrayT concrete bit array type /// @tparam WordT word used to pack bits template class bit_iterator_base { public: using difference_type = std::ptrdiff_t; using iterator_category = std::random_access_iterator_tag; using word_type = typename bit_array_base::word_type; using word_pointer = typename bit_array_base::word_pointer; static constexpr auto bits_per_word = bit_array_base::bits_per_word; inline constexpr bool operator==(const bit_iterator_base & rhs) const { return (word_ptr_ == rhs.word_ptr_) && (mask_ == rhs.mask_); } inline constexpr bool operator<(const bit_iterator_base & rhs) const { return word_ptr_ < rhs.word_ptr_ || (word_ptr_ == rhs.word_ptr_ && mask_ < rhs.mask_); } inline constexpr bool operator!=(const bit_iterator_base & rhs) const { return !(*this == rhs); } inline constexpr bool operator>(const bit_iterator_base & rhs) const { return rhs < *this; } inline constexpr bool operator<=(const bit_iterator_base & rhs) const { return !(rhs < *this); } inline constexpr bool operator>=(const bit_iterator_base & rhs) const { return !(*this < rhs); } protected: /// @brief bitmask for the least-significant bit of a word static constexpr word_type lsb = word_type(1); /// @brief bitmask for the most-significant bit of a word static constexpr word_type msb = ~(~word_type(0) >> 1); /// @brief pointer to the word containing the current iteration bit word_pointer word_ptr_; /// @brief mask into `*word_ptr_` to obtain current iteration bit word_type mask_; /// @brief Singular iterator. Comparable, not dereferenceable. HEDLEY_NO_THROW inline constexpr bit_iterator_base() noexcept : word_ptr_{nullptr}, mask_{lsb} { } /// @brief constructs a `bit_iterator_base` /// @{ /// @brief constructs a `bit_iterator_base` /// @param word_ptr pointer to the word containing the first iteration /// bit /// @note `word_ptr` must be dereferencable (not `nullptr`) HEDLEY_NO_THROW HEDLEY_NON_NULL() inline constexpr explicit bit_iterator_base(word_pointer word_ptr) noexcept : word_ptr_{word_ptr}, mask_{lsb} { assert(word_ptr_ != nullptr); } /// @brief constructs a `bit_iterator_base` /// @param word_ptr pointer to the word containing the first iteration /// bit /// @param mask bitmask to obtain `*word_ptr` first iteration bit from /// `word_ptr` /// @note `word_ptr` must be dereferencable (not `nullptr`) /// @note `mask` must have exactly one bit set HEDLEY_NO_THROW HEDLEY_NON_NULL() inline constexpr bit_iterator_base(word_pointer word_ptr, word_type mask) noexcept : word_ptr_{word_ptr}, mask_{mask} { assert(word_ptr_ != nullptr); detail::check_one_bit(mask_); } /// @} /// @brief increments the iterator by one bit HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW void constexpr increment() noexcept { if (HEDLEY_UNLIKELY(!(mask_ <<= 1))) { mask_ = lsb; ++word_ptr_; } } /// @brief decrements the iterator by one bit HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW void constexpr decrement() noexcept { if (HEDLEY_UNLIKELY(!(mask_ >>= 1))) { mask_ = msb; --word_ptr_; } } /// @brief increments the iterator by specified number of bits /// @param amt the number of bits to increment by HEDLEY_NO_THROW inline constexpr void increment_by(difference_type amt) noexcept { if (amt == 0) return; // Stay in signed arithmetic. `ctz` returns `size_t`, and dividing a // negative `ptrdiff_t` by that unsigned width walks the pointer the // wrong direction. const auto bpw = static_cast(bits_per_word); const auto bit = static_cast(utils::ctz(mask_)); difference_type offset = amt + bit; difference_type words = offset / bpw; difference_type rem = offset % bpw; if (rem < 0) { rem += bpw; --words; } word_ptr_ += words; mask_ = static_cast(static_cast(1) << rem); } /// @brief decrements the iterator by specified number of bits /// @param amt the number of bits to decrement by HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW void constexpr decrement_by(difference_type amt) noexcept { increment_by(-amt); } friend constexpr difference_type operator-(const bit_iterator_base & lhs, const bit_iterator_base & rhs) noexcept { const auto bpw = static_cast( bit_iterator_base::bits_per_word); const auto lb = static_cast(utils::ctz(lhs.mask_)); const auto rb = static_cast(utils::ctz(rhs.mask_)); return bpw * (lhs.word_ptr_ - rhs.word_ptr_) + (lb - rb); } friend class setbit_index_iterable; }; // class bit_iterator_base template class bit_iterator final : public bit_iterator_base { private: using base = bit_iterator_base; public: using difference_type = std::ptrdiff_t; using value_type = typename bit_array_base::value_type; using reference = typename bit_array_base::reference; using const_reference = typename bit_array_base::const_reference; using pointer = std::add_pointer_t; using iterator = bit_iterator; using word_type = typename bit_array_base::word_type; using word_pointer = typename bit_array_base::word_pointer; /// @brief Singular iterator. Comparable, not dereferenceable. HEDLEY_NO_THROW constexpr bit_iterator() noexcept = default; HEDLEY_NO_THROW inline constexpr bit_iterator(const bit_iterator &) noexcept = default; HEDLEY_NO_THROW inline constexpr bit_iterator(bit_iterator &&) noexcept = default; HEDLEY_NO_THROW HEDLEY_NON_NULL() inline constexpr explicit bit_iterator(word_pointer word_ptr) noexcept : base{word_ptr} { } HEDLEY_NO_THROW HEDLEY_NON_NULL() inline constexpr explicit bit_iterator(word_pointer word_ptr, word_type mask) noexcept : base{word_ptr, mask} { } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW bit_iterator & operator=(const bit_iterator &) noexcept = default; HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW bit_iterator & operator=(bit_iterator &&) noexcept = default; ~bit_iterator() = default; HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr reference operator*() const noexcept { return reference{base::word_ptr_, base::mask_}; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr iterator & operator++() noexcept { base::increment(); return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr iterator operator++(int) noexcept { iterator tmp = *this; base::increment(); return tmp; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr iterator & operator--() noexcept { base::decrement(); return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr iterator operator--(int) noexcept { iterator tmp = *this; base::decrement(); return tmp; } inline constexpr iterator & operator+=(difference_type amt) { base::increment_by(amt); return *this; } inline constexpr iterator & operator-=(difference_type amt) { base::decrement_by(amt); return *this; } inline constexpr iterator operator+(difference_type amt) const { iterator tmp = *this; return tmp += amt; } inline constexpr iterator operator-(difference_type amt) const { iterator tmp = *this; return tmp -= amt; } HEDLEY_NO_THROW friend constexpr iterator operator+(difference_type amt, iterator it) noexcept { return it + amt; } inline constexpr reference operator[](difference_type i) { return *(*this + i); } inline constexpr const_reference operator[](difference_type i) const { return *(*this + i); } friend class bit_array_base; friend class const_bit_iterator; }; // class bit_iterator template class const_bit_iterator final : public bit_iterator_base { private: using base = bit_iterator_base; public: using difference_type = std::ptrdiff_t; using value_type = typename bit_array_base::value_type; using reference = typename bit_array_base::const_reference; using const_reference = typename bit_array_base::const_reference; using pointer = std::add_pointer_t; using iterator = const_bit_iterator; using const_iterator = const_bit_iterator; using word_type = typename bit_array_base::word_type; using word_pointer = typename bit_array_base::word_pointer; using const_word_pointer = typename bit_array_base::const_word_pointer; /// @brief Singular iterator. Comparable, not dereferenceable. HEDLEY_NO_THROW constexpr const_bit_iterator() noexcept = default; HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW const_bit_iterator(const const_bit_iterator &) noexcept = default; HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW const_bit_iterator(const_bit_iterator &&) noexcept = default; HEDLEY_NO_THROW HEDLEY_NON_NULL() inline constexpr explicit const_bit_iterator(const_word_pointer word_ptr) noexcept : base{word_ptr} { } HEDLEY_NO_THROW HEDLEY_NON_NULL() inline constexpr explicit const_bit_iterator(const_word_pointer word_ptr, word_type mask) noexcept : base{const_cast(word_ptr), mask} { } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW const_bit_iterator & operator=(const const_bit_iterator &) noexcept = default; HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW const_bit_iterator & operator=(const_bit_iterator &&) noexcept = default; HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr explicit const_bit_iterator(const bit_iterator & to_copy) noexcept : base{to_copy.word_ptr_, to_copy.mask_} {} HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr explicit const_bit_iterator(bit_iterator && to_copy) noexcept : base{to_copy.word_ptr_, to_copy.mask_} {} ~const_bit_iterator() = default; HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr const_reference operator*() const noexcept { using bitref = typename bit_array_base::bit_reference; return bitref{base::word_ptr_, base::mask_}; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr const_iterator & operator++() noexcept { base::increment(); return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr const_iterator operator++(int) noexcept { const_iterator tmp = *this; base::increment(); return tmp; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr const_iterator & operator--() noexcept { base::decrement(); return *this; } HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr const_iterator operator--(int) noexcept { const_iterator tmp = *this; base::decrement(); return tmp; } inline constexpr const_iterator & operator+=(difference_type amt) noexcept { base::increment_by(amt); return *this; } HEDLEY_NO_THROW inline constexpr const_iterator & operator-=(difference_type amt) noexcept { base::decrement_by(amt); return *this; } HEDLEY_NO_THROW inline constexpr const_iterator operator+(difference_type amt) const noexcept { const_iterator tmp = *this; return tmp += amt; } HEDLEY_NO_THROW inline constexpr const_iterator operator-(difference_type amt) const noexcept { const_iterator tmp = *this; return tmp -= amt; } HEDLEY_NO_THROW friend constexpr const_iterator operator+(difference_type amt, const_iterator it) noexcept { return it + amt; } HEDLEY_NO_THROW inline constexpr const_reference operator[](difference_type i) const noexcept { return *(*this + i); } friend class bit_array_base; }; // class bit_array_base::const_bit_iterator template class alignas(utils::max_align_v) static_bit_array final : public bit_array_base, WordT> { private: using base = bit_array_base, WordT>; public: using size_type = typename base::size_type; using word_pointer = typename base::word_pointer; using const_word_pointer = typename base::const_word_pointer; using word_type = typename base::word_type; static constexpr auto bits_per_word = base::bits_per_word; private: /// @brief the number of `word_type`s are being used to represent the /// `size()` bits static constexpr size_type data_length_ = utils::quotient_ceiling(Nbits, bits_per_word); public: HEDLEY_NO_THROW constexpr static_bit_array(static_bit_array &&) noexcept = default; HEDLEY_NO_THROW constexpr static_bit_array(const static_bit_array &) noexcept = default; ~static_bit_array() = default; HEDLEY_NO_THROW constexpr static_bit_array & operator=(static_bit_array &&) noexcept = default; HEDLEY_NO_THROW constexpr static_bit_array & operator=(const static_bit_array &) noexcept = default; /// @brief constructs a zeroed `static_bit_array` that holds `Nbits` bits inline constexpr static_bit_array() : data_{} { data_[data_length_] = base::sentinel; } /// @brief constructs a `static_bit_array` from the low bits of `val` /// @param val the `val` inline constexpr explicit static_bit_array(std::size_t val) : data_{} { if constexpr (data_length_ > 0) { data_[0] = static_cast(val); // The partial limb is the last word. Word 0 is partial only when // the whole array fits in one word. if constexpr (data_length_ == 1 && Nbits % bits_per_word != 0) data_[0] &= base::low_bits_mask(Nbits % bits_per_word); } data_[data_length_] = base::sentinel; } /// @brief direct access to the underlying data array /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr word_pointer data() noexcept { return static_cast(__builtin_assume_aligned(std::data(data_), utils::max_align_v)); } /// @brief direct access to the underlying data array /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr const_word_pointer data() const noexcept { return static_cast(__builtin_assume_aligned(std::data(data_), utils::max_align_v)); } /// @brief length of the underlying data array /// @return the number of elements in the underlying array (excluding a /// non-data sentinel element) HEDLEY_ALWAYS_INLINE constexpr size_type data_length() const noexcept { return data_length_; } /// @brief returns the number of bits /// @return number of bits that the `static_bit_array` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE HEDLEY_ALWAYS_INLINE constexpr size_type size() const noexcept { return Nbits; } private: alignas(utils::max_align_v) std::array data_; }; template class dynamic_bit_array : public bit_array_base, WordT> { private: using base = bit_array_base, WordT>; public: using size_type = typename base::size_type; using word_pointer = typename base::word_pointer; using const_word_pointer = typename base::const_word_pointer; using word_type = typename base::word_type; static constexpr auto bits_per_word = base::bits_per_word; private: using allocator = aligned_allocator; using unique_ptr = typename allocator::unique_ptr; public: /// @brief constructs a zeroed `dynamic_bit_array` that holds `nbits` bits /// @param nbits the width in bits /// @param alloc the `alloc` /// @throws std::bad_alloc if allocating storage fails inline explicit dynamic_bit_array(std::size_t nbits, allocator alloc = allocator{}) : num_bits_{nbits}, data_length_{utils::quotient_ceiling(nbits, bits_per_word)}, data_{alloc.allocate_unique_ptr(data_length_+1)} { if (HEDLEY_UNLIKELY(data_ == nullptr)) throw std::bad_alloc{}; std::fill_n(data_.get(), data_length_, word_type{0}); data_[data_length_] = base::sentinel; } dynamic_bit_array(const dynamic_bit_array & other) : num_bits_{other.num_bits_}, data_length_{other.data_length_}, data_{other.data_ ? allocator{}.allocate_unique_ptr(other.data_length_ + 1) : unique_ptr{}} { if (data_) std::copy_n(other.data_.get(), data_length_ + 1, data_.get()); } HEDLEY_NO_THROW dynamic_bit_array(dynamic_bit_array && other) noexcept : num_bits_{std::exchange(other.num_bits_, 0)}, data_length_{std::exchange(other.data_length_, 0)}, data_{std::move(other.data_)} { } dynamic_bit_array & operator=(const dynamic_bit_array & other) { if (this != &other) { dynamic_bit_array tmp(other); *this = std::move(tmp); } return *this; } HEDLEY_NO_THROW dynamic_bit_array & operator=(dynamic_bit_array && other) noexcept { if (this != &other) { wipe(); data_ = std::move(other.data_); num_bits_ = std::exchange(other.num_bits_, 0); data_length_ = std::exchange(other.data_length_, 0); } return *this; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE ~dynamic_bit_array() noexcept { wipe(); } /// @brief direct access to the underlying data array /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW word_pointer data() noexcept { auto *raw = data_.get(); if (raw == nullptr) return nullptr; return static_cast(__builtin_assume_aligned(raw, utils::max_align_v)); } /// @brief direct access to the underlying data array /// @param i the `i` /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW word_type & data(std::size_t i) noexcept { return this->data()[i]; } /// @brief direct access to the underlying data array /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW const_word_pointer data() const noexcept { auto *raw = data_.get(); if (raw == nullptr) return nullptr; return static_cast(__builtin_assume_aligned(raw, utils::max_align_v)); } /// @brief direct access to the underlying data array /// @param i the `i` /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW const word_type & data(std::size_t i) const noexcept { return this->data()[i]; } /// @brief length of the underlying data array /// @return the number of elements in the underlying array (excluding a /// non-data sentinel element) HEDLEY_ALWAYS_INLINE constexpr size_type data_length() const noexcept { return data_length_; } /// @brief returns the number of bits /// @return number of bits that the `dynamic_bit_array` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE HEDLEY_ALWAYS_INLINE constexpr size_type size() const noexcept { return num_bits_; } private: /// @brief Store zeros through `volatile` so the wipe is not deleted as a dead store. HEDLEY_NO_THROW void wipe() noexcept { if (!data_) return; volatile word_type *p = data_.get(); for (size_type i = 0; i < data_length_ + 1; ++i) p[i] = word_type{0}; } size_type num_bits_; /// @brief the number of `word_type`s are being used to represent the /// `size()` bits size_type data_length_; unique_ptr data_; }; /// @brief Exchanges the bits named by two `dynamic_bit_array` proxies. /// @tparam WordT word used to pack bits /// @param lhs the left-hand operand /// @param rhs the right-hand operand template HEDLEY_NO_THROW inline constexpr void swap(typename dynamic_bit_array::reference lhs, typename dynamic_bit_array::reference rhs) noexcept { bool tmp = lhs; lhs = static_cast(rhs); rhs = tmp; } /// @brief Exchanges the bits named by two `static_bit_array` proxies. /// @tparam Nbits width in bits /// @tparam WordT word used to pack bits /// @param lhs the left-hand operand /// @param rhs the right-hand operand template HEDLEY_NO_THROW inline constexpr void swap(typename static_bit_array::reference lhs, typename static_bit_array::reference rhs) noexcept { bool tmp = lhs; lhs = static_cast(rhs); rhs = tmp; } namespace utils { template struct is_bit_array> : std::true_type {}; template struct is_bit_array> : std::true_type {}; template struct is_bit_array> : std::true_type {}; } // namespace utils } // namespace dpf namespace std { template struct iterator_traits> { private: using type = dpf::bit_iterator; public: using iterator_category = typename type::iterator_category; using difference_type = typename type::difference_type; using value_type = typename type::value_type; using reference = typename type::reference; using const_reference = typename type::const_reference; using pointer = typename type::pointer; }; template struct iterator_traits> { private: using type = dpf::const_bit_iterator; public: using iterator_category = typename type::iterator_category; using difference_type = typename type::difference_type; using value_type = typename type::value_type; using reference = typename type::reference; using const_reference = typename type::const_reference; using pointer = typename type::pointer; }; } // namespace std #endif // LIBDPF_INCLUDE_DPF_BIT_ARRAY_HPP__