From 7c04fa8891a5900315e15e4fb1502984d008e9f1 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 12 Mar 2024 22:42:02 +0100 Subject: [PATCH 1/5] ``: Test `generator::promise_type` --- stl/inc/generator | 28 +- tests/std/test.lst | 1 + .../tests/P2502R2_generator_promise/env.lst | 4 + .../tests/P2502R2_generator_promise/test.cpp | 257 ++++++++++++++++++ 4 files changed, 278 insertions(+), 12 deletions(-) create mode 100644 tests/std/tests/P2502R2_generator_promise/env.lst create mode 100644 tests/std/tests/P2502R2_generator_promise/test.cpp diff --git a/stl/inc/generator b/stl/inc/generator index 10777a59e3e..7bcbece960a 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -40,18 +40,19 @@ concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v class _Promise_allocator { // statically specified allocator type private: - using _Alloc = _Rebind_alloc_t<_Allocator, _Aligned_block>; + using _Alloc = _Rebind_alloc_t<_Allocator, _Aligned_block>; + using _Alloc_size_type = allocator_traits<_Alloc>::size_type; static void* _Allocate(_Alloc _Al, const size_t _Size) { if constexpr (default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { // do not store stateless allocator const size_t _Count = (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - return _Al.allocate(_Count); + return _Al.allocate(static_cast<_Alloc_size_type>(_Count)); } else { // store stateful allocator constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); const size_t _Count = (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); - void* const _Ptr = _Al.allocate(_Count); + void* const _Ptr = _Al.allocate(static_cast<_Alloc_size_type>(_Count)); const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); @@ -83,7 +84,7 @@ public: // make stateless allocator _Alloc _Al{}; const size_t _Count = (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Count)); } else { // retrieve stateful allocator const auto _Al_address = @@ -94,7 +95,7 @@ public: constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); const size_t _Count = (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Count)); } } }; @@ -108,7 +109,7 @@ private: static void __stdcall _Dealloc_stateless(void* const _Ptr, const size_t _Size) noexcept { _Alloc _Al{}; const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast::size_type>(_Count)); } template @@ -123,7 +124,7 @@ private: _Stored_al.~_Alloc(); const size_t _Count = (_Size + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast::size_type>(_Count)); } static void __stdcall _Dealloc_delete(void* const _Ptr, const size_t _Size) noexcept { @@ -132,15 +133,16 @@ private: template static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { - using _Alloc = _Rebind_alloc_t<_ProtoAlloc, _Aligned_block>; - auto _Al = static_cast<_Alloc>(_Proto); + using _Alloc = _Rebind_alloc_t<_ProtoAlloc, _Aligned_block>; + using _Alloc_size_type = allocator_traits<_Alloc>::size_type; + auto _Al = static_cast<_Alloc>(_Proto); if constexpr (default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { // don't store stateless allocator const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - void* const _Ptr = _Al.allocate(_Count); + void* const _Ptr = _Al.allocate(static_cast<_Alloc_size_type>(_Count)); _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); return _Ptr; } else { @@ -150,7 +152,7 @@ private: const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); - void* const _Ptr = _Al.allocate(_Count); + void* const _Ptr = _Al.allocate(static_cast<_Alloc_size_type>(_Count)); _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); _Size += sizeof(_Dealloc_fn); const auto _Al_address = @@ -215,7 +217,7 @@ public: #ifndef _PREFAST_ // TRANSITION, VSO-1662733 _NODISCARD #endif // ^^^ no workaround ^^^ - suspend_always initial_suspend() noexcept { + suspend_always initial_suspend() const noexcept { return {}; } @@ -446,6 +448,8 @@ private: friend _Gen_promise_base<_Gen_yield_t<_Ref>>; public: + using yielded = _Gen_yield_t<_Ref>; + struct __declspec(empty_bases) promise_type : _Promise_allocator<_Alloc>, _Gen_promise_base<_Gen_yield_t<_Ref>> { _NODISCARD generator get_return_object() noexcept { return generator{_Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; diff --git a/tests/std/test.lst b/tests/std/test.lst index bc2dcb65edf..d271be46f78 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -649,6 +649,7 @@ tests\P2474R2_views_repeat tests\P2474R2_views_repeat_death tests\P2494R2_move_only_range_adaptors tests\P2502R2_generator +tests\P2502R2_generator_promise tests\P2505R5_monadic_functions_for_std_expected tests\P2510R3_text_formatting_pointers tests\P2517R1_apply_conditional_noexcept diff --git a/tests/std/tests/P2502R2_generator_promise/env.lst b/tests/std/tests/P2502R2_generator_promise/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator_promise/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/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp new file mode 100644 index 00000000000..792dd9e8c9f --- /dev/null +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "range_algorithm_support.hpp" + +using namespace std; + +template +class TestAllocator : public allocator { +public: + using value_type = T; + using is_always_equal = AlwaysEqual; + using difference_type = DifferenceType; + using size_type = make_unsigned_t; + + TestAllocator() = default; + + template + TestAllocator(const TestAllocator&) {} + + T* allocate(const size_type s) { + return static_cast(::operator new(static_cast(s * sizeof(T)), align_val_t{alignof(T)})); + } + + void deallocate(T* const p, size_type s) { + ::operator delete(p, s * sizeof(T), align_val_t{alignof(T)}); + } + + operator pmr::polymorphic_allocator() const { + return {}; + } + + bool operator==(const TestAllocator&) const = default; +}; + +template +concept HasOperatorNew = requires(Args&&... args) { + { Promise::operator new(forward(args)...) } -> same_as; +}; + +template +struct generator_allocator {}; + +template +struct generator_allocator> { + using type = Alloc; +}; + +struct MoveOnly { + MoveOnly(const MoveOnly&) = delete; + MoveOnly& operator=(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; + MoveOnly& operator=(MoveOnly&&) = default; +}; + +static_assert(movable); +static_assert(!copyable); + +struct Immovable { + Immovable(Immovable&&) = delete; + Immovable& operator=(Immovable&&) = delete; +}; + +static_assert(!movable); + +template +struct Proxy { + Proxy(const T&); // not defined +}; + +template + requires convertible_to, typename Gen::yielded> +void test_yield_elements_of_range(typename Gen::promise_type& p) { + using Alloc = generator_allocator::type; + + { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval()})); + static_assert(convertible_to().await_ready()), bool>); + } + + if constexpr (!is_void_v) { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval(), declval()})); + static_assert(convertible_to().await_ready()), bool>); + } +} + +template +void test_operator_new(typename Gen::promise_type& p, const Alloc2& alloc2 = {}) { + using Promise = Gen::promise_type; + using Alloc = generator_allocator::type; + + // Test 'operator new(size_t)' + constexpr bool has_op_new1 = HasOperatorNew; + static_assert(has_op_new1 == (same_as || default_initializable) ); + if constexpr (has_op_new1) { + const size_t size = __STDCPP_DEFAULT_NEW_ALIGNMENT__; + void* const mem = p.operator new(size); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); + p.operator delete(mem, size); + } + + // Test 'operator new(size_t, allocator_arg_t, const Alloc2&, const Args&...)' + constexpr bool has_op_new2 = HasOperatorNew; + static_assert(has_op_new2 == (same_as || convertible_to) ); + if constexpr (has_op_new2) { + const size_t size = __STDCPP_DEFAULT_NEW_ALIGNMENT__; + void* const mem = p.operator new(size, allocator_arg, alloc2, 0, 0); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); + p.operator delete(mem, size); + } + + // Test 'operator new(size_t, const This&, allocator_arg_t, const Alloc2&, const Args&...)' + struct S {}; + constexpr bool has_op_new3 = HasOperatorNew; + static_assert(has_op_new3 == (same_as || convertible_to) ); + if constexpr (has_op_new3) { + const size_t size = __STDCPP_DEFAULT_NEW_ALIGNMENT__; + const S s; + void* const mem = p.operator new(size, s, allocator_arg, alloc2, 0, 0); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); + p.operator delete(mem, size); + } +} + +template +void test_one() { + using Gen = generator; + using Promise = Gen::promise_type; + using Yielded = Gen::yielded; + static_assert(semiregular); + + Promise p; + + // Test 'get_return_object' + static_assert(same_as); + static_assert(noexcept(p.get_return_object())); + + // Test 'initial_suspend' + static_assert(same_as); + static_assert(same_as); + static_assert(noexcept(p.initial_suspend())); + static_assert(noexcept(as_const(p).initial_suspend())); + + // Test 'final_suspend' + using FinalAwaitable = decltype(p.final_suspend()); + static_assert(convertible_to().await_ready()), bool>); + static_assert(noexcept(p.final_suspend())); + + // Test 'yield_value(yielded)' + static_assert(same_as())), suspend_always>); + static_assert(noexcept(p.yield_value(declval()))); + + // Test 'yield_value(const remove_reference_t&)' + if constexpr (is_rvalue_reference_v + && constructible_from, const remove_reference_t&>) { + using Lval = const remove_reference_t&; + using Awaitable = decltype(p.yield_value(declval())); + + static_assert(convertible_to().await_ready()), bool>); + static_assert(is_void_v().await_resume())>); + static_assert(noexcept(p.yield_value(declval())) + == is_nothrow_constructible_v, + const remove_reference_t&>); // strengthened + } + + { // Test 'yield_value(elements_of)' + { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval()})); + static_assert(convertible_to().await_ready()), bool>); + } + + if constexpr (!is_void_v) { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval(), declval()})); + static_assert(convertible_to().await_ready()), bool>); + } + } + + using ValTy = conditional_t, remove_cvref_t, V>; + if constexpr (convertible_to) { // Test 'yield_value(ranges::elements_of)' + test_yield_elements_of_range>(p); + test_yield_elements_of_range>(p); + test_yield_elements_of_range>(p); + test_yield_elements_of_range>(p); + } + + // Test 'await_transform' + static_assert(!requires(Promise& p) { p.await_transform(); }); + + // Test 'return_void' + static_assert(is_void_v); + static_assert(is_void_v); + static_assert(noexcept(p.return_void())); + static_assert(noexcept(as_const(p).return_void())); + + // Test 'unhandled_exception' + static_assert(is_void_v); + + // Test 'operator new(size_t, ARGS...)' + test_operator_new>(p); + test_operator_new>(p); + test_operator_new>(p); + if constexpr (same_as) { + test_operator_new>(p); + test_operator_new>(p); + test_operator_new>(p); + } +} + +template +void test_with_allocator() { + test_one(); + test_one>(); + test_one>(); + test_one>(); + test_one>(); + test_one>(); + test_one>(); +} + +template +void test_with_type() { + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + + test_with_allocator, T>(); + test_with_allocator&, T>(); + test_with_allocator&, T>(); + test_with_allocator&&, T>(); + test_with_allocator&&, T>(); +} + +int main() { + test_with_type(); + test_with_type(); + test_with_type(); + test_with_type(); + test_with_type(); + test_with_allocator::reference, bool>(); +} From 0a8925c798fee6352fffd6c7c8884299bcdd14c7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 26 Mar 2024 12:50:36 -0700 Subject: [PATCH 2/5] Include more headers. --- tests/std/tests/P2502R2_generator_promise/test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index 792dd9e8c9f..38cccc9ca40 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -3,9 +3,12 @@ #include #include +#include +#include #include #include #include +#include #include #include #include From 6b1577d709648d0779bd33b1ce5e380cabcc30eb Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 26 Mar 2024 12:51:45 -0700 Subject: [PATCH 3/5] Pre-existing: Drop default arg for `_Promise_allocator`. There's only one use, which always provides an argument. --- stl/inc/generator | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/generator b/stl/inc/generator index 7bcbece960a..2820ecf42c8 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -37,7 +37,7 @@ struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { template concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v::pointer>; -template +template class _Promise_allocator { // statically specified allocator type private: using _Alloc = _Rebind_alloc_t<_Allocator, _Aligned_block>; From 4b4a1c6bfa3692f5e15b2d31c923a2c6e2f3e1a4 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 26 Mar 2024 12:56:47 -0700 Subject: [PATCH 4/5] Use `yielded`. (It doesn't matter where `friend` appears, so we can move it down.) --- stl/inc/generator | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 2820ecf42c8..ca8d2485f9a 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -445,20 +445,19 @@ private: static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " "(N4971 [coro.generator.class]/1.1)"); - friend _Gen_promise_base<_Gen_yield_t<_Ref>>; - public: using yielded = _Gen_yield_t<_Ref>; - struct __declspec(empty_bases) promise_type : _Promise_allocator<_Alloc>, _Gen_promise_base<_Gen_yield_t<_Ref>> { + friend _Gen_promise_base; + + struct __declspec(empty_bases) promise_type : _Promise_allocator<_Alloc>, _Gen_promise_base { _NODISCARD generator get_return_object() noexcept { return generator{_Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; } }; _STL_INTERNAL_STATIC_ASSERT(is_standard_layout_v); #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT( - is_pointer_interconvertible_base_of_v<_Gen_promise_base<_Gen_yield_t<_Ref>>, promise_type>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, promise_type>); #endif // ^^^ no workaround ^^^ generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} @@ -479,7 +478,7 @@ public: _STL_ASSERT(_Coro, "Can't call begin on moved-from generator"); _Coro.resume(); return typename _Gen_iter_provider<_Value, _Ref>::_Iterator{ - _Gen_secret_tag{}, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>>::from_address(_Coro.address())}; + _Gen_secret_tag{}, coroutine_handle<_Gen_promise_base>::from_address(_Coro.address())}; } _NODISCARD default_sentinel_t end() const noexcept { From 8fb1a16fdc42d50c3c5fba957553daa5f431acc0 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 26 Mar 2024 13:00:18 -0700 Subject: [PATCH 5/5] Use `_Convert_size` when allocating. --- stl/inc/generator | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index ca8d2485f9a..0df72ea1c9d 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -47,12 +47,12 @@ private: if constexpr (default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { // do not store stateless allocator const size_t _Count = (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - return _Al.allocate(static_cast<_Alloc_size_type>(_Count)); + return _Al.allocate(_Convert_size<_Alloc_size_type>(_Count)); } else { // store stateful allocator constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); const size_t _Count = (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); - void* const _Ptr = _Al.allocate(static_cast<_Alloc_size_type>(_Count)); + void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Count)); const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); @@ -142,7 +142,7 @@ private: const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - void* const _Ptr = _Al.allocate(static_cast<_Alloc_size_type>(_Count)); + void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Count)); _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); return _Ptr; } else { @@ -152,7 +152,7 @@ private: const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); - void* const _Ptr = _Al.allocate(static_cast<_Alloc_size_type>(_Count)); + void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Count)); _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); _Size += sizeof(_Dealloc_fn); const auto _Al_address =