100.00% Lines (19/19) 100.00% Functions (2/2)
TLA Baseline Branch
Line Hits Code Line Hits Code
  1 + //
  2 + // Copyright (c) 2026 Michael Vandeberg
  3 + //
  4 + // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5 + // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6 + //
  7 + // Official repository: https://github.com/cppalliance/capy
  8 + //
  9 +
  10 + #ifndef BOOST_CAPY_EX_FRAME_ALLOC_MIXIN_HPP
  11 + #define BOOST_CAPY_EX_FRAME_ALLOC_MIXIN_HPP
  12 +
  13 + #include <boost/capy/detail/config.hpp>
  14 + #include <boost/capy/ex/frame_allocator.hpp>
  15 + #include <boost/capy/ex/recycling_memory_resource.hpp>
  16 +
  17 + #include <cstddef>
  18 + #include <cstring>
  19 + #include <memory_resource>
  20 +
  21 + namespace boost {
  22 + namespace capy {
  23 +
  24 + /** Mixin that adds frame-allocator-aware allocation to a promise type.
  25 +
  26 + Inherit from this class in any coroutine promise type to opt into
  27 + TLS-based frame allocation with the recycling memory resource
  28 + fast path. The mixin provides `operator new` and `operator delete`
  29 + that:
  30 +
  31 + 1. Read the thread-local frame allocator set by `run_async` or `run`.
  32 + 2. Bypass virtual dispatch when the allocator is the default
  33 + recycling memory resource.
  34 + 3. Store the allocator pointer at the end of each frame for
  35 + correct deallocation even when TLS changes between allocation
  36 + and deallocation.
  37 +
  38 + This is the same allocation strategy used by @ref
  39 + io_awaitable_promise_base. Use this mixin directly when your
  40 + promise type does not need the full environment and continuation
  41 + support that `io_awaitable_promise_base` provides.
  42 +
  43 + @par Example
  44 + @code
  45 + struct my_internal_coroutine
  46 + {
  47 + struct promise_type : frame_alloc_mixin
  48 + {
  49 + my_internal_coroutine get_return_object();
  50 + std::suspend_always initial_suspend() noexcept;
  51 + std::suspend_always final_suspend() noexcept;
  52 + void return_void();
  53 + void unhandled_exception() noexcept;
  54 + };
  55 + };
  56 + @endcode
  57 +
  58 + @par Thread Safety
  59 + The allocation fast path uses thread-local storage and requires
  60 + no synchronization. The global pool fallback is mutex-protected.
  61 +
  62 + @see io_awaitable_promise_base, frame_allocator, recycling_memory_resource
  63 + */
  64 + struct frame_alloc_mixin
  65 + {
  66 + /** Allocate a coroutine frame.
  67 +
  68 + Uses the thread-local frame allocator set by run_async.
  69 + Falls back to default memory resource if not set.
  70 + Stores the allocator pointer at the end of each frame for
  71 + correct deallocation even when TLS changes. Uses memcpy
  72 + to avoid alignment requirements on the trailing pointer.
  73 + Bypasses virtual dispatch for the recycling allocator.
  74 + */
HITGNC   75 + 5334 static void* operator new(std::size_t size)
  76 + {
HITGNC   77 + 5334 static auto* const rmr = get_recycling_memory_resource();
  78 +
HITGNC   79 + 5334 auto* mr = get_current_frame_allocator();
HITGNC   80 + 5334 if(!mr)
HITGNC   81 + 2816 mr = std::pmr::get_default_resource();
  82 +
HITGNC   83 + 5334 auto total = size + sizeof(std::pmr::memory_resource*);
  84 + void* raw;
HITGNC   85 + 5334 if(mr == rmr)
  86 + raw = static_cast<recycling_memory_resource*>(mr)
HITGNC   87 + 2104 ->allocate_fast(total, alignof(std::max_align_t));
  88 + else
HITGNC   89 + 3230 raw = mr->allocate(total, alignof(std::max_align_t));
HITGNC   90 + 5334 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITGNC   91 + 5334 return raw;
  92 + }
  93 +
  94 + /** Deallocate a coroutine frame.
  95 +
  96 + Reads the allocator pointer stored at the end of the frame
  97 + to ensure correct deallocation regardless of current TLS.
  98 + Bypasses virtual dispatch for the recycling allocator.
  99 + */
HITGNC   100 + 5334 static void operator delete(void* ptr, std::size_t size) noexcept
  101 + {
HITGNC   102 + 5334 static auto* const rmr = get_recycling_memory_resource();
  103 +
  104 + std::pmr::memory_resource* mr;
HITGNC   105 + 5334 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITGNC   106 + 5334 auto total = size + sizeof(std::pmr::memory_resource*);
HITGNC   107 + 5334 if(mr == rmr)
  108 + static_cast<recycling_memory_resource*>(mr)
HITGNC   109 + 2104 ->deallocate_fast(ptr, total, alignof(std::max_align_t));
  110 + else
HITGNC   111 + 3230 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITGNC   112 + 5334 }
  113 + };
  114 +
  115 + } // namespace capy
  116 + } // namespace boost
  117 +
  118 + #endif