Workaround MSVC bug that breaks when_all() and sync_wait().
MSVC 2017.8 generates bad code for when_all() and sync_wait() that causes a crash due to it resuming the coroutine at the wrong suspend-point in the expression 'co_yield co_await x'. Work around this by manually calling promise.yield_value() instead of using co_yield.
This commit is contained in:
parent
868c0f427d
commit
3864a047db
|
@ -66,6 +66,39 @@ namespace cppcoro
|
||||||
return completion_notifier{};
|
return completion_notifier{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
|
||||||
|
// See comment in make_sync_wait_task below.
|
||||||
|
template<typename Awaitable>
|
||||||
|
Awaitable&& await_transform(Awaitable&& awaitable)
|
||||||
|
{
|
||||||
|
return static_cast<Awaitable&&>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct get_promise_t {};
|
||||||
|
static constexpr get_promise_t get_promise = {};
|
||||||
|
|
||||||
|
auto await_transform(get_promise_t)
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
awaiter(sync_wait_task_promise* promise) noexcept : m_promise(promise) {}
|
||||||
|
bool await_ready() noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void await_suspend(std::experimental::coroutine_handle<>) noexcept {}
|
||||||
|
sync_wait_task_promise& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return *m_promise;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
sync_wait_task_promise* m_promise;
|
||||||
|
};
|
||||||
|
return awaiter{ this };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto yield_value(reference result) noexcept
|
auto yield_value(reference result) noexcept
|
||||||
{
|
{
|
||||||
m_result = std::addressof(result);
|
m_result = std::addressof(result);
|
||||||
|
@ -221,7 +254,15 @@ namespace cppcoro
|
||||||
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE& awaitable)
|
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE& awaitable)
|
||||||
{
|
{
|
||||||
co_yield co_await std::forward<AWAITABLE>(awaitable);
|
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||||
|
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||||
|
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||||
|
// rather than before the 'co_yield'.
|
||||||
|
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||||
|
auto& promise = co_await sync_wait_task_promise<RESULT>::get_promise;
|
||||||
|
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
|
||||||
|
|
||||||
|
//co_yield co_await std::forward<AWAITABLE>(awaitable);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<
|
template<
|
||||||
|
@ -230,7 +271,7 @@ namespace cppcoro
|
||||||
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||||
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
|
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
|
||||||
{
|
{
|
||||||
co_await std::forward<AWAITABLE>(awaitable);
|
co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
template<
|
template<
|
||||||
|
|
|
@ -76,6 +76,40 @@ namespace cppcoro
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
|
||||||
|
// See comment in make_when_all_task below.
|
||||||
|
template<typename Awaitable>
|
||||||
|
Awaitable&& await_transform(Awaitable&& awaitable)
|
||||||
|
{
|
||||||
|
return static_cast<Awaitable&&>(awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct get_promise_t {};
|
||||||
|
static constexpr get_promise_t get_promise = {};
|
||||||
|
|
||||||
|
auto await_transform(get_promise_t)
|
||||||
|
{
|
||||||
|
class awaiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
awaiter(when_all_task_promise* promise) noexcept : m_promise(promise) {}
|
||||||
|
bool await_ready() noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void await_suspend(std::experimental::coroutine_handle<>) noexcept {}
|
||||||
|
when_all_task_promise& await_resume() noexcept
|
||||||
|
{
|
||||||
|
return *m_promise;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
when_all_task_promise* m_promise;
|
||||||
|
};
|
||||||
|
return awaiter{ this };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
auto yield_value(RESULT&& result) noexcept
|
auto yield_value(RESULT&& result) noexcept
|
||||||
{
|
{
|
||||||
m_result = std::addressof(result);
|
m_result = std::addressof(result);
|
||||||
|
@ -267,7 +301,17 @@ namespace cppcoro
|
||||||
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
when_all_task<RESULT> make_when_all_task(AWAITABLE awaitable)
|
when_all_task<RESULT> make_when_all_task(AWAITABLE awaitable)
|
||||||
{
|
{
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||||
|
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||||
|
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||||
|
// rather than before the 'co_yield'.
|
||||||
|
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||||
|
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
|
||||||
|
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
|
||||||
|
#else
|
||||||
co_yield co_await static_cast<AWAITABLE&&>(awaitable);
|
co_yield co_await static_cast<AWAITABLE&&>(awaitable);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template<
|
template<
|
||||||
|
@ -285,7 +329,17 @@ namespace cppcoro
|
||||||
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||||
when_all_task<RESULT> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
|
when_all_task<RESULT> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
|
||||||
{
|
{
|
||||||
|
#if CPPCORO_COMPILER_MSVC
|
||||||
|
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||||
|
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||||
|
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||||
|
// rather than before the 'co_yield'.
|
||||||
|
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||||
|
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
|
||||||
|
co_await promise.yield_value(co_await awaitable.get());
|
||||||
|
#else
|
||||||
co_yield co_await awaitable.get();
|
co_yield co_await awaitable.get();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template<
|
template<
|
||||||
|
|
|
@ -21,7 +21,8 @@ namespace cppcoro
|
||||||
#if CPPCORO_COMPILER_MSVC
|
#if CPPCORO_COMPILER_MSVC
|
||||||
// HACK: Need to explicitly specify template argument to make_sync_wait_task
|
// HACK: Need to explicitly specify template argument to make_sync_wait_task
|
||||||
// here to work around a bug in MSVC when passing parameters by universal
|
// here to work around a bug in MSVC when passing parameters by universal
|
||||||
// reference to
|
// reference to a coroutine which causes the compiler to think it needs to
|
||||||
|
// 'move' parameters passed by rvalue reference.
|
||||||
auto task = detail::make_sync_wait_task<AWAITABLE>(awaitable);
|
auto task = detail::make_sync_wait_task<AWAITABLE>(awaitable);
|
||||||
#else
|
#else
|
||||||
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
|
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
|
||||||
|
|
Loading…
Reference in a new issue