diff --git a/include/nvexec/stream/common.cuh b/include/nvexec/stream/common.cuh index df60275f9..ec8d90811 100644 --- a/include/nvexec/stream/common.cuh +++ b/include/nvexec/stream/common.cuh @@ -439,7 +439,8 @@ namespace nv::execution return get_stream_provider(env)->own_stream_.value(); } - STDEXEC_ATTRIBUTE(host, device) auto operator()() const noexcept + STDEXEC_ATTRIBUTE(host, device) + auto operator()() const noexcept { return STDEXEC::read_env(*this); } diff --git a/include/stdexec/__detail/__completion_behavior.hpp b/include/stdexec/__detail/__completion_behavior.hpp index 126b0c482..e69b73017 100644 --- a/include/stdexec/__detail/__completion_behavior.hpp +++ b/include/stdexec/__detail/__completion_behavior.hpp @@ -133,11 +133,17 @@ namespace STDEXEC struct __common_t { template <__behavior... _CSs> - requires(sizeof...(_CSs) > 0) STDEXEC_ATTRIBUTE(nodiscard, host, device) constexpr auto operator()(__constant_t<_CSs>... __cbs) const noexcept { - return (__cbs | ...); + if constexpr (sizeof...(_CSs) == 0) + { + return __completion_behavior::__unknown; + } + else + { + return (__cbs | ...); + } } }; diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 8539d45ca..0ec78620b 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -839,6 +839,7 @@ namespace STDEXEC struct __assertion_failure {}; + STDEXEC_ATTRIBUTE(noreturn, host, device) inline void __throw_assertion_failure() { STDEXEC_THROW(__assertion_failure{}); diff --git a/include/stdexec/__detail/__let.hpp b/include/stdexec/__detail/__let.hpp index 9777fccc3..e4139b971 100644 --- a/include/stdexec/__detail/__let.hpp +++ b/include/stdexec/__detail/__let.hpp @@ -193,9 +193,10 @@ namespace STDEXEC using __sender_t = __invoke_result_t<_Fun, __decay_t<_Args>&...>; using __submit_t = submit_result<__sender_t, __rcvr_env<_Receiver, _Env2>>; - constexpr bool __nothrow_store = (__nothrow_decay_copyable<_Args> && ...); - constexpr bool __nothrow_invoke = __nothrow_invocable<_Fun, __decay_t<_Args>&...>; - constexpr bool __nothrow_submit = + STDEXEC_CONSTEXPR_LOCAL bool __nothrow_store = (__nothrow_decay_copyable<_Args> && ...); + STDEXEC_CONSTEXPR_LOCAL bool __nothrow_invoke = + __nothrow_invocable<_Fun, __decay_t<_Args>&...>; + STDEXEC_CONSTEXPR_LOCAL bool __nothrow_submit = __nothrow_constructible_from<__submit_t, __sender_t, __second_rcvr_t>; STDEXEC_TRY @@ -339,172 +340,185 @@ namespace STDEXEC __op_state_variant_t __storage_{__no_init}; }; - // // The set_value completions of: - // // - // // * a let_value sender are: - // // * the value completions of the secondary senders - // // - // // * a let_error sender are: - // // * the value completions of the predecessor sender - // // * the value completions of the secondary senders - // // - // // * a let_stopped sender are: - // // * the value completions of the predecessor sender - // // * the value completions of the secondary sender - // // - // // The set_error completions of: - // // - // // * a let_value sender are: - // // * the error completions of the predecessor sender - // // * the error completions of the secondary senders - // // * the value completions of the predecessor sender if decay copying the arguments can throw - // // - // // * a let_error sender are: - // // * the error completions of the secondary senders - // // * the error completions of the predecessor sender if decay copying the errors can throw - // // - // // * a let_stopped sender are: - // // * the error completions of the predecessor sender - // // * the error completions of the secondary senders - // // - // // The set_stopped completions of: - // // - // // * a let_value sender are: - // // * the stopped completions of the predecessor sender - // // * the stopped completions of the secondary senders - // // - // // * a let_error sender are: - // // * the stopped completions of the predecessor sender - // // * the stopped completions of the secondary senders - // // - // // * a let_stopped sender are: - // // * the stopped completions of the secondary sender - // // - // template - // struct __result_completion_behavior_fn - // { - // template - // [[nodiscard]] - // static constexpr auto __impl() noexcept - // { - // using __sndr_t = - // __minvoke_or_q<__invoke_result_t, __not_a_sender<>, _Fun, __decay_t<_Ts>&...>; - // return STDEXEC::__get_completion_behavior<_SetTag, __sndr_t, _JoinEnv2...>(); - // } - - // template - // using __f = decltype(__impl<_Ts...>()); - // }; - - // template - // struct __domain_transform_fn - // { - // template - // using __f = __completion_domain_of_t<_SetTag, - // __invoke_result_t<_Fun, __decay_t<_As>&...>, - // __result_env_t<_SetTag, _Sender, _Env>...>; - // }; - - // //! @tparam _LetTag The tag type for the let_ operation. - // //! @tparam _SetTag The completion signal of the let_ sender itself that is being - // //! queried. For example, you may be querying a let_value sender for its set_error - // //! completion domain. - // template - // [[nodiscard]] - // consteval auto __get_completion_domain() noexcept - // { - // if constexpr (sender_in<_Sndr, _Env...>) - // { - // using __domain_transform_fn = __let::__domain_transform_fn<_SetTag, _Fun, _Sndr, _Env...>; - // return __minvoke_or_q<__gather_completions_t, - // indeterminate_domain<>, - // __t<_LetTag>, - // __completion_signatures_of_t<_Sndr, _Env...>, - // __domain_transform_fn, - // __qq<__common_domain_t>>(); - // } - // else - // { - // return indeterminate_domain<>{}; - // } - // } - - // template - // using __let_completion_domain_t = __unless_one_of_t< - // __result_of<__let::__get_completion_domain<_LetTag, _SetTag, _Sndr, _Fun, _Env...>>, - // indeterminate_domain<>>; - - // template - // struct __attrs - // { - // using __set_tag_t = STDEXEC::__t<_LetTag>; - - // template - // constexpr auto query(get_completion_scheduler_t<_Tag>) const = delete; - - // template - // [[nodiscard]] - // constexpr auto query(get_completion_domain_t<__set_tag_t>, _Env const &...) const noexcept - // -> __ensure_valid_domain_t< - // __let_completion_domain_t<_LetTag, __set_tag_t, _Sndr, _Fun, _Env...>> - // { - // return {}; - // } - - // template <__one_of _Tag, class... _Env> - // requires(__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fun, _Env>::value && ...) - // [[nodiscard]] - // constexpr auto query(get_completion_domain_t<_Tag>, _Env const &...) const noexcept - // -> __ensure_valid_domain_t< - // __common_domain_t<__completion_domain_of_t<_Tag, _Sndr, __fwd_env_t<_Env>...>, - // __let_completion_domain_t<_LetTag, _Tag, _Sndr, _Fun, _Env...>>> - // { - // return {}; - // } - - // template - // requires(!__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fun, _Env>::value) - // [[nodiscard]] - // constexpr auto query(get_completion_domain_t, _Env const &) const noexcept - // -> __ensure_valid_domain_t< - // __common_domain_t<__completion_domain_of_t<__set_tag_t, _Sndr, __fwd_env_t<_Env>>, - // __completion_domain_of_t>, - // __let_completion_domain_t<_LetTag, set_error_t, _Sndr, _Fun, _Env>>> - // { - // return {}; - // } - - // template - // [[nodiscard]] - // constexpr auto query(__get_completion_behavior_t<__set_tag_t>, _Env const &...) const noexcept - // { - // if constexpr (sender_in<_Sndr, __fwd_env_t<_Env>...>) - // { - // // The completion behavior of let_value(sndr, fn) is the union of the completion - // // behavior of sndr and all the senders that fn can potentially produce. (MSVC - // // needs the constexpr computation broken up, hence the local variables.) - // using __transform_fn = - // __result_completion_behavior_fn<__set_tag_t, - // _Fun, - // __result_env_t<__set_tag_t, _Sndr, _Env>...>; - // using __completions_t = __completion_signatures_of_t<_Sndr, __fwd_env_t<_Env>...>; - - // constexpr auto __pred_behavior = - // STDEXEC::__get_completion_behavior<__set_tag_t, _Sndr, __fwd_env_t<_Env>...>(); - // constexpr auto __result_behaviors = __gather_completions_t< - // __set_tag_t, - // __completions_t, - // __transform_fn, - // __mbind_front_q<__call_result_t, __completion_behavior::__common_t>>(); - - // return __pred_behavior | __result_behaviors; - // } - // else - // { - // return __completion_behavior::__unknown; - // } - // } - // }; + // The set_value completions of: + // + // * a let_value sender are: + // * the value completions of the secondary senders + // + // * a let_error sender are: + // * the value completions of the predecessor sender + // * the value completions of the secondary senders + // + // * a let_stopped sender are: + // * the value completions of the predecessor sender + // * the value completions of the secondary sender + // + // The set_error completions of: + // + // * a let_value sender are: + // * the error completions of the predecessor sender + // * the error completions of the secondary senders + // * the value completions of the predecessor sender if decay copying the arguments can throw + // + // * a let_error sender are: + // * the error completions of the secondary senders + // * the error completions of the predecessor sender if decay copying the errors can throw + // + // * a let_stopped sender are: + // * the error completions of the predecessor sender + // * the error completions of the secondary senders + // + // The set_stopped completions of: + // + // * a let_value sender are: + // * the stopped completions of the predecessor sender + // * the stopped completions of the secondary senders + // + // * a let_error sender are: + // * the stopped completions of the predecessor sender + // * the stopped completions of the secondary senders + // + // * a let_stopped sender are: + // * the stopped completions of the secondary sender + // + template + struct __result_completion_behavior_fn + { + template + [[nodiscard]] + static constexpr auto __impl() noexcept + { + using __sndr_t = + __minvoke_or_q<__invoke_result_t, __not_a_sender<>, _Fun, __decay_t<_Ts>&...>; + return STDEXEC::__get_completion_behavior<_SetTag, __sndr_t, _JoinEnv2...>(); + } + + template + using __f = decltype(__impl<_Ts...>()); + }; + + template + struct __domain_transform_fn + { + template + using __f = __completion_domain_of_t<_SetTag, + __invoke_result_t<_Fun, __decay_t<_As>&...>, + __result_env_t<_SetTag, _Sender, _Env>...>; + }; + + //! @tparam _LetTag The tag type for the let_ operation. + //! @tparam _SetTag The completion signal of the let_ sender itself that is being + //! queried. For example, you may be querying a let_value sender for its set_error + //! completion domain. + template + [[nodiscard]] + consteval auto __get_completion_domain() noexcept + { + if constexpr (sender_in<_Sndr, _Env...>) + { + using __domain_transform_fn = __let::__domain_transform_fn<_SetTag, _Fun, _Sndr, _Env...>; + return __minvoke_or_q<__gather_completions_t, + indeterminate_domain<>, + __t<_LetTag>, + __completion_signatures_of_t<_Sndr, _Env...>, + __domain_transform_fn, + __qq<__common_domain_t>>(); + } + else + { + return indeterminate_domain<>{}; + } + } + + template + using __let_completion_domain_t = __unless_one_of_t< + decltype(__let::__get_completion_domain<_LetTag, _SetTag, _Sndr, _Fun, _Env...>()), + indeterminate_domain<>>; + + template + struct __attrs + { + using __set_tag_t = STDEXEC::__t<_LetTag>; + + template + constexpr auto query(get_completion_scheduler_t<_Tag>) const = delete; + + template + [[nodiscard]] + constexpr auto query(get_completion_domain_t<__set_tag_t>, _Env const &...) const noexcept + -> __ensure_valid_domain_t< + __let_completion_domain_t<_LetTag, __set_tag_t, _Sndr, _Fun, _Env...>> + { + return {}; + } + + template <__one_of _Tag, class... _Env> + requires(__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fun, _Env>::value && ...) + [[nodiscard]] + constexpr auto query(get_completion_domain_t<_Tag>, _Env const &...) const noexcept + -> __ensure_valid_domain_t< + __common_domain_t<__completion_domain_of_t<_Tag, _Sndr, __fwd_env_t<_Env>...>, + __let_completion_domain_t<_LetTag, _Tag, _Sndr, _Fun, _Env...>>> + { + return {}; + } + + template + requires(!__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fun, _Env>::value) + [[nodiscard]] + constexpr auto query(get_completion_domain_t, _Env const &) const noexcept + -> __ensure_valid_domain_t< + __common_domain_t<__completion_domain_of_t<__set_tag_t, _Sndr, __fwd_env_t<_Env>>, + __completion_domain_of_t>, + __let_completion_domain_t<_LetTag, set_error_t, _Sndr, _Fun, _Env>>> + { + return {}; + } + + // This overload is needed so that let_stopped will look for customizations based on + // the set_stopped completion domain of the predecessor sender, not the set_value + // completion domain of the predecessor sender. Likewise for let_error and the + // set_error completion domain of the predecessor sender. + template + requires __callable, env_of_t<_Sndr>, _Env const &...> + [[nodiscard]] + constexpr auto query(get_completion_domain_t<>, _Env const &...) const noexcept + -> __call_result_t, env_of_t<_Sndr>, _Env const &...> + { + return {}; + } + + template + [[nodiscard]] + constexpr auto query(__get_completion_behavior_t<__set_tag_t>, _Env const &...) const noexcept + { + if constexpr (sender_in<_Sndr, __fwd_env_t<_Env>...>) + { + // The completion behavior of let_value(sndr, fn) is the union of the completion + // behavior of sndr and all the senders that fn can potentially produce. (MSVC + // needs the constexpr computation broken up, hence the local variables.) + using __transform_fn = + __result_completion_behavior_fn<__set_tag_t, + _Fun, + __result_env_t<__set_tag_t, _Sndr, _Env>...>; + using __completions_t = __completion_signatures_of_t<_Sndr, __fwd_env_t<_Env>...>; + + STDEXEC_CONSTEXPR_LOCAL auto __pred_behavior = + STDEXEC::__get_completion_behavior<__set_tag_t, _Sndr, __fwd_env_t<_Env>...>(); + STDEXEC_CONSTEXPR_LOCAL auto __result_behaviors = __gather_completions_t< + __set_tag_t, + __completions_t, + __transform_fn, + __mbind_front_q<__call_result_t, __completion_behavior::__common_t>>(); + + return __pred_behavior | __result_behaviors; + } + else + { + return __completion_behavior::__unknown; + } + } + }; //! Implementation of the `let_*_t` types, where `_SetTag` is, e.g., `set_value_t` for `let_value`. template @@ -593,11 +607,10 @@ namespace STDEXEC } public: - static constexpr auto __get_attrs = - [](__ignore, __ignore, _Child const & __child) noexcept -> decltype(auto) + static constexpr auto __get_attrs = // + [](__ignore, _Fun const &, _Child const &) noexcept // { - // TODO(ericniebler): this needs a proper implementation - return __fwd_env(STDEXEC::get_env(__child)); + return __let::__attrs<_LetTag, _Child, _Fun>(); }; template diff --git a/test/nvexec/bulk.cpp b/test/nvexec/bulk.cpp index 35f5992f5..2953932e5 100644 --- a/test/nvexec/bulk.cpp +++ b/test/nvexec/bulk.cpp @@ -1,8 +1,12 @@ -#include #include +#include + +#include + +#include + #include "common.cuh" -#include "nvexec/stream_context.cuh" #include @@ -26,7 +30,7 @@ namespace nvexec::stream_context stream_ctx{}; auto const snd = ex::schedule(stream_ctx.get_scheduler()) | ex::bulk(ex::par, 1, [](int) {}); - REQUIRE(STDEXEC::sync_wait(snd).has_value()); + REQUIRE(ex::sync_wait(snd).has_value()); } TEST_CASE("nvexec bulk executes on GPU", "[cuda][stream][adaptors][bulk]") @@ -46,7 +50,7 @@ namespace flags.set(idx); } }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -71,7 +75,7 @@ namespace } } }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -96,7 +100,7 @@ namespace } } }); - auto const [i, d] = STDEXEC::sync_wait(std::move(snd)).value(); + auto const [i, d] = ex::sync_wait(std::move(snd)).value(); REQUIRE(flags_storage.all_set_once()); REQUIRE(i == 42); @@ -122,7 +126,7 @@ namespace flags.set(idx); } }); - [[maybe_unused]] auto [flags_actual] = STDEXEC::sync_wait(std::move(snd)).value(); + [[maybe_unused]] auto [flags_actual] = ex::sync_wait(std::move(snd)).value(); REQUIRE(flags_storage.all_set_once()); } @@ -152,7 +156,7 @@ namespace flags.set(2); } }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -183,7 +187,7 @@ namespace flags.set(idx); } }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -205,7 +209,7 @@ namespace flags.set(idx); } }); - STDEXEC::sync_wait(std::move(snd)).value(); + ex::sync_wait(std::move(snd)).value(); REQUIRE(flags_storage.all_set_once()); } @@ -220,19 +224,19 @@ namespace int const nelems = 10; cudaMallocManaged(&inout, nelems * sizeof(double)); - auto task = STDEXEC::just(cuda::std::span{inout, nelems}) - | STDEXEC::continues_on(ctx.get_scheduler()) - | STDEXEC::bulk(ex::par, - nelems, - [](std::size_t i, cuda::std::span out) - { out[i] = (double) i; }) - | STDEXEC::let_value([](cuda::std::span out) { return STDEXEC::just(out); }) - | STDEXEC::bulk(ex::par, - nelems, - [](std::size_t i, cuda::std::span out) - { out[i] = 2.0 * out[i]; }); - - STDEXEC::sync_wait(std::move(task)).value(); + auto task = ex::just(cuda::std::span{inout, nelems}) + | ex::continues_on(ctx.get_scheduler()) + | ex::bulk(ex::par, + nelems, + [](std::size_t i, cuda::std::span out) { out[i] = (double) i; }) + | ex::let_value([](cuda::std::span out) { return ex::just(out); }) + | exec::write_attrs( + ex::prop{ex::get_completion_scheduler, ctx.get_scheduler()}) + | ex::bulk(ex::par, + nelems, + [](std::size_t i, cuda::std::span out) { out[i] = 2.0 * out[i]; }); + + ex::sync_wait(std::move(task)).value(); for (int i = 0; i < nelems; ++i) { diff --git a/test/nvexec/let_value.cpp b/test/nvexec/let_value.cpp index a9345f636..85d1042c8 100644 --- a/test/nvexec/let_value.cpp +++ b/test/nvexec/let_value.cpp @@ -1,8 +1,12 @@ -#include #include +#include + +#include + +#include + #include "common.cuh" -#include "nvexec/stream_context.cuh" namespace ex = STDEXEC; @@ -36,7 +40,7 @@ namespace } return ex::just(); }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -61,7 +65,7 @@ namespace } return ex::just(); }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -87,7 +91,7 @@ namespace } return ex::just(); }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -98,7 +102,7 @@ namespace auto snd = ex::schedule(stream_ctx.get_scheduler()) | ex::let_value([=]() { return ex::just(is_on_gpu()); }); - auto const [result] = STDEXEC::sync_wait(std::move(snd)).value(); + auto const [result] = ex::sync_wait(std::move(snd)).value(); REQUIRE(result == 1); } @@ -130,7 +134,7 @@ namespace flags.set(1); } }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -153,7 +157,7 @@ namespace return ex::schedule(sch); }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } @@ -165,7 +169,9 @@ namespace flags_storage_t flags_storage{}; auto flags = flags_storage.get(); - auto snd = ex::schedule(sch) | ex::let_value([] { return nvexec::get_stream(); }) + auto snd = ex::schedule(sch) // + | ex::let_value([] { return nvexec::get_stream(); }) // + | exec::write_attrs(ex::prop{ex::get_completion_scheduler, sch}) // | ex::then( [flags](cudaStream_t stream) { @@ -175,7 +181,7 @@ namespace } return stream; }); - auto [stream] = STDEXEC::sync_wait(std::move(snd)).value(); + auto [stream] = ex::sync_wait(std::move(snd)).value(); static_assert(std::same_as); REQUIRE(flags_storage.all_set_once()); diff --git a/test/stdexec/algos/adaptors/test_let_error.cpp b/test/stdexec/algos/adaptors/test_let_error.cpp index 0375004f3..9c1b93f1c 100644 --- a/test/stdexec/algos/adaptors/test_let_error.cpp +++ b/test/stdexec/algos/adaptors/test_let_error.cpp @@ -15,6 +15,7 @@ */ #include +#define STDEXEC_PARALLEL_SCHEDULER_HEADER_ONLY 1 #include #include @@ -390,11 +391,12 @@ namespace TEST_CASE("let_error can be customized", "[adaptors][let_error]") { - basic_inline_scheduler sched{}; + auto attrs = ex::prop{ex::get_completion_domain, let_error_test_domain{}}; - // The customization will return a different value - auto snd = ex::just(std::string{"hello"}) - | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); - wait_for_value(ex::starts_on(sched, std::move(snd)), std::string{"what error?"}); + // The customization will return a different stopped + auto snd = ex::schedule(ex::get_parallel_scheduler()) // + | exec::write_attrs(attrs) // + | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"stopped"}); }); + wait_for_value(std::move(snd), std::string{"what error?"}); } } // namespace diff --git a/test/stdexec/algos/adaptors/test_let_stopped.cpp b/test/stdexec/algos/adaptors/test_let_stopped.cpp index f2c3ca465..bd87d6b5f 100644 --- a/test/stdexec/algos/adaptors/test_let_stopped.cpp +++ b/test/stdexec/algos/adaptors/test_let_stopped.cpp @@ -15,6 +15,7 @@ */ #include +#define STDEXEC_PARALLEL_SCHEDULER_HEADER_ONLY 1 #include #include @@ -231,10 +232,11 @@ namespace TEST_CASE("let_stopped can be customized", "[adaptors][let_stopped]") { - basic_inline_scheduler sched; + auto attrs = ex::prop{ex::get_completion_domain, let_stopped_test_domain{}}; // The customization will return a different stopped - auto snd = ex::just(std::string{"hello"}) | ex::continues_on(sched) + auto snd = ex::schedule(ex::get_parallel_scheduler()) // + | exec::write_attrs(attrs) // | ex::let_stopped([] { return ex::just(std::string{"stopped"}); }); wait_for_value(std::move(snd), std::string{"Don't stop me now"}); } diff --git a/test/test_common/schedulers.hpp b/test/test_common/schedulers.hpp index ab1ad13f2..d138b2370 100644 --- a/test/test_common/schedulers.hpp +++ b/test/test_common/schedulers.hpp @@ -43,7 +43,7 @@ namespace template struct sched_attrs { - sched_attrs(Scheduler sched, Tags...) + explicit sched_attrs(Scheduler sched, Tags...) : scheduler_(std::move(sched)) {}