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
await_suspendstdexec::__destroy_and_continue_msvc::__continue_t85 __coro::coroutine_handle<> await_suspend(
86 __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