From e3879494d8353f9aa48e45a1b29e21b2a78a2a36 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 10:48:34 +0200 Subject: [PATCH 01/19] Move `std::call_once` to `xcall_once.h` and remove `` include in `` --- stl/inc/format | 2 +- stl/inc/mutex | 68 ------------------------------------------ stl/inc/xcall_once.h | 71 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 69 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 1ed63d4ec7..f58fd70877 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -52,8 +52,8 @@ _EMIT_STL_WARNING(STL4038, "The contents of are available only with C++ #include #include #include -#include #include +#include #include #include #include diff --git a/stl/inc/mutex b/stl/inc/mutex index a17c022650..cbe5f15886 100644 --- a/stl/inc/mutex +++ b/stl/inc/mutex @@ -557,74 +557,6 @@ public: }; #endif // _HAS_CXX17 -#if defined(_M_CEE) || defined(_M_ARM64EC) || defined(_M_HYBRID) \ - || defined(__clang__) // TRANSITION, Clang doesn't recognize /ALTERNATENAME, not yet reported -#define _WINDOWS_API __stdcall -#define _RENAME_WINDOWS_API(_Api) _Api##_clr -#else // ^^^ use forwarders / use /ALTERNATENAME vvv -#define _WINDOWS_API __declspec(dllimport) __stdcall -#define _RENAME_WINDOWS_API(_Api) _Api -#endif // ^^^ use /ALTERNATENAME ^^^ - -// WINBASEAPI -// BOOL -// WINAPI -// InitOnceBeginInitialize( -// _Inout_ LPINIT_ONCE lpInitOnce, -// _In_ DWORD dwFlags, -// _Out_ PBOOL fPending, -// _Outptr_opt_result_maybenull_ LPVOID* lpContext -// ); -extern "C" _NODISCARD int _WINDOWS_API _RENAME_WINDOWS_API(__std_init_once_begin_initialize)( - void** _LpInitOnce, unsigned long _DwFlags, int* _FPending, void** _LpContext) noexcept; - -// WINBASEAPI -// BOOL -// WINAPI -// InitOnceComplete( -// _Inout_ LPINIT_ONCE lpInitOnce, -// _In_ DWORD dwFlags, -// _In_opt_ LPVOID lpContext -// ); -extern "C" _NODISCARD int _WINDOWS_API _RENAME_WINDOWS_API(__std_init_once_complete)( - void** _LpInitOnce, unsigned long _DwFlags, void* _LpContext) noexcept; - -extern "C" [[noreturn]] void __stdcall __std_init_once_link_alternate_names_and_abort() noexcept; - -// #define RTL_RUN_ONCE_INIT_FAILED 0x00000004UL -// #define INIT_ONCE_INIT_FAILED RTL_RUN_ONCE_INIT_FAILED -_INLINE_VAR constexpr unsigned long _Init_once_init_failed = 0x4UL; - -struct _Init_once_completer { - once_flag& _Once; - unsigned long _DwFlags; - ~_Init_once_completer() { - if (!_RENAME_WINDOWS_API(__std_init_once_complete)(&_Once._Opaque, _DwFlags, nullptr)) { - __std_init_once_link_alternate_names_and_abort(); - } - } -}; - -_EXPORT_STD template -void(call_once)(once_flag& _Once, _Fn&& _Fx, _Args&&... _Ax) noexcept( - noexcept(_STD invoke(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...))) /* strengthened */ { - // call _Fx(_Ax...) once - // parentheses against common "#define call_once(flag,func) pthread_once(flag,func)" - int _Pending; - if (!_RENAME_WINDOWS_API(__std_init_once_begin_initialize)(&_Once._Opaque, 0, &_Pending, nullptr)) { - _CSTD abort(); - } - - if (_Pending != 0) { - _Init_once_completer _Op{_Once, _Init_once_init_failed}; - _STD invoke(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); - _Op._DwFlags = 0; - } -} - -#undef _WINDOWS_API -#undef _RENAME_WINDOWS_API - _EXPORT_STD enum class cv_status { // names for wait returns no_timeout, timeout diff --git a/stl/inc/xcall_once.h b/stl/inc/xcall_once.h index 8ae5aa8fcd..04a14c477a 100644 --- a/stl/inc/xcall_once.h +++ b/stl/inc/xcall_once.h @@ -9,6 +9,9 @@ #include #if _STL_COMPILER_PREPROCESSOR +#include +#include + #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) #pragma warning(disable : _STL_DISABLED_WARNINGS) @@ -44,6 +47,74 @@ union _Immortalizer_impl { // constructs _Ty, never destroys _Ty _Storage; }; + +#if defined(_M_CEE) || defined(_M_ARM64EC) || defined(_M_HYBRID) \ + || defined(__clang__) // TRANSITION, Clang doesn't recognize /ALTERNATENAME, not yet reported +#define _WINDOWS_API __stdcall +#define _RENAME_WINDOWS_API(_Api) _Api##_clr +#else // ^^^ use forwarders / use /ALTERNATENAME vvv +#define _WINDOWS_API __declspec(dllimport) __stdcall +#define _RENAME_WINDOWS_API(_Api) _Api +#endif // ^^^ use /ALTERNATENAME ^^^ + +// WINBASEAPI +// BOOL +// WINAPI +// InitOnceBeginInitialize( +// _Inout_ LPINIT_ONCE lpInitOnce, +// _In_ DWORD dwFlags, +// _Out_ PBOOL fPending, +// _Outptr_opt_result_maybenull_ LPVOID* lpContext +// ); +extern "C" _NODISCARD int _WINDOWS_API _RENAME_WINDOWS_API(__std_init_once_begin_initialize)( + void** _LpInitOnce, unsigned long _DwFlags, int* _FPending, void** _LpContext) noexcept; + +// WINBASEAPI +// BOOL +// WINAPI +// InitOnceComplete( +// _Inout_ LPINIT_ONCE lpInitOnce, +// _In_ DWORD dwFlags, +// _In_opt_ LPVOID lpContext +// ); +extern "C" _NODISCARD int _WINDOWS_API _RENAME_WINDOWS_API(__std_init_once_complete)( + void** _LpInitOnce, unsigned long _DwFlags, void* _LpContext) noexcept; + +extern "C" [[noreturn]] void __stdcall __std_init_once_link_alternate_names_and_abort() noexcept; + +// #define RTL_RUN_ONCE_INIT_FAILED 0x00000004UL +// #define INIT_ONCE_INIT_FAILED RTL_RUN_ONCE_INIT_FAILED +_INLINE_VAR constexpr unsigned long _Init_once_init_failed = 0x4UL; + +struct _Init_once_completer { + once_flag& _Once; + unsigned long _DwFlags; + ~_Init_once_completer() { + if (!_RENAME_WINDOWS_API(__std_init_once_complete)(&_Once._Opaque, _DwFlags, nullptr)) { + __std_init_once_link_alternate_names_and_abort(); + } + } +}; + +_EXPORT_STD template +void(call_once)(once_flag& _Once, _Fn&& _Fx, _Args&&... _Ax) noexcept( + noexcept(_STD invoke(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...))) /* strengthened */ { + // call _Fx(_Ax...) once + // parentheses against common "#define call_once(flag,func) pthread_once(flag,func)" + int _Pending; + if (!_RENAME_WINDOWS_API(__std_init_once_begin_initialize)(&_Once._Opaque, 0, &_Pending, nullptr)) { + _CSTD abort(); + } + + if (_Pending != 0) { + _Init_once_completer _Op{_Once, _Init_once_init_failed}; + _STD invoke(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); + _Op._DwFlags = 0; + } +} + +#undef _WINDOWS_API +#undef _RENAME_WINDOWS_API _STD_END #pragma pop_macro("new") From 4e1e84ca769ee7123427f8127b507a048ec2a6f4 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 13:02:20 +0200 Subject: [PATCH 02/19] Testing: implement `choose_literal` for character types --- tests/std/include/test_format_support.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/std/include/test_format_support.hpp b/tests/std/include/test_format_support.hpp index 77cc36eb85..0beadde51b 100644 --- a/tests/std/include/test_format_support.hpp +++ b/tests/std/include/test_format_support.hpp @@ -18,6 +18,10 @@ struct choose_literal { static constexpr const char* choose(const char* s, const wchar_t*) { return s; } + + static constexpr char choose(char c, wchar_t) { + return c; + } }; template <> @@ -25,6 +29,10 @@ struct choose_literal { static constexpr const wchar_t* choose(const char*, const wchar_t* s) { return s; } + + static constexpr wchar_t choose(char, wchar_t c) { + return c; + } }; #define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) From 09559dbd131d4fa643115b36d207fcd2a95eea4f Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 13:39:55 +0200 Subject: [PATCH 03/19] Testing: test `thread::id` formatter --- tests/std/test.lst | 1 + .../P2693R1_text_formatting_thread_id/env.lst | 4 + .../test.cpp | 358 ++++++++++++++++++ 3 files changed, 363 insertions(+) create mode 100644 tests/std/tests/P2693R1_text_formatting_thread_id/env.lst create mode 100644 tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index 3059eef140..b20a265af6 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -619,6 +619,7 @@ tests\P2494R2_move_only_range_adaptors tests\P2505R5_monadic_functions_for_std_expected tests\P2517R1_apply_conditional_noexcept tests\P2609R3_relaxing_ranges_just_a_smidge +tests\P2693R1_text_formatting_thread_id tests\VSO_0000000_allocator_propagation tests\VSO_0000000_any_calling_conventions tests\VSO_0000000_c_math_functions diff --git a/tests/std/tests/P2693R1_text_formatting_thread_id/env.lst b/tests/std/tests/P2693R1_text_formatting_thread_id/env.lst new file mode 100644 index 0000000000..18e2d7c71e --- /dev/null +++ b/tests/std/tests/P2693R1_text_formatting_thread_id/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/P2693R1_text_formatting_thread_id/test.cpp b/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp new file mode 100644 index 0000000000..da9e6eac30 --- /dev/null +++ b/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp @@ -0,0 +1,358 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_format_support.hpp" + +#define STR(Str) TYPED_LITERAL(CharT, Str) + +using namespace std; + +template +struct FormatFn { + template + [[nodiscard]] auto operator()( + const basic_format_string...> str, Args&&... args) const { + return format(str, forward(args)...); + } +}; + +template +struct VFormatFn { + template + [[nodiscard]] auto operator()(const basic_string_view str, Args&&... args) const { + if constexpr (same_as) { + return vformat(str, make_format_args(forward(args)...)); + } else { + return vformat(str, make_wformat_args(forward(args)...)); + } + } +}; + +template +struct ExpectFormatError { +private: + VFormatFn base; + +public: + template + auto operator()(const basic_string_view str, Args&&... args) const { + try { + (void) base(str, forward(args)...); + assert(false && "No exception."); + } catch (const format_error&) { + return; + } catch (...) { + assert(false && "Incorrect exception."); + } + } +}; + +template +struct MoveOnlyFormat { +private: + struct StringInserter { + using iterator_category = output_iterator_tag; + using difference_type = ptrdiff_t; + using container_type = basic_string; + + StringInserter() = default; + StringInserter(StringInserter&&) = default; + StringInserter& operator=(StringInserter&&) = default; + + StringInserter(const StringInserter&) = delete; + StringInserter& operator=(const StringInserter&) = delete; + + StringInserter& operator=(const CharT val) { + str.push_back(val); + return *this; + } + + StringInserter& operator*() { + return *this; + } + + StringInserter& operator++() { + return *this; + } + + StringInserter operator++(int) { + return *this; + } + + basic_string str; + }; + +public: + static_assert(output_iterator); + static_assert(!copyable); + static_assert(movable); + + template + [[nodiscard]] auto operator()( + const basic_format_string...> str, Args&&... args) const { + return format_to(StringInserter{}, str, forward(args)...).str; + } +}; + +template class Fmt> +void check_formatting_of_default_constructed_thread_id() { + Fmt fmt; + thread::id id; + + // empty format-spec + assert(fmt(STR("{}"), id) == STR("0")); + assert(fmt(STR("{:}"), id) == STR("0")); + assert(fmt(STR("{0:}"), id) == STR("0")); + + // fill-and-align only + assert(fmt(STR("{::<}"), id) == STR("0")); + assert(fmt(STR("{:*^}"), id) == STR("0")); + assert(fmt(STR("{:=>}"), id) == STR("0")); + + // width only + assert(fmt(STR("{:5}"), id) == STR(" 0")); + + // width only (replacement field) + assert(fmt(STR("{:{}}"), id, 7) == STR(" 0")); + + // fill-and align with width + assert(fmt(STR("{:=<5}"), id) == STR("0====")); + assert(fmt(STR("{::^6}"), id) == STR("::0:::")); + assert(fmt(STR("{:*>7}"), id) == STR("******0")); + + // fill-and align with width (replacement field) + assert(fmt(STR("{:/<{}}"), id, 7) == STR("0//////")); + assert(fmt(STR("{::^{}}"), id, 6) == STR("::0:::")); + assert(fmt(STR("{0:_>{1}}"), id, 5) == STR("____0")); +} + +template class Fmt> +void check_formatting_of_this_thread_id() { + Fmt fmt; + const thread::id id = this_thread::get_id(); + const auto id_str = [id] { + // '_Get_underlying_id' is MSVC STL specific + if constexpr (same_as) { + return to_wstring(id._Get_underlying_id()); + } else { + return to_string(id._Get_underlying_id()); + } + }(); + + // empty format-spec + assert(fmt(STR("{}"), id) == id_str); + assert(fmt(STR("{:}"), id) == id_str); + assert(fmt(STR("{0:}"), id) == id_str); + + // fill-and-align only + assert(fmt(STR("{::<}"), id) == id_str); + assert(fmt(STR("{:*^}"), id) == id_str); + assert(fmt(STR("{:=>}"), id) == id_str); + + { // width only + constexpr int width = 13; + const int fill_width = static_cast(width - id_str.size()); + + const auto s = fmt(STR("{:13}"), id); + const auto it = ranges::mismatch(s, views::repeat(STR(' '), fill_width)).in1; + assert(it == s.begin() + fill_width); + assert(ranges::equal(ranges::subrange{it, s.end()}, id_str)); + } + + { // width only (replacement field) + constexpr int width = 15; + const int fill_width = static_cast(width - id_str.size()); + + const auto s = fmt(STR("{:{}}"), id, width); + const auto it = ranges::mismatch(s, views::repeat(STR(' '), fill_width)).in1; + assert(it == s.begin() + fill_width); + assert(ranges::equal(ranges::subrange{it, s.end()}, id_str)); + } + + { // fill-and align with width + constexpr int width = 21; + const int fill_width = static_cast(width - id_str.size()); + + { + const auto s = fmt(STR("{:=<21}"), id); + const auto it = ranges::mismatch(views::reverse(s), views::repeat(STR('='), fill_width)).in1; + assert(it.base() == s.end() - fill_width); + assert(ranges::equal(ranges::subrange{s.begin(), it.base()}, id_str)); + } + + { + const auto s = fmt(STR("{::^21}"), id); + const int left_fill_width = fill_width / 2; + const auto it1 = ranges::mismatch(s, views::repeat(STR(':'), left_fill_width)).in1; + assert(it1 == s.begin() + left_fill_width); + + const int right_fill_width = fill_width - left_fill_width; + const auto it2 = ranges::mismatch(views::reverse(s), views::repeat(STR(':'), right_fill_width)).in1; + assert(it2.base() == s.end() - right_fill_width); + + assert(ranges::equal(ranges::subrange{it1, it2.base()}, id_str)); + } + + { + const auto s = fmt(STR("{:*>21}"), id); + const auto it = ranges::mismatch(s, views::repeat(STR('*'), fill_width)).in1; + assert(it == s.begin() + fill_width); + assert(ranges::equal(ranges::subrange{it, s.end()}, id_str)); + } + } + + { // fill-and align with width (replacement field) + constexpr int width = 27; + const int fill_width = static_cast(width - id_str.size()); + + { + const auto s = fmt(STR("{:/<{}}"), id, width); + const auto it = ranges::mismatch(views::reverse(s), views::repeat(STR('/'), fill_width)).in1; + assert(it.base() == s.end() - fill_width); + assert(ranges::equal(ranges::subrange{s.begin(), it.base()}, id_str)); + } + + { + const auto s = fmt(STR("{::^{}}"), id, width); + const int left_fill_width = fill_width / 2; + const auto it1 = ranges::mismatch(s, views::repeat(STR(':'), left_fill_width)).in1; + assert(it1 == s.begin() + left_fill_width); + + const int right_fill_width = fill_width - left_fill_width; + const auto it2 = ranges::mismatch(views::reverse(s), views::repeat(STR(':'), right_fill_width)).in1; + assert(it2.base() == s.end() - right_fill_width); + + assert(ranges::equal(ranges::subrange{it1, it2.base()}, id_str)); + } + + { + const auto s = fmt(STR("{0:_>{1}}"), id, width); + const auto it = ranges::mismatch(s, views::repeat(STR('_'), fill_width)).in1; + assert(it == s.begin() + fill_width); + assert(ranges::equal(ranges::subrange{it, s.end()}, id_str)); + } + } +} + +template class FormatFn> +void check_format_versus_ostream() { + FormatFn fmt; + const thread::id id = this_thread::get_id(); + + { // empty format-spec + basic_ostringstream ss; + ss << id; + assert(fmt(STR("{}"), id) == ss.view()); + } + + { // fill-and-align only + basic_ostringstream ss; + ss.fill('='); + ss.setf(ss.flags() | ios_base::left); + ss << id; + assert(fmt(STR("{:=<}"), id) == ss.view()); + } + + { // width only + constexpr int w = 20; + basic_ostringstream ss; + ss.width(w); + ss << id; + assert(fmt(STR("{:20}"), id) == ss.view()); + assert(fmt(STR("{:{}}"), id, w) == ss.view()); + } + + { // fill-and align with width + constexpr int w = 30; + basic_ostringstream ss; + ss.fill('*'); + ss.setf(ss.flags() | ios_base::left); + ss.width(w); + ss << id; + assert(fmt(STR("{:*<30}"), id) == ss.view()); + assert(fmt(STR("{:*<{}}"), id, w) == ss.view()); + } + + { // check what happens if we change stream's locale, precision and fmtflags other than fill and alignment + constexpr int w = 25; + basic_ostringstream ss; + ss.imbue(locale{"en-US"}); + ss.fill('x'); + ss.setf(ss.flags() | ios_base::left | ios_base::hex | ios_base::uppercase | ios_base::showbase); + ss.width(w); + ss.precision(99); + ss << id; + assert(fmt(STR("{:x<25}"), id) == ss.view()); + assert(fmt(STR("{:x<{}}"), id, w) == ss.view()); + } +} + +template +void check_invalid_specs() { + ExpectFormatError fmt; + const thread::id id = this_thread::get_id(); + + { // damaged fields + fmt(STR("{"), id); + fmt(STR("{:"), id); + fmt(STR("{}}"), id); + fmt(STR("}"), id); + } + + { // fill-and-align should not contain '{' or '}' characters + fmt(STR("{:{^}"), id); + fmt(STR("{:{<}"), id); + } + + { // sign, #, 0, precision, L or type options are not allowed in format-specs + fmt(STR("{:+}"), id); + fmt(STR("{:#}"), id); + fmt(STR("{:0}"), id); + fmt(STR("{:.5}"), id); + fmt(STR("{:.{}}"), id, 5); + fmt(STR("{:L}"), id); + fmt(STR("{:d}"), id); + } + + { // mixed invalid format-specs + fmt(STR("{:+#0}"), id); + fmt(STR("{:=^5.5}"), id); + fmt(STR("{0:{1}.{1}}"), id, 5); + fmt(STR("{:Lx}"), id); + } +} + +template +void test() { + check_formatting_of_default_constructed_thread_id(); + check_formatting_of_default_constructed_thread_id(); + check_formatting_of_default_constructed_thread_id(); + + const array checks = { + // NB: those functions call 'this_thread::get_id' - let's check various ids + check_formatting_of_this_thread_id, + check_formatting_of_this_thread_id, + check_formatting_of_this_thread_id, + check_format_versus_ostream, + check_format_versus_ostream, + check_format_versus_ostream, + check_invalid_specs, + }; + for_each(execution::par, checks.begin(), checks.end(), [](auto f) { f(); }); +} + +int main() { + test(); + test(); +} From 7aa7a9dc90cbb352637412b689bc018c8c67462b Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 13:42:04 +0200 Subject: [PATCH 04/19] Add new `_Fill_align_and_width_specs` for `thread::id` and `stacktrace_entry` formatters --- stl/inc/format | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index f58fd70877..dd7ca00899 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -3934,6 +3934,110 @@ _NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const stri return _Unescaped_str; } + +template +struct _Fill_align_and_width_specs { // used by thread::id and stacktrace_entry formatters + int _Width = -1; + int _Dynamic_width_index = -1; + _Fmt_align _Alignment = _Fmt_align::_None; + uint8_t _Fill_length = 1; + // At most one codepoint (so one char32_t or four utf-8 char8_t). + _CharT _Fill[4 / sizeof(_CharT)] = {' '}; +}; + +template +class _Fill_align_and_width_specs_setter { +public: + constexpr explicit _Fill_align_and_width_specs_setter( + _Fill_align_and_width_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_) + : _Specs(_Specs_), _Parse_ctx(_Parse_ctx_) {} + + constexpr void _On_align(const _Fmt_align _Aln) { + _Specs._Alignment = _Aln; + } + + constexpr void _On_fill(const basic_string_view<_CharT> _Sv) { + if (_Sv.size() > _STD size(_Specs._Fill)) { + _Throw_format_error("Invalid fill (too long)."); + } + + const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); + _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); + _Specs._Fill_length = static_cast(_Sv.size()); + } + + constexpr void _On_width(const int _Width) { + _Specs._Width = _Width; + } + + constexpr void _On_dynamic_width(const size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id); + } + + constexpr void _On_dynamic_width(const _Auto_id_tag) { + _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); + } + +protected: + _Fill_align_and_width_specs<_CharT>& _Specs; + basic_format_parse_context<_CharT>& _Parse_ctx; + + _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { + if (_Idx > static_cast((numeric_limits::max)())) { + _Throw_format_error("Dynamic width index is too large."); + } + + return static_cast(_Idx); + } +}; + +template +_NODISCARD constexpr const _CharT* _Parse_fill_align_and_width_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + if (_Begin == _End || *_Begin == '}') { + return _Begin; + } + + _Begin = _Parse_align(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + return _Parse_width(_Begin, _End, _Callbacks); +} + +template +struct _Fill_align_and_width_formatter { +public: + _Fill_align_and_width_formatter() = default; + + _NODISCARD constexpr auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + _Fill_align_and_width_specs_setter<_CharT> _Callback{_Specs, _Parse_ctx}; + const auto _It = + _Parse_fill_align_and_width_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); + if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { + _Throw_format_error("Missing '}' in format string."); + } + + return _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + } + + template + _NODISCARD constexpr auto _Format( + _FormatContext& _Format_ctx, const int _Width, _Fmt_align _Default_align, _Func&& _Fn) const { + _Fill_align_and_width_specs _Format_specs = _Specs; + if (_Specs._Dynamic_width_index >= 0) { + _Format_specs._Width = + _Get_dynamic_specs<_Width_checker>(_Format_ctx.arg(static_cast(_Specs._Dynamic_width_index))); + } + + return _Write_aligned(_Format_ctx.out(), _Width, _Format_specs, _Default_align, std::forward<_Func>(_Fn)); + } + +private: + _Fill_align_and_width_specs<_CharT> _Specs; +}; #endif // _HAS_CXX23 #undef _FMT_P2286_END From 160da41b95cf0eafb37b14b48ba6bebb4becb658 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 13:44:54 +0200 Subject: [PATCH 05/19] Implement `thread::id` formatter --- stl/inc/thread | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/stl/inc/thread b/stl/inc/thread index 6d8f83c026..a66ff4e8c4 100644 --- a/stl/inc/thread +++ b/stl/inc/thread @@ -13,11 +13,16 @@ #include #include #include + #if _HAS_CXX20 #include #include #endif // _HAS_CXX20 +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 + #ifdef _M_CEE_PURE #error is not supported when compiling with /clr:pure. #endif // _M_CEE_PURE @@ -208,6 +213,12 @@ class thread::id { // thread id public: id() noexcept = default; // id for no thread +#if _HAS_CXX23 + _NODISCARD _Thrd_id_t _Get_underlying_id() const noexcept { + return _Id; + } +#endif //_HAS_CXX23 + private: explicit id(_Thrd_id_t _Other_id) noexcept : _Id(_Other_id) {} @@ -273,6 +284,32 @@ basic_ostream<_Ch, _Tr>& operator<<(basic_ostream<_Ch, _Tr>& _Str, thread::id _I return _Str << _Id._Id; } +#if _HAS_CXX23 && defined(__cpp_lib_concepts) +template +struct formatter { +public: + using _Pc = basic_format_parse_context<_CharT>; + + constexpr _Pc::iterator parse(_Pc& _Parse_ctx) { + return _Impl.parse(_Parse_ctx); + } + + template + _FormatContext::iterator format(thread::id _Val, _FormatContext& _Format_ctx) const { + _STL_INTERNAL_STATIC_ASSERT(is_same_v<_Thrd_id_t, unsigned int>); + char _Buffer[10]; // size(string with decimal value of numeric_limits<_Thrd_id_t>::max()) == 10 + const auto [_End, _Ec] = _STD to_chars(_Buffer, end(_Buffer), _Val._Get_underlying_id()); + _STL_INTERNAL_CHECK(_Ec == errc{}); + + return _Impl._Format(_Format_ctx, static_cast(_End - _Buffer), _Fmt_align::_Right, + [&](_FormatContext::iterator _Out) { return _Widen_and_copy<_CharT>(_Buffer, _End, _STD move(_Out)); }); + } + +private: + _Fill_align_and_width_formatter<_CharT> _Impl; +}; +#endif // _HAS_CXX23 && defined(__cpp_lib_concepts) + template <> struct hash { using _ARGUMENT_TYPE_NAME _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS = thread::id; From d55eb212e0c263baec9b1ae5358566a814a1f311 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 13:46:09 +0200 Subject: [PATCH 06/19] Fix `operator<<(thread::id)`, see LLVM-62073 --- stl/inc/thread | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stl/inc/thread b/stl/inc/thread index a66ff4e8c4..e28d5320bd 100644 --- a/stl/inc/thread +++ b/stl/inc/thread @@ -11,6 +11,7 @@ #include <__msvc_chrono.hpp> #include #include +#include #include #include @@ -281,7 +282,10 @@ _NODISCARD inline bool operator>=(thread::id _Left, thread::id _Right) noexcept _EXPORT_STD template basic_ostream<_Ch, _Tr>& operator<<(basic_ostream<_Ch, _Tr>& _Str, thread::id _Id) { - return _Str << _Id._Id; + basic_ostringstream<_Ch, _Tr> _Out; + _Out.imbue(locale::classic()); + _Out << _Id._Id; + return _Str << _Out.str(); } #if _HAS_CXX23 && defined(__cpp_lib_concepts) From 0ae81d911caa7a3578a3448eafbe1dfe24cd3743 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 14:07:08 +0200 Subject: [PATCH 07/19] Testing: fix calls to `setf` in `thread::id` tests --- tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp b/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp index da9e6eac30..3a24dce4f8 100644 --- a/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp +++ b/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp @@ -259,7 +259,7 @@ void check_format_versus_ostream() { { // fill-and-align only basic_ostringstream ss; ss.fill('='); - ss.setf(ss.flags() | ios_base::left); + ss.setf(ss.flags() | ios_base::left, ios_base::adjustfield); ss << id; assert(fmt(STR("{:=<}"), id) == ss.view()); } @@ -277,7 +277,7 @@ void check_format_versus_ostream() { constexpr int w = 30; basic_ostringstream ss; ss.fill('*'); - ss.setf(ss.flags() | ios_base::left); + ss.setf(ss.flags() | ios_base::left, ios_base::adjustfield); ss.width(w); ss << id; assert(fmt(STR("{:*<30}"), id) == ss.view()); @@ -289,7 +289,8 @@ void check_format_versus_ostream() { basic_ostringstream ss; ss.imbue(locale{"en-US"}); ss.fill('x'); - ss.setf(ss.flags() | ios_base::left | ios_base::hex | ios_base::uppercase | ios_base::showbase); + ss.setf(ss.flags() | ios_base::left | ios_base::hex | ios_base::uppercase | ios_base::showbase, + ios_base::adjustfield | ios_base::basefield); ss.width(w); ss.precision(99); ss << id; From 9c97071c2ec0bc379abd24fcf4512739d163c2cb Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 14:20:27 +0200 Subject: [PATCH 08/19] Testing: test fixed `operator<<(thread::id)` --- tests/std/test.lst | 1 + .../P2693R1_ostream_and_thread_id/env.lst | 4 + .../P2693R1_ostream_and_thread_id/test.cpp | 125 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 tests/std/tests/P2693R1_ostream_and_thread_id/env.lst create mode 100644 tests/std/tests/P2693R1_ostream_and_thread_id/test.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index b20a265af6..40605a8471 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -619,6 +619,7 @@ tests\P2494R2_move_only_range_adaptors tests\P2505R5_monadic_functions_for_std_expected tests\P2517R1_apply_conditional_noexcept tests\P2609R3_relaxing_ranges_just_a_smidge +tests\P2693R1_ostream_and_thread_id tests\P2693R1_text_formatting_thread_id tests\VSO_0000000_allocator_propagation tests\VSO_0000000_any_calling_conventions diff --git a/tests/std/tests/P2693R1_ostream_and_thread_id/env.lst b/tests/std/tests/P2693R1_ostream_and_thread_id/env.lst new file mode 100644 index 0000000000..19f025bd0e --- /dev/null +++ b/tests/std/tests/P2693R1_ostream_and_thread_id/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/P2693R1_ostream_and_thread_id/test.cpp b/tests/std/tests/P2693R1_ostream_and_thread_id/test.cpp new file mode 100644 index 0000000000..53b7107448 --- /dev/null +++ b/tests/std/tests/P2693R1_ostream_and_thread_id/test.cpp @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +using namespace std; + +// copied from the 'test_format_support.hpp' header +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define STR(Literal) (choose_literal::choose(Literal, L##Literal)) + +template +void check_fmtflags_and_locale(thread::id id) { + // changing fmtflags other than fill or align should not affect text representation of thread::id + auto make_text_rep = [id](ios_base::fmtflags flags = {}, ios_base::fmtflags mask = {}) { + std::basic_stringstream ss; + ss.setf(flags, mask); + ss << id; + return ss.str(); + }; + + // changing locale should not affect text representation of thread::id + auto make_localized_text_rep = [id](const char* name) { + std::basic_stringstream ss; + try { + ss.imbue(locale{name}); + } catch (...) { + } + ss << id; + return ss.str(); + }; + + // changing precision should not affect text representation of thread::id + auto make_text_rep_with_precision = [id](int prec) { + std::basic_stringstream ss; + ss.precision(prec); + ss << id; + return ss.str(); + }; + + const array, 14> reps = { + make_text_rep(), + make_text_rep(ios_base::hex, ios_base::basefield), + make_text_rep(ios_base::oct, ios_base::basefield), + make_text_rep(ios_base::oct | ios_base::showbase, ios_base::basefield), + make_text_rep(ios_base::showpos, ios_base::basefield), + make_text_rep(ios_base::hex | ios_base::uppercase, ios_base::basefield), + make_text_rep(ios_base::fixed, ios_base::basefield), + make_localized_text_rep(""), + make_localized_text_rep("en-US"), + make_localized_text_rep("de-DE"), + make_localized_text_rep("pl-PL"), + make_text_rep_with_precision(32), + make_text_rep_with_precision(6), + make_text_rep_with_precision(1), + }; + + // all text representations should be the same + assert(adjacent_find(reps.begin(), reps.end(), not_equal_to<>{}) == reps.end()); +} + +template +void check_fill_and_align() { + thread::id id; + + { // Align left + basic_stringstream ss; + ss.setf(ios_base::left, ios_base::adjustfield); + ss.fill('*'); + ss.width(5); + ss << id; + assert(ss.str() == STR("0****")); + } + + { // Align right + basic_stringstream ss; + ss.setf(ios_base::right, ios_base::adjustfield); + ss.fill(':'); + ss.width(10); + ss << id; + assert(ss.str() == STR(":::::::::0")); + } + + { // Align internal + basic_stringstream ss; + ss.setf(ios_base::internal, ios_base::adjustfield); + ss.fill('_'); + ss.width(3); + ss << id; + assert(ss.str() == STR("__0")); + } +} + +template +void test() { + check_fmtflags_and_locale(thread::id{}); + check_fmtflags_and_locale(this_thread::get_id()); + check_fill_and_align(); +} + +int main() { + test(); + test(); +} From 15e9f336a425c5797294483531c81134d7661e6e Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 14:24:56 +0200 Subject: [PATCH 09/19] Testing: move format adaptors to the `"test_format_support.hpp"` header file --- tests/std/include/test_format_support.hpp | 90 +++++++++++++++++++ .../test.cpp | 87 ------------------ 2 files changed, 90 insertions(+), 87 deletions(-) diff --git a/tests/std/include/test_format_support.hpp b/tests/std/include/test_format_support.hpp index 0beadde51b..6f0d60bfbd 100644 --- a/tests/std/include/test_format_support.hpp +++ b/tests/std/include/test_format_support.hpp @@ -4,8 +4,11 @@ #pragma once #include +#include #include #include +#include +#include #include #include @@ -134,3 +137,90 @@ void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback assert(err_expected); } } + +template +struct FormatFn { + template + [[nodiscard]] auto operator()( + const std::basic_format_string...> str, Args&&... args) const { + return format(str, std::forward(args)...); + } +}; + +template +struct VFormatFn { + template + [[nodiscard]] auto operator()(const std::basic_string_view str, Args&&... args) const { + if constexpr (std::same_as) { + return vformat(str, make_format_args(std::forward(args)...)); + } else { + return vformat(str, make_wformat_args(std::forward(args)...)); + } + } +}; + +template +struct ExpectFormatError { +private: + VFormatFn base; + +public: + template + auto operator()(const std::basic_string_view str, Args&&... args) const { + try { + (void) base(str, std::forward(args)...); + assert(false && "No exception."); + } catch (const std::format_error&) { + return; + } catch (...) { + assert(false && "Incorrect exception."); + } + } +}; + +template +struct MoveOnlyFormat { +private: + struct StringInserter { + using iterator_category = std::output_iterator_tag; + using difference_type = ptrdiff_t; + using container_type = std::basic_string; + + StringInserter() = default; + StringInserter(StringInserter&&) = default; + StringInserter& operator=(StringInserter&&) = default; + + StringInserter(const StringInserter&) = delete; + StringInserter& operator=(const StringInserter&) = delete; + + StringInserter& operator=(const CharT val) { + str.push_back(val); + return *this; + } + + StringInserter& operator*() { + return *this; + } + + StringInserter& operator++() { + return *this; + } + + StringInserter operator++(int) { + return *this; + } + + std::basic_string str; + }; + +public: + static_assert(std::output_iterator); + static_assert(!std::copyable); + static_assert(std::movable); + + template + [[nodiscard]] auto operator()( + const std::basic_format_string...> str, Args&&... args) const { + return format_to(StringInserter{}, str, std::forward(args)...).str; + } +}; diff --git a/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp b/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp index 3a24dce4f8..d7cb7aec1b 100644 --- a/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp +++ b/tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp @@ -18,93 +18,6 @@ using namespace std; -template -struct FormatFn { - template - [[nodiscard]] auto operator()( - const basic_format_string...> str, Args&&... args) const { - return format(str, forward(args)...); - } -}; - -template -struct VFormatFn { - template - [[nodiscard]] auto operator()(const basic_string_view str, Args&&... args) const { - if constexpr (same_as) { - return vformat(str, make_format_args(forward(args)...)); - } else { - return vformat(str, make_wformat_args(forward(args)...)); - } - } -}; - -template -struct ExpectFormatError { -private: - VFormatFn base; - -public: - template - auto operator()(const basic_string_view str, Args&&... args) const { - try { - (void) base(str, forward(args)...); - assert(false && "No exception."); - } catch (const format_error&) { - return; - } catch (...) { - assert(false && "Incorrect exception."); - } - } -}; - -template -struct MoveOnlyFormat { -private: - struct StringInserter { - using iterator_category = output_iterator_tag; - using difference_type = ptrdiff_t; - using container_type = basic_string; - - StringInserter() = default; - StringInserter(StringInserter&&) = default; - StringInserter& operator=(StringInserter&&) = default; - - StringInserter(const StringInserter&) = delete; - StringInserter& operator=(const StringInserter&) = delete; - - StringInserter& operator=(const CharT val) { - str.push_back(val); - return *this; - } - - StringInserter& operator*() { - return *this; - } - - StringInserter& operator++() { - return *this; - } - - StringInserter operator++(int) { - return *this; - } - - basic_string str; - }; - -public: - static_assert(output_iterator); - static_assert(!copyable); - static_assert(movable); - - template - [[nodiscard]] auto operator()( - const basic_format_string...> str, Args&&... args) const { - return format_to(StringInserter{}, str, forward(args)...).str; - } -}; - template class Fmt> void check_formatting_of_default_constructed_thread_id() { Fmt fmt; From c01bebf04c779c0274db823872d0aac430a69e55 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 11 Jul 2023 15:13:45 +0200 Subject: [PATCH 10/19] Testing: test `stacktrace_entry` and `basic_stacktrace` formatters --- tests/std/test.lst | 1 + .../env.lst | 8 + .../test.cpp | 216 ++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 tests/std/tests/P2693R1_text_formatting_stacktrace/env.lst create mode 100644 tests/std/tests/P2693R1_text_formatting_stacktrace/test.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index 40605a8471..476a918ecd 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -620,6 +620,7 @@ tests\P2505R5_monadic_functions_for_std_expected tests\P2517R1_apply_conditional_noexcept tests\P2609R3_relaxing_ranges_just_a_smidge tests\P2693R1_ostream_and_thread_id +tests\P2693R1_text_formatting_stacktrace tests\P2693R1_text_formatting_thread_id tests\VSO_0000000_allocator_propagation tests\VSO_0000000_any_calling_conventions diff --git a/tests/std/tests/P2693R1_text_formatting_stacktrace/env.lst b/tests/std/tests/P2693R1_text_formatting_stacktrace/env.lst new file mode 100644 index 0000000000..d7beb961d2 --- /dev/null +++ b/tests/std/tests/P2693R1_text_formatting_stacktrace/env.lst @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst +RUNALL_CROSSLIST +PM_CL="/Zi /DHAS_DEBUG_INFO" PM_LINK="/debug" +PM_CL="/DHAS_EXPORT" +PM_CL="" diff --git a/tests/std/tests/P2693R1_text_formatting_stacktrace/test.cpp b/tests/std/tests/P2693R1_text_formatting_stacktrace/test.cpp new file mode 100644 index 0000000000..acc3aaf82d --- /dev/null +++ b/tests/std/tests/P2693R1_text_formatting_stacktrace/test.cpp @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_format_support.hpp" + +using namespace std; + +template