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  
    @code
44  
    @code
45  
    struct my_internal_coroutine
45  
    struct my_internal_coroutine
46  
    {
46  
    {
47  
        struct promise_type : frame_alloc_mixin
47  
        struct promise_type : frame_alloc_mixin
48  
        {
48  
        {
49  
            my_internal_coroutine get_return_object();
49  
            my_internal_coroutine get_return_object();
50  
            std::suspend_always initial_suspend() noexcept;
50  
            std::suspend_always initial_suspend() noexcept;
51  
            std::suspend_always final_suspend() noexcept;
51  
            std::suspend_always final_suspend() noexcept;
52  
            void return_void();
52  
            void return_void();
53  
            void unhandled_exception() noexcept;
53  
            void unhandled_exception() noexcept;
54  
        };
54  
        };
55  
    };
55  
    };
56  
    @endcode
56  
    @endcode
57  

57  

58  
    @par Thread Safety
58  
    @par Thread Safety
59  
    The allocation fast path uses thread-local storage and requires
59  
    The allocation fast path uses thread-local storage and requires
60  
    no synchronization. The global pool fallback is mutex-protected.
60  
    no synchronization. The global pool fallback is mutex-protected.
61  

61  

62  
    @see io_awaitable_promise_base, frame_allocator, recycling_memory_resource
62  
    @see io_awaitable_promise_base, frame_allocator, recycling_memory_resource
63  
*/
63  
*/
64  
struct frame_alloc_mixin
64  
struct frame_alloc_mixin
65  
{
65  
{
66  
    /** Allocate a coroutine frame.
66  
    /** Allocate a coroutine frame.
67  

67  

68  
        Uses the thread-local frame allocator set by run_async.
68  
        Uses the thread-local frame allocator set by run_async.
69  
        Falls back to default memory resource if not set.
69  
        Falls back to default memory resource if not set.
70  
        Stores the allocator pointer at the end of each frame for
70  
        Stores the allocator pointer at the end of each frame for
71  
        correct deallocation even when TLS changes. Uses memcpy
71  
        correct deallocation even when TLS changes. Uses memcpy
72  
        to avoid alignment requirements on the trailing pointer.
72  
        to avoid alignment requirements on the trailing pointer.
73  
        Bypasses virtual dispatch for the recycling allocator.
73  
        Bypasses virtual dispatch for the recycling allocator.
74  
    */
74  
    */
75  
    static void* operator new(std::size_t size)
75  
    static void* operator new(std::size_t size)
76  
    {
76  
    {
77  
        static auto* const rmr = get_recycling_memory_resource();
77  
        static auto* const rmr = get_recycling_memory_resource();
78  

78  

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

82  

83  
        auto total = size + sizeof(std::pmr::memory_resource*);
83  
        auto total = size + sizeof(std::pmr::memory_resource*);
84  
        void* raw;
84  
        void* raw;
85  
        if(mr == rmr)
85  
        if(mr == rmr)
86  
            raw = static_cast<recycling_memory_resource*>(mr)
86  
            raw = static_cast<recycling_memory_resource*>(mr)
87  
                ->allocate_fast(total, alignof(std::max_align_t));
87  
                ->allocate_fast(total, alignof(std::max_align_t));
88  
        else
88  
        else
89  
            raw = mr->allocate(total, alignof(std::max_align_t));
89  
            raw = mr->allocate(total, alignof(std::max_align_t));
90  
        std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
90  
        std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
91  
        return raw;
91  
        return raw;
92  
    }
92  
    }
93  

93  

94  
    /** Deallocate a coroutine frame.
94  
    /** Deallocate a coroutine frame.
95  

95  

96  
        Reads the allocator pointer stored at the end of the frame
96  
        Reads the allocator pointer stored at the end of the frame
97  
        to ensure correct deallocation regardless of current TLS.
97  
        to ensure correct deallocation regardless of current TLS.
98  
        Bypasses virtual dispatch for the recycling allocator.
98  
        Bypasses virtual dispatch for the recycling allocator.
99  
    */
99  
    */
100  
    static void operator delete(void* ptr, std::size_t size) noexcept
100  
    static void operator delete(void* ptr, std::size_t size) noexcept
101  
    {
101  
    {
102  
        static auto* const rmr = get_recycling_memory_resource();
102  
        static auto* const rmr = get_recycling_memory_resource();
103  

103  

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

114  

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

117  

118  
#endif
118  
#endif