summaryrefslogtreecommitdiffstats
path: root/libcxx/include/experimental
diff options
context:
space:
mode:
authorBrian Gesiak <modocache@gmail.com>2019-03-26 17:46:06 +0000
committerBrian Gesiak <modocache@gmail.com>2019-03-26 17:46:06 +0000
commit57839425aa4802986acbb17392ca697ee76aa633 (patch)
tree9e173f9d545ee0dd2a54e5a7fca96a216130cc39 /libcxx/include/experimental
parent52221d56bcf3d087bb88d44b56c682fd27f59a13 (diff)
downloadbcm5719-llvm-57839425aa4802986acbb17392ca697ee76aa633.tar.gz
bcm5719-llvm-57839425aa4802986acbb17392ca697ee76aa633.zip
[coroutines] Add std::experimental::task<T> type
Summary: Adds the coroutine `std::experimental::task<T>` type described in proposal P1056R0. See https://wg21.link/P1056R0. This implementation allows customization of the allocator used to allocate the coroutine frame by passing std::allocator_arg as the first argument, followed by the allocator to use. This supports co_awaiting the same task multiple times. The second and subsequent times it returns a reference to the already-computed value. This diff also adds some implementations of other utilities that have potential for standardization as helpers within the test/... area: - `sync_wait(awaitable)` - See P1171R0 - `manual_reset_event` Move the definition of the __aligned_allocation_size helper function from <experimental/memory_resource> to <experimental/__memory> so it can be more widely used without pulling in memory_resource. Outstanding work: - Use C++14 keywords directly rather than macro versions eg. use `noexcept` instead of `_NOEXCEPT`). - Add support for overaligned coroutine frames. This may need wording in the Coroutines TS to support passing the extra `std::align_val_t`. - Eliminate use of `if constexpr` if we want it to compile under C++14. Patch by @lewissbaker (Lewis Baker). llvm-svn: 357010
Diffstat (limited to 'libcxx/include/experimental')
-rw-r--r--libcxx/include/experimental/__memory7
-rw-r--r--libcxx/include/experimental/memory_resource8
-rw-r--r--libcxx/include/experimental/task503
3 files changed, 510 insertions, 8 deletions
diff --git a/libcxx/include/experimental/__memory b/libcxx/include/experimental/__memory
index 4cf8978468c..6da6bef663a 100644
--- a/libcxx/include/experimental/__memory
+++ b/libcxx/include/experimental/__memory
@@ -73,6 +73,13 @@ struct __lfts_uses_alloc_ctor
>
{};
+// Round __s up to next multiple of __a.
+inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
+size_t __aligned_allocation_size(size_t __s, size_t __a) _NOEXCEPT
+{
+ return (__s + __a - 1) & ~(__a - 1);
+}
+
template <class _Tp, class _Alloc, class ..._Args>
inline _LIBCPP_INLINE_VISIBILITY
void __lfts_user_alloc_construct(
diff --git a/libcxx/include/experimental/memory_resource b/libcxx/include/experimental/memory_resource
index f999fb9befd..897bd1f8882 100644
--- a/libcxx/include/experimental/memory_resource
+++ b/libcxx/include/experimental/memory_resource
@@ -86,14 +86,6 @@ _LIBCPP_PUSH_MACROS
_LIBCPP_BEGIN_NAMESPACE_LFTS_PMR
-// Round __s up to next multiple of __a.
-inline _LIBCPP_INLINE_VISIBILITY
-size_t __aligned_allocation_size(size_t __s, size_t __a) _NOEXCEPT
-{
- _LIBCPP_ASSERT(__s + __a > __s, "aligned allocation size overflows");
- return (__s + __a - 1) & ~(__a - 1);
-}
-
// 8.5, memory.resource
class _LIBCPP_TYPE_VIS memory_resource
{
diff --git a/libcxx/include/experimental/task b/libcxx/include/experimental/task
new file mode 100644
index 00000000000..2bdcaf2ba34
--- /dev/null
+++ b/libcxx/include/experimental/task
@@ -0,0 +1,503 @@
+// -*- 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
OpenPOWER on IntegriCloud