From 860e5d93200468ea2fdecf16d54545e26b0c43f3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 15 Feb 2026 04:51:19 -0800 Subject: [PATCH 1/2] Asio buffer compatibility is opt-in --- CMakeLists.txt | 12 +- example/allocation/CMakeLists.txt | 25 ++- example/allocation/allocation.cpp | 39 ++-- example/asio/api/capy_streams.cpp | 5 +- example/asio/api/uni_stream.hpp | 7 +- include/boost/capy/buffers.hpp | 75 +------ include/boost/capy/buffers/asio.hpp | 331 ++++++++++++++++++++++++++++ test/unit/CMakeLists.txt | 5 + test/unit/buffers/asio.cpp | 329 +++++++++++++++++++++++++++ test/unit/buffers/buffer.cpp | 13 -- 10 files changed, 732 insertions(+), 109 deletions(-) create mode 100644 include/boost/capy/buffers/asio.hpp create mode 100644 test/unit/buffers/asio.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dae2718..033c3c2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,18 +98,18 @@ add_subdirectory(bench) #------------------------------------------------- # -# Tests +# Examples (before tests so Boost::asio is available) # #------------------------------------------------- -if (BOOST_CAPY_BUILD_TESTS) - add_subdirectory(test) +if (BOOST_CAPY_BUILD_EXAMPLES) + add_subdirectory(example) endif () #------------------------------------------------- # -# Examples +# Tests # #------------------------------------------------- -if (BOOST_CAPY_BUILD_EXAMPLES) - add_subdirectory(example) +if (BOOST_CAPY_BUILD_TESTS) + add_subdirectory(test) endif () diff --git a/example/allocation/CMakeLists.txt b/example/allocation/CMakeLists.txt index cb3eeec9..c38fa958 100644 --- a/example/allocation/CMakeLists.txt +++ b/example/allocation/CMakeLists.txt @@ -7,13 +7,15 @@ # Official repository: https://github.com/cppalliance/capy # -include(FetchContent) -FetchContent_Declare(mimalloc - GIT_REPOSITORY https://github.com/microsoft/mimalloc - GIT_TAG v2.2.7 - GIT_SHALLOW TRUE) -set(MI_BUILD_TESTS OFF CACHE BOOL "Disable mimalloc tests" FORCE) -FetchContent_MakeAvailable(mimalloc) +if(BUILD_SHARED_LIBS) + include(FetchContent) + FetchContent_Declare(mimalloc + GIT_REPOSITORY https://github.com/microsoft/mimalloc + GIT_TAG v2.2.7 + GIT_SHALLOW TRUE) + set(MI_BUILD_TESTS OFF CACHE BOOL "Disable mimalloc tests" FORCE) + FetchContent_MakeAvailable(mimalloc) +endif() file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp CMakeLists.txt @@ -26,6 +28,9 @@ add_executable(capy_example_allocation ${PFILES}) set_property(TARGET capy_example_allocation PROPERTY FOLDER "examples") -target_link_libraries(capy_example_allocation - Boost::capy - mimalloc-static) +target_link_libraries(capy_example_allocation Boost::capy) + +if(BUILD_SHARED_LIBS) + target_link_libraries(capy_example_allocation mimalloc-static) + target_compile_definitions(capy_example_allocation PRIVATE BOOST_CAPY_HAS_MIMALLOC=1) +endif() diff --git a/example/allocation/allocation.cpp b/example/allocation/allocation.cpp index c34831d4..b2bcfc36 100644 --- a/example/allocation/allocation.cpp +++ b/example/allocation/allocation.cpp @@ -17,7 +17,9 @@ #include #include +#if BOOST_CAPY_HAS_MIMALLOC #include +#endif #include #include #include @@ -25,6 +27,7 @@ #include #include #include +#include // Prevent HALO from eliding coroutine frame allocations #if defined(_MSC_VER) @@ -39,6 +42,7 @@ using namespace boost::capy; std::atomic counter{0}; +#if BOOST_CAPY_HAS_MIMALLOC // Adapts mimalloc to std::pmr::memory_resource class mi_memory_resource : public std::pmr::memory_resource @@ -71,6 +75,7 @@ class mi_memory_resource return this == &other; } }; +#endif // These coroutines simulate a "composed operation" // consisting of layered APIs. For example a user's @@ -124,6 +129,7 @@ int main() } auto t1 = std::chrono::steady_clock::now(); +#if BOOST_CAPY_HAS_MIMALLOC // With mimalloc counter.store(0); mi_memory_resource mi_mr; @@ -137,6 +143,7 @@ int main() ctx.run(); } auto t3 = std::chrono::steady_clock::now(); +#endif // With std::allocator (no recycling) counter.store(0); @@ -152,31 +159,37 @@ int main() auto ms_recycling = std::chrono::duration(t1 - t0).count(); - auto ms_mimalloc = - std::chrono::duration(t3 - t2).count(); auto ms_standard = std::chrono::duration(t5 - t4).count(); auto pct_rc_std = std::round( (ms_standard / ms_recycling - 1.0) * 1000.0) / 10.0; - auto pct_mi_std = std::round( - (ms_standard / ms_mimalloc - 1.0) * 1000.0) / 10.0; - auto pct_rc_mi = std::round( - (ms_mimalloc / ms_recycling - 1.0) * 1000.0) / 10.0; - std::cout - << iterations << " iterations, " + std::ostringstream os; + os << std::fixed << std::setprecision(1); + os << iterations << " iterations, " << "4-deep coroutine chain\n\n" - << std::fixed << std::setprecision(1) << " Recycling allocator: " << ms_recycling << " ms (+" - << pct_rc_std << "% vs std, +" - << pct_rc_mi << "% vs mimalloc)\n" + << pct_rc_std << "% vs std"; +#if BOOST_CAPY_HAS_MIMALLOC + auto ms_mimalloc = + std::chrono::duration(t3 - t2).count(); + auto pct_mi_std = std::round( + (ms_standard / ms_mimalloc - 1.0) * 1000.0) / 10.0; + auto pct_rc_mi = std::round( + (ms_mimalloc / ms_recycling - 1.0) * 1000.0) / 10.0; + os << ", +" << pct_rc_mi << "% vs mimalloc)\n" << " mimalloc: " << ms_mimalloc << " ms (+" - << pct_mi_std << "% vs std)\n" - << " std::allocator: " + << pct_mi_std << "% vs std)\n"; +#else + os << ")\n"; +#endif + os << " std::allocator: " << ms_standard << " ms\n"; + std::cout << os.str(); + return 0; } diff --git a/example/asio/api/capy_streams.cpp b/example/asio/api/capy_streams.cpp index 26d1ffe3..bcbeb259 100644 --- a/example/asio/api/capy_streams.cpp +++ b/example/asio/api/capy_streams.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -105,7 +106,7 @@ class asio_socket cancel_ = std::make_shared(env->stop_token); self_->socket_.async_read_some( - capy::mutable_buffer_array<8>(buffers_), + capy::to_asio(capy::mutable_buffer_array<8>(buffers_)), net::bind_cancellation_slot( cancel_->signal.slot(), [this, h, ex = env->executor]( @@ -167,7 +168,7 @@ class asio_socket cancel_ = std::make_shared(env->stop_token); self_->socket_.async_write_some( - capy::const_buffer_array<8>(buffers_), + capy::to_asio(capy::const_buffer_array<8>(buffers_)), net::bind_cancellation_slot( cancel_->signal.slot(), [this, h, ex = env->executor]( diff --git a/example/asio/api/uni_stream.hpp b/example/asio/api/uni_stream.hpp index 2aa81e52..b1a2962c 100644 --- a/example/asio/api/uni_stream.hpp +++ b/example/asio/api/uni_stream.hpp @@ -18,6 +18,7 @@ #include "capy_streams.hpp" +#include #include #include #include @@ -121,7 +122,8 @@ class uni_stream MutableBufferSequence bufs, ReadHandler h) -> capy::task<> { - auto [ec, n] = co_await stream->read_some(bufs); + auto [ec, n] = co_await stream->read_some( + capy::from_asio(bufs)); std::move(h)(ec, n); }(&self_->stream_, buffers, std::forward(handler))); } @@ -152,7 +154,8 @@ class uni_stream ConstBufferSequence bufs, WriteHandler h) -> capy::task<> { - auto [ec, n] = co_await stream->write_some(bufs); + auto [ec, n] = co_await stream->write_some( + capy::from_asio(bufs)); std::move(h)(ec, n); }(&self_->stream_, buffers, std::forward(handler))); } diff --git a/include/boost/capy/buffers.hpp b/include/boost/capy/buffers.hpp index 9f5768af..36e1cc75 100644 --- a/include/boost/capy/buffers.hpp +++ b/include/boost/capy/buffers.hpp @@ -32,38 +32,6 @@ namespace capy { class const_buffer; class mutable_buffer; -namespace detail { - -// satisfies Asio's buffer constructors, CANNOT be removed! -template -class basic_buffer -{ - constexpr auto data() const noexcept -> - std::conditional_t, void const*, void*> - { - return p_; - } - - constexpr std::size_t size() const noexcept - { - return n_; - } - - friend class capy::const_buffer; - friend class capy::mutable_buffer; - friend class asio::const_buffer; - friend class asio::mutable_buffer; - basic_buffer() = default; - constexpr basic_buffer(T* p, std::size_t n) noexcept : p_(p), n_(n) {} - constexpr basic_buffer subspan( - std::size_t, std::size_t = (std::size_t)(-1)) const noexcept; - - T* p_ = nullptr; - std::size_t n_ = 0; -}; - -} // detail - //------------------------------------------------ /// Tag type for customizing `buffer_size` via `tag_invoke`. @@ -98,8 +66,10 @@ enum class slice_how @see const_buffer, MutableBufferSequence */ class mutable_buffer - : public detail::basic_buffer { + unsigned char* p_ = nullptr; + std::size_t n_ = 0; + public: /// Construct an empty buffer. mutable_buffer() = default; @@ -115,19 +85,8 @@ class mutable_buffer /// Construct from pointer and size. constexpr mutable_buffer( void* data, std::size_t size) noexcept - : basic_buffer( - static_cast(data), size) - { - } - - /// Construct from Asio mutable_buffer. - template - requires std::same_as - constexpr mutable_buffer( - MutableBuffer const& b) noexcept - : basic_buffer( - static_cast( - b.data()), b.size()) + : p_(static_cast(data)) + , n_(size) { } @@ -199,8 +158,10 @@ class mutable_buffer @see mutable_buffer, ConstBufferSequence */ class const_buffer - : public detail::basic_buffer { + unsigned char const* p_ = nullptr; + std::size_t n_ = 0; + public: /// Construct an empty buffer. const_buffer() = default; @@ -215,28 +176,16 @@ class const_buffer /// Construct from pointer and size. constexpr const_buffer( void const* data, std::size_t size) noexcept - : basic_buffer( - static_cast(data), size) + : p_(static_cast(data)) + , n_(size) { } /// Construct from mutable_buffer. constexpr const_buffer( mutable_buffer const& b) noexcept - : basic_buffer( - static_cast(b.data()), b.size()) - { - } - - /// Construct from Asio buffer types. - template - requires (std::same_as || - std::same_as) - constexpr const_buffer( - ConstBuffer const& b) noexcept - : basic_buffer( - static_cast( - b.data()), b.size()) + : p_(static_cast(b.data())) + , n_(b.size()) { } diff --git a/include/boost/capy/buffers/asio.hpp b/include/boost/capy/buffers/asio.hpp new file mode 100644 index 00000000..f581191c --- /dev/null +++ b/include/boost/capy/buffers/asio.hpp @@ -0,0 +1,331 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_BUFFERS_ASIO_HPP +#define BOOST_CAPY_BUFFERS_ASIO_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { + +namespace detail { + +// true when BS itself or its range element is convertible +// to asio::const_buffer (i.e. it is an asio-native sequence) +template +constexpr bool is_native_asio_v = false; + +template +constexpr bool is_native_asio_v>> = true; + +template +constexpr bool is_native_asio_v && + std::ranges::bidirectional_range && + std::is_convertible_v< + std::ranges::range_value_t, + asio::const_buffer>>> = true; + +/** Adapts a buffer sequence so its iterators yield the other library's buffer type. + + When the source is an asio-native sequence the adaptor's + iterators produce `capy::mutable_buffer` or `capy::const_buffer`. + Otherwise the source is a capy sequence and the iterators + produce `asio::mutable_buffer` or `asio::const_buffer`. + + @tparam BufferSequence The source buffer sequence type. + @tparam IsMutable Whether the target buffer type is mutable. +*/ +template< + class BufferSequence, + bool IsMutable> +class buffer_sequence_adaptor +{ + static constexpr bool native_asio = + is_native_asio_v; + + static auto get_begin(BufferSequence const& bs) noexcept + { + if constexpr (native_asio) + return asio::buffer_sequence_begin(bs); + else + return capy::begin(bs); + } + + using source_iterator = decltype( + get_begin(std::declval())); + + using source_value_type = std::conditional_t< + native_asio, + std::conditional_t, + std::conditional_t>; + +public: + using value_type = std::conditional_t< + native_asio, + std::conditional_t, + std::conditional_t>; + + class const_iterator + { + source_iterator it_{}; + + public: + using value_type = buffer_sequence_adaptor::value_type; + using reference = value_type; + using pointer = void; + using difference_type = std::ptrdiff_t; + + using iterator_category = std::conditional_t< + std::random_access_iterator, + std::random_access_iterator_tag, + std::bidirectional_iterator_tag>; + + const_iterator() = default; + + explicit const_iterator( + source_iterator it) noexcept + : it_(it) + { + } + + reference operator*() const noexcept + { + source_value_type b(*it_); + return value_type(b.data(), b.size()); + } + + const_iterator& + operator++() noexcept + { + ++it_; + return *this; + } + + const_iterator + operator++(int) noexcept + { + auto tmp = *this; + ++*this; + return tmp; + } + + const_iterator& + operator--() noexcept + { + --it_; + return *this; + } + + const_iterator + operator--(int) noexcept + { + auto tmp = *this; + --*this; + return tmp; + } + + bool operator==( + const_iterator const& other) const noexcept + { + return it_ == other.it_; + } + + bool operator!=( + const_iterator const& other) const noexcept + { + return it_ != other.it_; + } + + //--- random access (conditional) --- + + const_iterator& + operator+=(difference_type n) noexcept + requires std::random_access_iterator + { + it_ += n; + return *this; + } + + const_iterator& + operator-=(difference_type n) noexcept + requires std::random_access_iterator + { + it_ -= n; + return *this; + } + + reference + operator[](difference_type n) const noexcept + requires std::random_access_iterator + { + return *(*this + n); + } + + friend const_iterator + operator+( + const_iterator it, + difference_type n) noexcept + requires std::random_access_iterator + { + it += n; + return it; + } + + friend const_iterator + operator+( + difference_type n, + const_iterator it) noexcept + requires std::random_access_iterator + { + it += n; + return it; + } + + friend const_iterator + operator-( + const_iterator it, + difference_type n) noexcept + requires std::random_access_iterator + { + it -= n; + return it; + } + + friend difference_type + operator-( + const_iterator const& a, + const_iterator const& b) noexcept + requires std::random_access_iterator + { + return a.it_ - b.it_; + } + + auto operator<=>( + const_iterator const& other) const noexcept + requires std::random_access_iterator + { + return it_ <=> other.it_; + } + }; + + template + explicit buffer_sequence_adaptor(BS&& bs) + noexcept(std::is_nothrow_constructible_v< + BufferSequence, BS&&>) + : bs_(std::forward(bs)) + { + } + + const_iterator + begin() const noexcept + { + if constexpr (native_asio) + return const_iterator( + asio::buffer_sequence_begin(bs_)); + else + return const_iterator( + capy::begin(bs_)); + } + + const_iterator + end() const noexcept + { + if constexpr (native_asio) + return const_iterator( + asio::buffer_sequence_end(bs_)); + else + return const_iterator( + capy::end(bs_)); + } + +private: + BufferSequence bs_; +}; + +} // detail + +//------------------------------------------------ + +/** Adapt a capy buffer sequence for use with Asio. + + Returns a wrapper whose iterators dereference to + `asio::mutable_buffer` or `asio::const_buffer`, + depending on whether the source is a + `MutableBufferSequence`. + + @param bs The capy buffer sequence to adapt. + @return An adapted buffer sequence usable with Asio. +*/ +template + requires ConstBufferSequence> +auto +to_asio(BS&& bs) +{ + using Seq = std::remove_cvref_t; + constexpr bool is_mutable = + MutableBufferSequence; + + return detail::buffer_sequence_adaptor< + Seq, is_mutable>( + std::forward(bs)); +} + +/** Adapt an Asio buffer sequence for use with capy. + + Returns a wrapper whose iterators dereference to + `capy::mutable_buffer` or `capy::const_buffer`, + depending on whether the source is convertible to + `asio::mutable_buffer`. Accepts single asio buffers + and ranges of asio buffers. + + @param bs The Asio buffer sequence to adapt. + @return An adapted buffer sequence usable with capy. +*/ +template + requires std::is_convertible_v< + std::remove_cvref_t, asio::const_buffer> || + std::ranges::bidirectional_range> +auto +from_asio(BS&& bs) +{ + using Seq = std::remove_cvref_t; + + constexpr bool is_mutable = [] { + if constexpr (std::is_convertible_v) + return true; + else if constexpr (std::is_convertible_v) + return false; + else + return std::is_convertible_v< + std::ranges::range_value_t, + asio::mutable_buffer>; + }(); + + return detail::buffer_sequence_adaptor< + Seq, is_mutable>( + std::forward(bs)); +} + +} // capy +} // boost + +#endif diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 864acbe4..0d744adf 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -26,6 +26,11 @@ target_link_libraries( target_include_directories(boost_capy_tests PRIVATE . ../../) +if(TARGET Boost::asio) + target_link_libraries(boost_capy_tests PRIVATE Boost::asio) + target_compile_definitions(boost_capy_tests PRIVATE BOOST_CAPY_HAS_ASIO=1) +endif() + # Register individual tests with CTest boost_capy_test_suite_discover_tests(boost_capy_tests) diff --git a/test/unit/buffers/asio.cpp b/test/unit/buffers/asio.cpp new file mode 100644 index 00000000..2be26180 --- /dev/null +++ b/test/unit/buffers/asio.cpp @@ -0,0 +1,329 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#if BOOST_CAPY_HAS_ASIO + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "test_buffers.hpp" + +namespace boost { +namespace capy { + +//------------------------------------------------ +// to_asio result satisfies asio buffer sequence traits + +using to_asio_const_t = decltype( + to_asio(std::declval const&>())); +using to_asio_mutable_t = decltype( + to_asio(std::declval const&>())); + +static_assert(asio::is_const_buffer_sequence::value); +static_assert(asio::is_const_buffer_sequence::value); +static_assert(!asio::is_mutable_buffer_sequence::value); +static_assert(asio::is_mutable_buffer_sequence::value); + +//------------------------------------------------ +// from_asio result satisfies capy buffer sequence concepts + +using from_asio_const_t = decltype( + from_asio(std::declval>())); +using from_asio_mutable_t = decltype( + from_asio(std::declval>())); + +static_assert(ConstBufferSequence); +static_assert(ConstBufferSequence); +static_assert(!MutableBufferSequence); +static_assert(MutableBufferSequence); + +//------------------------------------------------ +// to_asio iterator dereferences to asio buffer types + +static_assert(std::is_same_v< + to_asio_const_t::const_iterator::value_type, + asio::const_buffer>); +static_assert(std::is_same_v< + to_asio_mutable_t::const_iterator::value_type, + asio::mutable_buffer>); + +//------------------------------------------------ +// from_asio iterator dereferences to capy buffer types + +static_assert(std::is_same_v< + from_asio_const_t::const_iterator::value_type, + capy::const_buffer>); +static_assert(std::is_same_v< + from_asio_mutable_t::const_iterator::value_type, + capy::mutable_buffer>); + +//------------------------------------------------ + +struct asio_test +{ + void + test_to_asio_const() + { + char const data[] = "Hello"; + const_buffer cb(data, 5); + + auto adapted = to_asio(cb); + auto it = adapted.begin(); + auto end = adapted.end(); + BOOST_TEST(it != end); + + asio::const_buffer ab = *it; + BOOST_TEST_EQ(ab.data(), cb.data()); + BOOST_TEST_EQ(ab.size(), cb.size()); + + ++it; + BOOST_TEST(it == end); + } + + void + test_to_asio_mutable() + { + char data[] = "Hello"; + mutable_buffer mb(data, 5); + + auto adapted = to_asio(mb); + auto it = adapted.begin(); + auto end = adapted.end(); + BOOST_TEST(it != end); + + asio::mutable_buffer ab = *it; + BOOST_TEST_EQ(ab.data(), mb.data()); + BOOST_TEST_EQ(ab.size(), mb.size()); + + ++it; + BOOST_TEST(it == end); + } + + void + test_to_asio_array() + { + char d1[] = "abc"; + char d2[] = "defgh"; + mutable_buffer_array<4> bufs; + bufs = mutable_buffer_array<4>( + std::array{{ + mutable_buffer(d1, 3), + mutable_buffer(d2, 5) + }}); + + auto adapted = to_asio(bufs); + auto it = adapted.begin(); + auto end = adapted.end(); + + asio::mutable_buffer ab0 = *it; + BOOST_TEST_EQ(ab0.data(), static_cast(d1)); + BOOST_TEST_EQ(ab0.size(), 3u); + ++it; + + asio::mutable_buffer ab1 = *it; + BOOST_TEST_EQ(ab1.data(), static_cast(d2)); + BOOST_TEST_EQ(ab1.size(), 5u); + ++it; + + BOOST_TEST(it == end); + } + + void + test_from_asio_const() + { + char const data[] = "Hello"; + asio::const_buffer ab(data, 5); + std::span sp(&ab, 1); + + auto adapted = from_asio(sp); + auto it = adapted.begin(); + auto end = adapted.end(); + BOOST_TEST(it != end); + + const_buffer cb = *it; + BOOST_TEST_EQ(cb.data(), ab.data()); + BOOST_TEST_EQ(cb.size(), ab.size()); + + ++it; + BOOST_TEST(it == end); + } + + void + test_from_asio_mutable() + { + char data[] = "Hello"; + asio::mutable_buffer ab(data, 5); + std::span sp(&ab, 1); + + auto adapted = from_asio(sp); + auto it = adapted.begin(); + auto end = adapted.end(); + BOOST_TEST(it != end); + + mutable_buffer mb = *it; + BOOST_TEST_EQ(mb.data(), ab.data()); + BOOST_TEST_EQ(mb.size(), ab.size()); + + ++it; + BOOST_TEST(it == end); + } + + void + test_from_asio_array() + { + char d1[] = "abc"; + char d2[] = "defgh"; + std::array asio_bufs{{ + asio::mutable_buffer(d1, 3), + asio::mutable_buffer(d2, 5) + }}; + + auto adapted = from_asio(asio_bufs); + auto it = adapted.begin(); + auto end = adapted.end(); + + mutable_buffer mb0 = *it; + BOOST_TEST_EQ(mb0.data(), static_cast(d1)); + BOOST_TEST_EQ(mb0.size(), 3u); + ++it; + + mutable_buffer mb1 = *it; + BOOST_TEST_EQ(mb1.data(), static_cast(d2)); + BOOST_TEST_EQ(mb1.size(), 5u); + ++it; + + BOOST_TEST(it == end); + } + + void + test_roundtrip() + { + // capy -> asio -> capy preserves data/size + char data[] = "roundtrip"; + mutable_buffer mb(data, 9); + + auto asio_adapted = to_asio(mb); + auto it = asio_adapted.begin(); + asio::mutable_buffer ab = *it; + + std::span sp(&ab, 1); + auto capy_adapted = from_asio(sp); + auto it2 = capy_adapted.begin(); + mutable_buffer mb2 = *it2; + + BOOST_TEST_EQ(mb2.data(), mb.data()); + BOOST_TEST_EQ(mb2.size(), mb.size()); + } + + void + test_bidirectional() + { + char d1[] = "ab"; + char d2[] = "cd"; + std::array asio_bufs{{ + asio::const_buffer(d1, 2), + asio::const_buffer(d2, 2) + }}; + + auto adapted = from_asio(asio_bufs); + auto it = adapted.end(); + auto beg = adapted.begin(); + + --it; + const_buffer cb1 = *it; + BOOST_TEST_EQ(cb1.data(), static_cast(d2)); + + --it; + const_buffer cb0 = *it; + BOOST_TEST_EQ(cb0.data(), static_cast(d1)); + + BOOST_TEST(it == beg); + } + + void + test_random_access() + { + char d1[] = "ab"; + char d2[] = "cd"; + char d3[] = "ef"; + std::array asio_bufs{{ + asio::const_buffer(d1, 2), + asio::const_buffer(d2, 2), + asio::const_buffer(d3, 2) + }}; + + auto adapted = from_asio(asio_bufs); + auto beg = adapted.begin(); + auto end = adapted.end(); + + BOOST_TEST_EQ(end - beg, 3); + BOOST_TEST(beg < end); + + auto mid = beg + 1; + const_buffer cb = *mid; + BOOST_TEST_EQ(cb.data(), static_cast(d2)); + + cb = beg[2]; + BOOST_TEST_EQ(cb.data(), static_cast(d3)); + + mid += 1; + cb = *mid; + BOOST_TEST_EQ(cb.data(), static_cast(d3)); + + mid -= 2; + cb = *mid; + BOOST_TEST_EQ(cb.data(), static_cast(d1)); + } + + void + test_move_semantics() + { + char d1[] = "abc"; + mutable_buffer_array<4> bufs; + bufs = mutable_buffer_array<4>( + mutable_buffer(d1, 3)); + + auto adapted = to_asio(std::move(bufs)); + auto it = adapted.begin(); + asio::mutable_buffer ab = *it; + BOOST_TEST_EQ(ab.data(), static_cast(d1)); + BOOST_TEST_EQ(ab.size(), 3u); + } + + void + run() + { + test_to_asio_const(); + test_to_asio_mutable(); + test_to_asio_array(); + test_from_asio_const(); + test_from_asio_mutable(); + test_from_asio_array(); + test_roundtrip(); + test_bidirectional(); + test_random_access(); + test_move_semantics(); + } +}; + +TEST_SUITE( + asio_test, + "boost.capy.buffers.asio"); + +} // capy +} // boost + +#endif // BOOST_CAPY_HAS_ASIO diff --git a/test/unit/buffers/buffer.cpp b/test/unit/buffers/buffer.cpp index 96d8fada..d03a29af 100644 --- a/test/unit/buffers/buffer.cpp +++ b/test/unit/buffers/buffer.cpp @@ -460,16 +460,3 @@ TEST_SUITE( } // capy } // boost - -#if 0 -const_buffer -mutable_buffer -const_buffer_pair -mutable_buffer_pair -std::span -std::span -std::array -std::array -const_buffer[3] -mutable_buffer[3] -#endif From f3faef319d399cc137160f670feff6dd94891d4f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 20 Feb 2026 19:40:11 -0800 Subject: [PATCH 2/2] distinguish the frame_allocator fix #175 --- .../pages/4.coroutines/4d.io-awaitable.adoc | 2 +- .../capy/ex/io_awaitable_promise_base.hpp | 12 +++++------ include/boost/capy/ex/io_env.hpp | 6 +++--- include/boost/capy/ex/run.hpp | 8 ++++---- include/boost/capy/ex/this_coro.hpp | 20 +++++++++---------- include/boost/capy/task.hpp | 4 ++-- include/boost/capy/when_all.hpp | 2 +- include/boost/capy/when_any.hpp | 4 ++-- papers/B1002.comparison-to-cobalt.md | 4 ++-- test/unit/ex/get_executor.cpp | 16 +++++++-------- test/unit/ex/get_stop_token.cpp | 4 ++-- test/unit/ex/io_awaitable_promise_base.cpp | 2 +- test/unit/ex/run.cpp | 4 ++-- test/unit/ex/run_async.cpp | 2 +- test/unit/ex/run_blocking.cpp | 2 +- 15 files changed, 46 insertions(+), 46 deletions(-) diff --git a/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc b/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc index a4532089..59d022f3 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc @@ -51,7 +51,7 @@ This signature receives: * `env` — The execution environment containing: ** `env->executor` — The caller's executor for dispatching completions ** `env->stop_token` — A stop token for cooperative cancellation -** `env->allocator` — An optional allocator for frame allocation +** `env->frame_allocator` — An optional frame allocator The return type enables symmetric transfer. diff --git a/include/boost/capy/ex/io_awaitable_promise_base.hpp b/include/boost/capy/ex/io_awaitable_promise_base.hpp index 403df56c..e3a4c505 100644 --- a/include/boost/capy/ex/io_awaitable_promise_base.hpp +++ b/include/boost/capy/ex/io_awaitable_promise_base.hpp @@ -93,7 +93,7 @@ namespace capy { The mixin's `await_transform` intercepts @ref this_coro::environment_tag and the fine-grained tag types (@ref this_coro::executor_tag, - @ref this_coro::stop_token_tag, @ref this_coro::allocator_tag), + @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag), then delegates all other awaitables to your `transform_awaitable`. @par Making Your Coroutine an IoAwaitable @@ -268,7 +268,7 @@ class io_awaitable_promise_base This function handles @ref this_coro::environment_tag and the fine-grained tags (@ref this_coro::executor_tag, - @ref this_coro::stop_token_tag, @ref this_coro::allocator_tag) + @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag) specially, returning an awaiter that yields the stored value. All other awaitables are delegated to @ref transform_awaitable. @@ -317,17 +317,17 @@ class io_awaitable_promise_base }; return awaiter{env_->stop_token}; } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { BOOST_CAPY_ASSERT(env_); struct awaiter { - std::pmr::memory_resource* allocator_; + std::pmr::memory_resource* frame_allocator_; bool await_ready() const noexcept { return true; } void await_suspend(std::coroutine_handle<>) const noexcept { } - std::pmr::memory_resource* await_resume() const noexcept { return allocator_; } + std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; } }; - return awaiter{env_->allocator}; + return awaiter{env_->frame_allocator}; } else { diff --git a/include/boost/capy/ex/io_env.hpp b/include/boost/capy/ex/io_env.hpp index 68108474..085b5508 100644 --- a/include/boost/capy/ex/io_env.hpp +++ b/include/boost/capy/ex/io_env.hpp @@ -24,7 +24,7 @@ namespace capy { This struct bundles the execution context passed through coroutine chains via the IoAwaitable protocol. It contains the executor for resumption, a stop token for cancellation, - and an optional allocator for coroutine frame allocation. + and an optional frame allocator for coroutine frame allocation. @par Lifetime @@ -47,11 +47,11 @@ struct io_env /** The stop token for cancellation propagation. */ std::stop_token stop_token; - /** The allocator for coroutine frame allocation. + /** The frame allocator for coroutine frame allocation. When null, the default allocator is used. */ - std::pmr::memory_resource* allocator = nullptr; + std::pmr::memory_resource* frame_allocator = nullptr; }; } // capy diff --git a/include/boost/capy/ex/run.hpp b/include/boost/capy/ex/run.hpp index 12787d76..afb74ee7 100644 --- a/include/boost/capy/ex/run.hpp +++ b/include/boost/capy/ex/run.hpp @@ -246,9 +246,9 @@ struct [[nodiscard]] run_awaitable_ex env_.stop_token = st_; if constexpr (!std::is_void_v) - env_.allocator = resource_.get(); + env_.frame_allocator = resource_.get(); else - env_.allocator = caller_env->allocator; + env_.frame_allocator = caller_env->frame_allocator; p.set_environment(&env_); return h; @@ -345,9 +345,9 @@ struct [[nodiscard]] run_awaitable env_.stop_token = st_; if constexpr (!std::is_void_v) - env_.allocator = resource_.get(); + env_.frame_allocator = resource_.get(); else - env_.allocator = caller_env->allocator; + env_.frame_allocator = caller_env->frame_allocator; p.set_environment(&env_); return h; diff --git a/include/boost/capy/ex/this_coro.hpp b/include/boost/capy/ex/this_coro.hpp index e817e44e..6f7bca23 100644 --- a/include/boost/capy/ex/this_coro.hpp +++ b/include/boost/capy/ex/this_coro.hpp @@ -29,7 +29,7 @@ namespace capy { auto const& env = co_await this_coro::environment; auto ex = co_await this_coro::executor; auto token = co_await this_coro::stop_token; - auto* alloc = co_await this_coro::allocator; + auto* alloc = co_await this_coro::frame_allocator; } @endcode @@ -70,16 +70,16 @@ struct executor_tag {}; */ struct stop_token_tag {}; -/** Tag type for coroutine allocator retrieval. +/** Tag type for coroutine frame allocator retrieval. This tag is intercepted by a promise type's `await_transform` to yield the coroutine's current frame allocator. The tag itself carries no data; it serves only as a sentinel for compile-time dispatch. - @see allocator + @see frame_allocator @see io_awaitable_promise_base */ -struct allocator_tag {}; +struct frame_allocator_tag {}; /** Tag object that yields the current environment when awaited. @@ -95,7 +95,7 @@ struct allocator_tag {}; auto const& env = co_await this_coro::environment; // env.executor - the executor this coroutine is bound to // env.stop_token - the stop token for cancellation - // env.allocator - the allocator for frame allocation + // env.frame_allocator - the frame allocator } @endcode @@ -164,8 +164,8 @@ inline constexpr stop_token_tag stop_token{}; /** Tag object that yields the current frame allocator when awaited. - Use `co_await this_coro::allocator` inside a coroutine whose promise - type supports allocator access (e.g., inherits from + Use `co_await this_coro::frame_allocator` inside a coroutine whose promise + type supports frame allocator access (e.g., inherits from @ref io_awaitable_promise_base). The returned pointer is the memory resource used for coroutine frame allocation. @@ -173,7 +173,7 @@ inline constexpr stop_token_tag stop_token{}; @code task example() { - auto* alloc = co_await this_coro::allocator; + auto* alloc = co_await this_coro::frame_allocator; // alloc is nullptr when using the default allocator } @endcode @@ -182,10 +182,10 @@ inline constexpr stop_token_tag stop_token{}; @li Returns `nullptr` when the default allocator is in use. @li This operation never suspends; `await_ready()` always returns `true`. - @see allocator_tag + @see frame_allocator_tag @see io_awaitable_promise_base */ -inline constexpr allocator_tag allocator{}; +inline constexpr frame_allocator_tag frame_allocator{}; } // namespace this_coro } // namespace capy diff --git a/include/boost/capy/task.hpp b/include/boost/capy/task.hpp index 97d4cf1f..b50cfc46 100644 --- a/include/boost/capy/task.hpp +++ b/include/boost/capy/task.hpp @@ -148,7 +148,7 @@ struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE void await_resume() const noexcept { // Restore TLS when body starts executing - set_current_frame_allocator(p_->environment()->allocator); + set_current_frame_allocator(p_->environment()->frame_allocator); } }; return awaiter{this}; @@ -197,7 +197,7 @@ struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE decltype(auto) await_resume() { // Restore TLS before body resumes - set_current_frame_allocator(p_->environment()->allocator); + set_current_frame_allocator(p_->environment()->frame_allocator); return a_.await_resume(); } diff --git a/include/boost/capy/when_all.hpp b/include/boost/capy/when_all.hpp index 0f3200a5..bd539f23 100644 --- a/include/boost/capy/when_all.hpp +++ b/include/boost/capy/when_all.hpp @@ -346,7 +346,7 @@ class when_all_launcher auto h = runner.release(); h.promise().state_ = state_; - h.promise().env_ = io_env{caller_ex, token, state_->caller_env_->allocator}; + h.promise().env_ = io_env{caller_ex, token, state_->caller_env_->frame_allocator}; std::coroutine_handle<> ch{h}; state_->runner_handles_[I] = ch; diff --git a/include/boost/capy/when_any.hpp b/include/boost/capy/when_any.hpp index 8965c1ba..99713902 100644 --- a/include/boost/capy/when_any.hpp +++ b/include/boost/capy/when_any.hpp @@ -501,7 +501,7 @@ class when_any_launcher auto h = runner.release(); h.promise().state_ = state_; h.promise().index_ = I; - h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->allocator}; + h.promise().env_ = io_env{caller_ex, token, state_->core_.caller_env_->frame_allocator}; std::coroutine_handle<> ch{h}; state_->runner_handles_[I] = ch; @@ -763,7 +763,7 @@ class when_any_homogeneous_launcher auto h = runner.release(); h.promise().state_ = state_; h.promise().index_ = index; - h.promise().env_ = io_env{caller_env->executor, token, caller_env->allocator}; + h.promise().env_ = io_env{caller_env->executor, token, caller_env->frame_allocator}; state_->runner_handles_[index] = std::coroutine_handle<>{h}; ++index; diff --git a/papers/B1002.comparison-to-cobalt.md b/papers/B1002.comparison-to-cobalt.md index e10d05a9..e78d46d4 100644 --- a/papers/B1002.comparison-to-cobalt.md +++ b/papers/B1002.comparison-to-cobalt.md @@ -507,7 +507,7 @@ int main() { The burden at each level: - Every coroutine needs `Args&&...` or explicit allocator parameter -- Every coroutine must call `co_await this_coro::allocator` to query it +- Every coroutine must call `co_await this_coro::frame_allocator` to query it - Every child invocation requires `std::allocator_arg, alloc` - Forgetting to forward breaks the chain silently—child uses default allocator @@ -653,7 +653,7 @@ IoAwaitable uses tag types that `await_transform` intercepts. Fine-grained acces task example() { auto ex = co_await this_coro::executor; // executor_ref auto token = co_await this_coro::stop_token; // std::stop_token - auto* alloc = co_await this_coro::allocator; // std::pmr::memory_resource* + auto* alloc = co_await this_coro::frame_allocator; // std::pmr::memory_resource* auto env = co_await this_coro::environment; // io_env const* } ``` diff --git a/test/unit/ex/get_executor.cpp b/test/unit/ex/get_executor.cpp index b1d2ba68..2063ec0d 100644 --- a/test/unit/ex/get_executor.cpp +++ b/test/unit/ex/get_executor.cpp @@ -126,20 +126,20 @@ struct this_coro_tags_test void testAllocatorTagType() { - this_coro::allocator_tag tag1; - this_coro::allocator_tag tag2{}; + this_coro::frame_allocator_tag tag1; + this_coro::frame_allocator_tag tag2{}; (void)tag1; (void)tag2; - static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); } void testAllocatorConstant() { - auto tag = this_coro::allocator; + auto tag = this_coro::frame_allocator; static_assert(std::is_same_v< - decltype(this_coro::allocator), this_coro::allocator_tag const>); + decltype(this_coro::frame_allocator), this_coro::frame_allocator_tag const>); (void)tag; } @@ -195,10 +195,10 @@ struct this_coro_tags_test auto* mr = std::pmr::new_delete_resource(); io_env env; - env.allocator = mr; + env.frame_allocator = mr; c.h_.promise().set_environment(&env); - auto awaiter = c.h_.promise().await_transform(this_coro::allocator); + auto awaiter = c.h_.promise().await_transform(this_coro::frame_allocator); BOOST_TEST(awaiter.await_ready()); @@ -214,7 +214,7 @@ struct this_coro_tags_test io_env env; c.h_.promise().set_environment(&env); - auto awaiter = c.h_.promise().await_transform(this_coro::allocator); + auto awaiter = c.h_.promise().await_transform(this_coro::frame_allocator); auto* alloc = awaiter.await_resume(); BOOST_TEST(alloc == nullptr); } diff --git a/test/unit/ex/get_stop_token.cpp b/test/unit/ex/get_stop_token.cpp index 2512c980..8bfb95d4 100644 --- a/test/unit/ex/get_stop_token.cpp +++ b/test/unit/ex/get_stop_token.cpp @@ -156,10 +156,10 @@ struct this_coro_environment_access_test auto* mr = std::pmr::new_delete_resource(); io_env env; - env.allocator = mr; + env.frame_allocator = mr; c.h_.promise().set_environment(&env); - auto awaiter = c.h_.promise().await_transform(this_coro::allocator); + auto awaiter = c.h_.promise().await_transform(this_coro::frame_allocator); BOOST_TEST(awaiter.await_ready()); auto* alloc = awaiter.await_resume(); diff --git a/test/unit/ex/io_awaitable_promise_base.cpp b/test/unit/ex/io_awaitable_promise_base.cpp index 632927db..c0dd3f35 100644 --- a/test/unit/ex/io_awaitable_promise_base.cpp +++ b/test/unit/ex/io_awaitable_promise_base.cpp @@ -190,7 +190,7 @@ struct io_awaitable_promise_base_test c.h_.promise().await_transform(this_coro::stop_token); BOOST_TEST_EQ(c.h_.promise().transform_count_, 1); - c.h_.promise().await_transform(this_coro::allocator); + c.h_.promise().await_transform(this_coro::frame_allocator); BOOST_TEST_EQ(c.h_.promise().transform_count_, 1); } diff --git a/test/unit/ex/run.cpp b/test/unit/ex/run.cpp index e96a3977..5fe17ccd 100644 --- a/test/unit/ex/run.cpp +++ b/test/unit/ex/run.cpp @@ -346,7 +346,7 @@ struct run_test bool result = false; auto check = []() -> task { - auto* alloc = co_await this_coro::allocator; + auto* alloc = co_await this_coro::frame_allocator; co_return alloc != nullptr; }; @@ -364,7 +364,7 @@ struct run_test bool result = false; auto inner = []() -> task { - auto* alloc = co_await this_coro::allocator; + auto* alloc = co_await this_coro::frame_allocator; co_return alloc != nullptr; }; diff --git a/test/unit/ex/run_async.cpp b/test/unit/ex/run_async.cpp index ec7cd484..9a8a9003 100644 --- a/test/unit/ex/run_async.cpp +++ b/test/unit/ex/run_async.cpp @@ -365,7 +365,7 @@ struct run_async_test static task check_allocator_propagated() { - auto* alloc = co_await this_coro::allocator; + auto* alloc = co_await this_coro::frame_allocator; co_return alloc != nullptr; } diff --git a/test/unit/ex/run_blocking.cpp b/test/unit/ex/run_blocking.cpp index 9fbd963e..e6bd60c1 100644 --- a/test/unit/ex/run_blocking.cpp +++ b/test/unit/ex/run_blocking.cpp @@ -268,7 +268,7 @@ struct run_blocking_test static task check_allocator_propagated() { - auto* alloc = co_await this_coro::allocator; + auto* alloc = co_await this_coro::frame_allocator; co_return alloc != nullptr; }