diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 3314aab65..74c5f383b 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -8,6 +8,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/any ${CMAKE_CURRENT_LIST_DIR}/inc/array ${CMAKE_CURRENT_LIST_DIR}/inc/atomic + ${CMAKE_CURRENT_LIST_DIR}/inc/barrier ${CMAKE_CURRENT_LIST_DIR}/inc/bit ${CMAKE_CURRENT_LIST_DIR}/inc/bitset ${CMAKE_CURRENT_LIST_DIR}/inc/cassert @@ -153,6 +154,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/iso646.h ${CMAKE_CURRENT_LIST_DIR}/inc/istream ${CMAKE_CURRENT_LIST_DIR}/inc/iterator + ${CMAKE_CURRENT_LIST_DIR}/inc/latch ${CMAKE_CURRENT_LIST_DIR}/inc/limits ${CMAKE_CURRENT_LIST_DIR}/inc/list ${CMAKE_CURRENT_LIST_DIR}/inc/locale @@ -171,6 +173,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/ratio ${CMAKE_CURRENT_LIST_DIR}/inc/regex ${CMAKE_CURRENT_LIST_DIR}/inc/scoped_allocator + ${CMAKE_CURRENT_LIST_DIR}/inc/semaphore ${CMAKE_CURRENT_LIST_DIR}/inc/set ${CMAKE_CURRENT_LIST_DIR}/inc/shared_mutex ${CMAKE_CURRENT_LIST_DIR}/inc/span diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index c7aaea945..3ba146c97 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -105,6 +105,9 @@ #ifndef _M_CEE_PURE #include +#include +#include +#include #endif // _M_CEE_PURE #ifndef _M_CEE diff --git a/stl/inc/barrier b/stl/inc/barrier new file mode 100644 index 000000000..2258cfe26 --- /dev/null +++ b/stl/inc/barrier @@ -0,0 +1,198 @@ +// barrier standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _BARRIER_ +#define _BARRIER_ +#include +#if _STL_COMPILER_PREPROCESSOR + +#ifdef _M_CEE_PURE +#error is not supported when compiling with /clr:pure. +#endif // _M_CEE_PURE + +#if !_HAS_CXX20 +#pragma message("The contents of are available only with C++20 or later.") +#else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv + +#include +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +struct _No_completion_function { + void operator()() noexcept {} +}; + +template +class barrier; + +inline constexpr ptrdiff_t _Barrier_arrival_token_mask = 1; +inline constexpr ptrdiff_t _Barrier_value_mask = ~_Barrier_arrival_token_mask; +inline constexpr ptrdiff_t _Barrier_value_shift = 1; +inline constexpr ptrdiff_t _Barrier_invalid_token = 0; +inline constexpr ptrdiff_t _Barrier_value_step = 1 << _Barrier_value_shift; +inline constexpr ptrdiff_t _Barrier_max = (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 2)) - 1; + +template +class _Arrival_token { +public: + _Arrival_token(_Arrival_token&& _Other) noexcept { + _Value = _Other._Value; + _Other._Value = _Barrier_invalid_token; + } + + _Arrival_token& operator=(_Arrival_token&& _Other) noexcept { + _Value = _Other._Value; + _Other._Value = _Barrier_invalid_token; + return *this; + } + +private: + explicit _Arrival_token(ptrdiff_t _Value_) noexcept : _Value(_Value_) {} + friend barrier<_Completion_function>; + + ptrdiff_t _Value; +}; + +template +class barrier { +public: + static_assert( +#ifndef __cpp_noexcept_function_type + is_function_v> || +#endif // __cpp_noexcept_function_type + is_nothrow_invocable_v<_Completion_function&>, + "N4861 [thread.barrier.class]/5: is_nothrow_invocable_v shall be true"); + + using arrival_token = _Arrival_token<_Completion_function>; + + constexpr explicit barrier( + const ptrdiff_t _Expected, _Completion_function _Fn = _Completion_function()) noexcept /* strengthened */ + : _Val(_One_then_variadic_args_t{}, _STD move(_Fn), _Expected << _Barrier_value_shift) { + _STL_VERIFY(_Val._Myval2._Current.load(memory_order_relaxed) >= 0, + "Precondition: expected >= 0 and expected <= max() (N4861 [thread.barrier.class]/9)"); + } + + barrier(const barrier&) = delete; + barrier& operator=(const barrier&) = delete; + + _NODISCARD static constexpr ptrdiff_t(max)() noexcept { + return _Barrier_max; + } + + _NODISCARD arrival_token arrive(ptrdiff_t _Update = 1) noexcept /* strengthened */ { + // Shifting before precondition check, so that exceeding max() will trigger precondition check too + _Update <<= _Barrier_value_shift; + _STL_VERIFY(_Update > 0, "Precondition: update > 0 (N4861 [thread.barrier.class]/12)"); + // TRANSITION, GH-1133: should be memory_order_release + ptrdiff_t _Current = _Val._Myval2._Current.fetch_sub(_Update) - _Update; + _STL_VERIFY(_Current >= 0, "Precondition: update is less than or equal to the expected count " + "for the current barrier phase (N4861 [thread.barrier.class]/12)"); + if ((_Current & _Barrier_value_mask) == 0) { + // TRANSITION, GH-1133: should have this fence: + // atomic_thread_fence(memory_order_acquire); + _Completion(_Current); + } + // Embedding this into the token to provide an additional correctness check that the token is from the same + // barrier and wasn't used. All bits of this fit, as barrier should be aligned to at least the size of an + // atomic counter. + return arrival_token{(_Current & _Barrier_arrival_token_mask) | reinterpret_cast(this)}; + } + + void wait(arrival_token&& _Arrival) const noexcept /* strengthened */ { + _STL_VERIFY((_Arrival._Value & _Barrier_value_mask) == reinterpret_cast(this), + "Preconditions: arrival is associated with the phase synchronization point for the current phase " + "or the immediately preceding phase of the same barrier object (N4861 [thread.barrier.class]/19)"); + const ptrdiff_t _Arrival_value = _Arrival._Value & _Barrier_arrival_token_mask; + _Arrival._Value = _Barrier_invalid_token; + for (;;) { + // TRANSITION, GH-1133: should be memory_order_acquire + const ptrdiff_t _Current = _Val._Myval2._Current.load(); + _STL_VERIFY(_Current >= 0, "Invariant counter >= 0, possibly caused by preconditions violation " + "(N4861 [thread.barrier.class]/12)"); + if ((_Current & _Barrier_arrival_token_mask) != _Arrival_value) { + break; + } + _Val._Myval2._Current.wait(_Current, memory_order_relaxed); + } + } + + void arrive_and_wait() noexcept /* strengthened */ { + // TRANSITION, GH-1133: should be memory_order_acq_rel + ptrdiff_t _Current = _Val._Myval2._Current.fetch_sub(_Barrier_value_step) - _Barrier_value_step; + const ptrdiff_t _Arrival = _Current & _Barrier_arrival_token_mask; + _STL_VERIFY(_Current >= 0, "Precondition: update is less than or equal to the expected count " + "for the current barrier phase (N4861 [thread.barrier.class]/12)"); + if ((_Current & _Barrier_value_mask) == 0) { + _Completion(_Current); + return; + } + + for (;;) { + _Val._Myval2._Current.wait(_Current, memory_order_relaxed); + // TRANSITION, GH-1133: should be memory_order_acquire + _Current = _Val._Myval2._Current.load(); + _STL_VERIFY(_Current >= 0, "Invariant counter >= 0, possibly caused by preconditions violation " + "(N4861 [thread.barrier.class]/12)"); + if ((_Current & _Barrier_arrival_token_mask) != _Arrival) { + break; + } + } + } + + void arrive_and_drop() noexcept /* strengthened */ { + const ptrdiff_t _Rem_count = + _Val._Myval2._Total.fetch_sub(_Barrier_value_step, memory_order_relaxed) - _Barrier_value_step; + _STL_VERIFY(_Rem_count >= 0, "Precondition: The expected count for the current barrier phase " + "is greater than zero (N4861 [thread.barrier.class]/24) " + "(checked initial expected count, which is not less than the current)"); + (void) arrive(1); + } + +private: + void _Completion(const ptrdiff_t _Current) noexcept { + const ptrdiff_t _Rem_count = _Val._Myval2._Total.load(memory_order_relaxed); + _STL_VERIFY(_Rem_count >= 0, "Invariant: initial expected count less than zero, " + "possibly caused by preconditions violation " + "(N4861 [thread.barrier.class]/24)"); + _Val._Get_first()(); + const ptrdiff_t _New_phase_count = _Rem_count | ((_Current + 1) & _Barrier_arrival_token_mask); + // TRANSITION, GH-1133: should be memory_order_release + _Val._Myval2._Current.store(_New_phase_count); + _Val._Myval2._Current.notify_all(); + } + + struct _Counter_t { + constexpr explicit _Counter_t(ptrdiff_t _Initial) : _Current(_Initial), _Total(_Initial) {} + // wait(arrival_token&&) accepts a token from the current phase or the immediately preceding phase; this means + // we can track which phase is the current phase using 1 bit which alternates between each phase. For this + // purpose we use the low order bit of _Current. + atomic _Current; + atomic _Total; + }; + + _Compressed_pair<_Completion_function, _Counter_t> _Val; +}; + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // ^^^ _HAS_CXX20 ^^^ + +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _BARRIER_ diff --git a/stl/inc/latch b/stl/inc/latch new file mode 100644 index 000000000..3743af764 --- /dev/null +++ b/stl/inc/latch @@ -0,0 +1,101 @@ +// latch standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _LATCH_ +#define _LATCH_ +#include +#if _STL_COMPILER_PREPROCESSOR + +#ifdef _M_CEE_PURE +#error is not supported when compiling with /clr:pure. +#endif // _M_CEE_PURE + +#if !_HAS_CXX20 +#pragma message("The contents of are available only with C++20 or later.") +#else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv + +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +class latch { +public: + _NODISCARD static constexpr ptrdiff_t(max)() noexcept { + return (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 1)) - 1; + } + + constexpr explicit latch(const ptrdiff_t _Expected) noexcept /* strengthened */ : _Counter{_Expected} { + _STL_VERIFY(_Expected >= 0, "Precondition: expected >= 0 (N4861 [thread.latch.class]/4)"); + } + + latch(const latch&) = delete; + latch& operator=(const latch&) = delete; + + void count_down(const ptrdiff_t _Update = 1) noexcept /* strengthened */ { + _STL_VERIFY(_Update >= 0, "Precondition: update >= 0 (N4861 [thread.latch.class]/7)"); + // TRANSITION, GH-1133: should be memory_order_release + const ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update; + if (_Current == 0) { + _Counter.notify_all(); + } else { + _STL_VERIFY(_Current >= 0, "Precondition: update <= counter (N4861 [thread.latch.class]/7)"); + } + } + + _NODISCARD bool try_wait() const noexcept { + // TRANSITION, GH-1133: should be memory_order_acquire + return _Counter.load() == 0; + } + + void wait() const noexcept /* strengthened */ { + for (;;) { + // TRANSITION, GH-1133: should be memory_order_acquire + const ptrdiff_t _Current = _Counter.load(); + if (_Current == 0) { + return; + } else { + _STL_VERIFY(_Current > 0, "Invariant counter >= 0, possibly caused by preconditions violation " + "(N4861 [thread.latch.class]/7)"); + } + _Counter.wait(_Current, memory_order_relaxed); + } + } + + void arrive_and_wait(const ptrdiff_t _Update = 1) noexcept /* strengthened */ { + _STL_VERIFY(_Update >= 0, "Precondition: update >= 0 (N4861 [thread.latch.class]/7)"); + // TRANSITION, GH-1133: should be memory_order_acq_rel + const ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update; + if (_Current == 0) { + _Counter.notify_all(); + } else { + _STL_VERIFY(_Current > 0, "Precondition: update <= counter (N4861 [thread.latch.class]/7)"); + _Counter.wait(_Current, memory_order_relaxed); + wait(); + } + } + +private: + atomic _Counter; +}; + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // ^^^ _HAS_CXX20 ^^^ + +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _LATCH_ diff --git a/stl/inc/semaphore b/stl/inc/semaphore new file mode 100644 index 000000000..0845bdf00 --- /dev/null +++ b/stl/inc/semaphore @@ -0,0 +1,311 @@ +// semaphore standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _SEMAPHORE_ +#define _SEMAPHORE_ +#include +#if _STL_COMPILER_PREPROCESSOR + +#ifdef _M_CEE_PURE +#error is not supported when compiling with /clr:pure. +#endif // _M_CEE_PURE + +#if !_HAS_CXX20 +#pragma message("The contents of are available only with C++20 or later.") +#else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv + +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +template +_NODISCARD unsigned long long _Semaphore_deadline(const chrono::duration<_Rep, _Period>& _Rel_time) { + return __std_atomic_wait_get_deadline( + chrono::duration_cast>(_Rel_time).count()); +} + +template +_NODISCARD unsigned long _Semaphore_remaining_timeout(const chrono::time_point<_Clock, _Duration>& _Abs_time) { + const auto _Now = _Clock::now(); + if (_Now >= _Abs_time) { + return 0; + } + + const auto _Rel_time = chrono::ceil(_Abs_time - _Now); + static constexpr chrono::milliseconds _Ten_days{chrono::hours{24 * 10}}; + static_assert(_Ten_days.count() < ULONG_MAX, "Bad sizing assumption"); + if (_Rel_time >= _Ten_days) { + return static_cast(_Ten_days.count()); + } + + return static_cast(_Rel_time.count()); +} + +inline constexpr ptrdiff_t _Semaphore_max = (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 1)) - 1; + +template +class counting_semaphore { +public: + _NODISCARD static constexpr ptrdiff_t(max)() noexcept { + return _Least_max_value; + } + + constexpr explicit counting_semaphore(const ptrdiff_t _Desired) noexcept /* strengthened */ + : _Counter(_Desired) { + _STL_VERIFY(_Desired >= 0 && _Desired <= _Least_max_value, + "Precondition: desired >= 0, and desired <= max() (N4861 [thread.sema.cnt]/5)"); + } + + counting_semaphore(const counting_semaphore&) = delete; + counting_semaphore& operator=(const counting_semaphore&) = delete; + + void release(ptrdiff_t _Update = 1) noexcept /* strengthened */ { + if (_Update == 0) { + return; + } + _STL_VERIFY(_Update > 0 && _Update <= _Least_max_value, + "Precondition: update >= 0, and update <= max() - counter (N4861 [thread.sema.cnt]/8)"); + + // We need to notify (wake) at least _Update waiting threads. + // Errors towards waking more cannot be always avoided, but they are performance issues. + // Errors towards waking fewer must be avoided, as they are correctness issues. + + // release thread: Increment semaphore counter, then load waiting counter; + // acquire thread: Increment waiting counter, then load semaphore counter; + + // memory_order_seq_cst for all four operations guarantees that the release thread loads + // the incremented value, or the acquire thread loads the incremented value, or both, but not neither. + // memory_order_seq_cst might be superfluous for some hardware mappings of the C++ memory model, + // but from the point of view of the C++ memory model itself it is needed; weaker orders don't work. + + const ptrdiff_t _Prev = _Counter.fetch_add(static_cast(_Update)); + _STL_VERIFY(_Prev + _Update > 0 && _Prev + _Update <= _Least_max_value, + "Precondition: update <= max() - counter (N4861 [thread.sema.cnt]/8)"); + + const ptrdiff_t _Waiting_upper_bound = _Waiting.load(); + + if (_Waiting_upper_bound == 0) { + // Definitely no one is waiting + } else if (_Waiting_upper_bound <= _Update) { + // No more waiting threads than update, can wake everyone. + _Counter.notify_all(); + } else { + // Wake at most _Update. Though repeated notify_one() is somewhat less efficient than single notify_all(), + // the amount of OS calls is still the same; the benefit from trying not to wake unnecessary threads + // is expected to be greater than the loss on extra calls and atomic operations. + for (; _Update != 0; --_Update) { + _Counter.notify_one(); + } + } + } + + void _Wait(const unsigned long _Remaining_timeout) noexcept { + // See the comment in release() + _Waiting.fetch_add(1); + ptrdiff_t _Current = _Counter.load(); + if (_Current == 0) { + __std_atomic_wait_direct(&_Counter, &_Current, sizeof(_Current), _Remaining_timeout); + } + _Waiting.fetch_sub(1, memory_order_relaxed); + } + + void acquire() noexcept /* strengthened */ { + ptrdiff_t _Current = _Counter.load(memory_order_relaxed); + for (;;) { + while (_Current == 0) { + _Wait(_Atomic_wait_no_timeout); + _Current = _Counter.load(memory_order_relaxed); + } + _STL_VERIFY(_Current > 0 && _Current <= _Least_max_value, + "Invariant: counter >= 0, and counter <= max() " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + + // "happens after release" ordering is provided by this CAS, so loads and waits can be relaxed + if (_Counter.compare_exchange_weak(_Current, _Current - 1)) { + return; + } + } + } + + _NODISCARD bool try_acquire() noexcept { + ptrdiff_t _Current = _Counter.load(); + if (_Current == 0) { + return false; + } + _STL_VERIFY(_Current > 0 && _Current <= _Least_max_value, + "Invariant: counter >= 0, and counter <= max() " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + + return _Counter.compare_exchange_weak(_Current, _Current - 1); + } + + template + _NODISCARD bool try_acquire_for(const chrono::duration<_Rep, _Period>& _Rel_time) { + auto _Deadline = _Semaphore_deadline(_Rel_time); + ptrdiff_t _Current = _Counter.load(memory_order_relaxed); + for (;;) { + while (_Current == 0) { + const auto _Remaining_timeout = __std_atomic_wait_get_remaining_timeout(_Deadline); + if (_Remaining_timeout == 0) { + return false; + } + _Wait(_Remaining_timeout); + _Current = _Counter.load(memory_order_relaxed); + } + _STL_VERIFY(_Current > 0 && _Current <= _Least_max_value, + "Invariant: counter >= 0, and counter <= max() " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + + // "happens after release" ordering is provided by this CAS, so loads and waits can be relaxed + if (_Counter.compare_exchange_weak(_Current, _Current - 1)) { + return true; + } + } + } + + template + _NODISCARD bool try_acquire_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) { + ptrdiff_t _Current = _Counter.load(memory_order_relaxed); + for (;;) { + while (_Current == 0) { + const unsigned long _Remaining_timeout = _Semaphore_remaining_timeout(_Abs_time); + if (_Remaining_timeout == 0) { + return false; + } + _Wait(_Remaining_timeout); + _Current = _Counter.load(memory_order_relaxed); + } + _STL_VERIFY(_Current > 0 && _Current <= _Least_max_value, + "Invariant: counter >= 0, and counter <= max() " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + + // "happens after release" ordering is provided by this CAS, so loads and waits can be relaxed + if (_Counter.compare_exchange_weak(_Current, _Current - 1)) { + return true; + } + } + } + +private: + atomic _Counter; + atomic _Waiting; +}; + +template <> +class counting_semaphore<1> { +public: + _NODISCARD static constexpr ptrdiff_t(max)() noexcept { + return 1; + } + + constexpr explicit counting_semaphore(const ptrdiff_t _Desired) noexcept /* strengthened */ + : _Counter(static_cast(_Desired)) { + _STL_VERIFY((_Desired & ~1) == 0, "Precondition: desired >= 0, and desired <= max() " + "(N4861 [thread.sema.cnt]/5)"); + } + + counting_semaphore(const counting_semaphore&) = delete; + counting_semaphore& operator=(const counting_semaphore&) = delete; + + void release(const ptrdiff_t _Update = 1) noexcept /* strengthened */ { + if (_Update == 0) { + return; + } + _STL_VERIFY(_Update == 1, "Precondition: update >= 0, " + "and update <= max() - counter (N4861 [thread.sema.cnt]/8)"); + // TRANSITION, GH-1133: should be memory_order_release + _Counter.store(1); + _Counter.notify_one(); + } + + void acquire() noexcept /* strengthened */ { + for (;;) { + // "happens after release" ordering is provided by this exchange, so loads and waits can be relaxed + // TRANSITION, GH-1133: should be memory_order_acquire + unsigned char _Prev = _Counter.exchange(0); + if (_Prev == 1) { + break; + } + _STL_VERIFY(_Prev == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + _Counter.wait(0, memory_order_relaxed); + } + } + + _NODISCARD bool try_acquire() noexcept { + // TRANSITION, GH-1133: should be memory_order_acquire + unsigned char _Prev = _Counter.exchange(0); + _STL_VERIFY((_Prev & ~1) == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + return reinterpret_cast(_Prev); + } + + template + _NODISCARD bool try_acquire_for(const chrono::duration<_Rep, _Period>& _Rel_time) { + auto _Deadline = _Semaphore_deadline(_Rel_time); + for (;;) { + // "happens after release" ordering is provided by this exchange, so loads and waits can be relaxed + // TRANSITION, GH-1133: should be memory_order_acquire + unsigned char _Prev = _Counter.exchange(0); + if (_Prev == 1) { + return true; + } + _STL_VERIFY(_Prev == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + const auto _Remaining_timeout = __std_atomic_wait_get_remaining_timeout(_Deadline); + if (_Remaining_timeout == 0) { + return false; + } + __std_atomic_wait_direct(&_Counter, &_Prev, sizeof(_Prev), _Remaining_timeout); + } + } + + template + _NODISCARD bool try_acquire_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) { + for (;;) { + // "happens after release" ordering is provided by this exchange, so loads and waits can be relaxed + // TRANSITION, GH-1133: should be memory_order_acquire + unsigned char _Prev = _Counter.exchange(0); + if (_Prev == 1) { + return true; + } + _STL_VERIFY(_Prev == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), " + "possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)"); + + const unsigned long _Remaining_timeout = _Semaphore_remaining_timeout(_Abs_time); + if (_Remaining_timeout == 0) { + return false; + } + + __std_atomic_wait_direct(&_Counter, &_Prev, sizeof(_Prev), _Remaining_timeout); + } + } + +private: + atomic _Counter; +}; + +using binary_semaphore = counting_semaphore<1>; + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // ^^^ _HAS_CXX20 ^^^ + +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _SEMAPHORE_ diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 3043b0eec..e20c94598 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -185,7 +185,6 @@ // P1115R3 erase()/erase_if() Return size_type // P1123R0 Atomic Compare-And-Exchange With Padding Bits For atomic_ref // P1135R6 The C++20 Synchronization Library -// (partially implemented) // P1207R4 Movability Of Single-Pass Iterators // (partially implemented) // P1209R0 erase_if(), erase() @@ -204,6 +203,7 @@ // P1690R1 Refining Heterogeneous Lookup For Unordered Containers // P1716R3 Range Comparison Algorithms Are Over-Constrained // P1754R1 Rename Concepts To standard_case +// P1865R1 Adding max() To latch And barrier // P1870R1 Rename forwarding-range To borrowed_range (Was safe_range before LWG-3379) // P1871R1 disable_sized_sentinel_for // P1872R0 span Should Have size_type, Not index_type @@ -1145,6 +1145,7 @@ #define __cpp_lib_atomic_ref 201806L #define __cpp_lib_atomic_shared_ptr 201711L #define __cpp_lib_atomic_wait 201907L +#define __cpp_lib_barrier 201907L #define __cpp_lib_bind_front 201907L #define __cpp_lib_bit_cast 201806L #define __cpp_lib_bitops 201907L @@ -1185,9 +1186,11 @@ #define __cpp_lib_interpolate 201902L #define __cpp_lib_is_constant_evaluated 201811L #define __cpp_lib_is_nothrow_convertible 201806L +#define __cpp_lib_latch 201907L #define __cpp_lib_list_remove_return_type 201806L #define __cpp_lib_math_constants 201907L #define __cpp_lib_remove_cvref 201711L +#define __cpp_lib_semaphore 201907L #define __cpp_lib_shift 201806L #define __cpp_lib_span 202002L #define __cpp_lib_ssize 201902L diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 40698cb88..1495b8c53 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -469,26 +469,6 @@ std/language.support/support.limits/support.limits.general/functional.version.pa std/language.support/support.limits/support.limits.general/iterator.version.pass.cpp FAIL std/language.support/support.limits/support.limits.general/memory.version.pass.cpp FAIL -# C++20 P1135R6 "The C++20 Synchronization Library" -std/thread/thread.barrier/arrive.pass.cpp FAIL -std/thread/thread.barrier/arrive_and_drop.pass.cpp FAIL -std/thread/thread.barrier/arrive_and_wait.pass.cpp FAIL -std/thread/thread.barrier/completion.pass.cpp FAIL -std/thread/thread.barrier/max.pass.cpp FAIL -std/thread/thread.barrier/version.pass.cpp FAIL -std/thread/thread.latch/arrive_and_wait.pass.cpp FAIL -std/thread/thread.latch/count_down.pass.cpp FAIL -std/thread/thread.latch/max.pass.cpp FAIL -std/thread/thread.latch/try_wait.pass.cpp FAIL -std/thread/thread.latch/version.pass.cpp FAIL -std/thread/thread.semaphore/acquire.pass.cpp FAIL -std/thread/thread.semaphore/binary.pass.cpp FAIL -std/thread/thread.semaphore/max.pass.cpp FAIL -std/thread/thread.semaphore/release.pass.cpp FAIL -std/thread/thread.semaphore/timed.pass.cpp FAIL -std/thread/thread.semaphore/try_acquire.pass.cpp FAIL -std/thread/thread.semaphore/version.pass.cpp FAIL - # *** MISSING COMPILER FEATURES *** # Nothing here! :-) @@ -647,6 +627,19 @@ std/thread/thread.threads/thread.thread.class/thread.thread.member/join.pass.cpp # *** LIKELY BOGUS TESTS *** +# "error: _LIBCPP_VERSION not defined" +std/thread/thread.barrier/version.pass.cpp FAIL +std/thread/thread.latch/version.pass.cpp FAIL +std/thread/thread.semaphore/version.pass.cpp FAIL + +# "error C3861: 'assert': identifier not found" +std/thread/thread.semaphore/timed.pass.cpp FAIL +std/thread/thread.semaphore/try_acquire.pass.cpp FAIL + +# pass lambda without noexcept to barrier +std/thread/thread.barrier/completion.pass.cpp FAIL +std/thread/thread.barrier/max.pass.cpp FAIL + # Test bug/LEWG issue or STL bug. See GH-519 ": signbit() misses overloads for integer types". std/depr/depr.c.headers/math_h.pass.cpp FAIL std/numerics/c.math/cmath.pass.cpp FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 2f3866059..8c47fea11 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -469,26 +469,6 @@ language.support\support.limits\support.limits.general\functional.version.pass.c language.support\support.limits\support.limits.general\iterator.version.pass.cpp language.support\support.limits\support.limits.general\memory.version.pass.cpp -# C++20 P1135R6 "The C++20 Synchronization Library" -thread\thread.barrier\arrive.pass.cpp -thread\thread.barrier\arrive_and_drop.pass.cpp -thread\thread.barrier\arrive_and_wait.pass.cpp -thread\thread.barrier\completion.pass.cpp -thread\thread.barrier\max.pass.cpp -thread\thread.barrier\version.pass.cpp -thread\thread.latch\arrive_and_wait.pass.cpp -thread\thread.latch\count_down.pass.cpp -thread\thread.latch\max.pass.cpp -thread\thread.latch\try_wait.pass.cpp -thread\thread.latch\version.pass.cpp -thread\thread.semaphore\acquire.pass.cpp -thread\thread.semaphore\binary.pass.cpp -thread\thread.semaphore\max.pass.cpp -thread\thread.semaphore\release.pass.cpp -thread\thread.semaphore\timed.pass.cpp -thread\thread.semaphore\try_acquire.pass.cpp -thread\thread.semaphore\version.pass.cpp - # *** MISSING COMPILER FEATURES *** # Nothing here! :-) @@ -647,6 +627,19 @@ thread\thread.threads\thread.thread.class\thread.thread.member\join.pass.cpp # *** LIKELY BOGUS TESTS *** +# "error: _LIBCPP_VERSION not defined" +thread\thread.barrier\version.pass.cpp +thread\thread.latch\version.pass.cpp +thread\thread.semaphore\version.pass.cpp + +# "error C3861: 'assert': identifier not found" +thread\thread.semaphore\timed.pass.cpp +thread\thread.semaphore\try_acquire.pass.cpp + +# pass lambda without noexcept to barrier +thread\thread.barrier\completion.pass.cpp +thread\thread.barrier\max.pass.cpp + # Test bug/LEWG issue or STL bug. See GH-519 ": signbit() misses overloads for integer types". depr\depr.c.headers\math_h.pass.cpp numerics\c.math\cmath.pass.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index 499a40f90..45f1970fa 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -329,6 +329,9 @@ tests\P1032R1_miscellaneous_constexpr tests\P1135R6_atomic_flag_test tests\P1135R6_atomic_wait tests\P1135R6_atomic_wait_vista +tests\P1135R6_barrier +tests\P1135R6_latch +tests\P1135R6_semaphore tests\P1165R1_consistently_propagating_stateful_allocators tests\P1423R3_char8_t_remediation tests\P1645R1_constexpr_numeric diff --git a/tests/std/tests/P1135R6_barrier/env.lst b/tests/std/tests/P1135R6_barrier/env.lst new file mode 100644 index 000000000..642f530ff --- /dev/null +++ b/tests/std/tests/P1135R6_barrier/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P1135R6_barrier/test.cpp b/tests/std/tests/P1135R6_barrier/test.cpp new file mode 100644 index 000000000..3ba5944e4 --- /dev/null +++ b/tests/std/tests/P1135R6_barrier/test.cpp @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +void test() { + std::barrier b(2); + + std::atomic c{0}; + + std::thread t1([&] { + for (int i = 0; i < 5; ++i) { + auto token = b.arrive(); + b.wait(std::move(token)); + c.fetch_add(1, std::memory_order_relaxed); + } + }); + + std::thread t2([&] { + for (int i = 0; i < 3; ++i) { + b.arrive_and_wait(); + c.fetch_add(1, std::memory_order_relaxed); + } + b.arrive_and_drop(); + }); + + t1.join(); + t2.join(); + + assert(c.load(std::memory_order_relaxed) == 8); +} + +void test_with_functor() { + std::atomic c{0}; + std::atomic called_times{0}; + + struct Functor { + void operator()() noexcept { + switch (called_times->fetch_add(1, std::memory_order_relaxed) + 1) { + case 1: + assert(c->load(std::memory_order_relaxed) == 0); + break; + case 2: + assert(c->load(std::memory_order_relaxed) == 2); + break; + case 3: + assert(c->load(std::memory_order_relaxed) == 4); + break; + case 4: + assert(c->load(std::memory_order_relaxed) == 6); + break; + case 5: + assert(c->load(std::memory_order_relaxed) == 7); + break; + default: + assert(false); + break; + } + } + + std::atomic* called_times; + std::atomic* c; + } f = {&called_times, &c}; + + std::barrier b(2, f); + + std::thread t1([&] { + for (int i = 0; i < 5; ++i) { + auto token = b.arrive(); + b.wait(std::move(token)); + c.fetch_add(1, std::memory_order_relaxed); + } + }); + + std::thread t2([&] { + for (int i = 0; i < 3; ++i) { + b.arrive_and_wait(); + c.fetch_add(1, std::memory_order_relaxed); + } + b.arrive_and_drop(); + }); + + t1.join(); + t2.join(); + + assert(c.load(std::memory_order_relaxed) == 8); + assert(called_times.load(std::memory_order_relaxed) == 5); +} + + +void test_token() { + std::atomic called_times{0}; + + auto f = [&]() noexcept { called_times.fetch_add(1, std::memory_order_relaxed); }; + + std::barrier b(2, f); + auto t1 = b.arrive(); + auto t2 = std::move(t1); + + assert(called_times.load(std::memory_order_relaxed) == 0); + auto t3 = b.arrive(); + auto t4 = std::move(t3); + + assert(called_times.load(std::memory_order_relaxed) == 1); + b.wait(std::move(t4)); + assert(called_times.load(std::memory_order_relaxed) == 1); + b.wait(std::move(t2)); + assert(called_times.load(std::memory_order_relaxed) == 1); +} + +void barrier_callback_function() noexcept {} + +void test_functor_types() { + struct f1 { + void operator()() noexcept {} + + f1(int, int, int) {} + + f1(f1&&) noexcept = default; + f1& operator=(f1&&) = delete; + }; + std::barrier b1{1, f1{0, 0, 0}}; + b1.arrive_and_wait(); + + std::barrier b2{1, barrier_callback_function}; + b2.arrive_and_wait(); + + std::barrier b3{1, []() noexcept {}}; + b3.arrive_and_wait(); +} + +int main() { + static_assert(std::barrier<>::max() >= 5, "barrier should support some number of arrivals"); + + test(); + test_with_functor(); + test_token(); + test_functor_types(); +} diff --git a/tests/std/tests/P1135R6_latch/env.lst b/tests/std/tests/P1135R6_latch/env.lst new file mode 100644 index 000000000..642f530ff --- /dev/null +++ b/tests/std/tests/P1135R6_latch/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P1135R6_latch/test.cpp b/tests/std/tests/P1135R6_latch/test.cpp new file mode 100644 index 000000000..991869c1f --- /dev/null +++ b/tests/std/tests/P1135R6_latch/test.cpp @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +void test(const bool release_wait) { + std::latch l(5); + + std::thread t1([&] { l.wait(); }); + + std::thread t2([&] { l.arrive_and_wait(2); }); + + l.count_down(); + + if (release_wait) { + l.arrive_and_wait(2); + } else { + l.count_down(2); + } + + t1.join(); + t2.join(); +} + +int main() { + static_assert(std::latch::max() >= 5, "latch should support some number of count downs"); + + test(true); + test(false); +} diff --git a/tests/std/tests/P1135R6_semaphore/env.lst b/tests/std/tests/P1135R6_semaphore/env.lst new file mode 100644 index 000000000..642f530ff --- /dev/null +++ b/tests/std/tests/P1135R6_semaphore/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P1135R6_semaphore/test.cpp b/tests/std/tests/P1135R6_semaphore/test.cpp new file mode 100644 index 000000000..dfccaf23a --- /dev/null +++ b/tests/std/tests/P1135R6_semaphore/test.cpp @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +void wait_and_expect(std::atomic& v, const int val, const std::chrono::milliseconds delay_duration) { +#ifdef CAN_FAIL_ON_TIMING_ASSUMPTION + std::this_thread::sleep_for(delay_duration); + assert(v.load() == val); +#else // ^^^ CAN_FAIL_ON_TIMING_ASSUMPTION / !CAN_FAIL_ON_TIMING_ASSUMPTION vvv + while (v.load() < val) { + std::this_thread::sleep_for(delay_duration); + } + assert(v.load() == val); +#endif // ^^^ !CAN_FAIL_ON_TIMING_ASSUMPTION ^^^ +} + +void test_counting_semaphore_count(const std::chrono::milliseconds delay_duration) { + std::latch start{4}; + + std::counting_semaphore<4> s{2}; + std::atomic v{0}; + + auto thread_function = [&] { + start.arrive_and_wait(); + for (int i = 0; i < 3; ++i) { + s.acquire(); + v.fetch_add(1); + } + }; + + std::thread t1{thread_function}; + std::thread t2{thread_function}; + std::thread t3{thread_function}; + + start.arrive_and_wait(); + + wait_and_expect(v, 2, delay_duration); + + s.release(); + + wait_and_expect(v, 3, delay_duration); + + s.release(4); + + wait_and_expect(v, 7, delay_duration); + + s.release(4); + + wait_and_expect(v, 9, delay_duration); + + t1.join(); + t2.join(); + t3.join(); +} + +void test_binary_semaphore_count(const std::chrono::milliseconds delay_duration) { + std::latch start{3}; + + std::binary_semaphore s{1}; + + std::atomic v{0}; + + auto thread_function = [&] { + start.arrive_and_wait(); + for (int i = 0; i < 2; ++i) { + s.acquire(); + v.fetch_add(1); + } + }; + + std::thread t1{thread_function}; + std::thread t2{thread_function}; + + start.arrive_and_wait(); + + wait_and_expect(v, 1, delay_duration); + + s.release(); + wait_and_expect(v, 2, delay_duration); + + s.release(); + wait_and_expect(v, 3, delay_duration); + + s.release(); + wait_and_expect(v, 4, delay_duration); + + s.release(); + wait_and_expect(v, 4, delay_duration); + + t1.join(); + t2.join(); +} + +template +void test_semaphore_wait_for(const std::chrono::milliseconds delay_duration) { + std::latch start{2}; + + Semaphore s{0}; + + std::thread t([&] { + start.arrive_and_wait(); + + assert(s.try_acquire_for(delay_duration)); + assert(!s.try_acquire_for(delay_duration * 16)); + }); + + start.arrive_and_wait(); + + s.release(); + + std::this_thread::sleep_for(delay_duration * 4); + + t.join(); +} + +template +void test_semaphore_wait_until(const std::chrono::milliseconds delay_duration) { + std::latch start{2}; + + Semaphore s{0}; + + std::thread t([&] { + start.arrive_and_wait(); + + assert(s.try_acquire_until(std::chrono::steady_clock::now() + delay_duration)); + assert(!s.try_acquire_until(std::chrono::steady_clock::now() + delay_duration * 8)); + }); + + start.arrive_and_wait(); + + s.release(); + + std::this_thread::sleep_for(delay_duration * 4); + + t.join(); +} + +int main() { + constexpr auto max = std::numeric_limits::max(); + + static_assert(std::counting_semaphore::max() >= max, "semaphore should support some number of count downs"); + static_assert(std::counting_semaphore<5>::max() >= 5, "semaphore should support some number of count downs"); + static_assert(std::binary_semaphore::max() >= 1, "semaphore should support some number of count downs"); + + constexpr auto delay_duration = 200ms; + + test_counting_semaphore_count(delay_duration); + test_binary_semaphore_count(delay_duration); + +#ifdef CAN_FAIL_ON_TIMING_ASSUMPTION + test_semaphore_wait_for>(delay_duration); + test_semaphore_wait_until>(delay_duration); + test_semaphore_wait_for(delay_duration); + test_semaphore_wait_until(delay_duration); +#endif // CAN_FAIL_ON_TIMING_ASSUMPTION +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp index c3eaa1b57..455494fdd 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp @@ -189,6 +189,20 @@ STATIC_ASSERT(__cpp_lib_atomic_wait == 201907L); #endif #endif +#if _HAS_CXX20 +#ifndef __cpp_lib_barrier +#error __cpp_lib_barrier is not defined +#elif __cpp_lib_barrier != 201907L +#error __cpp_lib_barrier is not 201907L +#else +STATIC_ASSERT(__cpp_lib_barrier == 201907L); +#endif +#else +#ifdef __cpp_lib_barrier +#error __cpp_lib_barrier is defined +#endif +#endif + #if _HAS_CXX20 #ifndef __cpp_lib_bind_front #error __cpp_lib_bind_front is not defined @@ -848,6 +862,20 @@ STATIC_ASSERT(__cpp_lib_is_swappable == 201603L); #endif #endif +#if _HAS_CXX20 +#ifndef __cpp_lib_latch +#error __cpp_lib_latch is not defined +#elif __cpp_lib_latch != 201907L +#error __cpp_lib_latch is not 201907L +#else +STATIC_ASSERT(__cpp_lib_latch == 201907L); +#endif +#else +#ifdef __cpp_lib_latch +#error __cpp_lib_latch is defined +#endif +#endif + #if _HAS_CXX17 #ifndef __cpp_lib_launder #error __cpp_lib_launder is not defined @@ -1116,6 +1144,20 @@ STATIC_ASSERT(__cpp_lib_scoped_lock == 201703L); #endif #endif +#if _HAS_CXX20 +#ifndef __cpp_lib_semaphore +#error __cpp_lib_semaphore is not defined +#elif __cpp_lib_semaphore != 201907L +#error __cpp_lib_semaphore is not 201907L +#else +STATIC_ASSERT(__cpp_lib_semaphore == 201907L); +#endif +#else +#ifdef __cpp_lib_semaphore +#error __cpp_lib_semaphore is defined +#endif +#endif + #ifndef __cpp_lib_shared_mutex #error __cpp_lib_shared_mutex is not defined #elif __cpp_lib_shared_mutex != 201505L diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 34cc09895..71cb23f8d 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -9,6 +9,7 @@ PM_CL="/DMEOW_HEADER=algorithm" PM_CL="/DMEOW_HEADER=any" PM_CL="/DMEOW_HEADER=array" PM_CL="/DMEOW_HEADER=atomic" +PM_CL="/DMEOW_HEADER=barrier" PM_CL="/DMEOW_HEADER=bit" PM_CL="/DMEOW_HEADER=bitset" PM_CL="/DMEOW_HEADER=charconv" @@ -35,6 +36,7 @@ PM_CL="/DMEOW_HEADER=iostream" PM_CL="/DMEOW_HEADER=iso646.h" PM_CL="/DMEOW_HEADER=istream" PM_CL="/DMEOW_HEADER=iterator" +PM_CL="/DMEOW_HEADER=latch" PM_CL="/DMEOW_HEADER=limits" PM_CL="/DMEOW_HEADER=list" PM_CL="/DMEOW_HEADER=locale" @@ -53,6 +55,7 @@ PM_CL="/DMEOW_HEADER=ranges" PM_CL="/DMEOW_HEADER=ratio" PM_CL="/DMEOW_HEADER=regex" PM_CL="/DMEOW_HEADER=scoped_allocator" +PM_CL="/DMEOW_HEADER=semaphore" PM_CL="/DMEOW_HEADER=set" PM_CL="/DMEOW_HEADER=shared_mutex" PM_CL="/DMEOW_HEADER=span"