Skip to content

Commit 0758df6

Browse files
authored
make the get_completion_behavior query useful for optimizing async loops (#1902)
* make the `get_completion_behavior` query useful for optimizing async loops
1 parent df2ce08 commit 0758df6

14 files changed

Lines changed: 202 additions & 91 deletions

include/exec/completion_behavior.hpp

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,39 @@ namespace experimental::execution
2424
{
2525
struct completion_behavior
2626
{
27-
using unknown_t = STDEXEC::__completion_behavior::__unknown_t;
28-
using asynchronous_t = STDEXEC::__completion_behavior::__asynchronous_t;
29-
using asynchronous_affine_t = STDEXEC::__completion_behavior::__asynchronous_affine_t;
30-
using inline_completion_t = STDEXEC::__completion_behavior::__inline_completion_t;
31-
using weakest_t = STDEXEC::__completion_behavior::__weakest_t;
32-
33-
static constexpr auto const &unknown = STDEXEC::__completion_behavior::__unknown;
34-
static constexpr auto const &asynchronous = STDEXEC::__completion_behavior::__asynchronous;
35-
static constexpr auto const &asynchronous_affine =
36-
STDEXEC::__completion_behavior::__asynchronous_affine;
37-
static constexpr auto const &inline_completion =
38-
STDEXEC::__completion_behavior::__inline_completion;
39-
static constexpr auto const &weakest = STDEXEC::__completion_behavior::__weakest;
27+
private:
28+
using __cb_t = STDEXEC::__completion_behavior;
29+
30+
public:
31+
using unknown_t = __cb_t::__unknown_t;
32+
using asynchronous_t = __cb_t::__asynchronous_t;
33+
using asynchronous_affine_t = __cb_t::__asynchronous_affine_t;
34+
using inline_completion_t = __cb_t::__inline_completion_t;
35+
using common_t = __cb_t::__common_t;
36+
37+
static constexpr auto const &unknown = __cb_t::__unknown;
38+
static constexpr auto const &asynchronous = __cb_t::__asynchronous;
39+
static constexpr auto const &asynchronous_affine = __cb_t::__asynchronous_affine;
40+
static constexpr auto const &inline_completion = __cb_t::__inline_completion;
41+
static constexpr auto const &common = __cb_t::__common;
42+
43+
template <class _CB>
44+
static constexpr bool is_affine(_CB cb) noexcept
45+
{
46+
return __cb_t::__is_affine(cb);
47+
}
48+
49+
template <class _CB>
50+
static constexpr bool is_always_asynchronous(_CB cb) noexcept
51+
{
52+
return __cb_t::__is_always_asynchronous(cb);
53+
}
54+
55+
template <class _CB>
56+
static constexpr bool may_be_asynchronous(_CB cb) noexcept
57+
{
58+
return __cb_t::__may_be_asynchronous(cb);
59+
}
4060
};
4161

4262
template <STDEXEC::__completion_tag _Tag>
@@ -46,7 +66,7 @@ namespace experimental::execution
4666
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
4767
constexpr auto get_completion_behavior() noexcept
4868
{
49-
return STDEXEC::__get_completion_behavior<_Tag, _Sndr, _Env...>();
69+
return STDEXEC::__completion_behavior_of_v<_Tag, STDEXEC::env_of_t<_Sndr>, _Env...>();
5070
}
5171
} // namespace experimental::execution
5272

include/exec/task.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ namespace experimental::execution
131131
{
132132
if constexpr (__with_scheduler)
133133
{
134-
return completion_behavior::asynchronous_affine;
134+
return completion_behavior::asynchronous_affine | completion_behavior::inline_completion;
135135
}
136136
else
137137
{

include/stdexec/__detail/__affine_on.hpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ namespace STDEXEC
3535
// that tag, or if its completion behavior for that tag is already "inline" or
3636
// "__asynchronous_affine".
3737
template <class _Tag, class _Sender, class _Env>
38-
concept __already_affine = (!__sends<_Tag, _Sender, _Env>)
39-
|| (__get_completion_behavior<_Tag, _Sender, _Env>()
40-
>= __completion_behavior::__asynchronous_affine);
38+
concept __already_affine = __never_sends<_Tag, _Sender, _Env>
39+
|| __completion_behavior::__is_affine(
40+
__get_completion_behavior<_Tag, _Sender, _Env>());
4141

4242
// For the purpose of the affine_on algorithm, a sender that is "already affine" for
4343
// all three of the standard completion tags does not need to be adapted to become
@@ -123,12 +123,11 @@ namespace STDEXEC
123123
requires __queryable_with<_Attrs, __get_completion_behavior_t<_Tag>, _Env const &...>
124124
constexpr auto query(__get_completion_behavior_t<_Tag>, _Env const &...) const noexcept
125125
{
126-
using __behavior_t =
127-
__query_result_t<_Attrs, __get_completion_behavior_t<_Tag>, _Env const &...>;
126+
constexpr auto __behavior = __completion_behavior_of_v<_Tag, _Attrs, _Env...>;
128127

129128
// When the child sender completes inline, we can return "inline" here instead of
130129
// "__asynchronous_affine".
131-
if constexpr (__behavior_t::value == __completion_behavior::__inline_completion)
130+
if constexpr (__behavior == __completion_behavior::__inline_completion)
132131
{
133132
return __completion_behavior::__inline_completion;
134133
}

include/stdexec/__detail/__completion_behavior.hpp

Lines changed: 80 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#include "__query.hpp"
2424
#include "__utility.hpp"
2525

26-
#include <compare>
26+
#include <cstdint>
2727
#include <type_traits>
2828

2929
namespace STDEXEC
@@ -32,24 +32,35 @@ namespace STDEXEC
3232
// __get_completion_behavior
3333
struct __completion_behavior
3434
{
35-
enum class __behavior : int
35+
//private:
36+
template <__completion_tag _Tag>
37+
friend struct __get_completion_behavior_t;
38+
39+
enum __flag : std::uint8_t
3640
{
37-
__unknown, ///< The completion behavior is unknown.
38-
__asynchronous, ///< The operation's completion will not always happen on
39-
///< the calling thread before `start()` returns.
40-
__asynchronous_affine, ///< Like asynchronous, but completes where it starts.
41-
__inline_completion ///< The operation completes synchronously within `start()`
42-
///< on the same thread that called `start()`.
41+
__not_affine_ = 1,
42+
__async_ = 2,
43+
__inline_ = 4
4344
};
4445

45-
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
46-
friend constexpr auto
47-
operator<=>(__behavior __a, __behavior __b) noexcept -> std::strong_ordering
46+
enum class __behavior : std::uint8_t
4847
{
49-
return static_cast<int>(__a) <=> static_cast<int>(__b);
50-
}
48+
// The operation will complete asynchronously, and may complete on a different
49+
// context than the one that started it.
50+
__asynchronous = __async_ | __not_affine_,
51+
52+
// The operation will complete asynchronously, but will complete on the same
53+
// context that started it.
54+
__asynchronous_affine = __async_,
55+
56+
// The operation will complete synchronously (before 'start()' returns) on the same
57+
// thread that started it.
58+
__inline_completion = __inline_,
59+
60+
// The operation's completion behavior is unknown.
61+
__unknown = __not_affine_ | __async_ | __inline_
62+
};
5163

52-
private:
5364
template <__behavior _CB>
5465
using __constant_t = std::integral_constant<__behavior, _CB>;
5566

@@ -68,36 +79,57 @@ namespace STDEXEC
6879
static constexpr __asynchronous_affine_t __asynchronous_affine{};
6980
static constexpr __inline_completion_t __inline_completion{};
7081

71-
struct __weakest_t
82+
// __asynchronous | __asynchronous_affine => __async_ (aka __asynchronous)
83+
// __asynchronous | __inline_completion => __async_ | __inline_ | __not_affine (aka __unknown)
84+
// __asynchronous_affine | __inline_completion => __async_ | __inline_
85+
template <__behavior _Left, __behavior _Right>
86+
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
87+
friend constexpr auto operator|(__constant_t<_Left>, __constant_t<_Right>) noexcept
88+
{
89+
return __constant_t<static_cast<__behavior>(static_cast<std::uint8_t>(_Left)
90+
| static_cast<std::uint8_t>(_Right))>();
91+
}
92+
93+
template <__behavior _Left, __behavior _Right>
94+
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
95+
friend constexpr bool operator==(__constant_t<_Left>, __constant_t<_Right>) noexcept
96+
{
97+
return _Left == _Right;
98+
}
99+
100+
template <__behavior _CB>
101+
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
102+
static constexpr bool __is_affine(__constant_t<_CB>) noexcept
103+
{
104+
return !(static_cast<std::uint8_t>(_CB) & __not_affine_);
105+
}
106+
107+
template <__behavior _CB>
108+
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
109+
static constexpr bool __is_always_asynchronous(__constant_t<_CB>) noexcept
110+
{
111+
return !(static_cast<std::uint8_t>(_CB) & __inline_);
112+
}
113+
114+
template <__behavior _CB>
115+
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
116+
static constexpr bool __may_be_asynchronous(__constant_t<_CB>) noexcept
72117
{
73-
template <__behavior... _CBs>
74-
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
75-
constexpr auto operator()(__completion_behavior::__constant_t<_CBs>...) const noexcept
118+
return bool(static_cast<std::uint8_t>(_CB) & __async_);
119+
}
120+
121+
struct __common_t
122+
{
123+
template <__behavior... _CSs>
124+
requires(sizeof...(_CSs) > 0)
125+
STDEXEC_ATTRIBUTE(nodiscard, host, device)
126+
constexpr auto operator()(__constant_t<_CSs>... __cbs) const noexcept
76127
{
77-
constexpr auto __behavior = static_cast<__completion_behavior::__behavior>(
78-
STDEXEC::__umin({static_cast<std::size_t>(_CBs)...}));
79-
80-
if constexpr (__behavior == __completion_behavior::__unknown)
81-
{
82-
return __completion_behavior::__unknown;
83-
}
84-
else if constexpr (__behavior == __completion_behavior::__asynchronous)
85-
{
86-
return __completion_behavior::__asynchronous;
87-
}
88-
else if constexpr (__behavior == __completion_behavior::__asynchronous_affine)
89-
{
90-
return __completion_behavior::__asynchronous_affine;
91-
}
92-
else if constexpr (__behavior == __completion_behavior::__inline_completion)
93-
{
94-
return __completion_behavior::__inline_completion;
95-
}
96-
STDEXEC_UNREACHABLE();
128+
return (__cbs | ...);
97129
}
98130
};
99131

100-
static constexpr __weakest_t __weakest{};
132+
static constexpr __common_t __common{};
101133
};
102134

103135
//////////////////////////////////////////////////////////////////////////////////////////
@@ -116,8 +148,7 @@ namespace STDEXEC
116148
"The __get_completion_behavior query must be noexcept.");
117149
static_assert(__std::convertible_to<__result_t, __completion_behavior::__behavior>,
118150
"The __get_completion_behavior query must return one of the static member "
119-
"variables in "
120-
"execution::__completion_behavior.");
151+
"variables in STDEXEC::__completion_behavior.");
121152
return __result_t{};
122153
}
123154

@@ -152,26 +183,23 @@ namespace STDEXEC
152183
}
153184
};
154185

155-
template <class... _CBs>
156-
using __common_completion_behavior_t = __result_of<__completion_behavior::__weakest, _CBs...>;
186+
template <class _Tag, class _Attrs, class... _Env>
187+
inline constexpr auto __completion_behavior_of_v =
188+
__call_result_t<__get_completion_behavior_t<_Tag>, _Attrs const &, _Env const &...>{};
157189

158190
template <class _Tag, class _Attrs, class... _Env>
159-
concept __completes_inline =
160-
(__call_result_t<__get_completion_behavior_t<_Tag>, _Attrs const &, _Env const &...>{}
161-
== __completion_behavior::__inline_completion);
191+
concept __completes_inline = (__completion_behavior_of_v<_Tag, _Attrs, _Env...>
192+
== __completion_behavior::__inline_completion);
162193

163194
template <class _Tag, class _Attrs, class... _Env>
164-
concept __completes_where_it_starts =
165-
(__call_result_t<__get_completion_behavior_t<_Tag>, _Attrs const &, _Env const &...>{}
166-
>= __completion_behavior::__asynchronous_affine);
195+
concept __completes_where_it_starts = __completion_behavior::__is_affine(
196+
__completion_behavior_of_v<_Tag, _Attrs, _Env...>);
167197

168198
template <class _Tag, class _Sndr, class... _Env>
169199
STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device)
170200
constexpr auto __get_completion_behavior() noexcept
171201
{
172-
using __behavior_t =
173-
__call_result_t<__get_completion_behavior_t<_Tag>, env_of_t<_Sndr>, _Env const &...>;
174-
return __behavior_t{};
202+
return __completion_behavior_of_v<_Tag, env_of_t<_Sndr>, _Env...>;
175203
}
176204

177205
#if !defined(STDEXEC_DOXYGEN_INVOKED)
@@ -183,13 +211,11 @@ namespace STDEXEC
183211
using asynchronous_t = __completion_behavior::__asynchronous_t;
184212
using asynchronous_affine_t = __completion_behavior::__asynchronous_affine_t;
185213
using inline_completion_t = __completion_behavior::__inline_completion_t;
186-
using weakest_t = __completion_behavior::__weakest_t;
187214

188215
static constexpr auto const &unknown = __completion_behavior::__unknown;
189216
static constexpr auto const &asynchronous = __completion_behavior::__asynchronous;
190217
static constexpr auto const &asynchronous_affine = __completion_behavior::__asynchronous_affine;
191218
static constexpr auto const &inline_completion = __completion_behavior::__inline_completion;
192-
static constexpr auto const &weakest = __completion_behavior::__weakest;
193219
};
194220

195221
template <__completion_tag _Tag>

include/stdexec/__detail/__continues_on.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ namespace STDEXEC
291291
STDEXEC::__get_completion_behavior<_Tag, _SchSender, __fwd_env_t<_Env>...>();
292292
constexpr auto cb_sndr =
293293
STDEXEC::__get_completion_behavior<_Tag, _Sender, __fwd_env_t<_Env>...>();
294-
return __completion_behavior::__weakest(cb_sched, cb_sndr);
294+
return cb_sched | cb_sndr;
295295
}
296296

297297
//! @brief Forwards other queries to the underlying sender's environment.

include/stdexec/__detail/__deprecations.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ namespace STDEXEC
2525
inline constexpr __execute_may_block_caller_t const & execute_may_block_caller =
2626
__execute_may_block_caller;
2727

28-
[[deprecated("use STDEXEC::__completion_behavior::__weakest instead")]]
29-
inline constexpr auto const & min = __completion_behavior::__weakest;
30-
3128
using empty_env [[deprecated("STDEXEC::empty_env is now spelled STDEXEC::env<>")]] = env<>;
3229

3330
using dependent_completions [[deprecated("use dependent_sender_error instead of "

include/stdexec/__detail/__let.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ namespace STDEXEC
600600
{
601601
if constexpr (sender_in<_Sndr, __fwd_env_t<_Env>...>)
602602
{
603-
// The completion behavior of let_value(sndr, fn) is the weakest completion
603+
// The completion behavior of let_value(sndr, fn) is the union of the completion
604604
// behavior of sndr and all the senders that fn can potentially produce. (MSVC
605605
// needs the constexpr computation broken up, hence the local variables.)
606606
using __transform_fn =
@@ -611,13 +611,13 @@ namespace STDEXEC
611611

612612
constexpr auto __pred_behavior =
613613
STDEXEC::__get_completion_behavior<__set_tag_t, _Sndr, __fwd_env_t<_Env>...>();
614-
constexpr auto __result_behavior =
615-
__gather_completions_t<__set_tag_t,
616-
__completions_t,
617-
__transform_fn,
618-
__qq<__common_completion_behavior_t>>();
614+
constexpr auto __result_behaviors = __gather_completions_t<
615+
__set_tag_t,
616+
__completions_t,
617+
__transform_fn,
618+
__mbind_front_q<__call_result_t, __completion_behavior::__common_t>>();
619619

620-
return __completion_behavior::__weakest(__pred_behavior, __result_behavior);
620+
return __pred_behavior | __result_behaviors;
621621
}
622622
else
623623
{

include/stdexec/__detail/__run_loop.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,8 @@ namespace STDEXEC
116116
static_assert(noexcept(get_stop_token(__declval<env_of_t<_Rcvr>>()).stop_requested()));
117117
auto& __rcvr = static_cast<__opstate_t*>(__p)->__rcvr_;
118118

119-
// NOLINTNEXTLINE(bugprone-branch-clone)
120119
if constexpr (unstoppable_token<stop_token_of_t<env_of_t<_Rcvr>>>)
121-
{
120+
{ // NOLINT(bugprone-branch-clone)
122121
set_value(static_cast<_Rcvr&&>(__rcvr));
123122
}
124123
else if (get_stop_token(get_env(__rcvr)).stop_requested())

include/stdexec/__detail/__schedule_from.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// include these after __execution_fwd.hpp
2121
#include "__basic_sender.hpp"
2222
#include "__completion_signatures_of.hpp"
23+
#include "__queries.hpp"
2324
#include "__sender_introspection.hpp"
2425

2526
namespace STDEXEC
@@ -40,6 +41,12 @@ namespace STDEXEC
4041
template <>
4142
struct __sexpr_impl<schedule_from_t> : __sexpr_defaults
4243
{
44+
static constexpr auto __get_attrs =
45+
[]<class _Child>(schedule_from_t, __ignore, _Child const & __child) noexcept
46+
{
47+
return __sync_attrs{__child};
48+
};
49+
4350
template <class _Sender, class... _Env>
4451
static consteval auto __get_completion_signatures()
4552
{

include/stdexec/__detail/__task.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ namespace STDEXEC
232232
[[nodiscard]]
233233
constexpr auto query(__get_completion_behavior_t<_Tag>) const noexcept
234234
{
235-
return __completion_behavior::__asynchronous_affine;
235+
return __completion_behavior::__asynchronous_affine
236+
| __completion_behavior::__inline_completion;
236237
}
237238
};
238239

0 commit comments

Comments
 (0)