Skip to content

Commit

Permalink
Merge pull request #1422 from BenFrantzDale/add-MAINTAINERS.md-breadc…
Browse files Browse the repository at this point in the history
…rumbs

Add maintainers.md breadcrumbs, and other similar doxy comments
  • Loading branch information
ericniebler authored Oct 26, 2024
2 parents c4b9053 + 05224d5 commit 35e8941
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 7 deletions.
24 changes: 22 additions & 2 deletions include/exec/static_thread_pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,11 @@ namespace exec {
const nodemask& contraints = nodemask::any()) noexcept;
void enqueue(remote_queue& queue, task_base* task, std::size_t threadIndex) noexcept;

//! Enqueue a contiguous span of tasks across task queues.
//! Note: We use the concrete `TaskT` because we enqueue
//! tasks `task + 0`, `task + 1`, etc. so std::span<task_base>
//! wouldn't be correct.
//! This is O(n_threads) on the calling thread.
template <std::derived_from<task_base> TaskT>
void bulk_enqueue(TaskT* task, std::uint32_t n_threads) noexcept;
void bulk_enqueue(
Expand Down Expand Up @@ -810,12 +815,15 @@ namespace exec {

template <std::derived_from<task_base> TaskT>
void static_thread_pool_::bulk_enqueue(TaskT* task, std::uint32_t n_threads) noexcept {
auto& queue = *get_remote_queue();
auto& queue = *this->get_remote_queue();
for (std::uint32_t i = 0; i < n_threads; ++i) {
std::uint32_t index = i % available_parallelism();
std::uint32_t index = i % this->available_parallelism();
queue.queues_[index].push_front(task + i);
threadStates_[index]->notify();
}
// At this point the calling thread can exit and the pool will take over.
// Ultimately, the last completing thread passes the result forward.
// See `if (is_last_thread)` above.
}

inline void static_thread_pool_::bulk_enqueue(
Expand Down Expand Up @@ -1115,8 +1123,11 @@ namespace exec {
}
};

//! The customized operation state for `stdexec::bulk` operations
template <class CvrefSender, class Receiver, class Shape, class Fun, bool MayThrow>
struct static_thread_pool_::bulk_shared_state {
//! The actual `bulk_task` holds a pointer to the shared state
//! and its `__execute` function reads from that shared state.
struct bulk_task : task_base {
bulk_shared_state* sh_state_;

Expand All @@ -1127,6 +1138,9 @@ namespace exec {
auto total_threads = sh_state.num_agents_required();

auto computation = [&](auto&... args) {
// Each computation does one or more call to the the bulk function.
// In the case that the shape is much larger than the total number of threads,
// then each call to computation will call the function many times.
auto [begin, end] = even_share(sh_state.shape_, tid, total_threads);
for (Shape i = begin; i < end; ++i) {
sh_state.fun_(i, args...);
Expand Down Expand Up @@ -1192,6 +1206,8 @@ namespace exec {
std::exception_ptr exception_;
std::vector<bulk_task> tasks_;

//! The number of agents required is the minimum of `shape_` and the available parallelism.
//! That is, we don't need an agent for each of the shape values.
[[nodiscard]]
auto num_agents_required() const -> std::uint32_t {
return static_cast<std::uint32_t>(
Expand All @@ -1205,6 +1221,8 @@ namespace exec {
data_);
}

//! Construct from a pool, receiver, shape, and function.
//! Allocates O(min(shape, available_parallelism())) memory.
bulk_shared_state(static_thread_pool_& pool, Receiver rcvr, Shape shape, Fun fun)
: pool_{pool}
, rcvr_{static_cast<Receiver&&>(rcvr)}
Expand All @@ -1215,6 +1233,8 @@ namespace exec {
}
};


//! A customized receiver to allow parallel execution of `stdexec::bulk` operations:
template <class CvrefSenderId, class ReceiverId, class Shape, class Fun, bool MayThrow>
struct static_thread_pool_::bulk_receiver<CvrefSenderId, ReceiverId, Shape, Fun, MayThrow>::__t {
using __id = bulk_receiver;
Expand Down
13 changes: 11 additions & 2 deletions include/stdexec/__detail/__basic_sender.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -500,18 +500,24 @@ namespace stdexec {
using __get_attrs_fn =
__result_of<__detail::__drop_front, __mtypeof<__sexpr_impl<_Tag>::get_attrs>>;

//////////////////////////////////////////////////////////////////////////////////////////////////
// __basic_sender
//! A dummy type used only for diagnostic purposes.
//! See `__sexpr` for the implementation of P2300's _`basic-sender`_.
template <class...>
struct __basic_sender {
// See MAINTAINERS.md#class-template-parameters for `__id` and `__t`.
using __id = __basic_sender;
using __t = __basic_sender;
};

//! A struct template to aid in creating senders.
//! This struct closely resembles P2300's [_`basic-sender`_](https://eel.is/c++draft/exec#snd.expos-24),
//! but is not an exact implementation.
//! Note: The struct named `__basic_sender` is just a dummy type and is also not _`basic-sender`_.
template <auto _DescriptorFn, class = __anon>
struct __sexpr {
using sender_concept = sender_t;

// See MAINTAINERS.md#class-template-parameters for `__id` and `__t`.
using __id = __sexpr;
using __t = __sexpr;
using __desc_t = decltype(_DescriptorFn());
Expand Down Expand Up @@ -595,6 +601,9 @@ namespace stdexec {

//////////////////////////////////////////////////////////////////////////////////////////////////
// __make_sexpr
//! A tagged function-object
//! Takes data and children and
//! returns `__sexpr_t<_Tag, _Data, _Child...>{_Tag(), data, children...}`.
namespace __detail {
template <class _Tag>
struct __make_sexpr_t {
Expand Down
6 changes: 6 additions & 0 deletions include/stdexec/__detail/__bulk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ namespace stdexec {
return {};
};

//! This implements the core default behavior for `bulk`:
//! When setting value, it loops over the shape and invokes the function.
//! Note: This is not done in parallel. That is customized by the scheduler.
//! See, e.g., static_thread_pool::bulk_receiver::__t.
static constexpr auto complete = //
[]<class _Tag, class _State, class _Receiver, class... _Args>(
__ignore,
Expand All @@ -130,8 +134,10 @@ namespace stdexec {
_Tag,
_Args&&... __args) noexcept -> void {
if constexpr (std::same_as<_Tag, set_value_t>) {
// Intercept set_value and dispatch to the bulk operation.
using __shape_t = decltype(__state.__shape_);
if constexpr (noexcept(__state.__fun_(__shape_t{}, __args...))) {
// The noexcept version that doesn't need try/catch:
for (__shape_t __i{}; __i != __state.__shape_; ++__i) {
__state.__fun_(__i, __args...);
}
Expand Down
5 changes: 5 additions & 0 deletions include/stdexec/__detail/__domain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ namespace stdexec {
} // namespace __detail

/////////////////////////////////////////////////////////////////////////////
//! Function object implementing `get-domain-early(snd)`
//! from [exec.snd.general] item 3.9. It is the first well-formed expression of
//! a) `get_domain(get_env(sndr))`
//! b) `completion-domain(sndr)`
//! c) `default_domain()`
inline constexpr struct __get_early_domain_t {
template <class _Sender, class _Default = default_domain>
auto operator()(const _Sender&, _Default __def = {}) const noexcept {
Expand Down
17 changes: 17 additions & 0 deletions include/stdexec/__detail/__let.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ namespace stdexec {
};
}

//! Metafunction creating the operation state needed to connect the result of calling
//! the sender factory function, `_Fun`, and passing its result to a receiver.
template <class _Receiver, class _Fun, class _Set, class _Sched>
struct __op_state_for {
template <class... _Args>
Expand All @@ -346,6 +348,8 @@ namespace stdexec {
_Receiver>;
};

//! The core of the operation state for `let_*`.
//! This gets bundled up into a larger operation state (`__detail::__op_state<...>`).
template <class _Receiver, class _Fun, class _Set, class _Sched, class... _Tuples>
struct __let_state {
using __fun_t = _Fun;
Expand Down Expand Up @@ -385,10 +389,14 @@ namespace stdexec {
_Fun __fun_;
STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
_Sched __sched_;
//! Variant to hold the results passed from upstream before passing them to the function:
__result_variant __args_{};
//! Variant type for holding the operation state from connecting
//! the function result to the downstream receiver:
__op_state_variant __op_state3_{};
};

//! Implementation of the `let_*_t` types, where `_Set` is, e.g., `set_value_t` for `let_value`.
template <class _Set, class _Domain>
struct __let_t {
using __domain_t = _Domain;
Expand Down Expand Up @@ -471,11 +479,18 @@ namespace stdexec {
});
};

//! Helper function to actually invoke the function to produce `let_*`'s sender,
//! connect it to the downstream receiver, and start it.
//! This is the heart of `let_*`.
template <class _State, class _OpState, class... _As>
static void __bind_(_State& __state, _OpState& __op_state, _As&&... __as) {
// Store the passed-in (received) args:
auto& __args = __state.__args_.emplace_from(__tup::__mktuple, static_cast<_As&&>(__as)...);
// Apply the function to the args to get the sender:
auto __sndr2 = __args.apply(std::move(__state.__fun_), __args);
// Create a receiver based on the state, the computed sender, and the operation state:
auto __rcvr2 = __state.__get_result_receiver(__sndr2, __op_state);
// Connect the sender to the receiver and start it:
auto& __op2 = __state.__op_state3_.emplace_from(
stdexec::connect, std::move(__sndr2), std::move(__rcvr2));
stdexec::start(__op2);
Expand Down Expand Up @@ -514,8 +529,10 @@ namespace stdexec {
_Tag,
_As&&... __as) noexcept -> void {
if constexpr (__same_as<_Tag, _Set>) {
// Intercept the channel of interest to compute the sender and connect it:
__bind(__op_state, static_cast<_As&&>(__as)...);
} else {
// Forward the other channels downstream:
using _Receiver = decltype(__op_state.__rcvr_);
_Tag()(static_cast<_Receiver&&>(__op_state.__rcvr_), static_cast<_As&&>(__as)...);
}
Expand Down
22 changes: 19 additions & 3 deletions include/stdexec/__detail/__manual_lifetime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@

namespace stdexec {

//! Holds storage for a `_Ty`, but allows clients to `__construct(...)`, `__destry()`,
//! and `__get()` the `_Ty` without regard for usual lifetime rules.
template <class _Ty>
class __manual_lifetime {
public:
//! Constructor does nothing: It's on you to call `__construct(...)` or `__construct_from(...)`
//! if you want the `_Ty`'s lifetime to begin.
constexpr __manual_lifetime() noexcept {
}

//! Destructor does nothing: It's on you to call `__destroy()` if you mean to.
constexpr ~__manual_lifetime() {
}

Expand All @@ -40,6 +44,8 @@ namespace stdexec {
__manual_lifetime(__manual_lifetime&&) = delete;
auto operator=(__manual_lifetime&&) -> __manual_lifetime& = delete;

//! Construct the `_Ty` in place.
//! There are no safeties guarding against the case that there's already one there.
template <class... _Args>
auto __construct(_Args&&... __args) noexcept(
stdexec::__nothrow_constructible_from<_Ty, _Args...>) -> _Ty& {
Expand All @@ -49,30 +55,40 @@ namespace stdexec {
_Ty{static_cast<_Args&&>(__args)...});
}

//! Construct the `_Ty` in place from the result of calling `func`.
//! There are no safeties guarding against the case that there's already one there.
template <class _Func, class... _Args>
auto __construct_from(_Func&& func, _Args&&... __args) -> _Ty& {
// Use placement new instead of std::construct_at in case the function returns an immovable
// type.
return *std::launder(::new (static_cast<void*>(__buffer_))
_Ty{(static_cast<_Func&&>(func))(static_cast<_Args&&>(__args)...)});
}

//! End the lifetime of the contained `_Ty`.
//! Precondition: The lifetime has started.
void __destroy() noexcept {
std::destroy_at(&__get());
}

//! Get access to the `_Ty`.
//! Precondition: The lifetime has started.
auto __get() & noexcept -> _Ty& {
return *reinterpret_cast<_Ty*>(__buffer_);
}

//! Get access to the `_Ty`.
//! Precondition: The lifetime has started.
auto __get() && noexcept -> _Ty&& {
return static_cast<_Ty&&>(*reinterpret_cast<_Ty*>(__buffer_));
}

//! Get access to the `_Ty`.
//! Precondition: The lifetime has started.
auto __get() const & noexcept -> const _Ty& {
return *reinterpret_cast<const _Ty*>(__buffer_);
}

//! Move semantics aren't supported.
//! If you want to move the `_Ty`, use `std::move(ml.__get())`.
auto __get() const && noexcept -> const _Ty&& = delete;

private:
Expand Down
38 changes: 38 additions & 0 deletions include/stdexec/__detail/__meta.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#include "__utility.hpp"

namespace stdexec {
//! Convenience metafunction getting the dependant type `__t` out of `_Tp`.
//! That is, `typename _Tp::__t`.
//! See MAINTAINERS.md#class-template-parameters for details.
template <class _Tp>
using __t = typename _Tp::__t;

Expand Down Expand Up @@ -91,9 +94,11 @@ namespace stdexec {
using __msize_t = __muchar (*)[_Np + 1]; // +1 to avoid zero-size array
#endif

//! Metafunction selects the first of two type arguments.
template <class _Tp, class _Up>
using __mfirst = _Tp;

//! Metafunction selects the second of two type arguments.
template <class _Tp, class _Up>
using __msecond = _Up;

Expand Down Expand Up @@ -324,6 +329,15 @@ namespace stdexec {
template <class... _Args>
concept _Ok = (STDEXEC_IS_SAME(__ok_t<_Args>, __msuccess) && ...);

//! The struct `__i` is the implementation of P2300's
//! [_`META-APPLY`_](https://eel.is/c++draft/exec#util.cmplsig-5).
//! > [Note [1](https://eel.is/c++draft/exec#util.cmplsig-note-1): 
//! > The purpose of META-APPLY is to make it valid to use non-variadic
//! > templates as Variant and Tuple arguments to gather-signatures. — end note]
//! In addition to avoiding the dreaded "pack expanded into non-pack argument" error,
//! it is part of the meta-error propagation mechanism. if any of the argument types
//! are a specialization of `_ERROR_`, `__i` will short-circuit and return the error.
//! `__minvoke` and `__meval` are implemented in terms of `__i`.
template <bool _ArgsOK, bool _FnOK = true>
struct __i;

Expand Down Expand Up @@ -373,6 +387,9 @@ namespace stdexec {
typename __i<_Ok<_Args...>> //
::template __g<_Fn, _Args...>; //

//! Metafunction invocation
//! Given a metafunction, `_Fn`, and args.
//! We expect `_Fn::__f` to be type alias template "implementing" the metafunction `_Fn`.
template <class _Fn, class... _Args>
using __minvoke = //
typename __i<_Ok<_Args...>, _Ok<_Fn>> //
Expand Down Expand Up @@ -413,6 +430,14 @@ namespace stdexec {
using __f = _Fn;
};

//! This struct template is like [mpl::quote](https://www.boost.org/doc/libs/1_86_0/libs/mpl/doc/refmanual/quote.html).
//! It turns an alias/class template into a metafunction that also propagates "meta-exceptions".
//! All of the meta utilities recognize specializations of stdexec::_ERROR_ as an error type.
//! Error types short-circuit the evaluation of the metafunction and are automatically propagated like an exception.
//! Note: `__minvoke` and `__meval` also participate in this error propagation.
//!
//! This design lets us report type errors briefly at the library boundary, even if the
//! actual error happens deep inside a meta-program.
template <template <class...> class _Fn>
struct __q {
template <class... _Args>
Expand Down Expand Up @@ -499,6 +524,9 @@ namespace stdexec {
using __mmemoize_q = __mmemoize<__q<_Fn>, _Args...>;

struct __if_ {
//! Metafunction selects `_True` if the bool template is `true`, otherwise the second.
//! That is, `__<true>::__f<A, B>` is `A` and `__<false>::__f<A, B>` is B.
//! This is similar to `std::conditional_t<Cond, A, B>`.
template <bool>
struct __ {
template <class _True, class...>
Expand All @@ -509,6 +537,7 @@ namespace stdexec {
using __f = __minvoke<__<static_cast<bool>(__v<_Pred>)>, _True, _False...>;
};

// Specialization; see above.
template <>
struct __if_::__<false> {
template <class, class _False>
Expand Down Expand Up @@ -818,9 +847,13 @@ namespace stdexec {
template <class _Default>
using __msingle_or = __mbind_front_q<__msingle_or_, _Default>;

//! A concept checking if `_Ty` has a dependent type `_Ty::__id`.
//! See MAINTAINERS.md#class-template-parameters.
template <class _Ty>
concept __has_id = requires { typename _Ty::__id; };

//! Identity mapping `_Ty` to itself.
//! That is, `std::is_same_v<T, typename _Id<T>::__t>`.
template <class _Ty>
struct _Id {
using __t = _Ty;
Expand All @@ -833,6 +866,7 @@ namespace stdexec {
//static_assert(!__has_id<std::remove_cvref_t<_Ty>>);
};

//! Helper metafunction detail of `__id`, below.
template <bool = true>
struct __id_ {
template <class _Ty>
Expand All @@ -845,6 +879,10 @@ namespace stdexec {
using __f = _Id<_Ty>;
};

//! Metafunction mapping `_Ty` to either
//! * `typename _Ty::__id` if that exists, or to
//! * `_Ty` (itself) otherwise.
//! See MAINTAINERS.md#class-template-parameters.
template <class _Ty>
using __id = __minvoke<__id_<__has_id<_Ty>>, _Ty>;

Expand Down
Loading

0 comments on commit 35e8941

Please sign in to comment.