From 80556126e5caaf501ec46966c8135e2ae49d3f81 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 21:14:40 -0800 Subject: [PATCH 01/50] `generator` WIP (#4342) --- stl/CMakeLists.txt | 1 + stl/inc/__msvc_all_public_headers.hpp | 1 + stl/inc/generator | 472 ++++++++++++++++++ stl/inc/header-units.json | 1 + stl/inc/ranges | 15 + stl/inc/yvals_core.h | 8 +- stl/modules/std.ixx | 1 + .../include/test_header_units_and_modules.hpp | 17 + tests/std/test.lst | 1 + .../importable_cxx_library_headers.jsonc | 1 + .../test.cpp | 1 + tests/std/tests/P2502R2_generator/env.lst | 4 + tests/std/tests/P2502R2_generator/test.cpp | 339 +++++++++++++ .../test.compile.pass.cpp | 6 + .../include_each_header_alone_matrix.lst | 1 + 15 files changed, 868 insertions(+), 1 deletion(-) create mode 100644 stl/inc/generator create mode 100644 tests/std/tests/P2502R2_generator/env.lst create mode 100644 tests/std/tests/P2502R2_generator/test.cpp diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 207cc6704fb..a4959ea8a1f 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -162,6 +162,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/fstream ${CMAKE_CURRENT_LIST_DIR}/inc/functional ${CMAKE_CURRENT_LIST_DIR}/inc/future + ${CMAKE_CURRENT_LIST_DIR}/inc/generator ${CMAKE_CURRENT_LIST_DIR}/inc/hash_map ${CMAKE_CURRENT_LIST_DIR}/inc/hash_set ${CMAKE_CURRENT_LIST_DIR}/inc/header-units.json diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index 268f3a862f6..005d363b72e 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -96,6 +96,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/generator b/stl/inc/generator new file mode 100644 index 00000000000..22ff90589b7 --- /dev/null +++ b/stl/inc/generator @@ -0,0 +1,472 @@ +// generator standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _GENERATOR_ +#define _GENERATOR_ +#include +#if _STL_COMPILER_PREPROCESSOR + +#if !_HAS_CXX23 || !defined(__cpp_impl_coroutine) || !defined(__cpp_lib_concepts) // TRANSITION, GH-395 +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later coroutine support.") +#else // ^^^ no coroutine support / coroutines 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 + +// TRANSITION, non-_Ugly attribute tokens +#pragma push_macro("empty_bases") +#undef empty_bases + +_STD_BEGIN + +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { + unsigned char _Pad[__STDCPP_DEFAULT_NEW_ALIGNMENT__]; +}; + +template +concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v::pointer>; + +template +class _Promise_allocator { // statically specified allocator type +private: + using _Alloc = _Rebind_alloc_t<_Allocator, _Aligned_block>; + + 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); + } else { + // store stateful allocator + static 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); + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); + return _Ptr; + } + } + +public: + static void* operator new(const size_t _Size) + requires default_initializable<_Alloc> + { + return _Allocate(_Alloc{}, _Size); + } + + template + requires convertible_to + static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + return _Allocate(static_cast<_Alloc>(static_cast<_Allocator>(_Al)), _Size); + } + + template + requires convertible_to + static void* operator new(const size_t _Size, const _This&, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + return _Allocate(static_cast<_Alloc>(static_cast<_Allocator>(_Al)), _Size); + } + + static void operator delete(void* const _Ptr, const size_t _Size) noexcept { + if constexpr (default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { + // 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); + } else { + // retrieve stateful allocator + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); + _Alloc _Al{_STD move(_Stored_al)}; + _Stored_al.~_Alloc(); + + static 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); + } + } +}; + +template <> +class _Promise_allocator { // type-erased allocator +private: + using _Dealloc_fn = void (*)(void*, size_t); + + template + static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { + using _Alloc = _Rebind_alloc_t<_ProtoAlloc, _Aligned_block>; + 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 = [](void* const _Ptr, const size_t _Size) { + _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); + }; + + const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); + void* const _Ptr = _Al.allocate(_Count); + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); + return _Ptr; + } else { + // store stateful allocator + static constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + + const _Dealloc_fn _Dealloc = [](void* const _Ptr, size_t _Size) { + _Size += sizeof(_Dealloc_fn); + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + auto& _Stored_al = *reinterpret_cast(_Al_address); + _Alloc _Al{_STD move(_Stored_al)}; + _Stored_al.~_Alloc(); + + const size_t _Count = (_Size + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); + }; + + const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); + void* const _Ptr = _Al.allocate(_Count); + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); + _Size += sizeof(_Dealloc_fn); + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + ::new (reinterpret_cast(_Al_address)) _Alloc{_STD move(_Al)}; + return _Ptr; + } + } + +public: + static void* operator new(const size_t _Size) { // default: new/delete + void* const _Ptr = ::operator new[](_Size + sizeof(_Dealloc_fn)); + const _Dealloc_fn _Dealloc = [](void* const _Ptr, const size_t _Size) { + ::operator delete[](_Ptr, _Size + sizeof(_Dealloc_fn)); + }; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); + return _Ptr; + } + + template + static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + static_assert(_Has_real_pointers<_Alloc>, "coroutine allocators must use raw pointers"); + return _Allocate(_Al, _Size); + } + + template + static void* operator new(const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + static_assert(_Has_real_pointers<_Alloc>, "coroutine allocators must use raw pointers"); + return _Allocate(_Al, _Size); + } + + static void operator delete(void* const _Ptr, const size_t _Size) noexcept { + _Dealloc_fn _Dealloc; + _CSTD memcpy(&_Dealloc, static_cast(_Ptr) + _Size, sizeof(_Dealloc_fn)); + _Dealloc(_Ptr, _Size); + } +}; + +_EXPORT_STD template +class generator; + +template +using _Gen_value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; +template +using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; +template +using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; + +template +class _Gen_promise_base { +public: + _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); + +#ifndef _PREFAST_ // TRANSITION, VSO-1662733 + _NODISCARD +#endif // _PREFAST_ + suspend_always initial_suspend() noexcept { + return {}; + } + + _NODISCARD auto final_suspend() noexcept { + return _Final_awaiter{}; + } + + _NODISCARD suspend_always yield_value(_Yielded _Val) noexcept { + _Ptr = _STD addressof(_Val); + return {}; + } + + _NODISCARD auto yield_value(const remove_reference_t<_Yielded>& _Val) noexcept( + is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) + requires (is_rvalue_reference_v<_Yielded> + && constructible_from, const remove_reference_t<_Yielded>&>) + { + return _Element_awaiter{_Val}; + } + + template + requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> + _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { + return _Nested_awaitable<_Rty, _Vty, _Alloc>{std::move(_Elem.range)}; + } + + template <_RANGES input_range _Rng, class _Alloc> + requires convertible_to<_RANGES range_reference_t<_Rng>, _Yielded> + _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) noexcept { + using _Vty = _RANGES range_value_t<_Rng>; + return _Nested_awaitable<_Yielded, _Vty, _Alloc>{ + [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, + const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { + for (; _It != _Se; ++_It) { + co_yield static_cast<_Yielded>(*_It); + } + }(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; + } + + void await_transform() = delete; + + void return_void() noexcept {} + + void unhandled_exception() { + if (_Info) { + _Info->_Except = _STD current_exception(); + } else { + throw; + } + } + +private: + struct _Element_awaiter { + remove_cvref_t<_Yielded> _Val; + + _NODISCARD constexpr bool await_ready() const noexcept { + return false; + } + + template + constexpr void await_suspend(coroutine_handle<_Promise> _Handle) noexcept { +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); +#endif // __cpp_lib_is_pointer_interconvertible + + _Gen_promise_base& _Current = _Handle.promise(); + _Current._Ptr = _STD addressof(_Val); + } + + constexpr void await_resume() const noexcept {} + }; + + struct _Nest_info { + exception_ptr _Except; + coroutine_handle<_Gen_promise_base> _Parent; + coroutine_handle<_Gen_promise_base> _Root; + }; + + struct _Final_awaiter { + _NODISCARD bool await_ready() noexcept { + return false; + } + + template + _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_Promise> _Handle) noexcept { +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); +#endif // __cpp_lib_is_pointer_interconvertible + + _Gen_promise_base& _Current = _Handle.promise(); + if (!_Current._Info) { + return _STD noop_coroutine(); + } + + coroutine_handle<_Gen_promise_base> _Cont = _Current._Info->_Parent; + _Current._Info->_Root.promise()._Top = _Cont; + _Current._Info = nullptr; + return _Cont; + } + + void await_resume() noexcept {} + }; + + template + struct _Nested_awaitable { + _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); + + _Nest_info _Nested; + generator<_Rty, _Vty, _Alloc> _Gen; + + explicit _Nested_awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} + + _NODISCARD bool await_ready() noexcept { + return !_Gen._Coro; + } + + template + _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend(coroutine_handle<_Promise> _Current) noexcept { +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); +#endif // __cpp_lib_is_pointer_interconvertible + auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); + _Gen_promise_base& _Parent_promise = _Nested._Parent.promise(); + if (_Parent_promise._Info) { + _Nested._Root = _Parent_promise._Info->_Root; + } else { + _Nested._Root = _Nested._Parent; + } + _Nested._Root.promise()._Top = _Target; + _Target.promise()._Info = _STD addressof(_Nested); + return _Target; + } + + void await_resume() { + if (_Nested._Except) { + _STD rethrow_exception(_STD move(_Nested._Except)); + } + } + }; + + template + friend class _Gen_iter; + + // _Top and _Info are mutually exclusive, and could potentially be merged. + coroutine_handle<_Gen_promise_base> _Top = coroutine_handle<_Gen_promise_base>::from_promise(*this); + add_pointer_t<_Yielded> _Ptr = nullptr; + _Nest_info* _Info = nullptr; +}; + +struct _Gen_secret_tag {}; + +template +class _Gen_iter { +public: + using value_type = _Value; + using difference_type = ptrdiff_t; + + _Gen_iter(_Gen_iter&& _That) noexcept : _Coro{_STD exchange(_That._Coro, {})} {} + + _Gen_iter& operator=(_Gen_iter&& _That) noexcept { + _Coro = _STD exchange(_That._Coro, {}); + return *this; + } + + _NODISCARD _Ref operator*() const noexcept { + _STL_ASSERT(!_Coro.done(), "Can't dereference generator end iterator"); + return static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr); + } + + _Gen_iter& operator++() { + _STL_ASSERT(!_Coro.done(), "Can't increment generator end iterator"); + _Coro.promise()._Top.resume(); + return *this; + } + + void operator++(int) { + ++*this; + } + + _NODISCARD bool operator==(default_sentinel_t) const noexcept { + return _Coro.done(); + } + +private: + template + friend class generator; + + explicit _Gen_iter(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept + : _Coro{_Coro_} {} + + coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro; +}; + +_EXPORT_STD template +class generator : public ranges::view_interface> { +private: + using _Value = _Gen_value_t<_Rty, _Vty>; + static_assert(same_as, _Value> && is_object_v<_Value>, + "generator's value type must be a cv-unqualified object type"); + + using _Ref = _Gen_reference_t<_Rty, _Vty>; + static_assert( + is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), + "generator's second argument must be a reference type or a cv-unqualified " + "copy-constructible object type"); + + using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; + + static_assert(common_reference_with<_Ref&&, _Value&> && common_reference_with<_Ref&&, _RRef&&> + && common_reference_with<_RRef&&, const _Value&>, + "an iterator with the selected value and reference types cannot model indirectly_readable"); + + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers"); + + friend _Gen_promise_base<_Gen_yield_t<_Ref>>; + +public: + 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)}; + } + }; + _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>); +#endif // __cpp_lib_is_pointer_interconvertible + + generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} + + ~generator() { + if (_Coro) { + _Coro.destroy(); + } + } + + generator& operator=(generator _That) noexcept { + _STD swap(_Coro, _That._Coro); + return *this; + } + + _NODISCARD _Gen_iter<_Value, _Ref> begin() { + // Pre: _Coro is suspended at its initial suspend point + _STL_ASSERT(_Coro, "Can't call begin on moved-from generator"); + _Coro.resume(); + return _Gen_iter<_Value, _Ref>{ + _Gen_secret_tag{}, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>>::from_address(_Coro.address())}; + } + + _NODISCARD default_sentinel_t end() const noexcept { + return default_sentinel; + } + +private: + coroutine_handle _Coro = nullptr; + + explicit generator(_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} +}; + +_STD_END + +// TRANSITION, non-_Ugly attribute tokens +#pragma pop_macro("empty_bases") + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) + +#endif // !_HAS_CXX23 || !defined(__cpp_impl_coroutine) +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _GENERATOR_ diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index 74d6d4699e5..09c3fed37f6 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -69,6 +69,7 @@ "fstream", "functional", "future", + "generator", // "hash_map", // non-Standard, will be removed soon // "hash_set", // non-Standard, will be removed soon "initializer_list", diff --git a/stl/inc/ranges b/stl/inc/ranges index e0c1d7a9bfe..98094f2b2f6 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -77,6 +77,21 @@ namespace ranges { template requires (_Extent != dynamic_extent) inline constexpr auto _Compile_time_max_size> = _Extent; + +#ifdef __cpp_lib_byte + using _Elements_alloc_type = byte; +#else + using _Elements_alloc_type = char; +#endif + + _EXPORT_STD template > + struct elements_of { + /* [[no_unique_address]] */ _Rng range; + /* [[no_unique_address]] */ _Alloc allocator{}; + }; + + template > + elements_of(_Rng&&, _Alloc = {}) -> elements_of<_Rng&&, _Alloc>; #endif // _HAS_CXX23 // clang-format off diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index b18f335ac48..22e0936b1e5 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -376,6 +376,7 @@ // P2474R2 views::repeat // P2494R2 Relaxing Range Adaptors To Allow Move-Only Types // P2499R0 string_view Range Constructor Should Be explicit +// P2502R2 : Synchronous Coroutine Generator For Ranges // P2505R5 Monadic Functions For expected // P2539R4 Synchronizing print() With The Underlying Stream // P2540R1 Empty Product For Certain Views @@ -1833,7 +1834,12 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_formatters 202302L #endif // defined(__cpp_lib_concepts) -#define __cpp_lib_forward_like 202207L +#define __cpp_lib_forward_like 202207L + +#if defined(__cpp_lib_concepts) && defined(__cpp_lib_byte) && defined(__cpp_impl_coroutine) +#define __cpp_lib_generator 202207L +#endif // defined(__cpp_lib_concepts) && defined(__cpp_lib_byte) && defined(__cpp_impl_coroutine) + #define __cpp_lib_invoke_r 202106L #define __cpp_lib_ios_noreplace 202207L #define __cpp_lib_is_scoped_enum 202011L diff --git a/stl/modules/std.ixx b/stl/modules/std.ixx index 7a5fdbf7cef..d81e0f556bf 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -66,6 +66,7 @@ export module std; #include #include #include +#include #include #include #include diff --git a/tests/std/include/test_header_units_and_modules.hpp b/tests/std/include/test_header_units_and_modules.hpp index ae0e81eda4e..806ea9f0f12 100644 --- a/tests/std/include/test_header_units_and_modules.hpp +++ b/tests/std/include/test_header_units_and_modules.hpp @@ -280,6 +280,20 @@ void test_future() { assert(f.get() == 1729); } +#if TEST_STANDARD >= 23 +void test_generator() { + using namespace std; + puts("Testing ."); + auto some_ints = [](int hi) -> generator { + for (int i = 0; i < hi; ++i) { + co_yield i; + } + }; + constexpr int bound = 42; + assert(ranges::equal(some_ints(bound), views::iota(0, bound))); +} +#endif // TEST_STANDARD >= 23 + void test_initializer_list() { using namespace std; puts("Testing ."); @@ -1145,6 +1159,9 @@ void all_cpp_header_tests() { test_fstream(); test_functional(); test_future(); +#if TEST_STANDARD >= 23 + test_generator(); +#endif // TEST_STANDARD >= 23 test_initializer_list(); test_iomanip(); test_ios(); diff --git a/tests/std/test.lst b/tests/std/test.lst index 23a3ac90014..1f528074ac5 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -644,6 +644,7 @@ tests\P2467R1_exclusive_mode_fstreams tests\P2474R2_views_repeat tests\P2474R2_views_repeat_death tests\P2494R2_move_only_range_adaptors +tests\P2502R2_generator tests\P2505R5_monadic_functions_for_std_expected tests\P2510R3_text_formatting_pointers tests\P2517R1_apply_conditional_noexcept diff --git a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc index d4960889f74..f01082994e8 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc +++ b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc @@ -28,6 +28,7 @@ "fstream", "functional", "future", + "generator", "initializer_list", "iomanip", "ios", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index 1617b5e2859..7357e499a7a 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -36,6 +36,7 @@ import ; import ; import ; import ; +import ; import ; import ; import ; diff --git a/tests/std/tests/P2502R2_generator/env.lst b/tests/std/tests/P2502R2_generator/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P2502R2_generator/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp new file mode 100644 index 00000000000..0f5950ee33a --- /dev/null +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -0,0 +1,339 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ranges = std::ranges; + +template +constexpr bool static_checks() { + static_assert(ranges::input_range); + static_assert(ranges::view); + static_assert(!ranges::forward_range); + static_assert(!ranges::borrowed_range); + static_assert(!ranges::common_range); + + static_assert(std::same_as, V>); + static_assert(std::same_as, std::ptrdiff_t>); + static_assert(std::same_as, R>); + static_assert(std::same_as, RR>); + + // Non-portable size checks + static_assert(sizeof(G) == sizeof(void*)); + static_assert(sizeof(typename G::promise_type) == 3 * sizeof(void*)); + static_assert(sizeof(ranges::iterator_t) == sizeof(void*)); + + return true; +} + +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, const int&, const int&&>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, int&, int&&>()); +static_assert(static_checks, int, int, int>()); + +// From the proposal: +std::generator iota(int start = 0) { + while (true) { + co_yield start; + ++start; + } +} + +void f(std::ostream& os) { + os << '"'; + for (auto i : iota() | std::views::take(3)) { + os << i << ' '; // prints "0 1 2 " + } + os << "\"\n"; +} + +template +std::generator, ranges::range_reference_t>, + std::tuple, ranges::range_value_t>> + zip(Rng1 r1, Rng2 r2) { + auto it1 = ranges::begin(r1); + auto it2 = ranges::begin(r2); + const auto end1 = ranges::end(r1); + const auto end2 = ranges::end(r2); + for (; it1 != end1 && it2 != end2; ++it1, ++it2) { + co_yield {*it1, *it2}; + } +} + +// Not from the proposal: +template +std::generator meow(const int hi) { + for (int i = 0; i < hi; ++i) { + co_yield i; + } +} + +template +void dump(std::ostream& os, R&& r) { + os << '{'; + bool first = true; + for (auto&& e : r) { + if (first) { + first = false; + } else { + os << ", "; + } + os << e; + } + os << "}\n"; +} + +template +struct stateless_alloc { + using value_type = T; + + stateless_alloc() = default; + + template + constexpr stateless_alloc(const stateless_alloc&) noexcept {} + + T* allocate(const std::size_t n) { + void* vp; + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); + } else { + vp = std::malloc(n * sizeof(T)); + } + + if (vp) { + return static_cast(vp); + } + + throw std::bad_alloc{}; + } + + void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + ::_aligned_free(vp); + } else { + std::free(vp); + } + } + + template + constexpr bool operator==(const stateless_alloc&) noexcept { + return true; + } +}; +static_assert(std::default_initializable>); + +template +struct stateful_alloc { + using value_type = T; + + int domain; + + explicit stateful_alloc(int dom) noexcept : domain{dom} {} + + template + constexpr stateful_alloc(const stateful_alloc& that) noexcept : domain{that.domain} {} + + T* allocate(const std::size_t n) { + void* vp; + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); + } else { + vp = std::malloc(n * sizeof(T)); + } + + if (vp) { + return static_cast(vp); + } + + throw std::bad_alloc{}; + } + + void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + ::_aligned_free(vp); + } else { + std::free(vp); + } + } + + template + constexpr bool operator==(const stateful_alloc& that) noexcept { + return this->domain == that.domain; + } +}; +static_assert(!std::default_initializable>); + +void static_allocator_test() { + { + auto g = [](const int hi) -> std::generator> { + constexpr std::size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + assert(ranges::equal(g(1024), ranges::views::iota(0, 1024))); + } + + { + auto g = [](std::allocator_arg_t, stateless_alloc, + const int hi) -> std::generator> { + constexpr std::size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + assert(ranges::equal(g(std::allocator_arg, {}, 1024), ranges::views::iota(0, 1024))); + } + + { + auto g = [](std::allocator_arg_t, stateful_alloc, + const int hi) -> std::generator> { + constexpr std::size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + assert(ranges::equal(g(std::allocator_arg, stateful_alloc{42}, 1024), ranges::views::iota(0, 1024))); + } +} + +void dynamic_allocator_test() { + auto g = [](std::allocator_arg_t, const auto&, const int hi) -> std::generator { + constexpr std::size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + assert(ranges::equal(g(std::allocator_arg, std::allocator{}, 1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(std::allocator_arg, stateless_alloc{}, 1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(std::allocator_arg, stateful_alloc{1729}, 1024), ranges::views::iota(0, 1024))); +} + +void zip_example() { + int length = 0; + for (auto x : zip(std::array{1, 2, 3}, std::vector{10, 20, 30, 40, 50})) { + static_assert(std::same_as>); + assert(std::get<0>(x) * 10 == std::get<1>(x)); + ++length; + } + assert(length == 3); +} + +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 +std::generator iota_repeater(const int hi, const int depth) { + if (depth > 0) { + co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); + co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); + } else { + co_yield ranges::elements_of(meow(hi)); + } +} + +void recursive_test() { + struct some_error {}; + + static constexpr auto might_throw = []() -> std::generator { + co_yield 0; + throw some_error{}; + }; + + static constexpr auto nested_ints = []() -> std::generator { + try { + co_yield ranges::elements_of(might_throw()); + } catch (const some_error&) { + } + co_yield 1; + }; + + assert(ranges::equal(iota_repeater(3, 2), std::array{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2})); + assert(ranges::equal(nested_ints(), std::array{0, 1})); +} + +void arbitrary_range_test() { + auto yield_arbitrary_ranges = []() -> std::generator { + co_yield ranges::elements_of(std::vector{40, 30, 20, 10}); + co_yield ranges::elements_of(ranges::views::iota(0, 4)); + std::forward_list fl{500, 400, 300}; + co_yield ranges::elements_of(fl); + }; + + assert(ranges::equal(yield_arbitrary_ranges(), std::array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); +} +#endif // !(defined(__clang__) && defined(_M_IX86)) + +int main() { + { + std::stringstream ss; + f(ss); + assert(ss.str() == "\"0 1 2 \"\n"); + } + assert(ranges::equal(meow(6), ranges::views::iota(0, 6))); + + { + // test with mutable lvalue reference type + auto r = meow(32); + auto pos = r.begin(); + for (int i = 0; i < 16; ++i, ++*pos, ++pos) { + assert(pos != r.end()); + assert(*pos == 2 * i); + } + assert(pos == r.end()); + } + +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 + { + // test with mutable xvalue reference type + auto woof = [](std::size_t size, std::size_t count) -> std::generator&&> { + std::random_device rd{}; + std::uniform_int_distribution dist{0, 99}; + std::vector vec; + while (count-- > 0) { + vec.resize(size); + ranges::generate(vec, [&] { return dist(rd); }); + co_yield std::move(vec); + } + // test yielding lvalue + vec.resize(size); + ranges::generate(vec, [&] { return dist(rd); }); + const auto tmp = vec; + co_yield vec; + assert(tmp == vec); + }; + + constexpr size_t size = 16; + auto r = woof(size, 4); + for (auto i = r.begin(); i != r.end(); ++i) { + std::vector vec = *i; + assert(vec.size() == size); + assert((*i).empty()); + } + } +#endif // !(defined(__clang__) && defined(_M_IX86)) + + static_allocator_test(); + dynamic_allocator_test(); + + zip_example(); +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 + recursive_test(); + arbitrary_range_test(); +#endif // !(defined(__clang__) && defined(_M_IX86)) +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 7d8d9901dec..e33a08f282b 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -423,6 +423,12 @@ STATIC_ASSERT(__cpp_lib_gcd_lcm == 201606L); #error __cpp_lib_gcd_lcm is defined #endif +#if _HAS_CXX23 && defined(__cpp_lib_byte) && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +STATIC_ASSERT(__cpp_lib_generator == 202207L); +#elif defined(__cpp_lib_generator) +#error __cpp_lib_generator is defined +#endif + STATIC_ASSERT(__cpp_lib_generic_associative_lookup == 201304L); #if _HAS_CXX20 diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 4d07f7d8bba..6ee5539818a 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -31,6 +31,7 @@ PM_CL="/DMEOW_HEADER=forward_list" PM_CL="/DMEOW_HEADER=fstream" PM_CL="/DMEOW_HEADER=functional" PM_CL="/DMEOW_HEADER=future" +PM_CL="/DMEOW_HEADER=generator" PM_CL="/DMEOW_HEADER=initializer_list" PM_CL="/DMEOW_HEADER=iomanip" PM_CL="/DMEOW_HEADER=ios" From fb6a4cb50fe61bb1a5fac8f093f3cd29d9137b18 Mon Sep 17 00:00:00 2001 From: "S. B. Tam" Date: Wed, 31 Jan 2024 13:13:15 +0800 Subject: [PATCH 02/50] ``: Mark strengthened noexcept (plus tiny drive-by changes) (#4351) --- stl/inc/generator | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 22ff90589b7..4cc08ae93fc 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -210,7 +210,7 @@ public: } _NODISCARD auto yield_value(const remove_reference_t<_Yielded>& _Val) noexcept( - is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) + is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) /* strengthened */ requires (is_rvalue_reference_v<_Yielded> && constructible_from, const remove_reference_t<_Yielded>&>) { @@ -225,7 +225,7 @@ public: template <_RANGES input_range _Rng, class _Alloc> requires convertible_to<_RANGES range_reference_t<_Rng>, _Yielded> - _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) noexcept { + _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) { using _Vty = _RANGES range_value_t<_Rng>; return _Nested_awaitable<_Yielded, _Vty, _Alloc>{ [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, @@ -238,7 +238,7 @@ public: void await_transform() = delete; - void return_void() noexcept {} + void return_void() const noexcept {} void unhandled_exception() { if (_Info) { @@ -362,7 +362,7 @@ public: return *this; } - _NODISCARD _Ref operator*() const noexcept { + _NODISCARD _Ref operator*() const noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr))) { _STL_ASSERT(!_Coro.done(), "Can't dereference generator end iterator"); return static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr); } @@ -377,8 +377,9 @@ public: ++*this; } - _NODISCARD bool operator==(default_sentinel_t) const noexcept { - return _Coro.done(); + _NODISCARD_FRIEND bool operator==(const _Gen_iter& _It, default_sentinel_t) noexcept /* strengthened */ + { + return _It._Coro.done(); } private: From 12621bee61416569f10b79a60604a4a04555d59e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 12:32:03 -0800 Subject: [PATCH 03/50] `concepts_latest_matrix.lst` => `usual_latest_matrix.lst` --- tests/std/tests/P2502R2_generator/env.lst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/P2502R2_generator/env.lst b/tests/std/tests/P2502R2_generator/env.lst index 18e2d7c71ec..642f530ffad 100644 --- a/tests/std/tests/P2502R2_generator/env.lst +++ b/tests/std/tests/P2502R2_generator/env.lst @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -RUNALL_INCLUDE ..\concepts_latest_matrix.lst +RUNALL_INCLUDE ..\usual_latest_matrix.lst From 53bf3a442c09103a30c5c1ad3e7f3a852b200c21 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 12:40:55 -0800 Subject: [PATCH 04/50] Adjust VSO_0157762_feature_test_macros to match yvals_core.h. --- .../tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index acd7c5a5739..bffcd54957a 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -423,7 +423,7 @@ STATIC_ASSERT(__cpp_lib_gcd_lcm == 201606L); #error __cpp_lib_gcd_lcm is defined #endif -#if _HAS_CXX23 && defined(__cpp_lib_byte) && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +#if _HAS_CXX23 STATIC_ASSERT(__cpp_lib_generator == 202207L); #elif defined(__cpp_lib_generator) #error __cpp_lib_generator is defined From 84a6efe718f57d08df5722986b2197b7c352e432 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 13:06:51 -0800 Subject: [PATCH 05/50] Update ``'s `_HAS_CXX23` guard and message. Also add a missing semicolon - this emits a `_Pragma`. --- stl/inc/generator | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 4cc08ae93fc..c56cb5254a9 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -9,9 +9,9 @@ #include #if _STL_COMPILER_PREPROCESSOR -#if !_HAS_CXX23 || !defined(__cpp_impl_coroutine) || !defined(__cpp_lib_concepts) // TRANSITION, GH-395 -_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later coroutine support.") -#else // ^^^ no coroutine support / coroutines vvv +#if !_HAS_CXX23 +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later."); +#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv #include #include @@ -468,6 +468,6 @@ _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) #pragma pack(pop) -#endif // !_HAS_CXX23 || !defined(__cpp_impl_coroutine) +#endif // ^^^ _HAS_CXX23 ^^^ #endif // _STL_COMPILER_PREPROCESSOR #endif // _GENERATOR_ From f7e6bf1dae7ef92776b9a04805ad994ca9f1877e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 13:50:59 -0800 Subject: [PATCH 06/50] Work around VSO-1951821 by skipping `stateful_alloc` coverage for EDG. VSO-1951821 "EDG instantiates the wrong overload of `promise_type::operator new` for `generator` machinery" --- tests/std/tests/P2502R2_generator/test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 0f5950ee33a..20ec29a5dc4 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -199,6 +199,7 @@ void static_allocator_test() { assert(ranges::equal(g(std::allocator_arg, {}, 1024), ranges::views::iota(0, 1024))); } +#ifndef __EDG__ // TRANSITION, VSO-1951821 { auto g = [](std::allocator_arg_t, stateful_alloc, const int hi) -> std::generator> { @@ -211,6 +212,7 @@ void static_allocator_test() { assert(ranges::equal(g(std::allocator_arg, stateful_alloc{42}, 1024), ranges::views::iota(0, 1024))); } +#endif // ^^^ no workaround ^^^ } void dynamic_allocator_test() { @@ -224,7 +226,9 @@ void dynamic_allocator_test() { assert(ranges::equal(g(std::allocator_arg, std::allocator{}, 1024), ranges::views::iota(0, 1024))); assert(ranges::equal(g(std::allocator_arg, stateless_alloc{}, 1024), ranges::views::iota(0, 1024))); +#ifndef __EDG__ // TRANSITION, VSO-1951821 assert(ranges::equal(g(std::allocator_arg, stateful_alloc{1729}, 1024), ranges::views::iota(0, 1024))); +#endif // ^^^ no workaround ^^^ } void zip_example() { From e432c39651fbddc3715d2a0790899e895ff95d52 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 14:30:45 -0800 Subject: [PATCH 07/50] std.ixx: Guard `` with `_HAS_CXX23`. --- stl/modules/std.ixx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stl/modules/std.ixx b/stl/modules/std.ixx index d81e0f556bf..30d960de720 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -66,7 +66,9 @@ export module std; #include #include #include +#if _HAS_CXX23 #include +#endif // _HAS_CXX23 #include #include #include From d2aa714e0eb8bc177f1b76804bb0f68d791db996 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 14:31:30 -0800 Subject: [PATCH 08/50] Guard `import ;` with `#if TEST_STANDARD >= 23`. --- tests/std/tests/P1502R1_standard_library_header_units/test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index 7357e499a7a..a4e51a71043 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -36,7 +36,9 @@ import ; import ; import ; import ; +#if TEST_STANDARD >= 23 import ; +#endif // TEST_STANDARD >= 23 import ; import ; import ; From af01b6322c60188e1d5b17e7bed6a6d359ac3702 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 6 Feb 2024 14:35:21 -0800 Subject: [PATCH 09/50] Use `^^^ no workaround ^^^` comments around good code. --- stl/inc/generator | 10 +++++----- tests/std/tests/P2502R2_generator/test.cpp | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index c56cb5254a9..74bbf78df2d 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -195,7 +195,7 @@ public: #ifndef _PREFAST_ // TRANSITION, VSO-1662733 _NODISCARD -#endif // _PREFAST_ +#endif // ^^^ no workaround ^^^ suspend_always initial_suspend() noexcept { return {}; } @@ -260,7 +260,7 @@ private: constexpr void await_suspend(coroutine_handle<_Promise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); -#endif // __cpp_lib_is_pointer_interconvertible +#endif // ^^^ no workaround ^^^ _Gen_promise_base& _Current = _Handle.promise(); _Current._Ptr = _STD addressof(_Val); @@ -284,7 +284,7 @@ private: _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_Promise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); -#endif // __cpp_lib_is_pointer_interconvertible +#endif // ^^^ no workaround ^^^ _Gen_promise_base& _Current = _Handle.promise(); if (!_Current._Info) { @@ -317,7 +317,7 @@ private: _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend(coroutine_handle<_Promise> _Current) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); -#endif // __cpp_lib_is_pointer_interconvertible +#endif // ^^^ no workaround ^^^ auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); _Gen_promise_base& _Parent_promise = _Nested._Parent.promise(); @@ -425,7 +425,7 @@ public: #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>); -#endif // __cpp_lib_is_pointer_interconvertible +#endif // ^^^ no workaround ^^^ generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 20ec29a5dc4..9f072f1cf18 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -281,7 +281,7 @@ void arbitrary_range_test() { assert(ranges::equal(yield_arbitrary_ranges(), std::array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); } -#endif // !(defined(__clang__) && defined(_M_IX86)) +#endif // ^^^ no workaround ^^^ int main() { { @@ -330,7 +330,7 @@ int main() { assert((*i).empty()); } } -#endif // !(defined(__clang__) && defined(_M_IX86)) +#endif // ^^^ no workaround ^^^ static_allocator_test(); dynamic_allocator_test(); @@ -339,5 +339,5 @@ int main() { #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 recursive_test(); arbitrary_range_test(); -#endif // !(defined(__clang__) && defined(_M_IX86)) +#endif // ^^^ no workaround ^^^ } From d5c525de327a11e6f1e1902576b83fc9bfb8cb44 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 12 Mar 2024 19:08:06 +0800 Subject: [PATCH 10/50] ``: Make nested types of `generator` ADL-proof (#4464) --- stl/inc/generator | 132 ++++++++++++--------- stl/inc/ranges | 19 +-- tests/std/tests/P2502R2_generator/test.cpp | 39 ++++++ 3 files changed, 124 insertions(+), 66 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 74bbf78df2d..20a3b4d4046 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -188,8 +188,18 @@ using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; template using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; +template +struct _Gen_promise_base_provider { + class _Base; +}; + +template +struct _Gen_iter_provider { + class _Iterator; +}; + template -class _Gen_promise_base { +class _Gen_promise_base_provider<_Yielded>::_Base { public: _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); @@ -220,20 +230,21 @@ public: template requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { - return _Nested_awaitable<_Rty, _Vty, _Alloc>{std::move(_Elem.range)}; + using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; + return _Nested_awaitable{std::move(_Elem.range)}; } template <_RANGES input_range _Rng, class _Alloc> requires convertible_to<_RANGES range_reference_t<_Rng>, _Yielded> _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) { - using _Vty = _RANGES range_value_t<_Rng>; - return _Nested_awaitable<_Yielded, _Vty, _Alloc>{ - [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, - const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { - for (; _It != _Se; ++_It) { - co_yield static_cast<_Yielded>(*_It); - } - }(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; + using _Vty = _RANGES range_value_t<_Rng>; + using _Nested_awaitable = _Nested_awaitable_provider<_Yielded, _Vty, _Alloc>::_Awaitable; + return _Nested_awaitable{[](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, + const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { + for (; _It != _Se; ++_It) { + co_yield static_cast<_Yielded>(*_It); + } + }(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; } void await_transform() = delete; @@ -259,11 +270,11 @@ private: template constexpr void await_suspend(coroutine_handle<_Promise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _Promise>); #endif // ^^^ no workaround ^^^ - _Gen_promise_base& _Current = _Handle.promise(); - _Current._Ptr = _STD addressof(_Val); + _Base& _Current = _Handle.promise(); + _Current._Ptr = _STD addressof(_Val); } constexpr void await_resume() const noexcept {} @@ -271,8 +282,8 @@ private: struct _Nest_info { exception_ptr _Except; - coroutine_handle<_Gen_promise_base> _Parent; - coroutine_handle<_Gen_promise_base> _Root; + coroutine_handle<_Base> _Parent; + coroutine_handle<_Base> _Root; }; struct _Final_awaiter { @@ -283,17 +294,17 @@ private: template _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_Promise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _Promise>); #endif // ^^^ no workaround ^^^ - _Gen_promise_base& _Current = _Handle.promise(); + _Base& _Current = _Handle.promise(); if (!_Current._Info) { return _STD noop_coroutine(); } - coroutine_handle<_Gen_promise_base> _Cont = _Current._Info->_Parent; - _Current._Info->_Root.promise()._Top = _Cont; - _Current._Info = nullptr; + coroutine_handle<_Base> _Cont = _Current._Info->_Parent; + _Current._Info->_Root.promise()._Top = _Cont; + _Current._Info = nullptr; return _Cont; } @@ -301,63 +312,68 @@ private: }; template - struct _Nested_awaitable { - _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); + struct _Nested_awaitable_provider { + struct _Awaitable { + _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); - _Nest_info _Nested; - generator<_Rty, _Vty, _Alloc> _Gen; + _Nest_info _Nested; + generator<_Rty, _Vty, _Alloc> _Gen; - explicit _Nested_awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} + explicit _Awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} - _NODISCARD bool await_ready() noexcept { - return !_Gen._Coro; - } + _NODISCARD bool await_ready() noexcept { + return !_Gen._Coro; + } - template - _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend(coroutine_handle<_Promise> _Current) noexcept { + template + _NODISCARD coroutine_handle<_Base> await_suspend(coroutine_handle<_Promise> _Current) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _Promise>); #endif // ^^^ no workaround ^^^ - auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); - _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); - _Gen_promise_base& _Parent_promise = _Nested._Parent.promise(); - if (_Parent_promise._Info) { - _Nested._Root = _Parent_promise._Info->_Root; - } else { - _Nested._Root = _Nested._Parent; + auto _Target = coroutine_handle<_Base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Base>::from_address(_Current.address()); + _Base& _Parent_promise = _Nested._Parent.promise(); + if (_Parent_promise._Info) { + _Nested._Root = _Parent_promise._Info->_Root; + } else { + _Nested._Root = _Nested._Parent; + } + _Nested._Root.promise()._Top = _Target; + _Target.promise()._Info = _STD addressof(_Nested); + return _Target; } - _Nested._Root.promise()._Top = _Target; - _Target.promise()._Info = _STD addressof(_Nested); - return _Target; - } - void await_resume() { - if (_Nested._Except) { - _STD rethrow_exception(_STD move(_Nested._Except)); + void await_resume() { + if (_Nested._Except) { + _STD rethrow_exception(_STD move(_Nested._Except)); + } } - } + }; }; template - friend class _Gen_iter; + friend struct _Gen_iter_provider; // _Top and _Info are mutually exclusive, and could potentially be merged. - coroutine_handle<_Gen_promise_base> _Top = coroutine_handle<_Gen_promise_base>::from_promise(*this); - add_pointer_t<_Yielded> _Ptr = nullptr; - _Nest_info* _Info = nullptr; + coroutine_handle<_Base> _Top = coroutine_handle<_Base>::from_promise(*this); + add_pointer_t<_Yielded> _Ptr = nullptr; + _Nest_info* _Info = nullptr; }; +template +using _Gen_promise_base = _Gen_promise_base_provider<_Yielded>::_Base; + struct _Gen_secret_tag {}; template -class _Gen_iter { +class _Gen_iter_provider<_Value, _Ref>::_Iterator { public: using value_type = _Value; using difference_type = ptrdiff_t; - _Gen_iter(_Gen_iter&& _That) noexcept : _Coro{_STD exchange(_That._Coro, {})} {} + _Iterator(_Iterator&& _That) noexcept : _Coro{_STD exchange(_That._Coro, {})} {} - _Gen_iter& operator=(_Gen_iter&& _That) noexcept { + _Iterator& operator=(_Iterator&& _That) noexcept { _Coro = _STD exchange(_That._Coro, {}); return *this; } @@ -367,7 +383,7 @@ public: return static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr); } - _Gen_iter& operator++() { + _Iterator& operator++() { _STL_ASSERT(!_Coro.done(), "Can't increment generator end iterator"); _Coro.promise()._Top.resume(); return *this; @@ -377,7 +393,7 @@ public: ++*this; } - _NODISCARD_FRIEND bool operator==(const _Gen_iter& _It, default_sentinel_t) noexcept /* strengthened */ + _NODISCARD_FRIEND bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ { return _It._Coro.done(); } @@ -386,7 +402,7 @@ private: template friend class generator; - explicit _Gen_iter(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept + explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept : _Coro{_Coro_} {} coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro; @@ -440,11 +456,11 @@ public: return *this; } - _NODISCARD _Gen_iter<_Value, _Ref> begin() { + _NODISCARD _Gen_iter_provider<_Value, _Ref>::_Iterator begin() { // Pre: _Coro is suspended at its initial suspend point _STL_ASSERT(_Coro, "Can't call begin on moved-from generator"); _Coro.resume(); - return _Gen_iter<_Value, _Ref>{ + return typename _Gen_iter_provider<_Value, _Ref>::_Iterator{ _Gen_secret_tag{}, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>>::from_address(_Coro.address())}; } diff --git a/stl/inc/ranges b/stl/inc/ranges index 26c0e708c0e..c6851ca76ce 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -86,20 +86,23 @@ namespace ranges { requires (_Extent != dynamic_extent) inline constexpr auto _Compile_time_max_size> = _Extent; -#ifdef __cpp_lib_byte - using _Elements_alloc_type = byte; -#else - using _Elements_alloc_type = char; -#endif - - _EXPORT_STD template > +#if defined(__cpp_lib_byte) + _EXPORT_STD template > +#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv + _EXPORT_STD template +#endif // ^^^ !defined(__cpp_lib_byte) ^^^ struct elements_of { /* [[no_unique_address]] */ _Rng range; /* [[no_unique_address]] */ _Alloc allocator{}; }; - template > +#if defined(__cpp_lib_byte) + template > elements_of(_Rng&&, _Alloc = {}) -> elements_of<_Rng&&, _Alloc>; +#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv + template + elements_of(_Rng&&, _Alloc) -> elements_of<_Rng&&, _Alloc>; +#endif // ^^^ !defined(__cpp_lib_byte) ^^^ #endif // _HAS_CXX23 // clang-format off diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 9f072f1cf18..75dcad61af8 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include namespace ranges = std::ranges; @@ -281,6 +283,39 @@ void arbitrary_range_test() { assert(ranges::equal(yield_arbitrary_ranges(), std::array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); } + +#ifndef _M_CEE // TRANSITION, VSO-1659496 +template +struct holder { + T t; +}; + +struct incomplete; + +void adl_proof_test() { + using validator = holder*; + auto yield_range = []() -> std::generator { + co_yield ranges::elements_of( + ranges::views::repeat(nullptr, 42) | ranges::views::transform([](std::nullptr_t) { return validator{}; })); + }; + + using R = decltype(yield_range()); + static_assert(ranges::input_range); + + using It = ranges::iterator_t; + static_assert(std::is_same_v()), It*>); + + using Promise = R::promise_type; + static_assert(std::is_same_v()), Promise*>); + + std::size_t i = 0; + for (const auto elem : yield_range()) { + ++i; + assert(elem == nullptr); + } + assert(i == 42); +} +#endif // ^^^ no workaround ^^^ #endif // ^^^ no workaround ^^^ int main() { @@ -339,5 +374,9 @@ int main() { #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 recursive_test(); arbitrary_range_test(); + +#ifndef _M_CEE // TRANSITION, VSO-1659496 + adl_proof_test(); +#endif // ^^^ no workaround ^^^ #endif // ^^^ no workaround ^^^ } From 14bbb8e4950f2a1426ea6b3dc72d4535facffadc Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 13 Mar 2024 00:41:56 -0700 Subject: [PATCH 11/50] ``: Various fixes and cleanups (#4471) --- stl/inc/coroutine | 14 +-- stl/inc/generator | 108 ++++++++++++--------- tests/std/tests/P2502R2_generator/test.cpp | 1 + 3 files changed, 69 insertions(+), 54 deletions(-) diff --git a/stl/inc/coroutine b/stl/inc/coroutine index c41e6b36f59..94933610fbe 100644 --- a/stl/inc/coroutine +++ b/stl/inc/coroutine @@ -92,12 +92,12 @@ private: void* _Ptr = nullptr; }; -_EXPORT_STD template +_EXPORT_STD template struct coroutine_handle { constexpr coroutine_handle() noexcept = default; constexpr coroutine_handle(nullptr_t) noexcept {} - _NODISCARD static coroutine_handle from_promise(_Promise& _Prom) noexcept { // strengthened + _NODISCARD static coroutine_handle from_promise(_CoroPromise& _Prom) noexcept { // strengthened const auto _Prom_ptr = const_cast(static_cast(_STD addressof(_Prom))); const auto _Frame_ptr = __builtin_coro_promise(_Prom_ptr, 0, true); coroutine_handle _Result; @@ -144,8 +144,8 @@ struct coroutine_handle { __builtin_coro_destroy(_Ptr); } - _NODISCARD _Promise& promise() const noexcept { // strengthened - return *reinterpret_cast<_Promise*>(__builtin_coro_promise(_Ptr, 0, false)); + _NODISCARD _CoroPromise& promise() const noexcept { // strengthened + return *reinterpret_cast<_CoroPromise*>(__builtin_coro_promise(_Ptr, 0, false)); } private: @@ -184,10 +184,10 @@ _NODISCARD constexpr bool operator>=(const coroutine_handle<> _Left, const corou } #endif // ^^^ !_HAS_CXX20 ^^^ -template -struct hash> { +template +struct hash> { _NODISCARD _STATIC_CALL_OPERATOR size_t operator()( - const coroutine_handle<_Promise>& _Coro) _CONST_CALL_OPERATOR noexcept { + const coroutine_handle<_CoroPromise>& _Coro) _CONST_CALL_OPERATOR noexcept { return _Hash_representation(_Coro.address()); } }; diff --git a/stl/inc/generator b/stl/inc/generator index 20a3b4d4046..10777a59e3e 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -49,9 +49,9 @@ private: return _Al.allocate(_Count); } else { // store stateful allocator - static 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); + 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); const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); @@ -92,8 +92,8 @@ public: _Alloc _Al{_STD move(_Stored_al)}; _Stored_al.~_Alloc(); - static constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - const size_t _Count = (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); + 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); } } @@ -102,7 +102,33 @@ public: template <> class _Promise_allocator { // type-erased allocator private: - using _Dealloc_fn = void (*)(void*, size_t); + using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; + + template + 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); + } + + template + static void __stdcall _Dealloc_stateful(void* const _Ptr, size_t _Size) noexcept { + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + + _Size += sizeof(_Dealloc_fn); + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); + _Alloc _Al{_STD move(_Stored_al)}; + _Stored_al.~_Alloc(); + + const size_t _Count = (_Size + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); + } + + static void __stdcall _Dealloc_delete(void* const _Ptr, const size_t _Size) noexcept { + ::operator delete[](_Ptr, _Size + sizeof(_Dealloc_fn)); + } template static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { @@ -111,12 +137,7 @@ private: if constexpr (default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { // don't store stateless allocator - const _Dealloc_fn _Dealloc = [](void* const _Ptr, const size_t _Size) { - _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); - }; + 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); @@ -124,19 +145,9 @@ private: return _Ptr; } else { // store stateful allocator - static constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - - const _Dealloc_fn _Dealloc = [](void* const _Ptr, size_t _Size) { - _Size += sizeof(_Dealloc_fn); - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - auto& _Stored_al = *reinterpret_cast(_Al_address); - _Alloc _Al{_STD move(_Stored_al)}; - _Stored_al.~_Alloc(); + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - const size_t _Count = (_Size + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); - }; + 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); @@ -152,9 +163,7 @@ private: public: static void* operator new(const size_t _Size) { // default: new/delete void* const _Ptr = ::operator new[](_Size + sizeof(_Dealloc_fn)); - const _Dealloc_fn _Dealloc = [](void* const _Ptr, const size_t _Size) { - ::operator delete[](_Ptr, _Size + sizeof(_Dealloc_fn)); - }; + const _Dealloc_fn _Dealloc = _Dealloc_delete; _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); return _Ptr; } @@ -231,7 +240,7 @@ public: requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; - return _Nested_awaitable{std::move(_Elem.range)}; + return _Nested_awaitable{_STD move(_Elem.range)}; } template <_RANGES input_range _Rng, class _Alloc> @@ -239,12 +248,15 @@ public: _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) { using _Vty = _RANGES range_value_t<_Rng>; using _Nested_awaitable = _Nested_awaitable_provider<_Yielded, _Vty, _Alloc>::_Awaitable; - return _Nested_awaitable{[](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, - const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { + + auto _Lambda = [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, + const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { for (; _It != _Se; ++_It) { co_yield static_cast<_Yielded>(*_It); } - }(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; + }; + return _Nested_awaitable{ + _Lambda(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; } void await_transform() = delete; @@ -255,7 +267,7 @@ public: if (_Info) { _Info->_Except = _STD current_exception(); } else { - throw; + _RERAISE; } } @@ -267,10 +279,10 @@ private: return false; } - template - constexpr void await_suspend(coroutine_handle<_Promise> _Handle) noexcept { + template + constexpr void await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _Promise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); #endif // ^^^ no workaround ^^^ _Base& _Current = _Handle.promise(); @@ -291,10 +303,10 @@ private: return false; } - template - _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_Promise> _Handle) noexcept { + template + _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _Promise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); #endif // ^^^ no workaround ^^^ _Base& _Current = _Handle.promise(); @@ -325,10 +337,10 @@ private: return !_Gen._Coro; } - template - _NODISCARD coroutine_handle<_Base> await_suspend(coroutine_handle<_Promise> _Current) noexcept { + template + _NODISCARD coroutine_handle<_Base> await_suspend(coroutine_handle<_CoroPromise> _Current) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _Promise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); #endif // ^^^ no workaround ^^^ auto _Target = coroutine_handle<_Base>::from_address(_Gen._Coro.address()); _Nested._Parent = coroutine_handle<_Base>::from_address(_Current.address()); @@ -409,25 +421,27 @@ private: }; _EXPORT_STD template -class generator : public ranges::view_interface> { +class generator : public _RANGES view_interface> { private: using _Value = _Gen_value_t<_Rty, _Vty>; static_assert(same_as, _Value> && is_object_v<_Value>, - "generator's value type must be a cv-unqualified object type"); + "generator's value type must be a cv-unqualified object type (N4971 [coro.generator.class]/1.2)"); using _Ref = _Gen_reference_t<_Rty, _Vty>; static_assert( is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), - "generator's second argument must be a reference type or a cv-unqualified " - "copy-constructible object type"); + "generator's selected reference type must be an actual reference type " + "or a cv-unqualified copy-constructible object type (N4971 [coro.generator.class]/1.3)"); using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; static_assert(common_reference_with<_Ref&&, _Value&> && common_reference_with<_Ref&&, _RRef&&> && common_reference_with<_RRef&&, const _Value&>, - "an iterator with the selected value and reference types cannot model indirectly_readable"); + "generator's iterator type must model indirectly_readable, " + "but that's impossible with the selected value and reference types (N4971 [coro.generator.class]/1.4)"); - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers"); + 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>>; diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 75dcad61af8..ddc3af912ed 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include From 7f040446b18b723d4b458569da01ef21324e4bf4 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Wed, 27 Mar 2024 02:23:42 +0100 Subject: [PATCH 12/50] ``: Test `generator::promise_type` (#4534) Co-authored-by: Stephan T. Lavavej --- stl/inc/generator | 41 +-- tests/std/test.lst | 1 + .../tests/P2502R2_generator_promise/env.lst | 4 + .../tests/P2502R2_generator_promise/test.cpp | 260 ++++++++++++++++++ 4 files changed, 287 insertions(+), 19 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..0df72ea1c9d 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -37,21 +37,22 @@ 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>; + 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(_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(_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)); @@ -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(_Convert_size<_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(_Convert_size<_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 {}; } @@ -443,18 +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: - struct __declspec(empty_bases) promise_type : _Promise_allocator<_Alloc>, _Gen_promise_base<_Gen_yield_t<_Ref>> { + using yielded = _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, {})) {} @@ -475,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 { 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..38cccc9ca40 --- /dev/null +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -0,0 +1,260 @@ +// 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 +#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 45c7fd9e98213c6ee5759e6072489977e373c91f Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Wed, 3 Apr 2024 17:43:11 +0200 Subject: [PATCH 13/50] ``: Add death tests (#4558) --- tests/std/test.lst | 1 + .../std/tests/P2502R2_generator_death/env.lst | 4 ++ .../tests/P2502R2_generator_death/test.cpp | 50 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/std/tests/P2502R2_generator_death/env.lst create mode 100644 tests/std/tests/P2502R2_generator_death/test.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index d271be46f78..2d7e7cee3a3 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_death tests\P2502R2_generator_promise tests\P2505R5_monadic_functions_for_std_expected tests\P2510R3_text_formatting_pointers diff --git a/tests/std/tests/P2502R2_generator_death/env.lst b/tests/std/tests/P2502R2_generator_death/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator_death/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_death/test.cpp b/tests/std/tests/P2502R2_generator_death/test.cpp new file mode 100644 index 00000000000..9fcf309e290 --- /dev/null +++ b/tests/std/tests/P2502R2_generator_death/test.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +std::generator gen() { + co_return; +} + +void test_begin_after_initial_suspend_point() { + auto g = gen(); + (void) g.begin(); + (void) g.begin(); +} + +void test_begin_after_moving_from() { + auto g = gen(); + auto g2 = std::move(g); + (void) g.begin(); +} + +void test_end_iterator_dereference() { + auto g = gen(); + auto i = g.begin(); + (void) *i; +} + +void test_end_iterator_incrementation() { + auto g = gen(); + auto i = g.begin(); + ++i; +} + +int main(int argc, char** argv) { + std_testing::death_test_executive exec; + +#ifdef _DEBUG + exec.add_death_tests({ + test_begin_after_initial_suspend_point, + test_begin_after_moving_from, + test_end_iterator_dereference, + test_end_iterator_incrementation, + }); +#endif // _DEBUG + + return exec.run(argc, argv); +} From 79283876f6b18aaae9151829c7a4d7d4cadc33e6 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Wed, 17 Apr 2024 06:31:44 +0200 Subject: [PATCH 14/50] ``: Test `generator::iterator` (#4574) Co-authored-by: Casey Carter --- tests/std/include/test_generator_support.hpp | 81 ++++++++ tests/std/test.lst | 1 + tests/std/tests/P2502R2_generator/test.cpp | 1 - .../tests/P2502R2_generator_iterator/env.lst | 4 + .../tests/P2502R2_generator_iterator/test.cpp | 177 ++++++++++++++++++ .../tests/P2502R2_generator_promise/test.cpp | 51 +---- 6 files changed, 264 insertions(+), 51 deletions(-) create mode 100644 tests/std/include/test_generator_support.hpp create mode 100644 tests/std/tests/P2502R2_generator_iterator/env.lst create mode 100644 tests/std/tests/P2502R2_generator_iterator/test.cpp diff --git a/tests/std/include/test_generator_support.hpp b/tests/std/include/test_generator_support.hpp new file mode 100644 index 00000000000..3fa0b5dfa88 --- /dev/null +++ b/tests/std/include/test_generator_support.hpp @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include +#include +#include +#include +#include + +template +class TestAllocator : public std::allocator { +public: + using value_type = T; + using is_always_equal = AlwaysEqual; + using difference_type = DifferenceType; + using size_type = std::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)), std::align_val_t{alignof(T)})); + } + + void deallocate(T* const p, size_type s) { + ::operator delete(p, s * sizeof(T), std::align_val_t{alignof(T)}); + } + + operator std::pmr::polymorphic_allocator() const { + return {}; + } + + bool operator==(const TestAllocator&) const = default; +}; + +struct MoveOnly { + MoveOnly() = default; + MoveOnly(const MoveOnly&) = delete; + MoveOnly& operator=(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; + MoveOnly& operator=(MoveOnly&&) = default; +}; + +static_assert(std::movable); +static_assert(!std::copyable); + +struct Immovable { + Immovable() = default; + Immovable(Immovable&&) = delete; + Immovable& operator=(Immovable&&) = delete; +}; + +static_assert(!std::movable); + +template +class Proxy { +public: + Proxy(const T&) {} +}; + +template +class Proxy { +public: + Proxy(const T& _value_) : value(_value_) {} + + bool operator==(const Proxy& other) const { + return value == other.value; + } + + bool operator==(const T& x) const { + return value == x; + } + +private: + const T& value; +}; diff --git a/tests/std/test.lst b/tests/std/test.lst index 13b0d7eccca..e41d8be9d2a 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -655,6 +655,7 @@ tests\P2474R2_views_repeat_death tests\P2494R2_move_only_range_adaptors tests\P2502R2_generator tests\P2502R2_generator_death +tests\P2502R2_generator_iterator tests\P2502R2_generator_promise tests\P2505R5_monadic_functions_for_std_expected tests\P2510R3_text_formatting_pointers diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index ddc3af912ed..2232e29981b 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -33,7 +33,6 @@ constexpr bool static_checks() { // Non-portable size checks static_assert(sizeof(G) == sizeof(void*)); static_assert(sizeof(typename G::promise_type) == 3 * sizeof(void*)); - static_assert(sizeof(ranges::iterator_t) == sizeof(void*)); return true; } diff --git a/tests/std/tests/P2502R2_generator_iterator/env.lst b/tests/std/tests/P2502R2_generator_iterator/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator_iterator/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_iterator/test.cpp b/tests/std/tests/P2502R2_generator_iterator/test.cpp new file mode 100644 index 00000000000..6e9954ab450 --- /dev/null +++ b/tests/std/tests/P2502R2_generator_iterator/test.cpp @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include "test_generator_support.hpp" + +using namespace std; + +template +generator generate_zero() { + co_return; +} + +template +using gen_value_t = conditional_t, remove_cvref_t, V>; + +template +using gen_reference_t = conditional_t, Ref&&, Ref>; + +template > + requires default_initializable + && (same_as, ValueType> || constructible_from, ValueType&>) +generator generate_one() { + if constexpr (same_as, ValueType>) { + // non-proxy reference case + if constexpr (is_reference_v) { + remove_reference_t val{}; + co_yield static_cast(val); + } else { + co_yield ValueType{}; + } + } else { + ValueType val{}; + // proxy reference case + if constexpr (is_reference_v) { + // yielding a non-prvalue proxy reference is super weird, but not forbidden + remove_reference_t ref{val}; + co_yield static_cast(ref); + } else { + co_yield Ref{val}; + } + } +} + +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 +template +generator generate_one_recursively() { + co_yield ranges::elements_of{generate_zero()}; + co_yield ranges::elements_of{generate_one()}; + co_yield ranges::elements_of{generate_zero()}; +} +#endif // ^^^ no workaround ^^^ + +template +void test_one() { + using Gen = generator; + using Iter = ranges::iterator_t; + static_assert(input_iterator); + static_assert(sizeof(Iter) == sizeof(void*)); // NB: implementation defined + + // Test member types + static_assert(same_as>); + static_assert(same_as); + + // Test copying functions + static_assert(!is_copy_constructible_v); + static_assert(!is_copy_assignable_v); + + { // Test move constructor + Gen g = generate_zero(); + Iter i = g.begin(); + Iter j = move(i); + assert(j == default_sentinel); + + static_assert(is_nothrow_move_constructible_v); + } + + { // Test move assignment operator + Gen g1 = generate_one(); + Iter i = g1.begin(); + Gen g2 = generate_zero(); + Iter j = g2.begin(); + + same_as decltype(auto) k = (i = move(j)); + assert(&k == &i); + assert(k == default_sentinel); + static_assert(is_nothrow_move_assignable_v); + } + + { // Test indirection + auto g = generate_one(); + auto i = g.begin(); + + same_as> decltype(auto) r = *i; + + using ValueType = gen_value_t; + if constexpr (default_initializable && equality_comparable) { + assert(r == ValueType{}); + } + } + +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 + { // Test pre-incrementation + auto g = generate_one_recursively(); + auto i = g.begin(); + + same_as decltype(auto) i_ref = ++i; + assert(&i_ref == &i); + assert(i_ref == default_sentinel); + } + + { // Test post-incrementation + auto g = generate_one_recursively(); + auto i = g.begin(); + i++; + assert(i == default_sentinel); + + static_assert(is_void_v); + } +#endif // ^^^ no workaround ^^^ + + { // Test equal operator + auto g1 = generate_one(); + auto i = g1.begin(); + auto g2 = generate_zero(); + auto j = g2.begin(); + + same_as decltype(auto) b1 = i == default_sentinel; + assert(!b1); + + same_as decltype(auto) b2 = default_sentinel == j; + assert(b2); + + same_as decltype(auto) b3 = i != default_sentinel; + assert(b3); + + same_as decltype(auto) b4 = default_sentinel != j; + assert(!b4); + } +} + +template +void test_with_allocator() { + 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(); +} diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index 38cccc9ca40..43958421fd7 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -18,37 +18,10 @@ #include #include "range_algorithm_support.hpp" +#include "test_generator_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; @@ -62,28 +35,6 @@ 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) { From c2b0579522089df17b9733863546e437e9a82df4 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Fri, 10 May 2024 23:57:28 +0200 Subject: [PATCH 15/50] ``: Don't include `` (#4620) --- stl/inc/generator | 2 +- stl/inc/ranges | 18 ------------------ stl/inc/xmemory | 22 ++++++++++++++++++++++ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 0df72ea1c9d..e8cd5d54503 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -15,7 +15,7 @@ _EMIT_STL_WARNING(STL4038, "The contents of are available only with #include #include -#include +#include #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) diff --git a/stl/inc/ranges b/stl/inc/ranges index 1f73c77549b..d639701ec76 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -84,24 +84,6 @@ namespace ranges { template requires (_Extent != dynamic_extent) constexpr auto _Compile_time_max_size> = _Extent; - -#if defined(__cpp_lib_byte) - _EXPORT_STD template > -#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv - _EXPORT_STD template -#endif // ^^^ !defined(__cpp_lib_byte) ^^^ - struct elements_of { - /* [[no_unique_address]] */ _Rng range; - /* [[no_unique_address]] */ _Alloc allocator{}; - }; - -#if defined(__cpp_lib_byte) - template > - elements_of(_Rng&&, _Alloc = {}) -> elements_of<_Rng&&, _Alloc>; -#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv - template - elements_of(_Rng&&, _Alloc) -> elements_of<_Rng&&, _Alloc>; -#endif // ^^^ !defined(__cpp_lib_byte) ^^^ #endif // _HAS_CXX23 // clang-format off diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 2580027193f..6ced9914baa 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2587,6 +2587,28 @@ template concept _Transparent = _Is_transparent_v<_Ty>; #endif // _HAS_CXX20 +#if _HAS_CXX23 +namespace ranges { +#if defined(__cpp_lib_byte) + _EXPORT_STD template > +#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv + _EXPORT_STD template +#endif // ^^^ !defined(__cpp_lib_byte) ^^^ + struct elements_of { + /* [[no_unique_address]] */ _Rng range; + /* [[no_unique_address]] */ _Alloc allocator{}; + }; + +#if defined(__cpp_lib_byte) + template > + elements_of(_Rng&&, _Alloc = {}) -> elements_of<_Rng&&, _Alloc>; +#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv + template + elements_of(_Rng&&, _Alloc) -> elements_of<_Rng&&, _Alloc>; +#endif // ^^^ !defined(__cpp_lib_byte) ^^^ +} // namespace ranges +#endif // _HAS_CXX23 + template _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by both to_string and thread::id output // format _UVal into buffer *ending at* _RNext From 7ad2c7361e3cf4a98925671163c5fa9a1b2f8380 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Sat, 11 May 2024 00:03:33 +0200 Subject: [PATCH 16/50] ``: Don't use `operator new[]` and `operator delete[]` (#4621) --- stl/inc/generator | 4 ++-- tests/std/tests/P2502R2_generator_promise/test.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index e8cd5d54503..902d16cd139 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -128,7 +128,7 @@ private: } static void __stdcall _Dealloc_delete(void* const _Ptr, const size_t _Size) noexcept { - ::operator delete[](_Ptr, _Size + sizeof(_Dealloc_fn)); + ::operator delete(_Ptr, _Size + sizeof(_Dealloc_fn)); } template @@ -164,7 +164,7 @@ private: public: static void* operator new(const size_t _Size) { // default: new/delete - void* const _Ptr = ::operator new[](_Size + sizeof(_Dealloc_fn)); + void* const _Ptr = ::operator new(_Size + sizeof(_Dealloc_fn)); const _Dealloc_fn _Dealloc = _Dealloc_delete; _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); return _Ptr; diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index 43958421fd7..3266ba2572e 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,16 @@ using namespace std; +#pragma warning(disable : 28251) // Inconsistent annotation for 'new[]': this instance has no annotations. + +void* operator new[](size_t) { + abort(); +} + +void operator delete[](void*) noexcept { + abort(); +} + template concept HasOperatorNew = requires(Args&&... args) { { Promise::operator new(forward(args)...) } -> same_as; From 0191658a6933ed810d450b3be7790eb349ae0e36 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Sat, 11 May 2024 00:46:28 +0200 Subject: [PATCH 17/50] ``: An attempt to merge `_Top` and `_Info` (#4619) Co-authored-by: Stephan T. Lavavej Co-authored-by: Alex Guteniev --- stl/inc/generator | 62 ++++++++++++++-------- tests/std/tests/P2502R2_generator/test.cpp | 2 +- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 902d16cd139..efcb80fb671 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -266,7 +266,7 @@ public: void return_void() const noexcept {} void unhandled_exception() { - if (_Info) { + if (const auto _Info = _Try_get_nest_info()) { _Info->_Except = _STD current_exception(); } else { _RERAISE; @@ -311,15 +311,13 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - _Base& _Current = _Handle.promise(); - if (!_Current._Info) { - return _STD noop_coroutine(); + if (const auto _Info = _Handle.promise()._Try_get_nest_info()) { + coroutine_handle<_Base> _Cont = _Info->_Parent; + _Info->_Root.promise()._Set_top(_Cont); + return _Cont; } - coroutine_handle<_Base> _Cont = _Current._Info->_Parent; - _Current._Info->_Root.promise()._Top = _Cont; - _Current._Info = nullptr; - return _Cont; + return _STD noop_coroutine(); } void await_resume() noexcept {} @@ -344,16 +342,15 @@ private: #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - auto _Target = coroutine_handle<_Base>::from_address(_Gen._Coro.address()); - _Nested._Parent = coroutine_handle<_Base>::from_address(_Current.address()); - _Base& _Parent_promise = _Nested._Parent.promise(); - if (_Parent_promise._Info) { - _Nested._Root = _Parent_promise._Info->_Root; + auto _Target = coroutine_handle<_Base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Base>::from_address(_Current.address()); + if (const auto _Parent_nest_info = _Nested._Parent.promise()._Try_get_nest_info()) { + _Nested._Root = _Parent_nest_info->_Root; } else { _Nested._Root = _Nested._Parent; } - _Nested._Root.promise()._Top = _Target; - _Target.promise()._Info = _STD addressof(_Nested); + _Nested._Root.promise()._Set_top(_Target); + _Target.promise()._Set_nest_info(_STD addressof(_Nested)); return _Target; } @@ -365,13 +362,35 @@ private: }; }; + _NODISCARD _Nest_info* _Try_get_nest_info() const noexcept { + if ((_Data & 1U) != 0) { + return reinterpret_cast<_Nest_info*>(_Data ^ 1U); + } + + return nullptr; + } + + _NODISCARD coroutine_handle<_Base> _Get_top() const noexcept { + _STL_INTERNAL_CHECK((_Data & 1U) == 0); + return coroutine_handle<_Base>::from_address(reinterpret_cast(_Data)); + } + + void _Set_nest_info(_Nest_info* _Info) noexcept { + _Data = reinterpret_cast(_Info) | 1U; + } + + void _Set_top(coroutine_handle<_Base> _Top) noexcept { + _Data = reinterpret_cast(_Top.address()); + } + template friend struct _Gen_iter_provider; - // _Top and _Info are mutually exclusive, and could potentially be merged. - coroutine_handle<_Base> _Top = coroutine_handle<_Base>::from_promise(*this); + // Least significant bit of `_Data` indicates stored information: + // LSB 0: `_Data` is a top coroutine handle, + // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. + uintptr_t _Data = reinterpret_cast(coroutine_handle<_Base>::from_promise(*this).address()); add_pointer_t<_Yielded> _Ptr = nullptr; - _Nest_info* _Info = nullptr; }; template @@ -392,14 +411,15 @@ public: return *this; } - _NODISCARD _Ref operator*() const noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr))) { + _NODISCARD _Ref operator*() const + noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) { _STL_ASSERT(!_Coro.done(), "Can't dereference generator end iterator"); - return static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr); + return static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr); } _Iterator& operator++() { _STL_ASSERT(!_Coro.done(), "Can't increment generator end iterator"); - _Coro.promise()._Top.resume(); + _Coro.promise()._Get_top().resume(); return *this; } diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 2232e29981b..1ea311f474a 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -32,7 +32,7 @@ constexpr bool static_checks() { // Non-portable size checks static_assert(sizeof(G) == sizeof(void*)); - static_assert(sizeof(typename G::promise_type) == 3 * sizeof(void*)); + static_assert(sizeof(typename G::promise_type) == 2 * sizeof(void*)); return true; } From 62bb92b022b320ed3a9c2fe3b10f8a8a7f57945d Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Sat, 15 Jun 2024 00:38:56 +0200 Subject: [PATCH 18/50] ``: Cleanup `P2502R2_generator/test.cpp` (#4722) --- tests/std/include/test_generator_support.hpp | 32 ++- tests/std/tests/P2502R2_generator/test.cpp | 222 +++++++----------- .../tests/P2502R2_generator_iterator/test.cpp | 2 +- .../tests/P2502R2_generator_promise/test.cpp | 16 +- 4 files changed, 118 insertions(+), 154 deletions(-) diff --git a/tests/std/include/test_generator_support.hpp b/tests/std/include/test_generator_support.hpp index 3fa0b5dfa88..27a249420c9 100644 --- a/tests/std/include/test_generator_support.hpp +++ b/tests/std/include/test_generator_support.hpp @@ -5,39 +5,57 @@ #include #include +#include #include #include #include #include template -class TestAllocator : public std::allocator { +class StatelessAlloc : public std::allocator { public: using value_type = T; using is_always_equal = AlwaysEqual; using difference_type = DifferenceType; using size_type = std::make_unsigned_t; - TestAllocator() = default; + StatelessAlloc() = default; template - TestAllocator(const TestAllocator&) {} + StatelessAlloc(const StatelessAlloc&) {} T* allocate(const size_type s) { - return static_cast(::operator new(static_cast(s * sizeof(T)), std::align_val_t{alignof(T)})); + void* vp; + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + vp = ::_aligned_malloc(s * sizeof(T), alignof(T)); + } else { + vp = std::malloc(s * sizeof(T)); + } + + if (vp) { + return static_cast(vp); + } + + throw std::bad_alloc{}; } - void deallocate(T* const p, size_type s) { - ::operator delete(p, s * sizeof(T), std::align_val_t{alignof(T)}); + void deallocate(T* const p, size_type) { + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + ::_aligned_free(p); + } else { + std::free(p); + } } operator std::pmr::polymorphic_allocator() const { return {}; } - bool operator==(const TestAllocator&) const = default; + bool operator==(const StatelessAlloc&) const = default; }; +static_assert(std::default_initializable>); + struct MoveOnly { MoveOnly() = default; MoveOnly(const MoveOnly&) = delete; diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 1ea311f474a..26da66d1cde 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -8,27 +8,34 @@ #include #include #include +#include +#include +#include #include #include #include +#include +#include #include #include #include -namespace ranges = std::ranges; +#include + +using namespace std; template -constexpr bool static_checks() { +consteval bool static_checks() { static_assert(ranges::input_range); static_assert(ranges::view); static_assert(!ranges::forward_range); static_assert(!ranges::borrowed_range); static_assert(!ranges::common_range); - static_assert(std::same_as, V>); - static_assert(std::same_as, std::ptrdiff_t>); - static_assert(std::same_as, R>); - static_assert(std::same_as, RR>); + static_assert(same_as, V>); + static_assert(same_as, ptrdiff_t>); + static_assert(same_as, R>); + static_assert(same_as, RR>); // Non-portable size checks static_assert(sizeof(G) == sizeof(void*)); @@ -37,32 +44,29 @@ constexpr bool static_checks() { return true; } -static_assert(static_checks, int, int&&, int&&>()); -static_assert(static_checks, int, const int&, const int&&>()); -static_assert(static_checks, int, int&&, int&&>()); -static_assert(static_checks, int, int&, int&&>()); -static_assert(static_checks, int, int, int>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, const int&, const int&&>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, int&, int&&>()); +static_assert(static_checks, int, int, int>()); -// From the proposal: -std::generator iota(int start = 0) { +// [coroutine.generator.overview] Example 1: +generator ints(int start = 0) { while (true) { - co_yield start; - ++start; + co_yield start++; } } -void f(std::ostream& os) { - os << '"'; - for (auto i : iota() | std::views::take(3)) { - os << i << ' '; // prints "0 1 2 " +void f(ostream& os) { + for (auto i : ints() | views::take(3)) { + os << i << ' '; // prints '0 1 2 ' } - os << "\"\n"; } template -std::generator, ranges::range_reference_t>, - std::tuple, ranges::range_value_t>> - zip(Rng1 r1, Rng2 r2) { +generator, ranges::range_reference_t>, + tuple, ranges::range_value_t>> + co_zip(Rng1 r1, Rng2 r2) { auto it1 = ranges::begin(r1); auto it2 = ranges::begin(r2); const auto end1 = ranges::end(r1); @@ -74,66 +78,12 @@ std::generator, ranges::range_referen // Not from the proposal: template -std::generator meow(const int hi) { +generator co_upto(const int hi) { for (int i = 0; i < hi; ++i) { co_yield i; } } -template -void dump(std::ostream& os, R&& r) { - os << '{'; - bool first = true; - for (auto&& e : r) { - if (first) { - first = false; - } else { - os << ", "; - } - os << e; - } - os << "}\n"; -} - -template -struct stateless_alloc { - using value_type = T; - - stateless_alloc() = default; - - template - constexpr stateless_alloc(const stateless_alloc&) noexcept {} - - T* allocate(const std::size_t n) { - void* vp; - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); - } else { - vp = std::malloc(n * sizeof(T)); - } - - if (vp) { - return static_cast(vp); - } - - throw std::bad_alloc{}; - } - - void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - ::_aligned_free(vp); - } else { - std::free(vp); - } - } - - template - constexpr bool operator==(const stateless_alloc&) noexcept { - return true; - } -}; -static_assert(std::default_initializable>); - template struct stateful_alloc { using value_type = T; @@ -145,26 +95,26 @@ struct stateful_alloc { template constexpr stateful_alloc(const stateful_alloc& that) noexcept : domain{that.domain} {} - T* allocate(const std::size_t n) { + T* allocate(const size_t n) { void* vp; if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); } else { - vp = std::malloc(n * sizeof(T)); + vp = malloc(n * sizeof(T)); } if (vp) { return static_cast(vp); } - throw std::bad_alloc{}; + throw bad_alloc{}; } - void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { + void deallocate(void* const vp, [[maybe_unused]] const size_t n) noexcept { if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { ::_aligned_free(vp); } else { - std::free(vp); + free(vp); } } @@ -173,115 +123,112 @@ struct stateful_alloc { return this->domain == that.domain; } }; -static_assert(!std::default_initializable>); +static_assert(!default_initializable>); void static_allocator_test() { { - auto g = [](const int hi) -> std::generator> { - constexpr std::size_t n = 64; + auto g = [](const int hi) -> generator> { + constexpr size_t n = 64; int some_ints[n]; for (int i = 0; i < hi; ++i) { co_yield some_ints[i % n] = i; } }; - assert(ranges::equal(g(1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(1024), views::iota(0, 1024))); } { - auto g = [](std::allocator_arg_t, stateless_alloc, - const int hi) -> std::generator> { - constexpr std::size_t n = 64; + auto g = [](allocator_arg_t, StatelessAlloc, const int hi) -> generator> { + constexpr size_t n = 64; int some_ints[n]; for (int i = 0; i < hi; ++i) { co_yield some_ints[i % n] = i; } }; - assert(ranges::equal(g(std::allocator_arg, {}, 1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(allocator_arg, {}, 1024), views::iota(0, 1024))); } #ifndef __EDG__ // TRANSITION, VSO-1951821 { - auto g = [](std::allocator_arg_t, stateful_alloc, - const int hi) -> std::generator> { - constexpr std::size_t n = 64; + auto g = [](allocator_arg_t, stateful_alloc, const int hi) -> generator> { + constexpr size_t n = 64; int some_ints[n]; for (int i = 0; i < hi; ++i) { co_yield some_ints[i % n] = i; } }; - assert(ranges::equal(g(std::allocator_arg, stateful_alloc{42}, 1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(allocator_arg, stateful_alloc{42}, 1024), views::iota(0, 1024))); } #endif // ^^^ no workaround ^^^ } void dynamic_allocator_test() { - auto g = [](std::allocator_arg_t, const auto&, const int hi) -> std::generator { - constexpr std::size_t n = 64; + auto g = [](allocator_arg_t, const auto&, const int hi) -> generator { + constexpr size_t n = 64; int some_ints[n]; for (int i = 0; i < hi; ++i) { co_yield some_ints[i % n] = i; } }; - assert(ranges::equal(g(std::allocator_arg, std::allocator{}, 1024), ranges::views::iota(0, 1024))); - assert(ranges::equal(g(std::allocator_arg, stateless_alloc{}, 1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(allocator_arg, allocator{}, 1024), views::iota(0, 1024))); + assert(ranges::equal(g(allocator_arg, StatelessAlloc{}, 1024), views::iota(0, 1024))); #ifndef __EDG__ // TRANSITION, VSO-1951821 - assert(ranges::equal(g(std::allocator_arg, stateful_alloc{1729}, 1024), ranges::views::iota(0, 1024))); + assert(ranges::equal(g(allocator_arg, stateful_alloc{1729}, 1024), views::iota(0, 1024))); #endif // ^^^ no workaround ^^^ } void zip_example() { int length = 0; - for (auto x : zip(std::array{1, 2, 3}, std::vector{10, 20, 30, 40, 50})) { - static_assert(std::same_as>); - assert(std::get<0>(x) * 10 == std::get<1>(x)); + for (auto x : co_zip(array{1, 2, 3}, vector{10, 20, 30, 40, 50})) { + static_assert(same_as>); + assert(get<0>(x) * 10 == get<1>(x)); ++length; } assert(length == 3); } #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 -std::generator iota_repeater(const int hi, const int depth) { +generator iota_repeater(const int hi, const int depth) { if (depth > 0) { co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); } else { - co_yield ranges::elements_of(meow(hi)); + co_yield ranges::elements_of(co_upto(hi)); } } void recursive_test() { - struct some_error {}; - - static constexpr auto might_throw = []() -> std::generator { + static constexpr auto might_throw = []() -> generator { co_yield 0; - throw some_error{}; + throw runtime_error{"error"}; }; - static constexpr auto nested_ints = []() -> std::generator { + static constexpr auto nested_ints = []() -> generator { try { co_yield ranges::elements_of(might_throw()); - } catch (const some_error&) { + } catch (const runtime_error& e) { + assert(e.what() == "error"sv); } co_yield 1; }; - assert(ranges::equal(iota_repeater(3, 2), std::array{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2})); - assert(ranges::equal(nested_ints(), std::array{0, 1})); + assert(ranges::equal(iota_repeater(3, 2), array{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2})); + assert(ranges::equal(nested_ints(), array{0, 1})); } void arbitrary_range_test() { - auto yield_arbitrary_ranges = []() -> std::generator { - co_yield ranges::elements_of(std::vector{40, 30, 20, 10}); - co_yield ranges::elements_of(ranges::views::iota(0, 4)); - std::forward_list fl{500, 400, 300}; + auto yield_arbitrary_ranges = []() -> generator { + co_yield ranges::elements_of(vector{40, 30, 20, 10}); + co_yield ranges::elements_of(views::iota(0, 4)); + forward_list fl{500, 400, 300}; co_yield ranges::elements_of(fl); }; - assert(ranges::equal(yield_arbitrary_ranges(), std::array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); + assert(ranges::equal(yield_arbitrary_ranges(), array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); } #ifndef _M_CEE // TRANSITION, VSO-1659496 @@ -294,21 +241,21 @@ struct incomplete; void adl_proof_test() { using validator = holder*; - auto yield_range = []() -> std::generator { + auto yield_range = []() -> generator { co_yield ranges::elements_of( - ranges::views::repeat(nullptr, 42) | ranges::views::transform([](std::nullptr_t) { return validator{}; })); + views::repeat(nullptr, 42) | views::transform([](nullptr_t) { return validator{}; })); }; using R = decltype(yield_range()); static_assert(ranges::input_range); using It = ranges::iterator_t; - static_assert(std::is_same_v()), It*>); + static_assert(is_same_v()), It*>); using Promise = R::promise_type; - static_assert(std::is_same_v()), Promise*>); + static_assert(is_same_v()), Promise*>); - std::size_t i = 0; + size_t i = 0; for (const auto elem : yield_range()) { ++i; assert(elem == nullptr); @@ -320,15 +267,14 @@ void adl_proof_test() { int main() { { - std::stringstream ss; + stringstream ss; f(ss); - assert(ss.str() == "\"0 1 2 \"\n"); + assert(ss.str() == "0 1 2 "); } - assert(ranges::equal(meow(6), ranges::views::iota(0, 6))); + assert(ranges::equal(co_upto(6), views::iota(0, 6))); - { - // test with mutable lvalue reference type - auto r = meow(32); + { // Test with mutable lvalue reference type + auto r = co_upto(32); auto pos = r.begin(); for (int i = 0; i < 16; ++i, ++*pos, ++pos) { assert(pos != r.end()); @@ -338,18 +284,18 @@ int main() { } #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 - { - // test with mutable xvalue reference type - auto woof = [](std::size_t size, std::size_t count) -> std::generator&&> { - std::random_device rd{}; - std::uniform_int_distribution dist{0, 99}; - std::vector vec; + { // Test with mutable xvalue reference type + auto woof = [](size_t size, size_t count) -> generator&&> { + random_device rd{}; + uniform_int_distribution dist{0, 99}; + vector vec; while (count-- > 0) { vec.resize(size); ranges::generate(vec, [&] { return dist(rd); }); - co_yield std::move(vec); + co_yield move(vec); } - // test yielding lvalue + + // Test yielding lvalue vec.resize(size); ranges::generate(vec, [&] { return dist(rd); }); const auto tmp = vec; @@ -360,7 +306,7 @@ int main() { constexpr size_t size = 16; auto r = woof(size, 4); for (auto i = r.begin(); i != r.end(); ++i) { - std::vector vec = *i; + vector vec = *i; assert(vec.size() == size); assert((*i).empty()); } diff --git a/tests/std/tests/P2502R2_generator_iterator/test.cpp b/tests/std/tests/P2502R2_generator_iterator/test.cpp index 6e9954ab450..4f55690096f 100644 --- a/tests/std/tests/P2502R2_generator_iterator/test.cpp +++ b/tests/std/tests/P2502R2_generator_iterator/test.cpp @@ -151,7 +151,7 @@ void test_with_allocator() { test_one(); test_one>(); test_one>(); - test_one>(); + test_one>(); } template diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index 3266ba2572e..9d7075e62dc 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -178,11 +178,11 @@ void test_one() { // Test 'operator new(size_t, ARGS...)' test_operator_new>(p); 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); + test_operator_new>(p); + test_operator_new>(p); + test_operator_new>(p); } } @@ -191,10 +191,10 @@ void test_with_allocator() { test_one(); test_one>(); test_one>(); - test_one>(); - test_one>(); - test_one>(); - test_one>(); + test_one>(); + test_one>(); + test_one>(); + test_one>(); } template From 156b2dd9d729271eb9d75c009550b570c3000790 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 11 Jul 2024 15:38:54 -0700 Subject: [PATCH 19/50] `_NODISCARD_FRIEND` => `_NODISCARD friend` Resolves a stealth merge conflict with GH 4782. --- stl/inc/generator | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/generator b/stl/inc/generator index efcb80fb671..c09710308df 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -427,7 +427,7 @@ public: ++*this; } - _NODISCARD_FRIEND bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ + _NODISCARD friend bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ { return _It._Coro.done(); } From cf0bf1af15f3d19a98db2b6afbaa00f49758a0b0 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Thu, 18 Jul 2024 07:02:30 +0800 Subject: [PATCH 20/50] ``: Revert unnecessary ADL firewall for `_Gen_promise_base` (#4827) --- stl/inc/generator | 41 ++++++------- .../tests/P2502R2_generator_promise/test.cpp | 61 ++++++++++++------- 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index c09710308df..2277f6ebd09 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -199,18 +199,13 @@ using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; template using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; -template -struct _Gen_promise_base_provider { - class _Base; -}; - template struct _Gen_iter_provider { class _Iterator; }; template -class _Gen_promise_base_provider<_Yielded>::_Base { +class _Gen_promise_base { public: _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); @@ -284,11 +279,11 @@ private: template constexpr void await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - _Base& _Current = _Handle.promise(); - _Current._Ptr = _STD addressof(_Val); + _Gen_promise_base& _Current = _Handle.promise(); + _Current._Ptr = _STD addressof(_Val); } constexpr void await_resume() const noexcept {} @@ -296,8 +291,8 @@ private: struct _Nest_info { exception_ptr _Except; - coroutine_handle<_Base> _Parent; - coroutine_handle<_Base> _Root; + coroutine_handle<_Gen_promise_base> _Parent; + coroutine_handle<_Gen_promise_base> _Root; }; struct _Final_awaiter { @@ -308,11 +303,11 @@ private: template _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ if (const auto _Info = _Handle.promise()._Try_get_nest_info()) { - coroutine_handle<_Base> _Cont = _Info->_Parent; + coroutine_handle<_Gen_promise_base> _Cont = _Info->_Parent; _Info->_Root.promise()._Set_top(_Cont); return _Cont; } @@ -338,12 +333,13 @@ private: } template - _NODISCARD coroutine_handle<_Base> await_suspend(coroutine_handle<_CoroPromise> _Current) noexcept { + _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend( + coroutine_handle<_CoroPromise> _Current) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - auto _Target = coroutine_handle<_Base>::from_address(_Gen._Coro.address()); - _Nested._Parent = coroutine_handle<_Base>::from_address(_Current.address()); + auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); if (const auto _Parent_nest_info = _Nested._Parent.promise()._Try_get_nest_info()) { _Nested._Root = _Parent_nest_info->_Root; } else { @@ -370,16 +366,16 @@ private: return nullptr; } - _NODISCARD coroutine_handle<_Base> _Get_top() const noexcept { + _NODISCARD coroutine_handle<_Gen_promise_base> _Get_top() const noexcept { _STL_INTERNAL_CHECK((_Data & 1U) == 0); - return coroutine_handle<_Base>::from_address(reinterpret_cast(_Data)); + return coroutine_handle<_Gen_promise_base>::from_address(reinterpret_cast(_Data)); } void _Set_nest_info(_Nest_info* _Info) noexcept { _Data = reinterpret_cast(_Info) | 1U; } - void _Set_top(coroutine_handle<_Base> _Top) noexcept { + void _Set_top(coroutine_handle<_Gen_promise_base> _Top) noexcept { _Data = reinterpret_cast(_Top.address()); } @@ -389,13 +385,10 @@ private: // Least significant bit of `_Data` indicates stored information: // LSB 0: `_Data` is a top coroutine handle, // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. - uintptr_t _Data = reinterpret_cast(coroutine_handle<_Base>::from_promise(*this).address()); + uintptr_t _Data = reinterpret_cast(coroutine_handle<_Gen_promise_base>::from_promise(*this).address()); add_pointer_t<_Yielded> _Ptr = nullptr; }; -template -using _Gen_promise_base = _Gen_promise_base_provider<_Yielded>::_Base; - struct _Gen_secret_tag {}; template diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index 9d7075e62dc..1fdd7347a96 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -100,7 +100,7 @@ void test_operator_new(typename Gen::promise_type& p, const Alloc2& alloc2 = {}) } } -template +template void test_one() { using Gen = generator; using Promise = Gen::promise_type; @@ -109,6 +109,10 @@ void test_one() { Promise p; + // Test that operator& for promise_type resolves to the built-in version and doesn't involve ADL + static_assert(same_as); + assert(&p == addressof(p)); + // Test 'get_return_object' static_assert(same_as); static_assert(noexcept(p.get_return_object())); @@ -154,7 +158,8 @@ void test_one() { } using ValTy = conditional_t, remove_cvref_t, V>; - if constexpr (convertible_to) { // Test 'yield_value(ranges::elements_of)' + if constexpr (!TestingIncomplete && 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); @@ -186,32 +191,41 @@ void test_one() { } } -template +template void test_with_allocator() { - test_one(); - test_one>(); - test_one>(); - test_one>(); - test_one>(); - test_one>(); - test_one>(); + test_one(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); } -template +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>(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + + test_with_allocator, T, TestingIncomplete>(); + test_with_allocator&, T, TestingIncomplete>(); + test_with_allocator&, T, TestingIncomplete>(); + test_with_allocator&&, T, TestingIncomplete>(); + test_with_allocator&&, T, TestingIncomplete>(); } +#ifndef _M_CEE // TRANSITION, VSO-1659496 +template +struct Holder { + T t; +}; + +struct Incomplete; +#endif // ^^^ no workaround ^^^ + int main() { test_with_type(); test_with_type(); @@ -219,4 +233,7 @@ int main() { test_with_type(); test_with_type(); test_with_allocator::reference, bool>(); +#ifndef _M_CEE // TRANSITION, VSO-1659496 + test_with_type*, true>(); +#endif // ^^^ no workaround ^^^ } From 874880f4f77bf3a652a25a060c33c9ef5329104d Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 2 Aug 2024 07:35:29 +0800 Subject: [PATCH 21/50] Implement P2787R1 `pmr::generator` (#4869) --- stl/inc/generator | 9 +++++++++ stl/inc/yvals_core.h | 1 + tests/std/tests/P2502R2_generator/test.cpp | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/stl/inc/generator b/stl/inc/generator index 2277f6ebd09..882b5b17673 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -16,6 +16,9 @@ _EMIT_STL_WARNING(STL4038, "The contents of are available only with #include #include #include +#ifdef __cpp_lib_byte +#include +#endif // defined(__cpp_lib_byte) #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -504,6 +507,12 @@ private: explicit generator(_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} }; +#ifdef __cpp_lib_byte +namespace pmr { + _EXPORT_STD template + using generator = _STD generator<_Rty, _Vty, polymorphic_allocator<>>; +} // namespace pmr +#endif // defined(__cpp_lib_byte) _STD_END // TRANSITION, non-_Ugly attribute tokens diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 8daa211d4cc..ca184571ae3 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -392,6 +392,7 @@ // P2693R1 Formatting thread::id And stacktrace // P2713R1 Escaping Improvements In std::format // P2763R1 Fixing layout_stride's Default Constructor For Fully Static Extents +// P2787R1 pmr::generator // P2833R2 Freestanding Library: inout expected span // (except for __cpp_lib_span which also covers C++26 span::at) // P2836R1 basic_const_iterator Should Follow Its Underlying Type's Convertibility diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 26da66d1cde..dd2c7369f83 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -163,6 +164,18 @@ void static_allocator_test() { assert(ranges::equal(g(allocator_arg, stateful_alloc{42}, 1024), views::iota(0, 1024))); } #endif // ^^^ no workaround ^^^ + { + auto g = [](allocator_arg_t, pmr::polymorphic_allocator, const int hi) -> pmr::generator { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + static_assert(is_same_v, generator>>); + assert(ranges::equal(g(allocator_arg, pmr::polymorphic_allocator{}, 1024), views::iota(0, 1024))); + } } void dynamic_allocator_test() { @@ -179,6 +192,8 @@ void dynamic_allocator_test() { #ifndef __EDG__ // TRANSITION, VSO-1951821 assert(ranges::equal(g(allocator_arg, stateful_alloc{1729}, 1024), views::iota(0, 1024))); #endif // ^^^ no workaround ^^^ + pmr::synchronized_pool_resource pool; + assert(ranges::equal(g(allocator_arg, pmr::polymorphic_allocator{&pool}, 1024), views::iota(0, 1024))); } void zip_example() { From 89283a6d3c1b2b232dcb8bb01837f1bafe4151f9 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 12 Sep 2024 19:38:35 -0700 Subject: [PATCH 22/50] Polish `std::generator` (#4952) This should complete the `generator` feature branch and get it ready to merge. There are very few product code changes here, it's mostly expanded test coverage with some cleanup and reorganization. There's also some documentation of the "stack of coroutines" and the memory layout of allocations that will make it easier for folks to get up to speed on how the code works under the covers. It's broken down nicely by commit for ease of review; I'll replicate the commit messages here as an overview. Comments without specific context refer to `P2502R2_generator` where most changes were made. The `P2502R2_generator_iterator` and `P2502R2_generator_promise` tests were already very nicely complete. * Update `generator` template argument mandates * Update citations to WG21-N4988 * Reorder checks to specification order * Make `_RRef` more obviously reflect the wording for `RRef` in WG21-N4988 [coro.generator.class]/1.4. * Allocator testing updates * Simplify and correct `StatelessAlloc` * Deriving publicly from `std::allocator` not a great idea, as witnessed by the recent addition of `allocate_at_least`. * `deallocate` should be `noexcept` for a `Cpp17Allocator`. * We don't need to reimplement `std::allocator` when we can simply use it. * The domain of equality for `Cpp17Allocators` is an entire `rebind` family, i.e., `StatelessAlloc` and `allocator_traits>::rebind_alloc` must be comparable. * PascalCase `stateful_alloc` for consistency and move into the header with `StatelessAlloc`. * Promise test tweaks * "whose member `await_ready` returns `false`" in WG21-N4988 [coro.generator.promise]/11 implies the return type is exactly `bool`, not "convertible to `bool`". Update `test_yield_elements_of_range` accordingly. * Move non-portable size check from `P2502R2_generator` into `P2502R2_generator_promise` * Expand and complete `static_checks`: Implement a complete `generator` traits in the header to use in both `P2502R2_generator_iterator` and `P2502R2_generator`'s `static_checks`. * Create generic `test_one` template which takes a generator, a description of its static properties, and the expected result of piping the generator through a provided range adaptor. `test_one` validates the static properties with `static_checks`, and confirms the output is as expected. * Extract test cases for weird reference types (mutable lvalue and rvalue references) from `main` into new function `test_weird_reference_types`. * Several small tweaks: * It's no longer significant that `co_upto` wasn't an example in the proposal; strike the comment. * Enforce `co_upto`'s precondition so it's nicely documented. * Consistently prefer `same_as` to `is_same_v`. * Regroup calls to test cases in `main` topically and title each category. * Reorganize test code to agree with call order in `main`. [This is the largest individual commit; it is a pure reordering.] * All product code assertions now depend on `_CONTAINER_DEBUG_LEVEL`. These are all simple O(1) checks, there's no reason not to promote them from `_DEBUG` to `_CONTAINER_DEBUG_LEVEL`. * Clarify allocation mechanisms, including some fancy memory layout diagrams. * Rename `_Promise_allocator` to `_Coro_promise_allocator` to avoid any confusion with `_Promise`. Rename its template parameter `_Allocator` to `_Proto_allocator` to avoid confusion with the rebound allocator type `_Alloc`. * Expand `static_assert` message in `operator new`. If and when `_Coro_promise_allocator` is reused by other coroutine types we can worry about making the message more generic. * Extract block size computations. * `Element_awaiter` must direct-non-list-initialize its stored object, per WG21-N4988 [coro.generator.promise]/7. * Document the "stack of coroutine handles" in a code comment with another work of art. --- stl/inc/generator | 204 +++++--- tests/std/include/test_generator_support.hpp | 99 +++- tests/std/tests/P2502R2_generator/test.cpp | 443 +++++++++++------- .../tests/P2502R2_generator_death/test.cpp | 4 +- .../tests/P2502R2_generator_iterator/test.cpp | 6 - .../tests/P2502R2_generator_promise/test.cpp | 7 +- 6 files changed, 480 insertions(+), 283 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 882b5b17673..c388db394cd 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -40,26 +40,57 @@ struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { template concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v::pointer>; -template -class _Promise_allocator { // statically specified allocator type +template +constexpr bool _Allocator_is_stateless = + default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value; + +template +class _Coro_promise_allocator { // statically specified allocator type private: - using _Alloc = _Rebind_alloc_t<_Allocator, _Aligned_block>; + using _Alloc = _Rebind_alloc_t<_Proto_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(_Convert_size<_Alloc_size_type>(_Count)); + static size_t _Allocation_block_size(const size_t _Size) noexcept { + // Compute the number of _Aligned_blocks needed to store a coroutine + // frame of _Size bytes and, if necessary, an _Alloc. + if constexpr (_Allocator_is_stateless<_Alloc>) { + // allocator is stateless, we need no additional space + return (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); } else { - // store stateful allocator + // allocator is stateful, we need space for it 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(_Convert_size<_Alloc_size_type>(_Count)); + return (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); + } + } + + static void* _Allocate(_Alloc _Al, const size_t _Size) { + // memory layout: + // |--- coroutine frame --|- alignment padding -|---- _Alloc -----| + // |--- always present ---|----------- stateful only -------------| + // all rounded up to an integral number of _Aligned_blocks + + const size_t _Size_in_blocks = _Allocation_block_size(_Size); + void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Size_in_blocks)); + + if constexpr (!_Allocator_is_stateless<_Alloc>) { + // store only stateful allocators const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); - return _Ptr; + } + + return _Ptr; + } + + _NODISCARD static _Alloc _Get_allocator( + [[maybe_unused]] void* const _Ptr, [[maybe_unused]] const size_t _Size) noexcept { + // recreate or retrieve a copy of the allocator used to allocate _Ptr + if constexpr (_Allocator_is_stateless<_Alloc>) { + return _Alloc{}; + } else { + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + return *reinterpret_cast<_Alloc*>(_Al_address); } } @@ -71,93 +102,92 @@ public: } template - requires convertible_to + requires convertible_to static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { - return _Allocate(static_cast<_Alloc>(static_cast<_Allocator>(_Al)), _Size); + return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); } template - requires convertible_to + requires convertible_to static void* operator new(const size_t _Size, const _This&, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { - return _Allocate(static_cast<_Alloc>(static_cast<_Allocator>(_Al)), _Size); + return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); } static void operator delete(void* const _Ptr, const size_t _Size) noexcept { - if constexpr (default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { - // make stateless allocator - _Alloc _Al{}; - const size_t _Count = (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Count)); - } else { - // retrieve stateful allocator - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); - _Alloc _Al{_STD move(_Stored_al)}; - _Stored_al.~_Alloc(); - - 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), static_cast<_Alloc_size_type>(_Count)); - } + _Alloc _Al = _Get_allocator(_Ptr, _Size); + const size_t _Size_in_blocks = _Allocation_block_size(_Size); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Size_in_blocks)); } }; template <> -class _Promise_allocator { // type-erased allocator +class _Coro_promise_allocator { // type-erased allocator private: using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; + template + static size_t _Allocation_block_size(const size_t _Size) noexcept { + size_t _Bytes = _Size + sizeof(_Dealloc_fn); + if constexpr (_Allocator_is_stateless<_Alloc>) { + _Bytes += sizeof(_Aligned_block) - 1; + } else { + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + _Bytes += sizeof(_Alloc) + _Align - 1; + } + return _Bytes / sizeof(_Aligned_block); + } + template 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), static_cast::size_type>(_Count)); + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); } template - static void __stdcall _Dealloc_stateful(void* const _Ptr, size_t _Size) noexcept { - constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - - _Size += sizeof(_Dealloc_fn); - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + static void __stdcall _Dealloc_stateful(void* const _Ptr, const size_t _Size) noexcept { + const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + sizeof(_Dealloc_fn) + alignof(_Alloc) - 1) + & ~(alignof(_Alloc) - 1); auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); _Alloc _Al{_STD move(_Stored_al)}; _Stored_al.~_Alloc(); - const size_t _Count = (_Size + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast::size_type>(_Count)); + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); } - static void __stdcall _Dealloc_delete(void* const _Ptr, const size_t _Size) noexcept { + static void __stdcall _Dealloc_default(void* const _Ptr, const size_t _Size) noexcept { ::operator delete(_Ptr, _Size + sizeof(_Dealloc_fn)); } template static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { + // memory layout: + // |--- coroutine frame (_Size bytes) ---|--- _Dealloc_fn ---|-- alignment padding --|--- _Alloc ---| + // |-------------------- always present -----(not aligned)---|------------ stateful only -----------| + // all rounded up to an integral number of _Aligned_blocks + 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) { + _Alloc _Al{_Proto}; + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = _Convert_size<_Alloc_size_type>(_Size_in_blocks); + void* const _Ptr = _Al.allocate(_Alloc_size); + + if constexpr (_Allocator_is_stateless<_Alloc>) { // 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(_Convert_size<_Alloc_size_type>(_Count)); _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); return _Ptr; } else { // store stateful allocator - constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - 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(_Convert_size<_Alloc_size_type>(_Count)); _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); _Size += sizeof(_Dealloc_fn); + const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); ::new (reinterpret_cast(_Al_address)) _Alloc{_STD move(_Al)}; @@ -166,22 +196,24 @@ private: } public: - static void* operator new(const size_t _Size) { // default: new/delete + static void* operator new(const size_t _Size) { void* const _Ptr = ::operator new(_Size + sizeof(_Dealloc_fn)); - const _Dealloc_fn _Dealloc = _Dealloc_delete; + const _Dealloc_fn _Dealloc = _Dealloc_default; _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); return _Ptr; } template static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { - static_assert(_Has_real_pointers<_Alloc>, "coroutine allocators must use raw pointers"); + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } template static void* operator new(const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { - static_assert(_Has_real_pointers<_Alloc>, "coroutine allocators must use raw pointers"); + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } @@ -275,6 +307,10 @@ private: struct _Element_awaiter { remove_cvref_t<_Yielded> _Val; + explicit _Element_awaiter(const remove_reference_t<_Yielded>& _Val_) + noexcept(is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) + : _Val(_Val_) {} + _NODISCARD constexpr bool await_ready() const noexcept { return false; } @@ -292,6 +328,23 @@ private: constexpr void await_resume() const noexcept {} }; + // generator's stack of nested coroutines is realized as a linked list of promises. The "root" promise is the + // original that existed before any nested yields. The "top" promise is the one currently being iterated over. + // Iterators hold handles for the root's coroutine and the root promise holds a link to the top promise so iterators + // can resume it to generate the next element when incremented. Each promise keeps a link to the root so they can + // update the top link as promises are pushed and popped. It looks a bit like: + // + // +--------------------------- Top ---------------------------+ + // v | + // +=====+ +=====+ +=====+ +=====+ + // | |-- Parent -->| |-- Parent -->| |-- Parent -->| | + // +=====+ +=====+ +=====+ +=====+ + // | | | ^ + // +------- Root ------+------- Root ------+------- Root ------+ + // + // The parent and root links are actually stored out-of-line in a _Nest_info. Since a promise is either a root or is + // nested, the single promise member _Data can be used to store either a top link or a pointer to a _Nest_info. + struct _Nest_info { exception_ptr _Except; coroutine_handle<_Gen_promise_base> _Parent; @@ -305,6 +358,7 @@ private: template _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { + // Resume _Handle's parent coroutine, if any. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ @@ -338,6 +392,7 @@ private: template _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend( coroutine_handle<_CoroPromise> _Current) noexcept { + // Push _Gen's coroutine onto the coroutine stack that _Current is at the top of. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ @@ -409,12 +464,16 @@ public: _NODISCARD _Ref operator*() const noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) { - _STL_ASSERT(!_Coro.done(), "Can't dereference generator end iterator"); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(!_Coro.done(), "Can't dereference generator end iterator"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 return static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr); } _Iterator& operator++() { - _STL_ASSERT(!_Coro.done(), "Can't increment generator end iterator"); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(!_Coro.done(), "Can't increment generator end iterator"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 _Coro.promise()._Get_top().resume(); return *this; } @@ -441,32 +500,32 @@ private: _EXPORT_STD template class generator : public _RANGES view_interface> { private: + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); + using _Value = _Gen_value_t<_Rty, _Vty>; static_assert(same_as, _Value> && is_object_v<_Value>, - "generator's value type must be a cv-unqualified object type (N4971 [coro.generator.class]/1.2)"); + "generator's value type must be a cv-unqualified object type (N4988 [coro.generator.class]/1.2)"); using _Ref = _Gen_reference_t<_Rty, _Vty>; static_assert( is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), "generator's selected reference type must be an actual reference type " - "or a cv-unqualified copy-constructible object type (N4971 [coro.generator.class]/1.3)"); + "or a cv-unqualified copy-constructible object type (N4988 [coro.generator.class]/1.3)"); - using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; + using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; static_assert(common_reference_with<_Ref&&, _Value&> && common_reference_with<_Ref&&, _RRef&&> && common_reference_with<_RRef&&, const _Value&>, "generator's iterator type must model indirectly_readable, " - "but that's impossible with the selected value and reference types (N4971 [coro.generator.class]/1.4)"); - - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " - "(N4971 [coro.generator.class]/1.1)"); + "but that's impossible with the selected value and reference types (N4988 [coro.generator.class]/1.4)"); public: using yielded = _Gen_yield_t<_Ref>; friend _Gen_promise_base; - struct __declspec(empty_bases) promise_type : _Promise_allocator<_Alloc>, _Gen_promise_base { + struct __declspec(empty_bases) promise_type : _Coro_promise_allocator<_Alloc>, _Gen_promise_base { _NODISCARD generator get_return_object() noexcept { return generator{_Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; } @@ -491,7 +550,9 @@ public: _NODISCARD _Gen_iter_provider<_Value, _Ref>::_Iterator begin() { // Pre: _Coro is suspended at its initial suspend point - _STL_ASSERT(_Coro, "Can't call begin on moved-from generator"); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Coro, "Can't call begin on moved-from generator"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 _Coro.resume(); return typename _Gen_iter_provider<_Value, _Ref>::_Iterator{ _Gen_secret_tag{}, coroutine_handle<_Gen_promise_base>::from_address(_Coro.address())}; @@ -503,6 +564,9 @@ public: private: coroutine_handle _Coro = nullptr; + // The stack of coroutine handles depicted in the Standard is realized by a linked structure of promises and + // awaitable objects, so all necessary storage is allocated in coroutine frames. See the description in the body of + // _Gen_promise_base. explicit generator(_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} }; diff --git a/tests/std/include/test_generator_support.hpp b/tests/std/include/test_generator_support.hpp index 27a249420c9..62d6cf50158 100644 --- a/tests/std/include/test_generator_support.hpp +++ b/tests/std/include/test_generator_support.hpp @@ -6,13 +6,48 @@ #include #include #include +#include #include #include -#include #include +template +struct gen_traits; + +template +struct gen_traits { + using generator = std::generator; + using value = std::remove_cvref_t; + using reference = R&&; + using yielded = reference; + + // Verify the default template arguments + static_assert(std::same_as, std::generator>); + static_assert(std::same_as, std::generator>); +}; +template +struct gen_traits { + using generator = std::generator; + using value = V; + using reference = R; + using yielded = std::conditional_t, R, const R&>; + + // Ditto verify default template arguments + static_assert(std::same_as, std::generator>); +}; +template +struct gen_traits : gen_traits { + using generator = std::generator; +}; + +template +using gen_value_t = gen_traits::value; + +template +using gen_reference_t = gen_traits::reference; + template -class StatelessAlloc : public std::allocator { +class StatelessAlloc { public: using value_type = T; using is_always_equal = AlwaysEqual; @@ -25,37 +60,53 @@ class StatelessAlloc : public std::allocator { StatelessAlloc(const StatelessAlloc&) {} T* allocate(const size_type s) { - void* vp; - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - vp = ::_aligned_malloc(s * sizeof(T), alignof(T)); - } else { - vp = std::malloc(s * sizeof(T)); - } - - if (vp) { - return static_cast(vp); - } - - throw std::bad_alloc{}; + return std::allocator{}.allocate(s); } - void deallocate(T* const p, size_type) { - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - ::_aligned_free(p); - } else { - std::free(p); - } + void deallocate(T* const p, const size_type n) noexcept { + std::allocator{}.deallocate(p, n); } - operator std::pmr::polymorphic_allocator() const { - return {}; + template + bool operator==(const StatelessAlloc&) const noexcept { + return true; } - - bool operator==(const StatelessAlloc&) const = default; }; static_assert(std::default_initializable>); +template +class StatefulAlloc { +public: + using value_type = T; + + explicit StatefulAlloc(int dom) noexcept : domain{dom} {} + + template + StatefulAlloc(const StatefulAlloc& that) noexcept : domain{that.domain} {} + + T* allocate(const size_t n) { + return std::allocator{}.allocate(n); + } + + void deallocate(T* const p, const size_t n) noexcept { + return std::allocator{}.deallocate(p, n); + } + + template + bool operator==(const StatefulAlloc& that) noexcept { + return domain == that.domain; + } + +private: + int domain; + + template + friend class StatefulAlloc; +}; + +static_assert(!std::default_initializable>); + struct MoveOnly { MoveOnly() = default; MoveOnly(const MoveOnly&) = delete; diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index dd2c7369f83..7d58ceea5fc 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -11,59 +12,101 @@ #include #include #include -#include #include #include -#include #include #include #include +#include #include #include -#include +#include "test_generator_support.hpp" + +#pragma warning(disable : 28251) // Inconsistent annotation for 'new': this instance has no annotations. using namespace std; -template +template consteval bool static_checks() { - static_assert(ranges::input_range); + using G = Traits::generator; + static_assert(derived_from>); + + // Specializations of generator are move-only input-and-no-stronger views static_assert(ranges::view); + static_assert(ranges::input_range); static_assert(!ranges::forward_range); - static_assert(!ranges::borrowed_range); - static_assert(!ranges::common_range); - static_assert(same_as, V>); + static_assert(!copy_constructible); + static_assert(!is_copy_assignable_v); + + static_assert(is_nothrow_destructible_v); + static_assert(is_nothrow_move_constructible_v); + static_assert(is_nothrow_move_assignable_v); + + // Verify the generator's associated types + static_assert(same_as, ValueType>); static_assert(same_as, ptrdiff_t>); - static_assert(same_as, R>); - static_assert(same_as, RR>); + static_assert(same_as, ReferenceType>); + static_assert(same_as, RvalueReferenceType>); + static_assert(same_as); + + // Verify end + static_assert(same_as>); + static_assert(same_as().end())>); + static_assert(noexcept(declval().end())); + static_assert(noexcept(declval().end())); + + // iterator properties are verified in P2502R2_generator_iterator + // promise properties are verified in P2502R2_generator_promise - // Non-portable size checks + // Non-portable size check static_assert(sizeof(G) == sizeof(void*)); - static_assert(sizeof(typename G::promise_type) == 2 * sizeof(void*)); return true; } -static_assert(static_checks, int, int&&, int&&>()); -static_assert(static_checks, int, const int&, const int&&>()); -static_assert(static_checks, int, int&&, int&&>()); -static_assert(static_checks, int, int&, int&&>()); -static_assert(static_checks, int, int, int>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, const int&, const int&&>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, int&, int&&>()); + +static_assert(static_checks, int, int, int>()); +static_assert(static_checks, int, const int&, const int&&>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, int&, int&&>()); + +template +void test_one(generator g0, invocable> auto adaptor, ranges::input_range auto&& expected) + requires ranges::input_range +{ + static_assert(same_as, typename Traits::generator>); + static_assert(static_checks()); + + auto g1 = move(g0); + auto i = ranges::cbegin(expected); + for (auto&& x : adaptor(move(g1))) { + assert(i != ranges::cend(expected)); + assert(*i == x); + ++i; + // Verify iterator stays valid after move assignment + ranges::swap(g0, g1); + } + assert(i == ranges::cend(expected)); +} -// [coroutine.generator.overview] Example 1: +template +void test_one(generator g0, ranges::input_range auto&& expected) { + return test_one(move(g0), identity{}, expected); +} + +// Some simple end-to-end tests, mostly from the Working Draft or P2502R2 generator ints(int start = 0) { while (true) { co_yield start++; } } -void f(ostream& os) { - for (auto i : ints() | views::take(3)) { - os << i << ' '; // prints '0 1 2 ' - } -} - template generator, ranges::range_reference_t>, tuple, ranges::range_value_t>> @@ -77,133 +120,66 @@ generator, ranges::range_reference_t } } -// Not from the proposal: +void zip_example() { + using V = tuple; + using R = tuple; + + auto g0 = co_zip(array{1, 2, 3}, vector{10, 20, 30, 40, 50}); + test_one, V, R, R>(move(g0), array{tuple{1, 10}, tuple{2, 20}, tuple{3, 30}}); + + g0 = co_zip(array{3, 2, 1}, vector{10, 20, 30, 40, 50}); + test_one, V, R, R>(move(g0), array{tuple{3, 10}, tuple{2, 20}, tuple{1, 30}}); +} + template generator co_upto(const int hi) { + assert(hi >= 0); for (int i = 0; i < hi; ++i) { co_yield i; } } -template -struct stateful_alloc { - using value_type = T; - - int domain; - - explicit stateful_alloc(int dom) noexcept : domain{dom} {} - - template - constexpr stateful_alloc(const stateful_alloc& that) noexcept : domain{that.domain} {} - - T* allocate(const size_t n) { - void* vp; - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); - } else { - vp = malloc(n * sizeof(T)); - } - - if (vp) { - return static_cast(vp); - } - - throw bad_alloc{}; - } - - void deallocate(void* const vp, [[maybe_unused]] const size_t n) noexcept { - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - ::_aligned_free(vp); - } else { - free(vp); +void test_weird_reference_types() { + constexpr int n = 32; + { // Test mutable lvalue reference type + auto r = co_upto(n); + auto pos = r.begin(); + for (int i = 0; i < n / 2; ++i, ++*pos, ++pos) { + assert(pos != r.end()); + assert(*pos == 2 * i); } + assert(pos == r.end()); } - template - constexpr bool operator==(const stateful_alloc& that) noexcept { - return this->domain == that.domain; - } -}; -static_assert(!default_initializable>); - -void static_allocator_test() { - { - auto g = [](const int hi) -> generator> { - constexpr size_t n = 64; - int some_ints[n]; - for (int i = 0; i < hi; ++i) { - co_yield some_ints[i % n] = i; - } - }; - - assert(ranges::equal(g(1024), views::iota(0, 1024))); - } - - { - auto g = [](allocator_arg_t, StatelessAlloc, const int hi) -> generator> { - constexpr size_t n = 64; - int some_ints[n]; - for (int i = 0; i < hi; ++i) { - co_yield some_ints[i % n] = i; - } - }; - - assert(ranges::equal(g(allocator_arg, {}, 1024), views::iota(0, 1024))); - } - -#ifndef __EDG__ // TRANSITION, VSO-1951821 - { - auto g = [](allocator_arg_t, stateful_alloc, const int hi) -> generator> { - constexpr size_t n = 64; - int some_ints[n]; - for (int i = 0; i < hi; ++i) { - co_yield some_ints[i % n] = i; +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 + { // Test with mutable xvalue reference type + auto woof = [](size_t size, size_t count) -> generator&&> { + random_device rd{}; + uniform_int_distribution dist{0, 99}; + vector vec; + while (count-- > 0) { + vec.resize(size); + ranges::generate(vec, [&] { return dist(rd); }); + co_yield move(vec); + assert(vec.empty()); // when we yield an rvalue, the caller moves from it } - }; - assert(ranges::equal(g(allocator_arg, stateful_alloc{42}, 1024), views::iota(0, 1024))); - } -#endif // ^^^ no workaround ^^^ - { - auto g = [](allocator_arg_t, pmr::polymorphic_allocator, const int hi) -> pmr::generator { - constexpr size_t n = 64; - int some_ints[n]; - for (int i = 0; i < hi; ++i) { - co_yield some_ints[i % n] = i; - } + // Test yielding lvalue + vec.resize(size); + ranges::generate(vec, [&] { return dist(rd); }); + const auto tmp = vec; + co_yield vec; + assert(tmp == vec); // when we yield an lvalue, the caller moves from a copy }; - static_assert(is_same_v, generator>>); - assert(ranges::equal(g(allocator_arg, pmr::polymorphic_allocator{}, 1024), views::iota(0, 1024))); - } -} - -void dynamic_allocator_test() { - auto g = [](allocator_arg_t, const auto&, const int hi) -> generator { - constexpr size_t n = 64; - int some_ints[n]; - for (int i = 0; i < hi; ++i) { - co_yield some_ints[i % n] = i; + constexpr size_t size = 16; + auto r = woof(size, 4); + for (auto i = r.begin(); i != r.end(); ++i) { + vector vec = *i; + assert(vec.size() == size); } - }; - - assert(ranges::equal(g(allocator_arg, allocator{}, 1024), views::iota(0, 1024))); - assert(ranges::equal(g(allocator_arg, StatelessAlloc{}, 1024), views::iota(0, 1024))); -#ifndef __EDG__ // TRANSITION, VSO-1951821 - assert(ranges::equal(g(allocator_arg, stateful_alloc{1729}, 1024), views::iota(0, 1024))); -#endif // ^^^ no workaround ^^^ - pmr::synchronized_pool_resource pool; - assert(ranges::equal(g(allocator_arg, pmr::polymorphic_allocator{&pool}, 1024), views::iota(0, 1024))); -} - -void zip_example() { - int length = 0; - for (auto x : co_zip(array{1, 2, 3}, vector{10, 20, 30, 40, 50})) { - static_assert(same_as>); - assert(get<0>(x) * 10 == get<1>(x)); - ++length; } - assert(length == 3); +#endif // ^^^ no workaround ^^^ } #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 @@ -231,8 +207,8 @@ void recursive_test() { co_yield 1; }; - assert(ranges::equal(iota_repeater(3, 2), array{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2})); - assert(ranges::equal(nested_ints(), array{0, 1})); + test_one, int, int&&, int&&>(iota_repeater(3, 2), array{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2}); + test_one, int, int&&, int&&>(nested_ints(), array{0, 1}); } void arbitrary_range_test() { @@ -243,7 +219,8 @@ void arbitrary_range_test() { co_yield ranges::elements_of(fl); }; - assert(ranges::equal(yield_arbitrary_ranges(), array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); + test_one, int, const int&, const int&&>( + yield_arbitrary_ranges(), array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300}); } #ifndef _M_CEE // TRANSITION, VSO-1659496 @@ -265,10 +242,10 @@ void adl_proof_test() { static_assert(ranges::input_range); using It = ranges::iterator_t; - static_assert(is_same_v()), It*>); + static_assert(same_as()), It*>); using Promise = R::promise_type; - static_assert(is_same_v()), Promise*>); + static_assert(same_as()), Promise*>); size_t i = 0; for (const auto elem : yield_range()) { @@ -280,64 +257,172 @@ void adl_proof_test() { #endif // ^^^ no workaround ^^^ #endif // ^^^ no workaround ^^^ -int main() { +// Verify behavior with unerased allocator types +void static_allocator_test() { { - stringstream ss; - f(ss); - assert(ss.str() == "0 1 2 "); - } - assert(ranges::equal(co_upto(6), views::iota(0, 6))); + auto g = [](const int hi) -> generator> { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; - { // Test with mutable lvalue reference type - auto r = co_upto(32); - auto pos = r.begin(); - for (int i = 0; i < 16; ++i, ++*pos, ++pos) { - assert(pos != r.end()); - assert(*pos == 2 * i); - } - assert(pos == r.end()); + test_one>, int, int, int>(g(1024), views::iota(0, 1024)); } -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 - { // Test with mutable xvalue reference type - auto woof = [](size_t size, size_t count) -> generator&&> { - random_device rd{}; - uniform_int_distribution dist{0, 99}; - vector vec; - while (count-- > 0) { - vec.resize(size); - ranges::generate(vec, [&] { return dist(rd); }); - co_yield move(vec); + { + auto g = [](allocator_arg_t, StatelessAlloc, const int hi) -> generator> { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; } + }; - // Test yielding lvalue - vec.resize(size); - ranges::generate(vec, [&] { return dist(rd); }); - const auto tmp = vec; - co_yield vec; - assert(tmp == vec); + test_one>, int, int, int>( + g(allocator_arg, {}, 1024), views::iota(0, 1024)); + } + +#ifndef __EDG__ // TRANSITION, VSO-1951821 + { + auto g = [](allocator_arg_t, StatefulAlloc, const int hi) -> generator> { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } }; - constexpr size_t size = 16; - auto r = woof(size, 4); - for (auto i = r.begin(); i != r.end(); ++i) { - vector vec = *i; - assert(vec.size() == size); - assert((*i).empty()); - } + test_one>, int, int, int>( + g(allocator_arg, StatefulAlloc{42}, 1024), views::iota(0, 1024)); } #endif // ^^^ no workaround ^^^ +} - static_allocator_test(); - dynamic_allocator_test(); +// Verify behavior with erased allocator types +void dynamic_allocator_test() { + auto g = [](allocator_arg_t, const auto&, const int hi) -> generator { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + test_one, int, int&&, int&&>(g(allocator_arg, allocator{}, 1024), views::iota(0, 1024)); + test_one, int, int&&, int&&>(g(allocator_arg, StatelessAlloc{}, 1024), views::iota(0, 1024)); +#ifndef __EDG__ // TRANSITION, VSO-1951821 + test_one, int, int&&, int&&>( + g(allocator_arg, StatefulAlloc{1729}, 1024), views::iota(0, 1024)); +#endif // ^^^ no workaround ^^^ + pmr::synchronized_pool_resource pool; + test_one, int, int&&, int&&>( + g(allocator_arg, pmr::polymorphic_allocator<>{&pool}, 1024), views::iota(0, 1024)); +} + +static atomic allow_allocation{true}; + +void* operator new(const size_t n) { + if (allow_allocation) { + if (void* const result = malloc(n)) { + return result; + } + } + throw bad_alloc{}; +} + +void operator delete(void* const p) noexcept { + free(p); +} + +void operator delete(void* const p, size_t) noexcept { + free(p); +} + +void* operator new(const size_t n, const align_val_t al) { + if (allow_allocation) { + if (void* const result = ::_aligned_malloc(n, static_cast(al))) { + return result; + } + } + throw bad_alloc{}; +} + +void operator delete(void* const p, align_val_t) noexcept { + ::_aligned_free(p); +} + +void operator delete(void* const p, size_t, align_val_t) noexcept { + ::_aligned_free(p); +} + +class malloc_resource final : public pmr::memory_resource { +private: + void* do_allocate(size_t bytes, size_t align) override { + assert(align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__); + if (bytes == 0) { + bytes = 1; + } + + if (void* result = malloc(bytes)) { + return result; + } + throw bad_alloc{}; + } + + void do_deallocate(void* ptr, size_t, size_t align) noexcept override { + assert(align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__); + free(ptr); + } + + bool do_is_equal(const memory_resource& that) const noexcept override { + return typeid(malloc_resource) == typeid(that); + } +}; + +void pmr_generator_test() { + // Verify alias template + static_assert(same_as, generator>>); + static_assert(same_as, generator>>); + static_assert(same_as, generator>>); + + // Simple end-to-end test + malloc_resource mr{}; + auto g = [&mr](allocator_arg_t, pmr::polymorphic_allocator<> alloc, const int hi) -> pmr::generator { + assert(alloc.resource() == &mr); + + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + allow_allocation = false; + test_one>, int, int, int>( + g(allocator_arg, pmr::polymorphic_allocator<>{&mr}, 1024), views::iota(0, 1024)); + allow_allocation = true; +} + +int main() { + // End-to-end tests + test_one, int, int&&, int&&>(ints(), views::take(3), array{0, 1, 2}); + assert(ranges::equal(co_upto(6), views::iota(0, 6))); zip_example(); + test_weird_reference_types(); #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 recursive_test(); arbitrary_range_test(); #ifndef _M_CEE // TRANSITION, VSO-1659496 + // Verify generation of a range of pointers-to-incomplete adl_proof_test(); #endif // ^^^ no workaround ^^^ #endif // ^^^ no workaround ^^^ + + // Allocator tests + static_allocator_test(); + dynamic_allocator_test(); + pmr_generator_test(); } diff --git a/tests/std/tests/P2502R2_generator_death/test.cpp b/tests/std/tests/P2502R2_generator_death/test.cpp index 9fcf309e290..b45703596ee 100644 --- a/tests/std/tests/P2502R2_generator_death/test.cpp +++ b/tests/std/tests/P2502R2_generator_death/test.cpp @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#define _CONTAINER_DEBUG_LEVEL 1 + #include #include @@ -37,14 +39,12 @@ void test_end_iterator_incrementation() { int main(int argc, char** argv) { std_testing::death_test_executive exec; -#ifdef _DEBUG exec.add_death_tests({ test_begin_after_initial_suspend_point, test_begin_after_moving_from, test_end_iterator_dereference, test_end_iterator_incrementation, }); -#endif // _DEBUG return exec.run(argc, argv); } diff --git a/tests/std/tests/P2502R2_generator_iterator/test.cpp b/tests/std/tests/P2502R2_generator_iterator/test.cpp index 4f55690096f..045ddacdb4a 100644 --- a/tests/std/tests/P2502R2_generator_iterator/test.cpp +++ b/tests/std/tests/P2502R2_generator_iterator/test.cpp @@ -18,12 +18,6 @@ generator generate_zero() { co_return; } -template -using gen_value_t = conditional_t, remove_cvref_t, V>; - -template -using gen_reference_t = conditional_t, Ref&&, Ref>; - template > requires default_initializable && (same_as, ValueType> || constructible_from, ValueType&>) diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index 1fdd7347a96..e5d110a009d 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -53,12 +53,12 @@ void test_yield_elements_of_range(typename Gen::promise_type& p) { { using Awaitable = decltype(p.yield_value(ranges::elements_of{declval()})); - static_assert(convertible_to().await_ready()), bool>); + static_assert(same_as().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>); + static_assert(same_as().await_ready()), bool>); } } @@ -189,6 +189,9 @@ void test_one() { test_operator_new>(p); test_operator_new>(p); } + + // Non-portable size check + static_assert(sizeof(Promise) == 2 * sizeof(void*)); } template From bb573d38f8cc2a3108650acab4c0ba2ddcf108eb Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 12 Sep 2024 19:41:52 -0700 Subject: [PATCH 23/50] `forward` `expected` in `test_one` Addresses https://github.com/microsoft/STL/pull/4952#discussion_r1757906602. --- tests/std/tests/P2502R2_generator/test.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 7d58ceea5fc..358f29a75c4 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -95,9 +95,10 @@ void test_one(generator g0, invocable> auto adaptor, assert(i == ranges::cend(expected)); } -template -void test_one(generator g0, ranges::input_range auto&& expected) { - return test_one(move(g0), identity{}, expected); +template +void test_one(generator g0, Ex&& expected) { + return test_one(move(g0), identity{}, forward(expected)); } // Some simple end-to-end tests, mostly from the Working Draft or P2502R2 From b61b7ba55dc7a05ac2e498478823d8221ce6fd4d Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:00:57 -0700 Subject: [PATCH 24/50] Insert namespace `_Gen_detail` to hold implementation details --- stl/inc/generator | 767 +++++++++++++++++++++++----------------------- 1 file changed, 388 insertions(+), 379 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index c388db394cd..aba2f2e03e5 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -37,477 +37,485 @@ struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { unsigned char _Pad[__STDCPP_DEFAULT_NEW_ALIGNMENT__]; }; -template -concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v::pointer>; - template constexpr bool _Allocator_is_stateless = default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value; -template -class _Coro_promise_allocator { // statically specified allocator type -private: - using _Alloc = _Rebind_alloc_t<_Proto_allocator, _Aligned_block>; - using _Alloc_size_type = allocator_traits<_Alloc>::size_type; - - static size_t _Allocation_block_size(const size_t _Size) noexcept { - // Compute the number of _Aligned_blocks needed to store a coroutine - // frame of _Size bytes and, if necessary, an _Alloc. - if constexpr (_Allocator_is_stateless<_Alloc>) { - // allocator is stateless, we need no additional space - return (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); - } else { - // allocator is stateful, we need space for it - constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - return (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); - } - } +_EXPORT_STD template +class generator; - static void* _Allocate(_Alloc _Al, const size_t _Size) { - // memory layout: - // |--- coroutine frame --|- alignment padding -|---- _Alloc -----| - // |--- always present ---|----------- stateful only -------------| - // all rounded up to an integral number of _Aligned_blocks +namespace _Gen_detail { + template + concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v::pointer>; - const size_t _Size_in_blocks = _Allocation_block_size(_Size); - void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Size_in_blocks)); + template + class _Coro_promise_allocator { // statically specified allocator type + private: + using _Alloc = _Rebind_alloc_t<_Proto_allocator, _Aligned_block>; + using _Alloc_size_type = allocator_traits<_Alloc>::size_type; - if constexpr (!_Allocator_is_stateless<_Alloc>) { - // store only stateful allocators - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); + static size_t _Allocation_block_size(const size_t _Size) noexcept { + // Compute the number of _Aligned_blocks needed to store a coroutine + // frame of _Size bytes and, if necessary, an _Alloc. + if constexpr (_Allocator_is_stateless<_Alloc>) { + // allocator is stateless, we need no additional space + return (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); + } else { + // allocator is stateful, we need space for it + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + return (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); + } } - return _Ptr; - } + static void* _Allocate(_Alloc _Al, const size_t _Size) { + // memory layout: + // |--- coroutine frame --|- alignment padding -|---- _Alloc -----| + // |--- always present ---|----------- stateful only -------------| + // all rounded up to an integral number of _Aligned_blocks - _NODISCARD static _Alloc _Get_allocator( - [[maybe_unused]] void* const _Ptr, [[maybe_unused]] const size_t _Size) noexcept { - // recreate or retrieve a copy of the allocator used to allocate _Ptr - if constexpr (_Allocator_is_stateless<_Alloc>) { - return _Alloc{}; - } else { - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - return *reinterpret_cast<_Alloc*>(_Al_address); - } - } + const size_t _Size_in_blocks = _Allocation_block_size(_Size); + void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Size_in_blocks)); -public: - static void* operator new(const size_t _Size) - requires default_initializable<_Alloc> - { - return _Allocate(_Alloc{}, _Size); - } - - template - requires convertible_to - static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { - return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); - } + if constexpr (!_Allocator_is_stateless<_Alloc>) { + // store only stateful allocators + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); + } - template - requires convertible_to - static void* operator new(const size_t _Size, const _This&, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { - return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); - } + return _Ptr; + } - static void operator delete(void* const _Ptr, const size_t _Size) noexcept { - _Alloc _Al = _Get_allocator(_Ptr, _Size); - const size_t _Size_in_blocks = _Allocation_block_size(_Size); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Size_in_blocks)); - } -}; + _NODISCARD static _Alloc _Get_allocator( + [[maybe_unused]] void* const _Ptr, [[maybe_unused]] const size_t _Size) noexcept { + // recreate or retrieve a copy of the allocator used to allocate _Ptr + if constexpr (_Allocator_is_stateless<_Alloc>) { + return _Alloc{}; + } else { + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + return *reinterpret_cast<_Alloc*>(_Al_address); + } + } -template <> -class _Coro_promise_allocator { // type-erased allocator -private: - using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; + public: + static void* operator new(const size_t _Size) + requires default_initializable<_Alloc> + { + return _Allocate(_Alloc{}, _Size); + } - template - static size_t _Allocation_block_size(const size_t _Size) noexcept { - size_t _Bytes = _Size + sizeof(_Dealloc_fn); - if constexpr (_Allocator_is_stateless<_Alloc>) { - _Bytes += sizeof(_Aligned_block) - 1; - } else { - constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); - _Bytes += sizeof(_Alloc) + _Align - 1; - } - return _Bytes / sizeof(_Aligned_block); - } + template + requires convertible_to + static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); + } - template - static void __stdcall _Dealloc_stateless(void* const _Ptr, const size_t _Size) noexcept { - _Alloc _Al{}; - const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); - const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); - } + template + requires convertible_to + static void* operator new( + const size_t _Size, const _This&, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); + } - template - static void __stdcall _Dealloc_stateful(void* const _Ptr, const size_t _Size) noexcept { - const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + sizeof(_Dealloc_fn) + alignof(_Alloc) - 1) - & ~(alignof(_Alloc) - 1); - auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); - _Alloc _Al{_STD move(_Stored_al)}; - _Stored_al.~_Alloc(); - - const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); - const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); - _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); - } + static void operator delete(void* const _Ptr, const size_t _Size) noexcept { + _Alloc _Al = _Get_allocator(_Ptr, _Size); + const size_t _Size_in_blocks = _Allocation_block_size(_Size); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Size_in_blocks)); + } + }; - static void __stdcall _Dealloc_default(void* const _Ptr, const size_t _Size) noexcept { - ::operator delete(_Ptr, _Size + sizeof(_Dealloc_fn)); - } + template <> + class _Coro_promise_allocator { // type-erased allocator + private: + using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; + + template + static size_t _Allocation_block_size(const size_t _Size) noexcept { + size_t _Bytes = _Size + sizeof(_Dealloc_fn); + if constexpr (_Allocator_is_stateless<_Alloc>) { + _Bytes += sizeof(_Aligned_block) - 1; + } else { + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + _Bytes += sizeof(_Alloc) + _Align - 1; + } + return _Bytes / sizeof(_Aligned_block); + } - template - static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { - // memory layout: - // |--- coroutine frame (_Size bytes) ---|--- _Dealloc_fn ---|-- alignment padding --|--- _Alloc ---| - // |-------------------- always present -----(not aligned)---|------------ stateful only -----------| - // all rounded up to an integral number of _Aligned_blocks + template + static void __stdcall _Dealloc_stateless(void* const _Ptr, const size_t _Size) noexcept { + _Alloc _Al{}; + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); + } - using _Alloc = _Rebind_alloc_t<_ProtoAlloc, _Aligned_block>; - using _Alloc_size_type = allocator_traits<_Alloc>::size_type; + template + static void __stdcall _Dealloc_stateful(void* const _Ptr, const size_t _Size) noexcept { + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + sizeof(_Dealloc_fn) + alignof(_Alloc) - 1) + & ~(alignof(_Alloc) - 1); + auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); + _Alloc _Al{_STD move(_Stored_al)}; + _Stored_al.~_Alloc(); + + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); + } - _Alloc _Al{_Proto}; - const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); - const auto _Alloc_size = _Convert_size<_Alloc_size_type>(_Size_in_blocks); - void* const _Ptr = _Al.allocate(_Alloc_size); + static void __stdcall _Dealloc_default(void* const _Ptr, const size_t _Size) noexcept { + ::operator delete(_Ptr, _Size + sizeof(_Dealloc_fn)); + } - if constexpr (_Allocator_is_stateless<_Alloc>) { - // don't store stateless allocator - const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; - _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); - return _Ptr; - } else { - // store stateful allocator - const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; - _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); - _Size += sizeof(_Dealloc_fn); + template + static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { + // memory layout: + // |--- coroutine frame (_Size bytes) ---|--- _Dealloc_fn ---|-- alignment padding --|--- _Alloc ---| + // |-------------------- always present -----(not aligned)---|------------ stateful only -----------| + // all rounded up to an integral number of _Aligned_blocks + + using _Alloc = _Rebind_alloc_t<_ProtoAlloc, _Aligned_block>; + using _Alloc_size_type = allocator_traits<_Alloc>::size_type; + + _Alloc _Al{_Proto}; + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = _Convert_size<_Alloc_size_type>(_Size_in_blocks); + void* const _Ptr = _Al.allocate(_Alloc_size); + + if constexpr (_Allocator_is_stateless<_Alloc>) { + // don't store stateless allocator + const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); + return _Ptr; + } else { + // store stateful allocator + const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); + _Size += sizeof(_Dealloc_fn); + + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + ::new (reinterpret_cast(_Al_address)) _Alloc{_STD move(_Al)}; + return _Ptr; + } + } - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - ::new (reinterpret_cast(_Al_address)) _Alloc{_STD move(_Al)}; + public: + static void* operator new(const size_t _Size) { + void* const _Ptr = ::operator new(_Size + sizeof(_Dealloc_fn)); + const _Dealloc_fn _Dealloc = _Dealloc_default; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); return _Ptr; } - } - -public: - static void* operator new(const size_t _Size) { - void* const _Ptr = ::operator new(_Size + sizeof(_Dealloc_fn)); - const _Dealloc_fn _Dealloc = _Dealloc_default; - _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); - return _Ptr; - } - template - static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " - "(N4988 [coro.generator.class]/1.1)"); - return _Allocate(_Al, _Size); - } - - template - static void* operator new(const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " - "(N4988 [coro.generator.class]/1.1)"); - return _Allocate(_Al, _Size); - } + template + static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); + return _Allocate(_Al, _Size); + } - static void operator delete(void* const _Ptr, const size_t _Size) noexcept { - _Dealloc_fn _Dealloc; - _CSTD memcpy(&_Dealloc, static_cast(_Ptr) + _Size, sizeof(_Dealloc_fn)); - _Dealloc(_Ptr, _Size); - } -}; + template + static void* operator new( + const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); + return _Allocate(_Al, _Size); + } -_EXPORT_STD template -class generator; + static void operator delete(void* const _Ptr, const size_t _Size) noexcept { + _Dealloc_fn _Dealloc; + _CSTD memcpy(&_Dealloc, static_cast(_Ptr) + _Size, sizeof(_Dealloc_fn)); + _Dealloc(_Ptr, _Size); + } + }; -template -using _Gen_value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; -template -using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; -template -using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; + template + using _Gen_value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; + template + using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; + template + using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; -template -struct _Gen_iter_provider { - class _Iterator; -}; + template + struct _Gen_iter_provider { + class _Iterator; + }; -template -class _Gen_promise_base { -public: - _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); + template + class _Gen_promise_base { + public: + _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); #ifndef _PREFAST_ // TRANSITION, VSO-1662733 - _NODISCARD + _NODISCARD #endif // ^^^ no workaround ^^^ - suspend_always initial_suspend() const noexcept { - return {}; - } + suspend_always initial_suspend() const noexcept { + return {}; + } - _NODISCARD auto final_suspend() noexcept { - return _Final_awaiter{}; - } + _NODISCARD auto final_suspend() noexcept { + return _Final_awaiter{}; + } - _NODISCARD suspend_always yield_value(_Yielded _Val) noexcept { - _Ptr = _STD addressof(_Val); - return {}; - } + _NODISCARD suspend_always yield_value(_Yielded _Val) noexcept { + _Ptr = _STD addressof(_Val); + return {}; + } - _NODISCARD auto yield_value(const remove_reference_t<_Yielded>& _Val) noexcept( - is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) /* strengthened */ - requires (is_rvalue_reference_v<_Yielded> - && constructible_from, const remove_reference_t<_Yielded>&>) - { - return _Element_awaiter{_Val}; - } + _NODISCARD auto yield_value(const remove_reference_t<_Yielded>& _Val) + noexcept(is_nothrow_constructible_v, + const remove_reference_t<_Yielded>&>) /* strengthened */ + requires (is_rvalue_reference_v<_Yielded> + && constructible_from, const remove_reference_t<_Yielded>&>) + { + return _Element_awaiter{_Val}; + } - template - requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> - _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { - using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; - return _Nested_awaitable{_STD move(_Elem.range)}; - } + template + requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> + _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { + using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; + return _Nested_awaitable{_STD move(_Elem.range)}; + } - template <_RANGES input_range _Rng, class _Alloc> - requires convertible_to<_RANGES range_reference_t<_Rng>, _Yielded> - _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) { - using _Vty = _RANGES range_value_t<_Rng>; - using _Nested_awaitable = _Nested_awaitable_provider<_Yielded, _Vty, _Alloc>::_Awaitable; + template <_RANGES input_range _Rng, class _Alloc> + requires convertible_to<_RANGES range_reference_t<_Rng>, _Yielded> + _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) { + using _Vty = _RANGES range_value_t<_Rng>; + using _Nested_awaitable = _Nested_awaitable_provider<_Yielded, _Vty, _Alloc>::_Awaitable; - auto _Lambda = [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, - const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { - for (; _It != _Se; ++_It) { - co_yield static_cast<_Yielded>(*_It); - } - }; - return _Nested_awaitable{ - _Lambda(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; - } + auto _Lambda = [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, + const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { + for (; _It != _Se; ++_It) { + co_yield static_cast<_Yielded>(*_It); + } + }; + return _Nested_awaitable{ + _Lambda(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; + } - void await_transform() = delete; + void await_transform() = delete; - void return_void() const noexcept {} + void return_void() const noexcept {} - void unhandled_exception() { - if (const auto _Info = _Try_get_nest_info()) { - _Info->_Except = _STD current_exception(); - } else { - _RERAISE; + void unhandled_exception() { + if (const auto _Info = _Try_get_nest_info()) { + _Info->_Except = _STD current_exception(); + } else { + _RERAISE; + } } - } -private: - struct _Element_awaiter { - remove_cvref_t<_Yielded> _Val; + private: + struct _Element_awaiter { + remove_cvref_t<_Yielded> _Val; - explicit _Element_awaiter(const remove_reference_t<_Yielded>& _Val_) - noexcept(is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) - : _Val(_Val_) {} + explicit _Element_awaiter(const remove_reference_t<_Yielded>& _Val_) + noexcept(is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) + : _Val(_Val_) {} - _NODISCARD constexpr bool await_ready() const noexcept { - return false; - } + _NODISCARD constexpr bool await_ready() const noexcept { + return false; + } - template - constexpr void await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { + template + constexpr void await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - _Gen_promise_base& _Current = _Handle.promise(); - _Current._Ptr = _STD addressof(_Val); - } + _Gen_promise_base& _Current = _Handle.promise(); + _Current._Ptr = _STD addressof(_Val); + } - constexpr void await_resume() const noexcept {} - }; + constexpr void await_resume() const noexcept {} + }; - // generator's stack of nested coroutines is realized as a linked list of promises. The "root" promise is the - // original that existed before any nested yields. The "top" promise is the one currently being iterated over. - // Iterators hold handles for the root's coroutine and the root promise holds a link to the top promise so iterators - // can resume it to generate the next element when incremented. Each promise keeps a link to the root so they can - // update the top link as promises are pushed and popped. It looks a bit like: - // - // +--------------------------- Top ---------------------------+ - // v | - // +=====+ +=====+ +=====+ +=====+ - // | |-- Parent -->| |-- Parent -->| |-- Parent -->| | - // +=====+ +=====+ +=====+ +=====+ - // | | | ^ - // +------- Root ------+------- Root ------+------- Root ------+ - // - // The parent and root links are actually stored out-of-line in a _Nest_info. Since a promise is either a root or is - // nested, the single promise member _Data can be used to store either a top link or a pointer to a _Nest_info. - - struct _Nest_info { - exception_ptr _Except; - coroutine_handle<_Gen_promise_base> _Parent; - coroutine_handle<_Gen_promise_base> _Root; - }; + // generator's stack of nested coroutines is realized as a linked list of promises. The "root" promise is the + // original that existed before any nested yields. The "top" promise is the one currently being iterated over. + // Iterators hold handles for the root's coroutine and the root promise holds a link to the top promise so + // iterators can resume it to generate the next element when incremented. Each promise keeps a link to the root + // so they can update the top link as promises are pushed and popped. It looks a bit like: + // + // +--------------------------- Top ---------------------------+ + // v | + // +=====+ +=====+ +=====+ +=====+ + // | |-- Parent -->| |-- Parent -->| |-- Parent -->| | + // +=====+ +=====+ +=====+ +=====+ + // | | | ^ + // +------- Root ------+------- Root ------+------- Root ------+ + // + // The parent and root links are actually stored out-of-line in a _Nest_info. Since a promise is either a root + // or is nested, the single promise member _Data can be used to store either a top link or a pointer to a + // _Nest_info. + + struct _Nest_info { + exception_ptr _Except; + coroutine_handle<_Gen_promise_base> _Parent; + coroutine_handle<_Gen_promise_base> _Root; + }; - struct _Final_awaiter { - _NODISCARD bool await_ready() noexcept { - return false; - } + struct _Final_awaiter { + _NODISCARD bool await_ready() noexcept { + return false; + } - template - _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { - // Resume _Handle's parent coroutine, if any. + template + _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { + // Resume _Handle's parent coroutine, if any. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - if (const auto _Info = _Handle.promise()._Try_get_nest_info()) { - coroutine_handle<_Gen_promise_base> _Cont = _Info->_Parent; - _Info->_Root.promise()._Set_top(_Cont); - return _Cont; - } + if (const auto _Info = _Handle.promise()._Try_get_nest_info()) { + coroutine_handle<_Gen_promise_base> _Cont = _Info->_Parent; + _Info->_Root.promise()._Set_top(_Cont); + return _Cont; + } - return _STD noop_coroutine(); - } + return _STD noop_coroutine(); + } - void await_resume() noexcept {} - }; + void await_resume() noexcept {} + }; - template - struct _Nested_awaitable_provider { - struct _Awaitable { - _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); + template + struct _Nested_awaitable_provider { + struct _Awaitable { + _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); - _Nest_info _Nested; - generator<_Rty, _Vty, _Alloc> _Gen; + _Nest_info _Nested; + generator<_Rty, _Vty, _Alloc> _Gen; - explicit _Awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} + explicit _Awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} - _NODISCARD bool await_ready() noexcept { - return !_Gen._Coro; - } + _NODISCARD bool await_ready() noexcept { + return !_Gen._Coro; + } - template - _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend( - coroutine_handle<_CoroPromise> _Current) noexcept { - // Push _Gen's coroutine onto the coroutine stack that _Current is at the top of. + template + _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend( + coroutine_handle<_CoroPromise> _Current) noexcept { + // Push _Gen's coroutine onto the coroutine stack that _Current is at the top of. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); - _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); - if (const auto _Parent_nest_info = _Nested._Parent.promise()._Try_get_nest_info()) { - _Nested._Root = _Parent_nest_info->_Root; - } else { - _Nested._Root = _Nested._Parent; + auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); + if (const auto _Parent_nest_info = _Nested._Parent.promise()._Try_get_nest_info()) { + _Nested._Root = _Parent_nest_info->_Root; + } else { + _Nested._Root = _Nested._Parent; + } + _Nested._Root.promise()._Set_top(_Target); + _Target.promise()._Set_nest_info(_STD addressof(_Nested)); + return _Target; } - _Nested._Root.promise()._Set_top(_Target); - _Target.promise()._Set_nest_info(_STD addressof(_Nested)); - return _Target; - } - void await_resume() { - if (_Nested._Except) { - _STD rethrow_exception(_STD move(_Nested._Except)); + void await_resume() { + if (_Nested._Except) { + _STD rethrow_exception(_STD move(_Nested._Except)); + } } - } + }; }; - }; - _NODISCARD _Nest_info* _Try_get_nest_info() const noexcept { - if ((_Data & 1U) != 0) { - return reinterpret_cast<_Nest_info*>(_Data ^ 1U); - } + _NODISCARD _Nest_info* _Try_get_nest_info() const noexcept { + if ((_Data & 1U) != 0) { + return reinterpret_cast<_Nest_info*>(_Data ^ 1U); + } - return nullptr; - } + return nullptr; + } - _NODISCARD coroutine_handle<_Gen_promise_base> _Get_top() const noexcept { - _STL_INTERNAL_CHECK((_Data & 1U) == 0); - return coroutine_handle<_Gen_promise_base>::from_address(reinterpret_cast(_Data)); - } + _NODISCARD coroutine_handle<_Gen_promise_base> _Get_top() const noexcept { + _STL_INTERNAL_CHECK((_Data & 1U) == 0); + return coroutine_handle<_Gen_promise_base>::from_address(reinterpret_cast(_Data)); + } - void _Set_nest_info(_Nest_info* _Info) noexcept { - _Data = reinterpret_cast(_Info) | 1U; - } + void _Set_nest_info(_Nest_info* _Info) noexcept { + _Data = reinterpret_cast(_Info) | 1U; + } - void _Set_top(coroutine_handle<_Gen_promise_base> _Top) noexcept { - _Data = reinterpret_cast(_Top.address()); - } + void _Set_top(coroutine_handle<_Gen_promise_base> _Top) noexcept { + _Data = reinterpret_cast(_Top.address()); + } - template - friend struct _Gen_iter_provider; + template + friend struct _Gen_iter_provider; - // Least significant bit of `_Data` indicates stored information: - // LSB 0: `_Data` is a top coroutine handle, - // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. - uintptr_t _Data = reinterpret_cast(coroutine_handle<_Gen_promise_base>::from_promise(*this).address()); - add_pointer_t<_Yielded> _Ptr = nullptr; -}; + // Least significant bit of `_Data` indicates stored information: + // LSB 0: `_Data` is a top coroutine handle, + // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. + uintptr_t _Data = + reinterpret_cast(coroutine_handle<_Gen_promise_base>::from_promise(*this).address()); + add_pointer_t<_Yielded> _Ptr = nullptr; + }; -struct _Gen_secret_tag {}; + struct _Gen_secret_tag {}; -template -class _Gen_iter_provider<_Value, _Ref>::_Iterator { -public: - using value_type = _Value; - using difference_type = ptrdiff_t; + template + class _Gen_iter_provider<_Value, _Ref>::_Iterator { + public: + using value_type = _Value; + using difference_type = ptrdiff_t; - _Iterator(_Iterator&& _That) noexcept : _Coro{_STD exchange(_That._Coro, {})} {} + _Iterator(_Iterator&& _That) noexcept : _Coro{_STD exchange(_That._Coro, {})} {} - _Iterator& operator=(_Iterator&& _That) noexcept { - _Coro = _STD exchange(_That._Coro, {}); - return *this; - } + _Iterator& operator=(_Iterator&& _That) noexcept { + _Coro = _STD exchange(_That._Coro, {}); + return *this; + } - _NODISCARD _Ref operator*() const - noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) { + _NODISCARD _Ref operator*() const + noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) { #if _CONTAINER_DEBUG_LEVEL > 0 - _STL_VERIFY(!_Coro.done(), "Can't dereference generator end iterator"); + _STL_VERIFY(!_Coro.done(), "Can't dereference generator end iterator"); #endif // _CONTAINER_DEBUG_LEVEL > 0 - return static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr); - } + return static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr); + } - _Iterator& operator++() { + _Iterator& operator++() { #if _CONTAINER_DEBUG_LEVEL > 0 - _STL_VERIFY(!_Coro.done(), "Can't increment generator end iterator"); + _STL_VERIFY(!_Coro.done(), "Can't increment generator end iterator"); #endif // _CONTAINER_DEBUG_LEVEL > 0 - _Coro.promise()._Get_top().resume(); - return *this; - } + _Coro.promise()._Get_top().resume(); + return *this; + } - void operator++(int) { - ++*this; - } + void operator++(int) { + ++*this; + } - _NODISCARD friend bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ - { - return _It._Coro.done(); - } + _NODISCARD friend bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ + { + return _It._Coro.done(); + } -private: - template - friend class generator; + private: + template + friend class _STD generator; - explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept - : _Coro{_Coro_} {} + explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept + : _Coro{_Coro_} {} - coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro; -}; + coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro; + }; +} // namespace _Gen_detail _EXPORT_STD template class generator : public _RANGES view_interface> { private: - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " - "(N4988 [coro.generator.class]/1.1)"); + static_assert(_Gen_detail::_Has_real_pointers<_Alloc>, + "generator allocators must use raw pointers (N4988 [coro.generator.class]/1.1)"); - using _Value = _Gen_value_t<_Rty, _Vty>; + using _Value = _Gen_detail::_Gen_value_t<_Rty, _Vty>; static_assert(same_as, _Value> && is_object_v<_Value>, "generator's value type must be a cv-unqualified object type (N4988 [coro.generator.class]/1.2)"); - using _Ref = _Gen_reference_t<_Rty, _Vty>; + using _Ref = _Gen_detail::_Gen_reference_t<_Rty, _Vty>; static_assert( is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), "generator's selected reference type must be an actual reference type " @@ -521,18 +529,19 @@ private: "but that's impossible with the selected value and reference types (N4988 [coro.generator.class]/1.4)"); public: - using yielded = _Gen_yield_t<_Ref>; + using yielded = _Gen_detail::_Gen_yield_t<_Ref>; - friend _Gen_promise_base; + friend _Gen_detail::_Gen_promise_base; - struct __declspec(empty_bases) promise_type : _Coro_promise_allocator<_Alloc>, _Gen_promise_base { + struct __declspec(empty_bases) promise_type : _Gen_detail::_Coro_promise_allocator<_Alloc>, + _Gen_detail::_Gen_promise_base { _NODISCARD generator get_return_object() noexcept { - return generator{_Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; + return generator{_Gen_detail::_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, promise_type>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_detail::_Gen_promise_base, promise_type>); #endif // ^^^ no workaround ^^^ generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} @@ -548,14 +557,14 @@ public: return *this; } - _NODISCARD _Gen_iter_provider<_Value, _Ref>::_Iterator begin() { + _NODISCARD _Gen_detail::_Gen_iter_provider<_Value, _Ref>::_Iterator begin() { // Pre: _Coro is suspended at its initial suspend point #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Coro, "Can't call begin on moved-from generator"); #endif // _CONTAINER_DEBUG_LEVEL > 0 _Coro.resume(); - return typename _Gen_iter_provider<_Value, _Ref>::_Iterator{ - _Gen_secret_tag{}, coroutine_handle<_Gen_promise_base>::from_address(_Coro.address())}; + return typename _Gen_detail::_Gen_iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Gen_secret_tag{}, + coroutine_handle<_Gen_detail::_Gen_promise_base>::from_address(_Coro.address())}; } _NODISCARD default_sentinel_t end() const noexcept { @@ -568,7 +577,7 @@ private: // awaitable objects, so all necessary storage is allocated in coroutine frames. See the description in the body of // _Gen_promise_base. - explicit generator(_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} + explicit generator(_Gen_detail::_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} }; #ifdef __cpp_lib_byte From 4d372ed7411b92b819e413fbeb01e0a2277fa605 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:01:58 -0700 Subject: [PATCH 25/50] Rename `_Allocator_is_stateless` to `_Stateless_allocator` --- stl/inc/generator | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index aba2f2e03e5..f4f2fbd8688 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -38,7 +38,7 @@ struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { }; template -constexpr bool _Allocator_is_stateless = +constexpr bool _Stateless_allocator = default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value; _EXPORT_STD template @@ -57,7 +57,7 @@ namespace _Gen_detail { static size_t _Allocation_block_size(const size_t _Size) noexcept { // Compute the number of _Aligned_blocks needed to store a coroutine // frame of _Size bytes and, if necessary, an _Alloc. - if constexpr (_Allocator_is_stateless<_Alloc>) { + if constexpr (_Stateless_allocator<_Alloc>) { // allocator is stateless, we need no additional space return (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); } else { @@ -76,7 +76,7 @@ namespace _Gen_detail { const size_t _Size_in_blocks = _Allocation_block_size(_Size); void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Size_in_blocks)); - if constexpr (!_Allocator_is_stateless<_Alloc>) { + if constexpr (!_Stateless_allocator<_Alloc>) { // store only stateful allocators const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); @@ -89,7 +89,7 @@ namespace _Gen_detail { _NODISCARD static _Alloc _Get_allocator( [[maybe_unused]] void* const _Ptr, [[maybe_unused]] const size_t _Size) noexcept { // recreate or retrieve a copy of the allocator used to allocate _Ptr - if constexpr (_Allocator_is_stateless<_Alloc>) { + if constexpr (_Stateless_allocator<_Alloc>) { return _Alloc{}; } else { const auto _Al_address = @@ -133,7 +133,7 @@ namespace _Gen_detail { template static size_t _Allocation_block_size(const size_t _Size) noexcept { size_t _Bytes = _Size + sizeof(_Dealloc_fn); - if constexpr (_Allocator_is_stateless<_Alloc>) { + if constexpr (_Stateless_allocator<_Alloc>) { _Bytes += sizeof(_Aligned_block) - 1; } else { constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); @@ -183,7 +183,7 @@ namespace _Gen_detail { const auto _Alloc_size = _Convert_size<_Alloc_size_type>(_Size_in_blocks); void* const _Ptr = _Al.allocate(_Alloc_size); - if constexpr (_Allocator_is_stateless<_Alloc>) { + if constexpr (_Stateless_allocator<_Alloc>) { // don't store stateless allocator const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); From 6482abea557777fb63221a381f32524d44187e98 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:02:38 -0700 Subject: [PATCH 26/50] Rename `_Has_real_pointers` to `_Valid_allocator` --- stl/inc/generator | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index f4f2fbd8688..1a7d70ffed5 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -46,7 +46,7 @@ class generator; namespace _Gen_detail { template - concept _Has_real_pointers = same_as<_Alloc, void> || is_pointer_v::pointer>; + concept _Valid_allocator = same_as<_Alloc, void> || is_pointer_v::pointer>; template class _Coro_promise_allocator { // statically specified allocator type @@ -211,7 +211,7 @@ namespace _Gen_detail { template static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } @@ -219,7 +219,7 @@ namespace _Gen_detail { template static void* operator new( const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers " + static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } @@ -508,7 +508,7 @@ namespace _Gen_detail { _EXPORT_STD template class generator : public _RANGES view_interface> { private: - static_assert(_Gen_detail::_Has_real_pointers<_Alloc>, + static_assert(_Gen_detail::_Valid_allocator<_Alloc>, "generator allocators must use raw pointers (N4988 [coro.generator.class]/1.1)"); using _Value = _Gen_detail::_Gen_value_t<_Rty, _Vty>; From 2f9cf3880edbe091b42fded011a735738c13fdc8 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:04:55 -0700 Subject: [PATCH 27/50] Rename `_Coro_promise_allocator` to `_Promise_allocator` --- stl/inc/generator | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 1a7d70ffed5..8339bd41147 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -49,7 +49,7 @@ namespace _Gen_detail { concept _Valid_allocator = same_as<_Alloc, void> || is_pointer_v::pointer>; template - class _Coro_promise_allocator { // statically specified allocator type + class _Promise_allocator { // statically specified allocator type private: using _Alloc = _Rebind_alloc_t<_Proto_allocator, _Aligned_block>; using _Alloc_size_type = allocator_traits<_Alloc>::size_type; @@ -126,7 +126,7 @@ namespace _Gen_detail { }; template <> - class _Coro_promise_allocator { // type-erased allocator + class _Promise_allocator { // type-erased allocator private: using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; @@ -533,7 +533,7 @@ public: friend _Gen_detail::_Gen_promise_base; - struct __declspec(empty_bases) promise_type : _Gen_detail::_Coro_promise_allocator<_Alloc>, + struct __declspec(empty_bases) promise_type : _Gen_detail::_Promise_allocator<_Alloc>, _Gen_detail::_Gen_promise_base { _NODISCARD generator get_return_object() noexcept { return generator{_Gen_detail::_Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; From 483d091f0eec79ec4c89f72bdf579ff21c0ccf89 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:05:44 -0700 Subject: [PATCH 28/50] Rename `_Gen_value_t` to `_Value_t` --- stl/inc/generator | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 8339bd41147..916479c3116 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -232,7 +232,7 @@ namespace _Gen_detail { }; template - using _Gen_value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; + using _Value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; template using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; template @@ -511,7 +511,7 @@ private: static_assert(_Gen_detail::_Valid_allocator<_Alloc>, "generator allocators must use raw pointers (N4988 [coro.generator.class]/1.1)"); - using _Value = _Gen_detail::_Gen_value_t<_Rty, _Vty>; + using _Value = _Gen_detail::_Value_t<_Rty, _Vty>; static_assert(same_as, _Value> && is_object_v<_Value>, "generator's value type must be a cv-unqualified object type (N4988 [coro.generator.class]/1.2)"); From 402b5d83a9585142ae7b632e47a353ea64fd8845 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:06:34 -0700 Subject: [PATCH 29/50] Rename `_Gen_reference_t` to `_Reference_t` --- stl/inc/generator | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 916479c3116..7e6ac2148be 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -234,7 +234,7 @@ namespace _Gen_detail { template using _Value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; template - using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; + using _Reference_t = conditional_t, _Rty&&, _Rty>; template using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; @@ -274,7 +274,7 @@ namespace _Gen_detail { } template - requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> + requires same_as<_Gen_yield_t<_Reference_t<_Rty, _Vty>>, _Yielded> _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; return _Nested_awaitable{_STD move(_Elem.range)}; @@ -384,7 +384,7 @@ namespace _Gen_detail { template struct _Nested_awaitable_provider { struct _Awaitable { - _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); + _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Reference_t<_Rty, _Vty>>, _Yielded>); _Nest_info _Nested; generator<_Rty, _Vty, _Alloc> _Gen; @@ -515,7 +515,7 @@ private: static_assert(same_as, _Value> && is_object_v<_Value>, "generator's value type must be a cv-unqualified object type (N4988 [coro.generator.class]/1.2)"); - using _Ref = _Gen_detail::_Gen_reference_t<_Rty, _Vty>; + using _Ref = _Gen_detail::_Reference_t<_Rty, _Vty>; static_assert( is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), "generator's selected reference type must be an actual reference type " From 6c957a273bbb6de802e794ac495495cbb5e54a52 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:07:15 -0700 Subject: [PATCH 30/50] Rename `_Gen_yield_t` to `_Yield_t` --- stl/inc/generator | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 7e6ac2148be..5a6600307e4 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -236,7 +236,7 @@ namespace _Gen_detail { template using _Reference_t = conditional_t, _Rty&&, _Rty>; template - using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; + using _Yield_t = conditional_t, _Ref, const _Ref&>; template struct _Gen_iter_provider { @@ -274,7 +274,7 @@ namespace _Gen_detail { } template - requires same_as<_Gen_yield_t<_Reference_t<_Rty, _Vty>>, _Yielded> + requires same_as<_Yield_t<_Reference_t<_Rty, _Vty>>, _Yielded> _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; return _Nested_awaitable{_STD move(_Elem.range)}; @@ -384,7 +384,7 @@ namespace _Gen_detail { template struct _Nested_awaitable_provider { struct _Awaitable { - _STL_INTERNAL_STATIC_ASSERT(same_as<_Gen_yield_t<_Reference_t<_Rty, _Vty>>, _Yielded>); + _STL_INTERNAL_STATIC_ASSERT(same_as<_Yield_t<_Reference_t<_Rty, _Vty>>, _Yielded>); _Nest_info _Nested; generator<_Rty, _Vty, _Alloc> _Gen; @@ -498,10 +498,10 @@ namespace _Gen_detail { template friend class _STD generator; - explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept + explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Yield_t<_Ref>>> _Coro_) noexcept : _Coro{_Coro_} {} - coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro; + coroutine_handle<_Gen_promise_base<_Yield_t<_Ref>>> _Coro; }; } // namespace _Gen_detail @@ -529,7 +529,7 @@ private: "but that's impossible with the selected value and reference types (N4988 [coro.generator.class]/1.4)"); public: - using yielded = _Gen_detail::_Gen_yield_t<_Ref>; + using yielded = _Gen_detail::_Yield_t<_Ref>; friend _Gen_detail::_Gen_promise_base; From 43ed77d6008a8a9a7d29c9c522e1cbd7a9b4d51f Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:08:06 -0700 Subject: [PATCH 31/50] Rename `_Gen_iter_provider` to `_Iter_provider` --- stl/inc/generator | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 5a6600307e4..6ab1bd21274 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -239,7 +239,7 @@ namespace _Gen_detail { using _Yield_t = conditional_t, _Ref, const _Ref&>; template - struct _Gen_iter_provider { + struct _Iter_provider { class _Iterator; }; @@ -444,7 +444,7 @@ namespace _Gen_detail { } template - friend struct _Gen_iter_provider; + friend struct _Iter_provider; // Least significant bit of `_Data` indicates stored information: // LSB 0: `_Data` is a top coroutine handle, @@ -457,7 +457,7 @@ namespace _Gen_detail { struct _Gen_secret_tag {}; template - class _Gen_iter_provider<_Value, _Ref>::_Iterator { + class _Iter_provider<_Value, _Ref>::_Iterator { public: using value_type = _Value; using difference_type = ptrdiff_t; @@ -557,13 +557,13 @@ public: return *this; } - _NODISCARD _Gen_detail::_Gen_iter_provider<_Value, _Ref>::_Iterator begin() { + _NODISCARD _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator begin() { // Pre: _Coro is suspended at its initial suspend point #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Coro, "Can't call begin on moved-from generator"); #endif // _CONTAINER_DEBUG_LEVEL > 0 _Coro.resume(); - return typename _Gen_detail::_Gen_iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Gen_secret_tag{}, + return typename _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Gen_secret_tag{}, coroutine_handle<_Gen_detail::_Gen_promise_base>::from_address(_Coro.address())}; } From bba8c2ed6754f4f70769ebf3b8d08cec3b2248cb Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:08:44 -0700 Subject: [PATCH 32/50] Rename `_Gen_promise_base` to `_Promise_base` --- stl/inc/generator | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 6ab1bd21274..8aff5f2588a 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -244,7 +244,7 @@ namespace _Gen_detail { }; template - class _Gen_promise_base { + class _Promise_base { public: _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); @@ -323,10 +323,10 @@ namespace _Gen_detail { template constexpr void await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - _Gen_promise_base& _Current = _Handle.promise(); + _Promise_base& _Current = _Handle.promise(); _Current._Ptr = _STD addressof(_Val); } @@ -353,8 +353,8 @@ namespace _Gen_detail { struct _Nest_info { exception_ptr _Except; - coroutine_handle<_Gen_promise_base> _Parent; - coroutine_handle<_Gen_promise_base> _Root; + coroutine_handle<_Promise_base> _Parent; + coroutine_handle<_Promise_base> _Root; }; struct _Final_awaiter { @@ -366,11 +366,11 @@ namespace _Gen_detail { _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { // Resume _Handle's parent coroutine, if any. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ if (const auto _Info = _Handle.promise()._Try_get_nest_info()) { - coroutine_handle<_Gen_promise_base> _Cont = _Info->_Parent; + coroutine_handle<_Promise_base> _Cont = _Info->_Parent; _Info->_Root.promise()._Set_top(_Cont); return _Cont; } @@ -396,14 +396,14 @@ namespace _Gen_detail { } template - _NODISCARD coroutine_handle<_Gen_promise_base> await_suspend( + _NODISCARD coroutine_handle<_Promise_base> await_suspend( coroutine_handle<_CoroPromise> _Current) noexcept { // Push _Gen's coroutine onto the coroutine stack that _Current is at the top of. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 - _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _CoroPromise>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); #endif // ^^^ no workaround ^^^ - auto _Target = coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); - _Nested._Parent = coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); + auto _Target = coroutine_handle<_Promise_base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Promise_base>::from_address(_Current.address()); if (const auto _Parent_nest_info = _Nested._Parent.promise()._Try_get_nest_info()) { _Nested._Root = _Parent_nest_info->_Root; } else { @@ -430,16 +430,16 @@ namespace _Gen_detail { return nullptr; } - _NODISCARD coroutine_handle<_Gen_promise_base> _Get_top() const noexcept { + _NODISCARD coroutine_handle<_Promise_base> _Get_top() const noexcept { _STL_INTERNAL_CHECK((_Data & 1U) == 0); - return coroutine_handle<_Gen_promise_base>::from_address(reinterpret_cast(_Data)); + return coroutine_handle<_Promise_base>::from_address(reinterpret_cast(_Data)); } void _Set_nest_info(_Nest_info* _Info) noexcept { _Data = reinterpret_cast(_Info) | 1U; } - void _Set_top(coroutine_handle<_Gen_promise_base> _Top) noexcept { + void _Set_top(coroutine_handle<_Promise_base> _Top) noexcept { _Data = reinterpret_cast(_Top.address()); } @@ -450,7 +450,7 @@ namespace _Gen_detail { // LSB 0: `_Data` is a top coroutine handle, // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. uintptr_t _Data = - reinterpret_cast(coroutine_handle<_Gen_promise_base>::from_promise(*this).address()); + reinterpret_cast(coroutine_handle<_Promise_base>::from_promise(*this).address()); add_pointer_t<_Yielded> _Ptr = nullptr; }; @@ -498,10 +498,10 @@ namespace _Gen_detail { template friend class _STD generator; - explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Gen_promise_base<_Yield_t<_Ref>>> _Coro_) noexcept + explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro_) noexcept : _Coro{_Coro_} {} - coroutine_handle<_Gen_promise_base<_Yield_t<_Ref>>> _Coro; + coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro; }; } // namespace _Gen_detail @@ -531,17 +531,17 @@ private: public: using yielded = _Gen_detail::_Yield_t<_Ref>; - friend _Gen_detail::_Gen_promise_base; + friend _Gen_detail::_Promise_base; struct __declspec(empty_bases) promise_type : _Gen_detail::_Promise_allocator<_Alloc>, - _Gen_detail::_Gen_promise_base { + _Gen_detail::_Promise_base { _NODISCARD generator get_return_object() noexcept { return generator{_Gen_detail::_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_detail::_Gen_promise_base, promise_type>); + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Gen_detail::_Promise_base, promise_type>); #endif // ^^^ no workaround ^^^ generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} @@ -564,7 +564,7 @@ public: #endif // _CONTAINER_DEBUG_LEVEL > 0 _Coro.resume(); return typename _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Gen_secret_tag{}, - coroutine_handle<_Gen_detail::_Gen_promise_base>::from_address(_Coro.address())}; + coroutine_handle<_Gen_detail::_Promise_base>::from_address(_Coro.address())}; } _NODISCARD default_sentinel_t end() const noexcept { @@ -575,7 +575,7 @@ private: coroutine_handle _Coro = nullptr; // The stack of coroutine handles depicted in the Standard is realized by a linked structure of promises and // awaitable objects, so all necessary storage is allocated in coroutine frames. See the description in the body of - // _Gen_promise_base. + // _Promise_base. explicit generator(_Gen_detail::_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} }; From c4a4d6ebd2d233262cae2d92c6694cc25a78d88a Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:09:44 -0700 Subject: [PATCH 33/50] Rename `_Gen_secret_tag` to `_Secret_tag` --- stl/inc/generator | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 8aff5f2588a..4e69932df00 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -454,7 +454,7 @@ namespace _Gen_detail { add_pointer_t<_Yielded> _Ptr = nullptr; }; - struct _Gen_secret_tag {}; + struct _Secret_tag {}; template class _Iter_provider<_Value, _Ref>::_Iterator { @@ -498,7 +498,7 @@ namespace _Gen_detail { template friend class _STD generator; - explicit _Iterator(_Gen_secret_tag, coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro_) noexcept + explicit _Iterator(_Secret_tag, coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro_) noexcept : _Coro{_Coro_} {} coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro; @@ -536,7 +536,7 @@ public: struct __declspec(empty_bases) promise_type : _Gen_detail::_Promise_allocator<_Alloc>, _Gen_detail::_Promise_base { _NODISCARD generator get_return_object() noexcept { - return generator{_Gen_detail::_Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; + return generator{_Gen_detail::_Secret_tag{}, coroutine_handle::from_promise(*this)}; } }; _STL_INTERNAL_STATIC_ASSERT(is_standard_layout_v); @@ -563,7 +563,7 @@ public: _STL_VERIFY(_Coro, "Can't call begin on moved-from generator"); #endif // _CONTAINER_DEBUG_LEVEL > 0 _Coro.resume(); - return typename _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Gen_secret_tag{}, + return typename _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Secret_tag{}, coroutine_handle<_Gen_detail::_Promise_base>::from_address(_Coro.address())}; } @@ -577,7 +577,7 @@ private: // awaitable objects, so all necessary storage is allocated in coroutine frames. See the description in the body of // _Promise_base. - explicit generator(_Gen_detail::_Gen_secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} + explicit generator(_Gen_detail::_Secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} }; #ifdef __cpp_lib_byte From 9da3deb83ca38ef4480df614e49001516fd201d1 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:11:35 -0700 Subject: [PATCH 34/50] Clarify comments --- stl/inc/generator | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 4e69932df00..65c630bda43 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -58,10 +58,10 @@ namespace _Gen_detail { // Compute the number of _Aligned_blocks needed to store a coroutine // frame of _Size bytes and, if necessary, an _Alloc. if constexpr (_Stateless_allocator<_Alloc>) { - // allocator is stateless, we need no additional space + // allocator is stateless, we need not store it return (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); } else { - // allocator is stateful, we need space for it + // allocator is stateful, we need storage for it constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); return (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); } @@ -575,7 +575,7 @@ private: coroutine_handle _Coro = nullptr; // The stack of coroutine handles depicted in the Standard is realized by a linked structure of promises and // awaitable objects, so all necessary storage is allocated in coroutine frames. See the description in the body of - // _Promise_base. + // _Gen_detail::_Promise_base. explicit generator(_Gen_detail::_Secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} }; From a4a6b5529996d39553e2dd92ba38a00ad5b2c91e Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 16 Sep 2024 20:16:38 -0700 Subject: [PATCH 35/50] Rewrap some messages and reformat --- stl/inc/generator | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 65c630bda43..a235de86f74 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -38,8 +38,7 @@ struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { }; template -constexpr bool _Stateless_allocator = - default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value; +constexpr bool _Stateless_allocator = default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value; _EXPORT_STD template class generator; @@ -212,7 +211,7 @@ namespace _Gen_detail { template static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " - "(N4988 [coro.generator.class]/1.1)"); + "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } @@ -220,7 +219,7 @@ namespace _Gen_detail { static void* operator new( const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " - "(N4988 [coro.generator.class]/1.1)"); + "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } @@ -327,7 +326,7 @@ namespace _Gen_detail { #endif // ^^^ no workaround ^^^ _Promise_base& _Current = _Handle.promise(); - _Current._Ptr = _STD addressof(_Val); + _Current._Ptr = _STD addressof(_Val); } constexpr void await_resume() const noexcept {} @@ -449,8 +448,7 @@ namespace _Gen_detail { // Least significant bit of `_Data` indicates stored information: // LSB 0: `_Data` is a top coroutine handle, // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. - uintptr_t _Data = - reinterpret_cast(coroutine_handle<_Promise_base>::from_promise(*this).address()); + uintptr_t _Data = reinterpret_cast(coroutine_handle<_Promise_base>::from_promise(*this).address()); add_pointer_t<_Yielded> _Ptr = nullptr; }; @@ -518,15 +516,15 @@ private: using _Ref = _Gen_detail::_Reference_t<_Rty, _Vty>; static_assert( is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), - "generator's selected reference type must be an actual reference type " - "or a cv-unqualified copy-constructible object type (N4988 [coro.generator.class]/1.3)"); + "generator's selected reference type must be an actual reference type or a cv-unqualified copy-constructible " + "object type (N4988 [coro.generator.class]/1.3)"); using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; static_assert(common_reference_with<_Ref&&, _Value&> && common_reference_with<_Ref&&, _RRef&&> && common_reference_with<_RRef&&, const _Value&>, - "generator's iterator type must model indirectly_readable, " - "but that's impossible with the selected value and reference types (N4988 [coro.generator.class]/1.4)"); + "generator's iterator type must model indirectly_readable, but that's impossible with the selected value and " + "reference types (N4988 [coro.generator.class]/1.4)"); public: using yielded = _Gen_detail::_Yield_t<_Ref>; @@ -541,7 +539,8 @@ public: }; _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_detail::_Promise_base, promise_type>); + _STL_INTERNAL_STATIC_ASSERT( + is_pointer_interconvertible_base_of_v<_Gen_detail::_Promise_base, promise_type>); #endif // ^^^ no workaround ^^^ generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} From 3ab2a88a096924de1df23a4a9fc5c627f71cdfb7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 14:17:50 -0700 Subject: [PATCH 36/50] Avoid `#pragma once` in product code. --- stl/inc/generator | 1 - 1 file changed, 1 deletion(-) diff --git a/stl/inc/generator b/stl/inc/generator index a235de86f74..8582284494e 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -3,7 +3,6 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#pragma once #ifndef _GENERATOR_ #define _GENERATOR_ #include From 215c774e15a955ca5f3bb10f3a488dad6023c874 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 14:18:12 -0700 Subject: [PATCH 37/50] Default to `_Alloc = _Alloc()` for consistency. --- stl/inc/xmemory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 1ceb6b3560c..ad1530dab62 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2602,7 +2602,7 @@ namespace ranges { #if defined(__cpp_lib_byte) template > - elements_of(_Rng&&, _Alloc = {}) -> elements_of<_Rng&&, _Alloc>; + elements_of(_Rng&&, _Alloc = _Alloc()) -> elements_of<_Rng&&, _Alloc>; #else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv template elements_of(_Rng&&, _Alloc) -> elements_of<_Rng&&, _Alloc>; From c16ccbb43da403e632bdaa0e89b879652d9b9f81 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:37:28 -0700 Subject: [PATCH 38/50] Add `_NODISCARD` and `_NODISCARD_RAW_PTR_ALLOC`. --- stl/inc/generator | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 8582284494e..b9d37eb3c44 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -52,7 +52,7 @@ namespace _Gen_detail { using _Alloc = _Rebind_alloc_t<_Proto_allocator, _Aligned_block>; using _Alloc_size_type = allocator_traits<_Alloc>::size_type; - static size_t _Allocation_block_size(const size_t _Size) noexcept { + _NODISCARD static size_t _Allocation_block_size(const size_t _Size) noexcept { // Compute the number of _Aligned_blocks needed to store a coroutine // frame of _Size bytes and, if necessary, an _Alloc. if constexpr (_Stateless_allocator<_Alloc>) { @@ -65,7 +65,7 @@ namespace _Gen_detail { } } - static void* _Allocate(_Alloc _Al, const size_t _Size) { + _NODISCARD_RAW_PTR_ALLOC static void* _Allocate(_Alloc _Al, const size_t _Size) { // memory layout: // |--- coroutine frame --|- alignment padding -|---- _Alloc -----| // |--- always present ---|----------- stateful only -------------| @@ -97,7 +97,7 @@ namespace _Gen_detail { } public: - static void* operator new(const size_t _Size) + _NODISCARD_RAW_PTR_ALLOC static void* operator new(const size_t _Size) requires default_initializable<_Alloc> { return _Allocate(_Alloc{}, _Size); @@ -105,13 +105,14 @@ namespace _Gen_detail { template requires convertible_to - static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + _NODISCARD_RAW_PTR_ALLOC static void* operator new( + const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); } template requires convertible_to - static void* operator new( + _NODISCARD_RAW_PTR_ALLOC static void* operator new( const size_t _Size, const _This&, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); } @@ -129,7 +130,7 @@ namespace _Gen_detail { using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; template - static size_t _Allocation_block_size(const size_t _Size) noexcept { + _NODISCARD static size_t _Allocation_block_size(const size_t _Size) noexcept { size_t _Bytes = _Size + sizeof(_Dealloc_fn); if constexpr (_Stateless_allocator<_Alloc>) { _Bytes += sizeof(_Aligned_block) - 1; @@ -167,7 +168,7 @@ namespace _Gen_detail { } template - static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { + _NODISCARD_RAW_PTR_ALLOC static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { // memory layout: // |--- coroutine frame (_Size bytes) ---|--- _Dealloc_fn ---|-- alignment padding --|--- _Alloc ---| // |-------------------- always present -----(not aligned)---|------------ stateful only -----------| @@ -200,7 +201,7 @@ namespace _Gen_detail { } public: - static void* operator new(const size_t _Size) { + _NODISCARD_RAW_PTR_ALLOC static void* operator new(const size_t _Size) { void* const _Ptr = ::operator new(_Size + sizeof(_Dealloc_fn)); const _Dealloc_fn _Dealloc = _Dealloc_default; _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); @@ -208,14 +209,15 @@ namespace _Gen_detail { } template - static void* operator new(const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + _NODISCARD_RAW_PTR_ALLOC static void* operator new( + const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " "(N4988 [coro.generator.class]/1.1)"); return _Allocate(_Al, _Size); } template - static void* operator new( + _NODISCARD_RAW_PTR_ALLOC static void* operator new( const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " "(N4988 [coro.generator.class]/1.1)"); From 124dc45c1b72ee1964effe0bfa5d35c1e43daefb Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:39:21 -0700 Subject: [PATCH 39/50] Add `_STL_INTERNAL_STATIC_ASSERT` for stateless/stateful. --- stl/inc/generator | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stl/inc/generator b/stl/inc/generator index b9d37eb3c44..914b99dac25 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -143,6 +143,7 @@ namespace _Gen_detail { template static void __stdcall _Dealloc_stateless(void* const _Ptr, const size_t _Size) noexcept { + _STL_INTERNAL_STATIC_ASSERT(_Stateless_allocator<_Alloc>); _Alloc _Al{}; const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); @@ -151,6 +152,7 @@ namespace _Gen_detail { template static void __stdcall _Dealloc_stateful(void* const _Ptr, const size_t _Size) noexcept { + _STL_INTERNAL_STATIC_ASSERT(!_Stateless_allocator<_Alloc>); const auto _Al_address = (reinterpret_cast(_Ptr) + _Size + sizeof(_Dealloc_fn) + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); From 332e3a69e462c171253bd90fe7c5dbea9d422695 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:49:11 -0700 Subject: [PATCH 40/50] Extract `_Get_allocator_address()`. --- stl/inc/generator | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 914b99dac25..3a76a892108 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -46,6 +46,14 @@ namespace _Gen_detail { template concept _Valid_allocator = same_as<_Alloc, void> || is_pointer_v::pointer>; + template + _NODISCARD _Alloc* _Get_allocator_address(void* const _Ptr, const size_t _Size) noexcept { + _STL_INTERNAL_STATIC_ASSERT(!_Stateless_allocator<_Alloc>); + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + return reinterpret_cast<_Alloc*>(_Al_address); + } + template class _Promise_allocator { // statically specified allocator type private: @@ -76,9 +84,8 @@ namespace _Gen_detail { if constexpr (!_Stateless_allocator<_Alloc>) { // store only stateful allocators - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - ::new (reinterpret_cast(_Al_address)) _Alloc(_STD move(_Al)); + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size); + ::new (static_cast(_Al_address)) _Alloc(_STD move(_Al)); } return _Ptr; @@ -90,9 +97,8 @@ namespace _Gen_detail { if constexpr (_Stateless_allocator<_Alloc>) { return _Alloc{}; } else { - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - return *reinterpret_cast<_Alloc*>(_Al_address); + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size); + return *_Al_address; } } @@ -153,10 +159,8 @@ namespace _Gen_detail { template static void __stdcall _Dealloc_stateful(void* const _Ptr, const size_t _Size) noexcept { _STL_INTERNAL_STATIC_ASSERT(!_Stateless_allocator<_Alloc>); - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + sizeof(_Dealloc_fn) + alignof(_Alloc) - 1) - & ~(alignof(_Alloc) - 1); - auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size + sizeof(_Dealloc_fn)); + auto& _Stored_al = *_Al_address; _Alloc _Al{_STD move(_Stored_al)}; _Stored_al.~_Alloc(); @@ -170,7 +174,7 @@ namespace _Gen_detail { } template - _NODISCARD_RAW_PTR_ALLOC static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { + _NODISCARD_RAW_PTR_ALLOC static void* _Allocate(const _ProtoAlloc& _Proto, const size_t _Size) { // memory layout: // |--- coroutine frame (_Size bytes) ---|--- _Dealloc_fn ---|-- alignment padding --|--- _Alloc ---| // |-------------------- always present -----(not aligned)---|------------ stateful only -----------| @@ -193,11 +197,9 @@ namespace _Gen_detail { // store stateful allocator const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); - _Size += sizeof(_Dealloc_fn); - const auto _Al_address = - (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); - ::new (reinterpret_cast(_Al_address)) _Alloc{_STD move(_Al)}; + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size + sizeof(_Dealloc_fn)); + ::new (static_cast(_Al_address)) _Alloc{_STD move(_Al)}; return _Ptr; } } From 1532f083ba2b8bf89359b18061e23e80a5e212dc Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:51:04 -0700 Subject: [PATCH 41/50] Use `construct_at` with properly-typed `_Alloc*`. --- stl/inc/generator | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 3a76a892108..88acd27227e 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -85,7 +85,7 @@ namespace _Gen_detail { if constexpr (!_Stateless_allocator<_Alloc>) { // store only stateful allocators const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size); - ::new (static_cast(_Al_address)) _Alloc(_STD move(_Al)); + _STD construct_at(_Al_address, _STD move(_Al)); } return _Ptr; @@ -199,7 +199,7 @@ namespace _Gen_detail { _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size + sizeof(_Dealloc_fn)); - ::new (static_cast(_Al_address)) _Alloc{_STD move(_Al)}; + _STD construct_at(_Al_address, _STD move(_Al)); return _Ptr; } } From 75144b61eae2a7c871a83714828f408d7c49617c Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:52:13 -0700 Subject: [PATCH 42/50] Consistently use `sizeof(_Dealloc_fn)`. --- stl/inc/generator | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 88acd27227e..dbff11c199d 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -191,12 +191,12 @@ namespace _Gen_detail { if constexpr (_Stateless_allocator<_Alloc>) { // don't store stateless allocator const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; - _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); return _Ptr; } else { // store stateful allocator const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; - _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size + sizeof(_Dealloc_fn)); _STD construct_at(_Al_address, _STD move(_Al)); From 44658f2ff8f715c4e89009db221392aa81d8518b Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:52:57 -0700 Subject: [PATCH 43/50] Drop unnecessary parens in `requires`. --- stl/inc/generator | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index dbff11c199d..00d7f86d3e3 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -271,8 +271,8 @@ namespace _Gen_detail { _NODISCARD auto yield_value(const remove_reference_t<_Yielded>& _Val) noexcept(is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) /* strengthened */ - requires (is_rvalue_reference_v<_Yielded> - && constructible_from, const remove_reference_t<_Yielded>&>) + requires is_rvalue_reference_v<_Yielded> + && constructible_from, const remove_reference_t<_Yielded>&> { return _Element_awaiter{_Val}; } From 8573013bb071755c7ba8c51663b21d9514c266bc Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 20:53:44 -0700 Subject: [PATCH 44/50] Style: Attach brace. --- stl/inc/generator | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 00d7f86d3e3..3144018a218 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -492,8 +492,7 @@ namespace _Gen_detail { ++*this; } - _NODISCARD friend bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ - { + _NODISCARD friend bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ { return _It._Coro.done(); } From 586e6fa50d97344b2e953783464e585e83f50095 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 21:03:50 -0700 Subject: [PATCH 45/50] Too many secrets. --- stl/inc/generator | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stl/inc/generator b/stl/inc/generator index 3144018a218..2a40c7cd28c 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -457,7 +457,9 @@ namespace _Gen_detail { add_pointer_t<_Yielded> _Ptr = nullptr; }; - struct _Secret_tag {}; + struct _Secret_tag { + explicit _Secret_tag() = default; + }; template class _Iter_provider<_Value, _Ref>::_Iterator { From 61a1651a733239a51821dd15e6a0531a7f4db534 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Sep 2024 21:15:36 -0700 Subject: [PATCH 46/50] test_generator_support.hpp stopped using `` and `` after GH 4952. --- tests/std/include/test_generator_support.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/std/include/test_generator_support.hpp b/tests/std/include/test_generator_support.hpp index 62d6cf50158..7c5211de05c 100644 --- a/tests/std/include/test_generator_support.hpp +++ b/tests/std/include/test_generator_support.hpp @@ -5,10 +5,8 @@ #include #include -#include #include #include -#include #include template From 55516ddbb18c17efec91c35ee318bda90c75bfff Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 17 Sep 2024 12:46:30 -0700 Subject: [PATCH 47/50] STL's product code review comments * `const`-ify members of awaitable objects that can be `const`. * Annotate that the iterator's `operator*` has a `/* strengthened */` _noexcept-specifier_. --- stl/inc/generator | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 2a40c7cd28c..8e5d94619d9 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -362,12 +362,12 @@ namespace _Gen_detail { }; struct _Final_awaiter { - _NODISCARD bool await_ready() noexcept { + _NODISCARD bool await_ready() const noexcept { return false; } template - _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { + _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) const noexcept { // Resume _Handle's parent coroutine, if any. #ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); @@ -382,7 +382,7 @@ namespace _Gen_detail { return _STD noop_coroutine(); } - void await_resume() noexcept {} + void await_resume() const noexcept {} }; template @@ -395,7 +395,7 @@ namespace _Gen_detail { explicit _Awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} - _NODISCARD bool await_ready() noexcept { + _NODISCARD bool await_ready() const noexcept { return !_Gen._Coro; } @@ -475,7 +475,7 @@ namespace _Gen_detail { } _NODISCARD _Ref operator*() const - noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) { + noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) /* strengthened */ { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(!_Coro.done(), "Can't dereference generator end iterator"); #endif // _CONTAINER_DEBUG_LEVEL > 0 From 1f86f077b4516b7ca57385c9590265678affc3ac Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 17 Sep 2024 19:13:53 -0700 Subject: [PATCH 48/50] STL's review comments Several minor nits, and I burned away all the unnecessary parts of "mutable rvalue reference type". Don't know where I was heading with it, but it never made it there. --- tests/std/tests/P2502R2_generator/test.cpp | 40 +++++++------------ .../tests/P2502R2_generator_iterator/test.cpp | 3 ++ .../tests/P2502R2_generator_promise/test.cpp | 9 ++--- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 358f29a75c4..4d01c8ea329 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -153,31 +154,20 @@ void test_weird_reference_types() { } #if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 - { // Test with mutable xvalue reference type - auto woof = [](size_t size, size_t count) -> generator&&> { - random_device rd{}; - uniform_int_distribution dist{0, 99}; - vector vec; - while (count-- > 0) { - vec.resize(size); - ranges::generate(vec, [&] { return dist(rd); }); - co_yield move(vec); - assert(vec.empty()); // when we yield an rvalue, the caller moves from it - } + { // Test with mutable rvalue reference type + constexpr size_t segment_size = 16; + auto woof = []() -> generator&&> { + vector vec(segment_size); + + co_yield vec; // When we yield an lvalue... + assert(vec.size() == segment_size); // ... the caller moves from a copy. - // Test yielding lvalue - vec.resize(size); - ranges::generate(vec, [&] { return dist(rd); }); - const auto tmp = vec; - co_yield vec; - assert(tmp == vec); // when we yield an lvalue, the caller moves from a copy + co_yield move(vec); // When we yield an rvalue... + assert(vec.size() == 0); // ... the caller moves from it. }; - constexpr size_t size = 16; - auto r = woof(size, 4); - for (auto i = r.begin(); i != r.end(); ++i) { - vector vec = *i; - assert(vec.size() == size); + for (auto vec : woof()) { // Intentionally by value + assert(vec.size() == segment_size); } } #endif // ^^^ no workaround ^^^ @@ -194,12 +184,12 @@ generator iota_repeater(const int hi, const int depth) { } void recursive_test() { - static constexpr auto might_throw = []() -> generator { + constexpr auto might_throw = []() -> generator { co_yield 0; throw runtime_error{"error"}; }; - static constexpr auto nested_ints = []() -> generator { + constexpr auto nested_ints = [=]() -> generator { try { co_yield ranges::elements_of(might_throw()); } catch (const runtime_error& e) { @@ -366,7 +356,7 @@ class malloc_resource final : public pmr::memory_resource { bytes = 1; } - if (void* result = malloc(bytes)) { + if (void* const result = malloc(bytes)) { return result; } throw bad_alloc{}; diff --git a/tests/std/tests/P2502R2_generator_iterator/test.cpp b/tests/std/tests/P2502R2_generator_iterator/test.cpp index 045ddacdb4a..aa7fd415d87 100644 --- a/tests/std/tests/P2502R2_generator_iterator/test.cpp +++ b/tests/std/tests/P2502R2_generator_iterator/test.cpp @@ -3,11 +3,14 @@ #include #include +#include #include #include #include #include +#include #include +#include #include "test_generator_support.hpp" diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp index e5d110a009d..42a2f7c09ea 100644 --- a/tests/std/tests/P2502R2_generator_promise/test.cpp +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -73,7 +73,7 @@ void test_operator_new(typename Gen::promise_type& p, const Alloc2& alloc2 = {}) 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); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); p.operator delete(mem, size); } @@ -83,7 +83,7 @@ void test_operator_new(typename Gen::promise_type& p, const Alloc2& alloc2 = {}) 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); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); p.operator delete(mem, size); } @@ -95,7 +95,7 @@ void test_operator_new(typename Gen::promise_type& p, const Alloc2& alloc2 = {}) 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); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); p.operator delete(mem, size); } } @@ -141,8 +141,7 @@ void test_one() { 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 + == is_nothrow_constructible_v, Lval>); // strengthened } { // Test 'yield_value(elements_of)' From cebe6206d5e89a499103c2e1842f42f34ebb339d Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 17 Sep 2024 19:30:38 -0700 Subject: [PATCH 49/50] `` is now unused in `P2502R2_generator` --- tests/std/tests/P2502R2_generator/test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 4d01c8ea329..0fcb74a0f45 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include From 89ebb333a877f6aacda7b54d76a7102ad0b620e5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 18 Sep 2024 22:47:19 -0700 Subject: [PATCH 50/50] Work around EDG ICE VSO-2254804. --- tests/std/tests/P2502R2_generator/test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 0fcb74a0f45..5ad637b4fb1 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -152,7 +152,7 @@ void test_weird_reference_types() { assert(pos == r.end()); } -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 +#if !(defined(__EDG__) || (defined(__clang__) && defined(_M_IX86))) // TRANSITION, VSO-2254804 and LLVM-56507 { // Test with mutable rvalue reference type constexpr size_t segment_size = 16; auto woof = []() -> generator&&> { @@ -172,7 +172,7 @@ void test_weird_reference_types() { #endif // ^^^ no workaround ^^^ } -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 +#if !(defined(__EDG__) || (defined(__clang__) && defined(_M_IX86))) // TRANSITION, VSO-2254804 and LLVM-56507 generator iota_repeater(const int hi, const int depth) { if (depth > 0) { co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); @@ -401,7 +401,7 @@ int main() { assert(ranges::equal(co_upto(6), views::iota(0, 6))); zip_example(); test_weird_reference_types(); -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 +#if !(defined(__EDG__) || (defined(__clang__) && defined(_M_IX86))) // TRANSITION, VSO-2254804 and LLVM-56507 recursive_test(); arbitrary_range_test();