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