Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
async_ops.hpp
Go to the documentation of this file.
1
5#pragma once
6
7#include "async.hpp"
8#include "async_task.hpp"
9#include "awaiters.hpp"
10#include "debug_scheduler.hpp"
12#include <concepts>
13#include <functional>
14#include <memory>
15#include <type_traits>
16#include <utility>
17
18namespace uni20::async
19{
20namespace detail
21{
22
23template <class Output, class Input>
24void validate_distinct_async_operand_queues(Output const& output, Input const& input)
25{
26 if constexpr (requires {
27 output.queue();
28 input.queue();
29 })
30 {
31 ERROR_IF(std::addressof(output.queue()) == std::addressof(input.queue()),
32 "async output must not share an epoch queue with an input");
33 }
34}
35
36} // namespace detail
37
40
43template <typename Awaitable>
44concept read_buffer_awaitable = requires(Awaitable& a) {
45 typename std::remove_cvref_t<decltype(get_awaiter(a))>::value_type;
46 { get_awaiter(a).await_resume() };
47};
48
51template <typename Awaitable>
52concept write_buffer_awaitable = requires(Awaitable& a, typename Awaitable::value_type v) {
53 typename std::remove_cvref_t<decltype(get_awaiter(a))>::value_type;
54 { get_awaiter(a).await_resume() = std::move(v) };
55};
56
58template <typename Awaitable>
59concept emplace_buffer_awaitable = requires(Awaitable& a, typename Awaitable::value_type v) {
60 typename Awaitable::value_type;
61 get_awaiter(a).await_resume().emplace(std::move(v));
62};
63
64template <typename Awaitable, typename T>
66 emplace_buffer_awaitable<Awaitable> && std::constructible_from<typename Awaitable::value_type, T>;
67
71template <typename Awaitable, typename T>
73 requires(Awaitable a) { requires std::constructible_from<T, decltype(get_awaiter(a).await_resume())>; };
74
77template <typename Awaitable, typename T>
78concept write_buffer_awaitable_of = requires(Awaitable a) {
79 { get_awaiter(a).await_resume() = std::declval<T>() };
80};
81
84template <typename Awaitable>
86
89template <typename Awaitable, typename T>
92
94template <typename T>
95concept async_reader = requires(T t) { requires read_buffer_awaitable<decltype(t.read())>; };
96
98template <typename AsyncLike> using write_buffer_t = decltype(std::declval<AsyncLike&>().write());
100template <typename AsyncLike> using take_awaiter_t = decltype(std::declval<write_buffer_t<AsyncLike>&>().take());
101
103template <typename AsyncLike>
104concept async_writer = requires(AsyncLike t) { requires emplace_buffer_awaitable<decltype(t.write())>; };
105
106template <typename AsyncLike, typename T>
108 requires { requires read_buffer_awaitable_of<decltype(std::declval<AsyncLike&>().read()), T>; };
109
111template <typename AsyncLike, typename T>
113 requires { requires emplace_buffer_awaitable_of<decltype(std::declval<AsyncLike&>().write()), T>; };
114
115static_assert(async_writer_to<Async<int>, int>);
116static_assert(!async_writer_to<Async<int> const, int>);
117
120template <typename AsyncLike, typename T>
125
126template <typename AsyncLike, typename T>
131
135template <typename T>
136concept async_read_writer = requires(T t) {
137 requires async_reader<T>;
138 requires async_writer<T>;
139 requires emplace_buffer_awaitable<decltype(t.write())>;
140};
141
143template <typename T>
145
147template <typename T, typename U, typename Op>
149 decltype(std::declval<Op>()(std::declval<async_value_t<T>>(), std::declval<async_value_t<U>>()));
150
152template <typename T, typename U, typename Op>
154 requires(Op op, async_value_t<T> t, async_value_t<U> u) {
155 { op(t, u) };
156 };
157
160template <typename A, typename B, typename Writer, typename Op>
161concept async_binary_assignable = async_writer<Writer> && requires(Op op, async_value_t<A> a, async_value_t<B> b,
162 Writer& out) {
163 requires std::constructible_from<typename std::remove_cvref_t<decltype(out.write())>::value_type, decltype(op(a, b))>;
164};
165
166template <typename T, typename U, typename Op>
169 requires(async_value_t<T> lhs, async_value_t<U>&& rhs, Op op) { op(lhs, std::forward<async_value_t<U>>(rhs)); };
170
171// Example using move semantics
172// template <typename A, typename B, typename R, typename Op> void async_binary_op(Async<R>& out, A&& a, B&& b, Op op)
173// {
174// schedule([](auto a_, auto b_, WriteBuffer<R> out_, Op op_) -> AsyncTask {
175// auto [va, vb] = co_await all(std::move(a_), std::move(b_));
176// a_.release();
177// b_.release();
178// R tmp = op_(std::move(va), std::move(vb)); // move values into op
179// auto& outval = co_await out_;
180// outval = std::move(tmp);
181// co_return;
182// }(read(std::forward<A>(a)), read(std::forward<B>(b)), out.write(), std::move(op)));
183// }
184
193template <typename A, typename B, async_writer Writer, typename Op>
195void async_binary_op(Writer& out, A&& a, B&& b, Op op)
196{
197 detail::validate_distinct_async_operand_queues(out, a);
198 detail::validate_distinct_async_operand_queues(out, b);
199
200 auto a_buf = read(std::forward<A>(a));
201 auto b_buf = read(std::forward<B>(b));
202 auto out_buf = out.write();
203
204 schedule([](auto a_, auto b_, auto out_, Op op_) static -> AsyncTask {
205 auto ab = co_await all(a_, b_);
206 auto tmp = op_(std::get<0>(ab), std::get<1>(ab));
207 a_.release();
208 b_.release();
209 co_await out_ = std::move(tmp);
210 co_return;
211 }(std::move(a_buf), std::move(b_buf), std::move(out_buf), std::move(op)));
212}
213
225template <typename T, typename U, typename Op> void async_compound_op(Async<T>& lhs, U&& rhs, Op op)
226{
227 detail::validate_distinct_async_operand_queues(lhs, rhs);
228
229 auto rhs_buf = read(std::forward<U>(rhs));
230 auto lhs_out = lhs.write();
231
232 schedule([](WriteBuffer<T> out_, auto rhs_, Op op_) static -> AsyncTask {
233 auto output_storage = out_.storage();
234 auto awaited = co_await all(output_storage, rhs_);
235 auto& storage = std::get<0>(awaited);
236 auto& rhs_value = std::get<1>(awaited);
237 if (!storage.constructed()) throw buffer_write_uninitialized{};
238 op_(*storage, rhs_value);
239 co_return;
240 }(std::move(lhs_out), std::move(rhs_buf), std::move(op)));
241}
242
243// This version is correct, but does not use all() awaiter
244// template <typename A, typename B, typename R, typename Op> void async_binary_op(Async<R>& out, A&& a, B&& b, Op op)
245// {
246// auto a_buf = read(std::forward<A>(a));
247// auto b_buf = read(std::forward<B>(b));
248// auto out_buf = out.write();
249//
250// schedule([](auto a_, auto b_, WriteBuffer<R> out_, Op op_) -> AsyncTask {
251// auto const& va = co_await a_;
252// auto const& vb = co_await b_;
253// auto tmp = op_(va, vb);
254// a_.release();
255// b_.release();
256// co_await out_ = std::move(tmp); // Suspend *after* releasing readers
257// co_return;
258// }(read(std::forward<A>(a)), read(std::forward<B>(b)), out.write(), std::move(op)));
259// }
260
261namespace detail
262{
263
264template <typename Awaitable, typename Target>
265concept async_initialization_awaitable =
266 read_buffer_awaitable<Awaitable> &&
267 std::constructible_from<Target, decltype(get_awaiter(std::declval<Awaitable&>()).await_resume())>;
268
269template <typename Target, typename Source>
270concept current_value_assignment_source =
271 !is_async_alias_v<Target> && std::constructible_from<Target, async_value_t<Source> const&> &&
272 write_proxy_assignable_source<Target, Target>;
273
274template <typename Awaitable, typename Target>
275concept async_alias_assignment_awaitable =
276 read_buffer_awaitable<Awaitable> &&
277 async_alias_assignable_from<Target, decltype(get_awaiter(std::declval<Awaitable&>()).await_resume())>;
278
279} // namespace detail
280
284template <typename T, typename U>
285 requires(!is_async_alias_v<T> && detail::async_initialization_awaitable<U, T>)
286void async_initialize(WriteBuffer<T> destination, U&& source)
287{
288 schedule([](auto source_, WriteBuffer<T> destination_) static -> AsyncTask {
289 auto&& value = co_await source_;
290 auto target = co_await destination_;
291 target.emplace(value);
292 source_.release();
293 co_return;
294 }(std::forward<U>(source), std::move(destination)));
295}
296
298template <typename T, typename U>
299 requires(!is_async_alias_v<T> && detail::async_source<U> && std::constructible_from<T, async_value_t<U> const&>)
300void async_initialize(Async<T>& destination, U&& source)
301{
302 async_initialize(destination.write(), read(std::forward<U>(source)));
303}
304
308template <typename T, typename U>
309 requires(!is_async_alias_v<T> && read_buffer_awaitable_of<U, T> && detail::write_proxy_assignable_source<T, T>)
310void async_assign(WriteBuffer<T> destination, U&& source)
311{
312 schedule([](auto source_, WriteBuffer<T> destination_) static -> AsyncTask {
313 T value(co_await source_);
314 source_.release();
315 co_await destination_ = std::move(value);
316 co_return;
317 }(std::forward<U>(source), std::move(destination)));
318}
319
323template <typename T, typename U>
324 requires(is_async_alias_v<T> && detail::async_alias_assignment_awaitable<U, T>)
325void async_assign(WriteBuffer<T> destination, U&& source)
326{
327 schedule([](auto source_, WriteBuffer<T> destination_) static -> AsyncTask {
328 auto&& value = co_await source_;
329 co_await destination_ = value;
330 source_.release();
331 co_return;
332 }(std::forward<U>(source), std::move(destination)));
333}
334
340template <typename T, typename U>
341void async_assign(Async<T>& lhs, U&& rhs)
342 requires(detail::current_value_assignment_source<T, U> ||
343 (is_async_alias_v<T> && detail::async_alias_assignable_from<T, async_value_t<U> const&>))
344{
345 async_assign(lhs.write(), read(std::forward<U>(rhs)));
346}
347
353template <typename T, typename U>
354void async_assign(WriteBuffer<T> lhs, U&& rhs)
356 (detail::current_value_assignment_source<T, U> ||
357 (is_async_alias_v<T> && detail::async_alias_assignable_from<T, async_value_t<U> const&>))
358{
359 async_assign(std::move(lhs), read(std::forward<U>(rhs)));
360}
361
362template <typename T, typename U>
363 requires(!is_async_alias_v<T>) && async_mutable_writer_of<U, T> && async_movable_to<U, T>
369void async_move(WriteBuffer<T> lhs, U&& rhs)
370{
371 auto src = std::forward<U>(rhs).write();
372 schedule([](auto src, WriteBuffer<T> dst) static -> AsyncTask {
373 T movable(co_await src.take());
374 src.release();
375 co_await dst = std::move(movable);
376 co_return;
377 }(std::move(src), std::move(lhs)));
378}
379
380template <typename T, typename U>
381 requires(!is_async_alias_v<T>) && (!async_writer<std::remove_cvref_t<U>>) && std::constructible_from<T, U&&>
382void async_move(WriteBuffer<T> lhs, U&& rhs)
383{
384 schedule([](T val, WriteBuffer<T> dst) static -> AsyncTask {
385 co_await dst = std::move(val);
386 co_return;
387 }(std::move(rhs), std::move(lhs)));
388}
389
395template <typename T, typename U>
396 requires(!is_async_alias_v<T>)
397void async_move(Async<T>& lhs, U&& rhs)
398{
399 async_move(lhs.write(), std::forward<U>(rhs));
400}
401
402// +, -, *, /
403#define UNI20_DEFINE_BINARY_OP(OPNAME, OP) \
404 template <typename T, typename U> \
405 requires async_binary_applicable<T, U, OP> \
406 auto operator OPNAME(T&& a, U&& b) \
407 { \
408 using R = async_binary_result_t<T, U, OP>; \
409 Async<R> result; \
410 async_binary_op(result, std::forward<T>(a), std::forward<U>(b), OP{}); \
411 return result; \
412 }
413
414UNI20_DEFINE_BINARY_OP(+, std::plus<>)
415UNI20_DEFINE_BINARY_OP(-, std::minus<>)
416UNI20_DEFINE_BINARY_OP(*, std::multiplies<>)
417UNI20_DEFINE_BINARY_OP(/, std::divides<>)
418
419#undef UNI20_DEFINE_BINARY_OP
420
421#define UNI20_DEFINE_ASSIGN_OP(NAME, OP) \
422 struct NAME \
423 { \
424 template <typename LHS, typename RHS> void operator()(LHS& lhs, RHS&& rhs) const \
425 { \
426 lhs OP std::forward<RHS>(rhs); \
427 } \
428 };
429
430UNI20_DEFINE_ASSIGN_OP(plus_assign, +=)
431UNI20_DEFINE_ASSIGN_OP(minus_assign, -=)
432UNI20_DEFINE_ASSIGN_OP(multiplies_assign, *=)
433UNI20_DEFINE_ASSIGN_OP(divides_assign, /=)
434
435#undef UNI20_DEFINE_ASSIGN_OP
436
437#define UNI20_DEFINE_ASYNC_COMPOUND_OPERATOR(OPSYM, FUNCTOR) \
438 template <typename T, typename U> \
439 requires uni20::async::is_async_compound_applicable<Async<T>&, U, uni20::async::FUNCTOR> \
440 Async<T>& operator OPSYM(Async<T>& lhs, U && rhs) \
441 { \
442 uni20::async::async_compound_op(lhs, std::forward<U>(rhs), uni20::async::FUNCTOR{}); \
443 return lhs; \
444 }
445
448UNI20_DEFINE_ASYNC_COMPOUND_OPERATOR(*=, multiplies_assign)
449UNI20_DEFINE_ASYNC_COMPOUND_OPERATOR(/=, divides_assign)
450
451#undef UNI20_DEFINE_ASYNC_COMPOUND_OPERATOR
452
453// unary operators
454
460template <typename T, typename U>
461void async_negate(WriteBuffer<T> lhs, U&& rhs)
463{
464 schedule([](auto in_, WriteBuffer<T> out_) static -> AsyncTask {
465 auto result = -co_await in_;
466 in_.release();
467 co_await out_ = std::move(result);
468 co_return;
469 }(std::move(rhs), std::move(lhs)));
470}
471
476template <typename T>
477auto operator-(Async<T> const& x)
478 requires requires(T t) { -t; }
479{
480 Async<T> result;
481 async_negate(result.write(), x.read());
482 return result;
483}
484
486
487} // namespace uni20::async
Promise-neutral coroutine task ownership and the public AsyncTask type.
Utilities for awaiting on Async<T>: co_await overload, all(), try_await().
Canonical host-oriented coroutine task type.
Definition async_task.hpp:196
Async<T> is a container for coroutine-safe asynchronous read/write.
Definition async/async.hpp:93
WriteBuffer< T > write()
Begin exclusive asynchronous access to the payload.
Definition async/async.hpp:346
Exclusive mutable-access buffer for one Async<T> epoch.
Definition buffers.hpp:859
auto storage() &
Awaiter yielding direct access to writer shared storage.
Definition buffers.hpp:1029
void release() noexcept
Release the writer epoch held by this buffer.
Definition buffers.hpp:957
Raised when a writer requests a mutable reference before construction.
Definition async_errors.hpp:74
Concept to determine if Op(T,U) is a candidate for forwarding for Async.
Definition async_ops.hpp:153
concept for validating a binary operation true if the operation result can be emplaced into the write...
Definition async_ops.hpp:161
Concept for a type that behaves like an asynchronous reader and writer (like Async<T>).
Definition async_ops.hpp:144
Concept for an Awaitable that yields a movable object that can be moved into T.
Definition async_ops.hpp:121
Definition async_ops.hpp:127
Concept for a type that behaves like an asynchronous writer that is also readable: it has a write() f...
Definition async_ops.hpp:136
Definition async_ops.hpp:107
An async_reader is satisfied if .read() returns an async read buffer.
Definition async_ops.hpp:95
Concept requiring an async writer that accepts values constructible from T.
Definition async_ops.hpp:112
An async_writer is satisfied if .write() returns a buffer supporting emplace(...).
Definition async_ops.hpp:104
concept for a valid type that supports in-place construction via emplace(...)
Definition async_ops.hpp:59
Definition async_ops.hpp:167
concept for a valid type that behaves as a read buffer awaitable for type T.
Definition async_ops.hpp:72
concept for a valid type that behaves as a read buffer awaitable This requires that the awaiter has a...
Definition async_ops.hpp:44
concept for a valid type that behaves as a write buffer that is also readable This requires that T x ...
Definition async_ops.hpp:90
concept for a valid type that behaves as a write buffer that is also readable This requires that valu...
Definition async_ops.hpp:85
concept for a valid type that behaves as a write buffer awaitable This requires that T x; co_await a ...
Definition async_ops.hpp:78
concept for a valid type that behaves as a write buffer awaitable This requires that value_type x; co...
Definition async_ops.hpp:52
locale_t value
Definition data_table.cpp:49
Coroutine scheduler for AsyncTask.
void async_move(WriteBuffer< T > lhs, U &&rhs)
Definition async_ops.hpp:369
decltype(std::declval< Op >()(std::declval< async_value_t< T > >(), std::declval< async_value_t< U > >())) async_binary_result_t
Result type of applying binary op Op to async_value_t<T>, async_value_t
Definition async_ops.hpp:149
#define UNI20_DEFINE_ASSIGN_OP(NAME, OP)
Definition async_ops.hpp:421
void async_binary_op(Writer &out, A &&a, B &&b, Op op)
Launch a coroutine computing result = op(a, b)
Definition async_ops.hpp:195
void async_initialize(Async< T > &destination, U &&source)
Initialize a fresh independent async value from another async source.
Definition async_ops.hpp:300
void async_compound_op(Async< T > &lhs, U &&rhs, Op op)
Launch a coroutine applying an in-place operation op(lhs, rhs).
Definition async_ops.hpp:225
#define UNI20_DEFINE_ASYNC_COMPOUND_OPERATOR(OPSYM, FUNCTOR)
Definition async_ops.hpp:437
auto operator-(Async< T > const &x)
Unary negation overload for Async values.
Definition async_ops.hpp:477
decltype(std::declval< write_buffer_t< AsyncLike > & >().take()) take_awaiter_t
Alias for the awaiter type returned by write_buffer_t::take().
Definition async_ops.hpp:100
void async_assign(WriteBuffer< T > destination, U &&source)
Assign an awaitable value into an existing independent-value timeline.
Definition async_ops.hpp:310
decltype(std::declval< AsyncLike & >().write()) write_buffer_t
Alias for the write-buffer type produced by write() on an lvalue.
Definition async_ops.hpp:98
#define UNI20_DEFINE_BINARY_OP(OPNAME, OP)
Definition async_ops.hpp:403
void async_negate(WriteBuffer< T > lhs, U &&rhs)
Negate a source value asynchronously and write into a destination buffer.
Definition async_ops.hpp:461
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26
void schedule(AsyncTask &&task)
Schedule a task on the currently configured global scheduler.
Definition debug_scheduler.hpp:237
auto all(Aw &&... aw) noexcept
Build an awaitable that waits for all of the provided awaitables.
Definition awaiters.hpp:200
decltype(auto) get_awaiter(U &&u) noexcept
for an awaitable a, return the actual Awaitable object returned by co_await
Definition awaiters.hpp:225
ReadBuffer< T > read(Async< T > const &a)
Convenience helper that forwards to Async<T>::read().
Definition async/async.hpp:540
typename async_value_type< std::remove_cvref_t< T > >::type async_value_t
Extract the underlying value type from an immediate value or Async<T>.
Definition async_traits.hpp:30
Async aliases for lazy tensor-level views.
#define ERROR_IF(cond,...)
Definition trace.hpp:389