Index: include/experimental/task =================================================================== --- /dev/null +++ include/experimental/task @@ -0,0 +1,622 @@ +// -*- C++ -*- +//===----------------------------- coroutine -----------------------------===// +// NOTE: at the moment it is in its own header, we intend to eventually +// move it into experimental/coroutine header. +// +// +// 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 +#include + +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +#ifdef _LIBCPP_HAS_NO_COROUTINES +# if defined(_LIBCPP_WARNING) + _LIBCPP_WARNING(" cannot be used with this compiler") +# else +# warning cannot be used with this compiler +# endif +#endif + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_COROUTINES + +////// task + +struct __task_promise_final_awaitable { + _LIBCPP_INLINE_VISIBILITY + _LIBCPP_CONSTEXPR bool await_ready() const _NOEXCEPT { return false; } + + template + _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, _VSTD::size_t __size); + +public: + + void* operator new (size_t __size) + { + // Allocate space for an extra pointer immediately after __size that holds + // the type-erased deallocation function. + auto __paddedSize1 = (__size + alignof(_DeallocFunc*) - 1) & ~(alignof(_DeallocFunc*) - 1); + auto __paddedSize2 = __paddedSize1 + sizeof(_DeallocFunc*); + void* __mem = ::operator new (__paddedSize2); + _DeallocFunc*& __deallocFunc = *reinterpret_cast<_DeallocFunc**>(static_cast(__mem) + __paddedSize1); + __deallocFunc = [](void* __pointer, _VSTD::size_t __size) _NOEXCEPT + { + auto __paddedSize1 = (__size + alignof(_DeallocFunc*)-1) & ~(alignof(_DeallocFunc*)-1); + auto __paddedSize2 = __paddedSize1 + sizeof(_DeallocFunc*); + ::operator delete(__pointer, __paddedSize2); + }; + return __mem; + } + + template + void* operator new(size_t __size, allocator_arg_t, _Alloc& __allocator, _Args&...) + { + auto __paddedSize1 = (__size + alignof(_DeallocFunc*)-1) & ~(alignof(_DeallocFunc*)-1); + auto __paddedSize2 = __paddedSize1 + sizeof(_DeallocFunc*); + auto __paddedSize3 = (__paddedSize2 + alignof(_Alloc)-1) & ~(alignof(_Alloc)-1); + auto __paddedSize4 = __paddedSize3 + sizeof(_Alloc); + void* __mem = __allocator.allocate(__paddedSize4); + _DeallocFunc*& __deallocFunc = *reinterpret_cast<_DeallocFunc**>(static_cast(__mem) + __paddedSize1); + __deallocFunc = [](void* __pointer, _VSTD::size_t __size) _NOEXCEPT + { + auto __paddedSize1 = (__size + alignof(_DeallocFunc*)-1) & ~(alignof(_DeallocFunc*)-1); + auto __paddedSize2 = __paddedSize1 + sizeof(_DeallocFunc*); + auto __paddedSize3 = (__paddedSize2 + alignof(_Alloc)-1) & ~(alignof(_Alloc)-1); + auto __paddedSize4 = __paddedSize3 + sizeof(_Alloc); + _Alloc& __heapAllocator = *reinterpret_cast<_Alloc*>(static_cast(__pointer) + __paddedSize3); + static_assert( + is_nothrow_move_constructible_v<_Alloc>, + "task coroutine custom alloctor requires a _NOEXCEPT move constructor"); + // We require the _Alloc move constructor to be noexcept here. + // If it were to throw then we'd have problems + _Alloc __allocatorOnStack = move(__heapAllocator); + __allocatorOnStack.deallocate( + static_cast(__pointer), __paddedSize4); + }; + + // Copy the allocator into the heap frame. +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif + new (static_cast(__mem) + __paddedSize3) _Alloc(__allocator); +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + // Allocator copy constructor has thrown + // Free the memory and let exceptions propagate out. + try + { + __allocator.deallocate( + static_cast(__mem), + __paddedSize4); + } + catch (...) + { + terminate(); + } + + throw; + } +#endif + + return __mem; + } + + void operator delete(void* __pointer, size_t __size) + { + auto __paddedSize1 = (__size + alignof(_DeallocFunc*) - 1) & ~(alignof(_DeallocFunc*) - 1); + _DeallocFunc*& __deallocFunc = *reinterpret_cast<_DeallocFunc**>(static_cast(__pointer) + __paddedSize1); + __deallocFunc(__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) _NOEXCEPT { + __continuation_ = __continuation; + } + +private: + friend class __task_promise_final_awaitable; + + coroutine_handle<> __continuation_; +}; + +template +class _LIBCPP_TEMPLATE_VIS __task_promise final : public __task_promise_base { + using _Handle = coroutine_handle<__task_promise>; + +public: + _LIBCPP_INLINE_VISIBILITY + __task_promise() _NOEXCEPT : __state_(_State::_no_value) {} + + ~__task_promise() { + switch (__state_) { + case _State::_value: + reinterpret_cast<_Tp*>(&__storage_)->~_Tp(); + break; +#ifndef _LIBCPP_NO_EXCEPTIONS + case _State::_exception: + reinterpret_cast(&__storage_)->~exception_ptr(); + break; +#endif + case _State::_no_value: + break; + }; + } + + _LIBCPP_INLINE_VISIBILITY + _Handle get_return_object() _NOEXCEPT { return _Handle::from_promise(*this); } + + void unhandled_exception() _NOEXCEPT { +#ifndef _LIBCPP_NO_EXCEPTIONS + new (&__storage_) exception_ptr(current_exception()); + __state_ = _State::_exception; +#else + terminate(); +#endif + } + + // Only enable return_value() overload if _Tp is implicitly constructible from _Value + template < + typename _Value, + enable_if_t::value, int> = 0> + void return_value(_Value&& __value) + _NOEXCEPT_((is_nothrow_constructible<_Tp, _Value&&>::value)) { + new (&__storage_) _Tp(static_cast<_Value&&>(__value)); + // Only set __state_ after successfully constructing the value. + // If constructor throws then state will be updated by unhandled_exception(). + __state_ = _State::_value; + } + + _Tp& __lvalue_result() { + __throw_if_exception(); + return *reinterpret_cast<_Tp*>(&__storage_); + } + + _Tp&& __rvalue_result() { + __throw_if_exception(); + return static_cast<_Tp&&>(*reinterpret_cast<_Tp*>(&__storage_)); + } + +private: + void __throw_if_exception() { +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__state_ == _State::_exception) { + rethrow_exception( + *reinterpret_cast(__storage_)); + } +#endif + } + + enum class _State { + _no_value, + _value +#ifndef _LIBCPP_NO_EXCEPTIONS + , + _exception +#endif + }; + + static constexpr size_t __storageSize = +#ifdef _LIBCPP_NO_EXCEPTIONS + sizeof(_Tp); +#else + sizeof(_Tp) > sizeof(exception_ptr) ? sizeof(_Tp) + : sizeof(exception_ptr); +#endif + + static constexpr size_t __storageAlignment = +#ifdef _LIBCPP_NO_EXCEPTIONS + alignof(_Tp); +#else + alignof(_Tp) > alignof(exception_ptr) + ? alignof(_Tp) + : alignof(exception_ptr); +#endif + + _State __state_; + alignas(__storageAlignment) char __storage_[__storageSize]; +}; + +template +class __task_promise<_Tp&> final : public __task_promise_base { + using _Ptr = _Tp*; + using _Handle = coroutine_handle<__task_promise>; + +public: + __task_promise() _NOEXCEPT +#ifndef _LIBCPP_NO_EXCEPTIONS + : __has_exception_(false) +#endif + { + } + + ~__task_promise() { +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__has_exception_) { + reinterpret_cast(&__storage_)->~exception_ptr(); + } +#endif + } + + _LIBCPP_INLINE_VISIBILITY + _Handle get_return_object() _NOEXCEPT { return _Handle::from_promise(*this); } + + void unhandled_exception() _NOEXCEPT { +#ifndef _LIBCPP_NO_EXCEPTIONS + new (&__storage_) exception_ptr(current_exception()); + __has_exception_ = true; +#else + // FIXME: Should this be _LIBCPP_ASSERT(false)? + terminate(); +#endif + } + + void return_value(_Tp& __value) _NOEXCEPT { + new (&__storage_) _Ptr(_VSTD::addressof(__value)); + } + + _Tp& __lvalue_result() { + __throw_if_exception(); + return *reinterpret_cast<_Ptr>(&__storage_); + } + + _Tp& __rvalue_result() { return __lvalue_result(); } + +private: + void __throw_if_exception() { +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__has_exception_) { + rethrow_exception( + *reinterpret_cast(&__storage_)); + } +#endif + } + + static constexpr auto __storageSize = +#ifdef _LIBCPP_NO_EXCEPTIONS + sizeof(_Ptr); +#else + sizeof(_Ptr) > sizeof(exception_ptr) + ? sizeof(_Ptr) + : sizeof(exception_ptr); +#endif + + static constexpr auto __storageAlignment = +#ifdef _LIBCPP_NO_EXCEPTIONS + alignof(_Ptr); +#else + alignof(_Ptr) > alignof(exception_ptr) + ? alignof(_Ptr) + : alignof(exception_ptr); +#endif + + alignas(__storageAlignment) char __storage_[__storageSize]; + bool __has_exception_; +}; + +template <> +class __task_promise final : public __task_promise_base { + using _Handle = coroutine_handle<__task_promise>; + +public: + __task_promise() _NOEXCEPT +#ifndef _LIBCPP_NO_EXCEPTIONS + : __has_exception_(false) +#endif + { + } + + ~__task_promise() { +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__has_exception_) { + reinterpret_cast(&__storage_)->~exception_ptr(); + } +#endif + } + + _Handle get_return_object() _NOEXCEPT { return _Handle::from_promise(*this); } + + void return_void() _NOEXCEPT {} + + void unhandled_exception() _NOEXCEPT { +#ifndef _LIBCPP_NO_EXCEPTIONS + new (&__storage_) exception_ptr(current_exception()); + __has_exception_ = true; +#endif + } + + void __lvalue_result() { __throw_if_exception(); } + + void __rvalue_result() { __throw_if_exception(); } + +private: + void __throw_if_exception() { +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__has_exception_) { + rethrow_exception( + *reinterpret_cast(&__storage_)); + } +#endif + } + +#ifndef _LIBCPP_NO_EXCEPTIONS + alignas(exception_ptr) char __storage_[sizeof(exception_ptr)]; + bool __has_exception_; +#endif +}; + +template +class _LIBCPP_TEMPLATE_VIS 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 _NOEXCEPT { return !__coro_ || __coro_.done(); } + + _LIBCPP_INLINE_VISIBILITY + _Handle await_suspend(coroutine_handle<> __continuation) const _NOEXCEPT { + __coro_.promise().__set_continuation(__continuation); + return __coro_; + } + + protected: + _Handle __coro_; + }; + +public: + + _LIBCPP_INLINE_VISIBILITY + task(_Handle __coro) _NOEXCEPT : __coro_(__coro) {} + + _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 + task& operator=(task&& __other) _NOEXCEPT { + _Handle __oldHandle = + _VSTD::exchange(__coro_, _VSTD::exchange(__other.__coro_, {})); + if (__oldHandle) + __oldHandle.destroy(); + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + bool valid() const _NOEXCEPT { return __coro_; } + + _LIBCPP_INLINE_VISIBILITY + bool ready() const _NOEXCEPT { return !__coro_ || __coro_.done(); } + + _LIBCPP_INLINE_VISIBILITY + auto operator co_await() & _NOEXCEPT { + class _Awaiter : public _AwaiterBase { + public: + using _AwaiterBase::_AwaiterBase; + _LIBCPP_INLINE_VISIBILITY + decltype(auto) await_resume() { + // FIXME: Should it be undefined-behaviour to co_await a task + // that is not valid() or should we throw std::future_error(broken_promise) + // or a std::logic_error{} of some sort? + if (!this->__coro_) +#ifndef _LIBCPP_NO_EXCEPTIONS + throw logic_error{"task is not valid()"}; +#else + terminate(); +#endif + return this->__coro_.promise().__lvalue_result(); + } + }; + return _Awaiter{__coro_}; + } + + _LIBCPP_INLINE_VISIBILITY + auto operator co_await() && _NOEXCEPT { + class _Awaiter : public _AwaiterBase { + public: + using _AwaiterBase::_AwaiterBase; + _LIBCPP_INLINE_VISIBILITY + decltype(auto) await_resume() { + // FIXME: Should it be undefined-behaviour to co_await a task + // that is not valid() or should we throw std::future_error(broken_promise) + // or a std::logic_error{} of some sort? + if (!this->__coro_) +#ifndef _LIBCPP_NO_EXCEPTIONS + throw logic_error{"task is not valid()"}; +#else + terminate(); +#endif + return this->__coro_.promise().__rvalue_result(); + } + }; + return _Awaiter{__coro_}; + } + + void swap(task& __other) _NOEXCEPT { _VSTD::swap(__coro_, __other.__coro_); } + +private: + _Handle __coro_; +}; + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_COROUTINES + + +#endif + +//////////////////////// +// Tests for task + +using namespace std::experimental; + +task f1() +{ + co_return; +} + +task f2() +{ + co_await f1(); +} + +task f3() +{ + task t = f1(); + co_await t; +} + +task g1() +{ + co_return 123; +} + +task g2() +{ + co_return co_await g1() + 100; +} + +task g3() +{ + // co_await rvalue returns rvalue reference to result. + { + decltype(auto) x = co_await g1(); + static_assert(std::is_same_v); + } + + // co_await lvalue returns lvalue reference to result + task t = g1(); + decltype(auto) y = co_await t; + static_assert(std::is_same_v); + + co_return y * 2; +} + +class MoveOnly +{ +public: + MoveOnly(); + MoveOnly(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) noexcept; + ~MoveOnly(); + int get() const; +private: + void* _data; +}; + +task h1(bool x) +{ + if (x) { + MoveOnly value; + co_return std::move(value); + } + co_return MoveOnly{}; +} + +task h2() +{ + auto x = co_await h1(true); + co_return x.get(); +} + +static int x; +static int y; + +task h3(bool cond) +{ + co_return cond ? x : y; +} + +task h3consumer() +{ + int& result = co_await h3(true); + result = 32; +} + +// custom allocator tests + +template +task a1(std::allocator_arg_t, Allocator allocator, bool x) +{ + co_return; +} + +class my_allocator +{ +public: + my_allocator(); + my_allocator(const my_allocator&) noexcept; + my_allocator(my_allocator&&) noexcept; + char* allocate(size_t n); + void deallocate(char* p, size_t n); +}; + +task a2() +{ + my_allocator alloc; + co_await a1(std::allocator_arg, alloc, true); +} + +task a2a() +{ + my_allocator alloc; + return a1(std::allocator_arg, alloc, true); +} + + +task a3() +{ + co_await a1(std::allocator_arg, std::allocator{}, false); +} + +task a3a() +{ + return a1(std::allocator_arg, std::allocator{}, false); +}