Index: include/CMakeLists.txt =================================================================== --- include/CMakeLists.txt +++ include/CMakeLists.txt @@ -86,6 +86,7 @@ experimental/string experimental/string_view experimental/system_error + experimental/task experimental/tuple experimental/type_traits experimental/unordered_map Index: include/experimental/__memory =================================================================== --- include/experimental/__memory +++ include/experimental/__memory @@ -73,6 +73,13 @@ > {}; +// 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 inline _LIBCPP_INLINE_VISIBILITY void __lfts_user_alloc_construct( Index: include/experimental/memory_resource =================================================================== --- include/experimental/memory_resource +++ include/experimental/memory_resource @@ -86,14 +86,6 @@ _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 { Index: include/experimental/task =================================================================== --- /dev/null +++ include/experimental/task @@ -0,0 +1,504 @@ +// -*- 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 +#include +#include + +#include +#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 + +template +class 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, size_t __size) _NOEXCEPT; + + template + 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 + 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 + 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(__frameStart) + + __get_dealloc_func_offset(__frameSize)); + } + + template + _LIBCPP_INLINE_VISIBILITY static _Alloc& + __get_allocator(void* __frameStart, size_t __frameSize) _NOEXCEPT { + return *reinterpret_cast<_Alloc*>( + static_cast(__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 + static void* operator new(size_t __size, allocator_arg_t, _Alloc& __alloc, + _Args&...) { + using _CharAlloc = + typename allocator_traits<_Alloc>::template rebind_alloc; + + _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 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(__pointer), + __paddedSize); + } else { + _CharAlloc __alloc; + __alloc.deallocate(static_cast(__pointer), __paddedSize); + } + }; + + // Copy the allocator into the heap frame (if required) + if constexpr (__allocator_needs_to_be_stored<_CharAlloc>) { + // task 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( + _VSTD::addressof(__get_allocator<_CharAlloc>(__pointer, __size)))) + _CharAlloc(_VSTD::move(__charAllocator)); + } + + return __pointer; + } + + template + 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 +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(&__exception_)) + exception_ptr(current_exception()); + __state_ = _State::__exception; +#else + _LIBCPP_ASSERT( + false, "task coroutine unexpectedly called unhandled_exception()"); +#endif + } + + // Only enable return_value() overload if _Tp is implicitly constructible from + // _Value + template ::value, int> = 0> + void return_value(_Value&& __value) + _NOEXCEPT_((is_nothrow_constructible_v<_Tp, _Value>)) { + __construct_value(static_cast<_Value&&>(__value)); + } + + template + 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> { + __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 + void __construct_value(_Args&&... __args) { + ::new (static_cast(_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 +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(&__exception_)) + exception_ptr(current_exception()); + __has_exception_ = true; +#else + _LIBCPP_ASSERT( + false, "task coroutine unexpectedly called unhandled_exception()"); +#endif + } + + void return_value(_Tp& __value) _NOEXCEPT { + ::new (static_cast(&__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 final : public __task_promise_base { + using _Handle = coroutine_handle<__task_promise>; + +public: + task 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 +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"); + 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"); + return _Awaiter{__coro_}; + } + +private: + friend class __task_promise<_Tp>; + + _LIBCPP_INLINE_VISIBILITY + task(_Handle __coro) _NOEXCEPT : __coro_(__coro) {} + + _Handle __coro_; +}; + +template +task<_Tp> __task_promise<_Tp>::get_return_object() _NOEXCEPT { + return task<_Tp>{_Handle::from_promise(*this)}; +} + +template +task<_Tp&> __task_promise<_Tp&>::get_return_object() _NOEXCEPT { + return task<_Tp&>{_Handle::from_promise(*this)}; +} + +task __task_promise::get_return_object() _NOEXCEPT { + return task{_Handle::from_promise(*this)}; +} + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_COROUTINES + +#endif Index: include/module.modulemap =================================================================== --- include/module.modulemap +++ include/module.modulemap @@ -579,6 +579,11 @@ header "experimental/string" export * } + module task { + requires coroutines + header "experimental/task" + export * + } module type_traits { header "experimental/type_traits" export * Index: test/std/experimental/task/awaitable_traits.hpp =================================================================== --- /dev/null +++ test/std/experimental/task/awaitable_traits.hpp @@ -0,0 +1,117 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_TEST_EXPERIMENTAL_TASK_AWAITABLE_TRAITS +#define _LIBCPP_TEST_EXPERIMENTAL_TASK_AWAITABLE_TRAITS + +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_COROUTINES + +template +struct __is_coroutine_handle : std::false_type {}; + +template +struct __is_coroutine_handle> : + std::true_type +{}; + +template +struct __is_valid_await_suspend_result : + std::disjunction< + std::is_void<_Tp>, + std::is_same<_Tp, bool>, + __is_coroutine_handle<_Tp>> +{}; + +template +struct is_awaiter : std::false_type {}; + +template +struct is_awaiter<_Tp, std::void_t< + decltype(std::declval<_Tp&>().await_ready()), + decltype(std::declval<_Tp&>().await_resume()), + decltype(std::declval<_Tp&>().await_suspend( + std::declval>()))>> : + std::conjunction< + std::is_same().await_ready()), bool>, + __is_valid_await_suspend_result().await_suspend( + std::declval>()))>> +{}; + +template +constexpr bool is_awaiter_v = is_awaiter<_Tp>::value; + +template +struct __has_member_operator_co_await : std::false_type {}; + +template +struct __has_member_operator_co_await<_Tp, std::void_t().operator co_await())>> +: is_awaiter().operator co_await())> +{}; + +template +struct __has_non_member_operator_co_await : std::false_type {}; + +template +struct __has_non_member_operator_co_await<_Tp, std::void_t()))>> +: is_awaiter()))> +{}; + +template +struct is_awaitable : std::disjunction< + is_awaiter<_Tp>, + __has_member_operator_co_await<_Tp>, + __has_non_member_operator_co_await<_Tp>> +{}; + +template +constexpr bool is_awaitable_v = is_awaitable<_Tp>::value; + +template< + typename _Tp, + std::enable_if_t, int> = 0> +decltype(auto) get_awaiter(_Tp&& __awaitable) +{ + if constexpr (__has_member_operator_co_await<_Tp>::value) + { + return static_cast<_Tp&&>(__awaitable).operator co_await(); + } + else if constexpr (__has_non_member_operator_co_await<_Tp>::value) + { + return operator co_await(static_cast<_Tp&&>(__awaitable)); + } + else + { + return static_cast<_Tp&&>(__awaitable); + } +} + +template +struct await_result +{}; + +template +struct await_result<_Tp, std::enable_if_t>> +{ +private: + using __awaiter = decltype(get_awaiter(std::declval<_Tp>())); +public: + using type = decltype(std::declval<__awaiter&>().await_resume()); +}; + +template +using await_result_t = typename await_result<_Tp>::type; + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_COROUTINES + +#endif Index: test/std/experimental/task/counted.hpp =================================================================== --- /dev/null +++ test/std/experimental/task/counted.hpp @@ -0,0 +1,96 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_TEST_EXPERIMENTAL_TASK_COUNTED +#define _LIBCPP_TEST_EXPERIMENTAL_TASK_COUNTED + +class counted +{ +public: + + counted() : id_(nextId_++) + { + ++defaultConstructedCount_; + } + + counted(const counted& other) : id_(other.id_) + { + ++copyConstructedCount_; + } + + counted(counted&& other) : id_(std::exchange(other.id_, 0)) + { + ++moveConstructedCount_; + } + + ~counted() + { + ++destructedCount_; + } + + static void reset() + { + nextId_ = 1; + defaultConstructedCount_ = 0; + copyConstructedCount_ = 0; + moveConstructedCount_ = 0; + destructedCount_ = 0; + } + + static std::size_t active_instance_count() + { + return + defaultConstructedCount_ + + copyConstructedCount_ + + moveConstructedCount_ - + destructedCount_; + } + + static std::size_t copy_constructor_count() + { + return copyConstructedCount_; + } + + static std::size_t move_constructor_count() + { + return moveConstructedCount_; + } + + static std::size_t default_constructor_count() + { + return defaultConstructedCount_; + } + + static std::size_t destructor_count() + { + return destructedCount_; + } + + std::size_t id() const { return id_; } + +private: + std::size_t id_; + + static std::size_t nextId_; + static std::size_t defaultConstructedCount_; + static std::size_t copyConstructedCount_; + static std::size_t moveConstructedCount_; + static std::size_t destructedCount_; + +}; + +#define DEFINE_COUNTED_VARIABLES() \ + std::size_t counted::nextId_; \ + std::size_t counted::defaultConstructedCount_; \ + std::size_t counted::copyConstructedCount_; \ + std::size_t counted::moveConstructedCount_; \ + std::size_t counted::destructedCount_ + +#endif Index: test/std/experimental/task/lit.local.cfg =================================================================== --- /dev/null +++ test/std/experimental/task/lit.local.cfg @@ -0,0 +1,9 @@ +# If the compiler doesn't support coroutines mark all of the tests under +# this directory as unsupported. Otherwise add the required `-fcoroutines-ts` +# flag. +if 'fcoroutines-ts' not in config.available_features: + config.unsupported = True +else: + import copy + config.test_format.cxx = copy.deepcopy(config.test_format.cxx) + config.test_format.cxx.compile_flags += ['-fcoroutines-ts'] Index: test/std/experimental/task/manual_reset_event.hpp =================================================================== --- /dev/null +++ test/std/experimental/task/manual_reset_event.hpp @@ -0,0 +1,127 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_TEST_EXPERIMENTAL_TASK_MANUAL_RESET_EVENT +#define _LIBCPP_TEST_EXPERIMENTAL_TASK_MANUAL_RESET_EVENT + +#include +#include + +// manual_reset_event is a coroutine synchronisation tool that allows one +// coroutine to await the event object and if the event was not crrently +// in the 'set' state then will suspend the awaiting coroutine until some +// thread calls .set() on the event. +class manual_reset_event +{ + friend class _Awaiter; + + class _Awaiter + { + public: + + _Awaiter(const manual_reset_event* __event) noexcept + : __event_(__event) + {} + + bool await_ready() const noexcept + { + return __event_->is_set(); + } + + bool await_suspend(std::experimental::coroutine_handle<> __coro) noexcept + { + _LIBCPP_ASSERT( + __event_->__state_.load(std::memory_order_relaxed) != + __State::__not_set_waiting_coroutine, + "This manual_reset_event already has another coroutine awaiting it. " + "Only one awaiting coroutine is supported." + ); + + __event_->__awaitingCoroutine_ = __coro; + + // If the compare-exchange fails then this means that the event was + // already 'set' and so we should not suspend - this code path requires + // 'acquire' semantics so we have visibility of writes prior to the + // .set() operation that transitioned the event to the 'set' state. + // If the compare-exchange succeeds then this needs 'release' semantics + // so that a subsequent call to .set() has visibility of our writes + // to the coroutine frame and to __event_->__awaitingCoroutine_ after + // reading our write to __event_->__state_. + _State oldState = _State::__not_set; + return __event_->__state_.compare_exchange_strong( + oldState, + _State::__not_set_waiting_coroutine, + std::memory_order_release, + std::memory_order_acquire); + } + + void await_resume() const noexcept {} + + private: + const manual_reset_event* __event_; + }; + +public: + + manual_reset_event(bool __initiallySet = false) noexcept + : __state_(__initiallySet ? _State::__set : _State::__not_set) + {} + + bool is_set() const noexcept + { + return __state_.load(std::memory_order_acquire) == _State::__set; + } + + void set() noexcept + { + // Needs to be 'acquire' in case the old value was a waiting coroutine + // so that we have visibility of the writes to the coroutine frame in + // the current thrad before we resume it. + // Also needs to be 'release' in case the old value was 'not-set' so that + // another thread that subsequently awaits the + _State oldState = __state_.exchange(_State::__set, std::memory_order_acq_rel); + if (oldState == _State::__not_set_waiting_coroutine) + { + _VSTD::exchange(__awaitingCoroutine_, {}).resume(); + } + } + + void reset() noexcept + { + _LIBCPP_ASSERT( + __state_.load(std::memory_order_relaxed) != _State::__not_set_waiting_coroutine, + "Illegal to call reset() if a coroutine is currently awaiting the event."); + + // Note, we use 'relaxed' memory order here since it considered a + // data-race to call reset() concurrently either with operator co_await() + // or with set(). + __state_.store(_State::__not_set, std::memory_order_relaxed); + } + + auto operator co_await() const noexcept + { + return _Awaiter{ this }; + } + +private: + + enum class _State { + __not_set, + __not_set_waiting_coroutine, + __set + }; + + // TODO: Can we combine these two members into a single std::atomic? + mutable std::atomic<_State> __state_; + mutable std::experimental::coroutine_handle<> __awaitingCoroutine_; + +}; + +#endif Index: test/std/experimental/task/sync_wait.hpp =================================================================== --- /dev/null +++ test/std/experimental/task/sync_wait.hpp @@ -0,0 +1,284 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_TEST_EXPERIMENTAL_TASK_SYNC_WAIT +#define _LIBCPP_TEST_EXPERIMENTAL_TASK_SYNC_WAIT + +#include +#include +#include +#include +#include + +#include "awaitable_traits.hpp" + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_COROUTINES + +// Thread-synchronisation helper that allows one thread to block in a call +// to .wait() until another thread signals the thread by calling .set(). +class __oneshot_event +{ +public: + __oneshot_event() : __isSet_(false) {} + + void set() noexcept + { + unique_lock __lock{ __mutex_ }; + __isSet_ = true; + __cv_.notify_all(); + } + + void wait() noexcept + { + unique_lock __lock{ __mutex_ }; + __cv_.wait(__lock, [this] { return __isSet_; }); + } + +private: + mutex __mutex_; + condition_variable __cv_; + bool __isSet_; +}; + +template +class __sync_wait_promise_base +{ +public: + + using __handle_t = coroutine_handle<_Derived>; + +private: + + struct _FinalAwaiter + { + bool await_ready() noexcept { return false; } + void await_suspend(__handle_t __coro) noexcept + { + __sync_wait_promise_base& __promise = __coro.promise(); + __promise.__event_.set(); + } + void await_resume() noexcept {} + }; + + friend struct _FinalAwaiter; + +public: + + __handle_t get_return_object() { return __handle(); } + suspend_always initial_suspend() { return {}; } + _FinalAwaiter final_suspend() { return {}; } + +private: + + __handle_t __handle() noexcept + { + return __handle_t::from_promise(static_cast<_Derived&>(*this)); + } + +protected: + + // Start the coroutine and then block waiting for it to finish. + void run() noexcept + { + __handle().resume(); + __event_.wait(); + } + +private: + + __oneshot_event __event_; + +}; + +template +class __sync_wait_promise final + : public __sync_wait_promise_base<__sync_wait_promise<_Tp>> +{ +public: + + __sync_wait_promise() : __state_(_State::__empty) {} + + ~__sync_wait_promise() + { + switch (__state_) + { + case _State::__empty: + case _State::__value: + break; +#ifndef _LIBCPP_NO_EXCEPTIONS + case _State::__exception: + __exception_.~exception_ptr(); + break; +#endif + } + } + + void return_void() noexcept + { + // Should be unreachable since coroutine should always + // suspend at `co_yield` point where it will be destroyed + // or will fail with an exception and bypass return_void() + // and call unhandled_exception() instead. + std::abort(); + } + + void unhandled_exception() noexcept + { +#ifndef _LIBCPP_NO_EXCEPTIONS + ::new (static_cast(&__exception_)) exception_ptr( + std::current_exception()); + __state_ = _State::__exception; +#else + _VSTD::abort(); +#endif + } + + auto yield_value(_Tp&& __value) noexcept + { + __valuePtr_ = std::addressof(__value); + __state_ = _State::__value; + return this->final_suspend(); + } + + _Tp&& get() + { + this->run(); + +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__state_ == _State::__exception) + { + std::rethrow_exception(_VSTD::move(__exception_)); + } +#endif + + return static_cast<_Tp&&>(*__valuePtr_); + } + +private: + + enum class _State { + __empty, + __value, + __exception + }; + + _State __state_; + union { + std::add_pointer_t<_Tp> __valuePtr_; + std::exception_ptr __exception_; + }; + +}; + +template<> +struct __sync_wait_promise final + : public __sync_wait_promise_base<__sync_wait_promise> +{ +public: + + void unhandled_exception() noexcept + { +#ifndef _LIBCPP_NO_EXCEPTIONS + __exception_ = std::current_exception(); +#endif + } + + void return_void() noexcept {} + + void get() + { + this->run(); + +#ifndef _LIBCPP_NO_EXCEPTIONS + if (__exception_) + { + std::rethrow_exception(_VSTD::move(__exception_)); + } +#endif + } + +private: + + std::exception_ptr __exception_; + +}; + +template +class __sync_wait_task final +{ +public: + using promise_type = __sync_wait_promise<_Tp>; + +private: + using __handle_t = typename promise_type::__handle_t; + +public: + + __sync_wait_task(__handle_t __coro) noexcept : __coro_(__coro) {} + + ~__sync_wait_task() + { + _LIBCPP_ASSERT(__coro_, "Should always have a valid coroutine handle"); + __coro_.destroy(); + } + + decltype(auto) get() + { + return __coro_.promise().get(); + } +private: + __handle_t __coro_; +}; + +template +struct __remove_rvalue_reference +{ + using type = _Tp; +}; + +template +struct __remove_rvalue_reference<_Tp&&> +{ + using type = _Tp; +}; + +template +using __remove_rvalue_reference_t = + typename __remove_rvalue_reference<_Tp>::type; + +template< + typename _Awaitable, + typename _AwaitResult = await_result_t<_Awaitable>, + std::enable_if_t, int> = 0> +__sync_wait_task<_AwaitResult> __make_sync_wait_task(_Awaitable&& __awaitable) +{ + co_await static_cast<_Awaitable&&>(__awaitable); +} + +template< + typename _Awaitable, + typename _AwaitResult = await_result_t<_Awaitable>, + std::enable_if_t, int> = 0> +__sync_wait_task<_AwaitResult> __make_sync_wait_task(_Awaitable&& __awaitable) +{ + co_yield co_await static_cast<_Awaitable&&>(__awaitable); +} + +template +auto sync_wait(_Awaitable&& __awaitable) + -> __remove_rvalue_reference_t> +{ + return _VSTD_CORO::__make_sync_wait_task( + static_cast<_Awaitable&&>(__awaitable)).get(); +} + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_COROUTINES + +#endif Index: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp @@ -0,0 +1,230 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11, c++14 + +#include +#include +#include +#include +#include +#include + +#include "../sync_wait.hpp" + +namespace coro = std::experimental::coroutines_v1; + +namespace +{ + static size_t allocator_instance_count = 0; + + // A custom allocator that tracks the number of allocator instances that + // have been constructed/destructed as well as the number of bytes that + // have been allocated/deallocated using the allocator. + template + class my_allocator { + public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using is_always_equal = std::false_type; + + explicit my_allocator( + std::shared_ptr totalAllocated) noexcept + : totalAllocated_(std::move(totalAllocated)) + { + ++allocator_instance_count; + assert(totalAllocated_); + } + + my_allocator(const my_allocator& other) + : totalAllocated_(other.totalAllocated_) + { + ++allocator_instance_count; + } + + my_allocator(my_allocator&& other) + : totalAllocated_(std::move(other.totalAllocated_)) + { + ++allocator_instance_count; + } + + template + my_allocator(const my_allocator& other) + : totalAllocated_(other.totalAllocated_) + { + ++allocator_instance_count; + } + + template + my_allocator(my_allocator&& other) + : totalAllocated_(std::move(other.totalAllocated_)) + { + ++allocator_instance_count; + } + + ~my_allocator() + { + --allocator_instance_count; + } + + char* allocate(size_t n) { + const auto byteCount = n * sizeof(T); + void* p = std::malloc(byteCount); + if (!p) { + throw std::bad_alloc{}; + } + *totalAllocated_ += byteCount; + return static_cast(p); + } + + void deallocate(char* p, size_t n) { + const auto byteCount = n * sizeof(T); + *totalAllocated_ -= byteCount; + std::free(p); + } + private: + template + friend class my_allocator; + + std::shared_ptr totalAllocated_; + }; +} + +template +coro::task f(std::allocator_arg_t, [[maybe_unused]] Allocator alloc) +{ + co_return; +} + +void test_custom_allocator_is_destructed() +{ + auto totalAllocated = std::make_shared(0); + + assert(allocator_instance_count == 0); + + { + std::vector> tasks; + tasks.push_back( + f(std::allocator_arg, my_allocator{ totalAllocated })); + tasks.push_back( + f(std::allocator_arg, my_allocator{ totalAllocated })); + + assert(allocator_instance_count == 4); + assert(*totalAllocated > 0); + } + + assert(allocator_instance_count == 0); + assert(*totalAllocated == 0); +} + +void test_custom_allocator_type_rebinding() +{ + auto totalAllocated = std::make_shared(0); + { + std::vector> tasks; + tasks.emplace_back( + f(std::allocator_arg, my_allocator{ totalAllocated })); + coro::sync_wait(tasks[0]); + } + assert(*totalAllocated == 0); + assert(allocator_instance_count == 0); +} + +void test_mixed_custom_allocator_type_erasure() +{ + assert(allocator_instance_count == 0); + + // Show that different allocators can be used within a vector of tasks + // of the same type. ie. that the allocator is type-erased inside the + // coroutine. + std::vector> tasks; + tasks.push_back(f( + std::allocator_arg, std::allocator{})); + tasks.push_back(f( + std::allocator_arg, + std::experimental::pmr::polymorphic_allocator{ + std::experimental::pmr::new_delete_resource() })); + tasks.push_back(f( + std::allocator_arg, + my_allocator{ std::make_shared(0) })); + + assert(allocator_instance_count > 0); + + for (auto& t : tasks) + { + coro::sync_wait(t); + } + + tasks.clear(); + + assert(allocator_instance_count == 0); +} + +template +coro::task add_async(std::allocator_arg_t, [[maybe_unused]] Allocator alloc, int a, int b) +{ + co_return a + b; +} + +void test_task_custom_allocator_with_extra_args() +{ + std::vector> tasks; + + for (int i = 0; i < 5; ++i) { + tasks.push_back(add_async( + std::allocator_arg, + std::allocator{}, + i, 2 * i)); + } + + for (int i = 0; i < 5; ++i) + { + assert(sync_wait(std::move(tasks[i])) == 3 * i); + } +} + +struct some_type { + template + coro::task get_async(std::allocator_arg_t, [[maybe_unused]] Allocator alloc) { + co_return 42; + } + + template + coro::task add_async(std::allocator_arg_t, [[maybe_unused]] Allocator alloc, int a, int b) { + co_return a + b; + } +}; + +void test_task_custom_allocator_on_member_function() +{ + assert(allocator_instance_count == 0); + + auto totalAllocated = std::make_shared(0); + some_type obj; + assert(sync_wait(obj.get_async(std::allocator_arg, std::allocator{})) == 42); + assert(sync_wait(obj.get_async(std::allocator_arg, my_allocator{totalAllocated})) == 42); + assert(sync_wait(obj.add_async(std::allocator_arg, std::allocator{}, 2, 3)) == 5); + assert(sync_wait(obj.add_async(std::allocator_arg, my_allocator{totalAllocated}, 2, 3)) == 5); + + assert(allocator_instance_count == 0); + assert(*totalAllocated == 0); +} + +int main() +{ + test_custom_allocator_is_destructed(); + test_custom_allocator_type_rebinding(); + test_mixed_custom_allocator_type_erasure(); + test_task_custom_allocator_with_extra_args(); + test_task_custom_allocator_on_member_function(); + + return 0; +} Index: test/std/experimental/task/task.basic/task_of_value.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/task/task.basic/task_of_value.pass.cpp @@ -0,0 +1,70 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11, c++14 + +#include +#include +#include +#include +#include "../sync_wait.hpp" + +void test_returning_move_only_type() +{ + auto move_only_async = + [](bool x) -> std::experimental::task> { + if (x) { + auto p = std::make_unique(123); + co_return p; // Should be implicit std::move(p) here. + } + + co_return std::make_unique(456); + }; + + assert(*sync_wait(move_only_async(true)) == 123); + assert(*sync_wait(move_only_async(false)) == 456); +} + +void test_co_return_with_curly_braces() +{ + auto t = []() -> std::experimental::task> + { + co_return { 123, "test" }; + }(); + + auto result = sync_wait(std::move(t)); + + assert(std::get<0>(result) == 123); + assert(std::get<1>(result) == "test"); +} + +void test_co_return_by_initialiser_list() +{ + auto t = []() -> std::experimental::task> + { + co_return { 2, 10, -1 }; + }(); + + auto result = sync_wait(std::move(t)); + + assert(result.size() == 3); + assert(result[0] == 2); + assert(result[1] == 10); + assert(result[2] == -1); +} + +int main() +{ + test_returning_move_only_type(); + test_co_return_with_curly_braces(); + test_co_return_by_initialiser_list(); + + return 0; +} Index: test/std/experimental/task/task.basic/task_of_void.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/task/task.basic/task_of_void.pass.cpp @@ -0,0 +1,96 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11, c++14 + +#include +#include "../manual_reset_event.hpp" +#include "../sync_wait.hpp" + +#include +#include + +namespace coro = std::experimental::coroutines_v1; + +static bool has_f_executed = false; + +static coro::task f() +{ + has_f_executed = true; + co_return; +} + +static void test_coroutine_executes_lazily() +{ + coro::task t = f(); + assert(!has_f_executed); + coro::sync_wait(t); + assert(has_f_executed); +} + +static std::optional last_value_passed_to_g; + +static coro::task g(int a) +{ + last_value_passed_to_g = a; + co_return; +} + +void test_coroutine_accepts_arguments() +{ + auto t = g(123); + assert(!last_value_passed_to_g); + coro::sync_wait(t); + assert(last_value_passed_to_g); + assert(*last_value_passed_to_g == 123); +} + +int shared_value = 0; +int read_value = 0; + +coro::task consume_async(manual_reset_event& event) +{ + co_await event; + read_value = shared_value; +} + +void produce(manual_reset_event& event) +{ + shared_value = 101; + event.set(); +} + +void test_async_completion() +{ + manual_reset_event e; + std::thread t1{ [&e] + { + sync_wait(consume_async(e)); + }}; + + assert(read_value == 0); + + std::thread t2{ [&e] { produce(e); }}; + + t1.join(); + + assert(read_value == 101); + + t2.join(); +} + +int main() +{ + test_coroutine_executes_lazily(); + test_coroutine_accepts_arguments(); + test_async_completion(); + + return 0; +} Index: test/std/experimental/task/task.lifetime/task_parameter_lifetime.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/task/task.lifetime/task_parameter_lifetime.pass.cpp @@ -0,0 +1,57 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11, c++14 + +#include +#include + +#include "../counted.hpp" +#include "../sync_wait.hpp" + +DEFINE_COUNTED_VARIABLES(); + +void test_parameter_lifetime() +{ + counted::reset(); + + auto f = [](counted c) -> std::experimental::task + { + co_return c.id(); + }; + + { + auto t = f({}); + + assert(counted::active_instance_count() == 1); + assert(counted::copy_constructor_count() == 0); + assert(counted::move_constructor_count() <= 2); // Ideally <= 1 + + auto id = sync_wait(t); + assert(id == 1); + + assert(counted::active_instance_count() == 1); + assert(counted::copy_constructor_count() == 0); + + // We are relying on C++17 copy-elision when passing the temporary counter + // into f(). Then f() must move the parameter into the coroutine frame by + // calling the move-constructor. This move could also potentially be + // elided by the + assert(counted::move_constructor_count() <= 1); + } + + assert(counted::active_instance_count() == 0); +} + +int main() +{ + test_parameter_lifetime(); + return 0; +} Index: test/std/experimental/task/task.lifetime/task_return_value_lifetime.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/task/task.lifetime/task_return_value_lifetime.pass.cpp @@ -0,0 +1,86 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11, c++14 + +#include +#include +#include + +#include "../counted.hpp" +#include "../sync_wait.hpp" + +DEFINE_COUNTED_VARIABLES(); + +void test_return_value_lifetime() +{ + counted::reset(); + + auto f = [](bool x) -> std::experimental::task + { + if (x) { + counted c; + co_return std::move(c); + } + co_return {}; + }; + + { + auto t = f(true); + + assert(counted::active_instance_count() == 0); + assert(counted::copy_constructor_count() == 0); + assert(counted::move_constructor_count() == 0); + + { + auto c = sync_wait(std::move(t)); + assert(c.id() == 1); + + assert(counted::active_instance_count() == 2); + assert(counted::copy_constructor_count() == 0); + assert(counted::move_constructor_count() > 0); + assert(counted::default_constructor_count() == 1); + } + + // The result value in 't' is still alive until 't' destructs. + assert(counted::active_instance_count() == 1); + } + + assert(counted::active_instance_count() == 0); + + counted::reset(); + + { + auto t = f(false); + + assert(counted::active_instance_count() == 0); + assert(counted::copy_constructor_count() == 0); + assert(counted::move_constructor_count() == 0); + + { + auto c = sync_wait(std::move(t)); + assert(c.id() == 1); + + assert(counted::active_instance_count() == 2); + assert(counted::copy_constructor_count() == 0); + assert(counted::move_constructor_count() > 0); + assert(counted::default_constructor_count() == 1); + } + + // The result value in 't' is still alive until 't' destructs. + assert(counted::active_instance_count() == 1); + } +} + +int main() +{ + test_return_value_lifetime(); + return 0; +}