kokkos-execution 0.0.1
Loading...
Searching...
No Matches
enter_scopes.hpp
Go to the documentation of this file.
1// NOLINTBEGIN
2/*
3 * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4 * Copyright (c) 2025 Robert Leahy. All rights reserved.
5 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 *
7 * Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * https://llvm.org/LICENSE.txt
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20#pragma once
21
22#include <atomic>
23#include <cstddef>
24#include <tuple>
25#include <type_traits>
26#include <utility>
27#include <variant>
28
29#include "elide.hpp"
31#include "stdexec/execution.hpp"
33
34#include <iostream>
35
37
39
40struct t {
41 template <::exec::enter_scope_sender... Senders>
42 constexpr ::exec::enter_scope_sender auto operator()(Senders&&... senders) const
43 noexcept((std::is_nothrow_constructible_v<std::remove_cvref_t<Senders>, Senders> && ...)) {
44 return ::STDEXEC::__sexpr{t(), ::STDEXEC::__(), (Senders&&) senders...};
45 }
46
47 template <::exec::enter_scope_sender Sender>
48 constexpr ::exec::enter_scope_sender auto operator()(Sender&& sender) const
49 noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Sender>, Sender>) {
50 return (Sender&&) sender;
51 }
52
53 constexpr ::exec::enter_scope_sender auto operator()() const noexcept {
54 return ::STDEXEC::just(::STDEXEC::just());
55 }
56};
57
58template <::exec::enter_scope_sender Sender, typename Receiver, typename RollbackReceiver>
60 using env_type_ = ::STDEXEC::env_of_t<Receiver>;
61 using exit_scope_sender_type_ = ::exec::exit_scope_sender_of_t<Sender, env_type_>;
62
63 struct receiver_type_ : Receiver {
64 // Note the parameter is by value which means that we can't possibly have a
65 // reference back into the operation state, this is exception safe because
66 // exit senders must be nothrow decay-copyable
67 constexpr void set_value(exit_scope_sender_type_ sender) && noexcept {
68 auto base = (Receiver&&) *this;
69 auto&& self = self_;
70 // This destroys *this
71 self.storage_.template emplace<exit_scope_sender_type_>(std::move(sender));
72 ::STDEXEC::set_value(std::move(base));
73 }
74
76 };
77
78 using operation_state_type_ = ::STDEXEC::connect_result_t<Sender, receiver_type_>;
79 using rollback_operation_state_type_ = ::STDEXEC::connect_result_t<exit_scope_sender_type_, RollbackReceiver>;
80 std::variant<operation_state_type_, exit_scope_sender_type_, rollback_operation_state_type_> storage_;
81 static constexpr bool nothrow_constructible_ = ::STDEXEC::__nothrow_connectable<Sender, Receiver>;
82 public:
83 constexpr explicit enter_scope_sender_state(Sender&& sender, Receiver r) noexcept(nothrow_constructible_)
84 : storage_(std::in_place_type<operation_state_type_>, ::exec::elide([&]() noexcept(nothrow_constructible_) {
85 return ::STDEXEC::connect((Sender&&) sender, receiver_type_{{(Receiver&&) r}, *this});
86 })) {
87 }
88
89 constexpr void start() & noexcept {
90 const auto ptr = std::get_if<operation_state_type_>(&storage_);
91 STDEXEC_ASSERT(ptr);
92 ::STDEXEC::start(*ptr);
93 }
94
95 [[nodiscard]]
96 constexpr bool connect_rollback(RollbackReceiver r) & noexcept {
97 const auto ptr = std::get_if<exit_scope_sender_type_>(&storage_);
98 if (!ptr) {
99 return false;
100 }
101 auto sender = std::move(*ptr);
102 storage_.template emplace<rollback_operation_state_type_>(
103 ::exec::elide([&]() noexcept { return ::STDEXEC::connect(std::move(sender), std::move(r)); }));
104 return true;
105 }
106
107 constexpr void start_rollback() & noexcept {
108 if (const auto ptr = std::get_if<rollback_operation_state_type_>(&storage_); ptr) {
109 ::STDEXEC::start(*ptr);
110 }
111 }
112
113 constexpr exit_scope_sender_type_&& complete() && noexcept {
114 const auto ptr = std::get_if<exit_scope_sender_type_>(&storage_);
115 STDEXEC_ASSERT(ptr);
116 return std::move(*ptr);
117 }
118};
119
120template <typename...>
121using remove_set_value_t = ::STDEXEC::completion_signatures<>;
122
123template <typename... Senders>
124using exit_sender_t = decltype(::STDEXEC::when_all(std::declval<Senders>()...));
125
126template <typename... Env>
128 template <typename... Senders>
129 using __f = ::exec::storage_for_completion_signatures<::STDEXEC::transform_completion_signatures<
130 ::STDEXEC::__concat_completion_signatures_t<::STDEXEC::completion_signatures_of_t<Senders, Env...>...>,
131 ::STDEXEC::completion_signatures<>,
133 >>;
134};
135
136template <typename Receiver, typename... Senders>
137class state {
138 using env_type_ = ::STDEXEC::env_of_t<Receiver>;
139
141 using receiver_concept = ::STDEXEC::receiver_t;
142 constexpr env_type_ get_env() const noexcept;
144 };
145
148 constexpr void set_value() && noexcept;
149 template <typename... Args>
150 constexpr void set_error(Args&&...) && noexcept;
151 template <typename... Args>
152 constexpr void set_stopped(Args&&...) && noexcept;
153 };
154
157 constexpr void set_value() && noexcept;
158 };
159
160 template <typename S>
162 Receiver r_;
163 std::tuple<state_type_<Senders>...> states_;
164 // TODO: There's no need for these two members if no child can fail or stop
166 std::atomic<bool> stored_{false};
167 std::atomic<std::size_t> outstanding_{sizeof...(Senders)};
168
169 constexpr bool is_complete_() noexcept {
170 return outstanding_.fetch_sub(1, std::memory_order_acq_rel) == 1;
171 }
172
173 constexpr void complete_() noexcept {
174 if (!is_complete_()) {
175 return;
176 }
177 std::apply(
178 [&](auto&... states) noexcept {
179 if (stored_.load(std::memory_order_relaxed)) {
180 // Starting with one prevents the child operations from finalizing
181 // the operation out from under us, thereby preventing UB (it does
182 // require that we check to see if we have to finalize as we
183 // complete, see below)
184 outstanding_.store(1, std::memory_order_relaxed);
185 const auto impl = [&](auto& state) noexcept {
186 if (state.connect_rollback(rollback_receiver_type_{{*this}})) {
187 outstanding_.fetch_add(1, std::memory_order_relaxed);
188 }
189 };
190 (impl(states), ...);
191 (states.start_rollback(), ...);
193 } else {
194 ::STDEXEC::set_value(std::move(r_), ::STDEXEC::when_all(std::move(states).complete()...));
195 }
196 },
197 states_);
198 }
199
200 template <typename... Args>
201 constexpr void fail_(Args&&... args) noexcept {
202 if (!stored_.exchange(true, std::memory_order_relaxed)) {
203 storage_.arrive((Args&&) args...);
204 }
205 complete_();
206 }
207
208 constexpr void rollback_complete_() noexcept {
209 if (is_complete_()) {
210 std::move(storage_).complete(std::move(r_));
211 }
212 }
213 public:
214 explicit constexpr state(Receiver r, Senders&&... senders)
215 noexcept((std::is_nothrow_constructible_v<state_type_<Senders>, Senders, receiver_type_> && ...))
216 : r_((Receiver&&) r)
217 , states_(
218 ::exec::elide(
219 [&]() noexcept(std::is_nothrow_constructible_v<state_type_<Senders>, Senders, receiver_type_>) {
220 return state_type_<Senders>((Senders&&) senders, receiver_type_{{*this}});
221 })...) {
222 }
223
224 constexpr void start() & noexcept {
225 std::apply([](auto&&... states) noexcept { (states.start(), ...); }, states_);
226 }
227};
228
229template <typename Receiver, typename... Senders>
231 return ::STDEXEC::get_env(self_.r_);
232}
233
234template <typename Receiver, typename... Senders>
236 self_.complete_();
237}
238
239template <typename Receiver, typename... Senders>
240template <typename... Args>
241constexpr void state<Receiver, Senders...>::receiver_type_::set_error(Args&&... args) && noexcept {
242 self_.fail_(::STDEXEC::set_error, (Args&&) args...);
243}
244
245template <typename Receiver, typename... Senders>
246template <typename... Args>
247constexpr void state<Receiver, Senders...>::receiver_type_::set_stopped(Args&&... args) && noexcept {
248 self_.fail_(::STDEXEC::set_stopped, (Args&&) args...);
249}
250
251template <typename Receiver, typename... Senders>
253 self_.rollback_complete_();
254}
255
256template <typename... Env>
258
259template <typename Env>
260struct completions<Env> {
261 template <typename... Children>
262 using __f = ::STDEXEC::transform_completion_signatures<
263 ::STDEXEC::completion_signatures<::STDEXEC::set_value_t(
264 exit_sender_t<::exec::exit_scope_sender_of_t<Children, Env>...>)>,
265 typename storage_for_completion_signatures<Env>::template __f<Children...>::completion_signatures
266 >;
267};
268
269// TODO: We should be able to get a better "message" than this
271
272template <typename Receiver>
274 template <typename... Children>
275 using __f = state<Receiver, Children...>;
276};
277
278template <typename Receiver>
280 template <typename... Children>
281 using __f = std::is_nothrow_constructible<state<Receiver, Children...>, Receiver, Children...>;
282};
283
284class impl : public ::STDEXEC::__sexpr_defaults {
285 template <typename Sender, typename... Env>
286 using completions_ = ::STDEXEC::__children_of<Sender, completions<Env...>>;
287 public:
288 template <typename Sender, typename... Env>
289 static consteval auto __get_completion_signatures() {
290 if constexpr (::STDEXEC::__minvocable_q<completions_, Sender, Env...>) {
291 return completions_<Sender, Env...>{};
292 } else if constexpr (sizeof...(Env) == 0) {
293 return STDEXEC::__throw_dependent_sender_error<Sender>();
294 } else {
295 return ::STDEXEC::__throw_compile_time_error<
297 ::STDEXEC::_WITH_SENDER_<Sender>
298 >();
299 }
300 }
301
302 static constexpr auto __connect = []<typename Sender, typename Receiver>(Sender&& sender, Receiver r) noexcept(
303 ::STDEXEC::__children_of<Sender, nothrow_connect<Receiver>>::value)
304 -> ::STDEXEC::__children_of<Sender, connect_result<Receiver>> {
305 return ::STDEXEC::__apply(
306 [&](auto&&, auto&&, auto&&... children) noexcept(
307 ::STDEXEC::__children_of<Sender, nothrow_connect<Receiver>>::value) {
308 return ::STDEXEC::__children_of<Sender, connect_result<Receiver>>(
309 (Receiver&&) r, (decltype(children)&&) children...);
310 },
311 (Sender&&) sender);
312 };
313};
314
315} // namespace detail::enter_scopes
316
319
320} // namespace experimental::execution
321
322namespace STDEXEC {
323
324template <>
325struct __sexpr_impl<::exec::enter_scopes_t> : ::exec::detail::enter_scopes::impl { };
326
327} // namespace STDEXEC
328
329// NOLINTEND
std::variant< operation_state_type_, exit_scope_sender_type_, rollback_operation_state_type_ > storage_
constexpr exit_scope_sender_type_ && complete() &&noexcept
::stdexec::connect_result_t< exit_scope_sender_type_, RollbackReceiver > rollback_operation_state_type_
constexpr bool connect_rollback(RollbackReceiver r) &noexcept
constexpr enter_scope_sender_state(Sender &&sender, Receiver r) noexcept(nothrow_constructible_)
::stdexec::connect_result_t< Sender, receiver_type_ > operation_state_type_
::exec::exit_scope_sender_of_t< Sender, env_type_ > exit_scope_sender_type_
::stdexec::__children_of< Sender, completions< Env... > > completions_
constexpr state(Receiver r, Senders &&... senders) noexcept((std::is_nothrow_constructible_v< state_type_< Senders >, Senders, receiver_type_ > &&...))
storage_for_completion_signatures<::stdexec::env_of_t< Receiver > >::template __f< Senders... > storage_
constexpr void fail_(Args &&... args) noexcept
enter_scope_sender_state< S, receiver_type_, rollback_receiver_type_ > state_type_
::stdexec::completion_signatures<> remove_set_value_t
decltype(::stdexec::when_all(std::declval< Senders >()...)) exit_sender_t
constexpr enter_scopes_t enter_scopes
detail::enter_scopes::t enter_scopes_t
::stdexec::transform_completion_signatures< ::stdexec::completion_signatures<::stdexec::set_value_t( exit_sender_t<::exec::exit_scope_sender_of_t< Children, Env >... >)>, typename storage_for_completion_signatures< Env >::template __f< Children... >::completion_signatures > __f
constexpr void set_value(exit_scope_sender_type_ sender) &&noexcept
std::is_nothrow_constructible< state< Receiver, Children... >, Receiver, Children... > __f
::exec::storage_for_completion_signatures<::stdexec::transform_completion_signatures< ::stdexec::__concat_completion_signatures_t<::stdexec::completion_signatures_of_t< Senders, Env... >... >, ::stdexec::completion_signatures<>, remove_set_value_t > > __f
constexpr ::exec::enter_scope_sender auto operator()(Senders &&... senders) const noexcept((std::is_nothrow_constructible_v< std::remove_cvref_t< Senders >, Senders > &&...))
constexpr ::exec::enter_scope_sender auto operator()(Sender &&sender) const noexcept(std::is_nothrow_constructible_v< std::remove_cvref_t< Sender >, Sender >)
constexpr ::exec::enter_scope_sender auto operator()() const noexcept