https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/130500
>From c132aa555a38efde9b04c2a3f435ba598778c28d Mon Sep 17 00:00:00 2001 From: Mark de Wever <ko...@xs4all.nl> Date: Sat, 30 Mar 2024 17:35:56 +0100 Subject: [PATCH 1/2] [libc++][format] Implements P3107R5 in <print>. The followup paper P3235R3 which is voted in as a DR changes the names foo_locking to foo_buffered. These changes have been applied in this patch. Before ------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------- printf 71.3 ns 71.3 ns 9525175 print_string 226 ns 226 ns 3105850 print_stack 232 ns 232 ns 3026498 print_direct 530 ns 530 ns 1318447 After ------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------- printf 70.6 ns 70.6 ns 9789585 print_string 222 ns 222 ns 3147678 print_stack 227 ns 227 ns 3084767 print_direct 474 ns 474 ns 1472786 Note: The performance of libc++'s std::print is still extemely slow compared to printf. Based on P3107R5 std::print should outperform printf. The main culprit is the call to isatty, which is resolved after implementing LWG4044 Confusing requirements for std::print on POSIX platforms Implements - P3107R5 - Permit an efficient implementation of ``std::print`` Implements parts of - P3235R3 std::print more types faster with less memory Fixes: #105435 --- libcxx/docs/ReleaseNotes/21.rst | 1 + libcxx/include/__format/buffer.h | 3 + libcxx/include/print | 270 +++++++++++++++++- libcxx/modules/std/print.inc | 1 + .../test/libcxx/system_reserved_names.gen.py | 5 + .../test/libcxx/transitive_includes/cxx03.csv | 5 + .../test/libcxx/transitive_includes/cxx11.csv | 5 + .../test/libcxx/transitive_includes/cxx14.csv | 5 + .../test/libcxx/transitive_includes/cxx17.csv | 5 + .../test/libcxx/transitive_includes/cxx23.csv | 5 +- .../test/libcxx/transitive_includes/cxx26.csv | 4 + 11 files changed, 296 insertions(+), 13 deletions(-) diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst index e7cfa625a132c..a1f30b26c5a1d 100644 --- a/libcxx/docs/ReleaseNotes/21.rst +++ b/libcxx/docs/ReleaseNotes/21.rst @@ -40,6 +40,7 @@ Implemented Papers - N4258: Cleaning-up noexcept in the Library (`Github <https://github.com/llvm/llvm-project/issues/99937>`__) - P1361R2: Integration of chrono with text formatting (`Github <https://github.com/llvm/llvm-project/issues/100014>`__) +- P3107R5 - Permit an efficient implementation of ``std::print`` (`Github <https://github.com/llvm/llvm-project/issues/105435>`__) Improvements and New Features ----------------------------- diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h index c88b7f3222010..d6e4ddc840e2d 100644 --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -12,6 +12,7 @@ #include <__algorithm/copy_n.h> #include <__algorithm/fill_n.h> +#include <__algorithm/for_each.h> #include <__algorithm/max.h> #include <__algorithm/min.h> #include <__algorithm/ranges_copy.h> @@ -34,11 +35,13 @@ #include <__memory/construct_at.h> #include <__memory/destroy.h> #include <__memory/uninitialized_algorithms.h> +#include <__system_error/system_error.h> #include <__type_traits/add_pointer.h> #include <__type_traits/conditional.h> #include <__utility/exception_guard.h> #include <__utility/move.h> #include <stdexcept> +#include <stdio.h> // Uses the POSIX/Windows unlocked stream I/O #include <string_view> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) diff --git a/libcxx/include/print b/libcxx/include/print index 1794d6014efcd..f6d03edfbd4bc 100644 --- a/libcxx/include/print +++ b/libcxx/include/print @@ -27,9 +27,11 @@ namespace std { void vprint_unicode(string_view fmt, format_args args); void vprint_unicode(FILE* stream, string_view fmt, format_args args); + void vprint_unicode_buffered(FILE* stream, string_view fmt, format_args args); void vprint_nonunicode(string_view fmt, format_args args); void vprint_nonunicode(FILE* stream, string_view fmt, format_args args); + void vprint_nonunicode_buffered(FILE* stream, string_view fmt, format_args args); } */ @@ -41,6 +43,7 @@ namespace std { # include <__config> # include <__system_error/throw_system_error.h> # include <__utility/forward.h> +# include <__utility/move.h> # include <cerrno> # include <cstdio> # include <format> @@ -52,6 +55,9 @@ namespace std { # pragma GCC system_header # endif +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + _LIBCPP_BEGIN_NAMESPACE_STD # ifdef _LIBCPP_WIN32API @@ -213,6 +219,122 @@ _LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream) # endif } +_LIBCPP_HIDE_FROM_ABI inline void __flockfile(FILE* __stream) { +# if defined(_LIBCPP_WIN32API) + ::_lock_file(__stream); +# elif __has_include(<unistd.h>) + ::flockfile(__stream); +# else +# error "Provide a way to do unlocked stream I/O operations" +# endif +} +_LIBCPP_HIDE_FROM_ABI inline void __funlockfile(FILE* __stream) { +# if defined(_LIBCPP_WIN32API) + ::_unlock_file(__stream); +# elif __has_include(<unistd.h>) + ::funlockfile(__stream); +# else +# error "Provide a way to do unlocked stream I/O operations" +# endif +} + +_LIBCPP_HIDE_FROM_ABI inline int __fflush_unlocked(FILE* __stream) { +# if defined(_LIBCPP_WIN32API) + return ::_fflush_nolock(__stream); +# elif defined(__PICOLIBC__) || defined(_AIX) || defined(__ANDROID__) + // There is no fflush_unlocked on these systems. + // This funcion is not part of POSIX. + return ::fflush(__stream); +# elif __has_include(<unistd.h>) + return ::fflush_unlocked(__stream); +# else +# error "Provide a way to do unlocked stream I/O operations" +# endif +} + +// Note for our use-case __size is always 1; +_LIBCPP_HIDE_FROM_ABI inline size_t +__fwrite_unlocked(const void* __buffer, [[maybe_unused]] size_t __size, size_t __n, FILE* __stream) { +# if defined(_LIBCPP_WIN32API) + return ::_fwrite_nolock(__buffer, __size, __n, __stream); +# elif defined(__PICOLIBC__) || defined(_AIX) || defined(__ANDROID__) + // There is no fwrite_unlocked on these systems. + // This funcion is not part of POSIX. + auto __b = static_cast<const char*>(__buffer); + for (size_t __i = 0; __i < __n; ++__i, ++__b) + // Unqualified since putc_unlocked is a macro on AIX. + if (putc_unlocked(*__b, __stream) == EOF) + return __i; + return __n; +# elif __has_include(<unistd.h>) + return ::fwrite_unlocked(__buffer, __size, __n, __stream); +# else +# error "Provide a way to do unlocked stream I/O operations" +# endif +} + +// This "buffer" is not a typical buffer but an adaptor for FILE* +// +// This adaptor locks the file stream, allowing it to use unlocked I/O. +// This is used by the *_buffered functions in <print>. The print functions have +// no wchar_t support so char is hard-coded. Since the underlaying I/O functions +// encode narrow or wide in their name this avoids some `if constexpr` branches. +// +// The underlying functions for unlocked I/O are not in the C Standard, and +// their names differ between POSIX and Windows, therefore the functions are +// wrapped in this class. +class __file_stream_buffer : public __format::__output_buffer<char> { +public: + using value_type = char; + + __file_stream_buffer(const __file_stream_buffer&) = delete; + __file_stream_buffer operator=(const __file_stream_buffer&) = delete; + + _LIBCPP_HIDE_FROM_ABI explicit __file_stream_buffer(FILE* __stream) + : __output_buffer<char>{__small_buffer_, __buffer_size, __prepare_write, nullptr}, __stream_(__stream) { + __print::__flockfile(__stream_); + } + + _LIBCPP_HIDE_FROM_ABI ~__file_stream_buffer() { __print::__funlockfile(__stream_); } + + // In order to ensure all data is written this function needs to be called. + // + // The class wraps C based APIs that never throw. However the Standard + // requires exceptions to be throw when a write operation fails. Therefore + // this function should be called before the class is destroyed. + _LIBCPP_HIDE_FROM_ABI void __write_internal_buffer() && { __write_buffer(); } + +private: + FILE* __stream_; + + // This class uses a fixed size buffer and appends the elements in + // __buffer_size chunks. An alternative would be to use an allocating buffer + // and append the output in a single write operation. Benchmarking showed no + // performance difference. + static constexpr size_t __buffer_size = 256; + char __small_buffer_[__buffer_size]; + + _LIBCPP_HIDE_FROM_ABI void __write_buffer() { + size_t __n = this->__size(); + size_t __size = __print::__fwrite_unlocked(__small_buffer_, 1, __n, __stream_); + if (__size < __n) { + if (std::feof(__stream_)) + std::__throw_system_error(EIO, "EOF while writing the formatted output"); + std::__throw_system_error(std::ferror(__stream_), "failed to write formatted output"); + } + } + + _LIBCPP_HIDE_FROM_ABI void __prepare_write() { + __write_buffer(); + this->__buffer_flushed(); + } + + _LIBCPP_HIDE_FROM_ABI static void + __prepare_write(__output_buffer<char>& __buffer, [[maybe_unused]] size_t __size_hint) { + static_cast<__file_stream_buffer&>(__buffer).__prepare_write(); + } +}; + template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). _LIBCPP_HIDE_FROM_ABI inline void __vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { @@ -229,6 +351,26 @@ __vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool } } +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void __vprint_nonunicode_buffered( + __print::__file_stream_buffer& __buffer, string_view __fmt, format_args __args, bool __write_nl) { + std::__format::__vformat_to(basic_format_parse_context{__fmt, __args.__size()}, + std::__format_context_create(__buffer.__make_output_iterator(), __args)); + if (__write_nl) + __buffer.push_back('\n'); +} + +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void +__vprint_nonunicode_buffered(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { + _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); + __print::__file_stream_buffer __buffer(__stream); + + __print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl); + + std::move(__buffer).__write_internal_buffer(); +} + # if _LIBCPP_HAS_UNICODE // Note these helper functions are mainly used to aid testing. @@ -246,10 +388,27 @@ __vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bo __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); } +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_posix( + FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); + __print::__file_stream_buffer __buffer(__stream); + + // TODO PRINT Should flush errors throw too? + if (__is_terminal) + __print::__fflush_unlocked(__stream); + + __print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl); + + std::move(__buffer).__write_internal_buffer(); +} # if _LIBCPP_HAS_WIDE_CHARACTERS + template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). _LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); + if (!__is_terminal) return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); @@ -284,6 +443,49 @@ __vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, "__write_to_windows_console is not available."); # endif } + +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_windows( + FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { + _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); + + if (!__is_terminal) + return __print::__vprint_nonunicode_buffered(__stream, __fmt, __args, __write_nl); + + [[maybe_unused]] __print::__file_stream_buffer __b(__stream); + + // TODO PRINT Should flush errors throw too? + __print::__fflush_unlocked(__stream); + + string __str = std::vformat(__fmt, __args); + // UTF-16 uses the same number or less code units than UTF-8. + // However the size of the code unit is 16 bits instead of 8 bits. + // + // The buffer uses the worst-case estimate and should never resize. + // However when the string is large this could lead to OOM. Using a + // smaller size might work, but since the buffer uses a grow factor + // the final size might be larger when the estimate is wrong. + // + // TODO PRINT profile and improve the speed of this code. + __format::__retarget_buffer<wchar_t> __buffer{__str.size()}; + __unicode::__transcode(__str.begin(), __str.end(), __buffer.__make_output_iterator()); + if (__write_nl) + __buffer.push_back(L'\n'); + + [[maybe_unused]] wstring_view __view = __buffer.__view(); + + // The macro _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION is used to change + // the behavior in the test. This is not part of the public API. +# ifdef _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION + _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION(__stream, __view); +# elif defined(_LIBCPP_WIN32API) + std::__write_to_windows_console(__stream, __view); +# else + std::__throw_runtime_error("No defintion of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION and " + "__write_to_windows_console is not available."); +# endif +} + # endif // _LIBCPP_HAS_WIDE_CHARACTERS template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). @@ -324,6 +526,23 @@ __vprint_unicode([[maybe_unused]] FILE* __stream, # endif } +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered( + [[maybe_unused]] FILE* __stream, + [[maybe_unused]] string_view __fmt, + [[maybe_unused]] format_args __args, + [[maybe_unused]] bool __write_nl) { + _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); + +# ifndef _LIBCPP_WIN32API + __print::__vprint_unicode_buffered_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); +# elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) + __print::__vprint_unicode_buffered_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); +# else +# error "Windows builds with wchar_t disabled are not supported." +# endif +} + # endif // _LIBCPP_HAS_UNICODE } // namespace __print @@ -331,13 +550,23 @@ __vprint_unicode([[maybe_unused]] FILE* __stream, template <class... _Args> _LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { # if _LIBCPP_HAS_UNICODE - if constexpr (__print::__use_unicode_execution_charset) - __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false); - else - __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false); + constexpr bool __use_unicode = __print::__use_unicode_execution_charset; # else // _LIBCPP_HAS_UNICODE - __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false); + constexpr bool __use_unicode = false; # endif // _LIBCPP_HAS_UNICODE + constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...); + + if constexpr (__use_unicode) { + if constexpr (__locksafe) + __print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false); + else + __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false); + } else { + if constexpr (__locksafe) + __print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false); + else + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false); + } } template <class... _Args> @@ -348,16 +577,26 @@ _LIBCPP_HIDE_FROM_ABI void print(format_string<_Args...> __fmt, _Args&&... __arg template <class... _Args> _LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { # if _LIBCPP_HAS_UNICODE + constexpr bool __use_unicode = __print::__use_unicode_execution_charset; +# else // _LIBCPP_HAS_UNICODE + constexpr bool __use_unicode = false; +# endif // _LIBCPP_HAS_UNICODE + constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...); + // Note the wording in the Standard is inefficient. The output of // std::format is a std::string which is then copied. This solution // just appends a newline at the end of the output. - if constexpr (__print::__use_unicode_execution_charset) - __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true); - else - __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true); -# else // _LIBCPP_HAS_UNICODE - __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true); -# endif // _LIBCPP_HAS_UNICODE + if constexpr (__use_unicode) { + if constexpr (__locksafe) + __print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true); + else + __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true); + } else { + if constexpr (__locksafe) + __print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true); + else + __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true); + } } template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). @@ -381,6 +620,11 @@ _LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(FILE* __stream, string_view __f __print::__vprint_unicode(__stream, __fmt, __args, false); } +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode_buffered(FILE* __stream, string_view __fmt, format_args __args) { + __print::__vprint_unicode_buffered(__stream, __fmt, __args, false); +} + template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). _LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(string_view __fmt, format_args __args) { std::vprint_unicode(stdout, __fmt, __args); @@ -402,6 +646,8 @@ _LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(string_view __fmt, format_ar _LIBCPP_END_NAMESPACE_STD +_LIBCPP_POP_MACROS + #endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) #endif // _LIBCPP_PRINT diff --git a/libcxx/modules/std/print.inc b/libcxx/modules/std/print.inc index 5354025ca8bd8..9c6d8b20a22d8 100644 --- a/libcxx/modules/std/print.inc +++ b/libcxx/modules/std/print.inc @@ -16,6 +16,7 @@ export namespace std { using std::vprint_nonunicode; # if _LIBCPP_HAS_UNICODE using std::vprint_unicode; + using std::vprint_unicode_buffered; # endif // _LIBCPP_HAS_UNICODE #endif // _LIBCPP_STD_VER >= 23 } // namespace std diff --git a/libcxx/test/libcxx/system_reserved_names.gen.py b/libcxx/test/libcxx/system_reserved_names.gen.py index f01126249c881..304c803b76c3d 100644 --- a/libcxx/test/libcxx/system_reserved_names.gen.py +++ b/libcxx/test/libcxx/system_reserved_names.gen.py @@ -119,6 +119,11 @@ #define __acquire SYSTEM_RESERVED_NAME #define __release SYSTEM_RESERVED_NAME +// Android and FreeBSD use this for __attribute__((__unused__)) +#if !defined(__FreeBSD__) && !defined(__ANDROID__) +#define __unused SYSTEM_RESERVED_NAME +#endif + // These names are not reserved, so the user can macro-define them. // These are intended to find improperly _Uglified template parameters. #define A SYSTEM_RESERVED_NAME diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv index ec5db90597d92..7d52c9fe13594 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -122,15 +122,19 @@ atomic ratio atomic type_traits atomic version barrier atomic +barrier cctype barrier climits barrier cmath barrier compare barrier concepts barrier cstddef barrier cstdint +barrier cstdio barrier cstdlib barrier cstring barrier ctime +barrier cwchar +barrier cwctype barrier exception barrier initializer_list barrier iosfwd @@ -2024,6 +2028,7 @@ stdexcept new stdexcept type_traits stdexcept typeinfo stdexcept version +stop_token cstddef stop_token iosfwd stop_token version streambuf algorithm diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv index ec5db90597d92..7d52c9fe13594 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -122,15 +122,19 @@ atomic ratio atomic type_traits atomic version barrier atomic +barrier cctype barrier climits barrier cmath barrier compare barrier concepts barrier cstddef barrier cstdint +barrier cstdio barrier cstdlib barrier cstring barrier ctime +barrier cwchar +barrier cwctype barrier exception barrier initializer_list barrier iosfwd @@ -2024,6 +2028,7 @@ stdexcept new stdexcept type_traits stdexcept typeinfo stdexcept version +stop_token cstddef stop_token iosfwd stop_token version streambuf algorithm diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv index 95024df0590b8..951a785cf077b 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -125,15 +125,19 @@ atomic ratio atomic type_traits atomic version barrier atomic +barrier cctype barrier climits barrier cmath barrier compare barrier concepts barrier cstddef barrier cstdint +barrier cstdio barrier cstdlib barrier cstring barrier ctime +barrier cwchar +barrier cwctype barrier exception barrier initializer_list barrier iosfwd @@ -2064,6 +2068,7 @@ stdexcept new stdexcept type_traits stdexcept typeinfo stdexcept version +stop_token cstddef stop_token iosfwd stop_token version streambuf algorithm diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv index a3518f7f62ecb..7742c23b796f7 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -122,15 +122,19 @@ atomic ratio atomic type_traits atomic version barrier atomic +barrier cctype barrier climits barrier cmath barrier compare barrier concepts barrier cstddef barrier cstdint +barrier cstdio barrier cstdlib barrier cstring barrier ctime +barrier cwchar +barrier cwctype barrier exception barrier initializer_list barrier iosfwd @@ -2077,6 +2081,7 @@ stdexcept new stdexcept type_traits stdexcept typeinfo stdexcept version +stop_token cstddef stop_token iosfwd stop_token version streambuf algorithm diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv index 17972b8453743..167c79130bfbf 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -556,7 +556,6 @@ istream ios istream iosfwd istream limits istream locale - istream ratio istream stdexcept istream streambuf @@ -765,6 +764,7 @@ queue deque queue initializer_list queue iosfwd queue limits +queue optional queue stdexcept queue string queue string_view @@ -831,6 +831,7 @@ regex deque regex initializer_list regex iosfwd regex limits +regex optional regex stdexcept regex string regex string_view @@ -1075,6 +1076,7 @@ thread iosfwd thread istream thread limits thread locale +thread optional thread ratio thread sstream thread stdexcept @@ -1146,6 +1148,7 @@ vector cwctype vector initializer_list vector iosfwd vector limits +vector optional vector stdexcept vector string vector string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv index 00ab78e61a457..2bc61974a2ad8 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -763,6 +763,7 @@ queue deque queue initializer_list queue iosfwd queue limits +queue optional queue stdexcept queue string queue string_view @@ -829,6 +830,7 @@ regex deque regex initializer_list regex iosfwd regex limits +regex optional regex stdexcept regex string regex string_view @@ -1073,6 +1075,7 @@ thread iosfwd thread istream thread limits thread locale +thread optional thread ratio thread sstream thread stdexcept @@ -1144,6 +1147,7 @@ vector cwctype vector initializer_list vector iosfwd vector limits +vector optional vector stdexcept vector string vector string_view >From 4fcce8dc4c58ccc092087d89c4ee73c326165373 Mon Sep 17 00:00:00 2001 From: Mark de Wever <ko...@xs4all.nl> Date: Thu, 13 Mar 2025 18:44:10 +0100 Subject: [PATCH 2/2] Cleanups and improvements. --- libcxx/include/__format/buffer.h | 3 - libcxx/include/__ostream/print.h | 4 +- libcxx/include/print | 257 +++++++----------- libcxx/modules/std/print.inc | 1 + .../print.fun/vprint_unicode_posix.pass.cpp | 13 +- .../print.fun/vprint_unicode_windows.pass.cpp | 49 ++-- .../test/libcxx/transitive_includes/cxx23.csv | 4 - .../test/libcxx/transitive_includes/cxx26.csv | 4 - .../vprint_nonunicode_buffered.file.pass.cpp | 149 ++++++++++ .../vprint_unicode_buffered.file.pass.cpp | 156 +++++++++++ 10 files changed, 454 insertions(+), 186 deletions(-) create mode 100644 libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode_buffered.file.pass.cpp create mode 100644 libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode_buffered.file.pass.cpp diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h index d6e4ddc840e2d..c88b7f3222010 100644 --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -12,7 +12,6 @@ #include <__algorithm/copy_n.h> #include <__algorithm/fill_n.h> -#include <__algorithm/for_each.h> #include <__algorithm/max.h> #include <__algorithm/min.h> #include <__algorithm/ranges_copy.h> @@ -35,13 +34,11 @@ #include <__memory/construct_at.h> #include <__memory/destroy.h> #include <__memory/uninitialized_algorithms.h> -#include <__system_error/system_error.h> #include <__type_traits/add_pointer.h> #include <__type_traits/conditional.h> #include <__utility/exception_guard.h> #include <__utility/move.h> #include <stdexcept> -#include <stdio.h> // Uses the POSIX/Windows unlocked stream I/O #include <string_view> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) diff --git a/libcxx/include/__ostream/print.h b/libcxx/include/__ostream/print.h index a4d7506cffc48..b5ce81ea5a10a 100644 --- a/libcxx/include/__ostream/print.h +++ b/libcxx/include/__ostream/print.h @@ -111,9 +111,9 @@ _LIBCPP_HIDE_FROM_ABI void __vprint_unicode(ostream& __os, string_view __fmt, fo ostream::sentry __s(__os); if (__s) { # ifndef _LIBCPP_WIN32API - __print::__vprint_unicode_posix(__file, __fmt, __args, __write_nl, true); + __print::__vprint_unicode_posix<__print::__lock_policy::__stdio>(__file, __fmt, __args, __write_nl, true); # elif _LIBCPP_HAS_WIDE_CHARACTERS - __print::__vprint_unicode_windows(__file, __fmt, __args, __write_nl, true); + __print::__vprint_unicode_windows<__print::__lock_policy::__stdio>(__file, __fmt, __args, __write_nl, true); # else # error "Windows builds with wchar_t disabled are not supported." # endif diff --git a/libcxx/include/print b/libcxx/include/print index f6d03edfbd4bc..3372b08bbcad1 100644 --- a/libcxx/include/print +++ b/libcxx/include/print @@ -258,11 +258,11 @@ __fwrite_unlocked(const void* __buffer, [[maybe_unused]] size_t __size, size_t _ # if defined(_LIBCPP_WIN32API) return ::_fwrite_nolock(__buffer, __size, __n, __stream); # elif defined(__PICOLIBC__) || defined(_AIX) || defined(__ANDROID__) - // There is no fwrite_unlocked on these systems. - // This funcion is not part of POSIX. + // The function fwrite_unlocked is not part of POSIX and not available on + // these systems. auto __b = static_cast<const char*>(__buffer); for (size_t __i = 0; __i < __n; ++__i, ++__b) - // Unqualified since putc_unlocked is a macro on AIX. + // Unqualified since putc_unlocked is a macro on AIX. if (putc_unlocked(*__b, __stream) == EOF) return __i; return __n; @@ -273,17 +273,37 @@ __fwrite_unlocked(const void* __buffer, [[maybe_unused]] size_t __size, size_t _ # endif } -// This "buffer" is not a typical buffer but an adaptor for FILE* -// -// This adaptor locks the file stream, allowing it to use unlocked I/O. -// This is used by the *_buffered functions in <print>. The print functions have -// no wchar_t support so char is hard-coded. Since the underlaying I/O functions -// encode narrow or wide in their name this avoids some `if constexpr` branches. +enum class __lock_policy { + // The locking is done manually, which allows calling the *_unlocked functions. + // + // These are used in the *_buffered overloads. + __manual, + // The locking is done inside the stdio library. + __stdio, + +}; + +template <__lock_policy __policy> +_LIBCPP_HIDE_FROM_ABI int __fflush(FILE* __stream) { + if constexpr (__policy == __lock_policy::__manual) + return __print ::__fflush_unlocked(__stream); + else if constexpr (__policy == __lock_policy::__stdio) + return std::fflush(__stream); + else +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(__lock_policy) == 0 +# else + static_assert(false, "Unsupported policy"); +# endif +} + +// This "buffer" is not a typical buffer but an buffered adaptor for FILE* // -// The underlying functions for unlocked I/O are not in the C Standard, and -// their names differ between POSIX and Windows, therefore the functions are -// wrapped in this class. -class __file_stream_buffer : public __format::__output_buffer<char> { +// Based on the __lock_policy it will leave the locking to stdio or manually +// locks the stream and then uses unlocked stdio functions. This policy makes +// the print functions "foo" and "foo_buffered" to be templated. +template <__lock_policy __policy> +class _LIBCPP_TEMPLATE_VIS __file_stream_buffer : public __format::__output_buffer<char> { public: using value_type = char; @@ -292,10 +312,26 @@ public: _LIBCPP_HIDE_FROM_ABI explicit __file_stream_buffer(FILE* __stream) : __output_buffer<char>{__small_buffer_, __buffer_size, __prepare_write, nullptr}, __stream_(__stream) { - __print::__flockfile(__stream_); + if constexpr (__policy == __lock_policy::__manual) + __print::__flockfile(__stream_); + else if constexpr (__policy != __lock_policy::__stdio) +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(__lock_policy) == 0 +# else + static_assert(false, "Unsupported policy"); +# endif } - _LIBCPP_HIDE_FROM_ABI ~__file_stream_buffer() { __print::__funlockfile(__stream_); } + _LIBCPP_HIDE_FROM_ABI ~__file_stream_buffer() { + if constexpr (__policy == __lock_policy::__manual) + __print::__funlockfile(__stream_); + else if constexpr (__policy != __lock_policy::__stdio) +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(__lock_policy) == 0 +# else + static_assert(false, "Unsupported policy"); +# endif + } // In order to ensure all data is written this function needs to be called. // @@ -316,7 +352,19 @@ private: _LIBCPP_HIDE_FROM_ABI void __write_buffer() { size_t __n = this->__size(); - size_t __size = __print::__fwrite_unlocked(__small_buffer_, 1, __n, __stream_); + size_t __size = [&] { + if constexpr (__policy == __lock_policy::__manual) + return __print::__fwrite_unlocked(__small_buffer_, 1, __n, __stream_); + else if constexpr (__policy == __lock_policy::__stdio) + return std::fwrite(__small_buffer_, 1, __n, __stream_); + else +# if defined(_LIBCPP_APPLE_CLANG_VER) && _LIBCPP_APPLE_CLANG_VER < 1600 + sizeof(__lock_policy) == 0 +# else + static_assert(false, "Unsupported policy"); +# endif + }(); + if (__size < __n) { if (std::feof(__stream_)) std::__throw_system_error(EIO, "EOF while writing the formatted output"); @@ -331,42 +379,19 @@ private: _LIBCPP_HIDE_FROM_ABI static void __prepare_write(__output_buffer<char>& __buffer, [[maybe_unused]] size_t __size_hint) { - static_cast<__file_stream_buffer&>(__buffer).__prepare_write(); + static_cast<__file_stream_buffer<__policy>&>(__buffer).__prepare_write(); } }; -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void -__vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { +template <__lock_policy __policy> +_LIBCPP_HIDE_FROM_ABI void __vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); - string __str = std::vformat(__fmt, __args); - if (__write_nl) - __str.push_back('\n'); - - size_t __size = fwrite(__str.data(), 1, __str.size(), __stream); - if (__size < __str.size()) { - if (std::feof(__stream)) - std::__throw_system_error(EIO, "EOF while writing the formatted output"); - std::__throw_system_error(std::ferror(__stream), "failed to write formatted output"); - } -} + __print::__file_stream_buffer<__policy> __buffer(__stream); -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void __vprint_nonunicode_buffered( - __print::__file_stream_buffer& __buffer, string_view __fmt, format_args __args, bool __write_nl) { std::__format::__vformat_to(basic_format_parse_context{__fmt, __args.__size()}, std::__format_context_create(__buffer.__make_output_iterator(), __args)); if (__write_nl) __buffer.push_back('\n'); -} - -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void -__vprint_nonunicode_buffered(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) { - _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); - __print::__file_stream_buffer __buffer(__stream); - - __print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl); std::move(__buffer).__write_internal_buffer(); } @@ -378,84 +403,33 @@ __vprint_nonunicode_buffered(FILE* __stream, string_view __fmt, format_args __ar // terminal when the output is redirected. Typically during testing the // output is redirected to be able to capture it. This makes it hard to // test this code path. -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void -__vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { - // TODO PRINT Should flush errors throw too? - if (__is_terminal) - std::fflush(__stream); - - __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); -} -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_posix( - FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { +template <__lock_policy __policy> +_LIBCPP_HIDE_FROM_ABI void +__vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); - __print::__file_stream_buffer __buffer(__stream); // TODO PRINT Should flush errors throw too? if (__is_terminal) - __print::__fflush_unlocked(__stream); - - __print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl); + __print::__fflush<__policy>(__stream); - std::move(__buffer).__write_internal_buffer(); + __print::__vprint_nonunicode<__policy>(__stream, __fmt, __args, __write_nl); } + # if _LIBCPP_HAS_WIDE_CHARACTERS -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void +template <__lock_policy __policy> +_LIBCPP_HIDE_FROM_ABI void __vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); if (!__is_terminal) - return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl); - - // TODO PRINT Should flush errors throw too? - std::fflush(__stream); - - string __str = std::vformat(__fmt, __args); - // UTF-16 uses the same number or less code units than UTF-8. - // However the size of the code unit is 16 bits instead of 8 bits. - // - // The buffer uses the worst-case estimate and should never resize. - // However when the string is large this could lead to OOM. Using a - // smaller size might work, but since the buffer uses a grow factor - // the final size might be larger when the estimate is wrong. - // - // TODO PRINT profile and improve the speed of this code. - __format::__retarget_buffer<wchar_t> __buffer{__str.size()}; - __unicode::__transcode(__str.begin(), __str.end(), __buffer.__make_output_iterator()); - if (__write_nl) - __buffer.push_back(L'\n'); - - [[maybe_unused]] wstring_view __view = __buffer.__view(); - - // The macro _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION is used to change - // the behavior in the test. This is not part of the public API. -# ifdef _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION - _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION(__stream, __view); -# elif defined(_LIBCPP_WIN32API) - std::__write_to_windows_console(__stream, __view); -# else - std::__throw_runtime_error("No defintion of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION and " - "__write_to_windows_console is not available."); -# endif -} - -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_windows( - FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) { - _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); - - if (!__is_terminal) - return __print::__vprint_nonunicode_buffered(__stream, __fmt, __args, __write_nl); + return __print::__vprint_nonunicode<__policy>(__stream, __fmt, __args, __write_nl); - [[maybe_unused]] __print::__file_stream_buffer __b(__stream); + [[maybe_unused]] __print::__file_stream_buffer<__policy> __b(__stream); // TODO PRINT Should flush errors throw too? - __print::__fflush_unlocked(__stream); + __print::__fflush<__policy>(__stream); string __str = std::vformat(__fmt, __args); // UTF-16 uses the same number or less code units than UTF-8. @@ -488,8 +462,8 @@ _LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_windows( # endif // _LIBCPP_HAS_WIDE_CHARACTERS -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void +template <__lock_policy __policy> +_LIBCPP_HIDE_FROM_ABI void __vprint_unicode([[maybe_unused]] FILE* __stream, [[maybe_unused]] string_view __fmt, [[maybe_unused]] format_args __args, @@ -518,31 +492,13 @@ __vprint_unicode([[maybe_unused]] FILE* __stream, // Windows there is a different API. This API requires transcoding. # ifndef _LIBCPP_WIN32API - __print::__vprint_unicode_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); + __print::__vprint_unicode_posix<__policy>(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); # elif _LIBCPP_HAS_WIDE_CHARACTERS - __print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); + __print::__vprint_unicode_windows<__policy>(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); # else # error "Windows builds with wchar_t disabled are not supported." # endif } - -template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). -_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered( - [[maybe_unused]] FILE* __stream, - [[maybe_unused]] string_view __fmt, - [[maybe_unused]] format_args __args, - [[maybe_unused]] bool __write_nl) { - _LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream"); - -# ifndef _LIBCPP_WIN32API - __print::__vprint_unicode_buffered_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); -# elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) - __print::__vprint_unicode_buffered_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream)); -# else -# error "Windows builds with wchar_t disabled are not supported." -# endif -} - # endif // _LIBCPP_HAS_UNICODE } // namespace __print @@ -556,17 +512,13 @@ _LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, # endif // _LIBCPP_HAS_UNICODE constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...); - if constexpr (__use_unicode) { - if constexpr (__locksafe) - __print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false); - else - __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false); - } else { - if constexpr (__locksafe) - __print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false); - else - __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false); - } + using enum __print::__lock_policy; + if constexpr (__use_unicode) + __print::__vprint_unicode<__locksafe ? __manual : __stdio>( + __stream, __fmt.get(), std::make_format_args(__args...), false); + else + __print::__vprint_nonunicode<__locksafe ? __manual : __stdio>( + __stream, __fmt.get(), std::make_format_args(__args...), false); } template <class... _Args> @@ -586,17 +538,13 @@ _LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt // Note the wording in the Standard is inefficient. The output of // std::format is a std::string which is then copied. This solution // just appends a newline at the end of the output. - if constexpr (__use_unicode) { - if constexpr (__locksafe) - __print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true); - else - __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true); - } else { - if constexpr (__locksafe) - __print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true); - else - __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true); - } + using enum __print::__lock_policy; + if constexpr (__use_unicode) + __print::__vprint_unicode<__locksafe ? __manual : __stdio>( + __stream, __fmt.get(), std::make_format_args(__args...), true); + else + __print::__vprint_nonunicode<__locksafe ? __manual : __stdio>( + __stream, __fmt.get(), std::make_format_args(__args...), true); } template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). @@ -617,12 +565,12 @@ _LIBCPP_HIDE_FROM_ABI void println(format_string<_Args...> __fmt, _Args&&... __a # if _LIBCPP_HAS_UNICODE template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). _LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(FILE* __stream, string_view __fmt, format_args __args) { - __print::__vprint_unicode(__stream, __fmt, __args, false); + __print::__vprint_unicode<__print::__lock_policy::__stdio>(__stream, __fmt, __args, false); } template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). _LIBCPP_HIDE_FROM_ABI inline void vprint_unicode_buffered(FILE* __stream, string_view __fmt, format_args __args) { - __print::__vprint_unicode_buffered(__stream, __fmt, __args, false); + __print::__vprint_unicode<__print::__lock_policy::__manual>(__stream, __fmt, __args, false); } template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). @@ -634,7 +582,12 @@ _LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(string_view __fmt, format_args template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). _LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args) { - __print::__vprint_nonunicode(__stream, __fmt, __args, false); + __print::__vprint_nonunicode< __print::__lock_policy::__stdio >(__stream, __fmt, __args, false); +} + +template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). +_LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode_buffered(FILE* __stream, string_view __fmt, format_args __args) { + __print::__vprint_nonunicode<__print::__lock_policy::__manual>(__stream, __fmt, __args, false); } template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563). diff --git a/libcxx/modules/std/print.inc b/libcxx/modules/std/print.inc index 9c6d8b20a22d8..123bce409a06a 100644 --- a/libcxx/modules/std/print.inc +++ b/libcxx/modules/std/print.inc @@ -14,6 +14,7 @@ export namespace std { using std::println; using std::vprint_nonunicode; + using ::std::vprint_nonunicode_buffered; # if _LIBCPP_HAS_UNICODE using std::vprint_unicode; using std::vprint_unicode_buffered; diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp index b89d02ba99425..db92811f803fa 100644 --- a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp @@ -21,6 +21,7 @@ // <print> // Tests the implementation of +// template <__print::__lock_policy __policy> // void __print::__vprint_unicode_posix(FILE* __stream, string_view __fmt, // format_args __args, bool __write_nl, // bool __is_terminal); @@ -39,7 +40,8 @@ #include "test_macros.h" -int main(int, char**) { +template <std::__print::__lock_policy policy> +static void test() { std::array<char, 100> buffer; std::ranges::fill(buffer, '*'); @@ -55,7 +57,7 @@ int main(int, char**) { #endif // Test writing to a "non-terminal" stream does not flush. - std::__print::__vprint_unicode_posix(file, " world", std::make_format_args(), false, false); + std::__print::__vprint_unicode_posix<policy>(file, " world", std::make_format_args(), false, false); assert(std::ftell(file) == 11); #if defined(TEST_HAS_GLIBC) && \ !(__has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || __has_feature(memory_sanitizer)) @@ -63,7 +65,7 @@ int main(int, char**) { #endif // Test writing to a "terminal" stream flushes before writing. - std::__print::__vprint_unicode_posix(file, "!", std::make_format_args(), false, true); + std::__print::__vprint_unicode_posix<policy>(file, "!", std::make_format_args(), false, true); assert(std::ftell(file) == 12); assert(std::string_view(buffer.data(), buffer.data() + 11) == "Hello world"); #if defined(TEST_HAS_GLIBC) @@ -74,6 +76,11 @@ int main(int, char**) { // Test everything is written when closing the stream. std::fclose(file); assert(std::string_view(buffer.data(), buffer.data() + 12) == "Hello world!"); +} + +int main(int, char**) { + test<std::__print::__lock_policy::__manual>(); + test<std::__print::__lock_policy::__stdio>(); return 0; } diff --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp index bcd1d05a3aeeb..8028d7c04b85b 100644 --- a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp +++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp @@ -20,6 +20,7 @@ // <print> // Tests the implementation of +// template <__print::__lock_policy __policy> // void __print::__vprint_unicode_windows(FILE* __stream, string_view __fmt, // format_args __args, bool __write_nl, // bool __is_terminal); @@ -47,8 +48,8 @@ TEST_GCC_DIAGNOSTIC_IGNORED("-Wuse-after-free") #define SV(S) MAKE_STRING_VIEW(wchar_t, S) -bool calling = false; -std::wstring_view expected = L" world"; +bool calling = true; +std::wstring_view expected; void write_to_console(FILE*, std::wstring_view data) { assert(calling); @@ -58,29 +59,35 @@ void write_to_console(FILE*, std::wstring_view data) { scoped_test_env env; std::string filename = env.create_file("output.txt"); +template <std::__print::__lock_policy policy> static void test_basics() { FILE* file = std::fopen(filename.c_str(), "wb"); assert(file); + calling = false; + expected = L" world"; + // Test writing to a "non-terminal" stream does not call WriteConsoleW. - std::__print::__vprint_unicode_windows(file, "Hello", std::make_format_args(), false, false); + std::__print::__vprint_unicode_windows<policy>(file, "Hello", std::make_format_args(), false, false); assert(std::ftell(file) == 5); // It's not possible to reliably test whether writing to a "terminal" stream // flushes before writing. Testing flushing a closed stream worked on some // platforms, but was unreliable. calling = true; - std::__print::__vprint_unicode_windows(file, " world", std::make_format_args(), false, true); + std::__print::__vprint_unicode_windows<policy>(file, " world", std::make_format_args(), false, true); + std::fclose(file); } // When the output is a file the data is written as-is. // When the output is a "terminal" invalid UTF-8 input is flagged. +template <std::__print::__lock_policy policy> static void test(std::wstring_view output, std::string_view input) { // *** File *** FILE* file = std::fopen(filename.c_str(), "wb"); assert(file); - std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, false); + std::__print::__vprint_unicode_windows<policy>(file, input, std::make_format_args(), false, false); assert(std::ftell(file) == static_cast<long>(input.size())); std::fclose(file); @@ -95,12 +102,13 @@ static void test(std::wstring_view output, std::string_view input) { // *** Terminal *** expected = output; - std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, true); + std::__print::__vprint_unicode_windows<policy>(file, input, std::make_format_args(), false, true); } +template <std::__print::__lock_policy policy> static void test() { // *** Test valid UTF-8 *** -#define TEST(S) test(SV(S), S) +#define TEST(S) test<policy>(SV(S), S) TEST("hello world"); // copied from benchmarks/std_format_spec_string_unicode.bench.cpp @@ -112,24 +120,29 @@ static void test() { #undef TEST // *** Test invalid utf-8 *** - test(SV("\ufffd"), "\xc3"); - test(SV("\ufffd("), "\xc3\x28"); + test<policy>(SV("\ufffd"), "\xc3"); + test<policy>(SV("\ufffd("), "\xc3\x28"); // surrogate range - test(SV("\ufffd"), "\xed\xa0\x80"); // U+D800 - test(SV("\ufffd"), "\xed\xaf\xbf"); // U+DBFF - test(SV("\ufffd"), "\xed\xbf\x80"); // U+DC00 - test(SV("\ufffd"), "\xed\xbf\xbf"); // U+DFFF + test<policy>(SV("\ufffd"), "\xed\xa0\x80"); // U+D800 + test<policy>(SV("\ufffd"), "\xed\xaf\xbf"); // U+DBFF + test<policy>(SV("\ufffd"), "\xed\xbf\x80"); // U+DC00 + test<policy>(SV("\ufffd"), "\xed\xbf\xbf"); // U+DFFF // beyond valid values - test(SV("\ufffd"), "\xf4\x90\x80\x80"); // U+110000 - test(SV("\ufffd"), "\xf4\xbf\xbf\xbf"); // U+11FFFF + test<policy>(SV("\ufffd"), "\xf4\x90\x80\x80"); // U+110000 + test<policy>(SV("\ufffd"), "\xf4\xbf\xbf\xbf"); // U+11FFFF // Validates http://unicode.org/review/pr-121.html option 3. - test(SV("\u0061\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0062"), "\x61\xf1\x80\x80\xe1\x80\xc2\x62"); + test<policy>(SV("\u0061\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0062"), "\x61\xf1\x80\x80\xe1\x80\xc2\x62"); } int main(int, char**) { - test_basics(); - test(); + test_basics<std::__print::__lock_policy::__manual>(); + test<std::__print::__lock_policy::__manual>(); + + test_basics<std::__print::__lock_policy::__stdio>(); + test<std::__print::__lock_policy::__stdio>(); + + return 0; } diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv index 167c79130bfbf..a26aeeebef75e 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -764,7 +764,6 @@ queue deque queue initializer_list queue iosfwd queue limits -queue optional queue stdexcept queue string queue string_view @@ -831,7 +830,6 @@ regex deque regex initializer_list regex iosfwd regex limits -regex optional regex stdexcept regex string regex string_view @@ -1076,7 +1074,6 @@ thread iosfwd thread istream thread limits thread locale -thread optional thread ratio thread sstream thread stdexcept @@ -1148,7 +1145,6 @@ vector cwctype vector initializer_list vector iosfwd vector limits -vector optional vector stdexcept vector string vector string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv index 2bc61974a2ad8..00ab78e61a457 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -763,7 +763,6 @@ queue deque queue initializer_list queue iosfwd queue limits -queue optional queue stdexcept queue string queue string_view @@ -830,7 +829,6 @@ regex deque regex initializer_list regex iosfwd regex limits -regex optional regex stdexcept regex string regex string_view @@ -1075,7 +1073,6 @@ thread iosfwd thread istream thread limits thread locale -thread optional thread ratio thread sstream thread stdexcept @@ -1147,7 +1144,6 @@ vector cwctype vector initializer_list vector iosfwd vector limits -vector optional vector stdexcept vector string vector string_view diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode_buffered.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode_buffered.file.pass.cpp new file mode 100644 index 0000000000000..a5a35bf8bedb1 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode_buffered.file.pass.cpp @@ -0,0 +1,149 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// TODO PRINT Enable again +// https://reviews.llvm.org/D150044 +// https://lab.llvm.org/buildbot/#/builders/237/builds/3578 +// UNSUPPORTED: asan, hwasan, msan + +// XFAIL: availability-fp_to_chars-missing + +// The error exception has no system error string. +// XFAIL: LIBCXX-ANDROID-FIXME + +// <print> + +// void vprint_nonunicode_buffered(FILE* stream, string_view fmt, format_args args); + +#include <algorithm> +#include <array> +#include <cassert> +#include <cstddef> +#include <cstdio> +#include <fstream> +#include <iterator> +#include <print> +#include <string_view> + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = []<class... Args>(std::string_view expected, std::string_view fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_nonunicode_buffered(file, fmt, std::make_format_args(args...)); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator<char>{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = []<class... Args>([[maybe_unused]] std::string_view what, + [[maybe_unused]] std::string_view fmt, + [[maybe_unused]] Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::format_error, + [&]([[maybe_unused]] const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode_buffered(file, fmt, std::make_format_args(args...))); + + fclose(file); +}; + +// Glibc fails writing to a wide stream. +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode_buffered(file, "hello", std::make_format_args())); +} +#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{ + "failed to write formatted output: " TEST_IF_AIX("Broken pipe", "Operation not permitted")}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_nonunicode_buffered(file, "hello", std::make_format_args())); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::vprint_nonunicode_buffered(file, "\n", std::make_format_args()); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_nonunicode_buffered(file, "\n", std::make_format_args()); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + test_wide_stream(); +#endif + test_read_only(); + test_new_line(); + + return 0; +} diff --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode_buffered.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode_buffered.file.pass.cpp new file mode 100644 index 0000000000000..23d77b5079a08 --- /dev/null +++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode_buffered.file.pass.cpp @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: no-filesystem +// UNSUPPORTED: libcpp-has-no-unicode +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// TODO PRINT Enable again +// https://reviews.llvm.org/D150044 +// https://lab.llvm.org/buildbot/#/builders/237/builds/3578 +// UNSUPPORTED: asan, hwasan, msan + +// XFAIL: availability-fp_to_chars-missing + +// The error exception has no system error string. +// XFAIL: LIBCXX-ANDROID-FIXME + +// <print> + +// void vprint_unicode_buffered(FILE* stream, string_view fmt, format_args args); + +// In the library when the stdout is redirected to a file it is no +// longer considered a terminal and the special terminal handling is no +// longer executed. There are tests in +// libcxx/test/libcxx/input.output/iostream.format/print.fun/ +// to validate that behaviour + +#include <algorithm> +#include <array> +#include <cassert> +#include <cstddef> +#include <cstdio> +#include <fstream> +#include <iterator> +#include <print> +#include <string_view> + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "print_tests.h" +#include "test_macros.h" + +scoped_test_env env; +std::string filename = env.create_file("output.txt"); + +auto test_file = []<class... Args>(std::string_view expected, std::string_view fmt, Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_unicode_buffered(file, fmt, std::make_format_args(args...)); + std::fclose(file); + + std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary}; + std::string out(std::istreambuf_iterator<char>{stream}, {}); + TEST_REQUIRE(out == expected, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = []<class... Args>([[maybe_unused]] std::string_view what, + [[maybe_unused]] std::string_view fmt, + [[maybe_unused]] Args&&... args) { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::format_error, + [&]([[maybe_unused]] const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode_buffered(file, fmt, std::make_format_args(args...))); + + fclose(file); +}; + +// Glibc fails writing to a wide stream. +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) +static void test_wide_stream() { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + int mode = std::fwide(file, 1); + assert(mode > 0); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{"failed to write formatted output"}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode_buffered(file, "hello", std::make_format_args())); +} +#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + +static void test_read_only() { + FILE* file = fopen(filename.c_str(), "r"); + assert(file); + + TEST_VALIDATE_EXCEPTION( + std::system_error, + [&]([[maybe_unused]] const std::system_error& e) { + [[maybe_unused]] std::string_view what{ + "failed to write formatted output: " TEST_IF_AIX("Broken pipe", "Operation not permitted")}; + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + std::vprint_unicode_buffered(file, "hello", std::make_format_args())); +} + +static void test_new_line() { + // Text does newline translation. + { + FILE* file = fopen(filename.c_str(), "w"); + assert(file); + + std::vprint_unicode_buffered(file, "\n", std::make_format_args()); +#ifndef _WIN32 + assert(std::ftell(file) == 1); +#else + assert(std::ftell(file) == 2); +#endif + } + // Binary no newline translation. + { + FILE* file = fopen(filename.c_str(), "wb"); + assert(file); + + std::vprint_unicode_buffered(file, "\n", std::make_format_args()); + assert(std::ftell(file) == 1); + } +} + +int main(int, char**) { + print_tests(test_file, test_exception); + +#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS) + test_wide_stream(); +#endif + test_read_only(); + test_new_line(); + + return 0; +} _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits