| // -*- C++ -*- |
| //===------------------------------- task ---------------------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef _LIBCPP_EXPERIMENTAL_TASK |
| #define _LIBCPP_EXPERIMENTAL_TASK |
| |
| #include <experimental/__config> |
| #include <experimental/__memory> |
| #include <experimental/coroutine> |
| |
| #include <exception> |
| #include <type_traits> |
| #include <utility> |
| |
| #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) |
| #pragma GCC system_header |
| #endif |
| |
| #ifdef _LIBCPP_HAS_NO_COROUTINES |
| #if defined(_LIBCPP_WARNING) |
| _LIBCPP_WARNING("<experimental/task> cannot be used with this compiler") |
| #else |
| #warning <experimental/task> cannot be used with this compiler |
| #endif |
| #endif |
| |
| _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_COROUTINES |
| |
| ////// task<T> |
| |
| template <typename _Tp = void> |
| class task; |
| |
| struct __task_promise_final_awaitable { |
| _LIBCPP_INLINE_VISIBILITY |
| _LIBCPP_CONSTEXPR bool await_ready() const _NOEXCEPT { return false; } |
| |
| template <typename _TaskPromise> |
| _LIBCPP_INLINE_VISIBILITY coroutine_handle<> |
| await_suspend(coroutine_handle<_TaskPromise> __coro) const _NOEXCEPT { |
| _LIBCPP_ASSERT( |
| __coro.promise().__continuation_, |
| "Coroutine completed without a valid continuation attached."); |
| return __coro.promise().__continuation_; |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| void await_resume() const _NOEXCEPT {} |
| }; |
| |
| class _LIBCPP_TYPE_VIS __task_promise_base { |
| using _DeallocFunc = void(void* __ptr, size_t __size) _NOEXCEPT; |
| |
| template <typename _Alloc> |
| static constexpr bool __allocator_needs_to_be_stored = |
| !allocator_traits<_Alloc>::is_always_equal::value || |
| !is_default_constructible_v<_Alloc>; |
| |
| static _LIBCPP_CONSTEXPR size_t |
| __get_dealloc_func_offset(size_t __frameSize) _NOEXCEPT { |
| return _VSTD_LFTS::__aligned_allocation_size(__frameSize, |
| alignof(_DeallocFunc*)); |
| } |
| |
| static _LIBCPP_CONSTEXPR size_t |
| __get_padded_frame_size(size_t __frameSize) _NOEXCEPT { |
| return __get_dealloc_func_offset(__frameSize) + sizeof(_DeallocFunc*); |
| } |
| |
| template <typename _Alloc> |
| static _LIBCPP_CONSTEXPR size_t |
| __get_allocator_offset(size_t __frameSize) _NOEXCEPT { |
| return _VSTD_LFTS::__aligned_allocation_size( |
| __get_padded_frame_size(__frameSize), alignof(_Alloc)); |
| } |
| |
| template <typename _Alloc> |
| static _LIBCPP_CONSTEXPR size_t |
| __get_padded_frame_size_with_allocator(size_t __frameSize) _NOEXCEPT { |
| if constexpr (__allocator_needs_to_be_stored<_Alloc>) { |
| return __get_allocator_offset<_Alloc>(__frameSize) + sizeof(_Alloc); |
| } else { |
| return __get_padded_frame_size(__frameSize); |
| } |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| static _DeallocFunc*& __get_dealloc_func(void* __frameStart, |
| size_t __frameSize) _NOEXCEPT { |
| return *reinterpret_cast<_DeallocFunc**>( |
| static_cast<char*>(__frameStart) + |
| __get_dealloc_func_offset(__frameSize)); |
| } |
| |
| template <typename _Alloc> |
| _LIBCPP_INLINE_VISIBILITY static _Alloc& |
| __get_allocator(void* __frameStart, size_t __frameSize) _NOEXCEPT { |
| return *reinterpret_cast<_Alloc*>( |
| static_cast<char*>(__frameStart) + |
| __get_allocator_offset<_Alloc>(__frameSize)); |
| } |
| |
| public: |
| __task_promise_base() _NOEXCEPT = default; |
| |
| // Explicitly disable special member functions. |
| __task_promise_base(const __task_promise_base&) = delete; |
| __task_promise_base(__task_promise_base&&) = delete; |
| __task_promise_base& operator=(const __task_promise_base&) = delete; |
| __task_promise_base& operator=(__task_promise_base&&) = delete; |
| |
| static void* operator new(size_t __size) { |
| // Allocate space for an extra pointer immediately after __size that holds |
| // the type-erased deallocation function. |
| void* __pointer = ::operator new(__get_padded_frame_size(__size)); |
| |
| _DeallocFunc*& __deallocFunc = __get_dealloc_func(__pointer, __size); |
| __deallocFunc = [](void* __pointer, size_t __size) _NOEXCEPT { |
| ::operator delete(__pointer, __get_padded_frame_size(__size)); |
| }; |
| |
| return __pointer; |
| } |
| |
| template <typename _Alloc, typename... _Args> |
| static void* operator new(size_t __size, allocator_arg_t, _Alloc& __alloc, |
| _Args&...) { |
| using _CharAlloc = |
| typename allocator_traits<_Alloc>::template rebind_alloc<char>; |
| |
| _CharAlloc __charAllocator{__alloc}; |
| |
| void* __pointer = __charAllocator.allocate( |
| __get_padded_frame_size_with_allocator<_CharAlloc>(__size)); |
| |
| _DeallocFunc*& __deallocFunc = __get_dealloc_func(__pointer, __size); |
| __deallocFunc = [](void* __pointer, size_t __size) _NOEXCEPT { |
| // Allocators are required to not throw from their move constructors |
| // however they aren't required to be declared noexcept so we can't |
| // actually check this with a static_assert. |
| // |
| // static_assert(is_nothrow_move_constructible<_Alloc>::value, |
| // "task<T> coroutine custom allocator requires a noexcept " |
| // "move constructor"); |
| |
| size_t __paddedSize = |
| __get_padded_frame_size_with_allocator<_CharAlloc>(__size); |
| |
| if constexpr (__allocator_needs_to_be_stored<_CharAlloc>) { |
| _CharAlloc& __allocatorInFrame = |
| __get_allocator<_CharAlloc>(__pointer, __size); |
| _CharAlloc __allocatorOnStack = _VSTD::move(__allocatorInFrame); |
| __allocatorInFrame.~_CharAlloc(); |
| // Allocator requirements state that deallocate() must not throw. |
| // See [allocator.requirements] from C++ standard. |
| // We are relying on that here. |
| __allocatorOnStack.deallocate(static_cast<char*>(__pointer), |
| __paddedSize); |
| } else { |
| _CharAlloc __alloc; |
| __alloc.deallocate(static_cast<char*>(__pointer), __paddedSize); |
| } |
| }; |
| |
| // Copy the allocator into the heap frame (if required) |
| if constexpr (__allocator_needs_to_be_stored<_CharAlloc>) { |
| // task<T> coroutine custom allocation requires the copy constructor to |
| // not throw but we can't rely on it being declared noexcept. |
| // If it did throw we'd leak the allocation here. |
| ::new (static_cast<void*>( |
| _VSTD::addressof(__get_allocator<_CharAlloc>(__pointer, __size)))) |
| _CharAlloc(_VSTD::move(__charAllocator)); |
| } |
| |
| return __pointer; |
| } |
| |
| template <typename _This, typename _Alloc, typename... _Args> |
| static void* operator new(size_t __size, _This&, allocator_arg_t __allocArg, _Alloc& __alloc, |
| _Args&...) { |
| return __task_promise_base::operator new(__size, __allocArg, __alloc); |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| static void operator delete(void* __pointer, size_t __size)_NOEXCEPT { |
| __get_dealloc_func(__pointer, __size)(__pointer, __size); |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| suspend_always initial_suspend() const _NOEXCEPT { return {}; } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| __task_promise_final_awaitable final_suspend() _NOEXCEPT { return {}; } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| void __set_continuation(coroutine_handle<> __continuation) { |
| _LIBCPP_ASSERT(!__continuation_, "task already has a continuation"); |
| __continuation_ = __continuation; |
| } |
| |
| private: |
| friend struct __task_promise_final_awaitable; |
| |
| coroutine_handle<> __continuation_; |
| }; |
| |
| template <typename _Tp> |
| class _LIBCPP_TEMPLATE_VIS __task_promise final : public __task_promise_base { |
| using _Handle = coroutine_handle<__task_promise>; |
| |
| public: |
| __task_promise() _NOEXCEPT : __state_(_State::__no_value) {} |
| |
| ~__task_promise() { |
| switch (__state_) { |
| case _State::__value: |
| __value_.~_Tp(); |
| break; |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| case _State::__exception: |
| __exception_.~exception_ptr(); |
| break; |
| #endif |
| case _State::__no_value: |
| break; |
| }; |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| task<_Tp> get_return_object() _NOEXCEPT; |
| |
| void unhandled_exception() _NOEXCEPT { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| ::new (static_cast<void*>(&__exception_)) |
| exception_ptr(current_exception()); |
| __state_ = _State::__exception; |
| #else |
| _LIBCPP_ASSERT( |
| false, "task<T> coroutine unexpectedly called unhandled_exception()"); |
| #endif |
| } |
| |
| // Only enable return_value() overload if _Tp is implicitly constructible from |
| // _Value |
| template <typename _Value, |
| enable_if_t<is_convertible<_Value, _Tp>::value, int> = 0> |
| void return_value(_Value&& __value) |
| _NOEXCEPT_((is_nothrow_constructible_v<_Tp, _Value>)) { |
| __construct_value(static_cast<_Value&&>(__value)); |
| } |
| |
| template <typename _Value> |
| auto return_value(std::initializer_list<_Value> __initializer) _NOEXCEPT_( |
| (is_nothrow_constructible_v<_Tp, std::initializer_list<_Value>>)) |
| -> std::enable_if_t< |
| std::is_constructible_v<_Tp, std::initializer_list<_Value>>> { |
| __construct_value(_VSTD::move(__initializer)); |
| } |
| |
| auto return_value(_Tp&& __value) |
| _NOEXCEPT_((is_nothrow_move_constructible_v<_Tp>)) |
| -> std::enable_if_t<std::is_move_constructible_v<_Tp>> { |
| __construct_value(static_cast<_Tp&&>(__value)); |
| } |
| |
| _Tp& __lvalue_result() { |
| __throw_if_exception(); |
| return __value_; |
| } |
| |
| _Tp __rvalue_result() { |
| __throw_if_exception(); |
| return static_cast<_Tp&&>(__value_); |
| } |
| |
| private: |
| template <typename... _Args> |
| void __construct_value(_Args&&... __args) { |
| ::new (static_cast<void*>(_VSTD::addressof(__value_))) |
| _Tp(static_cast<_Args&&>(__args)...); |
| |
| // Only set __state_ after successfully constructing the value. |
| // If constructor throws then state will be updated by |
| // unhandled_exception(). |
| __state_ = _State::__value; |
| } |
| |
| void __throw_if_exception() { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| if (__state_ == _State::__exception) { |
| rethrow_exception(__exception_); |
| } |
| #endif |
| } |
| |
| enum class _State { __no_value, __value, __exception }; |
| |
| _State __state_ = _State::__no_value; |
| union { |
| char __empty_; |
| _Tp __value_; |
| exception_ptr __exception_; |
| }; |
| }; |
| |
| template <typename _Tp> |
| class __task_promise<_Tp&> final : public __task_promise_base { |
| using _Ptr = _Tp*; |
| using _Handle = coroutine_handle<__task_promise>; |
| |
| public: |
| __task_promise() _NOEXCEPT = default; |
| |
| ~__task_promise() { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| if (__has_exception_) { |
| __exception_.~exception_ptr(); |
| } |
| #endif |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| task<_Tp&> get_return_object() _NOEXCEPT; |
| |
| void unhandled_exception() _NOEXCEPT { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| ::new (static_cast<void*>(&__exception_)) |
| exception_ptr(current_exception()); |
| __has_exception_ = true; |
| #else |
| _LIBCPP_ASSERT( |
| false, "task<T> coroutine unexpectedly called unhandled_exception()"); |
| #endif |
| } |
| |
| void return_value(_Tp& __value) _NOEXCEPT { |
| ::new (static_cast<void*>(&__pointer_)) _Ptr(_VSTD::addressof(__value)); |
| } |
| |
| _Tp& __lvalue_result() { |
| __throw_if_exception(); |
| return *__pointer_; |
| } |
| |
| _Tp& __rvalue_result() { return __lvalue_result(); } |
| |
| private: |
| void __throw_if_exception() { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| if (__has_exception_) { |
| rethrow_exception(__exception_); |
| } |
| #endif |
| } |
| |
| union { |
| char __empty_; |
| _Ptr __pointer_; |
| exception_ptr __exception_; |
| }; |
| bool __has_exception_ = false; |
| }; |
| |
| template <> |
| class __task_promise<void> final : public __task_promise_base { |
| using _Handle = coroutine_handle<__task_promise>; |
| |
| public: |
| task<void> get_return_object() _NOEXCEPT; |
| |
| void return_void() _NOEXCEPT {} |
| |
| void unhandled_exception() _NOEXCEPT { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| __exception_ = current_exception(); |
| #endif |
| } |
| |
| void __lvalue_result() { __throw_if_exception(); } |
| |
| void __rvalue_result() { __throw_if_exception(); } |
| |
| private: |
| void __throw_if_exception() { |
| #ifndef _LIBCPP_NO_EXCEPTIONS |
| if (__exception_) { |
| rethrow_exception(__exception_); |
| } |
| #endif |
| } |
| |
| exception_ptr __exception_; |
| }; |
| |
| template <typename _Tp> |
| class _LIBCPP_TEMPLATE_VIS _LIBCPP_NODISCARD_AFTER_CXX17 task { |
| public: |
| using promise_type = __task_promise<_Tp>; |
| |
| private: |
| using _Handle = coroutine_handle<__task_promise<_Tp>>; |
| |
| class _AwaiterBase { |
| public: |
| _AwaiterBase(_Handle __coro) _NOEXCEPT : __coro_(__coro) {} |
| |
| _LIBCPP_INLINE_VISIBILITY |
| bool await_ready() const { return __coro_.done(); } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| _Handle await_suspend(coroutine_handle<> __continuation) const { |
| __coro_.promise().__set_continuation(__continuation); |
| return __coro_; |
| } |
| |
| protected: |
| _Handle __coro_; |
| }; |
| |
| public: |
| _LIBCPP_INLINE_VISIBILITY |
| task(task&& __other) _NOEXCEPT |
| : __coro_(_VSTD::exchange(__other.__coro_, {})) {} |
| |
| task(const task&) = delete; |
| task& operator=(const task&) = delete; |
| |
| _LIBCPP_INLINE_VISIBILITY |
| ~task() { |
| if (__coro_) |
| __coro_.destroy(); |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| void swap(task& __other) _NOEXCEPT { _VSTD::swap(__coro_, __other.__coro_); } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| auto operator co_await() & { |
| class _Awaiter : public _AwaiterBase { |
| public: |
| using _AwaiterBase::_AwaiterBase; |
| |
| _LIBCPP_INLINE_VISIBILITY |
| decltype(auto) await_resume() { |
| return this->__coro_.promise().__lvalue_result(); |
| } |
| }; |
| |
| _LIBCPP_ASSERT(__coro_, |
| "Undefined behaviour to co_await an invalid task<T>"); |
| return _Awaiter{__coro_}; |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY |
| auto operator co_await() && { |
| class _Awaiter : public _AwaiterBase { |
| public: |
| using _AwaiterBase::_AwaiterBase; |
| |
| _LIBCPP_INLINE_VISIBILITY |
| decltype(auto) await_resume() { |
| return this->__coro_.promise().__rvalue_result(); |
| } |
| }; |
| |
| _LIBCPP_ASSERT(__coro_, |
| "Undefined behaviour to co_await an invalid task<T>"); |
| return _Awaiter{__coro_}; |
| } |
| |
| private: |
| friend class __task_promise<_Tp>; |
| |
| _LIBCPP_INLINE_VISIBILITY |
| task(_Handle __coro) _NOEXCEPT : __coro_(__coro) {} |
| |
| _Handle __coro_; |
| }; |
| |
| template <typename _Tp> |
| task<_Tp> __task_promise<_Tp>::get_return_object() _NOEXCEPT { |
| return task<_Tp>{_Handle::from_promise(*this)}; |
| } |
| |
| template <typename _Tp> |
| task<_Tp&> __task_promise<_Tp&>::get_return_object() _NOEXCEPT { |
| return task<_Tp&>{_Handle::from_promise(*this)}; |
| } |
| |
| task<void> __task_promise<void>::get_return_object() _NOEXCEPT { |
| return task<void>{_Handle::from_promise(*this)}; |
| } |
| |
| _LIBCPP_END_NAMESPACE_EXPERIMENTAL_COROUTINES |
| |
| #endif |