From deaa55564edd01c372c8395582285b7c936640e3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 11 Jan 2026 16:54:30 -0500 Subject: [PATCH] xo-arena: CircularBuffer utest + bugfixes --- .../include/xo/arena/CircularBufferConfig.hpp | 1 + xo-arena/include/xo/arena/DCircularBuffer.hpp | 5 +- xo-arena/include/xo/arena/span.hpp | 21 ++++++- xo-arena/src/arena/DCircularBuffer.cpp | 33 ++++++++-- xo-arena/utest/CMakeLists.txt | 3 +- xo-arena/utest/DCircularBuffer.test.cpp | 61 +++++++++++++++++++ 6 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 xo-arena/utest/DCircularBuffer.test.cpp diff --git a/xo-arena/include/xo/arena/CircularBufferConfig.hpp b/xo-arena/include/xo/arena/CircularBufferConfig.hpp index 710812a4..587b9d22 100644 --- a/xo-arena/include/xo/arena/CircularBufferConfig.hpp +++ b/xo-arena/include/xo/arena/CircularBufferConfig.hpp @@ -20,6 +20,7 @@ namespace xo { /** optional name, for diagnostics **/ std::string name_; /** hard maximum buffer size = reserved virtual memory. + * However actual max will be this value rounded up to at least page size. * Buffer will generally map much less than this amount of memory **/ std::size_t max_capacity_ = 0; diff --git a/xo-arena/include/xo/arena/DCircularBuffer.hpp b/xo-arena/include/xo/arena/DCircularBuffer.hpp index 17bd6b7a..7e49def3 100644 --- a/xo-arena/include/xo/arena/DCircularBuffer.hpp +++ b/xo-arena/include/xo/arena/DCircularBuffer.hpp @@ -79,6 +79,7 @@ namespace xo { const_span_type reserved_range() const noexcept { return reserved_range_; } const_span_type mapped_range() const noexcept { return mapped_range_; } const_span_type occupied_range() const noexcept { return occupied_range_; } + const_span_type input_range() const noexcept { return input_range_; } /** verify DCircularBuffer invariants. * Act on failure according to policy @p p @@ -98,6 +99,8 @@ namespace xo { /** @defgroup mm-circularbuffer-nonconst-methods CircularBuffer non-const methods **/ ///@{ + span_type input_range() noexcept { return input_range_; } + /** copy memory in span @p r into buffer starting at the end of * @ref occupied_range_. Map new physical memory as needed. * On success returns empty suffix of @p r. @@ -139,7 +142,7 @@ namespace xo { * Caller represents that it won't need to read this memory again * unless overlaps with a pinned span. **/ - void consume(span_type r); + void consume(const_span_type input); /** pin memory range @p r. circular buffer will not touch * addresses that appear in any pinned range. diff --git a/xo-arena/include/xo/arena/span.hpp b/xo-arena/include/xo/arena/span.hpp index e902193f..c038fce5 100644 --- a/xo-arena/include/xo/arena/span.hpp +++ b/xo-arena/include/xo/arena/span.hpp @@ -28,6 +28,9 @@ namespace xo { /** typealias for span size (in units of CharT) **/ using size_type = std::uint64_t; + /** typealias for span elements **/ + using value_type = CharT; + ///@} public: @@ -58,7 +61,8 @@ namespace xo { * A null span can be concatenated with any other span * without triggering matching-endpoint asserts. **/ - static span make_null() { return span(static_cast(nullptr), static_cast(nullptr)); } + static span make_null() { return span(static_cast(nullptr), + static_cast(nullptr)); } /** @brief create span for C-style string @p cstr **/ static span from_cstr(const CharT * cstr) { @@ -69,13 +73,21 @@ namespace xo { } /** @brief create span from std::string @p str **/ - static span from_string(const std::string& str) { + static span from_string(const std::string & str) { CharT * lo = &(*str.begin()); CharT * hi = &(*str.end()); return span(lo, hi); } + /** @brief create span from std::string @p str **/ + static span from_string_view(const std::string_view & sv) { + CharT * lo = &(*sv.begin()); + CharT * hi = &(*sv.end()); + + return span(lo, hi); + } + /** @brief concatenate two contiguous spans */ static span concat(const span & span1, const span & span2) { if (span1.is_null()) @@ -119,6 +131,11 @@ namespace xo { return (other.lo() <= lo_) && (hi_ <= other.hi()); } + /** convert to string view **/ + std::string_view to_string_view() const { + return std::string_view((const char *)lo_, (const char *)hi_); + } + ///@} /** @defgroup span-general-methods **/ diff --git a/xo-arena/src/arena/DCircularBuffer.cpp b/xo-arena/src/arena/DCircularBuffer.cpp index e4d31f46..9e8b28b8 100644 --- a/xo-arena/src/arena/DCircularBuffer.cpp +++ b/xo-arena/src/arena/DCircularBuffer.cpp @@ -10,6 +10,9 @@ #include namespace xo { + using xo::print::operator<<; + using xo::print::printspan; + namespace mm { DCircularBuffer::DCircularBuffer(DCircularBuffer && other) @@ -152,6 +155,7 @@ namespace xo { ::memcpy(occupied_range_.hi(), src.lo(), copy_z); this->occupied_range_ += span_type(dest.lo(), copy_z); + this->input_range_ += span_type(dest.lo(), copy_z); return src.after_prefix(copy_z); } @@ -198,30 +202,49 @@ namespace xo { } void - DCircularBuffer::consume(span_type r) + DCircularBuffer::consume(const_span_type input) { - if (r.lo() != input_range_.lo()) { + scope log(XO_DEBUG(false), xtag("input", input.to_string_view())); + + if (input.lo() != input_range_.lo()) { assert(false); return; } - if (r.hi() > occupied_range_.hi()) { + if (input.hi() > occupied_range_.hi()) { assert(false); return; } if (occupied_range_.lo() < input_range_.lo()) { + log && log("pinned range prevents shrinking occupied range"); + /* here: a pinned range prevents shrinking occupied_range */ - this->input_range_ = input_range_.suffix_from(r.hi()); + this->input_range_ + = input_range_.suffix_from((span_type::value_type *)input.hi()); } else { + log && log(xtag("msg", "will shrink occupied range"), + xtag("input.lo", (void*)input.lo()), + xtag("input.hi", (void*)input.hi()), + xtag("stored.lo", (void*)input_range_.lo()), + xtag("stored.hi", (void*)input_range_.hi()) + ); + /* here: input; recompute occupied boundary */ - this->input_range_ = input_range_.suffix_from(r.hi()); + this->input_range_ + = input_range_.suffix_from((span_type::value_type *)input.hi()); + + log && log(xtag("occupied", occupied_range_.size()), + xtag("input", input_range_.size())); this->_shrink_occupied_to_fit(); + + log && log(xtag("occupied", occupied_range_.size()), + xtag("input", input_range_.size())); } this->_check_reset_map_start(); diff --git a/xo-arena/utest/CMakeLists.txt b/xo-arena/utest/CMakeLists.txt index c45a9710..886ecb7a 100644 --- a/xo-arena/utest/CMakeLists.txt +++ b/xo-arena/utest/CMakeLists.txt @@ -8,9 +8,8 @@ set(UTEST_SRCS DArena.test.cpp DArenaVector.test.cpp DArenaHashMap.test.cpp + DCircularBuffer.test.cpp # DArenaIterator.test.cpp -# Collector.test.cpp -# DX1CollectorIterator.test.cpp # random_allocs.cpp ) diff --git a/xo-arena/utest/DCircularBuffer.test.cpp b/xo-arena/utest/DCircularBuffer.test.cpp new file mode 100644 index 00000000..debfc41f --- /dev/null +++ b/xo-arena/utest/DCircularBuffer.test.cpp @@ -0,0 +1,61 @@ +/** @file DCircularBuffer.test.cpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#include "DCircularBuffer.hpp" +#include "print.hpp" +#include +#include + +namespace xo { + using xo::mm::DCircularBuffer; + using xo::mm::CircularBufferConfig; + using xo::mm::span; + using std::byte; + + namespace ut { + TEST_CASE("DCircularBuffer-tiny", "[arena][DCircularBuffer]") + { + // buffer works with bytes, not chars + + CircularBufferConfig cfg { .name_ = "testcbuf", + .max_capacity_ = 1 }; + DCircularBuffer buf = DCircularBuffer::map(cfg); + + REQUIRE(buf.reserved_range().size() == getpagesize()); + REQUIRE(buf.mapped_range().size() == 0); + REQUIRE(buf.occupied_range().size() == 0); + REQUIRE(buf.input_range().size() == 0); + + REQUIRE(buf.verify_ok(verify_policy::log_only())); + REQUIRE(buf.get_append_span(1).size() == getpagesize()); + REQUIRE(buf.mapped_range().size() == getpagesize()); + REQUIRE(buf.occupied_range().size() == 0); + REQUIRE(buf.input_range().size() == 0); + + std::string_view s0 = "abcdefghijk"; + /* return value is unaccepted suffix of input */ + REQUIRE(buf.append(DCircularBuffer::span_type((byte *)s0.begin(), + (byte *)s0.end())).empty()); + REQUIRE(buf.verify_ok(verify_policy::log_only())); + REQUIRE(buf.mapped_range().size() == getpagesize()); + REQUIRE(buf.occupied_range().size() == s0.size()); + REQUIRE(buf.input_range().size() == s0.size()); + + std::string_view s1 = "lmnopq"; + REQUIRE(buf.append(DCircularBuffer::span_type((byte *)s1.begin(), + (byte *)s1.end())).empty()); + REQUIRE(buf.mapped_range().size() == getpagesize()); + REQUIRE(buf.occupied_range().size() == s0.size() + s1.size()); + + REQUIRE(buf.occupied_range().to_string_view() == std::string_view("abcdefghijklmnopq")); + + buf.consume(buf.occupied_range().prefix(3)); + + REQUIRE(buf.occupied_range().to_string_view() == std::string_view("defghijklmnopq")); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end DCircularBuffer.test.cpp */