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