xref: /openbmc/sdbusplus/include/sdbusplus/async/stdexec/coroutine.hpp (revision 10d0b4b7d1498cfd5c3d37edea271a54d1984e41)
1 /*
2  * Copyright (c) 2021-2024 NVIDIA Corporation
3  *
4  * Licensed under the Apache License Version 2.0 with LLVM Exceptions
5  * (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  *
8  *   https://llvm.org/LICENSE.txt
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "__detail/__awaitable.hpp" // IWYU pragma: export
19 #include "__detail/__config.hpp"
20 
21 #if STDEXEC_MSVC() && STDEXEC_MSVC_VERSION <= 19'39
22 namespace stdexec {
23   // MSVCBUG https://developercommunity.visualstudio.com/t/destroy-coroutine-from-final_suspend-r/10096047
24 
25   // Prior to Visual Studio 17.9 (Feb, 2024), aka MSVC 19.39, MSVC incorrectly allocates the return
26   // buffer for await_suspend calls within the suspended coroutine frame. When the suspended
27   // coroutine is destroyed within await_suspend, the continuation coroutine handle is not only used
28   // after free, but also overwritten by the debug malloc implementation when NRVO is in play.
29 
30   // This workaround delays the destruction of the suspended coroutine by wrapping the continuation
31   // in another coroutine which destroys the former and transfers execution to the original
32   // continuation.
33 
34   // The wrapping coroutine is thread-local and is reused within the thread for each
35   // destroy-and-continue sequence. The wrapping coroutine itself is destroyed at thread exit.
36 
37   namespace __destroy_and_continue_msvc {
38     struct __task {
39       struct promise_type {
get_return_objectstdexec::__destroy_and_continue_msvc::__task::promise_type40         __task get_return_object() noexcept {
41           return {__coro::coroutine_handle<promise_type>::from_promise(*this)};
42         }
43 
initial_suspendstdexec::__destroy_and_continue_msvc::__task::promise_type44         static std::suspend_never initial_suspend() noexcept {
45           return {};
46         }
47 
final_suspendstdexec::__destroy_and_continue_msvc::__task::promise_type48         static std::suspend_never final_suspend() noexcept {
49           STDEXEC_ASSERT(!"Should never get here");
50           return {};
51         }
52 
return_voidstdexec::__destroy_and_continue_msvc::__task::promise_type53         static void return_void() noexcept {
54           STDEXEC_ASSERT(!"Should never get here");
55         }
56 
unhandled_exceptionstdexec::__destroy_and_continue_msvc::__task::promise_type57         static void unhandled_exception() noexcept {
58           STDEXEC_ASSERT(!"Should never get here");
59         }
60       };
61 
62       __coro::coroutine_handle<> __coro_;
63     };
64 
65     struct __continue_t {
await_readystdexec::__destroy_and_continue_msvc::__continue_t66       static constexpr bool await_ready() noexcept {
67         return false;
68       }
69 
await_suspendstdexec::__destroy_and_continue_msvc::__continue_t70       __coro::coroutine_handle<> await_suspend(__coro::coroutine_handle<>) noexcept {
71         return __continue_;
72       }
73 
await_resumestdexec::__destroy_and_continue_msvc::__continue_t74       static void await_resume() noexcept {
75       }
76 
77       __coro::coroutine_handle<> __continue_;
78     };
79 
80     struct __context {
81       __coro::coroutine_handle<> __destroy_;
82       __coro::coroutine_handle<> __continue_;
83     };
84 
__co_impl(__context & __c)85     inline __task __co_impl(__context& __c) {
86       while (true) {
87         co_await __continue_t{__c.__continue_};
88         __c.__destroy_.destroy();
89       }
90     }
91 
92     struct __context_and_coro {
__context_and_corostdexec::__destroy_and_continue_msvc::__context_and_coro93       __context_and_coro() {
94         __context_.__continue_ = __coro::noop_coroutine();
95         __coro_ = __co_impl(__context_).__coro_;
96       }
97 
~__context_and_corostdexec::__destroy_and_continue_msvc::__context_and_coro98       ~__context_and_coro() {
99         __coro_.destroy();
100       }
101 
102       __context __context_;
103       __coro::coroutine_handle<> __coro_;
104     };
105 
106     inline __coro::coroutine_handle<>
__impl(__coro::coroutine_handle<> __destroy,__coro::coroutine_handle<> __continue)107       __impl(__coro::coroutine_handle<> __destroy, __coro::coroutine_handle<> __continue) {
108       static thread_local __context_and_coro __c;
109       __c.__context_.__destroy_ = __destroy;
110       __c.__context_.__continue_ = __continue;
111       return __c.__coro_;
112     }
113   } // namespace __destroy_and_continue_msvc
114 } // namespace stdexec
115 
116 #  define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue)                                      \
117     (::stdexec::__destroy_and_continue_msvc::__impl(__destroy, __continue))
118 #else
119 #  define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue) (__destroy.destroy(), __continue)
120 #endif
121