diff --git a/huffman/BUILD.bazel b/huffman/BUILD.bazel index f7cc9a7..a0391cb 100644 --- a/huffman/BUILD.bazel +++ b/huffman/BUILD.bazel @@ -4,6 +4,7 @@ cc_library( name = "huffman", srcs = [ "src/bit.hpp", + "src/bit_span.hpp", "src/code.hpp", "src/detail/base_view.hpp", "src/detail/iterator_interface.hpp", diff --git a/huffman/huffman.hpp b/huffman/huffman.hpp index 867d8e7..986be4b 100644 --- a/huffman/huffman.hpp +++ b/huffman/huffman.hpp @@ -1,6 +1,7 @@ #pragma once #include "huffman/src/bit.hpp" +#include "huffman/src/bit_span.hpp" #include "huffman/src/code.hpp" #include "huffman/src/encoding.hpp" #include "huffman/src/table.hpp" diff --git a/huffman/src/bit.hpp b/huffman/src/bit.hpp index 670cfe6..a0fc4f4 100644 --- a/huffman/src/bit.hpp +++ b/huffman/src/bit.hpp @@ -1,3 +1,4 @@ + #pragma once #include diff --git a/huffman/src/bit_span.hpp b/huffman/src/bit_span.hpp new file mode 100644 index 0000000..5e0843b --- /dev/null +++ b/huffman/src/bit_span.hpp @@ -0,0 +1,92 @@ + +#include "huffman/src/bit.hpp" +#include "huffman/src/detail/iterator_interface.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace gpu_deflate::huffman { + /// A non-owning span of bits. Allows for iteration over the individual bits. +class bit_span +{ + + const std::byte* data_; + size_t bit_size_; + +public: + /// An iterator over the bits in a bit_span. + class iterator : public detail::iterator_interface + { + private: + const bit_span* parent_; + size_t offset_; + + public: + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + using value_type = bit; + using reference = bit; + using pointer = void; + + iterator() = default; + constexpr iterator(const bit_span& parent, size_t offset) + : parent_(&parent), offset_(offset) + { + assert(offset_ <= std::numeric_limits::max()); + } + + constexpr auto operator*() const -> reference + { + std::bitset byte{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + static_cast(parent_->data_[offset_ / CHAR_BIT])}; + // most significant bit first + return bit{byte[CHAR_BIT - 1 - offset_ % CHAR_BIT]}; + } + + constexpr auto operator+=(difference_type n) -> iterator& + { + const auto newOffset = static_cast(offset_) + n; + assert(newOffset >= 0); + offset_ = static_cast(newOffset); + return *this; + } + + friend constexpr auto + operator-(const iterator& lhs, const iterator& rhs) -> difference_type + { + assert(lhs.parent_ == rhs.parent_); + + return static_cast(lhs.offset_) - + static_cast(rhs.offset_); + } + + friend constexpr auto + operator<=>(const iterator&, const iterator&) = default; + }; + + /// Constructs a bit_span from the given data. + /// + /// @param data a pointer to the first byte of the data. + /// @param bit_size the number of bits in the data. + constexpr bit_span(const std::byte* data, size_t bit_size) + : data_(data), bit_size_(bit_size) + {} + + [[nodiscard]] + constexpr auto begin() const -> iterator + { + return iterator{*this, 0}; + }; + [[nodiscard]] + constexpr auto end() const -> iterator + { + return iterator{*this, bit_size_}; + }; +}; +} // namespace gpu_deflate::huffman diff --git a/huffman/test/BUILD.bazel b/huffman/test/BUILD.bazel index 97a3092..cee9e2a 100644 --- a/huffman/test/BUILD.bazel +++ b/huffman/test/BUILD.bazel @@ -60,6 +60,16 @@ cc_test( ], ) +cc_test( + name = "bit_span_test", + timeout = "short", + srcs = ["bit_span_test.cpp"], + deps = [ + "//huffman", + "@boost_ut", + ], +) + cc_binary( name = "bench", srcs = ["bench.cpp"], diff --git a/huffman/test/bit_span_test.cpp b/huffman/test/bit_span_test.cpp new file mode 100644 index 0000000..5048ee2 --- /dev/null +++ b/huffman/test/bit_span_test.cpp @@ -0,0 +1,31 @@ +#include "huffman/huffman.hpp" + +#include + +#include +#include +#include +#include + +auto main() -> int +{ + using ::boost::ut::aborts; + using ::boost::ut::expect; + using ::boost::ut::test; + + namespace huffman = ::gpu_deflate::huffman; + using namespace huffman::literals; + + test("basic") = [] { + static constexpr std::array data{ + std::byte{0b10101010}, std::byte{0xff}}; + // leave off the last bit of the last byte + constexpr huffman::bit_span span{data.data(), (data.size() * CHAR_BIT) - 1}; + constexpr std::string_view expected = "101010101111111"; + expect(std::ranges::equal( + span, + expected | std::views::transform([](char c) { + return huffman::bit{c}; + }))); + }; +}