From dbbd0561bedcc7afca7ab2b18d84f27125638659 Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Tue, 7 Apr 2026 14:30:15 +0200 Subject: [PATCH 1/3] P3927R2 task_scheduler support for parallel bulk execution Fixes NB US 238-368 (C++26 CD). --- source/exec.tex | 230 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 170 insertions(+), 60 deletions(-) diff --git a/source/exec.tex b/source/exec.tex index 4455a1c991..c0e1b81f47 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -7029,10 +7029,10 @@ \begin{codeblock} namespace std::execution { class @\libglobal{task_scheduler}@ { - class @\exposidnc{ts-sender}@; // \expos + class @\exposidnc{ts-domain}@; // \expos - template<@\libconcept{receiver}@ R> - class @\exposidnc{state}@; // \expos + template<@\libconcept{scheduler}@ Sch> + class @\exposid{backend-for}@; // \expos public: using scheduler_concept = scheduler_t; @@ -7045,7 +7045,7 @@ task_scheduler(const task_scheduler&) = default; task_scheduler& operator=(const task_scheduler&) = default; - @\exposid{ts-sender}@ schedule(); + @\seebelow@ schedule(); friend bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) noexcept; @@ -7055,7 +7055,7 @@ friend bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept; private: - shared_ptr @\exposidnc{sch_}@; // \expos + shared_ptr @\exposidnc{sch_}@; // \expos; see \ref{exec.sysctxrepl.psb} }; } \end{codeblock} @@ -7066,6 +7066,10 @@ Given an object \tcode{s} of type \tcode{task_scheduler}, let \tcode{\exposid{SCHED}(s)} be the object pointed to by the pointer owned by \tcode{s.\exposid{sch_}}. +The expression \tcode{get_forward_progress_guarantee(s)} is equivalent to +\tcode{get_forward_progress_guarantee(SCHED(s))}. +The expression \tcode{get_completion_domain(s)} is equivalent to +\tcode{task_scheduler::ts-domain()}. \indexlibraryctor{task_scheduler} \begin{itemdecl} @@ -7077,7 +7081,7 @@ \pnum \effects Initialize \exposid{sch_} with -\tcode{allocate_shared>(alloc,\brk{} std::forward\brk{}(sch))}. +\tcode{allocate_shared<\exposid{backend-for}>>(alloc, std::forward(sch))}. \pnum \recommended @@ -7086,22 +7090,10 @@ \pnum \remarks -Any allocations performed by construction of \exposid{ts-sender} or -\exposid{state} objects resulting from calls on \tcode{*this} are +Any allocations performed by calls on \tcode{*this} are performed using a copy of \tcode{alloc}. \end{itemdescr} -\indexlibrarymember{scheduler}{task_scheduler}% -\begin{itemdecl} -@\exposid{ts-sender}@ schedule(); -\end{itemdecl} -\begin{itemdescr} -\pnum -\effects -Returns an object of type \exposid{ts-sender} containing a sender -initialized with \tcode{sched\-ule(\brk{}\exposid{SCHED}\brk{}(*this))}. -\end{itemdescr} - \indexlibrarymember{operator==}{task_scheduler}% \begin{itemdecl} bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) noexcept; @@ -7127,77 +7119,195 @@ \end{itemdescr} \pnum +For an lvalue \tcode{r} of a type derived from \tcode{receiver_proxy}, +let \tcode{\exposid{WRAP-RCVR}(r)}x be an object of a type +that models \libconcept{receiver} and +whose completion handlers result in +invoking the corresponding completion handlers of \tcode{r}. \begin{codeblock} namespace std::execution { - class task_scheduler::@\exposidnc{ts-sender}@ { // \expos + template<@\libconcept{scheduler}@ Sch> + class task_scheduler::@\exposid{backend-for}@ + : public system_context_replaceability::parallel_scheduler_backend { // \expos public: - using sender_concept = sender_t; + explicit @\exposid{backend-for}@(Sch sch) : @\exposid{sched_}@(std::move(sch)) {} - template<@\libconcept{receiver}@ Rcvr> - @\exposid{state}@ connect(Rcvr&& rcvr) &&; + void schedule(receiver_proxy& r, span s) noexcept override; + void schedule_bulk_chunked(size_t shape, bulk_item_receiver_proxy& r, + span s) noexcept override; + void schedule_bulk_unchunked(size_t shape, bulk_item_receiver_proxy& r, + span s) noexcept override; + + private: + Sch @\exposid{sched_}@; }; } \end{codeblock} -\exposid{ts-sender} is an exposition-only class that models -\libconcept{sender}\iref{exec.snd} and for which -\tcode{completion_signatures_of_t<\exposid{ts-sender}>} denotes: -\begin{codeblock} -completion_signatures< - set_value_t(), - set_error_t(error_code), - set_error_t(exception_ptr), - set_stopped_t()> -\end{codeblock} \pnum -Let \tcode{\placeholder{sch}} be an object of type \tcode{task_scheduler} -and let \tcode{sndr} be an object of type \exposid{ts-sender} obtained -from \tcode{schedule(\placeholder{sch})}. -Then \tcode{get_completion_scheduler(get_env(sndr)) == \placeholder{sch}} -is \tcode{true}. -The object \tcode{\exposid{SENDER}(sndr)} is the sender object contained by -\tcode{sndr} or an object move constructed from it. +Let \tcode{env} be a pack of subexpressions, and +let \exposid{just-sndr-like} be a sender +whose only value completion signature is \tcode{set_value_t()} and +for which the expression +\tcode{get_completion_scheduler(get_env(\exposid{just-sndr-like)}, env...)} +is expression-equivalent to +\tcode{get_completion_scheduler(\exposid{sched_}, env...)}. -\indexlibrarymember{connect}{task_scheduler::\exposid{ts-sender}}% \begin{itemdecl} -template<@\libconcept{receiver}@ Rcvr> - @\exposid{state}@ connect(Rcvr&& rcvr) &&; +void schedule(receiver_proxy& r, span s) noexcept override; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Constructs an operation state \tcode{os} +with \tcode{connect(schedule(\exposid{sched_}), \exposid{WRAP-RCVR}(r))} and +calls \tcode{start(os)}. +\end{itemdescr} + +\begin{itemdecl} +void schedule_bulk_chunked(size_t shape, bulk_item_receiver_proxy& r, + span s) noexcept override; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Let \tcode{chunk_size} be an integer less than or equal to \tcode{shape}, +let \tcode{num_chunks} be \tcode{(shape + chunk_size - 1) / chunk_size}, and +let \tcode{fn} be a function object such that +for an integer \tcode{i}, +\tcode{fn(i)} calls \tcode{r.execute(i * chunk_size, m)}, +where \tcode{m} is the lesser of \tcode{(i + 1) * chunk_size} and \tcode{shape}. +Constructs an operation state \tcode{os} as if with +\begin{codeblock} +connect(bulk(@\exposid{just-sndr-like}@, par, num_chunks, fn), @\exposid{WRAP-RCVR}@(r)) +\end{codeblock} +and calls \tcode{start(os)}. +\end{itemdescr} + +\begin{itemdecl} +void schedule_bulk_unchunked(size_t shape, bulk_item_receiver_proxy& r, + span s) noexcept override; \end{itemdecl} + \begin{itemdescr} \pnum \effects -Let \tcode{\placeholder{r}} be an object of a type that models \libconcept{receiver} -and whose completion handlers result in invoking the corresponding -completion handlers of \tcode{rcvr} or copy thereof. -Returns an object of type \tcode{\exposid{state}} containing -an operation state object initialized with \tcode{connect(\exposid{SENDER}(*this), -std::move(\placeholder{r}))}. +Let \tcode{fn} be a function object such that +for an integer \tcode{i}, +\tcode{fn(i)} is equivalent to \tcode{r.execute(i, i + 1)}. +Constructs an operation state \tcode{os} as if with +\tcode{connect(bulk(\exposid{just-sndr-like}, par, shape, fn), \exposid{WRAP-RCVR}(r))} +and calls \tcode{start(os)}. \end{itemdescr} +\begin{itemdecl} +@\seebelow@ schedule(); +\end{itemdecl} + +\begin{itemdescr} \pnum +\returns +A prvalue \exposid{ts-sndr} whose type models \libconcept{sender} such that: +\begin{itemize} +\item +\tcode{get_completion_scheduler(get_env(\exposid{ts-sndr}))} +is equal to \tcode{*this.} +\item +\tcode{get_completion_domain(get_env(\exposid{ts-sndr}))} +is expression-equivalent to \tcode{\exposid{ts-domain}()}. +\item +If a receiver \tcode{rcvr} is connected to \exposid{ts-sndr} and +the resulting operation state is started, +calls \tcode{\exposid{sch_}->schedule(r, s)}, where +\begin{itemize} +\item +\tcode{r} is a proxy for \tcode{rcvr} with base +\tcode{system_context_replaceability::eceiver_proxy}\iref{exec.par.scheduler} +and +\item +\tcode{s} is a preallocated backend storage for \tcode{r}. +\end{itemize} +\item +For any type \tcode{E}, +\tcode{completion_signatures_of_t} denotes +\tcode{completion_signatures} if +\tcode{\libconcept{unstoppable_token}>} is \tcode{true}, and +otherwise \tcode{completion_signatures}. +\end{itemize} +\end{itemdescr} + \begin{codeblock} namespace std::execution { - template<@\libconcept{receiver}@ R> - class task_scheduler::@\exposidnc{state}@ { // \expos + class task_scheduler::@\exposid{ts-domain}@ : public default_domain { // \expos public: - using operation_state_concept = operation_state_t; - - void start() & noexcept; + template + static constexpr auto transform_sender(set_value_t, BulkSndr&& bulk_sndr, const Env& env) + noexcept(@\seebelow@); }; } \end{codeblock} -\exposid{state} is an exposition-only class template whose -specializations model \libconcept{operation_state}\iref{exec.opstate}. -\indexlibrarymember{start}{task_scheduler::\exposid{state}}% \begin{itemdecl} -void start() & noexcept; +template + static constexpr auto transform_sender(BulkSndr&& bulk_sndr, const Env& env) + noexcept(is_nothrow_constructible_v, BulkSndr>); \end{itemdecl} + \begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item +\tcode{\libconcept{sender_in}} is \tcode{true}, +\item +\tcode{auto(std::forward(bulk_sndr))} is well-formed, and +either \tcode{\exposconcept{sender-for}} or +\exposconcept{sender-for} is \tcode{true}. +\end{itemize} + \pnum \effects -Equivalent to \tcode{start(st)} where \tcode{st} is the operation -state object contained by \tcode{*this}. +Equivalent to: +\begin{codeblock} +auto& [_, data, child] = bulk_sndr; +auto& [_, shape, fn] = data; +auto sch = @\exposid{call-with-default}@(get_completion_scheduler, + @\exposid{not-a-scheduler}@(), get_env(child), @\exposid{FWD-ENV}@(env)); +return @$e$@; +\end{codeblock} +where $e$ is \tcode{\exposid{not-a-sender}()} +if the type of \tcode{sch} is not \tcode{task_scheduler}; +otherwise, it is a prvalue whose type models \libconcept{sender} such that, +if it is connected to a receiver \tcode{rcvr} and +the resulting operation state is started, +\tcode{child} is connected to an unspecified receiver \tcode{R} and started. +The expression \tcode{get_env(R)} +is expression-equivalent to \tcode{\exposid{FWD-ENV}(get_env(\exposid{rcvr-copy}))}, +where \exposid{rcvr-copy} is an lvalue subexpression +designating an object decay-copied from \tcode{rcvr}. + +If \tcode{child} completes with an error or a stopped completion, +the completion operation is forwarded unchanged to \tcode{rcvr}. +Otherwise, let \tcode{args} be a pack of lvalue subexpressions +designating objects decay-copied from the value result datums. +Then: +\begin{itemize} +\item +If \tcode{bulk_sndr} was the result of the evaluation of +an expression equivalent to \tcode{bulk_chunked(child, policy, shape, fn)} or +a copy of such, +then \tcode{\exposid{sch_}->schedule_bulk_chunked(shape, r, s)} is called +where \tcode{r} is a bulk chunked proxy\iref{exec.par.scheduler} +for \tcode{rcvr} with callable \tcode{fn} and arguments \tcode{args}, and +\tcode{s} is a preallocated backend storage for \tcode{r}. +\item +Otherwise, calls \tcode{\exposid{sch_}->schedule_bulk_unchunked(shape, r, s)} +where \tcode{r} is a bulk unchunked proxy for \tcode{rcvr} +with callable \tcode{fn} and arguments \tcode{args}, and +\tcode{s} is a preallocated backend storage for \tcode{r}. +\end{itemize} \end{itemdescr} \rSec2[exec.task]{\tcode{execution::task}} From 7d812fe3e1177a6099adae1c010008e0a1fb6e32 Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Tue, 7 Apr 2026 15:20:10 +0200 Subject: [PATCH 2/3] fixup: overfull hboxes --- source/exec.tex | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source/exec.tex b/source/exec.tex index c0e1b81f47..57fc134c30 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -7055,7 +7055,8 @@ friend bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept; private: - shared_ptr @\exposidnc{sch_}@; // \expos; see \ref{exec.sysctxrepl.psb} + // see \ref{exec.sysctxrepl.psb} + shared_ptr @\exposidnc{sch_}@; // \expos }; } \end{codeblock} @@ -7081,7 +7082,9 @@ \pnum \effects Initialize \exposid{sch_} with -\tcode{allocate_shared<\exposid{backend-for}>>(alloc, std::forward(sch))}. +\begin{codeblock} +allocate_shared<@\exposid{backend-for}@>>(alloc, std::forward(sch)) +\end{codeblock} \pnum \recommended @@ -7216,7 +7219,7 @@ is equal to \tcode{*this.} \item \tcode{get_completion_domain(get_env(\exposid{ts-sndr}))} -is expression-equivalent to \tcode{\exposid{ts-domain}()}. +is expression-equivalent to \tcode{\exposid{ts-do\-main}()}. \item If a receiver \tcode{rcvr} is connected to \exposid{ts-sndr} and the resulting operation state is started, @@ -7224,7 +7227,7 @@ \begin{itemize} \item \tcode{r} is a proxy for \tcode{rcvr} with base -\tcode{system_context_replaceability::eceiver_proxy}\iref{exec.par.scheduler} +\tcode{system_context_replaceability::receiver_proxy}\iref{exec.par.scheduler} and \item \tcode{s} is a preallocated backend storage for \tcode{r}. @@ -7274,7 +7277,7 @@ auto& [_, data, child] = bulk_sndr; auto& [_, shape, fn] = data; auto sch = @\exposid{call-with-default}@(get_completion_scheduler, - @\exposid{not-a-scheduler}@(), get_env(child), @\exposid{FWD-ENV}@(env)); + @\exposid{not-a-scheduler}@(), get_env(child), @\exposid{FWD-ENV}@(env)); return @$e$@; \end{codeblock} where $e$ is \tcode{\exposid{not-a-sender}()} From e87901a72fa1dcc733a58ee001d77dd29aa3cc0b Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Tue, 7 Apr 2026 16:17:39 +0200 Subject: [PATCH 3/3] [exec.task.scheduler] Fix formatting --- source/exec.tex | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/source/exec.tex b/source/exec.tex index 57fc134c30..92f3494e31 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -7029,7 +7029,7 @@ \begin{codeblock} namespace std::execution { class @\libglobal{task_scheduler}@ { - class @\exposidnc{ts-domain}@; // \expos + class @\exposid{ts-domain}@; // \expos template<@\libconcept{scheduler}@ Sch> class @\exposid{backend-for}@; // \expos @@ -7038,8 +7038,7 @@ using scheduler_concept = scheduler_t; template> - requires (!@\libconcept{same_as}@>) - && @\libconcept{scheduler}@ + requires (!@\libconcept{same_as}@>) && @\libconcept{scheduler}@ explicit task_scheduler(Sch&& sch, Allocator alloc = {}); task_scheduler(const task_scheduler&) = default; @@ -7047,11 +7046,9 @@ @\seebelow@ schedule(); - friend bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) - noexcept; + friend bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) noexcept; template - requires (!@\libconcept{same_as}@) - && @\libconcept{scheduler}@ + requires (!@\libconcept{same_as}@) && @\libconcept{scheduler}@ friend bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept; private: