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 +
    */
 
75 +
    static void* operator new(std::size_t size)
 
76 +
    {
 
77 +
        static auto* const rmr = get_recycling_memory_resource();
 
78 +

 
79 +
        auto* mr = get_current_frame_allocator();
 
80 +
        if(!mr)
 
81 +
            mr = std::pmr::get_default_resource();
 
82 +

 
83 +
        auto total = size + sizeof(std::pmr::memory_resource*);
 
84 +
        void* raw;
 
85 +
        if(mr == rmr)
 
86 +
            raw = static_cast<recycling_memory_resource*>(mr)
 
87 +
                ->allocate_fast(total, alignof(std::max_align_t));
 
88 +
        else
 
89 +
            raw = mr->allocate(total, alignof(std::max_align_t));
 
90 +
        std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
 
91 +
        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 +
    */
 
100 +
    static void operator delete(void* ptr, std::size_t size) noexcept
 
101 +
    {
 
102 +
        static auto* const rmr = get_recycling_memory_resource();
 
103 +

 
104 +
        std::pmr::memory_resource* mr;
 
105 +
        std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
 
106 +
        auto total = size + sizeof(std::pmr::memory_resource*);
 
107 +
        if(mr == rmr)
 
108 +
            static_cast<recycling_memory_resource*>(mr)
 
109 +
                ->deallocate_fast(ptr, total, alignof(std::max_align_t));
 
110 +
        else
 
111 +
            mr->deallocate(ptr, total, alignof(std::max_align_t));
 
112 +
    }
 
113 +
};
 
114 +

 
115 +
} // namespace capy
 
116 +
} // namespace boost
 
117 +

 
118 +
#endif