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"
19 #include "__detail/__config.hpp"
20 
21 #if STDEXEC_MSVC() && _MSC_VER >= 1939
22 namespace stdexec
23 {
24 // MSVCBUG
25 // https://developercommunity.visualstudio.com/t/destroy-coroutine-from-final_suspend-r/10096047
26 
27 // Prior to Visual Studio 17.9 (Feb, 2024), aka MSVC 19.39, MSVC incorrectly
28 // allocates the return buffer for await_suspend calls within the suspended
29 // coroutine frame. When the suspended coroutine is destroyed within
30 // await_suspend, the continuation coroutine handle is not only used after free,
31 // but also overwritten by the debug malloc implementation when NRVO is in play.
32 
33 // This workaround delays the destruction of the suspended coroutine by wrapping
34 // the continuation in another coroutine which destroys the former and transfers
35 // execution to the original continuation.
36 
37 // The wrapping coroutine is thread-local and is reused within the thread for
38 // each destroy-and-continue sequence. The wrapping coroutine itself is
39 // destroyed at thread exit.
40 
41 namespace __destroy_and_continue_msvc
42 {
43 struct __task
44 {
45     struct promise_type
46     {
get_return_objectstdexec::__destroy_and_continue_msvc::__task::promise_type47         __task get_return_object() noexcept
48         {
49             return {
50                 __coro::coroutine_handle<promise_type>::from_promise(*this)};
51         }
52 
initial_suspendstdexec::__destroy_and_continue_msvc::__task::promise_type53         static std::suspend_never initial_suspend() noexcept
54         {
55             return {};
56         }
57 
final_suspendstdexec::__destroy_and_continue_msvc::__task::promise_type58         static std::suspend_never final_suspend() noexcept
59         {
60             STDEXEC_ASSERT(!"Should never get here");
61             return {};
62         }
63 
return_voidstdexec::__destroy_and_continue_msvc::__task::promise_type64         static void return_void() noexcept
65         {
66             STDEXEC_ASSERT(!"Should never get here");
67         }
68 
unhandled_exceptionstdexec::__destroy_and_continue_msvc::__task::promise_type69         static void unhandled_exception() noexcept
70         {
71             STDEXEC_ASSERT(!"Should never get here");
72         }
73     };
74 
75     __coro::coroutine_handle<> __coro_;
76 };
77 
78 struct __continue_t
79 {
await_readystdexec::__destroy_and_continue_msvc::__continue_t80     static constexpr bool await_ready() noexcept
81     {
82         return false;
83     }
84 
85     __coro::coroutine_handle<>
await_suspendstdexec::__destroy_and_continue_msvc::__continue_t86         await_suspend(__coro::coroutine_handle<>) noexcept
87     {
88         return __continue_;
89     }
90 
await_resumestdexec::__destroy_and_continue_msvc::__continue_t91     static void await_resume() noexcept {}
92 
93     __coro::coroutine_handle<> __continue_;
94 };
95 
96 struct __context
97 {
98     __coro::coroutine_handle<> __destroy_;
99     __coro::coroutine_handle<> __continue_;
100 };
101 
__co_impl(__context & __c)102 inline __task __co_impl(__context& __c)
103 {
104     while (true)
105     {
106         co_await __continue_t{__c.__continue_};
107         __c.__destroy_.destroy();
108     }
109 }
110 
111 struct __context_and_coro
112 {
__context_and_corostdexec::__destroy_and_continue_msvc::__context_and_coro113     __context_and_coro()
114     {
115         __context_.__continue_ = __coro::noop_coroutine();
116         __coro_ = __co_impl(__context_).__coro_;
117     }
118 
~__context_and_corostdexec::__destroy_and_continue_msvc::__context_and_coro119     ~__context_and_coro()
120     {
121         __coro_.destroy();
122     }
123 
124     __context __context_;
125     __coro::coroutine_handle<> __coro_;
126 };
127 
__impl(__coro::coroutine_handle<> __destroy,__coro::coroutine_handle<> __continue)128 inline __coro::coroutine_handle<> __impl(__coro::coroutine_handle<> __destroy,
129                                          __coro::coroutine_handle<> __continue)
130 {
131     static thread_local __context_and_coro __c;
132     __c.__context_.__destroy_ = __destroy;
133     __c.__context_.__continue_ = __continue;
134     return __c.__coro_;
135 }
136 } // namespace __destroy_and_continue_msvc
137 } // namespace stdexec
138 
139 #define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue)                    \
140     (::stdexec::__destroy_and_continue_msvc::__impl(__destroy, __continue))
141 #else
142 #define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue)                    \
143     (__destroy.destroy(), __continue)
144 #endif
145