LCOV - code coverage report
Current view: top level - capy/ex - frame_alloc_mixin.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 26 26
Test Date: 2026-10-30 18:56:53 Functions: 100.0 % 4 4

           TLA  Line data    Source 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/detail/frame_memory_resource.hpp>
      15                 : #include <boost/capy/ex/frame_allocator.hpp>
      16                 : #include <boost/capy/ex/recycling_memory_resource.hpp>
      17                 : 
      18                 : #include <cstddef>
      19                 : #include <cstring>
      20                 : #include <memory_resource>
      21                 : 
      22                 : namespace boost {
      23                 : namespace capy {
      24                 : 
      25                 : /** Mixin that adds frame-allocator-aware allocation to a promise type.
      26                 : 
      27                 :     Inherit from this class in any coroutine promise type to opt into
      28                 :     TLS-based frame allocation with the recycling memory resource
      29                 :     fast path. The mixin provides `operator new` and `operator delete`
      30                 :     that:
      31                 : 
      32                 :     1. Read the thread-local frame allocator set by `run_async` or `run`.
      33                 :     2. Bypass virtual dispatch when the allocator is the default
      34                 :        recycling memory resource.
      35                 :     3. Store the allocator pointer at the end of each frame for
      36                 :        correct deallocation even when TLS changes between allocation
      37                 :        and deallocation.
      38                 : 
      39                 :     This is the same allocation strategy used by @ref
      40                 :     context_awaitable_promise_base. Use this mixin directly when your
      41                 :     promise type does not need the full environment and continuation
      42                 :     support that `context_awaitable_promise_base` provides.
      43                 : 
      44                 :     @par Example
      45                 :     @par !example example
      46                 : 
      47                 : 
      48                 :     @par Thread Safety
      49                 :     The allocation fast path uses thread-local storage and requires
      50                 :     no synchronization. The global pool fallback is mutex-protected.
      51                 : 
      52                 :     @see @ref context_awaitable_promise_base, @ref recycling_memory_resource
      53                 : */
      54                 : struct frame_alloc_mixin
      55                 : {
      56                 :     /** Allocate a coroutine frame.
      57                 : 
      58                 :         Uses the thread-local frame allocator set by run_async.
      59                 :         Falls back to default memory resource if not set.
      60                 :         Stores the allocator pointer at the end of each frame for
      61                 :         correct deallocation even when TLS changes. Uses memcpy
      62                 :         to avoid alignment requirements on the trailing pointer.
      63                 :         Bypasses virtual dispatch for the recycling allocator.
      64                 : 
      65                 :         @param size The size, in bytes, of the coroutine frame.
      66                 : 
      67                 :         @return A pointer to storage for the frame.
      68                 : 
      69                 :         @par Exception Safety
      70                 :         Propagates any exception thrown by the underlying memory
      71                 :         resource's `allocate`, for example `std::bad_alloc` from
      72                 :         `::operator new`.
      73                 :     */
      74 HIT      109312 :     static void* operator new(std::size_t size)
      75                 :     {
      76          109312 :         static auto* const rmr = get_recycling_memory_resource();
      77                 : 
      78          109312 :         auto* mr = get_current_frame_allocator();
      79          109312 :         if(!mr)
      80            2179 :             mr = std::pmr::get_default_resource();
      81                 : 
      82          109312 :         auto total = size + sizeof(std::pmr::memory_resource*);
      83                 :         void* raw;
      84          109312 :         if(mr == rmr)
      85                 :             raw = static_cast<recycling_memory_resource*>(mr)
      86          103336 :                 ->allocate_fast(total, detail::frame_alignment);
      87                 :         else
      88            5976 :             raw = mr->allocate(total, detail::frame_alignment);
      89          109312 :         std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
      90          109312 :         return raw;
      91                 :     }
      92                 : 
      93                 :     /** Deallocate a coroutine frame.
      94                 : 
      95                 :         Reads the allocator pointer stored at the end of the frame
      96                 :         to ensure correct deallocation regardless of current TLS.
      97                 :         Bypasses virtual dispatch for the recycling allocator.
      98                 : 
      99                 :         @param ptr The frame storage returned by `operator new`.
     100                 : 
     101                 :         @param size The size, in bytes, that was passed to `operator new`.
     102                 :         The allocator pointer is read from `ptr + size`, which is where
     103                 :         `operator new` wrote it, so this value must match.
     104                 :     */
     105          109312 :     static void operator delete(void* ptr, std::size_t size) noexcept
     106                 :     {
     107          109312 :         static auto* const rmr = get_recycling_memory_resource();
     108                 : 
     109                 :         std::pmr::memory_resource* mr;
     110          109312 :         std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
     111          109312 :         auto total = size + sizeof(std::pmr::memory_resource*);
     112          109312 :         if(mr == rmr)
     113                 :             static_cast<recycling_memory_resource*>(mr)
     114          103336 :                 ->deallocate_fast(ptr, total, detail::frame_alignment);
     115                 :         else
     116            5976 :             mr->deallocate(ptr, total, detail::frame_alignment);
     117          109312 :     }
     118                 : };
     119                 : 
     120                 : namespace detail {
     121                 : 
     122                 : // For capy's own frames whose teardown can release the chain that owns
     123                 : // the frame allocator: they are never allocated from it, so freeing them
     124                 : // afterwards never reaches a resource that died with that chain.
     125                 : struct process_frame_mixin
     126                 : {
     127             422 :     static void* operator new(std::size_t size)
     128                 :     {
     129                 :         return static_cast<recycling_memory_resource*>(
     130             422 :             get_recycling_memory_resource())
     131             422 :                 ->allocate_fast(size, detail::frame_alignment);
     132                 :     }
     133                 : 
     134             422 :     static void operator delete(void* ptr, std::size_t size) noexcept
     135                 :     {
     136                 :         static_cast<recycling_memory_resource*>(
     137             422 :             get_recycling_memory_resource())
     138             422 :                 ->deallocate_fast(ptr, size, detail::frame_alignment);
     139             422 :     }
     140                 : };
     141                 : 
     142                 : } // namespace detail
     143                 : 
     144                 : } // namespace capy
     145                 : } // namespace boost
     146                 : 
     147                 : #endif
        

Generated by: LCOV version 2.3