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:
Lewis Baker 2018-08-17 07:36:35 -07:00
parent 868c0f427d
commit 3864a047db
3 changed files with 99 additions and 3 deletions

View file

@ -66,6 +66,39 @@ namespace cppcoro
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
{
m_result = std::addressof(result);
@ -221,7 +254,15 @@ namespace cppcoro
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
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<
@ -230,7 +271,7 @@ namespace cppcoro
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
{
co_await std::forward<AWAITABLE>(awaitable);
co_await static_cast<AWAITABLE&&>(awaitable);
}
#else
template<

View file

@ -76,6 +76,40 @@ namespace cppcoro
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
{
m_result = std::addressof(result);
@ -267,7 +301,17 @@ namespace cppcoro
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
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);
#endif
}
template<
@ -285,7 +329,17 @@ namespace cppcoro
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
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();
#endif
}
template<

View file

@ -21,7 +21,8 @@ namespace cppcoro
#if CPPCORO_COMPILER_MSVC
// 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
// 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);
#else
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));