From c8eb939efa0e97f7b7e16fd58fc04f9d0b1634c1 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 20 Sep 2022 09:42:30 -0700 Subject: [PATCH 1/8] Implement std::generator --- stl/CMakeLists.txt | 1 + stl/inc/__msvc_all_public_headers.hpp | 1 + stl/inc/generator | 324 +++++++++++++++ stl/inc/header-units.json | 1 + stl/inc/xmemory | 168 ++++++++ stl/inc/yvals_core.h | 8 +- stl/modules/std.ixx | 1 + .../include/test_header_units_and_modules.hpp | 13 + 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 | 371 ++++++++++++++++++ .../test.compile.pass.cpp | 6 + .../include_each_header_alone_matrix.lst | 1 + 15 files changed, 901 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..8d97592c184 --- /dev/null +++ b/stl/inc/generator @@ -0,0 +1,324 @@ +// 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) +#pragma message("The contents of are available only in c++latest mode with 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 + +_STD_BEGIN + +_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 {}; + } + + // clang-format off + _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>&>) { + // clang-format on + return _Element_awaiter{_Val}; + } + + // clang-format off + template + requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> + _NODISCARD auto yield_value( + _RANGES elements_of&&, _Unused> _Elem) noexcept { + // clang-format on + return _Nested_awaitable<_Rty, _Vty, _Alloc>{std::move(_Elem.range)}; + } + + // clang-format off + 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 { + // clang-format on + 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"); + + // clang-format off + 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"); + // clang-format on + + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use true 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 + +#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/xmemory b/stl/inc/xmemory index 7d709382ebd..6662d5e5410 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2667,6 +2667,174 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot } while (_UVal_trunc != 0); return _RNext; } + +#if _HAS_CXX23 && defined(__cpp_lib_ranges) +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 true 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 true 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); + } +}; + +namespace ranges { +#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>; +} // namespace ranges +#endif // _HAS_CXX23 && defined(__cpp_lib_ranges) + _STD_END #pragma pop_macro("new") 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..add14f01eb7 100644 --- a/tests/std/include/test_header_units_and_modules.hpp +++ b/tests/std/include/test_header_units_and_modules.hpp @@ -280,6 +280,18 @@ void test_future() { assert(f.get() == 1729); } +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))); +} + void test_initializer_list() { using namespace std; puts("Testing ."); @@ -1145,6 +1157,7 @@ void all_cpp_header_tests() { test_fstream(); test_functional(); test_future(); + test_generator(); 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..830daa2d194 --- /dev/null +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -0,0 +1,371 @@ +// 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) { + // std::cerr << "stateless_alloc::allocate(" << n << " * " << sizeof(T) << " == " << n * sizeof(T) << ") = "; + + 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) { + // std::cerr << vp << '\n'; + return static_cast(vp); + } + + // std::cerr << "bad_alloc\n"; + throw std::bad_alloc{}; + } + + void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { + // std::cerr << "stateless_alloc::deallocate(" << n << " * " << sizeof(T) << " == " << n * sizeof(T) << ")\n"; + + 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) { + // std::cerr << "stateful_alloc{" << domain << "}::allocate(" << n << " * " << sizeof(T) << " == " + // << n * sizeof(T) << ") = "; + + 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) { + // std::cerr << vp << '\n'; + return static_cast(vp); + } + + // std::cerr << "bad_alloc\n"; + throw std::bad_alloc{}; + } + + void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { + // std::cerr << "stateful_alloc{" << domain << "}::deallocate(" << n << " * " << sizeof(T) + // << " == " << n * sizeof(T) << ")\n"; + + 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() { + // std::cerr << "static_allocator_test:\n"; + + { + 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() { + // std::cerr << "dynamic_allocator_test:\n"; + + 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); +} + +std::generator iota_repeater(const int hi, const int depth) { +#ifdef __clang__ // TRANSITION, https://llvm.org/PR47357 + 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)}; + } +#else + 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)); + } +#endif // TRANSITION, https://llvm.org/PR47357 +} + +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 { +#ifdef __clang__ // TRANSITION, https://llvm.org/PR47357 + co_yield ranges::elements_of{might_throw()}; +#else + co_yield ranges::elements_of(might_throw()); +#endif // TRANSITION, https://llvm.org/PR47357 + } 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 { +#ifdef __clang__ // TRANSITION, https://llvm.org/PR47357 + 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}; +#else + 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); +#endif // TRANSITION, https://llvm.org/PR47357 + }; + + assert(ranges::equal(yield_arbitrary_ranges(), std::array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); +} + +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()); + } + + { + // 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()); + } + } + + static_allocator_test(); + dynamic_allocator_test(); + + zip_example(); + recursive_test(); + arbitrary_range_test(); +} 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 77fad69cee1658633227037b304fd5b12d401afb Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 09:29:06 -0800 Subject: [PATCH 2/8] Quick cleanup * Move `elements_of` into `` where it belongs * Move `_Promise_allocator` into `` since we have no other standard promise types to reuse it * Remove lots of now-unnecessary clang-format suppression --- stl/inc/generator | 177 +++++++++++++++++++++++++++++++++++++++++----- stl/inc/ranges | 15 ++++ stl/inc/xmemory | 167 ------------------------------------------- 3 files changed, 174 insertions(+), 185 deletions(-) diff --git a/stl/inc/generator b/stl/inc/generator index 8d97592c184..255905a090a 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -9,7 +9,7 @@ #include #if _STL_COMPILER_PREPROCESSOR -#if !_HAS_CXX23 || !defined(__cpp_impl_coroutine) +#if !_HAS_CXX23 || !defined(__cpp_impl_coroutine) || !defined(__cpp_lib_concepts) // TRANSITION, GH-395 #pragma message("The contents of are available only in c++latest mode with coroutine support") #else // ^^^ no coroutine support / coroutines vvv @@ -26,6 +26,154 @@ _STL_DISABLE_CLANG_WARNINGS _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; @@ -57,28 +205,23 @@ public: return {}; } - // clang-format off - _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>&>) { - // clang-format on + _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}; } - // clang-format off template requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> - _NODISCARD auto yield_value( - _RANGES elements_of&&, _Unused> _Elem) noexcept { - // clang-format on + _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { return _Nested_awaitable<_Rty, _Vty, _Alloc>{std::move(_Elem.range)}; } - // clang-format off 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 { - // clang-format on using _Vty = _RANGES range_value_t<_Rng>; return _Nested_awaitable<_Yielded, _Vty, _Alloc>{ [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, @@ -251,21 +394,19 @@ private: static_assert(same_as, _Value> && is_object_v<_Value>, "generator's value type must be a cv-unqualified object type"); - // clang-format off using _Ref = _Gen_reference_t<_Rty, _Vty>; - static_assert(is_reference_v<_Ref> - || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), + 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&>, + && common_reference_with<_RRef&&, const _Value&>, "an iterator with the selected value and reference types cannot model indirectly_readable"); - // clang-format on - static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use true pointers"); + static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use raw pointers"); friend _Gen_promise_base<_Gen_yield_t<_Ref>>; 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/xmemory b/stl/inc/xmemory index 6662d5e5410..5c7c4fd3dee 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2668,173 +2668,6 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot return _RNext; } -#if _HAS_CXX23 && defined(__cpp_lib_ranges) -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 true 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 true 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); - } -}; - -namespace ranges { -#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>; -} // namespace ranges -#endif // _HAS_CXX23 && defined(__cpp_lib_ranges) - _STD_END #pragma pop_macro("new") From 2bb2707ca71c9015ca6c51d2201eba6cb679b0a5 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 09:29:58 -0800 Subject: [PATCH 3/8] revert extraneous newline addition to `` --- stl/inc/xmemory | 1 - 1 file changed, 1 deletion(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 5c7c4fd3dee..7d709382ebd 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2667,7 +2667,6 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot } while (_UVal_trunc != 0); return _RNext; } - _STD_END #pragma pop_macro("new") From 97610b8254c203b5c4e0f53a57a53df190de705d Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 10:05:44 -0800 Subject: [PATCH 4/8] Fix C++23 message and defend against macroization of "empty_bases" --- stl/inc/generator | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/stl/inc/generator b/stl/inc/generator index 255905a090a..22ff90589b7 100644 --- a/stl/inc/generator +++ b/stl/inc/generator @@ -10,7 +10,7 @@ #if _STL_COMPILER_PREPROCESSOR #if !_HAS_CXX23 || !defined(__cpp_impl_coroutine) || !defined(__cpp_lib_concepts) // TRANSITION, GH-395 -#pragma message("The contents of are available only in c++latest mode with coroutine support") +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later coroutine support.") #else // ^^^ no coroutine support / coroutines vvv #include @@ -24,6 +24,10 @@ _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 { @@ -455,6 +459,9 @@ private: _STD_END +// TRANSITION, non-_Ugly attribute tokens +#pragma pop_macro("empty_bases") + #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) From a416986331a4fe84a0b1c4699e49f53a90ee1528 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 10:08:28 -0800 Subject: [PATCH 5/8] C++23 guards for header unit test --- tests/std/include/test_header_units_and_modules.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/std/include/test_header_units_and_modules.hpp b/tests/std/include/test_header_units_and_modules.hpp index add14f01eb7..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,7 @@ void test_future() { assert(f.get() == 1729); } +#if TEST_STANDARD >= 23 void test_generator() { using namespace std; puts("Testing ."); @@ -291,6 +292,7 @@ void test_generator() { 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; @@ -1157,7 +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(); From 659056eab7e767c9fa8b146629c71a7071fdccdb Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 18:09:24 -0800 Subject: [PATCH 6/8] Remove workarounds for the fixed LLVM-46701 (https://llvm.org/PR47357) --- tests/std/tests/P2502R2_generator/test.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 830daa2d194..281a1a817a1 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -256,21 +256,12 @@ void zip_example() { } std::generator iota_repeater(const int hi, const int depth) { -#ifdef __clang__ // TRANSITION, https://llvm.org/PR47357 - 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)}; - } -#else 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)); } -#endif // TRANSITION, https://llvm.org/PR47357 } void recursive_test() { @@ -283,11 +274,7 @@ void recursive_test() { static constexpr auto nested_ints = []() -> std::generator { try { -#ifdef __clang__ // TRANSITION, https://llvm.org/PR47357 - co_yield ranges::elements_of{might_throw()}; -#else co_yield ranges::elements_of(might_throw()); -#endif // TRANSITION, https://llvm.org/PR47357 } catch (const some_error&) { } co_yield 1; @@ -299,17 +286,10 @@ void recursive_test() { void arbitrary_range_test() { auto yield_arbitrary_ranges = []() -> std::generator { -#ifdef __clang__ // TRANSITION, https://llvm.org/PR47357 - 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}; -#else 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); -#endif // TRANSITION, https://llvm.org/PR47357 }; assert(ranges::equal(yield_arbitrary_ranges(), std::array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300})); From 95212ddd28c4ec43656c81ef17c61e6421c2c77e Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 18:13:51 -0800 Subject: [PATCH 7/8] Disable test code triggering unreported clang bug --- tests/std/tests/P2502R2_generator/test.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 281a1a817a1..8d436a3135b 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -255,6 +255,7 @@ void zip_example() { assert(length == 3); } +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, unreported clang bug std::generator iota_repeater(const int hi, const int depth) { if (depth > 0) { co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); @@ -294,6 +295,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)) int main() { { @@ -314,6 +316,7 @@ int main() { assert(pos == r.end()); } +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, unreported clang bug { // test with mutable xvalue reference type auto woof = [](std::size_t size, std::size_t count) -> std::generator&&> { @@ -341,11 +344,14 @@ int main() { assert((*i).empty()); } } +#endif // !(defined(__clang__) && defined(_M_IX86)) static_allocator_test(); dynamic_allocator_test(); zip_example(); +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, unreported clang bug recursive_test(); arbitrary_range_test(); +#endif // !(defined(__clang__) && defined(_M_IX86)) } From ad0d033cb3b6b8c721baa76e562c68a0bc14293e Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 25 Jan 2024 20:23:14 -0800 Subject: [PATCH 8/8] Mark workarounds for LLVM-56507 Drive-by: remove commented-out printf debugging code --- tests/std/tests/P2502R2_generator/test.cpp | 24 +++------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp index 8d436a3135b..0f5950ee33a 100644 --- a/tests/std/tests/P2502R2_generator/test.cpp +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -103,8 +103,6 @@ struct stateless_alloc { constexpr stateless_alloc(const stateless_alloc&) noexcept {} T* allocate(const std::size_t n) { - // std::cerr << "stateless_alloc::allocate(" << n << " * " << sizeof(T) << " == " << n * sizeof(T) << ") = "; - void* vp; if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); @@ -113,17 +111,13 @@ struct stateless_alloc { } if (vp) { - // std::cerr << vp << '\n'; return static_cast(vp); } - // std::cerr << "bad_alloc\n"; throw std::bad_alloc{}; } void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { - // std::cerr << "stateless_alloc::deallocate(" << n << " * " << sizeof(T) << " == " << n * sizeof(T) << ")\n"; - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { ::_aligned_free(vp); } else { @@ -150,9 +144,6 @@ struct stateful_alloc { constexpr stateful_alloc(const stateful_alloc& that) noexcept : domain{that.domain} {} T* allocate(const std::size_t n) { - // std::cerr << "stateful_alloc{" << domain << "}::allocate(" << n << " * " << sizeof(T) << " == " - // << n * sizeof(T) << ") = "; - void* vp; if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { vp = ::_aligned_malloc(n * sizeof(T), alignof(T)); @@ -161,18 +152,13 @@ struct stateful_alloc { } if (vp) { - // std::cerr << vp << '\n'; return static_cast(vp); } - // std::cerr << "bad_alloc\n"; throw std::bad_alloc{}; } void deallocate(void* const vp, [[maybe_unused]] const std::size_t n) noexcept { - // std::cerr << "stateful_alloc{" << domain << "}::deallocate(" << n << " * " << sizeof(T) - // << " == " << n * sizeof(T) << ")\n"; - if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { ::_aligned_free(vp); } else { @@ -188,8 +174,6 @@ struct stateful_alloc { static_assert(!std::default_initializable>); void static_allocator_test() { - // std::cerr << "static_allocator_test:\n"; - { auto g = [](const int hi) -> std::generator> { constexpr std::size_t n = 64; @@ -230,8 +214,6 @@ void static_allocator_test() { } void dynamic_allocator_test() { - // std::cerr << "dynamic_allocator_test:\n"; - auto g = [](std::allocator_arg_t, const auto&, const int hi) -> std::generator { constexpr std::size_t n = 64; int some_ints[n]; @@ -255,7 +237,7 @@ void zip_example() { assert(length == 3); } -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, unreported clang bug +#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)); @@ -316,7 +298,7 @@ int main() { assert(pos == r.end()); } -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, unreported clang bug +#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&&> { @@ -350,7 +332,7 @@ int main() { dynamic_allocator_test(); zip_example(); -#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, unreported clang bug +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 recursive_test(); arbitrary_range_test(); #endif // !(defined(__clang__) && defined(_M_IX86))