Skip to content

Commit f86130a

Browse files
committed
Fixed tail-call implementation for lazy_task amd when_all.
If await_suspend() returns a std::coroutine_handle then this is unconditionally resumed. So we now return a dummy coroutine_handle whose resume() method just immediately suspends in cases where there is no continuation ready to be resumed.
1 parent 39553bb commit f86130a

File tree

5 files changed

+84
-18
lines changed

5 files changed

+84
-18
lines changed

include/cppcoro/detail/continuation.hpp

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace cppcoro
1515
{
1616
public:
1717

18-
using callback_t = void(void*);
18+
using callback_t = std::experimental::coroutine_handle<>(void*);
1919

2020
continuation() noexcept
2121
: m_callback(nullptr)
@@ -39,26 +39,18 @@ namespace cppcoro
3939

4040
void resume() noexcept
4141
{
42-
if (m_callback == nullptr)
43-
{
44-
std::experimental::coroutine_handle<>::from_address(m_state).resume();
45-
}
46-
else
47-
{
48-
m_callback(m_state);
49-
}
42+
tail_call_resume().resume();
5043
}
5144

52-
std::experimental::coroutine_handle<> resume_or_get_coroutine_handle()
45+
std::experimental::coroutine_handle<> tail_call_resume()
5346
{
5447
if (m_callback == nullptr)
5548
{
5649
return std::experimental::coroutine_handle<>::from_address(m_state);
5750
}
5851
else
5952
{
60-
m_callback(m_state);
61-
return {};
53+
return m_callback(m_state);
6254
}
6355
}
6456

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
///////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) Lewis Baker
3+
// Licenced under MIT license. See LICENSE.txt for details.
4+
///////////////////////////////////////////////////////////////////////////////
5+
#ifndef CPPCORO_DETAIL_DUMMY_COROUTINE_HPP_INCLUDED
6+
#define CPPCORO_DETAIL_DUMMY_COROUTINE_HPP_INCLUDED
7+
8+
#include <experimental/coroutine>
9+
10+
namespace cppcoro
11+
{
12+
namespace detail
13+
{
14+
class dummy_coroutine final
15+
{
16+
public:
17+
18+
struct promise_type final
19+
{
20+
dummy_coroutine get_return_object() noexcept;
21+
std::experimental::suspend_never initial_suspend() noexcept { return {}; }
22+
std::experimental::suspend_never final_suspend() noexcept { return {}; }
23+
void unhandled_exception() noexcept {}
24+
void return_void() noexcept {}
25+
};
26+
27+
static std::experimental::coroutine_handle<> handle() noexcept
28+
{
29+
static const auto dummyHandle = coroutine().m_handle;
30+
return dummyHandle;
31+
}
32+
33+
private:
34+
35+
36+
explicit dummy_coroutine(std::experimental::coroutine_handle<> handle) noexcept
37+
: m_handle(handle)
38+
{}
39+
40+
static dummy_coroutine coroutine()
41+
{
42+
for (;;)
43+
{
44+
co_await std::experimental::suspend_always{};
45+
}
46+
}
47+
48+
const std::experimental::coroutine_handle<> m_handle;
49+
50+
};
51+
52+
inline dummy_coroutine dummy_coroutine::promise_type::get_return_object() noexcept
53+
{
54+
return dummy_coroutine{
55+
std::experimental::coroutine_handle<promise_type>::from_promise(*this)
56+
};
57+
}
58+
}
59+
}
60+
61+
#endif

include/cppcoro/detail/when_all_awaitable.hpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define CPPCORO_DETAIL_WHEN_ALL_AWAITABLE_HPP_INCLUDED
77

88
#include <cppcoro/detail/continuation.hpp>
9+
#include <cppcoro/detail/dummy_coroutine.hpp>
910

1011
#include <atomic>
1112

@@ -17,7 +18,7 @@ namespace cppcoro
1718
{
1819
public:
1920

20-
when_all_awaitable(std::size_t count) noexcept
21+
explicit when_all_awaitable(std::size_t count) noexcept
2122
: m_refCount(count + 1)
2223
{}
2324

@@ -31,22 +32,33 @@ namespace cppcoro
3132
return m_refCount.load(std::memory_order_acquire) == 1;
3233
}
3334

34-
bool await_suspend(std::experimental::coroutine_handle<> awaiter) noexcept
35+
auto await_suspend(std::experimental::coroutine_handle<> awaiter) noexcept
3536
{
3637
m_awaiter = awaiter;
37-
return m_refCount.fetch_sub(1, std::memory_order_acq_rel) > 1;
38+
if (m_refCount.fetch_sub(1, std::memory_order_acq_rel) == 1)
39+
{
40+
return awaiter;
41+
}
42+
else
43+
{
44+
return dummy_coroutine::handle();
45+
}
3846
}
3947

4048
void await_resume() noexcept {}
4149

4250
private:
4351

44-
static void resumer_callback(void* state) noexcept
52+
static std::experimental::coroutine_handle<> resumer_callback(void* state) noexcept
4553
{
4654
auto* that = static_cast<when_all_awaitable*>(state);
4755
if (that->m_refCount.fetch_sub(1, std::memory_order_acq_rel) == 1)
4856
{
49-
that->m_awaiter.resume();
57+
return that->m_awaiter;
58+
}
59+
else
60+
{
61+
return dummy_coroutine::handle();
5062
}
5163
}
5264

include/cppcoro/lazy_task.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ namespace cppcoro
5151
std::experimental::coroutine_handle<>
5252
await_suspend([[maybe_unused]] std::experimental::coroutine_handle<> coroutine)
5353
{
54-
return m_continuation.resume_or_get_coroutine_handle();
54+
return m_continuation.tail_call_resume();
5555
}
5656

5757
void await_resume() noexcept {}

lib/build.cake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ includes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', [
4242

4343
detailIncludes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'detail', [
4444
'continuation.hpp',
45+
'dummy_coroutine.hpp',
4546
'when_all_awaitable.hpp',
4647
'unwrap_reference.hpp',
4748
])

0 commit comments

Comments
 (0)