diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4 index eb2d2628656..d040e8d30be 100644 --- a/libstdc++-v3/acinclude.m4 +++ b/libstdc++-v3/acinclude.m4 @@ -5804,6 +5804,89 @@ AC_DEFUN([GLIBCXX_CHECK_DEBUGGING], [ AC_LANG_RESTORE ]) +dnl +dnl Check whether the dependencies for optimized std::print are available. +dnl +dnl Defines: +dnl _GLIBCXX_USE_STDIO_LOCKING if flockfile, putc_unlocked etc. are present. +dnl _GLIBCXX_USE_GLIBC_STDIO_EXT if FILE::_IO_write_ptr etc. are also present. +dnl +AC_DEFUN([GLIBCXX_CHECK_STDIO_LOCKING], [ +AC_LANG_SAVE + AC_LANG_CPLUSPLUS + + AC_MSG_CHECKING([whether flockfile and putc_unlocked are defined in ]) + AC_TRY_COMPILE([ + #include + ],[ + FILE* f = ::fopen("", ""); + ::flockfile(f); + ::putc_unlocked(' ', f); + ::funlockfile(f); + ::fclose(f); + ], [ac_stdio_locking=yes], [ac_stdio_locking=no]) + AC_MSG_RESULT($ac_stdio_locking) + + if test "$ac_stdio_locking" = yes; then + AC_DEFINE_UNQUOTED(_GLIBCXX_USE_STDIO_LOCKING, 1, + [Define if flockfile and putc_unlocked should be used for std::print.]) + + # This is not defined in POSIX, but is present in glibc, musl, and Solaris. + AC_MSG_CHECKING([whether fwrite_unlocked is defined in ]) + AC_TRY_COMPILE([ + #include + ],[ + FILE* f = ::fopen("", ""); + ::flockfile(f); + ::fwrite_unlocked("", 1, 1, f); + ::funlockfile(f); + ::fclose(f); + ], [ac_fwrite_unlocked=yes], [ac_fwrite_unlocked=no]) + AC_MSG_RESULT($ac_fwrite_unlocked) + if test "$ac_fwrite_unlocked" = yes; then + AC_DEFINE(HAVE_FWRITE_UNLOCKED, 1, + [Define if fwrite_unlocked can be used for std::print.]) + + # Check for Glibc-specific FILE members and extensions. + case "${target_os}" in + gnu* | linux* | kfreebsd*-gnu | knetbsd*-gnu) + AC_MSG_CHECKING([for FILE::_IO_write_ptr and ]) + AC_TRY_COMPILE([ + #include + #include + extern "C" { + using f1_type = int (*)(FILE*) noexcept; + using f2_type = size_t (*)(FILE*) noexcept; + } + ],[ + f1_type twritable = &::__fwritable; + f1_type tblk = &::__flbf; + f2_type pbufsize = &::__fbufsize; + FILE* f = ::fopen("", ""); + int i = ::__overflow(f, EOF); + bool writeable = ::__fwritable(f); + bool line_buffered = ::__flbf(f); + size_t bufsz = ::__fbufsize(f); + char*& pptr = f->_IO_write_ptr; + char*& epptr = f->_IO_buf_end; + ::fflush_unlocked(f); + ::fclose(f); + ], [ac_glibc_stdio=yes], [ac_glibc_stdio=no]) + AC_MSG_RESULT($ac_glibc_stdio) + if test "$ac_glibc_stdio" = yes; then + AC_DEFINE_UNQUOTED(_GLIBCXX_USE_GLIBC_STDIO_EXT, 1, + [Define if Glibc FILE internals should be used for std::print.]) + fi + ;; + *) + ;; + esac + fi + fi + + AC_LANG_RESTORE +]) + # Macros from the top-level gcc directory. m4_include([../config/gc++filt.m4]) diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in index 818117aa6cc..4cfb9ba26be 100644 --- a/libstdc++-v3/config.h.in +++ b/libstdc++-v3/config.h.in @@ -152,6 +152,9 @@ /* Define to 1 if you have the `frexpl' function. */ #undef HAVE_FREXPL +/* Define if fwrite_unlocked can be used for std::print. */ +#undef HAVE_FWRITE_UNLOCKED + /* Define if getentropy is available in . */ #undef HAVE_GETENTROPY @@ -828,6 +831,9 @@ /* Define if get_nprocs is available in . */ #undef _GLIBCXX_USE_GET_NPROCS +/* Define if Glibc FILE internals should be used for std::print. */ +#undef _GLIBCXX_USE_GLIBC_STDIO_EXT + /* Define if init_priority should be used for iostream initialization. */ #undef _GLIBCXX_USE_INIT_PRIORITY_ATTRIBUTE @@ -893,6 +899,9 @@ /* Define if sendfile is available in . */ #undef _GLIBCXX_USE_SENDFILE +/* Define if flockfile and putc_unlocked should be used for std::print. */ +#undef _GLIBCXX_USE_STDIO_LOCKING + /* Define to restrict std::__basic_file<> to stdio APIs. */ #undef _GLIBCXX_USE_STDIO_PURE diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure index 713038b390b..86ec969aaf1 100755 --- a/libstdc++-v3/configure +++ b/libstdc++-v3/configure @@ -54949,6 +54949,154 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu +# For std::print + + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether flockfile and putc_unlocked are defined in " >&5 +$as_echo_n "checking whether flockfile and putc_unlocked are defined in ... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main () +{ + + FILE* f = ::fopen("", ""); + ::flockfile(f); + ::putc_unlocked(' ', f); + ::funlockfile(f); + ::fclose(f); + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_stdio_locking=yes +else + ac_stdio_locking=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_stdio_locking" >&5 +$as_echo "$ac_stdio_locking" >&6; } + + if test "$ac_stdio_locking" = yes; then + +cat >>confdefs.h <<_ACEOF +#define _GLIBCXX_USE_STDIO_LOCKING 1 +_ACEOF + + + # This is not defined in POSIX, but is present in glibc, musl, and Solaris. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether fwrite_unlocked is defined in " >&5 +$as_echo_n "checking whether fwrite_unlocked is defined in ... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main () +{ + + FILE* f = ::fopen("", ""); + ::flockfile(f); + ::fwrite_unlocked("", 1, 1, f); + ::funlockfile(f); + ::fclose(f); + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_fwrite_unlocked=yes +else + ac_fwrite_unlocked=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_fwrite_unlocked" >&5 +$as_echo "$ac_fwrite_unlocked" >&6; } + if test "$ac_fwrite_unlocked" = yes; then + +$as_echo "#define HAVE_FWRITE_UNLOCKED 1" >>confdefs.h + + + # Check for Glibc-specific FILE members and extensions. + case "${target_os}" in + gnu* | linux* | kfreebsd*-gnu | knetbsd*-gnu) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for FILE::_IO_write_ptr and " >&5 +$as_echo_n "checking for FILE::_IO_write_ptr and ... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + extern "C" { + using f1_type = int (*)(FILE*) noexcept; + using f2_type = size_t (*)(FILE*) noexcept; + } + +int +main () +{ + + f1_type twritable = &::__fwritable; + f1_type tblk = &::__flbf; + f2_type pbufsize = &::__fbufsize; + FILE* f = ::fopen("", ""); + int i = ::__overflow(f, EOF); + bool writeable = ::__fwritable(f); + bool line_buffered = ::__flbf(f); + size_t bufsz = ::__fbufsize(f); + char*& pptr = f->_IO_write_ptr; + char*& epptr = f->_IO_buf_end; + ::fflush_unlocked(f); + ::fclose(f); + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_glibc_stdio=yes +else + ac_glibc_stdio=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_glibc_stdio" >&5 +$as_echo "$ac_glibc_stdio" >&6; } + if test "$ac_glibc_stdio" = yes; then + +cat >>confdefs.h <<_ACEOF +#define _GLIBCXX_USE_GLIBC_STDIO_EXT 1 +_ACEOF + + fi + ;; + *) + ;; + esac + fi + fi + + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + # Define documentation rules conditionally. # See if makeinfo has been installed and is modern enough diff --git a/libstdc++-v3/configure.ac b/libstdc++-v3/configure.ac index 0bf219174fe..47813eb95ca 100644 --- a/libstdc++-v3/configure.ac +++ b/libstdc++-v3/configure.ac @@ -590,6 +590,9 @@ GLIBCXX_CHECK_TEXT_ENCODING # For std::is_debugger_present GLIBCXX_CHECK_DEBUGGING +# For std::print +GLIBCXX_CHECK_STDIO_LOCKING + # Define documentation rules conditionally. # See if makeinfo has been installed and is modern enough diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h index 314b55d50bc..883b772752a 100644 --- a/libstdc++-v3/include/bits/formatfwd.h +++ b/libstdc++-v3/include/bits/formatfwd.h @@ -190,6 +190,11 @@ namespace __format }(); #endif // format_ranges +#if __glibcxx_print >= 202403L + template + constexpr bool enable_nonlocking_formatter_optimization = false; +#endif + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std #endif // __glibcxx_format diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 7c91a18c686..1c0f43e465b 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1865,7 +1865,7 @@ ftms = { ftms = { name = print; values = { - v = 202211; + v = 202403; cxxmin = 23; hosted = yes; }; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 7ba78774041..7b97accc47e 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -2083,9 +2083,9 @@ #if !defined(__cpp_lib_print) # if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED -# define __glibcxx_print 202211L +# define __glibcxx_print 202403L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_print) -# define __cpp_lib_print 202211L +# define __cpp_lib_print 202403L # endif # endif #endif /* !defined(__cpp_lib_print) */ diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 842972eed4c..1d01bc39e9c 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -2599,6 +2599,11 @@ namespace __format __format::__formatter_int<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<__format::__char _CharT> + constexpr bool enable_nonlocking_formatter_optimization<_CharT> = true; +#endif + #ifdef _GLIBCXX_USE_WCHAR_T /// Format a char value for wide character output. template<> @@ -2660,6 +2665,11 @@ namespace __format __format::__formatter_str<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<__format::__char _CharT> + constexpr bool enable_nonlocking_formatter_optimization<_CharT*> = true; +#endif + template<__format::__char _CharT> struct formatter { @@ -2685,6 +2695,12 @@ namespace __format __format::__formatter_str<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<__format::__char _CharT> + constexpr bool + enable_nonlocking_formatter_optimization = true; +#endif + template<__format::__char _CharT, size_t _Nm> struct formatter<_CharT[_Nm], _CharT> { @@ -2709,6 +2725,11 @@ namespace __format __format::__formatter_str<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<__format::__char _CharT, size_t _Nm> + constexpr bool enable_nonlocking_formatter_optimization<_CharT[_Nm]> = true; +#endif + template struct formatter, char> { @@ -2733,6 +2754,13 @@ namespace __format __format::__formatter_str _M_f; }; +#if __glibcxx_print >= 202403L + template + constexpr bool + enable_nonlocking_formatter_optimization> + = true; +#endif + #ifdef _GLIBCXX_USE_WCHAR_T template struct formatter, wchar_t> @@ -2757,6 +2785,14 @@ namespace __format private: __format::__formatter_str _M_f; }; + +#if __glibcxx_print >= 202403L + template + constexpr bool + enable_nonlocking_formatter_optimization> + = true; +#endif + #endif // USE_WCHAR_T template @@ -2783,6 +2819,13 @@ namespace __format __format::__formatter_str _M_f; }; +#if __glibcxx_print >= 202403L + template + constexpr bool + enable_nonlocking_formatter_optimization> + = true; +#endif + #ifdef _GLIBCXX_USE_WCHAR_T template struct formatter, wchar_t> @@ -2807,6 +2850,13 @@ namespace __format private: __format::__formatter_str _M_f; }; + +#if __glibcxx_print >= 202403L + template + constexpr bool + enable_nonlocking_formatter_optimization> + = true; +#endif #endif // USE_WCHAR_T /// @} @@ -2831,12 +2881,14 @@ namespace __format #endif template<> inline constexpr bool __is_formattable_integer = false; template<> inline constexpr bool __is_formattable_integer = false; + + template + concept __formattable_integer = __is_formattable_integer<_Tp>; } /// @endcond /// Format an integer. - template - requires __format::__is_formattable_integer<_Tp> + template<__format::__formattable_integer _Tp, __format::__char _CharT> struct formatter<_Tp, _CharT> { formatter() = default; @@ -2857,6 +2909,12 @@ namespace __format __format::__formatter_int<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<__format::__formattable_integer _Tp> + constexpr bool + enable_nonlocking_formatter_optimization<_Tp> = true; +#endif + #if defined __glibcxx_to_chars /// Format a floating-point value. template<__format::__formattable_float _Tp, __format::__char _CharT> @@ -2878,6 +2936,12 @@ namespace __format __format::__formatter_fp<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<__format::__formattable_float _Tp> + constexpr bool + enable_nonlocking_formatter_optimization<_Tp> = true; +#endif + #if __LDBL_MANT_DIG__ == __DBL_MANT_DIG__ // Reuse __formatter_fp::format for long double. template<__format::__char _CharT> @@ -3056,6 +3120,12 @@ namespace __format __format::__formatter_ptr<_CharT> _M_f; }; +#if __glibcxx_print >= 202403L + template<> + inline constexpr bool + enable_nonlocking_formatter_optimization = true; +#endif + template<__format::__char _CharT> struct formatter { @@ -3075,6 +3145,12 @@ namespace __format __format::__formatter_ptr<_CharT> _M_f; }; +#if __glibcxx_print >= 202403l + template<> + inline constexpr bool + enable_nonlocking_formatter_optimization = true; +#endif + template<__format::__char _CharT> struct formatter { @@ -3095,6 +3171,12 @@ namespace __format }; /// @} +#if __glibcxx_print >= 202403L + template<> + inline constexpr bool + enable_nonlocking_formatter_optimization = true; +#endif + #if defined _GLIBCXX_USE_WCHAR_T && __glibcxx_format_ranges // _GLIBCXX_RESOLVE_LIB_DEFECTS // 3944. Formatters converting sequences of char to sequences of wchar_t diff --git a/libstdc++-v3/include/std/ostream b/libstdc++-v3/include/std/ostream index 3a0a0d35df1..33872969fe0 100644 --- a/libstdc++-v3/include/std/ostream +++ b/libstdc++-v3/include/std/ostream @@ -259,9 +259,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION print(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args) { auto __fmtargs = std::make_format_args(__args...); +#if defined(_WIN32) && !defined(__CYGWIN__) if constexpr (__unicode::__literal_encoding_is_utf8()) std::vprint_unicode(__os, __fmt.get(), __fmtargs); else +#endif std::vprint_nonunicode(__os, __fmt.get(), __fmtargs); } @@ -269,10 +271,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION inline void println(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args) { - // _GLIBCXX_RESOLVE_LIB_DEFECTS - // 4088. println ignores the locale imbued in std::ostream - std::print(__os, "{}\n", std::format(__os.getloc(), __fmt, - std::forward<_Args>(__args)...)); + auto __fmtargs = std::make_format_args(__args...); + std::string __fmtn; + __fmtn.reserve(__fmt.get().size() + 1); + __fmtn = __fmt.get(); + __fmtn += '\n'; +#if defined(_WIN32) && !defined(__CYGWIN__) + if constexpr (__unicode::__literal_encoding_is_utf8()) + std::vprint_unicode(__os, __fmtn, __fmtargs); + else +#endif + std::vprint_nonunicode(__os, __fmtn, __fmtargs); } // Defined for C++26, supported as an extension to C++23. diff --git a/libstdc++-v3/include/std/print b/libstdc++-v3/include/std/print index 92dbe118fc3..6ffd9a4b1b3 100644 --- a/libstdc++-v3/include/std/print +++ b/libstdc++-v3/include/std/print @@ -53,8 +53,213 @@ namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION +namespace __format +{ +#if _GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT + // These are defined in but we don't want to include that. + extern "C" int __fwritable(FILE*) noexcept; + extern "C" int __flbf(FILE*) noexcept; + extern "C" size_t __fbufsize(FILE*) noexcept; + + // A format sink that writes directly to a Glibc FILE. + // The file is locked on construction and its buffer is accessed directly. + class _File_sink final : _Buf_sink + { + struct _File + { + explicit + _File(FILE* __f) : _M_file(__f) + { + ::flockfile(__f); + // Ensure stream is in write mode + if (!__fwritable(__f)) + { + ::funlockfile(__f); + __throw_system_error(EACCES); + } + // Allocate buffer if needed: + if (_M_write_buf().empty()) + if (::__overflow(__f, EOF) == EOF) + { + const int __err = errno; + ::funlockfile(__f); + __throw_system_error(__err); + } + } + + ~_File() { ::funlockfile(_M_file); } + + _File(_File&&) = delete; + + // A span viewing the unused portion of the stream's output buffer. + std::span + _M_write_buf() noexcept + { + return {_M_file->_IO_write_ptr, + size_t(_M_file->_IO_buf_end - _M_file->_IO_write_ptr)}; + } + + // Flush the output buffer to the file so we can write to it again. + void + _M_flush() + { + if (::fflush_unlocked(_M_file)) + __throw_system_error(errno); + } + + // Update the current position in the output buffer. + void + _M_bump(size_t __n) noexcept + { _M_file->_IO_write_ptr += __n; } + + bool + _M_line_buffered() const noexcept + { return __flbf(_M_file); } // Or: _M_file->_flags & 0x200 + + bool + _M_unbuffered() const noexcept + { return __fbufsize(_M_file) == 1; } // Or: _M_file->_flags & 0x2 + + FILE* _M_file; + } _M_file; + + bool _M_add_newline; // True for std::println, false for std::print. + + // Flush the stream's put area so it can be refilled. + void + _M_overflow() override + { + auto __s = this->_M_used(); + if (__s.data() == this->_M_buf) + { + // Characters in internal buffer need to be transferred to the FILE. + auto __n = ::fwrite_unlocked(__s.data(), 1, __s.size(), + _M_file._M_file); + if (__n != __s.size()) + __throw_system_error(errno); + this->_M_reset(this->_M_buf); + } + else + { + // Characters were written directly to the FILE's output buffer. + _M_file._M_bump(__s.size()); + _M_file._M_flush(); + this->_M_reset(_M_file._M_write_buf()); + } + } + + public: + _File_sink(FILE* __f, bool __add_newline) + : _M_file(__f), _M_add_newline(__add_newline) + { + if (!_M_file._M_unbuffered()) + // Write directly to the FILE's output buffer. + this->_M_reset(_M_file._M_write_buf()); + } + + ~_File_sink() noexcept(false) + { + auto __s = this->_M_used(); + if (__s.data() == this->_M_buf) // Unbuffered stream + { + _File_sink::_M_overflow(); + if (_M_add_newline) + ::putc_unlocked('\n', _M_file._M_file); + } + else + { + _M_file._M_bump(__s.size()); + if (_M_add_newline) + ::putc_unlocked('\n', _M_file._M_file); + else if (_M_file._M_line_buffered() && __s.size() + && (__s.back() == '\n' + || __builtin_memchr(__s.data(), '\n', __s.size()))) + _M_file._M_flush(); + } + } + + using _Sink::out; + }; +#elif _GLIBCXX_USE_STDIO_LOCKING + // A format sink that buffers output and then copies it to a stdio FILE. + // The file is locked on construction and written to using fwrite_unlocked. + class _File_sink final : _Buf_sink + { + FILE* _M_file; + bool _M_add_newline; + + // Transfer buffer contents to the FILE, so buffer can be refilled. + void + _M_overflow() override + { + auto __s = this->_M_used(); +#if _GLIBCXX_HAVE_FWRITE_UNLOCKED + auto __n = ::fwrite_unlocked(__s.data(), 1, __s.size(), _M_file); + if (__n != __s.size()) + __throw_system_error(errno); +#else + for (char __c : __s) + ::putc_unlocked(__c, _M_file); + if (::ferror(_M_file)) + __throw_system_error(errno); +#endif + this->_M_reset(this->_M_buf); + } + + public: + _File_sink(FILE* __f, bool __add_newline) noexcept + : _Buf_sink(), _M_file(__f), _M_add_newline(__add_newline) + { ::flockfile(__f); } + + ~_File_sink() noexcept(false) + { + _File_sink::_M_overflow(); + if (_M_add_newline) + ::putc_unlocked('\n', _M_file); + ::funlockfile(_M_file); + } + + using _Sink::out; + }; +#else + // A wrapper around a format sink that copies the output to a stdio FILE. + // This is not actually a _Sink itself, but it creates one to hold the + // formatted characters and then copies them to the file when finished. + class _File_sink final + { + FILE* _M_file; + _Str_sink _M_sink; + bool _M_add_newline; + + public: + _File_sink(FILE* __f, bool __add_newline) noexcept + : _M_file(__f), _M_add_newline(__add_newline) + { } + + ~_File_sink() noexcept(false) + { + string __s = std::move(_M_sink).get(); + if (_M_add_newline) + __s += '\n'; + auto __n = std::fwrite(__s.data(), 1, __s.size(), _M_file); + if (__n < __s.size()) + __throw_system_error(EIO); + } + + auto out() { return _M_sink.out(); } + }; +#endif +} // namespace __format + inline void vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args) + { + std::vformat_to(__format::_File_sink(__stream, false).out(), __fmt, __args); + } + + inline void + vprint_nonunicode_buffered(FILE* __stream, string_view __fmt, + format_args __args) { __format::_Str_sink __buf; std::vformat_to(__buf.out(), __fmt, __args); @@ -80,7 +285,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // If stream refers to a terminal, write a native Unicode string to it. if (auto __term = __open_terminal(__stream)) { - string __out = std::vformat(__fmt, __args); error_code __e; if (!std::fflush(__stream)) { @@ -95,21 +299,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX_THROW_OR_ABORT(system_error(__e, "std::vprint_unicode")); } - // Otherwise just write the string to the file as vprint_nonunicode does. + // Otherwise just write the string to the file. if (std::fwrite(__out.data(), 1, __out.size(), __stream) != __out.size()) __throw_system_error(EIO); #endif } + inline void + vprint_unicode_buffered(FILE* __stream, string_view __fmt, format_args __args) + { +#if !defined(_WIN32) || defined(__CYGWIN__) + // For most targets we don't need to do anything special to write + // Unicode to a terminal. Just use the nonunicode function. + std::vprint_nonunicode_buffered(__stream, __fmt, __args); +#else + // For Windows the locking function formats everything first anyway, + // so no formatting happens while a lock is taken. Just use that. + std::vprint_unicode(__stream, __fmt, __args); +#endif + } + template inline void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { + constexpr bool __locksafe = + (enable_nonlocking_formatter_optimization> && ...); + auto __fmtargs = std::make_format_args(__args...); +#if defined(_WIN32) && !defined(__CYGWIN__) if constexpr (__unicode::__literal_encoding_is_utf8()) - std::vprint_unicode(__stream, __fmt.get(), __fmtargs); + std::vprint_unicode_buffered(__stream, __fmt.get(), __fmtargs); else +#endif + + if constexpr (__locksafe) std::vprint_nonunicode(__stream, __fmt.get(), __fmtargs); + else + std::vprint_nonunicode_buffered(__stream, __fmt.get(), __fmtargs); } template @@ -121,8 +348,45 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION inline void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { - std::print(__stream, "{}\n", - std::format(__fmt, std::forward<_Args>(__args)...)); + constexpr bool __locksafe = + (enable_nonlocking_formatter_optimization> && ...); + + // The standard wants us to call + // print(stream, runtime_format(string(fmt.get()) + '\n'), args...) + // here, but we can avoid that string concatenation in most cases, + // and we know what that would call, so we can call that directly. + + auto __fmtargs = std::make_format_args(__args...); +#if defined(_WIN32) && !defined(__CYGWIN__) + if constexpr (__unicode::__literal_encoding_is_utf8()) + { + // We can't avoid the string concatenation here, but we can call + // vprint_unicode_buffered directly, since that's what print would do. + string __fmtn; + __fmtn.reserve(__fmt.get().size() + 1); + __fmtn = __fmt.get(); + __fmtn += '\n'; + std::vprint_unicode_buffered(__stream, __fmtn, __fmtargs); + } + else +#endif + + // For non-Windows and for non-Unicode on Windows, we know that print + // would call vprint_nonunicode or vprint_nonunicode_buffered with a + // newline appended to the format-string. Use a _File_sink that adds + // the newline automatically and write to it directly. + if constexpr (__locksafe) + std::vformat_to(__format::_File_sink(__stream, true).out(), + __fmt.get(), __fmtargs); + else + { + // Format to a string buffer first, then write the result to a + // _File_sink that adds a newline. + __format::_Str_sink __buf; + std::vformat_to(__buf.out(), __fmt.get(), __fmtargs); + string_view __s(__buf.view()); + __format::_File_sink(__stream, true).out() = __s; + } } template @@ -131,19 +395,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { std::println(stdout, __fmt, std::forward<_Args>(__args)...); } inline void - vprint_unicode(string_view __fmt, format_args __args) - { std::vprint_unicode(stdout, __fmt, __args); } + vprint_unicode_buffered(string_view __fmt, format_args __args) + { std::vprint_unicode_buffered(stdout, __fmt, __args); } inline void - vprint_nonunicode(string_view __fmt, format_args __args) - { std::vprint_nonunicode(stdout, __fmt, __args); } + vprint_nonunicode_buffered(string_view __fmt, format_args __args) + { std::vprint_nonunicode_buffered(stdout, __fmt, __args); } // Defined for C++26, supported as an extension to C++23. inline void println(FILE* __stream) { #if defined(_WIN32) && !defined(__CYGWIN__) if constexpr (__unicode::__literal_encoding_is_utf8()) - std::vprint_unicode(__stream, "\n", std::make_format_args()); + std::vprint_unicode_buffered(__stream, "\n", std::make_format_args()); else #endif if (std::putc('\n', __stream) == EOF) diff --git a/libstdc++-v3/testsuite/27_io/print/1.cc b/libstdc++-v3/testsuite/27_io/print/1.cc index 2a74e5002f4..58f1eb163df 100644 --- a/libstdc++-v3/testsuite/27_io/print/1.cc +++ b/libstdc++-v3/testsuite/27_io/print/1.cc @@ -68,15 +68,22 @@ test_print_raw() void test_vprint_nonunicode() { - std::vprint_nonunicode("{0} in \xc0 {0} out\n", + std::vprint_nonunicode_buffered("{0} in \xc0 {0} out\n", std::make_format_args("garbage")); - // { dg-output "garbage in . garbage out" } + // { dg-output "garbage in . garbage out\r?\n" } + std::vprint_nonunicode_buffered(stdout, "{0} in \xc3 {0} out\n", + std::make_format_args("junk")); + // { dg-output "junk in . junk out\r?\n" } + std::vprint_nonunicode(stdout, "{0} in \xc2 {0} out\n", + std::make_format_args("trash")); + // { dg-output "trash in . trash out\r?\n" } + } +#ifdef __cpp_exceptions void test_errors() { -#ifdef __cpp_exceptions try { std::print(stdin, "{}", "nope"); @@ -85,9 +92,47 @@ test_errors() catch (const std::system_error&) { } -#endif } +struct ThrowOnFormat +{}; + +template +struct std::formatter +{ + constexpr typename std::basic_format_parse_context::iterator + parse(const std::basic_format_parse_context& pc) const + { return pc.begin(); } + + template + typename std::basic_format_context::iterator + format(ThrowOnFormat, const std::basic_format_context&) const + { throw ThrowOnFormat{}; } +}; + +void +test_buffered() +{ + __gnu_test::scoped_file f; + FILE* strm = std::fopen(f.path.string().c_str(), "w"); + VERIFY( strm ); + try + { + std::string s = "Test"; + ThrowOnFormat tf; + std::vprint_unicode_buffered(strm, "{} {} {} {}", std::make_format_args(s, s, s, tf)); + VERIFY(false); + } + catch (ThrowOnFormat) + { } + std::fclose(strm); + + std::ifstream in(f.path); + std::string txt(std::istreambuf_iterator(in), {}); + VERIFY( txt.empty() ); +} +#endif + int main() { test_print_default(); @@ -96,5 +141,8 @@ int main() test_println_file(); test_print_raw(); test_vprint_nonunicode(); +#ifdef __cpp_exceptions test_errors(); + test_buffered(); +#endif } diff --git a/libstdc++-v3/testsuite/std/format/formatter/nonlocking.cc b/libstdc++-v3/testsuite/std/format/formatter/nonlocking.cc new file mode 100644 index 00000000000..a726e9d74ce --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/formatter/nonlocking.cc @@ -0,0 +1,59 @@ +// { dg-do compile { target c++23 } } + +#include +#include + +template +struct MyTraits : std::char_traits +{}; + +template +struct MyAlloc : std::allocator +{ + using std::allocator::allocator; +}; + +template +void testCharacters() +{ + static_assert(std::enable_nonlocking_formatter_optimization< + CharT>); + static_assert(std::enable_nonlocking_formatter_optimization< + CharT*>); + static_assert(std::enable_nonlocking_formatter_optimization< + const CharT*>); + static_assert(std::enable_nonlocking_formatter_optimization< + CharT[5]>); + + static_assert(std::enable_nonlocking_formatter_optimization< + std::basic_string>); + static_assert(std::enable_nonlocking_formatter_optimization< + std::basic_string>>); + static_assert(std::enable_nonlocking_formatter_optimization< + std::basic_string, MyAlloc>>); + + static_assert(std::enable_nonlocking_formatter_optimization< + std::basic_string_view>); + static_assert(std::enable_nonlocking_formatter_optimization< + std::basic_string_view>>); +} + +void testAll() +{ + static_assert(std::enable_nonlocking_formatter_optimization< + int>); + static_assert(std::enable_nonlocking_formatter_optimization< + float>); + static_assert(std::enable_nonlocking_formatter_optimization< + void*>); + static_assert(std::enable_nonlocking_formatter_optimization< + const void*>); + static_assert(std::enable_nonlocking_formatter_optimization< + std::nullptr_t>); + + testCharacters(); +#ifdef _GLIBCXX_USE_WCHAR_T + testCharacters(); +#endif // USE_WCHAR_T +} +