Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
async/async.hpp
Go to the documentation of this file.
1
3
4// NOTE: Immediately-invoked coroutine lambdas must not capture variables.
5// Captures (by reference or value) are stored in the lambda frame, which is destroyed
6// after the lambda returns. If the coroutine suspends, any captured state becomes dangling.
7// Instead, pass all needed data via parameters, which are safely moved into the coroutine frame.
8
9#pragma once
10
11#include "async_errors.hpp"
12#include "async_node.hpp"
13#include "async_traits.hpp"
14#include "buffers.hpp"
15#include "epoch_queue.hpp"
16#include <concepts>
17#include <initializer_list>
18#include <memory>
19#include <string>
20#include <type_traits>
22#include <uni20/config.hpp>
23#include <utility>
24
25namespace uni20::async
26{
27
28class DebugScheduler;
29
33
36
37template <typename T> class ReverseValue; // forward declaration so we can add it as a friend of Async<T>
38
39namespace detail
40{
41
42template <typename Source>
43concept async_source = std::same_as<std::remove_cvref_t<Source>, Async<async_value_t<Source>>>;
44
45template <typename Target, typename Source>
46concept value_rebind_source = (async_source<Source> && std::constructible_from<Target, async_value_t<Source> const&>) ||
47 (!async_source<Source> && std::constructible_from<Target, Source&&>);
48
49template <typename Target, typename Source>
50concept async_assignment_source =
51 (!std::same_as<std::remove_cvref_t<Source>, Async<Target>>) &&
52 ((!is_async_alias_v<Target> && value_rebind_source<Target, Source>) ||
53 (is_async_alias_v<Target> && async_alias_assignable_from<Target, async_value_t<Source> const&>));
54
55} // namespace detail
56
57template <typename T, typename U>
58 requires(!is_async_alias_v<T> && detail::async_source<U> && std::constructible_from<T, async_value_t<U> const&>)
59void async_initialize(Async<T>& destination, U&& source);
60
62template <typename Alias, typename Parent, typename... Args>
63[[nodiscard]] Async<Alias> make_async_alias(Async<Parent> const& parent, Args&&... args);
64
69template <typename Alias, typename Parent, typename... Args>
70[[nodiscard]] Async<Alias> make_async_alias_from_parent(Async<Parent>& parent, Args&&... args);
71
73template <typename Alias, typename Parent, typename... Args>
74[[nodiscard]] Async<Alias> make_async_alias_from_parent(Async<Parent> const& parent, Args&&... args);
75
93template <typename T> class Async {
94 public:
95 using value_type = T;
96
97 static_assert(std::is_object_v<T> && !std::is_array_v<T>, "Async<T> requires a non-array object type");
98 static_assert(std::destructible<T>, "Async<T> requires a non-throwing destructor");
99
102 requires(!is_async_alias_v<T>)
103 : storage_(make_unconstructed_shared_storage<T>()), queue_(std::make_shared<EpochQueue>())
104 {
105 queue_->latest()->start();
106#if UNI20_DEBUG_DAG
107 queue_->initialize_node(storage_);
108#endif
109 }
110
114 requires(!is_async_alias_v<T>)
115 : storage_(make_unconstructed_shared_storage<T>()), queue_(std::make_shared<EpochQueue>())
116 {
117 (void)tag;
118#if UNI20_DEBUG_DAG
119 queue_->initialize_node(storage_);
120#endif
121 }
122
126 template <typename U>
127 requires(std::convertible_to<U, T> && !is_async_alias_v<T>)
128 Async(U&& val) : storage_(make_shared_storage<T>(std::forward<U>(val))), queue_(std::make_shared<EpochQueue>())
129 {
130 queue_->latest()->start_reading();
131#if UNI20_DEBUG_DAG
132 queue_->initialize_node(storage_);
133#endif
134 }
135
139 template <typename U>
140 requires(std::constructible_from<T, U> && (!std::convertible_to<U, T>) && !is_async_alias_v<T>)
141 explicit Async(U&& u) : storage_(make_shared_storage<T>(std::forward<U>(u))), queue_(std::make_shared<EpochQueue>())
142 {
143 queue_->latest()->start_reading();
144#if UNI20_DEBUG_DAG
145 queue_->initialize_node(storage_);
146#endif
147 }
148
152 template <typename... Args>
153 requires(std::constructible_from<T, Args...> && !is_async_alias_v<T>)
154 Async(Args&&... args)
155 : storage_(make_shared_storage<T>(std::forward<Args>(args)...)), queue_(std::make_shared<EpochQueue>())
156 {
157 queue_->latest()->start_reading();
158#if UNI20_DEBUG_DAG
159 queue_->initialize_node(storage_);
160#endif
161 }
162
169 template <typename U, typename... Args>
170 requires(std::constructible_from<T, std::initializer_list<U>&, Args...> && !is_async_alias_v<T>)
171 Async(std::initializer_list<U> init, Args&&... args)
172 : storage_(make_shared_storage<T>(init, std::forward<Args>(args)...)), queue_(std::make_shared<EpochQueue>())
173 {
174 queue_->latest()->start_reading();
175#if UNI20_DEBUG_DAG
176 queue_->initialize_node(storage_);
177#endif
178 }
179
189 Async(Async const& rhs)
190 requires(!is_async_alias_v<T> && std::constructible_from<T, T const&>)
191 : Async()
192 {
193 async_initialize(*this, rhs);
194 }
195
197 Async(Async const&)
198 requires(!is_async_alias_v<T> && !std::constructible_from<T, T const&>)
199 = delete;
200
204 Async(Async const& rhs)
205 requires(is_async_alias_v<T>)
206 : storage_(rhs.storage_), queue_(rhs.queue_)
207 {}
208
212 Async& debug_name(std::string const& label)
213 {
214#if UNI20_DEBUG_DAG
215 TaskRegistry::name_async_value(queue_->debug_node(), label);
216#else
217 static_cast<void>(label);
218#endif
219 return *this;
220 }
221
225 Async const& debug_name(std::string const& label) const
226 {
227#if UNI20_DEBUG_DAG
228 TaskRegistry::name_async_value(queue_->debug_node(), label);
229#else
230 static_cast<void>(label);
231#endif
232 return *this;
233 }
234
243 template <typename U>
244 requires detail::async_assignment_source<T, U>
245 Async& operator=(U&& rhs);
246
267 Async& operator=(Async const& rhs)
268 requires(!is_async_alias_v<T> && std::constructible_from<T, T const&>)
269 {
270 if (this != &rhs)
271 {
272 Async<T> replacement;
273 async_initialize(replacement, rhs);
274 *this = std::move(replacement);
275 }
276 return *this;
277 }
278
281 requires(!is_async_alias_v<T> && !std::constructible_from<T, T const&>)
282 = delete;
283
288 Async& operator=(Async const& rhs)
289 requires(is_async_alias_v<T> && detail::async_alias_assignable_from<T, T const&>)
290 {
291 if (this != &rhs) async_assign(*this, rhs);
292 return *this;
293 }
294
297 requires(is_async_alias_v<T> && !detail::async_alias_assignable_from<T, T const&>)
298 = delete;
299
305 Async(Async&& other) noexcept = default;
309 Async& operator=(Async&& other) noexcept
310 requires(!is_async_alias_v<T>)
311 {
312 if (this != &other)
313 {
314 storage_ = std::move(other.storage_);
315 queue_ = std::move(other.queue_);
316 }
317 return *this;
318 }
319
324 requires(is_async_alias_v<T> && detail::async_alias_assignable_from<T, T const&>)
325 {
326 if (this != &other) async_assign(*this, other);
327 return *this;
328 }
329
332 requires(is_async_alias_v<T> && !detail::async_alias_assignable_from<T, T const&>)
333 = delete;
334
335 ~Async() = default;
336
339 ReadBuffer<T> read() const { return ReadBuffer<T>(queue_->create_read_context(storage_)); }
340
346 WriteBuffer<T> write() { return WriteBuffer<T>(queue_->create_write_context(storage_)); }
347
348 // template <typename Sched> T& get_wait(Sched& sched)
349 // {
350 // while (queue_.has_pending_writers())
351 // {
352 // TRACE_MODULE(ASYNC, "Has pending writers");
353 // sched.run();
354 // }
355 // return *value_;
356 // }
357
359 void wait() const;
360
364 void wait(IAsyncScheduler& sched) const;
365
368 [[nodiscard]] T const& get_wait() const;
369
376 [[nodiscard]] T const& get_wait(IAsyncScheduler& sched) const;
377
378 // TODO: we could have a version that returns a ref-counted proxy, which enables reference rather than copy
379
383 requires(!is_async_alias_v<T> && std::constructible_from<T, T &&>);
384
390 void unsafe_set(T const& x)
391 requires(!is_async_alias_v<T> && std::assignable_from<T&, T const&>)
392 {
393 auto* ptr = require_value();
394 *ptr = x;
395 }
396
401 T unsafe_value() const
402 requires(!is_async_alias_v<T> && std::constructible_from<T, T const&>)
403 {
404 return *require_value();
405 }
406
409 T const& unsafe_value_ref() const { return *require_value(); }
410
414 requires(!is_async_alias_v<T>)
415 {
416 return *require_value();
417 }
418
423 EpochQueue const& queue() const { return *queue_; }
426 shared_storage<T> const& storage() const { return storage_; }
427
433 [[nodiscard]] T* storage_address() noexcept
434 requires(!is_async_alias_v<T>)
435 {
436 return storage_.storage_address();
437 }
438
444 [[nodiscard]] T const* storage_address() const noexcept { return storage_.storage_address(); }
445
453 std::shared_ptr<T> value_ptr()
454 requires(!is_async_alias_v<T>)
455 {
456 if (!storage_.valid()) return {};
457 return std::shared_ptr<T>(storage_.get(), [storage = storage_](T*) mutable { storage.reset(); });
458 }
459
462 std::shared_ptr<T const> value_ptr() const
463 {
464 if (!storage_.valid()) return {};
465 return std::shared_ptr<T const>(storage_.get(), [storage = storage_](T const*) mutable { storage.reset(); });
466 }
467
468 private:
470 Async(shared_storage<T> storage, std::shared_ptr<EpochQueue> queue)
471 : storage_(std::move(storage)), queue_(std::move(queue))
472 {
473 CHECK(storage_.has_lifetime_owner());
474 CHECK(queue_);
475 }
476
480 T* try_get_value() const
481 {
482 if (!storage_.valid()) throw async_storage_missing{};
483 return storage_.get();
484 }
485
489 T* require_value() const
490 {
491 if (auto* ptr = try_get_value()) return ptr;
493 }
494
495 friend class ReverseValue<T>;
496 template <typename Alias, typename Parent, typename... Args>
497 friend Async<Alias> make_async_alias(Async<Parent> const& parent, Args&&... args);
498 template <typename Alias, typename Parent, typename... Args>
499 friend Async<Alias> make_async_alias_from_parent(Async<Parent>& parent, Args&&... args);
500 template <typename Alias, typename Parent, typename... Args>
501 friend Async<Alias> make_async_alias_from_parent(Async<Parent> const& parent, Args&&... args);
502
503 mutable shared_storage<T> storage_;
507 std::shared_ptr<EpochQueue> queue_;
508};
509
510template <typename Alias, typename Parent, typename... Args>
511[[nodiscard]] Async<Alias> make_async_alias(Async<Parent> const& parent, Args&&... args)
512{
513 static_assert(is_async_alias_v<Alias>, "Async aliases require is_async_alias_v<Alias>");
514 return Async<Alias>(make_shared_storage_alias<Alias>(parent.storage_, std::forward<Args>(args)...), parent.queue_);
515}
516
517template <typename Alias, typename Parent, typename... Args>
518[[nodiscard]] Async<Alias> make_async_alias_from_parent(Async<Parent>& parent, Args&&... args)
519{
520 static_assert(is_async_alias_v<Alias>, "Async aliases require is_async_alias_v<Alias>");
521 return Async<Alias>(
522 make_shared_storage_alias<Alias>(parent.storage_, parent.storage_.storage_address(), std::forward<Args>(args)...),
523 parent.queue_);
524}
525
526template <typename Alias, typename Parent, typename... Args>
527[[nodiscard]] Async<Alias> make_async_alias_from_parent(Async<Parent> const& parent, Args&&... args)
528{
529 static_assert(is_async_alias_v<Alias>, "Async aliases require is_async_alias_v<Alias>");
530 return Async<Alias>(make_shared_storage_alias<Alias>(parent.storage_,
531 std::as_const(parent.storage_).storage_address(),
532 std::forward<Args>(args)...),
533 parent.queue_);
534}
535
540template <typename T> ReadBuffer<T> read(Async<T> const& a) { return a.read(); }
545template <typename T> WriteBuffer<T> write(Async<T>& a) { return a.write(); }
546
547} // namespace uni20::async
548
549#include "async_impl.hpp"
Exception hierarchy for async/dataflow operations.
Inline implementation of Async<T> helpers and task await transforms.
Debug metadata nodes used for async DAG visualization.
Payload classification and write-through customization for asynchronous values.
Awaitable capabilities for shared reads and exclusive mutable access to Async<T>.
static void name_async_value(async::NodeInfo const *node, std::string const &label)
Assigns an optional human-readable label to an async value node.
Async<T> is a container for coroutine-safe asynchronous read/write.
Definition async/async.hpp:93
Async(Async const &rhs)
Structurally copy an async alias handle.
Definition async/async.hpp:204
Async()
Initializes async state without constructing the stored value.
Definition async/async.hpp:101
Async(Async const &)=delete
Forbid copying an independent async value whose payload cannot be copy-constructed.
Async & operator=(Async const &)=delete
Forbid assignment to a read-only async alias.
T * storage_address() noexcept
Return the address reserved for the contained value when constructing an alias descriptor.
Definition async/async.hpp:433
T unsafe_value() const
Return a copy of the stored value.
Definition async/async.hpp:401
T move_from_wait()
Block until the value is available, then move it out of the Async.
Definition async_impl.hpp:64
ReadBuffer< T > read() const
Begin an asynchronous read of the value.
Definition async/async.hpp:339
Async(U &&u)
Explicitly construct from a value that requires explicit conversion.
Definition async/async.hpp:141
T const & get_wait() const
Block the current thread until the value becomes available.
Definition async_impl.hpp:52
void unsafe_set(T const &x)
Overwrite the stored value directly.
Definition async/async.hpp:390
Async(Async &&other) noexcept=default
Move-construct from another Async<T> handle.
T & unsafe_value_ref()
Access the stored value without synchronization.
Definition async/async.hpp:413
T const & unsafe_value_ref() const
Access the stored value without synchronization.
Definition async/async.hpp:409
EpochQueue const & queue() const
Inspect the shared epoch queue.
Definition async/async.hpp:423
Async(async_do_not_start_t tag)
Initializes async state without constructing the stored value or starting the queue.
Definition async/async.hpp:113
WriteBuffer< T > write()
Begin exclusive asynchronous access to the payload.
Definition async/async.hpp:346
T const * storage_address() const noexcept
Return the const address reserved for the contained value when constructing an alias descriptor.
Definition async/async.hpp:444
std::shared_ptr< T const > value_ptr() const
Access the const stored value pointer with shared ownership semantics.
Definition async/async.hpp:462
Async(Args &&... args)
Construct the stored value in place using forwarded arguments.
Definition async/async.hpp:154
Async & operator=(Async &&other) noexcept
Rebind an independent async value from another Async<T> handle.
Definition async/async.hpp:309
friend Async< Alias > make_async_alias(Async< Parent > const &parent, Args &&... args)
Construct an async alias that retains a parent value and shares its epoch queue.
Definition async/async.hpp:511
Async & operator=(U &&rhs)
Assign an immediate or heterogeneous async value.
std::shared_ptr< T > value_ptr()
Access the mutable stored value pointer with shared ownership semantics.
Definition async/async.hpp:453
T value_type
Definition async/async.hpp:95
Async const & debug_name(std::string const &label) const
Assign an optional debug label for task-registry graph output.
Definition async/async.hpp:225
Async(Async const &rhs)
Construct a new Async<T> by copying the value from another Async<T>.
Definition async/async.hpp:189
Async & operator=(Async const &rhs)
Copy-assign from another Async<T>, overwriting this instance's value timeline.
Definition async/async.hpp:267
shared_storage< T > const & storage() const
Access the shared storage control block.
Definition async/async.hpp:426
Async & operator=(Async &&other)
Assign through a mutable async alias from an exact-type rvalue alias.
Definition async/async.hpp:323
Async(U &&val)
Construct from a value convertible to T.
Definition async/async.hpp:128
Async & operator=(Async &&)=delete
Forbid assignment to a read-only async alias.
Async & debug_name(std::string const &label)
Assign an optional debug label for task-registry graph output.
Definition async/async.hpp:212
friend Async< Alias > make_async_alias_from_parent(Async< Parent > &parent, Args &&... args)
Construct an async alias from a mutable parent descriptor.
Definition async/async.hpp:518
Async & operator=(Async const &rhs)
Assign through a mutable async alias from another exact-type alias.
Definition async/async.hpp:288
Async(std::initializer_list< U > init, Args &&... args)
Construct the stored value in place from an initializer list.
Definition async/async.hpp:171
Async & operator=(Async const &)=delete
Forbid copy assignment when an independent payload cannot be copy-constructed.
void wait() const
Block the current thread until the value becomes available.
Definition async_impl.hpp:42
Coordinates multiple epochs of read/write gates.
Definition epoch_queue.hpp:22
Scheduler interface for initial AsyncTask submission and host-side progress.
Definition scheduler.hpp:89
RAII handle for reading the value from an Async container at a given epoch.
Definition buffers.hpp:53
Reverse-mode accumulation endpoint with staged input/output gradient channels.
Definition reverse_value.hpp:70
Exclusive mutable-access buffer for one Async<T> epoch.
Definition buffers.hpp:859
Thrown when an Async value is accessed before initialization.
Definition async_errors.hpp:32
A lightweight, thread-safe, reference-counted storage for a single object.
Definition shared_storage.hpp:94
FIFO queue enforcing writer→readers→next-writer ordering.
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_assign(WriteBuffer< T > destination, U &&source)
Assign an awaitable value into an existing independent-value timeline.
Definition async_ops.hpp:310
Definition qnum.hpp:403
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26
ReadBuffer< T > read(Async< T > const &a)
Convenience helper that forwards to Async<T>::read().
Definition async/async.hpp:540
Async< Alias > make_async_alias(Async< Parent > const &parent, Args &&... args)
Construct an async alias that retains a parent value and shares its epoch queue.
Definition async/async.hpp:511
Async< Alias > make_async_alias_from_parent(Async< Parent > &parent, Args &&... args)
Construct an async alias from a mutable parent descriptor.
Definition async/async.hpp:518
constexpr bool is_async_alias_v
True when T is an owner-bound async alias descriptor.
Definition async_traits.hpp:53
shared_storage< T > make_shared_storage(Args &&... args)
Create a new shared_storage<T> with T(args...) in-place.
Definition shared_storage.hpp:392
WriteBuffer< T > write(Async< T > &a)
Convenience helper that forwards to Async<T>::write().
Definition async/async.hpp:545
constexpr async_do_not_start_t async_do_not_start
Tag constant to construct an Async without starting the queue object.
Definition async/async.hpp:35
shared_storage< T > make_unconstructed_shared_storage()
Create a new shared_storage<T> with an unconstructed T.
Definition shared_storage.hpp:381
Tag type to construct an Async without starting the queue object.
Definition async/async.hpp:32
#define CHECK(cond,...)
Definition trace.hpp:299